Skip to the page
It's Decomplicated
Nintendo, 1983

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

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.

Go to the steps

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.

Go to the steps

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.

  1. 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 .nes file outside the project folder, for example in D:\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 NESRecomp README, a GitHub search for <game> nes disassembly, and the game's Data Crystal page.
    • Check that NESRecomp supports your mapper in runner/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 Claude
    GOAL: 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 from Get-FileHash, the mapper number is filled in, and your own APPROVED: <date> line is there.

    Proof this works: Pac Man Recomp, Shadowgreat, https://www.nesdev.org/wiki/INES

  2. 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, then cmake -S recompiler -B build/recompiler -G 'Visual Studio 17 2022' -A x64 and cmake --build build/recompiler --config Release --parallel.
    • Or skip the build: download nesrecomp-cli-windows-x86_64.zip from the releases page and run nesrecomp.exe build --rom <ROM> --output <folder>.
    • Make your game repo with game.toml, CLAUDE.md, SCOPE.md, PROGRESS.md, notes/ and tools/. Add nesrecomp as a pinned git submodule.
    • Add a .gitignore and a pre-commit hook that refuse *.nes, saves, dumps, screenshots and generated/.
    • 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 Claude
    GOAL: 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.ps1 prints 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

  3. 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 GhidraNes extension. Import the .nes file with the format set to NES ROM.
    • Open reset and vblank in the Symbol Tree. Note the bank-switch routine and the NMI handler.
    • Read nesrecomp/docs/PATTERNS.md. Mark every JSR followed by an address table, and every PLA/PHA trick on the return address.
    • If a ca65 disassembly exists, build it with ld65 -Ln game.lbl and convert names with python nesrecomp/tools/build_symbols_from_lbl.py game.lbl symbols.sym. Then set symbol_file = "symbols.sym" under [game] in game.toml.
    • Write notes/rom-map.md: banks, vectors, bank-switch address, dispatch routines and data ranges, each with how it was checked.
    Prompt for Claude
    GOAL: 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.md lists 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

  4. First light: boot to a screenshot

    You make the smallest possible build. A short game.toml and the headless host turn the ROM into a program with no window. It runs 600 frames and saves a picture.

    • Start game.toml with only [game], output_prefix = "<name>" and cycle_accurate = true. Add the bank-switch routine under [mapper] bank_switch if 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/cyc and cmake --build build/cyc --config Release. You get build/cyc/Release/cyc_oracle.exe, a console model built from TriCNES.
    • 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.png and the same moment in Mesen side by side. Note what differs in PROGRESS.md.
    Prompt for Claude
    GOAL: 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 writes boot.png, and cyc_verify.py exits 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

  5. 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 read dispatch_misses.log next to the program. Each new line is a ready-made extra_func entry.
    • 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] in game.toml. The next build then compiles the addresses the interpreter had to run.
    • Regenerate, rebuild and rerun every route after each change.
    Prompt for Claude
    GOAL: 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 and tools/check.ps1 prints PASS.

    Proof this works: Dr. Mario NES Recomp, Yoshi NES Recomp, Dragon Warrior4NESRecomp

  6. 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_shot from a MesenCE source checkout. Then run cmp_picture.py on your --mem-out file, with --ram-init zeros on 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 Claude
    GOAL: 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 in notes/findings.md.

    Proof this works: Yoshi NES Recomp, Dr. Mario NES Recomp, https://github.com/mstan/nesrecomp/blob/master/runner/cyc/README.md

  7. 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-root folder and --no-save, so they never touch your real settings or saves.
    • Make tools/check.ps1 run every route. Have a fresh Claude session run it on a clean clone.
    Prompt for Claude
    GOAL: 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

  8. 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_game from nesrecomp/runner/cyc/project.cmake in your own CMakeLists.txt. Add your features as extra host source files.
    • Add recomp-ui as a submodule for the launcher and settings menu.
    • If you build with llvm-mingw, link libc++ and libunwind statically 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 Claude
    GOAL: 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

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.

  1. 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 Claude
    GOAL: 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 own APPROVED: <date> line is there.

    Proof this works: FF1 Recomp, z2rs, https://github.com/Kryyto/boulder-dash-nes-decomp

  2. Set up the assembler and the rebuild check

    You install the cc65 tools 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 cc65 Windows snapshot zip and add its bin folder to PATH. WSL: sudo apt install cc65.
    • Download retrodisasm from its releases page (retrodisasm_v0.4.1_windows_64bit.zip). Unzip it and put retrodisasm.exe on PATH.
    • 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 with ca65, links with ld65, and compares the SHA-1 with the one in SCOPE.md.
    Prompt for Claude
    GOAL: 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.py prints 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

  3. 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-banks writes each bank to its own file. retrodisasm refuses 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.py after 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 Claude
    GOAL: 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.py prints PASS and every bank has its routines named or marked unk_.

    Proof this works: https://github.com/retroenv/retrodisasm, https://github.com/FiendsOfTheElements/z2disassembly, https://github.com/Kryyto/boulder-dash-nes-decomp

  4. 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.py that writes notes/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 Claude
    GOAL: 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.toml lists every routine with size and status, and tools/coverage.py prints per-bank totals.

    Proof this works: z2rs, FF1 Recomp

  5. 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-core beside 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 Claude
    GOAL: 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

  6. 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.toml with status todo and 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 ported only when the test passes.
    Prompt for Claude
    GOAL: 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 is ported.

    Proof this works: FF1 Recomp, https://github.com/Kryyto/boulder-dash-nes-decomp

  7. 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 ctest and python tools/check.py after every merge.
    • Keep data tables in the ROM. Read them at start-up through offsets, never typed by hand.
    • Run python tools/coverage.py daily and record the totals in PROGRESS.md.
    Prompt for Claude
    GOAL: 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.py shows every routine ported, and the full test run passes on the merged branch.

    Proof this works: z2rs, FF1 Recomp, https://github.com/RodoLy/HarvestMoonSnesDecomp

  8. 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 Claude
    GOAL: 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

  9. 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.c and audio.c only.
    • 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-linux and cmake --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 Claude
    GOAL: 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

Tools you need

ToolWhat it doesHow to get itHow 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 CMakeREADME, 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 2022nesrecomp 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 itREADME 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 itMesenCE 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

skills.sh

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

skills.sh

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

skills.sh

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

skills.sh

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

skills.sh

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

skills.sh

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

skills.sh

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)

MCP server

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)

MCP server

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

write-your-own

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

write-your-own

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.

JobWhat it means on this systemMax 20x (now called Ultra)Max 5x (now called Max)
Plan the projectPick the ROM version and mapper, look for a sister NESRecomp port or a disassembly, and write SCOPE.md.Fable 5.2
effort high
One session. This choice shapes months of work.
Opus 5.5
effort high
Use Fable 5.2 for this one session if you have weekly room.
Set up the projectBuild 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 fileFind 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 medium
Raise to high for function edges and data layouts. A mistake there spreads.
Bulk unitsPath B: port one small routine whose callees are done. Path A: add one extra_func for a clear miss.Sonnet 5.5
effort medium
Haiku 5.5 for the plainest units.
Sonnet 5.5
effort medium
Fewer workers at once. Haiku 5.5 for the plainest units.
Hard unitsRoutines that failed five tries, sound engines, code that plays with the stack, and timing-bound PPU writes.Fable 5.2
effort high
Go to xhigh only after a failed try at high.
Opus 5.5
effort high
Send only the last few that still fail to Fable 5.2.
Build the runtimePath 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 medium
Raise to high for timing and graphics bugs.
Find a bugFind the first byte that differs from Mesen, trace the instruction that wrote it, and name the cause before any fix.Opus 5.5
effort high
After two failed tries with full evidence, move to Fable 5.2.
Opus 5.5
effort high
One bug at a time.
Check the workA fresh session reruns the check on a clean clone. It looks for edited baselines, forced interpreter use, and looser compares.Opus 5.5
effort medium
Always a new session, never the one that did the work.
Sonnet 5.5
effort high
Always a new session, never the one that did the work.
Keep the recordsUpdate 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

Add to 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.log or the first difference from a diff, never whole traces.
  • Clear the chat after each card. The ledgers and PROGRESS.md carry 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.md short. Put the 6502 tricks in notes/patterns.md and 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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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.

Prompt for Claude
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'.

Prompt for Claude
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.

Prompt for Claude
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 detourThe 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 AIHow sure
Path A: running on PC1 to 4 weeks2 to 10 weeks3 to 12 monthsmedium confidence
Path B: full decomp6 to 20 weeks3 to 12 months1 to 3 yearslow 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.toml entry 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

ProjectWhat happenedLink
ShadowgreatFirst commit 2026-11-06, release 1.0.0 on 2026-11-09. One person. The catalog says Claude wrote the code.source
Pac Man RecompFirst commit 2026-09-28, first release 2026-10-03, six releases in four days. One person, with AI coding help.source
Dragon Warrior4NESRecompFirst 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 RecompFramework repo made 2026-03-14; Dr. Mario released 2026-03-28. 30 of 52 Dr. Mario commits are co-authored by Claude.source
z2rs97 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.

Top rated on RecompLauncherreimplementationplayable
Godot 4.6GDScript

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

Top rated on RecompLauncherreimplementationplayable
CMakeMSVC or clangAllegro 5replay tests

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

Top rated on RecompLauncherreimplementationplayable
Rustcargo xtasktetanes-core oracleca65 and ld65Mesen 2

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

Top rated on RecompLauncherreimplementationplayable
CMakeSDL2 or SDL3Nes_Snd_Emunes_ntsc

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

Top rated on RecompLauncherstatic recompplayable
NESRecomprecomp-uiVisual Studio 2022Ghidra MCPMesenvitest

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

Top rated on RecompLauncherstatic recompplayable
NESRecomp (fork)recomp-ui (fork)llvm-mingwTinyCCCMakeNinja

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

Top rated on RecompLauncherstatic recompplayable
NESRecomprecomp-uiNestopia oracleGhidra MCPVisual Studio 2022

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

Top rated on RecompLauncherstatic recompplayable
NESRecomprecomp-uiNestopia oraclevitestVisual Studio 2022

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

Top rated on RecompLauncherreimplementationcomplete
CMakeMinGW-w64 GCCSDL3LOVE 11.5ca65 and ld65

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

Top rated on RecompLauncherstatic recompplayable
NESRecomp (cycle backend)recomp-uillvm-mingwCMakeNinjaMesen 2

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

Top rated on RecompLauncherstatic recompplayable
NESRecomp (cycle backend)recomp-uiCMakeNinjaPythonDocker or Podman

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

Top rated on RecompLauncherstatic recompcomplete
custom generator (not published)CMakeVisual Studio 2022SDL3CTest

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)

Added: developer used AIemulator-basedin progress
CMakeNinjaC++20 compilerCTest

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

Added: developer used AIstatic recompplayable
tools/recomp.py (own)MSVC clDirect3D 11Python

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 GhidraMCP 2.0 (built for Ghidra 12.1.4) and GhidraNes (newest build for Ghidra 12.1) work in the same Ghidra install.
  • Whether NESRecomp builds 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 retrodisasm handles 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.py works with a full game build and the TriCNES oracle for every mapper. Its docs show test ROMs and four commercial games.
  • Whether mesen_shot builds from a MesenCE checkout on Windows without extra steps.
  • Which mapper Pac-Man uses. The project's docs do not say.

Sources