Nintendo Entertainment System decomp guide
Small ROMs, hand-written assembly, a beta recompiler: quick to get running, but there is no C to match.
What this system is like
NES games are small. A whole game is often 32 KB to 512 KB of code and data. The code was written by hand in 6502 assembly. Assembly is the lowest kind of code, one machine step per line.
For Path A, most new projects use NESRecomp. It is a free static recompiler. It turns the game's 6502 code into C and adds a runtime for the console. Three people got a game playable in under a week with AI help. Two of them say the AI was Claude. All three already knew the tool.
Path B is different here. There was never any C, so there is no C to match byte for byte. The byte check comes from a disassembly instead. A disassembly is the game turned back into assembly text that rebuilds the exact same ROM. Then you port each routine to C and test it against the real game.
The hard parts are bank switching and odd jump tricks. Bank switching means the game swaps which part of the cartridge the chip can see. Claude does well here when it has a check it can run. That can be a log of missed code, a screenshot, or an emulator diff.
There was never any C
NES games were written in assembly. So there is nothing to byte-match in C. The byte check on NES is a disassembly that rebuilds the exact ROM. Your C port is checked by tests instead.
Bank switching
The CPU can only see 32 KB of game code at once. Bigger games swap banks of 8 KB, 16 KB or 32 KB in and out. The same address can hold different code in different banks. Every tool must know which bank is live.
Jump tricks the tools cannot guess
Many games jump through tables stored right after a JSR call. Some push an address and use RTS as a jump. A recompiler cannot always find these. You tell it in game.toml, one address at a time.
Tiny code, great emulators
Zelda II has about 3,000 routines, by the z2rs ledger. NESRecomp checks its own output against a console model built from TriCNES, and against Mesen pictures. That gives you a trusted copy of the game to compare against at every frame.
Many disassemblies exist already
Fans have rebuilt the assembly of Zelda, Zelda II, Dr. Mario, Super Mario Bros. and others. These give you routine names for free. Always check that a disassembly matches your exact ROM version.
Two paths
Get it running on PC
NESRecomp reads your ROM and writes C for every piece of 6502 code it can find. Its runner stands in for the picture chip, the sound chip and the mapper. Code it missed runs in a small built-in interpreter and is logged as a dispatch miss. You feed those addresses back into game.toml and build again. You repeat until a scripted run has zero misses and matches an emulator.
Full decomp for porting
First you get assembly source that rebuilds your exact ROM. That is the NES form of a matching decomp, and the build hash proves it. Then you port each routine to C. A test runs the same routine in the original and in your C, and compares RAM and every hardware access. When all routines pass, the game runs on C alone and can move to new systems.
Path A: Get it running on PC
NESRecomp reads your ROM and writes C for every piece of 6502 code it can find. Its runner stands in for the picture chip, the sound chip and the mapper. Code it missed runs in a small built-in interpreter and is logged as a dispatch miss. You feed those addresses back into game.toml and build again. You repeat until a scripted run has zero misses and matches an emulator.
Method: static recompilation with NESRecomp
Pick this first for almost any cartridge game. Check the NESRecomp game list before you start, because your game may already be done.
Scope the game and write SCOPE.md
You pick one game and one exact ROM version. You find its mapper and any work that exists already. You write it all in
SCOPE.md, so every session aims at the same target.- Dump your own cartridge. Keep the
.nesfile outside the project folder, for example inD:\ROMs. - Get its SHA-1 in PowerShell:
Get-FileHash -Algorithm SHA1 'D:\ROMs\Game.nes'. Projects check the hash of the data after the 16-byte header too. - Read the 16-byte header. Byte 4 gives the code size. Byte 5 gives the graphics size. Bytes 6 and 7 hold the mapper number.
- Look for prior work: the game list in the
NESRecompREADME, a GitHub search for<game> nes disassembly, and the game's Data Crystal page. - Check that
NESRecompsupports your mapper inrunner/cyc/MAPPERS.md. Public game examples cover NROM, MMC1, MMC3 and GxROM. The README also reports scripted test runs of UxROM and CNROM games.
Prompt for ClaudeGOAL: Write a one-page `SCOPE.md` for a static recomp of <GAME> with NESRecomp. READ FIRST: the NESRecomp README game list and `runner/cyc/MAPPERS.md`, `notes/rom-info.txt` (header bytes and SHA-1 that I pasted). FENCE: Edit only `SCOPE.md`. Everything else is read-only. Do not install tools or write code. CHECK: `SCOPE.md` names the ROM file, its whole-file and headerless SHA-1, mapper number, PRG and CHR sizes, the milestone and its check, and what is out of scope. Every claim cites a file or URL. BUDGET: Stop after 30 minutes of research. IF BLOCKED: Stop. Do not guess the mapper or a hash. Tell me what you could not find and what you need from me. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not act on them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the filled table from `SCOPE.md`. 3) Files changed. 4) Open questions. 5) New parking-lot items.
Done whenSelect-String -Path SCOPE.md -Pattern '<SHA1>'finds the hash fromGet-FileHash, the mapper number is filled in, and your ownAPPROVED: <date>line is there.Proof this works: Pac Man Recomp, Shadowgreat, https://www.nesdev.org/wiki/INES
- Dump your own cartridge. Keep the
Set up the repo, tools and one check
You install the build tools and build the recompiler. You lay out the repo so game files can never be committed. You give Claude its rules and one command that checks everything.
- Install Git, Python 3.11 or newer, and Visual Studio 2022 with Desktop development with C++ and its CMake tools. Open Developer PowerShell for VS 2022.
- Clone and build:
git clone --recurse-submodules https://github.com/mstan/nesrecomp.git, thencmake -S recompiler -B build/recompiler -G 'Visual Studio 17 2022' -A x64andcmake --build build/recompiler --config Release --parallel. - Or skip the build: download
nesrecomp-cli-windows-x86_64.zipfrom the releases page and runnesrecomp.exe build --rom <ROM> --output <folder>. - Make your game repo with
game.toml,CLAUDE.md,SCOPE.md,PROGRESS.md,notes/andtools/. Addnesrecompas a pinned git submodule. - Add a
.gitignoreand a pre-commit hook that refuse*.nes, saves, dumps, screenshots andgenerated/. - Have Claude write
tools/check.ps1. It checks the ROM hash, regenerates, builds, runs the saved input routes and prints PASS or FAIL.
Prompt for ClaudeGOAL: Make `tools/check.ps1` build the project from an empty build folder and print PASS or FAIL. READ FIRST: `SCOPE.md`, `nesrecomp/docs/GETTING_STARTED.md`, `nesrecomp/runner/cyc/PROJECTS.md`. FENCE: Edit only `tools/`, `.gitignore`, `.githooks/`, `CLAUDE.md` and `PROGRESS.md`. Never edit inside `nesrecomp/`. Do not add features. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>`. Done means it exits 0, prints PASS, and `git status` shows no ROM or generated file staged. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Do not work around a missing tool. Tell me the exact error and the command that failed. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: paste the last 20 lines of the check. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whentools/check.ps1prints PASS on a clean clone, even though no game code runs yet.Proof this works: https://github.com/mstan/nesrecomp/blob/master/docs/GETTING_STARTED.md, Shadowgreat, z2rs
Map the ROM: banks, vectors and jump tricks
You learn where the code lives before the recompiler guesses. You find the bank-switch routine, the jump-table tricks and the data areas. You save it all in a ledger file that every session reads.
- Install Ghidra and the
GhidraNesextension. Import the.nesfile with the format set to NES ROM. - Open
resetandvblankin the Symbol Tree. Note the bank-switch routine and the NMI handler. - Read
nesrecomp/docs/PATTERNS.md. Mark everyJSRfollowed by an address table, and everyPLA/PHAtrick on the return address. - If a
ca65disassembly exists, build it withld65 -Ln game.lbland convert names withpython nesrecomp/tools/build_symbols_from_lbl.py game.lbl symbols.sym. Then setsymbol_file = "symbols.sym"under[game]ingame.toml. - Write
notes/rom-map.md: banks, vectors, bank-switch address, dispatch routines and data ranges, each with how it was checked.
Prompt for ClaudeGOAL: Write `notes/rom-map.md` with the bank layout, vectors, bank-switch routine, inline dispatch routines and known data ranges of <GAME>. READ FIRST: `SCOPE.md`, `nesrecomp/docs/PATTERNS.md`, `nesrecomp/docs/EXTRACTION.md`. Use Ghidra over MCP if it answers; if not, use the disassembly at <DISASM_PATH>. FENCE: Edit only `notes/rom-map.md` and `symbols.sym`. Do not edit `game.toml` yet. Do not read `generated/`. CHECK: Each entry cites a ROM address and how you saw it (Ghidra listing, disassembly line, or bytes). The three vectors at `$FFFA`-`$FFFF` match the bytes in the ROM. BUDGET: Stop after 90 minutes, or when a bank has no clear entry point after two passes. IF BLOCKED: Stop. Say which bank or address you cannot explain and what you tried. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the vector bytes and three sample entries with their sources. 3) Files changed. 4) Banks still unclear. 5) New parking-lot items.
Done whennotes/rom-map.mdlists every bank, the bank-switch routine and each dispatch trick, each with a cited address.Proof this works: Dr. Mario NES Recomp, YoshisCookieRecomp, https://github.com/kylewlacy/GhidraNes
- Install Ghidra and the
First light: boot to a screenshot
You make the smallest possible build. A short
game.tomland the headless host turn the ROM into a program with no window. It runs 600 frames and saves a picture.- Start
game.tomlwith only[game],output_prefix = "<name>"andcycle_accurate = true. Add the bank-switch routine under[mapper] bank_switchif your map found one. - Configure:
cmake -S nesrecomp/runner/cyc/project -B build-headless -G 'Visual Studio 17 2022' -A x64 -DNESRECOMP_ROM='D:/ROMs/Game.nes' -DNESRECOMP_GAME_CONFIG="$PWD/game.toml" -DNESRECOMP_HEADLESS=ON. - Build:
cmake --build build-headless --config Release --parallel. - Run:
build-headless\Release\nes_game.exe 'D:\ROMs\Game.nes' --frames 600 --screenshot boot.png. - Build the test oracle once:
cmake -S nesrecomp/runner/cyc -B build/cycandcmake --build build/cyc --config Release. You getbuild/cyc/Release/cyc_oracle.exe, a console model built fromTriCNES. - Compare the first 600 frames:
python nesrecomp/tools/cyc/cyc_verify.py --exe build-headless/Release/nes_game.exe --oracle build/cyc/Release/cyc_oracle.exe --rom 'D:/ROMs/Game.nes' --frames 600 --out verify/. It exits 0 when every run matches. - Open
boot.pngand the same moment in Mesen side by side. Note what differs inPROGRESS.md.
Prompt for ClaudeGOAL: Get the headless host to run 600 frames of <GAME> and save `boot.png` that shows the title or logo. READ FIRST: `SCOPE.md`, `notes/rom-map.md`, `nesrecomp/docs/GETTING_STARTED.md` section 3. FENCE: Edit only `game.toml` and `PROGRESS.md`. Never edit `generated/` or anything in `nesrecomp/`. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH> -Frames 600`. Done means the build passes, `boot.png` exists, and `python nesrecomp/tools/cyc/cyc_verify.py --exe <EXE> --oracle <ORACLE> --rom <ROM_PATH> --frames 600 --out verify/` exits 0. Paste the `dispatch_misses.log` line count too. BUDGET: Stop after 3 builds that fail the same way. IF BLOCKED: Stop. Do not stub hardware or skip code to get a picture. Tell me the first error and the address involved. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output and a description of `boot.png`. 3) Files changed. 4) Miss count. 5) New parking-lot items.
Done whenThe headless run writesboot.png, andcyc_verify.pyexits 0 for the first 600 frames.Proof this works: Dragon Warrior4NESRecomp, https://github.com/mstan/nesrecomp/blob/master/docs/GETTING_STARTED.md, https://github.com/mstan/nesrecomp/blob/master/runner/cyc/README.md
- Start
Grind: drive dispatch misses to zero
You play scripted routes and collect every address the recompiler missed. You fix each one in
game.toml, never in the generated C. You stop when every route reports zero misses.- Write input routes as text files with one frame and button set per line. Start with boot to the first level, then one route per mode.
- Run a route headless:
nes_game.exe <ROM> --input <route.txt> --frames <N> --screenshot <png> --miss-log cyc_seeds.txt. Then readdispatch_misses.lognext to the program. Each new line is a ready-madeextra_funcentry. - For each miss, look up the address in Ghidra. Then add one of:
[[extra_func]],[[extra_label]],[[inline_dispatch]],[[inline_pointer]],[[data_region]]or a[functions]list. - Set
cycle_seed_file = "cyc_seeds.txt"under[game]ingame.toml. The next build then compiles the addresses the interpreter had to run. - Regenerate, rebuild and rerun every route after each change.
Prompt for ClaudeGOAL: Remove the dispatch miss at <BANK>:<ADDRESS> seen on route `<ROUTE_FILE>`. READ FIRST: `CLAUDE.md`, `notes/rom-map.md`, `nesrecomp/docs/PATTERNS.md`, the last 20 lines of `dispatch_misses.log`. FENCE: Edit only `game.toml` and `notes/findings.md`. Never edit or read `generated/` whole. Never edit `nesrecomp/`; if the recompiler itself is wrong, stop and say so. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>`. Done means this address is gone from the log, no new miss appears on any route, and every route still prints PASS. BUDGET: Stop after 3 tries, or 45 minutes with no change in the miss list. IF BLOCKED: Stop. Do not force the address to run in the interpreter to hide it. Tell me what the code at that address does and why each fix failed. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: miss count before and after, and the check output. 3) Files changed. 4) What the code does, with the Ghidra address. 5) New parking-lot items.
Done whenEvery saved route reports zero dispatch misses andtools/check.ps1prints PASS.Proof this works: Dr. Mario NES Recomp, Yoshi NES Recomp, Dragon Warrior4NESRecomp
Grind: match an emulator, one bug at a time
Zero misses does not mean correct. You now compare RAM and pictures with a trusted emulator on the same input. Each difference becomes one bug card with proof.
- Run the same route in your build and in the oracle:
python nesrecomp/tools/cyc/cyc_verify.py --exe <EXE> --oracle <ORACLE> --rom <ROM> --input <route.txt> --frames <N> --interp --out verify/. It prints the first frame that differs. - Rerun both sides with
--trace-frame <F> --trace-out <file>and diff the two files. Add--mem-frame <F> --mem-out <file>to dump RAM and the picture. - For a Mesen picture check, build
nesrecomp/tools/cyc/mesen_shotfrom a MesenCE source checkout. Then runcmp_picture.pyon your--mem-outfile, with--ram-init zeroson your build. - Check the frame offset first. Compare a frame from before any button press with Mesen frames N and N+1. Never assume frame N is the same in both.
- For each first difference, find the instruction that wrote the wrong byte. Name the cause as codegen, runner, timing or config before any fix.
- Config fixes go in
game.toml. Framework fixes go in a numbered patch file, not inside the submodule.
Prompt for ClaudeGOAL: Find and fix the first RAM difference between my build and Mesen on route `<ROUTE_FILE>`. READ FIRST: `CLAUDE.md`, `notes/findings.md`, `notes/dead-ends.md`, the two RAM dumps at <DUMP_A> and <DUMP_B>. FENCE: Edit only `game.toml`, `patches/nesrecomp/` and `notes/`. Never edit `generated/`. No printf logging; use the TCP debug server (`--tcp <PORT>`) and `--trace-out`. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>`. Done means the first difference moves later or disappears, and every other route still prints PASS. BUDGET: Stop after 2 failed fixes, or 60 minutes without a proven cause. IF BLOCKED: Stop. No fix until the cause is proven. Tell me the first differing byte, the frame, the writing instruction and your best two theories. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the diff before and after, and the check output. 3) Files changed. 4) Cause class: codegen, runner, timing or config. 5) New parking-lot items.
Done whencyc_verify.py --input <route>exits 0 for every saved route, and each fix has a line innotes/findings.md.Proof this works: Yoshi NES Recomp, Dr. Mario NES Recomp, https://github.com/mstan/nesrecomp/blob/master/runner/cyc/README.md
- Run the same route in your build and in the oracle:
Harden: lock fixes in with regression routes
You turn every route into a test that runs by itself. Each test checks RAM values and picture hashes against a saved baseline. Old fixes can then never break without you knowing.
- Run every route with
--hash-out <route>.hash. It writes hashes of memory, the picture and the CPU registers for every frame. - Save the hash files from a known-good run in
tests/baseline/. Make the check compare new runs with them line by line. - Run tests with an empty
--mods-rootfolder and--no-save, so they never touch your real settings or saves. - Make
tools/check.ps1run every route. Have a fresh Claude session run it on a clean clone.
Prompt for ClaudeGOAL: Turn the routes in `routes/` into regression tests with a frame-hash baseline. READ FIRST: `CLAUDE.md`, `routes/`, `tools/check.ps1`, `PROGRESS.md`. FENCE: Edit only `tests/`, `tools/check.ps1` and `routes/`. Do not change `game.toml` or any game behavior. Never edit files in `tests/baseline/` by hand after they are first made. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>` twice in a row. Done means both runs print PASS with the same hashes. Then break one byte in a copied route and show that the check fails. BUDGET: Stop after 3 failed tries on the same test. IF BLOCKED: Stop. Tell me which route is not repeatable and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: both PASS runs and the planted failure. 3) Files changed. 4) Routes not yet covered. 5) New parking-lot items.
Done whenThe check passes twice in a row on a clean clone and fails when a route is broken on purpose.Proof this works: Dr. Mario NES Recomp, Pac Man Recomp, ZQuest Classic
- Run every route with
Add the desktop host and ship without game data
You move from the headless host to a real window with sound, pads and a launcher. You package it so the release holds no game code. The player's PC builds the game from their own ROM.
- Use
nesrecomp_add_cycle_gamefromnesrecomp/runner/cyc/project.cmakein your ownCMakeLists.txt. Add your features as extra host source files. - Add
recomp-uias a submodule for the launcher and settings menu. - If you build with
llvm-mingw, linklibc++andlibunwindstatically or ship the DLLs beside the program. - Package so the release builds the game on the player's PC from their ROM, as Pac Man Recomp and Shadowgreat do.
- Run every regression route again on the desktop build.
Prompt for ClaudeGOAL: Build a desktop version of <GAME> with window, sound, pads and the recomp-ui launcher, and a release zip that holds no game data. READ FIRST: `CLAUDE.md`, `nesrecomp/runner/cyc/PROJECTS.md`, the `CMakeLists.txt` of a sister project at <SISTER_REPO>. FENCE: Edit only `CMakeLists.txt`, `src/`, `tools/package.ps1` and `PROGRESS.md`. Never edit `generated/`, `nesrecomp/` or `recomp-ui/`. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>` and `powershell -File tools/package.ps1`. Done means all routes PASS and the package script reports zero ROM-derived files in the zip. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Tell me the error and which file it points at. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: both outputs. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whenThe desktop build passes every route, and the release zip holds no ROM bytes or generated C.Proof this works: Pac Man Recomp, Shadowgreat, https://github.com/RetroPortingToolKit/recomp-ui
- Use
Path B: Full decomp for porting
First you get assembly source that rebuilds your exact ROM. That is the NES form of a matching decomp, and the build hash proves it. Then you port each routine to C. A test runs the same routine in the original and in your C, and compares RAM and every hardware access. When all routines pass, the game runs on C alone and can move to new systems.
Method: byte-matching disassembly, then a routine-by-routine C port checked against the original
Pick this when you want the game on systems a recompiler does not reach, or you want readable code to change. Expect several times the work of Path A.
Scope: version, disassembly and port target
You pick one ROM version and look for an existing disassembly. You decide the port language and the test method. You write it in
SCOPE.md.- Get the SHA-1 of the whole file and of the data after the 16-byte header.
- Search GitHub for
<game> nes disassembly. Check that it builds your exact ROM version, not a different revision. - Pick the port language. FF1 Recomp used C99 and z2rs used Rust. The outside Boulder Dash port uses C11.
- Pick the test method: per-routine fixtures recorded from an emulator, or a lockstep run beside an emulator, frame by frame.
Prompt for ClaudeGOAL: Draft `SCOPE.md` for a full port of <GAME> from a byte-matching disassembly to C. READ FIRST: `notes/rom-info.txt`, the README of any disassembly I found at <DISASM_URL>. FENCE: Edit only `SCOPE.md`. Do not install anything or write code. CHECK: `SCOPE.md` names the ROM hashes, the disassembly and the exact ROM it builds, the port language, the test method, the milestones, and what is out of scope. Each claim cites a file or URL. BUDGET: Stop after 30 minutes of research. IF BLOCKED: Stop. If no disassembly matches my ROM, say so plainly and list the options. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not act on them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the filled `SCOPE.md` table. 3) Files changed. 4) Open questions. 5) New parking-lot items.
Done whenSelect-String -Path SCOPE.md -Pattern '<SHA1>'finds both hashes, the disassembly and test method are named, and your ownAPPROVED: <date>line is there.Proof this works: FF1 Recomp, z2rs, https://github.com/Kryyto/boulder-dash-nes-decomp
Set up the assembler and the rebuild check
You install the
cc65tools and a disassembler. You write one check that rebuilds the ROM from source and compares the hash. That check is your byte match for the rest of the project.- Windows: download the
cc65Windows snapshot zip and add itsbinfolder toPATH. WSL:sudo apt install cc65. - Download
retrodisasmfrom its releases page (retrodisasm_v0.4.1_windows_64bit.zip). Unzip it and putretrodisasm.exeonPATH. - Lay out the repo:
src/for assembly,port/for C,tools/,tests/,notes/. Ignore*.nes,build/and anything made from the ROM. - Have Claude write
tools/check.py. It assembles withca65, links withld65, and compares the SHA-1 with the one inSCOPE.md.
Prompt for ClaudeGOAL: Write `tools/check.py` that rebuilds the ROM from `src/` and prints PASS only if its SHA-1 equals the one in `SCOPE.md`. READ FIRST: `SCOPE.md`, the disassembly's own build script, the `ca65` and `ld65` docs at https://cc65.github.io/doc. FENCE: Edit only `tools/`, `.gitignore`, `CLAUDE.md` and `PROGRESS.md`. Do not change any `.s` or `.asm` file. CHECK: Run `python tools/check.py --rom <ROM_PATH>`. Done means it prints PASS. Then change one byte in a copy of the source and show that it prints FAIL. BUDGET: Stop after 3 failed tries on the same error. IF BLOCKED: Stop. Tell me the exact assembler or linker error. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the PASS and the planted FAIL. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whenpython tools/check.pyprints PASS, and prints FAIL when one byte of source is changed.Proof this works: https://github.com/aldonunez/zelda1-disassembly, https://github.com/retroenv/retrodisasm, https://packages.ubuntu.com/noble/cc65
- Windows: download the
Get a disassembly that rebuilds the exact ROM
If a matching disassembly exists, you use it. If not, a tracing disassembler gives you a first draft that rebuilds byte for byte. You then fix code and data splits and add names, keeping the hash the same.
- Draft one:
retrodisasm -a ca65 -o src/game.asm 'D:\ROMs\Game.nes'. Prove it:retrodisasm -verify 'D:\ROMs\Game.nes'. - For a bank-switched game,
-split-bankswrites each bank to its own file.retrodisasmrefuses mappers it does not support. - Its banking support is marked experimental. For bank-switched games, check every bank by hand in Ghidra or Mesen.
- Add names, comments and code or data hints. Rerun
python tools/check.pyafter each batch. - Never paste ROM bytes into the repo. Generate
src/from the ROM in a script, as the Boulder Dash port does.
Prompt for ClaudeGOAL: Rename the routines in bank <BANK> of `src/` with names that say what each does, keeping the ROM byte-identical. READ FIRST: `CLAUDE.md`, `notes/rom-map.md`, `notes/routines.toml`. FENCE: Edit only labels and comments in the bank <BANK> source file and `notes/routines.toml`. Do not change any instruction, byte or data value. CHECK: Run `python tools/check.py --rom <ROM_PATH>`. Done means it prints PASS, and every renamed routine has a one-line reason in `notes/routines.toml`. BUDGET: Stop after 40 routines or 90 minutes. IF BLOCKED: Stop. Name a routine `unk_<ADDR>` rather than guess. Tell me which ones you could not explain. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output. 3) Files changed. 4) Routines left unnamed. 5) New parking-lot items.
Done whenpython tools/check.pyprints PASS and every bank has its routines named or markedunk_.Proof this works: https://github.com/retroenv/retrodisasm, https://github.com/FiendsOfTheElements/z2disassembly, https://github.com/Kryyto/boulder-dash-nes-decomp
- Draft one:
Map every routine into a ledger
You make a list of every routine with its bank, address, size, callers and status. This list is the to-do list for the whole port. A script fills it; people only change the status and notes.
- Get labels from the linker with
ld65 ... -Ln game.lbl --dbgfile game.dbg. - Have Claude write
tools/ledger.pythat writesnotes/routines.toml: name, bank, address, size, callers, callees, status. - Mark sound engine, reset and NMI code. These touch hardware most and need special care.
- Write a coverage command that prints how many routines are ported, per bank.
Prompt for ClaudeGOAL: Write `tools/ledger.py` that builds `notes/routines.toml` from the linker label file and the source, and `tools/coverage.py` that prints totals per bank. READ FIRST: `CLAUDE.md`, `notes/rom-map.md`, `build/game.lbl`. FENCE: Edit only `tools/ledger.py`, `tools/coverage.py` and `notes/routines.toml`. Do not change source files. CHECK: Run `python tools/ledger.py` and `python tools/coverage.py`. Done means every label at a routine start appears once, the sizes add up to the code size of each bank, and `python tools/check.py` still prints PASS. BUDGET: Stop after 3 failed tries. IF BLOCKED: Stop. Tell me which routines the script cannot size and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: coverage output. 3) Files changed. 4) Odd cases. 5) New parking-lot items.
Done whennotes/routines.tomllists every routine with size and status, andtools/coverage.pyprints per-bank totals.Proof this works: z2rs, FF1 Recomp
- Get labels from the linker with
Build the test rig that compares C with the original
Your C has no byte check, so this rig is your proof. It runs a routine in the original game and records RAM and every hardware access. Then it runs your C version on the same input and compares.
- Record fixtures from a real run. Save RAM before and after each routine call, plus every PPU, APU, controller and mapper access in order.
- Use an emulator you can script. The Boulder Dash port uses its own small Python emulator. z2rs runs the ROM in
tetanes-corebeside its port. - Test the rig on itself first: replay the original against the original and expect zero differences.
- Write known emulator quirks as named exceptions, not as a looser compare.
- Make tests that need the ROM skip cleanly when no ROM path is set.
Prompt for ClaudeGOAL: Build `tests/ramdiff` that replays recorded calls of one routine through the C port and compares RAM and the ordered list of hardware accesses. READ FIRST: `CLAUDE.md`, `SCOPE.md` test method, the Boulder Dash README section "How the port is checked" at https://github.com/Kryyto/boulder-dash-nes-decomp. FENCE: Edit only `tools/record.py`, `tests/` and `port/hw/`. Do not port any game routine yet. CHECK: Run `python tools/record.py <ROM_PATH> build/fixtures.bin` then `ctest --test-dir build --output-on-failure`. Done means the self-test (original against original) passes with zero differences, and a planted wrong write fails. BUDGET: Stop after 3 failed tries on the same problem. IF BLOCKED: Stop. Tell me what the recorder cannot capture. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: self-test output and the planted failure. 3) Files changed. 4) Gaps in what is recorded. 5) New parking-lot items.
Done whenThe self-test passes with zero differences, and a planted wrong write makes it fail.Proof this works: https://github.com/Kryyto/boulder-dash-nes-decomp, z2rs
First light: port one small routine
You port one short routine end to end. It proves the whole chain works: ledger, C file, fixtures and compare. Pick a routine with no hardware access and few callers.
- Pick a short routine from
notes/routines.tomlwith statustodoand no PPU or APU writes. - Write it in C with the same integer sizes, the same order of writes, and the same bugs. Name the original routine in a comment.
- Run its fixtures through
tests/ramdiff. - Set its status to
portedonly when the test passes.
Prompt for ClaudeGOAL: Port routine `<ROUTINE>` (bank <BANK>, `<ADDRESS>`) to C so its fixtures pass. READ FIRST: `CLAUDE.md`, the routine's source in `src/`, its line in `notes/routines.toml`, `port/README.md`. FENCE: Edit only `port/src/<FILE>.c`, its header, and this routine's line in `notes/routines.toml`. Do not change tests, fixtures or other routines. CHECK: Run `ctest --test-dir build -R <ROUTINE> --output-on-failure`. Done means every recorded call passes with zero differences. BUDGET: Stop after 5 failed tries. IF BLOCKED: Stop. Do not loosen the compare. Tell me the first differing byte or access and your theory. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test output. 3) Files changed. 4) Any original bug you kept. 5) New parking-lot items.
Done whenThe routine's fixtures pass with zero differences and its ledger status isported.Proof this works: FF1 Recomp, https://github.com/Kryyto/boulder-dash-nes-decomp
- Pick a short routine from
Grind: port routines in parallel, bank by bank
Now you run many small cards. Each worker ports one routine or one small group in its own worktree. Leaves of the call tree go first, so callers can be tested on ported parts.
- Have a script pick the next routines: status
todo, all callees already ported, smallest first. - Give each worker one card and one worktree:
claude --worktree <name>. - Merge one routine at a time. Run the full
ctestandpython tools/check.pyafter every merge. - Keep data tables in the ROM. Read them at start-up through offsets, never typed by hand.
- Run
python tools/coverage.pydaily and record the totals inPROGRESS.md.
Prompt for ClaudeGOAL: Port the routines listed below to C so each one's fixtures pass: <ROUTINE_LIST>. READ FIRST: `CLAUDE.md`, each routine's source in `src/`, `notes/routines.toml`, `notes/porting-rules.md`. FENCE: Work only in this worktree. Edit only the `port/src/` files named in the list and their lines in `notes/routines.toml`. Never type game data into C; read it from the ROM by offset. CHECK: Run `ctest --test-dir build --output-on-failure` and `python tools/check.py --rom <ROM_PATH>`. Done means every listed routine passes, no other test fails, and the ROM check prints PASS. BUDGET: Stop after 5 failed tries on one routine; mark it `hard` and move to the next. IF BLOCKED: Stop on that routine. Do not loosen a compare or skip a fixture. Note the first difference in `notes/routines.toml`. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test summary line. 3) Files changed. 4) Routines marked `hard`. 5) New parking-lot items.
Done whentools/coverage.pyshows every routine ported, and the full test run passes on the merged branch.Proof this works: z2rs, FF1 Recomp, https://github.com/RodoLy/HarvestMoonSnesDecomp
- Have a script pick the next routines: status
Harden: run the whole game on C alone
You remove the last link to the original code. The game must boot and play on your C only. Long scripted runs and screenshots prove it matches the original.
- Turn off any fallback that runs original 6502 code. The build must fail loudly if a routine is missing.
- Replay long inputs, such as published speedrun movies, and compare RAM and pictures frame by frame.
- Feed random input from saved states to reach rare states. Fix and add a test for each difference.
- Write scripted scenes with PASS or FAIL and a screenshot, as FF1 Recomp does with
ff1-screenshots.
Prompt for ClaudeGOAL: Make the port boot and play <ROUTE_NAME> on C alone, with no original 6502 code running. READ FIRST: `CLAUDE.md`, `PROGRESS.md`, `routes/<ROUTE_NAME>.txt`, `notes/routines.toml`. FENCE: Edit only `port/`, `tests/scenes/` and `notes/`. Do not change fixtures or the compare code. CHECK: Run `ctest --test-dir build --output-on-failure` and `build/<GAME>-scenes <ROM_PATH>`. Done means all tests pass and every scene prints PASS with the interpreter switched off. BUDGET: Stop after 3 failed tries on one scene. IF BLOCKED: Stop. Tell me the first frame that differs and which routine wrote it. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test and scene output. 3) Files changed. 4) Scenes not yet covered. 5) New parking-lot items.
Done whenEvery scripted scene passes with no original code running, on a clean clone.Proof this works: FF1 Recomp, z2rs, https://github.com/Kryyto/boulder-dash-nes-decomp
Port: move the console parts behind one layer
You keep game logic free of SDL and the screen. Only a few files touch the window, sound and pads. Then you build for a second system and run the same scenes there.
- Keep SDL calls in a few files only. FF1 Recomp allows SDL in
main.c,input.candaudio.conly. - Read the game's RAM as a C struct. Keep the picture model apart from the code that draws to the screen.
- Build for Linux in WSL first:
cmake -S port -B build-linuxandcmake --build build-linux. - Run the same scenes and tests on the new build. Then try other targets, as z2rs did for browser and Android.
Prompt for ClaudeGOAL: Build the port for Linux in WSL and pass the same tests and scenes as on Windows. READ FIRST: `CLAUDE.md`, `port/README.md`, `PROGRESS.md`. FENCE: Edit only `port/platform/`, `CMakeLists.txt` files and `notes/`. Do not change game logic files or tests. CHECK: In WSL run `cmake -S port -B build-linux && cmake --build build-linux && ctest --test-dir build-linux --output-on-failure`. Done means every test passes, and the scenes print PASS. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Tell me which file pulls in a Windows-only call. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test output from WSL. 3) Files changed. 4) Platform calls left in game code. 5) New parking-lot items.
Done whenThe Linux build in WSL passes every test and scene that the Windows build passes.Proof this works: FF1 Recomp, z2rs, OpenMadoola
- Keep SDL calls in a few files only. FF1 Recomp allows SDL in
Tools you need
| Tool | What it does | How to get it | How we know |
|---|---|---|---|
| Git Required both | Gets NESRecomp and its submodules, and keeps your project's history. Worktrees for parallel work need it too.Path A Path B | Download Git for Windows from https://git-scm.com/downloads/win. WSL: `sudo apt install git` | nesrecomp docs/GETTING_STARTED.md asks for Git for Windows (read 2026-11-13); download page answers 200; Ubuntu 24.04 package git 2.43.0. |
| NESRecomp Required Windows | Turns NES 6502 code into C and gives you a runner for the picture chip, sound, mappers, input and saves. It is in beta. Path A | Download `nesrecomp-cli-windows-x86_64.zip` from https://github.com/mstan/nesrecomp/releases, or `git clone --recurse-submodules https://github.com/mstan/nesrecomp.git` and build with CMake | README, docs/GETTING_STARTED.md and release v0.5.0 (read 2026-11-13). Windows x64 is the main platform; macOS is experimental; Linux 'likely works'. Used by: Dr. Mario NES Recomp, Yoshi NES Recomp, Pac Man Recomp, Shadowgreat, Dragon Warrior IV Recomp. |
| recomp-ui Helpful both | A launcher and in-game settings menu shared by many recomp projects. You add it as a git submodule. Path A | `git submodule add https://github.com/RetroPortingToolKit/recomp-ui recomp-ui` | README (read 2026-11-13); the old mstan/recomp-ui address now points here. Used by: Dr. Mario NES Recomp, Pac Man Recomp, Shadowgreat. |
| Visual Studio 2022 (C++ desktop workload) Required Windows | The C and C++ compiler that NESRecomp is tested with on Windows. Its CMake tools come with it.Path A Path B | Install Visual Studio 2022 or its Build Tools with **Desktop development with C++** and the CMake tools, then open Developer PowerShell for VS 2022 | nesrecomp docs/GETTING_STARTED.md (read 2026-11-13). Used by: Dr. Mario NES Recomp, Yoshi NES Recomp, Bubble Bobble Recomp. |
| CMake Required both | Makes the build files for the recompiler, the game and your port. NESRecomp needs version 3.20 or newer.Path A Path B | Comes with the Visual Studio CMake tools; in WSL: `sudo apt install cmake` | nesrecomp README (CMake 3.20+), read 2026-11-13; Ubuntu 24.04 package cmake 3.28.3 on packages.ubuntu.com. Used by every reviewed C or C++ project. |
| Ninja Helpful both | A fast build runner that CMake can drive. Projects using llvm-mingw build with it.Path A | Download `ninja-win.zip` from https://github.com/ninja-build/ninja/releases and put `ninja.exe` on `PATH` | Release v1.13.2 assets (read 2026-11-13). Used by: Shadowgreat, Pac Man Recomp, Dragon Warrior IV Recomp. |
| llvm-mingw Helpful Windows | A Clang compiler for Windows that some NES recomps use instead of Visual Studio. Path A | Download `llvm-mingw-20261104-ucrt-x86_64.zip` from https://github.com/mstorsjo/llvm-mingw/releases and unzip it | README and release 20261104 (read 2026-11-13). Used by: Shadowgreat, Pac Man Recomp. |
| Mesen (Community Edition) Required both | A NES emulator with a Lua script window. NESRecomp's docs use it as an outside picture check. It is your trusted copy of the game for every compare.Path A Path B | Download `Mesen_2.2.1_Windows.zip` from https://github.com/nesdev-org/MesenCE/releases and unzip it | MesenCE README and release 2.2.1 (read 2026-11-13); Lua script window per nesrecomp tools/mesen_mcp.lua; picture check per nesrecomp runner/cyc/README.md. The old SourMesen/Mesen2 repo is archived and points here. Used by: Shadowgreat, Dr. Mario NES Recomp, z2rs, zelda1-disassembly. |
| Ghidra Helpful both | A free reverse-engineering tool. It shows the 6502 code of each bank, lets you name routines, and serves Claude over MCP. Path A Path B | Install JDK 25 64-bit. From https://github.com/NationalSecurityAgency/ghidra/releases, download Ghidra 12.1 if you want `GhidraNes` (no build for 12.2 yet), unzip, run `ghidraRun.bat` | README, release list (12.2 newest; 12.1 and 12.1.4 also listed) read 2026-11-13. Used by: Dr. Mario NES Recomp and Yoshi NES Recomp (through an MCP server). |
| GhidraNes Helpful both | A Ghidra add-on that loads .nes files and maps each bank to its own memory block. You fix bank-switch jumps by hand.Path A Path B | Download the zip for your Ghidra version from https://github.com/kylewlacy/GhidraNes/releases, then Ghidra **File > Install Extensions** | README and release v20260531 (read 2026-11-13). Its newest build targets Ghidra 12.1, so pick that Ghidra version if you need it. |
| GhidraMCP Helpful both | An MCP server that lets Claude ask Ghidra for code, names and cross references. MCP is the way Claude Code talks to outside tools. Path A Path B | Install `uv` (https://docs.astral.sh/uv/). Install the release zip with Ghidra **File > Install Extensions** and enable `GhidraMCPPlugin` under **File > Configure > Developer**. Then add the MCP server: `claude mcp add ghidra -- uvx --from git+https://github.com/LaurieWired/GhidraMCP ghidra-mcp --ghidra-server http://127.0.0.1:8080/` | README and release 2.0 (read 2026-11-13). Its README asks for Ghidra 12.1.4. Dr. Mario NES Recomp and Yoshi NES Recomp used a Ghidra MCP server; which one is not stated. |
| cc65 (ca65 and ld65) Required both | The 6502 assembler and linker the NES community uses to rebuild games from source. Your Path B byte check runs on them. Path B Path A | Windows: download `cc65-snapshot-win64.zip` (linked from the cc65 README). WSL: `sudo apt install cc65` | README (read 2026-11-13); Ubuntu 24.04 package cc65 2.19-1 on packages.ubuntu.com. Used by: FF1 Recomp, z2rs, zelda1-disassembly. |
| retrodisasm Helpful both | A tracing disassembler that writes NES assembly which rebuilds the exact same ROM. It can check the rebuild for you. Path B | Download `retrodisasm_v0.4.1_windows_64bit.zip` from https://github.com/retroenv/retrodisasm/releases and put `retrodisasm.exe` on `PATH`. (With Go 1.25 or newer you can instead run `go install github.com/retroenv/retrodisasm@latest`.) | README and docs/nes.md (read 2026-11-13). Banking support is marked experimental; unsupported mappers are refused (docs/nes.md). Not used by a reviewed project. |
| Python 3 Required both | Runs the helper scripts: symbol conversion, fixture recording, ledgers and packaging. Path A Path B | Install Python 3.11 or newer from https://www.python.org/downloads/ (`NESRecomp` asks for 3.11 or newer). WSL: `sudo apt install python3` | nesrecomp docs/GETTING_STARTED.md (read 2026-11-13). Used by: Dragon Warrior IV Recomp, FF1 Recomp, Final Fantasy NES Recomp. |
| SDL2 Required both | A library for the window, sound and game pads. Your Path B port uses it on every system. NESRecomp ships its own copy for Path A.Path B | Windows: download `SDL2-devel-2.32.10-VC.zip` from https://github.com/libsdl-org/SDL/releases and point CMake at it. WSL: `sudo apt install libsdl2-dev` | SDL release list (release-2.32.10 assets) and Ubuntu 24.04 package libsdl2-dev 2.30.0, read 2026-11-13. nesrecomp bundles SDL2 under runner/external/SDL2. Used by: OpenMadoola (SDL2 or SDL3), Boulder Dash port (SDL2); FF1 Recomp uses SDL3. |
Skills and add-ons
Read any skill before you install it. A skill is a set of instructions that Claude will follow. See Tools and Skills for the full list and safety notes.
static-recomp-scope-plan
Makes you write down the exact ROM, the scenes to test and the pass marks before work starts. That fits the Path A scope step well.
npx skills add bgyss/switchrecomp --skill static-recomp-scope-plan -a claude-code
static-recomp-av-compare
Scores your build's picture and sound against your own recordings of the real game. Use it in the harden step. It needs ffmpeg and recordings you made. Its script paths need a fix for Claude Code.
npx skills add bgyss/switchrecomp --skill static-recomp-av-compare -a claude-code
ghidra-headless
Runs Ghidra with no window and saves functions and code to files. Use it if the MCP server gives you trouble. Install GhidraNes first so Ghidra can read .nes files. On Windows it needs Git Bash and a GHIDRA_HOME you set.
npx skills add trailofbits/skills-curated --skill ghidra-headless -a claude-code
verification-before-completion
Claude may not say done without fresh proof. On NES that means a pasted miss count, route result or fixture result.
npx skills add obra/superpowers --skill verification-before-completion -a claude-code
systematic-debugging
No fix until the cause is found. It matches the Yoshi rule: find the first wrong byte before you change anything.
npx skills add obra/superpowers --skill systematic-debugging -a claude-code
handoff
Writes a short note so a new session can carry on. Tell it to save into notes/handoff.md in your project, not the temp folder.
npx skills add mattpocock/skills --skill handoff -a claude-code
dispatching-parallel-agents
Shows the lead session how to brief workers and when not to split. Use it for the Path B routine grind.
npx skills add obra/superpowers --skill dispatching-parallel-agents -a claude-code
Ghidra MCP server (GhidraMCP)
Dr. Mario and Yoshi used a Ghidra MCP server so Claude could look up any address instead of guessing. Check that it answers before each session.
claude mcp add ghidra -- uvx --from git+https://github.com/LaurieWired/GhidraMCP ghidra-mcp --ghidra-server http://127.0.0.1:8080/
Mesen MCP bridge (in NESRecomp)
Lets Claude read Mesen's emulator state while it compares your build with the real game. It is listed in the NESRecomp tools, but its commands were not run here.
Run `pip install mcp`. Load `nesrecomp/tools/mesen_mcp.lua` in Mesen's script window with **Allow I/O access** checked. Then add `python nesrecomp/tools/mesen_mcp_server.py` as a stdio MCP server, as in `nesrecomp/.mcp.json.example`. It trades files in `C:/temp`.
nes-miss-loop
Every Path A session runs the same loop the same way. This is the loop from the Dr. Mario handoff file, written down once.
A project skill in `.claude/skills/nes-miss-loop/SKILL.md`. It holds: the route list, the exact build and run commands, where `dispatch_misses.log` lives, the five `game.toml` fix types with one example each, the rule never to edit `generated/`, and a line to append to `notes/findings.md` per fix.
nes-routine-port
Path B workers port one routine at a time. The skill keeps every worker on the same rules as FF1 Recomp and Boulder Dash.
A project skill in `.claude/skills/nes-routine-port/SKILL.md`. It holds: how to read one routine from `src/`, the C style rules (same integer sizes, same write order, keep bugs, name the original routine), the `ctest -R <ROUTINE>` command, the five-try limit, and how to set the status in `notes/routines.toml`.
Claude setup for this system
On Path A, one unit is one dispatch miss or one first difference from Mesen. On Path B, one unit is one routine, or a few small routines from one bank. Every unit has its own check: the miss log, a route result, or a routine's fixtures. Keep game.toml and the merged branch under one owner, so only the lead changes them. Workers report the change they want, with proof.
Which model and effort for each job
The model and effort names come from one shared table, so they stay the same on every guide. See Claude Setup for the reasons.
| Job | What it means on this system | Max 20x (now called Ultra) | Max 5x (now called Max) |
|---|---|---|---|
| Plan the project | Pick the ROM version and mapper, look for a sister NESRecomp port or a disassembly, and write SCOPE.md. | Fable 5.2 effort highOne session. This choice shapes months of work. | Opus 5.5 effort highUse Fable 5.2 for this one session if you have weekly room. |
| Set up the project | Build NESRecomp or the cc65 rebuild, write tools/check.ps1 or tools/check.py, the pre-commit hook and CLAUDE.md. | Opus 5.5 effort medium | Sonnet 5.5 effort medium |
| Study the game file | Find the bank-switch routine, jump-table tricks and data areas in Ghidra. On Path B, name routines and fill notes/routines.toml. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for function edges and data layouts. A mistake there spreads. |
| Bulk units | Path B: port one small routine whose callees are done. Path A: add one extra_func for a clear miss. | Sonnet 5.5 effort mediumHaiku 5.5 for the plainest units. | Sonnet 5.5 effort mediumFewer workers at once. Haiku 5.5 for the plainest units. |
| Hard units | Routines that failed five tries, sound engines, code that plays with the stack, and timing-bound PPU writes. | Fable 5.2 effort highGo to xhigh only after a failed try at high. | Opus 5.5 effort highSend only the last few that still fail to Fable 5.2. |
| Build the runtime | Path A: desktop host, launcher and packaging. Path B: the picture model, sound and the platform layer for each new system. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for timing and graphics bugs. |
| Find a bug | Find the first byte that differs from Mesen, trace the instruction that wrote it, and name the cause before any fix. | Opus 5.5 effort highAfter two failed tries with full evidence, move to Fable 5.2. | Opus 5.5 effort highOne bug at a time. |
| Check the work | A fresh session reruns the check on a clean clone. It looks for edited baselines, forced interpreter use, and looser compares. | Opus 5.5 effort mediumAlways a new session, never the one that did the work. | Sonnet 5.5 effort highAlways a new session, never the one that did the work. |
| Keep the records | Update PROGRESS.md, notes/findings.md, notes/dead-ends.md and the coverage totals after each card. | Haiku 5.5 effort medium | Haiku 5.5 effort medium |
How many sessions at once
Max 20x (now called Ultra)
Path A: one lead plus two or three workers, each on one miss or one bug in its own worktree. Workers do not edit game.toml; they send the change to the lead. Path B: one lead plus four to six workers, each on routines from a different bank, plus one verify session a day.
Max 5x (now called Max)
Path A: one lead and one worker at a time, one bug each. Path B: one lead plus one or two workers on routines. Run the verify session every few days, not daily.
Lines for this project's CLAUDE.md
- Read `SCOPE.md`, `PROGRESS.md` and `notes/dead-ends.md` at the start of every session. - Never edit or read whole any file in `generated/`. Fix `game.toml`, the recompiler or the runner, then regenerate. - The one check is `powershell -File tools/check.ps1 -Rom <ROM_PATH>` (Path B: `python tools/check.py --rom <ROM_PATH>` and `ctest --test-dir build`). Done means it prints PASS. Paste the output. - `nesrecomp/` and `recomp-ui/` are pinned submodules. Never commit inside them. Framework fixes go in `patches/<submodule>/NNNN-<name>.patch`. - No guessing. Every claim about 6502 code cites a ROM address seen in Ghidra, the disassembly, or an emulator diff. Say 'I don't know yet' otherwise. - Before any fix, name the cause as one of: codegen, runner, timing, config. - No printf debugging. Use the TCP debug server (`--tcp <PORT>`) for runtime state. - When you compare with Mesen, first find the frame offset from a frame before any button press. Never assume frame N is the same in both. - Test runs use a copied save file and an empty `--mods-root`. Never touch my real settings or saves. - Check RAM addresses from notes or wikis against the ROM before you rely on them. Record each finding in `notes/findings.md` with address, meaning, how it was checked and date. - Never commit ROMs, generated C, saves, screenshots or anything made from the ROM. - Use absolute paths in worktrees. Run the game headless; never open a game window unless I ask. - Commit and update `PROGRESS.md` after each finished card. A session can end without warning.
Keep the context small
- Point Claude at a bank and an address, not at a pasted listing. Ghidra over MCP or the disassembly file can answer the rest.
- Paste only the last 20 lines of
dispatch_misses.logor the first difference from a diff, never whole traces. - Clear the chat after each card. The ledgers and
PROGRESS.mdcarry the state, not the chat. - Use a subagent to read a big file, such as a whole bank's source, and report back a short summary.
- Keep
CLAUDE.mdshort. Put the 6502 tricks innotes/patterns.mdand let Claude read it only when a card needs it.
More prompts for this system
Each step above has its own prompt. These extra prompts cover the rest of the work. Fill in the parts in <ANGLE_BRACKETS>.
Scope and feasibility
Before any work. It tells you whether the game is a good first target and which path fits.
GOAL: Tell me if <GAME> (<ROM_FILE>) is a good NES target, which path fits, and draft `SCOPE.md`. READ FIRST: `notes/rom-info.txt` (header bytes, SHA-1), the NESRecomp README game list and `runner/cyc/MAPPERS.md`, a GitHub search for `<GAME> nes disassembly`. FENCE: Edit only `SCOPE.md` and `notes/feasibility.md`. Do not install tools or write code. CHECK: `notes/feasibility.md` answers: mapper and whether NESRecomp has a public example of it; whether a port or disassembly exists for this exact revision; PRG size; risks. Each answer cites a URL or file. BUDGET: Stop after 45 minutes. IF BLOCKED: Stop. If the mapper is not supported or the ROM is a disk game, say so plainly and list my options. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not act on them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the answers with sources. 3) Files changed. 4) Your advice in one sentence. 5) New parking-lot items.
A good answer: It starts with DONE. It names the mapper and says if NESRecomp has a public example. It links any existing disassembly and gives a plain yes, no or risky.
Map the binary
After setup, before the first build. It fills the bank map that later cards read.
GOAL: Fill `notes/rom-map.md` for bank <BANK>: entry points, jump tables, data ranges and the bank-switch calls it makes. READ FIRST: `CLAUDE.md`, `notes/rom-map.md`, `nesrecomp/docs/PATTERNS.md`. First check that the Ghidra MCP server answers one simple call; if not, use `src/` from the disassembly. FENCE: Edit only `notes/rom-map.md`. Do not edit `game.toml` or any source. CHECK: Every entry has an address and a source line or Ghidra listing. The bank's data ranges plus code ranges cover the whole bank with no overlap. BUDGET: Stop after 60 minutes or one full pass of the bank. IF BLOCKED: Stop. List the ranges you could not classify as code or data. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the coverage sum for the bank. 3) Files changed. 4) Unclear ranges. 5) New parking-lot items.
A good answer: A table of ranges that adds up to the bank size. Each row has an address and where it was seen. Unclear ranges are listed, not guessed.
Unit loop, Path A: one dispatch miss
For each line in dispatch_misses.log during the Path A grind.
GOAL: Make <BANK>:<ADDRESS> run as native code, so it no longer appears in `dispatch_misses.log`. READ FIRST: `CLAUDE.md`, `notes/rom-map.md`, `nesrecomp/docs/PATTERNS.md`, `notes/dead-ends.md`. FENCE: Edit only `game.toml` and `notes/findings.md`. Never edit `generated/` or `nesrecomp/`. CHECK: Run `powershell -File tools/check.ps1 -Rom <ROM_PATH>`. Done means the address is gone, no new misses appear, and all routes PASS. BUDGET: Stop after 3 tries. IF BLOCKED: Stop. Do not add a `[force_interp]` entry to hide it. Tell me what the code does and why the fixes failed. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: miss counts before and after. 3) Files changed. 4) Which `game.toml` entry type you used and why. 5) New parking-lot items.
A good answer: DONE with the miss count dropping by at least one. One game.toml entry added, with a reason tied to a Ghidra address.
Unit loop, Path B: one routine
For each routine in the Path B grind.
GOAL: Port `<ROUTINE>` (bank <BANK>, `<ADDRESS>`) to C so all its recorded calls match the original. READ FIRST: `CLAUDE.md`, `notes/porting-rules.md`, the routine in `src/`, its line in `notes/routines.toml`. FENCE: Edit only `port/src/<FILE>.c`, its header, and this routine's ledger line. Do not touch tests, fixtures or other routines. Do not type game data into C. CHECK: Run `ctest --test-dir build -R <ROUTINE> --output-on-failure`. Done means zero differences on every call. BUDGET: Stop after 5 tries; then set status `hard` with the first difference. IF BLOCKED: Stop. Do not loosen the compare. Tell me the first differing byte or hardware access. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test output. 3) Files changed. 4) Original bugs kept. 5) New parking-lot items.
A good answer: DONE with a passing test line. The C names the original routine and keeps its quirks. The ledger shows ported.
Batch dispatch (lead session)
When you run several workers at once on Path B, or on Path A bugs.
GOAL: Pick the next <N> units and start one worker per unit, each in its own worktree. READ FIRST: `CLAUDE.md`, `PROGRESS.md`, `notes/routines.toml` (or `dispatch_misses.log`), `notes/dead-ends.md`. FENCE: You only plan, dispatch and merge. Do not port or fix anything yourself. Only you edit `game.toml`, `notes/routines.toml` and the main branch. CHECK: After each merge run `ctest --test-dir build --output-on-failure` and `python tools/check.py --rom <ROM_PATH>` (Path A: `tools/check.ps1`). Done means all <N> units are merged or marked `hard`, and the checks PASS on the merged branch. BUDGET: Stop when <N> units are handled or after <HOURS> hours. IF BLOCKED: Stop. Tell me which merges failed and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output on the merged branch. 3) Units merged, units marked `hard`. 4) Coverage totals. 5) New parking-lot items.
A good answer: A table of units with merged or hard, a passing check on the merged branch, and the new coverage number.
Debug with evidence
When the build draws wrong, freezes or plays differently from Mesen.
GOAL: Find the cause of <SYMPTOM> on route `<ROUTE_FILE>` at about frame <FRAME>. Do not fix it yet. READ FIRST: `CLAUDE.md`, `notes/findings.md`, `notes/dead-ends.md`. FENCE: Read-only, except `notes/debug-<SYMPTOM>.md`. No printf logging; use the TCP debug server and Ghidra. CHECK: The note names the first byte that differs from Mesen, the frame, the instruction that wrote it, and the cause class: codegen, runner, timing or config. For pictures, say if tile shapes, positions or colors are wrong. BUDGET: Stop after 60 minutes. IF BLOCKED: Stop. Tell me what data you would need to go further. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the diff lines. 3) Files changed. 4) The proposed fix and where it goes. 5) New parking-lot items.
A good answer: A first differing byte with a frame number and a writing instruction. One cause class. No code changed yet.
Verify (hunt for cheating)
In a fresh session after every few merged cards, and before any release.
GOAL: Check that the work in <COMMIT_RANGE> is real and follows the rules. READ FIRST: `CLAUDE.md`, `SCOPE.md`, `git diff <COMMIT_RANGE> --stat`. FENCE: Read-only. Do not fix anything. Write findings only to `notes/verify-<DATE>.md`. CHECK: On a clean clone, run the full check. Then look for: edits in `generated/`, `nesrecomp/` or `tests/baseline/`; new `[force_interp]` or `nop_jsr` entries with no reason; loosened compares or skipped fixtures; game data typed into C; ROM bytes in git. BUDGET: Stop after 45 minutes. IF BLOCKED: Stop. Tell me what you could not check. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the check output from the clean clone. 3) Files changed: only the verify note. 4) Each rule broken, with file and line. 5) New parking-lot items.
A good answer: A clean-clone PASS or FAIL, then a list of rule breaks with file and line, or a plain 'none found'.
Status report
Once a week, or before you decide what to do next.
GOAL: Give me a status report from the ledgers, with numbers only. READ FIRST: `PROGRESS.md`, `notes/routines.toml` or `dispatch_misses.log`, `notes/parking-lot.md`. FENCE: Read-only, except `PROGRESS.md` where you add a dated status block. CHECK: Run `python tools/coverage.py` (Path B) or count the lines of `dispatch_misses.log` and run `tools/check.ps1` (Path A). Every number in the report comes from that output. BUDGET: Stop after 15 minutes. IF BLOCKED: Stop. Tell me which ledger is missing or out of date. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the raw counts. 3) Files changed. 4) Top three blockers. 5) Parking-lot items waiting for me.
A good answer: Five to ten lines of numbers: routines ported or misses left, routes passing, open hard units, top blockers. No padding.
Session handoff
At the end of every session, or when the chat gets long.
GOAL: Write `notes/handoff.md` so a new session can carry on with no chat history. READ FIRST: `PROGRESS.md`, `git log -10 --oneline`, `notes/findings.md`. FENCE: Edit only `notes/handoff.md` and `PROGRESS.md`. CHECK: The note has: what is PROVEN (with the check output), what is NOT the problem, the one next question, the files the next session may and may not touch, and the exact commands to run first. BUDGET: Stop after 10 minutes. IF BLOCKED: Stop. Tell me what you could not confirm. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: paste `notes/handoff.md`. 3) Files changed. 4) The next question. 5) New parking-lot items.
A good answer: A short note with proven facts, ruled-out causes and one next question, like the Yoshi handoff files.
Blocked or cannot deliver
When a card keeps failing, or Claude keeps saying 'should work'.
GOAL: Give me a plain answer about <UNIT>: can it be done in this project as scoped, yes or no? READ FIRST: `SCOPE.md`, `notes/dead-ends.md`, the last report for <UNIT>. FENCE: Read-only, except `notes/dead-ends.md`. CHECK: Your answer lists each thing tried, the result of each with proof, and the reason it fails. It names whether the blocker is in my game, the recompiler, the runner, or the test rig. BUDGET: Stop after 20 minutes. IF BLOCKED: Say BLOCKED and list what you would need from me. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the evidence for each try. 3) Files changed. 4) Two or three options with cost and risk. 5) New parking-lot items.
A good answer: A one-word answer first, then the evidence, then a few real options. No 'it should work now'.
Scope change request
When Claude needs to edit something outside the fence, such as the recompiler itself.
GOAL: Ask me before you leave the fence. Explain the change you want and why. READ FIRST: `SCOPE.md`, `CLAUDE.md`, the card you are working on. FENCE: Do not edit anything. Write the request in `notes/scope-requests.md` only. CHECK: The request names the file to change, the exact change, the proof that the fence blocks the card, and how we would undo it. For `nesrecomp/` it says if a patch file in `patches/` would do. BUDGET: Stop after 15 minutes. IF BLOCKED: Stop and wait for my yes or no. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) BLOCKED until I answer. 2) Proof: the evidence. 3) Files changed: only the request. 4) Risk if I say yes. 5) New parking-lot items.
A good answer: A short request with the file, the change and the proof. Claude waits for your answer and changes nothing.
Keep Claude on task
These are the detours Claude tends to take on this system, and the rule that stops each one. The full method is on Stay on Task.
| The detour | The rule to give Claude |
|---|---|
| Claude edits the generated C to fix a bug fast. The next build wipes the fix and the real bug stays. | Never edit generated/. Fix game.toml, the recompiler or the runner, then regenerate. Add a PreToolUse hook that blocks edits there. |
| Claude says what an address 'probably' does without looking. It then builds fixes on that story. | No guessing. Cite the Ghidra listing, the disassembly line or an emulator diff for every claim. Otherwise say 'I don't know yet'. |
| Claude hides a dispatch miss by forcing the address to run in the interpreter, then reports the game as done. | A [force_interp] or nop_jsr entry needs my yes and a reason in notes/findings.md. Done means zero misses with no new entries of that kind. |
| Claude patches the shared framework in place for one game. Upstream changes and other games break. | Never commit inside nesrecomp/ or recomp-ui/. Put framework fixes in numbered patch files and rerun every route. |
| Claude takes a screenshot that looks right as proof the game works. | A picture is not proof. Done means zero misses on every route, no RAM difference from Mesen, and a PASS from the check. |
| Claude floods the disk with printf logs and huge trace files. | No printf debugging. Use the TCP debug server. Remove any temporary log before you report. |
| On Path B, Claude writes a formula that seems close instead of porting the routine as written. | Port routines, not features. Keep the same integer sizes, write order and bugs. Invented formulas are bugs. |
| On Path B, Claude types game tables into C or loosens the compare to make a test pass. | Read all data from the ROM by offset. Never change the compare or fixtures. A failing routine is marked hard. |
Problems other projects hit
Data placed right after a JSR was read as code. That made garbage jump targets, such as $4CA6 in PPU register space.
Fix: Declare those routines in game.toml as [[inline_pointer]] or [[inline_dispatch]]. Then regenerate.
Seen in: Dr. Mario NES Recomp, YoshisCookieRecomp
The wrong ROM revision was used. Routines sat at shifted addresses, or the build broke before the title screen.
Fix: Pin the hash of the data after the 16-byte header. Keep one config per revision, and say which versions do not work.
Seen in: Dr. Mario NES Recomp, Yoshi NES Recomp, Pac Man Recomp
The function finder walked from real code into ROM data. It made huge functions full of illegal opcodes.
Fix: Mark data with [[data_region]] in game.toml. Check each new function's size against your map.
Seen in: Yoshi NES Recomp
Save states from one backend do not load in another, and old saves broke when the ROM check changed.
Fix: Tell players before a backend change. Make fresh save states for your tests after each such change.
Seen in: Dr. Mario NES Recomp, YoshisCookieRecomp, Yoshi NES Recomp
A Clang build for Windows would not start on other PCs because DLLs were missing.
Fix: Link libc++ and libunwind statically, or copy libc++.dll and libunwind.dll beside the program.
Seen in: Shadowgreat, Pac Man Recomp
An unsigned launcher that downloads build tools was flagged by Windows Defender. Long folder paths broke the build.
Fix: Check each download against a fixed SHA-256, tell players what to expect, and suggest a short folder like C:\Games\<name>.
Seen in: Shadowgreat
The reference emulator had its own drawing quirks, such as at the right edge and on line 0.
Fix: Test the oracle against itself first. Write each quirk down as a named exception, not as a looser compare.
Seen in: z2rs
Game data ended up in a public repo. The project had to restart its history from zero.
Fix: Add a pre-commit hook that refuses ROMs and any file whose hash matches the ROM, from the first day, as z2rs and Shadowgreat do.
Seen in: FF1 Recomp
Agent notes went stale. Yoshi's CLAUDE.md and Dr. Mario's HANDOFF.md named old config files and folder paths from the author's own PC.
Fix: Update CLAUDE.md in the same commit as any tool or file change. Have the verify session check it.
Seen in: Yoshi NES Recomp, Dr. Mario NES Recomp
NESRecomp uses a noncommercial licence. Projects built on it may not be sold.
Fix: Read the licence and PERMISSIONS.md before you plan to take any money.
Seen in: Shadowgreat, Pac Man Recomp, Yoshi NES Recomp
How long it takes
| Max 20x (now called Ultra) | Max 5x (now called Max) | One person, no AI | How sure | |
|---|---|---|---|---|
| Path A: running on PC | 1 to 4 weeks | 2 to 10 weeks | 3 to 12 months | medium confidence |
| Path B: full decomp | 6 to 20 weeks | 3 to 12 months | 1 to 3 years | low confidence |
Read this first
All ranges assume one medium-sized cartridge game and one person giving 10 to 15 hours a week. Path A is medium confidence. Three dated AI-assisted NES projects reached a playable release in 3 to 5 days. But their authors already knew the tools. Only Pac Man Recomp says the whole game was play-tested. Path B is low confidence. No dated AI-assisted full NES port was found, so the Max 20x range comes from a SNES project in the same chip family. The no-AI ranges come from two hobby projects before agents.
What makes it faster or slower
- Whether a NESRecomp port of a sister game, or a disassembly of your exact ROM, already exists.
- The mapper and how much code is bank-switched. A 32 KB NROM game is far less work than a 512 KB MMC1 game.
- How many jump-table tricks the game uses. Each one is a
game.tomlentry you must find. - How much of the game your routes reach. Untested modes, endings and two-player play hide misses.
- For Path B, how good your test rig is. Without per-routine fixtures, small bugs hide until late.
The facts behind these numbers
| Project | What happened | Link |
|---|---|---|
| Shadowgreat | First commit 2026-11-06, release 1.0.0 on 2026-11-09. One person. The catalog says Claude wrote the code. | source |
| Pac Man Recomp | First commit 2026-09-28, first release 2026-10-03, six releases in four days. One person, with AI coding help. | source |
| Dragon Warrior4NESRecomp | First commit 2026-10-24, first release 2026-10-29 for a 512 KB MMC1 game. Coded with Claude. README says not fully play-tested. | source |
| NESRecomp and Dr. Mario NES Recomp | Framework repo made 2026-03-14; Dr. Mario released 2026-03-28. 30 of 52 Dr. Mario commits are co-authored by Claude. | source |
| z2rs | 97 private commits from 2026-09-02 to 2026-09-18, first release 2026-09-22. Hybrid: unported routines still run on a 6502 interpreter. AI use not stated. | source |
| SuperMarioBros-C (outside, before agents) | First commit 2014-10-10. The 'finally works' commit is 2016-12-04. One person, no AI. | source |
| zelda1-disassembly (outside, before agents) | README says work began in October 2020; first commit 2021-01-22. One person, a full byte-matching disassembly. | source |
| HarvestMoonSnesDecomp (SNES, same chip family) | AI-assisted. Repo made 2026-10-20; first release 2026-10-29 with all 778 routines in C, each checked against the original. | source |
Projects reviewed (14)
We took the top-starred project for each of the top 12 games on RecompLauncher. We added two more that were flagged as made with AI. Final Fantasy has two projects. VKRT is an emulator for several games. No reviewed project came from outside RecompLauncher. Outside repos, such as the Boulder Dash port and the Zelda disassembly, appear only as step or time evidence.
What each project teaches
Super Mario Bros. Remastered
Super Mario Bros.
Started: 2025-09. First release: 2025-09. Code: GDScript.
AI: none stated
Lesson: A remake in a game engine is a different job from a decomp. No 6502 code is used, so physics are only 'close'. It shows how to read graphics from the player's ROM.
ZQuest Classic
The Legend of Zelda
Started: 2016-01. First release: unknown. Code: C++.
AI: yes: docs/ai.md says some maintainers use AI for bugs, tests and chores; AGENTS.md and three Claude skills are in the repo Agent files: AGENTS.md, .claude/skills/cherry-pick-2-55/SKILL.md, .claude/skills/replay-batches/SKILL.md, .claude/skills/zscript/SKILL.md.
Lesson: It is a fan-game engine, not a decomp. Its replay tests with a hash per frame are a model for your regression check.
z2rs
Zelda II: The Adventure of Link
Started: 2026-09. First release: 2026-09. Code: Rust.
AI: none stated
Lesson: Port one routine at a time behind a trap table, with the real ROM running the rest. Every frame is compared with an emulator, so the game stays playable while you port.
OpenMadoola
The Wing of Madoola
Started: 2023-12. First release: 2023-12. Code: C.
AI: none stated
Lesson: A hand-written C version can ship legally by reading all game data from the player's ROM. It reuses proven sound and video libraries instead of writing its own.
Dr. Mario NES Recomp
Dr. Mario
Started: 2026-03. First release: 2026-03. Code: C.
AI: yes: HANDOFF.md is a prompt for a new Claude Code session; 30 of 52 commits carry a Claude co-author line Agent files: HANDOFF.md.
Lesson: Most game-specific work is telling the recompiler about jump tricks in game.toml. Dispatch misses are the progress signal: drive them to zero on scripted runs.
Pac Man Recomp
Pac-Man
Started: 2026-09. First release: 2026-10. Code: C.
AI: yes: README credits AI coding assistance; no tool named
Lesson: A small game went from first commit to release in five days. Its headless input scripts with RAM checks make a cheap regression test.
Yoshi NES Recomp
Yoshi
Started: 2026-03. First release: 2026-03. Code: C.
AI: yes: CLAUDE.md rules for Claude Code debugging sessions; handoff notes for the next session Agent files: CLAUDE.md.
Lesson: Its CLAUDE.md is the best NES rule set: no guessing, find the first differing byte, and name the cause before any fix.
YoshisCookieRecomp
Yoshi's Cookie
Started: 2026-03. First release: 2026-07. Code: C.
AI: yes: store flag; README links the author's 'Recomp + AI' article
Lesson: A short game.toml was enough for an MMC3 game: one trampoline, one dispatch routine, one data range, plus two thunk entries.
FF1 Recomp
Final Fantasy
Started: 2026-10. First release: 2026-11. Code: C.
AI: none stated
Lesson: The closest reviewed project to a NES Path B. Each C routine names the assembly routine it ports and keeps its bugs. It has 463 unit checks and 40 scripted scenes.
Shadowgreat
Shadowgate
Started: 2026-11. First release: 2026-11. Code: C and C++.
AI: yes: catalog note from the developer says Claude wrote the code
Lesson: Keep a provenance file for every RAM fact, with the date and how it was checked. Borrowed addresses stay 'unverified' until tested in Mesen.
Dragon Warrior4NESRecomp
Dragon Warrior IV
Started: 2026-10. First release: 2026-10. Code: Python and C.
AI: yes: README says the project was coded with Claude; HD art made with an image model Agent files: CLAUDE.md.
Lesson: Never edit a pinned framework in place. Keep numbered patch files and a script that applies them to clean copies.
Bubble Bobble Recomp
Bubble Bobble
Started: 2026-08. First release: 2026-08. Code: C.
AI: yes: .recomp.json says the recompiler was developed with ChatGPT
Lesson: Fail closed: unknown code stops with an error instead of falling back. But the generator is not in the repo, so no one can rebuild the C from a ROM.
VKRT NES Remaster
Metroid, Super Mario Bros. 3 and Ninja Gaiden 3 (emulator)
Started: 2026-10. First release: 2026-11. Code: C++.
AI: yes: developer flag in the catalog; the repo does not say how
Lesson: An emulator is not a decomp. Its claim-boundary file, listing what the tests do not prove, is worth copying.
Final Fantasy NES Recomp
Final Fantasy
Started: 2026-11. First release: 2026-11. Code: C++ and Python.
AI: yes: developer flag in the catalog; no detail in the repo
Lesson: A one-game recompiler can be about 490 lines of Python. But it has no ROM hash check and no tests, so nobody knows how much of the game works.
What we could not check
- Whether
GhidraMCP2.0 (built for Ghidra 12.1.4) andGhidraNes(newest build for Ghidra 12.1) work in the same Ghidra install. - Whether
NESRecompbuilds and runs in WSL or on Linux. Its README says Linux 'likely works' and is less tested. - The Mesen MCP bridge in
nesrecomp/tools: its commands were not run or read in full. - Which Ghidra MCP server Dr. Mario NES Recomp and Yoshi NES Recomp used. Their files only name a tool prefix and a port.
- How long a medium NES game takes with no AI on Path A. No dated pre-agent NES static recomp was found.
- How long a full NES port takes with AI. No dated AI-assisted NES example was found; the range uses a SNES project.
- Whether
retrodisasmhandles bank-switched games well. Its own docs call banking support experimental. - The private history of z2rs, FF1 Recomp, OpenMadoola, Bubble Bobble Recomp and Final Fantasy NES Recomp. Their public dates may not show the real work time.
- Which tool the original NES developers used to assemble their games.
- No command in this guide was run here. Every command comes from a tool's own docs or a reviewed project.
- Whether
cyc_verify.pyworks with a full game build and theTriCNESoracle for every mapper. Its docs show test ROMs and four commercial games. - Whether
mesen_shotbuilds from a MesenCE checkout on Windows without extra steps. - Which mapper Pac-Man uses. The project's docs do not say.
Sources
- NESRecomp README
- NESRecomp: Your first port (GETTING_STARTED.md)
- NESRecomp CLAUDE.md
- NESRecomp tools README (symbols from ca65, Mesen MCP)
- NESRecomp legacy co-simulation (COSIM.md)
- recomp-ui README
- Mesen Community Edition README
- Mesen 2 archive notice
- GhidraNes README
- GhidraMCP README
- Ghidra README
- cc65 README
- Ubuntu 24.04 cc65 package
- retrodisasm README
- llvm-mingw README
- nesdev wiki: iNES
- nesdev wiki: CPU
- nesdev wiki: CPU memory map
- nesdev wiki: PPU
- nesdev wiki: Mapper
- Retro Game Coders: NES (most production NES code is assembly)
- Zelda 1 disassembly README
- Boulder Dash NES decomp and C port
- SuperMarioBros-C
- Harvest Moon SNES decomp
- Recomp + AI: 5 Months Later
- NESRecomp cycle backend guide (runner/cyc/README.md)
- NESRecomp cycle project reference (PROJECTS.md)
- NESRecomp PERMISSIONS.md
- retrodisasm NES guide
- SDL releases
- Git for Windows