jonathanpeppers/dotnes/nes-emu-debug
Claude Code and agent skill by jonathanpeppers/dotnes
- community
Checking that the SKILL.md file is still in the repository.
Run NES ROMs in the MesenCE emulator to debug runtime behavior. Use this skill whenever the user wants to run a ROM and inspect what actually happens at runtime: read NES memory (RAM, palette, nametable, OAM), dump CPU/PPU/APU state after N frames, capture the screen buffer, compare runtime behavior between two ROMs, or verify that a sample displays correctly. Also use when the user says things like "run the ROM", "what does the screen look like", "check the palette", "inspect nametable", "read zero page", "dump memory", or "the ROM doesn't display correctly". This is the dynamic/runtime counterpart to nes-rom-debug (which does static binary analysis).
Install
npx vskill@latest install jonathanpeppers/dotnes/nes-emu-debugSKILL.md
NES Emulator Debug (MesenCE)
Run NES ROMs headlessly in MesenCE's test runner to inspect runtime state, memory, and screen output. This complements the nes-rom-debug skill (static binary analysis) with dynamic runtime inspection.
Prerequisites
Build the local packages, then a sample to install MesenCE:
The dotnes.mesen 2.2.1 package downloads MesenCE from nesdev-org/MesenCE into its NuGet package's bin directory. Building the packaging project alone does not download the emulator. Query the sample's evaluated RunCommand rather than hardcoding the cache path or platform-specific executable.
How It Works
MesenCE has a headless test runner mode that loads a ROM, runs a Lua script, and exits — no window, no GUI. The Lua script has full access to the emulator API: CPU/PPU/APU state, all memory types, screen buffer, frame callbacks, etc.
Basic Invocation
Key Command-Line Flags
| Flag | Purpose | |
|---|---|---|
--testRunner | Headless mode — no window, runs at max speed, exits via emu.stop(code) | |
--enableStdout | Print emulator log (ROM info, mapper details) to stdout | |
--doNotSaveSettings | Don't write settings.json (safe for automation) | |
--timeout=N | Kill if script doesn't exit within N seconds (default: 100) | |
--debug.scriptWindow.allowIoOsAccess=true | Required for Lua io.open/os.* functions |
Lua Script API Reference
State Access
The state table returned by emu.getState() uses flat dotted-string keys, not nested tables. Access fields with bracket notation:
Key state fields:
| Key | Type | Description | |
|---|---|---|---|
cpu.pc | number | Program counter | |
cpu.a | number | Accumulator | |
cpu.x | number | X index register | |
cpu.y | number | Y index register | |
cpu.sp | number | Stack pointer | |
cpu.ps | number | Processor status flags | |
cpu.cycleCount | number | Total CPU cycles elapsed | |
ppu.scanline | number | Current PPU scanline | |
ppu.cycle | number | Current PPU cycle within scanline | |
ppu.frameCount | number | Total PPU frames rendered | |
ppu.control.* | various | PPU control register bits | |
ppu.mask.* | various | PPU mask register bits | |
ppu.statusFlags.* | various | PPU status flags | |
frameCount | number | Emulator frame count | |
masterClock | number | Master clock ticks |
Memory Types
Use emu.read(address, memType) to read memory. The address is relative to the start of that memory region (not the CPU/PPU mapped address).
| Memory Type | Enum Value | Size | Description | |
|---|---|---|---|---|
emu.memType.nesInternalRam | 46 | 2 KB | CPU RAM ($0000-$07FF). Use this for zero page. | |
emu.memType.nesPaletteRam | 53 | 32 B | Background + sprite palette (addr 0-31) | |
emu.memType.nesNametableRam | 49 | 2 KB | VRAM nametables (addr 0 = NT0 tile 0,0) | |
emu.memType.nesSpriteRam | 51 | 256 B | OAM — 64 sprites × 4 bytes each | |
emu.memType.nesPrgRom | 45 | varies | PRG ROM (addr 0 = first PRG byte) | |
emu.memType.nesChrRom | 55 | varies | CHR ROM pattern tables | |
emu.memType.nesChrRam | 54 | varies | CHR RAM (if mapper uses RAM instead of ROM) | |
emu.memType.nesWorkRam | 47 | varies | Battery-backed / work RAM | |
emu.memType.nesSaveRam | 48 | varies | Save RAM | |
emu.memType.nesMemory | 8 | 64 KB | Full CPU address space (mapped, may cause side effects) | |
emu.memType.nesPpuMemory | 9 | 16 KB | Full PPU address space (mapped) |
Important: For reading zero page / RAM, use nesInternalRam (direct access), not nesMemory (which goes through the CPU bus and may return stale/zero values in test runner mode). Similarly, use nesPaletteRam for palette, not nesPpuMemory.
Frame Callbacks
Register a callback to run after each frame:
Screen Buffer
To save as raw RGB file:
Convert to PNG (from PowerShell):
Exiting
The exit code is returned as the process exit code, so you can check it from PowerShell via $proc.ExitCode.
Lua Script Templates
Template 1: Dump CPU State After N Frames
Template 2: Read NES Library Zero Page Variables
The NES library stores state in zero page. Key addresses (from src/dotnes.tasks/Utilities/NESConstants.cs):
| Address | Name | Description | |
|---|---|---|---|
| $01 | STARTUP | Startup flag | |
| $02 | NES_PRG_BANKS | Number of PRG banks | |
| $03 | VRAM_UPDATE | Non-zero = VRAM update pending | |
| $04-$05 | NAME_UPD_ADR | Nametable update address (16-bit) | |
| $06 | NAME_UPD_ENABLE | Nametable update enable flag | |
| $07 | PAL_UPDATE | Palette update pending | |
| $08-$09 | PAL_BG_PTR | Background palette pointer (16-bit) | |
| $0A-$0B | PAL_SPR_PTR | Sprite palette pointer (16-bit) | |
| $0C | SCROLL_X | Horizontal scroll position | |
| $0D | SCROLL_Y | Vertical scroll position | |
| $0E | SCROLL_X1 | split() saved X scroll | |
| $0F | PPU_CTRL_VAR1 | split() saved PPU_CTRL | |
| $10 | PRG_FILEOFFS | PRG file offset | |
| $12 | PPU_MASK_VAR | Shadow of PPU mask register | |
| $14-$16 | NMI_CALLBACK | JMP opcode + address for NMI callback | |
| $17 | TEMP | Temporary variable | |
| $18 | TEMP_HI | Temp high byte / DUP_TEMP | |
| $19 | TEMP2 | Additional temp | |
| $1A | TEMP3 | Additional temp | |
| $1B | OAM_OFF | OAM buffer offset | |
| $1C | UPDPTR | VRAM update buffer index | |
| $22 | sp | C stack pointer | |
| $3C | RAND_SEED | Random seed for PRNG |
Template 3: Dump Palette
Template 4: Dump Nametable Region
Template 5: Capture Screenshot as Raw RGB
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 100 of 100Pattern checks on the skill's files.
- Model reviewNot runA model review is scheduled for the next rescan.
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryChecking.github/skills/nes-emu-debug/SKILL.md
Popularity
- Repository stars
- 673
- Repository forks
- 24
- 7-day trend score
- 33
Stars and forks are for the whole GitHub repository, not this skill alone. Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/jonathanpeppers/dotnes/nes-emu-debug)