yaze 0.3.2
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
YAZE Emulator Enhancement Roadmap

Version: 1.0
Date: October 8, 2025
Status: Planning Phase
Target: Mesen2-Level Debugging + AI Integration


๐Ÿ“‹ Executive Summary

This document outlines the roadmap for evolving YAZE's SNES emulator from a basic runtime into a world-class debugging platform with AI agent integration. The goal is to achieve feature parity with Mesen2's advanced debugging capabilities while adding unique AI-powered features through the z3ed CLI system.

Core Objectives

  1. Advanced Debugger - Breakpoints, watchpoints, memory inspection, trace logging
  2. Performance Optimization - Cycle-accurate timing, dynarec, frame pacing
  3. Audio System Fix - SDL2 audio output currently broken, needs investigation
  4. AI Integration - z3ed agent can read/write emulator state, automate testing
  5. SPC700 Debugger - Full audio CPU debugging with APU state inspection

๐ŸŽฏ Current State Analysis

What Works โœ…

  • CPU Emulation: 65816 core functional, runs games
  • PPU Rendering: Display works, texture updates to SDL2
  • ROM Loading: Can load and execute SNES ROMs
  • Basic Controls: Start/stop/pause/reset functionality
  • Memory Access: Read/write to CPU memory space
  • Renderer Integration: Now using IRenderer interface (SDL3-ready!)
  • Stability: Emulator pauses during window resize (macOS protection)

What's Broken โŒ

  • Audio Output: SDL2 audio device initialized but no sound plays
  • SPC700 Debugging: No inspection tools for audio CPU
  • Performance: Not cycle-accurate, timing issues
  • Debugging Tools: Minimal breakpoint support, no watchpoints
  • Memory Viewer: Basic hex view, no structured inspection
  • Trace Logging: No execution tracing capability

What's Missing ๐Ÿšง

  • Advanced Breakpoints: Conditional, access-based, CPU/SPC700
  • Memory Watchpoints: Track reads/writes to specific addresses
  • Disassembly View: Real-time code annotation
  • Performance Profiling: Hotspot analysis, cycle counting
  • Event Viewer: Track NMI, IRQ, DMA events
  • PPU Inspector: VRAM, OAM, palette debugging
  • APU Inspector: DSP state, sample buffer, channel visualization
  • AI Integration: z3ed agent can't access emulator yet

๐Ÿ”ง Phase 1: Audio System Fix (Priority: CRITICAL)

Problem Analysis

Current State:

// controller.cc:31-33
editor_manager_.emulator().set_audio_buffer(window_.audio_buffer_.get());
editor_manager_.emulator().set_audio_device_id(window_.audio_device_);
// window.cc:114-130
window.audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
SDL_PauseAudioDevice(window.audio_device_, 0); // Unpause

The Issue: Audio device is initialized and unpaused, but SDL_QueueAudio() in emulator isn't producing sound.

Investigation Steps

  1. Verify Audio Device State
    // Add to Emulator::Run()
    if (frame_count_ % 60 == 0) { // Every second
    uint32_t queued = SDL_GetQueuedAudioSize(audio_device_);
    SDL_AudioStatus status = SDL_GetAudioDeviceStatus(audio_device_);
    printf("[AUDIO] Queued: %u bytes, Status: %d (1=playing, 2=paused)\n",
    queued, status);
    }
  2. Check SPC700 Sample Generation
    // Verify snes_.SetSamples() is producing valid data
    snes_.SetSamples(audio_buffer_, wanted_samples_);
    // Debug output
    int16_t* samples = audio_buffer_;
    bool has_audio = false;
    for (int i = 0; i < wanted_samples_ * 2; i++) {
    if (samples[i] != 0) {
    has_audio = true;
    break;
    }
    }
    if (!has_audio && frame_count_ % 60 == 0) {
    printf("[AUDIO] Warning: All samples are zero!\n");
    }
  3. Validate Audio Format Compatibility
    // window.cc - Check if requested format matches obtained format
    SDL_AudioSpec want, have;
    // ... (existing code)
    window.audio_device_ = SDL_OpenAudioDevice(NULL, 0, &want, &have, 0);
    printf("[AUDIO] Requested: %dHz, %d channels, format=%d\n",
    want.freq, want.channels, want.format);
    printf("[AUDIO] Obtained: %dHz, %d channels, format=%d\n",
    have.freq, have.channels, have.format);
    if (have.freq != want.freq || have.channels != want.channels) {
    LOG_ERROR("Audio", "Audio spec mismatch - may need resampling");
    }
    #define LOG_ERROR(category, format,...)
    Definition log.h:110

Likely Fixes

Fix 1: Audio Device Paused State

// The audio device might be re-pausing itself
// Try forcing unpause in the emulator loop
if (frame_count_ % 60 == 0) {
SDL_PauseAudioDevice(audio_device_, 0); // Ensure unpaused
}

Fix 2: Sample Format Conversion

// SPC700 might be outputting wrong format
// Ensure AUDIO_S16 (signed 16-bit) matches emulator output
void Snes::SetSamples(int16_t* buffer, int count) {
// Verify apu_.GetSamples() returns int16_t, not float or uint16_t
apu_.GetSamples(buffer, count);
// Debug: Check for clipping or DC offset
for (int i = 0; i < count * 2; i++) {
if (buffer[i] < -32768 || buffer[i] > 32767) {
printf("[AUDIO] Sample %d out of range: %d\n", i, buffer[i]);
}
}
}

Fix 3: Buffer Size Mismatch

// Ensure buffer allocation matches usage
// window.cc:128
window.audio_buffer_ = std::make_shared<int16_t>(audio_frequency / 50 * 4);
// This allocates: 48000 / 50 * 4 = 3840 int16_t samples
// Emulator uses: wanted_samples_ * 4 bytes = (48000/60) * 4 = 3200 bytes
// = 1600 int16_t samples (800 per channel)
// MISMATCH! Should be:
window.audio_buffer_ = std::make_shared<int16_t>(audio_frequency / 50 * 2); // Stereo

Quick Win Actions

File: window.cc Line: 128 Change:

// Before:
window.audio_buffer_ = std::make_shared<int16_t>(audio_frequency / 50 * 4);
// After:
// Allocate for stereo at 60Hz (worst case)
// 48000Hz / 60 FPS = 800 samples/frame * 2 channels = 1600 int16_t
window.audio_buffer_ = std::make_shared<int16_t>((audio_frequency / 50) * 2);

File: emulator.cc
After Line: 216 Add:

// Debug audio output
if (frame_count_ % 300 == 0) { // Every 5 seconds
uint32_t queued = SDL_GetQueuedAudioSize(audio_device_);
SDL_AudioStatus status = SDL_GetAudioDeviceStatus(audio_device_);
printf("[AUDIO] Status=%d, Queued=%u, WantedSamples=%d\n",
status, queued, wanted_samples_);
}

Estimated Fix Time: 2-4 hours


๐Ÿ› Phase 2: Advanced Debugger (Mesen2 Feature Parity)

Feature Comparison: YAZE vs Mesen2

Feature Mesen2 YAZE Current YAZE Target
CPU Breakpoints โœ… Execute/Read/Write โš ๏ธ Basic Execute โœ… Full Support
Memory Watchpoints โœ… Conditional โŒ None โœ… Conditional
Disassembly View โœ… Live Annotated โŒ Static โœ… Live + Labels
Memory Viewer โœ… Multi-region โš ๏ธ Basic Hex โœ… Structured
Trace Logger โœ… CPU/SPC/DMA โŒ None โœ… All Channels
Event Viewer โœ… IRQ/NMI/DMA โŒ None โœ… Full Timeline
Performance โœ… Cycle Accurate โŒ Approximate โœ… Cycle Accurate
Save States โš ๏ธ Limited โš ๏ธ Basic โœ… Full State
PPU Debugger โœ… Layer Viewer โŒ None โœ… VRAM Inspector
APU Debugger โœ… DSP Viewer โŒ None โœ… Channel Mixer
Scripting โœ… Lua โŒ None โœ… z3ed + AI!

2.1 Breakpoint System

Architecture:

// src/app/emu/debug/breakpoint_manager.h
class BreakpointManager {
public:
enum class Type {
EXECUTE, // Break when PC reaches address
READ, // Break when address is read
WRITE, // Break when address is written
ACCESS, // Break on read OR write
CONDITIONAL // Break when condition is true
};
struct Breakpoint {
uint32_t address;
Type type;
bool enabled;
std::string condition; // Lua expression or simple comparison
uint32_t hit_count;
std::function<bool()> callback; // Optional custom logic
};
uint32_t AddBreakpoint(uint32_t address, Type type,
const std::string& condition = "");
void RemoveBreakpoint(uint32_t id);
bool ShouldBreak(uint32_t address, Type access_type);
std::vector<Breakpoint> ListBreakpoints();
private:
std::unordered_map<uint32_t, Breakpoint> breakpoints_;
uint32_t next_id_ = 1;
};

CPU Integration:

// src/app/emu/cpu/cpu.cc
void CPU::RunOpcode() {
// Check execute breakpoint BEFORE running
if (breakpoint_mgr_->ShouldBreak(PC, BreakpointType::EXECUTE)) {
emulator_->OnBreakpointHit(PC, BreakpointType::EXECUTE);
return; // Pause execution
}
// Run instruction...
ExecuteInstruction();
// Memory access breakpoints handled in Read()/Write()
}
uint8_t CPU::Read(uint32_t address) {
if (breakpoint_mgr_->ShouldBreak(address, BreakpointType::READ)) {
emulator_->OnBreakpointHit(address, BreakpointType::READ);
}
return memory_->Read(address);
}

z3ed Integration:

# CLI commands
z3ed emu breakpoint add --address 0x00FFD9 --type execute
z3ed emu breakpoint add --address 0x7E0010 --type write --condition "value > 100"
z3ed emu breakpoint list
z3ed emu breakpoint remove --id 1
# AI agent can use these
"Set a breakpoint at the Link damage handler"
โ†’ Agent finds damage code address โ†’ z3ed emu breakpoint add

Estimated Effort: 8-12 hours


2.2 Memory Watchpoints

Features:

  • Track specific memory regions
  • Log all accesses with stack traces
  • Detect buffer overflows
  • Find data corruption sources

Implementation:

// src/app/emu/debug/watchpoint_manager.h
class WatchpointManager {
public:
struct Watchpoint {
uint32_t start_address;
uint32_t end_address;
bool track_reads;
bool track_writes;
std::vector<AccessLog> history;
};
struct AccessLog {
uint32_t pc; // Where the access happened
uint32_t address; // What address was accessed
uint8_t old_value; // Value before write
uint8_t new_value; // Value after write
bool is_write;
uint64_t cycle_count;
};
void AddWatchpoint(uint32_t start, uint32_t end, bool reads, bool writes);
void OnMemoryAccess(uint32_t pc, uint32_t address, bool is_write,
uint8_t old_val, uint8_t new_val);
std::vector<AccessLog> GetHistory(uint32_t address, int max_entries = 100);
};

Use Cases:

  • Find where Link's HP is being modified
  • Track item collection bugs
  • Debug event flag corruption
  • Detect unintended memory writes

z3ed Commands:

z3ed emu watch add --start 0x7E0000 --end 0x7E1FFF --reads --writes
z3ed emu watch history --address 0x7E0010
z3ed emu watch export --format csv

Estimated Effort: 6-8 hours


2.3 Live Disassembly Viewer

Mesen2 Inspiration:

  • Scrollable code view with current PC highlighted
  • Labels from ROM labels file
  • Inline comments
  • Jump target visualization
  • Hot code highlighting (most-executed instructions)

Architecture:

// src/app/emu/debug/disassembly_viewer.h (already exists!)
// Enhance existing viewer
class DisassemblyViewer {
public:
struct DisassembledInstruction {
uint32_t address;
std::string mnemonic;
std::string operands;
std::string comment;
uint32_t execution_count; // NEW: Hotspot tracking
bool is_breakpoint;
bool is_current_pc;
};
void Update(CPU& cpu);
void RenderWindow();
void JumpToAddress(uint32_t address);
void ToggleBreakpoint(uint32_t address);
// NEW: Hotspot profiling
void EnableProfiling(bool enable);
std::vector<uint32_t> GetHotspots(int top_n = 10);
private:
std::unordered_map<uint32_t, uint32_t> execution_counts_;
std::shared_ptr<ResourceLabel> labels_; // From ROM
};

ImGui Integration:

void Emulator::RenderDisassemblyWindow() {
if (ImGui::Begin("Disassembly", &show_disassembly_)) {
// Scrollable list
ImGui::BeginChild("DisasmScroll");
// Show ยฑ50 instructions around PC
uint32_t pc = snes_.cpu().PC;
for (int offset = -50; offset <= 50; offset++) {
auto instr = disassembly_viewer_.GetInstruction(pc + offset);
// Highlight current PC
if (offset == 0) {
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,1,0,1));
}
// Show execution count for hotspots
if (instr.execution_count > 1000) {
ImGui::TextColored(ImVec4(1,0.5,0,1), "๐Ÿ”ฅ"); // Hot code
}
ImGui::Text("%06X %s %s ; %s",
instr.address, instr.mnemonic.c_str(),
instr.operands.c_str(), instr.comment.c_str());
if (offset == 0) ImGui::PopStyleColor();
// Click to toggle breakpoint
if (ImGui::IsItemClicked()) {
disassembly_viewer_.ToggleBreakpoint(instr.address);
}
}
ImGui::EndChild();
}
ImGui::End();
}

Estimated Effort: 10-12 hours


2.4 Enhanced Memory Viewer

Multi-Region Support:

enum class MemoryRegion {
WRAM, // 0x7E0000-0x7FFFFF (128KB)
SRAM, // Cartridge RAM
VRAM, // PPU video RAM
CGRAM, // PPU palette RAM
OAM, // PPU sprite RAM
ARAM, // SPC700 audio RAM
ROM, // Cartridge ROM
REGISTERS // Hardware registers
};

Structured Views:

class MemoryViewer {
public:
void RenderHexView(MemoryRegion region);
void RenderStructView(uint32_t address, const std::string& struct_name);
void RenderDiffView(uint32_t address, const uint8_t* reference);
// NEW: ROM label integration
void SetLabels(std::shared_ptr<ResourceLabel> labels);
std::string GetLabelForAddress(uint32_t address);
// NEW: Goto functionality
void GotoAddress(uint32_t address);
void GotoLabel(const std::string& label);
};

ImGui Layout:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Memory Viewer โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Region: [WRAM โ–ผ] | Goto: [0x7E0010] [Find] โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Addr +0 +1 +2 +3 +4 +5 +6 +7 ASCII โ”‚
โ”‚ 7E0000 00 05 3C 00 00 00 00 00 ..<..... โ”‚ โ† Link's HP
โ”‚ 7E0008 1F 00 00 00 00 00 00 00 ........ โ”‚
โ”‚ ... โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Watchpoints: [Add] [Clear All] โ”‚
โ”‚ โ€ข 0x7E0000-0x7E0010 (R/W) - Link stats โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Estimated Effort: 6-8 hours


๐Ÿš€ Phase 3: Performance Optimizations

3.1 Cycle-Accurate Timing

Current Issue: Emulator runs on frame timing, not cycle timing

Mesen2 Approach:

class CPU {
void RunCycle() {
if (cycles_until_next_instruction_ == 0) {
ExecuteInstruction(); // Sets cycles_until_next_instruction_
} else {
cycles_until_next_instruction_--;
}
// Other components run per-cycle
ppu_.RunCycle();
apu_.RunCycle();
dma_.RunCycle();
}
};
// Emulator loop
void Emulator::RunFrame() {
const int cycles_per_frame = snes_.memory().pal_timing() ? 1538400 : 1789773;
for (int i = 0; i < cycles_per_frame; i++) {
snes_.RunCycle(); // ONE cycle at a time
}
}

Benefits:

  • Accurate mid-scanline effects
  • Proper DMA timing
  • Correct PPU rendering edge cases
  • Deterministic emulation

Estimated Effort: 20-30 hours (major refactor)


3.2 Dynamic Recompilation (Dynarec)

Why: Cycle-accurate interpretation is slow (~30 FPS). Dynarec can hit 60 FPS.

Strategy:

class Dynarec {
// Compile frequently-executed code blocks to native ARM/x64
void* CompileBlock(uint32_t start_pc);
void InvalidateBlock(uint32_t address); // When code changes
// Cache compiled blocks
std::unordered_map<uint32_t, void*> code_cache_;
};
void CPU::RunOpcode() {
if (dynarec_enabled_) {
// Check if block is compiled
if (auto* block = dynarec_.GetBlock(PC)) {
return ((BlockFunc)block)(); // Execute native code
}
}
// Fallback to interpreter
ExecuteInstruction();
}

Complexity: Very high - requires assembly code generation

Alternative: Use existing dynarec library like bsnes-jit

Estimated Effort: 40-60 hours (or use library: 10 hours)


3.3 Frame Pacing Improvements

Current Issue: SDL_Delay(1) is too coarse

Better Approach:

void Emulator::RunFrame() {
auto frame_start = std::chrono::high_resolution_clock::now();
// Run SNES frame
snes_.RunFrame();
// Calculate how long to wait
auto frame_end = std::chrono::high_resolution_clock::now();
auto frame_duration = std::chrono::duration_cast<std::chrono::microseconds>(
frame_end - frame_start);
auto target_duration = std::chrono::microseconds(
static_cast<int64_t>(wanted_frames_ * 1'000'000));
if (frame_duration < target_duration) {
auto sleep_time = target_duration - frame_duration;
std::this_thread::sleep_for(sleep_time); // Precise sleep
}
}

Estimated Effort: 2-3 hours


๐ŸŽฎ Phase 4: SPC700 Audio CPU Debugger

4.1 APU Inspector Window

Layout:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ APU Debugger โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ SPC700 CPU State: โ”‚
โ”‚ PC: 0x1234 A: 0x00 X: 0x05 Y: 0xFF PSW: 0x02 โ”‚
โ”‚ SP: 0xEF Cycles: 12,345,678 โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ DSP Registers: [Channel 0โ–ผ] โ”‚
โ”‚ VOL_L: 127 VOL_R: 127 PITCH: 2048 โ”‚
โ”‚ ADSR: 0xBE7F GAIN: 0x7F ENVX: 45 OUTX: 78 โ”‚
โ”‚ โ”‚
โ”‚ Waveform: [โ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘โ–’โ–’โ–’โ–’โ–ˆโ–ˆโ–ˆโ–ˆโ–“โ–“โ–“โ–“โ–‘โ–‘โ–‘โ–‘] (live) โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Audio RAM (64KB): โ”‚
โ”‚ 0000 BRR BRR BRR BRR ... (sample data) โ”‚
โ”‚ ... โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Channel Mixer: โ”‚
โ”‚ 0: โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘ (75%) 1: โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ (0%) โ”‚
โ”‚ 2: โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ (45%) 3: โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ (0%) โ”‚
โ”‚ ... โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Implementation:

// src/app/emu/debug/apu_inspector.h
class ApuInspector {
public:
void RenderWindow(Snes& snes);
void RenderChannelMixer();
void RenderWaveform(int channel);
void RenderDspRegisters();
void RenderAudioRam();
// Sample buffer visualization
void UpdateWaveformData(const int16_t* samples, int count);
private:
std::array<std::vector<float>, 8> channel_waveforms_;
int selected_channel_ = 0;
};

z3ed Commands:

z3ed emu apu status
z3ed emu apu channel --id 0
z3ed emu apu dump-ram --output audio_ram.bin
z3ed emu apu export-samples --channel 0 --format wav

Estimated Effort: 12-15 hours


4.2 Audio Sample Export

Feature: Export audio samples to WAV for analysis

class AudioExporter {
public:
void StartRecording(int channel = -1); // -1 = all channels
void StopRecording();
void ExportToWav(const std::string& filename);
private:
std::vector<int16_t> recorded_samples_;
bool recording_ = false;
};

Use Cases:

  • Debug why sound effects aren't playing
  • Export game music for analysis
  • Compare with real SNES hardware recordings

Estimated Effort: 4-6 hours


๐Ÿค– Phase 5: z3ed AI Agent Integration

5.1 Emulator State Access

Add to ConversationalAgentService:

// src/cli/service/agent/conversational_agent_service.h
class ConversationalAgentService {
public:
// NEW: Emulator access
void SetEmulator(emu::Emulator* emulator);
// Tool: Get emulator state
std::string HandleEmulatorState();
std::string HandleCpuState();
std::string HandleMemoryRead(uint32_t address, int count);
std::string HandleMemoryWrite(uint32_t address, const std::vector<uint8_t>& data);
private:
emu::Emulator* emulator_ = nullptr;
};

z3ed Tool Schema:

{
"name": "emulator-read-memory",
"description": "Read emulator memory at a specific address",
"parameters": {
"address": {"type": "integer", "description": "Memory address (e.g., 0x7E0010)"},
"count": {"type": "integer", "description": "Number of bytes to read"},
"region": {"type": "string", "enum": ["wram", "sram", "rom", "aram"]}
}
}

Example AI Queries:

User: "What is Link's current HP?"
Agent: [calls emulator-read-memory address=0x7E0000 count=1]
โ†’ Response: "Link has 6 hearts (0x60 = 96 health points)"
User: "Set Link to full health"
Agent: [calls emulator-write-memory address=0x7E0000 data=[0xA0]]
โ†’ Response: "Link's HP set to 160 (full health)"
User: "Where is the game stuck?"
Agent: [calls emulator-cpu-state]
โ†’ Response: "PC=$00:8234 - Infinite loop in NMI handler"

5.2 Automated Test Generation

Use Case: AI generates emulator tests from natural language

Example Flow:

z3ed agent test-scenario --prompt "Test that Link takes damage from enemies"
# AI generates:
{
"steps": [
{"action": "load-state", "file": "link_at_full_hp.sfc"},
{"action": "run-frames", "count": 60},
{"action": "assert-memory", "address": "0x7E0000", "value": "0xA0"},
{"action": "move-link", "direction": "right", "frames": 30},
{"action": "assert-memory-decreased", "address": "0x7E0000"},
{"action": "screenshot", "name": "link_damaged.png"}
]
}

Implementation:

// src/cli/commands/agent/test_scenario_runner.h
class TestScenarioRunner {
public:
struct TestStep {
std::string action;
absl::flat_hash_map<std::string, std::string> params;
};
absl::Status RunScenario(const std::vector<TestStep>& steps);
private:
void ExecuteLoadState(const std::string& file);
void ExecuteRunFrames(int count);
void ExecuteAssertMemory(uint32_t address, uint8_t expected);
void ExecuteMoveLink(const std::string& direction, int frames);
void ExecuteScreenshot(const std::string& filename);
};

Estimated Effort: 8-10 hours


5.3 Memory Map Learning

Feature: AI learns ROM's memory layout from debugging sessions

Architecture:

// Extends existing learn command
z3ed agent learn --memory-map "0x7E0000" --label "link_hp" --type "uint8"
z3ed agent learn --memory-map "0x7E0010" --label "link_x_pos" --type "uint16"
// AI can then use this knowledge
User: "What is Link's position?"
Agent: [checks learned memory map]
[calls emulator-read-memory address=0x7E0010 count=2 type=uint16]

Storage:

// ~/.yaze/agent/memory_maps/zelda3.json
{
"rom_hash": "abc123...",
"symbols": {
"0x7E0000": {"name": "link_hp", "type": "uint8", "description": "Link's health"},
"0x7E0010": {"name": "link_x_pos", "type": "uint16", "description": "Link X coordinate"},
"0x7E0012": {"name": "link_y_pos", "type": "uint16", "description": "Link Y coordinate"}
}
}

Estimated Effort: 6-8 hours


๐Ÿ“Š Phase 6: Performance Profiling

6.1 Cycle Counter & Profiler

Mesen2 Feature: Shows which code is hot, helps optimize hacks

Implementation:

class PerformanceProfiler {
public:
struct FunctionProfile {
uint32_t start_address;
uint32_t end_address;
std::string name;
uint64_t total_cycles;
uint32_t call_count;
float percentage;
};
void StartProfiling();
void StopProfiling();
std::vector<FunctionProfile> GetHotFunctions(int top_n = 20);
void ExportFlameGraph(const std::string& filename);
private:
std::unordered_map<uint32_t, uint64_t> address_cycle_counts_;
};

ImGui Visualization:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Performance Profiler โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ Function Cycles Calls % โ”‚
โ”‚ NMI Handler 2,456,789 1,234 15.2% โ”‚
โ”‚ Link Update 1,987,654 3,600 12.3% โ”‚
โ”‚ PPU Transfer 1,234,567 890 7.6% โ”‚
โ”‚ ... โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [Start Profiling] [Stop] [Export Flame Graph] โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

z3ed Commands:

z3ed emu profile start
# ... run game for a bit ...
z3ed emu profile stop
z3ed emu profile report --top 20
z3ed emu profile export --format flamegraph --output profile.svg

Estimated Effort: 10-12 hours


6.2 Frame Time Analysis

Track frame timing issues:

class FrameTimeAnalyzer {
struct FrameStats {
float cpu_time_ms;
float ppu_time_ms;
float apu_time_ms;
float total_time_ms;
int dropped_frames;
};
void RecordFrame();
FrameStats GetAverageStats(int last_n_frames = 60);
std::vector<float> GetFrameTimeGraph(int frames = 300); // 5 seconds
};

Visualization: Real-time graph showing frame time spikes

Estimated Effort: 4-6 hours


๐ŸŽฏ Phase 7: Event System & Timeline

7.1 Event Logger

Mesen2 Feature: Timeline view of all hardware events

Events to Track:

  • NMI (V-Blank)
  • IRQ (H-Blank, Timer)
  • DMA transfers
  • HDMA activations
  • PPU mode changes
  • Audio sample playback starts

Implementation:

class EventLogger {
public:
enum class EventType {
NMI, IRQ, DMA, HDMA, PPU_MODE_CHANGE, APU_SAMPLE_START
};
struct Event {
EventType type;
uint64_t cycle;
uint32_t pc; // Where CPU was when event occurred
std::string details;
};
void LogEvent(EventType type, const std::string& details);
std::vector<Event> GetEvents(uint64_t start_cycle, uint64_t end_cycle);
void Clear();
private:
std::deque<Event> event_history_; // Keep last 10,000 events
};

Visualization:

Timeline (last 5 frames):
Frame 1: [NMI]โ”€โ”€โ”€โ”€[DMA]โ”€โ”€[HDMA]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Frame 2: [NMI]โ”€โ”€โ”€โ”€[DMA]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€[IRQ]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
Frame 3: [NMI]โ”€โ”€โ”€โ”€[DMA]โ”€โ”€[HDMA]โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
^ ^ ^
16.67ms 18ms 20ms (timing shown)

Estimated Effort: 8-10 hours


๐Ÿง  Phase 8: AI-Powered Debugging

8.1 Intelligent Crash Analysis

Feature: AI analyzes emulator state when game crashes/freezes

z3ed agent debug-crash --state latest_crash.state
# AI examines:
# - CPU registers and flags
# - Stack contents
# - Recent code execution
# - Memory watchpoint history
# - Event timeline
# AI response:
"The game crashed because:
1. Infinite loop detected at $00:8234
2. This is the NMI handler
3. It's waiting for PPU register 0x2137 to change
4. The value hasn't changed in 10,000 cycles
5. Likely cause: PPU is in wrong mode (Mode 0 instead of Mode 1)
Suggested fix:
- Check PPU mode initialization at game start
- Verify NMI handler only runs when PPU is ready"

Estimated Effort: 15-20 hours


8.2 Automated Bug Reproduction

Feature: AI creates minimal test case from bug description

z3ed agent repro --prompt "Link takes damage when he shouldn't"
# AI generates reproduction steps:
{
"steps": [
"load_state: link_full_hp.sfc",
"move: right, 50 frames",
"assert: no damage taken",
"expected: HP stays at 0xA0",
"actual: HP decreased to 0x90",
"analysis: Enemy collision box too large"
]
}

Estimated Effort: 12-15 hours


๐Ÿ—บ๏ธ Implementation Roadmap

Sprint 1: Audio Fix (Week 1)

Priority: CRITICAL
Time: 4 hours
Deliverables:

  • โœ… Investigate audio buffer size mismatch
  • โœ… Add audio debug logging
  • โœ… Fix SDL2 audio output
  • โœ… Verify audio plays correctly

Sprint 2: Basic Debugger (Weeks 2-3)

Priority: HIGH
Time: 20 hours
Deliverables:

  • โœ… Breakpoint manager with execute/read/write
  • โœ… Enhanced disassembly viewer with hotspots
  • โœ… Improved memory viewer with regions
  • โœ… z3ed CLI commands for debugging

Sprint 3: SPC700 Debugger (Week 4)

Priority: MEDIUM
Time: 15 hours
Deliverables:

  • โœ… APU inspector window
  • โœ… Channel waveform visualization
  • โœ… Audio RAM viewer
  • โœ… Sample export to WAV

Sprint 4: AI Integration (Weeks 5-6)

Priority: MEDIUM
Time: 25 hours
Deliverables:

  • โœ… Emulator state tools for z3ed agent
  • โœ… Memory map learning system
  • โœ… Automated test scenario generation
  • โœ… Crash analysis AI

Sprint 5: Performance (Weeks 7-8)

Priority: LOW (Future)
Time: 40+ hours
Deliverables:

  • โœ… Cycle-accurate timing
  • โœ… Dynamic recompilation
  • โœ… Performance profiler
  • โœ… Frame pacing improvements

๐Ÿ”ฌ Technical Deep Dives

Audio System Architecture (SDL2)

Current Flow:

SPC700 โ†’ APU::GetSamples() โ†’ Snes::SetSamples() โ†’ audio_buffer_
โ†’ SDL_QueueAudio() โ†’ SDL Audio Device โ†’ System Audio

Debug Points:

// 1. Check SPC700 output
void APU::GetSamples(int16_t* buffer, int count) {
// Are samples being generated?
LOG_IF_ZERO_SAMPLES(buffer, count);
}
// 2. Check buffer handoff
void Snes::SetSamples(int16_t* buffer, int count) {
apu_.GetSamples(buffer, count);
// Are samples copied correctly?
VERIFY_BUFFER_NOT_SILENT(buffer, count);
}
// 3. Check SDL queue
if (SDL_QueueAudio(device, buffer, size) < 0) {
LOG_ERROR("SDL_QueueAudio failed: %s", SDL_GetError());
}
// 4. Check device status
if (SDL_GetAudioDeviceStatus(device) != SDL_AUDIO_PLAYING) {
LOG_ERROR("Audio device not playing!");
}

Common Issues:

  1. Buffer size mismatch - Fixed in Phase 1
  2. Format mismatch - SPC700 outputs float, SDL wants int16
  3. Device paused - SDL_PauseAudioDevice() called somewhere
  4. No APU timing - SPC700 not running or too slow

Memory Regions Reference

Region Address Range Size Description
WRAM 0x7E0000-0x7FFFFF 128KB Work RAM (game state)
SRAM 0x700000-0x77FFFF Variable Save RAM (battery)
ROM 0x000000-0x3FFFFF Up to 6MB Cartridge ROM
VRAM PPU Internal 64KB Video RAM (tiles, maps)
CGRAM PPU Internal 512B Palette RAM (colors)
OAM PPU Internal 544B Sprite RAM (objects)
ARAM SPC700 Internal 64KB Audio RAM (samples)

Access Patterns:

// CPU โ†’ WRAM (direct)
uint8_t value = cpu.Read(0x7E0010);
// CPU โ†’ VRAM (through PPU registers)
cpu.Write(0x2118, low_byte); // VRAM write
cpu.Write(0x2119, high_byte);
// CPU โ†’ ARAM (through APU registers)
cpu.Write(0x2140, data); // APU I/O port 0

๐ŸŽฎ Phase 9: Advanced Features (Mesen2 Parity)

9.1 Rewind Feature

User Experience: Hold button to rewind gameplay

class RewindManager {
void RecordFrame(); // Save state every frame
void Rewind(int frames);
// Circular buffer (last 10 seconds = 600 frames)
std::deque<SaveState> frame_history_;
static constexpr int kMaxFrames = 600;
};

Memory Impact: ~600 * 100KB = 60MB (acceptable)

Estimated Effort: 6-8 hours


9.2 TAS (Tool-Assisted Speedrun) Input Recording

Feature: Record and replay input sequences

class InputRecorder {
struct InputFrame {
uint16_t buttons; // SNES controller state
uint64_t frame_number;
};
void StartRecording();
void StopRecording();
void SaveMovie(const std::string& filename);
void PlayMovie(const std::string& filename);
std::vector<InputFrame> recorded_inputs_;
};

File Format (JSON):

{
"rom_hash": "abc123...",
"frames": [
{"frame": 0, "buttons": 0x0000},
{"frame": 60, "buttons": 0x0080}, // A button pressed
{"frame": 61, "buttons": 0x0000}
]
}

z3ed Integration:

z3ed emu record start
# ... play game ...
z3ed emu record stop --output my_gameplay.json
z3ed emu replay --input my_gameplay.json --verify
# AI can generate TAS inputs!
z3ed agent tas --prompt "Beat the first dungeon as fast as possible"

Estimated Effort: 8-10 hours


9.3 Comparison Mode

Feature: Run two emulator instances side-by-side

Use Case: Compare vanilla vs hacked ROM, or before/after AI changes

class ComparisonEmulator {
Emulator emu_a_;
Emulator emu_b_;
void RunBothFrames();
void RenderSideBySide();
void HighlightDifferences();
};

Visualization:

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ Vanilla ROM โ”‚ Hacked ROM โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ [Game Screen A] โ”‚ [Game Screen B] โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ HP: 6 โคโคโค โ”‚ HP: 12 โคโคโคโคโคโค โ”‚ โ† Difference
โ”‚ Rupees: 50 โ”‚ Rupees: 50 โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
Memory Diff: 147 bytes different

Estimated Effort: 12-15 hours


๐Ÿ› ๏ธ Optimization Summary

Quick Wins (< 1 week)

  1. Fix audio output - 4 hours
  2. Add CPU breakpoints - 6 hours
  3. Enhanced memory viewer - 6 hours
  4. Frame pacing - 3 hours

Medium Term (1-2 months)

  1. Live disassembly - 10 hours
  2. APU debugger - 15 hours
  3. Event logger - 8 hours
  4. AI emulator tools - 25 hours

Long Term (3-6 months)

  1. Cycle accuracy - 30 hours
  2. Dynarec - 60 hours
  3. TAS recording - 10 hours
  4. Comparison mode - 15 hours

๐Ÿค– z3ed Agent Emulator Tools

New Tool Categories

Emulator Control:

{
"name": "emulator-control",
"actions": ["start", "stop", "pause", "reset", "step"],
"description": "Control emulator execution"
}

Memory Tools:

{
"name": "emulator-read-memory",
"parameters": {
"address": "hex string (e.g., '0x7E0010')",
"count": "number of bytes",
"region": "wram|sram|rom|vram|aram"
}
},
{
"name": "emulator-write-memory",
"parameters": {
"address": "hex string",
"data": "array of bytes"
}
}

State Tools:

{
"name": "emulator-cpu-state",
"returns": {
"pc": "Program Counter",
"a": "Accumulator",
"x": "X Register",
"y": "Y Register",
"sp": "Stack Pointer",
"flags": "Processor flags"
}
},
{
"name": "emulator-save-state",
"parameters": {"filename": "string"}
},
{
"name": "emulator-load-state",
"parameters": {"filename": "string"}
}

Debug Tools:

{
"name": "emulator-add-breakpoint",
"parameters": {
"address": "hex string",
"type": "execute|read|write|access",
"condition": "optional expression"
}
},
{
"name": "emulator-get-hotspots",
"parameters": {"top_n": "integer"},
"returns": "array of most-executed code addresses"
}

Example AI Conversations

Debugging:

User: "Why is Link's HP decreasing randomly?"
Agent: Let me investigate...
[calls emulator-read-memory address=0x7E0000 count=1]
โ†’ Current HP: 0x60 (6 hearts)
[calls emulator-add-breakpoint address=0x7E0000 type=write]
โ†’ Breakpoint set
[waits for breakpoint hit]
โ†’ Breakpoint hit! PC=$00:8234
[calls emulator-read-memory address=0x8234 count=10]
โ†’ Disassembly shows: Enemy collision handler
Agent: "Link's HP is being modified by the enemy collision code at $00:8234.
The collision box is checking at the wrong Y offset (+8 instead of +16).
This causes damage when Link is near but not touching enemies."

Testing:

User: "Test that the new sword does double damage"
Agent: [calls emulator-load-state file=enemy_encounter.sfc]
[calls emulator-read-memory address=0x7E0010 count=2] # Link's damage
โ†’ Current damage: 0x0004
[calls emulator-write-memory address=0x7E0010 data=[0x08]] # Set 2x
[simulates sword swing - could use input recording]
[calls emulator-read-memory address=0x7EF280] # Enemy HP
โ†’ Enemy lost 8 HP (was 4 before)
Agent: "Confirmed! The new sword deals 8 damage instead of 4.
The 2x multiplier is working correctly."

๐Ÿ“ File Structure for New Features

src/app/emu/
โ”œโ”€โ”€ debug/
โ”‚ โ”œโ”€โ”€ breakpoint_manager.{h,cc} # NEW
โ”‚ โ”œโ”€โ”€ watchpoint_manager.{h,cc} # NEW
โ”‚ โ”œโ”€โ”€ disassembly_viewer.{h,cc} # EXISTS - enhance
โ”‚ โ”œโ”€โ”€ memory_viewer.{h,cc} # NEW
โ”‚ โ”œโ”€โ”€ event_logger.{h,cc} # NEW
โ”‚ โ”œโ”€โ”€ performance_profiler.{h,cc} # NEW
โ”‚ โ””โ”€โ”€ apu_inspector.{h,cc} # NEW
โ”œโ”€โ”€ tas/
โ”‚ โ”œโ”€โ”€ input_recorder.{h,cc} # NEW
โ”‚ โ”œโ”€โ”€ movie_file.{h,cc} # NEW
โ”‚ โ””โ”€โ”€ rewind_manager.{h,cc} # NEW
โ””โ”€โ”€ emulator.{h,cc} # EXISTS - integrate above
src/cli/commands/agent/
โ”œโ”€โ”€ emulator_tools.{h,cc} # NEW - z3ed agent emulator commands
โ””โ”€โ”€ test_scenario_runner.{h,cc} # NEW - automated testing
src/cli/service/agent/
โ””โ”€โ”€ tools/
โ”œโ”€โ”€ emulator_control_tool.cc # NEW
โ”œโ”€โ”€ emulator_memory_tool.cc # NEW
โ””โ”€โ”€ emulator_debug_tool.cc # NEW

๐ŸŽจ UI Mockups

Debugger Layout (ImGui)

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ YAZE Emulator - Debugging Mode โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ Disassembly โ”‚ Game Display โ”‚ Registers โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ 00:8000 LDA โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ A: 0x00 X: 0x05 โ”‚
โ”‚ 00:8002 STA โ”‚ โ”‚ โ”‚ โ”‚ Y: 0xFF SP: 0xEF โ”‚
โ”‚โ–บ00:8004 JMP โ”‚ โ”‚ [Zelda 3] โ”‚ โ”‚ PC: 0x8004 โ”‚
โ”‚ 00:8007 NOP โ”‚ โ”‚ โ”‚ โ”‚ PB: 0x00 DB: 0x00 โ”‚
โ”‚ 00:8008 RTL โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ Flags: nv--dizc โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ Memory โ”‚ Event Timeline โ”‚ Stack โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ”‚ 7E0000 00 05 โ”‚ Frame 123: โ”‚ 0x1FF: 0x00 โ”‚
โ”‚ 7E0008 3C 00 โ”‚ [NMI]โ”€โ”€[DMA]โ”€โ”€[IRQ] โ”‚ 0x1FE: 0x80 โ”‚
โ”‚ 7E0010 1F 00 โ”‚ โ”‚ 0x1FD: 0x04 โ† SP โ”‚
โ”‚ โ”‚ โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

APU Debugger Layout

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚ SPC700 Audio Debugger โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
โ”‚ SPC700 Disassembly โ”‚ DSP State โ”‚
โ”‚ โ”‚ โ”‚
โ”‚ 0100 MOV A,#$00 โ”‚ Master Volume: L=127 R=127 โ”‚
โ”‚โ–บ0102 MOV (X),A โ”‚ Echo: OFF FIR: Standard โ”‚
โ”‚ 0104 INCX โ”‚ โ”‚
โ”‚ โ”‚ Channel 0: โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘ (75%) โ”‚
โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค VOL: L=100 R=100 PITCH=2048 โ”‚
โ”‚ Audio RAM (64KB) โ”‚ ADSR: Attack=15 Decay=7 Sustain=7 โ”‚
โ”‚ โ”‚ Sample: 0x0000-0x1234 (BRR) โ”‚
โ”‚ 0000 00 00 00 00 โ”‚ โ”‚
โ”‚ 0010 BRR BRR ... โ”‚ Channel 1: โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ (0%) โ”‚
โ”‚ โ”‚ (Inactive) โ”‚
โ”‚ [Export Samples] โ”‚ โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

๐Ÿš€ Performance Targets

Current Performance

  • Emulation Speed: ~60 FPS (with frame skipping)
  • CPU Usage: 40-60% (one core)
  • Memory: 80MB (emulator only)
  • Accuracy: ~85% (some timing issues)

Target Performance (Post-Optimization)

  • Emulation Speed: Solid 60 FPS (no skipping)
  • CPU Usage: 20-30% (with dynarec)
  • Memory: 100MB (with debug features)
  • Accuracy: 98%+ (cycle-accurate)

Optimization Strategy Priority

  1. Audio fix - Enables testing with sound
  2. Frame pacing - Eliminates jitter
  3. Hotspot profiling - Identifies slow code
  4. Cycle accuracy - Fixes timing bugs
  5. Dynarec - 3x speed boost

๐Ÿงช Testing Integration

Automated Emulator Tests (z3ed)

Unit Tests:

# Test CPU instructions
z3ed emu test cpu --test-suite 65816_opcodes.json
# Test PPU rendering
z3ed emu test ppu --test-rom ppu_test.sfc --frames 600
# Test APU audio
z3ed emu test apu --test-rom audio_test.sfc --export samples.wav

Regression Tests:

# Run all ROM compatibility tests
z3ed emu test regression --rom-dir test_roms/ --report report.html
# Compare with reference emulator
z3ed emu test compare --rom zelda3.sfc --reference bsnes --frames 1000

AI-Generated Tests:

z3ed agent test-scenario --prompt "Test that bombs damage enemies"
# AI generates emulator test script
{
"setup": {
"load_state": "link_with_bombs.sfc"
},
"steps": [
{"action": "place-bomb", "x": 100, "y": 100},
{"action": "wait-frames", "count": 60},
{"action": "spawn-enemy", "type": "octorok", "x": 110, "y": 100},
{"action": "wait-frames", "count": 60},
{"action": "assert-memory", "address": "0x7EF280", "comparison": "less_than", "reference": "initial"}
]
}

๐Ÿ”Œ z3ed Agent + Emulator Integration

New Agent Tools

Tool Registration (automatic):

// src/cli/service/agent/tool_dispatcher.cc
void ToolDispatcher::RegisterEmulatorTools(Emulator* emulator) {
RegisterTool("emulator-start", [emulator]() { /* ... */ });
RegisterTool("emulator-stop", [emulator]() { /* ... */ });
RegisterTool("emulator-read-memory", [emulator](args) { /* ... */ });
RegisterTool("emulator-cpu-state", [emulator]() { /* ... */ });
RegisterTool("emulator-screenshot", [emulator]() { /* ... */ });
// ... 15 more tools
}

Example Conversations:

1. Live Debugging:

User: "Why does Link keep getting stuck in walls?"
Agent: Let me debug this...
[calls emulator-load-state file=link_stuck.sfc]
[calls emulator-cpu-state]
โ†’ PC=$00:9ABC (Link movement handler)
[calls emulator-read-memory address=0x7E0022 count=2]
โ†’ Link X: 0x0478, Y: 0x0234
[calls emulator-read-memory address=0x7E0020 count=2]
โ†’ Subpixel X: 0x0F, Y: 0x0F (stuck at boundary!)
[calls emulator-add-breakpoint address=0x9ABC type=execute]
[calls emulator-step]
Agent: "Found it! The collision detection at $00:9ABC is using
integer division instead of proper subpixel handling.
When Link's subpixel position is 0x0F, the boundary
check fails and he gets stuck."

2. Performance Analysis:

User: "The overworld feels laggy after my changes"
Agent: [calls emulator-start]
[calls emulator-profile-start]
[waits 300 frames = 5 seconds]
[calls emulator-profile-stop]
[calls emulator-get-hotspots top_n=10]
โ†’ Top hotspot: $00:8500 (45% of cycles)
[calls emulator-disassemble address=0x8500 count=20]
Agent: "Your tile drawing code at $00:8500 is running every frame
and taking 45% of CPU time! The original game only runs this
when tiles change. Consider adding a dirty flag check."

3. Automated ROM Testing:

User: "Test my randomizer hack thoroughly"
Agent: I'll run comprehensive tests...
[calls emulator-load-state file=randomizer_start.sfc]
For each dungeon:
[calls emulator-teleport-link dungeon=N]
[calls emulator-run-frames count=3600] # 60 seconds
[calls emulator-screenshot name=dungeon_N.png]
[calls emulator-check-crashes]
[calls emulator-verify-items-reachable]
[calls emulator-generate-report]
Agent: "Tested all 12 dungeons. Found issues:
- Dungeon 3: Softlock at room 0x34 (missing key)
- Dungeon 7: Crash at room 0x12 (invalid sprite ID)
All other dungeons: โœ… PASS"

๐ŸŽ“ Learning from Mesen2

What Makes Mesen2 Great

  1. Comprehensive Debugging
    • Every hardware event is logged
    • Full state inspection at any time
    • Breakpoints on everything (CPU, PPU, APU, memory)
  2. Performance
    • Cycle-accurate yet fast (dynarec)
    • 60 FPS even with debugging enabled
    • Efficient state save/restore
  3. User Experience
    • Integrated debugger in same window as game
    • Real-time visualization of state changes
    • Intuitive UI for complex operations
  4. Extensibility
    • Lua scripting for automation
    • Event system for plugins
    • Export capabilities (traces, memory dumps)

Our Unique Advantages

YAZE + z3ed has features Mesen2 doesn't:

  1. AI Integration
    • Natural language debugging
    • Automated test generation
    • Intelligent crash analysis
  2. ROM Editor Integration
    • Edit ROM while emulator runs
    • See changes immediately
    • Debugging informs editing
  3. Collaborative Debugging
    • Share emulator state with team
    • Remote debugging via gRPC
    • AI agent can help multiple users
  4. Cross-Platform Testing
    • Same emulator in CLI and GUI
    • Automated test scenarios
    • CI/CD integration

๐Ÿ“Š Resource Requirements

Development Time Estimates

Phase Hours Weeks (Part-Time)
Audio Fix 4 0.5
Basic Debugger 20 2.5
SPC700 Debugger 15 2
AI Integration 25 3
Performance Opts 40 5
Total 104 13

Memory Requirements

Feature RAM Usage
Base Emulator 80 MB
Breakpoint Manager +2 MB
Event Logger +5 MB (10K events)
Rewind Buffer +60 MB (10 seconds)
Performance Profiler +10 MB
Total **~160 MB**

CPU Requirements

Configuration CPU % (Single Core)
Interpreter Only 40-60%
+ Debugging 50-70%
+ Profiling 60-80%
+ Dynarec (future) 20-30%

๐Ÿ›ฃ๏ธ Recommended Implementation Order

Month 1: Foundation

Weeks 1-2: Audio fix + Basic breakpoints
Weeks 3-4: Memory viewer + Disassembly enhancements

Month 2: Audio & Events

Weeks 5-6: SPC700 debugger + APU inspector
Weeks 7-8: Event logger + Timeline view

Month 3: AI Integration

Weeks 9-10: z3ed emulator tools + Agent integration
Weeks 11-12: Automated testing + Scenario runner

Month 4: Performance

Weeks 13-14: Cycle accuracy refactor
Weeks 15-16: Dynarec or JIT library integration

Month 5: Polish

Weeks 17-18: UI/UX improvements
Weeks 19-20: Documentation + Examples


๐Ÿ”ฎ Future Vision: AI-Powered ROM Hacking

The Ultimate Workflow

  1. AI Explores the Game
    z3ed agent explore --rom zelda3.sfc --goal "Find all heart piece locations"
    # Agent:
    # - Loads ROM in emulator
    # - Runs around overworld automatically
    # - Detects heart piece spawn events via memory watchpoints
    # - Screenshots each location
    # - Generates report with coordinates
  2. AI Debugs Your Hack
    z3ed agent debug --rom my_hack.sfc --issue "Boss doesn't take damage"
    # Agent:
    # - Loads hack in emulator
    # - Adds breakpoints on damage handlers
    # - Simulates boss fight
    # - Identifies missing damage check
    # - Suggests code fix with hex addresses
  3. AI Generates TAS
    z3ed agent speedrun --rom zelda3.sfc --category "any%"
    # Agent:
    # - Studies game mechanics via emulator
    # - Discovers optimal movement patterns
    # - Generates frame-perfect input sequence
    # - Exports TAS movie file
  4. AI Validates Randomizers
    z3ed agent validate --rom randomizer.sfc --seed 12345
    # Agent:
    # - Generates logic graph from ROM
    # - Simulates playthrough via emulator
    # - Verifies all items are reachable
    # - Checks for softlocks
    # - Rates difficulty

๐Ÿ› Appendix A: Audio Debugging Checklist

Run these checks to diagnose audio issues:

Check 1: Device Status

SDL_AudioStatus status = SDL_GetAudioDeviceStatus(audio_device_);
printf("Audio Status: %d (1=playing, 2=paused, 3=stopped)\n", status);
// Expected: 1 (SDL_AUDIO_PLAYING)

Check 2: Queue Size

uint32_t queued = SDL_GetQueuedAudioSize(audio_device_);
printf("Queued Audio: %u bytes\n", queued);
// Expected: 1000-8000 bytes (1-2 frames worth)
// If 0: Not queueing
// If >50000: Overflowing, audio thread stalled

Check 3: Sample Validation

int16_t* samples = audio_buffer_;
bool all_zero = true;
for (int i = 0; i < wanted_samples_ * 2; i++) {
if (samples[i] != 0) {
all_zero = false;
break;
}
}
if (all_zero) {
printf("ERROR: All audio samples are zero! SPC700 not outputting.\n");
}

Check 4: Buffer Allocation

// In window.cc:128, verify size calculation:
// For 48000Hz, 60 FPS, stereo:
// samples_per_frame = 48000 / 60 = 800
// stereo_samples = 800 * 2 = 1600 int16_t
// Size should be: 1600, NOT 3840
printf("Audio buffer size: %zu int16_t\n", audio_buffer_.size());
// Expected: 1600-1920 (for 60-50Hz)

Check 5: SPC700 Execution

// Verify SPC700 is actually running
uint64_t apu_cycles = snes_.apu().GetCycles();
// Should increase every frame
// If stuck: SPC700 deadlock or not running

Quick Fixes to Try

Fix A: Force Unpause

// emulator.cc, in RunFrame():
SDL_PauseAudioDevice(audio_device_, 0); // Force play state

Fix B: Larger Queue

// If buffer underruns, queue more:
SDL_QueueAudio(audio_device_, audio_buffer_, wanted_samples_ * 4 * 2); // 2 frames

Fix C: Clear Stale Queue

// If queue is stuck:
if (SDL_GetQueuedAudioSize(audio_device_) > 50000) {
SDL_ClearQueuedAudio(audio_device_); // Reset
}

๐Ÿ“ Appendix B: Mesen2 Feature Reference

Debugger Windows (Inspiration)

  1. CPU Debugger: Disassembly, registers, breakpoints
  2. Memory Tools: Hex viewer, search, compare
  3. PPU Viewer: Layer toggles, VRAM, OAM, palettes
  4. Event Viewer: Timeline of all hardware events
  5. Trace Logger: Full execution log with filters
  6. Performance Profiler: Hotspot analysis
  7. Script Window: Lua scripting for automation

Event Types Tracked

  • CPU: NMI, IRQ, BRK instruction, RESET
  • PPU: V-Blank, H-Blank, Mode change, Sprite overflow
  • DMA: General DMA, HDMA, channels used
  • APU: Sample playback, DSP writes, Timer IRQ
  • Cart: Save RAM write, special chip events

Trace Logger Format

Cycle PC Opcode A X Y SP Flags Event
0 $00FFD9 SEI 00 00 00 1FF nvmxdiZc
1 $00FFDA CLI 00 00 00 1FF nvmxdizc
2 $00FFDB JMP 00 00 00 1FF nvmxdizc
...
16749 $008234 LDA 05 00 00 1EF Nvmxdizc [NMI]

๐ŸŽฏ Success Criteria

Phase 1 Complete When:

  • โœ… Audio plays correctly from SDL2
  • โœ… Can hear game music and sound effects
  • โœ… No audio crackling or dropouts
  • โœ… Audio buffer diagnostics implemented

Phase 2 Complete When:

  • โœ… Can set breakpoints on any address
  • โœ… Disassembly view shows live execution
  • โœ… Memory viewer has multi-region support
  • โœ… z3ed CLI can control debugger

Phase 3 Complete When:

  • โœ… SPC700 debugger shows all APU state
  • โœ… Can visualize audio channels
  • โœ… Can export audio samples to WAV

Phase 4 Complete When:

  • โœ… AI agent can read emulator memory
  • โœ… AI agent can control emulation
  • โœ… AI can generate test scenarios
  • โœ… Automated ROM testing works

Phase 5 Complete When:

  • โœ… Emulator is cycle-accurate
  • โœ… 60 FPS maintained with debugging
  • โœ… Dynarec provides 3x speedup
  • โœ… All Mesen2 features implemented

๐ŸŽ“ Learning Resources

SNES Emulation

Audio Debugging

Performance Optimization


๐Ÿ™ Credits & Acknowledgments

Emulator Core: Based on LakeSnes by elzo-d
Debugger Inspiration: Mesen2 by SourMesen
AI Integration: z3ed agent system
Documentation: With love (and Puerto Rican soup! ๐Ÿฒ)


Document Version: 1.0
Last Updated: October 8, 2025
Next Review: After Audio Fix
Sleep Well! ๐Ÿ˜ด