Game Boy decomp guide
A free recompiler gets most games running fast. A port-ready source still takes months.
What this system is like
Game Boy games are small. The reviewed games use ROMs from 32 KB (Tetris) to 1 MB (Pokemon Red). The chip is the Sharp SM83, a simple 8-bit CPU. That makes this one of the easiest systems to start on.
For Path A, a free tool called gb-recompiled does most of the work. Its gbrecomp program turns a ROM into C code plus a runtime. A runtime is the code that stands in for the console. Six of the twelve reviewed projects used it.
Path B is different here. Game Boy games were written by hand in assembly, not in C. So there is no C to match. The community instead builds a byte-matching assembly listing with RGBDS, like pret/pokered. A port then means rewriting the game routine by routine, with tests.
Recomp work runs on Windows or in WSL. The disassembly path is smoothest in WSL. Expect days to weeks for Path A, and many months for Path B.
The games were assembly, not C
Nintendo's kit was an assembler called ISAS. So there is no original C compiler to match. On this system, a full decomp is a labeled assembly listing that rebuilds the same bytes.
Banks hide the real address
Only two 16 KB windows of ROM are visible at once. The same address can mean different code in different banks. Every tool and note must name a function as bank plus address, like 03:47f2.
A ready-made recompiler exists
gbrecomp turns a ROM into a C project in one command. It also ships an interpreter as a safety net. You spend your time on missed code and runtime bugs, not on the tool. Its README calls it experimental and makes no promise that every game works.
Code hides behind jump tables
Games jump through tables and rst calls. Some code is copied into RAM and runs there. Static analysis misses these spots. Real play logs fill the gaps.
Strong free references
Pan Docs explains the hardware. SameBoy passes the main hardware test ROMs. Many games already have byte-matching disassemblies from pret and others. Check for one before you start.
Two paths
Get it running on PC
You run gbrecomp on your own ROM. It writes a C project and copies in its runtime. You build it with CMake and SDL2. Then you fix missed code one address at a time. You check each fix with the tool's built-in differential test.
Full decomp for porting
Game Boy games were assembly, so the matching source is labeled assembly. You start with mgbdis, which writes RGBDS files that rebuild the exact ROM. Then you split code from data, name every routine and document it. Last, you port the game by rewriting each routine in C or Rust. A byte-level test proves each rewrite. No reviewed project built a Game Boy disassembly from zero with AI. So the first half of this path has no AI track record yet.
Path A: Get it running on PC
You run gbrecomp on your own ROM. It writes a C project and copies in its runtime. You build it with CMake and SDL2. Then you fix missed code one address at a time. You check each fix with the tool's built-in differential test.
Method: static recompilation
Pick this first. It gives you a playable PC build in days to weeks. Pick it even if you want Path B later, because the function list helps there too.
Pick the game and write the scope
You pin down the exact ROM and goal. A different revision is a different program. You also look for prior work. A disassembly or a symbol file saves weeks.
- Hash your own dump in WSL:
sha1sum roms/game.gb. For CRC32, runpython3 -c "import zlib,sys;print(hex(zlib.crc32(open(sys.argv[1],'rb').read())))" roms/game.gb. - Search for an existing disassembly on pret.github.io and GitHub. Search RecompLauncher for an existing recomp.
- Read the header byte at
$0147to learn the MBC type. Pan Docs lists the values. - Write
SCOPE.md: game, revision, SHA-1, CRC32, size, goal, technique, first milestone, out-of-scope list.
Prompt for ClaudeGOAL: Draft SCOPE.md for a Game Boy static recomp of <GAME> (<REGION> <REVISION>). READ FIRST: the ROM header bytes $0100-$014F of `<ABSOLUTE_PATH_TO_ROM>` (read only), https://gbdev.io/pandocs/The_Cartridge_Header.html, https://pret.github.io/ FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. Do not copy or move the ROM. Do not write code. CHECK: Run `sha1sum <ABSOLUTE_PATH_TO_ROM>` and paste it. Done means SCOPE.md has the game, revision, SHA-1, CRC32, size in bytes, MBC type from byte $0147, goal, technique (gb-recompiled), first milestone and its check, and an out-of-scope list. BUDGET: Stop after 30 minutes of searching for prior work. IF BLOCKED: Stop. Do not guess a revision or a hash. Tell me what is missing. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: paste the sha1sum output and the header fields. 3) Files changed. 4) Prior work found, with links. 5) New parking-lot items.
Done whenSCOPE.mdnames the exact ROM by SHA-1 and size, and you approved it.Proof this works: TetrisGBRecomp, DK94 Recomp, https://gbdev.io/pandocs/MBCs.html
- Hash your own dump in WSL:
Set up the repo and the recompiler
You build
gbrecompand lay out a small repo. The repo holds only your config and code. The ROM and the generated C stay out of git. You also write one check command.- In WSL:
sudo apt install build-essential cmake ninja-build libsdl2-dev git python3. - Add the tool as a pinned submodule:
git submodule add https://github.com/arcanite24/gb-recompiled. - Build it:
cmake -G Ninja -S gb-recompiled -B gb-recompiled/buildthenninja -C gb-recompiled/build. - Add
*.gb,*.gbc,roms/,output/andlogs/to.gitignorebefore the first commit. - Write
tools/check.sh. It regenerates, builds, and runs--headless --limit-frames 600. - Write
CLAUDE.mdandPROGRESS.md.
Prompt for ClaudeGOAL: Make one command, `tools/check.sh`, that regenerates, builds and smoke-runs the recomp of <GAME>. READ FIRST: `SCOPE.md`, `gb-recompiled/README.md`, `gb-recompiled/AGENTS.md` (section "Required validation for generated behavior"). FENCE: Edit only `tools/check.sh`, `.gitignore`, `CLAUDE.md`, `PROGRESS.md`. Do not edit anything inside `gb-recompiled/`. Do not add features or tools I did not ask for. CHECK: Run `bash tools/check.sh`. Done means it runs `gbrecomp <ABSOLUTE_PATH_TO_ROM> -o output/game`, builds with CMake and Ninja, runs `--headless --limit-frames 600`, and exits 0. Then break the build on purpose and show the script exits non-zero. BUDGET: Stop after 3 failed tries at the same error. IF BLOCKED: Stop. Do not work around a missing package. Tell me the exact error and the command. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: paste both check outputs. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whenbash tools/check.shexits 0, and exits non-zero when you break the build.Proof this works: TetrisGBRecomp, Bigodao, https://github.com/arcanite24/gb-recompiled
- In WSL:
Map the ROM
You find the code that static analysis will miss. That means jump tables, the bank-switch routine and code copied into RAM. You write each one down with its bank and address. This list becomes the recompiler's hints.
- Install Ghidra 11.3.2 with JDK 21. Add the
GhidraBoyzip built for 11.3.2 through File, Install Extensions. Version 11.3.2 is the one release that bothGhidraBoyandGhidraMCP1.4 support. - Import the ROM with the GhidraBoy loader. It maps each bank as an overlay.
- GhidraBoy does not know which bank a call into
$4000-$7FFFgoes to. Its README says you must set the bank by hand for each such call. - If a disassembly exists, build its
.symfile and pass it with--symbols. - List interrupt vectors (
$0040to$0060),rstdispatchers, the bank-switch routine and any HRAM code, like the OAM DMA routine. - Save it all in
notes/map.mdwith one line per address and a reason.
Prompt for ClaudeGOAL: Write `notes/map.md`: every dispatcher, bank-switch routine and RAM-copied routine in <GAME>, as bank:address with evidence. READ FIRST: `SCOPE.md`, Ghidra project `<GHIDRA_PROJECT>` with GhidraBoy loaded, https://gbdev.io/pandocs/Memory_Map.html FENCE: Edit only `notes/map.md`. Do not edit generated C. Do not change recompiler code. CHECK: For each entry, paste the Ghidra listing lines that prove it (address, bytes, instruction). Done means the file lists the five interrupt vectors, each rst handler used, the bank-switch routine, every jump table found, and every routine copied to RAM, each with proof. BUDGET: Stop after 90 minutes, or after 20 minutes with no new entry. IF BLOCKED: Stop. If Ghidra or GhidraBoy is not loaded, say so and stop. Do not decode bytes by hand. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: counts per entry type. 3) Files changed. 4) Addresses you could not explain. 5) New parking-lot items.
Done whennotes/map.mdlists every vector, dispatcher, bank switch and RAM routine, each with a Ghidra listing as proof.Proof this works: DK94 Recomp, WarioBlast Recomp, TetrisGBRecomp, https://github.com/Gekkio/GhidraBoy
- Install Ghidra 11.3.2 with JDK 21. Add the
Boot to the first frame
You run the recompiler on the whole ROM and build it. The game should open a window and reach its title screen. You save frame dumps as proof. This is the smallest full run.
- Generate:
gb-recompiled/build/bin/gbrecomp roms/game.gb -o output/game. - Build:
cmake -G Ninja -S output/game -B output/game/buildthenninja -C output/game/build. - Make the log folder first:
mkdir -p logs. The runtime does not create it. - Run headless with frame dumps:
./output/game/build/game --headless --limit-frames 600 --dump-frames 60,300,600 --screenshot-prefix logs/shot. It writeslogs/shot_00060.ppm,logs/shot_00300.ppmandlogs/shot_00600.ppm. - Open the dumps and compare them by eye with the same frames in SameBoy.
- Keep
output/game/*_metadata.json. It lists every function by bank and address.
Prompt for ClaudeGOAL: Show <GAME> reaching its title screen in the recompiled build. READ FIRST: `SCOPE.md`, `notes/map.md`, `gb-recompiled/RUNTIME.md` (Execution options). FENCE: Edit only `tools/check.sh` and `PROGRESS.md`. Never edit files under `output/`. Generated C is a build artifact. CHECK: Run `bash tools/check.sh`, then `mkdir -p logs && ./output/game/build/game --headless --limit-frames 600 --dump-frames 60,300,600 --screenshot-prefix logs/shot`. Done means it exits 0, `logs/shot_00300.ppm` exists, and that frame shows the title screen. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Do not patch generated C. Tell me the first error line, the command, 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 run output and the paths of the three frame files. 3) Files changed. 4) Interpreter fallback lines seen in the log. 5) New parking-lot items.
Done whenThe headless run exits 0 andtest -s logs/shot_00300.ppmpasses. Then you confirm by eye that the frame is the title screen.Proof this works: Kirby's Dream Land Recomp, Super Mario Land 3: Wario Land — Recomp, https://github.com/arcanite24/gb-recompiled/blob/main/RUNTIME.md
- Generate:
Close the code gaps
Code the analyzer missed runs on the built-in interpreter instead. That still works, but it hides gaps. You log each fallback address, prove it is real code, and add it as an entry point. You repeat until a full play log shows no fallbacks.
- Record a route through the game:
./output/game/build/game --record-input logs/route1.input. - Replay it and log fallbacks:
./output/game/build/game --headless --limit-frames <N> --input "$(cat logs/route1.input)" --log-file logs/interp.log --log-frame-fallbacks --report-interpreter-hotspots. Set<N>to the route's length in frames, so the run stops. - Summarize:
python3 gb-recompiled/tools/summarize_interpreter_log.py logs/interp.log. When it printsNo interpreter fallback recorded, the route is clean. - Check each address in Ghidra. Then regenerate with
--add-entry-point <BANK>:<ADDR>. The tool reads the address as hex. It reads the bank as decimal, or as hex with0x. So write bank 10 as10or0x0a. A leading zero, as in08, is read the wrong way. - For more coverage, use the trace tool. It must run from inside the tool's own folder:
cd gb-recompiled && python3 tools/run_ground_truth.py ../roms/game.gb --output-dir ../output/game-trace --frames 18000. It needsPyBoy. It deletes and remakes its output folder. It samples only one address per frame, so treat its hits as hints.
Prompt for ClaudeGOAL: Remove the interpreter fallback at <BANK:ADDRESS> from the replay of `logs/<ROUTE>.input`. READ FIRST: `notes/map.md`, `logs/interp.log` (only lines for <BANK:ADDRESS>), `gb-recompiled/GROUND_TRUTH_WORKFLOW.md`. FENCE: Edit only the `--add-entry-point` list in `tools/check.sh` and `notes/map.md`. Never edit generated C. Do not add an address you have not proven is code. CHECK: Run `bash tools/check.sh`, replay with `--headless --limit-frames <N> --input "$(cat logs/<ROUTE>.input)" --log-file logs/interp.log --log-frame-fallbacks`, then run `python3 gb-recompiled/tools/summarize_interpreter_log.py logs/interp.log`. Done means <BANK:ADDRESS> is gone from the summary and no new fallback appears. BUDGET: Stop after 3 tries, or 30 minutes. IF BLOCKED: Stop. If the address is RAM code or data, do not force it in. Tell me what Ghidra shows there. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the Ghidra lines and the before and after summary. 3) Files changed. 4) Fallbacks left. 5) New parking-lot items.
Done whenFor every recorded route,summarize_interpreter_log.pyprintsNo interpreter fallback recorded.Proof this works: TetrisGBRecomp, DK94 Recomp, WarioBlast Recomp, https://github.com/arcanite24/gb-recompiled/blob/main/GROUND_TRUTH_WORKFLOW.md
- Record a route through the game:
Fix runtime bugs with proof
Now you fix wrong pictures, sound or timing. One bug per session. Each fix needs proof of the cause first. The differential mode compares compiled code with the interpreter, step by step.
- Run the differential check from boot:
./output/game/build/game --differential 500000 --differential-log 100000 --differential-fail-on-fallback. - Reproduce the bug with a recorded input file. Stop just before it and save the machine:
--headless --limit-frames <N> --input "$(cat logs/<ROUTE>.input)" --dump-state logs/bug.json --save-state-file logs/bug.state. - Run the differential check from that point:
--differential 500000 --differential-state logs/bug.state --differential-fail-on-fallback. - Remember what the differential check proves. It compares compiled code with the tool's own interpreter. Both share the same graphics, sound and timer code, so it cannot catch a bug in those parts.
- Compare the same moment in SameBoy's debugger. Pan Docs is the first hardware reference.
- Fix the recompiler or runtime, never the generated C. Regenerate and replay.
Prompt for ClaudeGOAL: Find and fix the cause of <BUG> seen at frame <N> of `logs/<ROUTE>.input`. READ FIRST: `notes/map.md`, `notes/findings.md`, Pan Docs page <PAGE>, the frame dump `logs/<FILE>`. FENCE: Edit only `<RUNTIME_OR_RECOMPILER_FILE>` and `notes/findings.md`. Never edit generated C. No game-specific hacks unless I approve them. CHECK: Run `bash tools/check.sh`. Replay with `--headless --limit-frames <N> --input "$(cat logs/<ROUTE>.input)" --dump-frames <N> --screenshot-prefix logs/fix --save-state-file logs/bug.state`. Then run `--differential 500000 --differential-state logs/bug.state --differential-fail-on-fallback`. Done means the frame is correct, the differential run passes, and the check script exits 0. BUDGET: Stop after 2 failed fixes. Write the cause first; no fix before the cause is proven. IF BLOCKED: Stop. Tell me the evidence you have, the guesses you ruled out, and what you need. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: before and after frames and the differential output. 3) Files changed. 4) Cause in one sentence, with addresses. 5) New parking-lot items.
Done whenThe differential run fromlogs/bug.statereports no mismatch and no fallback.bash tools/check.shexits 0. The new frame dump matches SameBoy at the same frame.Proof this works: Bigodao, TetrisGBRecomp, https://github.com/arcanite24/gb-recompiled/blob/main/AGENTS.md
- Run the differential check from boot:
Build a regression set
You turn your recorded routes into tests. Each route has a saved final-state hash. Any change that alters game logic then fails the test. This keeps old fixes fixed.
- Copy each route's input file out of the ignored
logs/folder:cp logs/route1.input tests/routes/route1.input. Input files hold your button presses, not game data. - Save each route's final state:
--headless --input "$(cat tests/routes/route1.input)" --limit-frames <N> --dump-state tests/routes/route1.json. - If the game has a clock chip (MBC3 RTC), add
--rtc-unix-time <SECONDS>so every replay sees the same time. - Write
tools/regress.sh. It replays every route and compares the state files. - Keep extras like widescreen render-only, so the state hash still proves logic is unchanged.
- Run the full check from a fresh clone before each release.
Prompt for ClaudeGOAL: Make `tools/regress.sh` replay every route in `tests/routes/` and fail on any state change. READ FIRST: `PROGRESS.md`, `gb-recompiled/RUNTIME.md` (--input, --dump-state, --rtc-unix-time). FENCE: Edit only `tools/regress.sh`, `tests/` and `PROGRESS.md`. Do not change expected state files except for a route I name. CHECK: Run `bash tools/regress.sh` on a clean clone, with the ROM path passed in. Done means every route passes. Then change one byte of one expected file and show the script fails. BUDGET: Stop after 3 failed tries. 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 outputs. 3) Files changed. 4) Routes not yet covered. 5) New parking-lot items.
Done whenbash tools/regress.shpasses on a clean clone and fails when an expected file is changed.Proof this works: DK94 Recomp, WarioBlast Recomp, G1R Deluxe
- Copy each route's input file out of the ignored
Build for Windows and ship without the game
You build a Windows program and package it. The package must hold no ROM bytes and no generated C files. The program checks the player's own ROM by hash. Then you test it from a clean folder. Know this first: by default
gbrecompwrites the whole ROM into a file named<NAME>_rom.cand builds it into the program. And any built program still holds the game's code, turned into machine code. DK94 Recomp and WarioBlast Recomp avoid that by recompiling the player's ROM on the player's PC at start.- Remove the built-in ROM. Bigodao drops
*_rom.cfrom its CMake build, loads the player's ROM at start, and fails the build ifrom_datais still used. Plan this asruntimework. - Install MSYS2. In its UCRT64 shell run
pacman -S mingw-w64-ucrt-x86_64-gcc mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja mingw-w64-ucrt-x86_64-SDL2. - Build with a clean
PATHthat has only MSYS2 and Windows folders. Other toolchains onPATHbroke builds. - Gate the ROM by CRC32 or SHA-256 at start. Check the baked value matches your dump.
- Write
tools/check_release.py. It fails if any 32-byte run of the ROM appears in the package. WarioBlast Recomp uses this rule. - Copy the package to a fresh folder on another PC and run it.
Prompt for ClaudeGOAL: Produce a Windows release zip of the <GAME> recomp that runs from a clean folder and holds no ROM data. READ FIRST: `SCOPE.md`, `PROGRESS.md`, `tools/check_release.py`, the generated `CMakeLists.txt` (find `<NAME>_rom.c`). FENCE: Edit only `tools/package.sh`, `tools/check_release.py` and `PROGRESS.md`. Do not put the ROM, generated C, or files under `output/` in the zip. If the program still builds in `<NAME>_rom.c`, stop and report BLOCKED. CHECK: Run `python3 tools/check_release.py dist/<ZIP>`. Done means it prints zero ROM matches, and the unzipped program starts with the correct ROM and refuses a wrong one. BUDGET: Stop after 3 failed packaging tries. IF BLOCKED: Stop. Tell me which DLL or file is missing, with the exact error. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the scan output and both launch results. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whenThe release scan finds zero ROM bytes, and the unzipped build runs on a clean PC with your ROM.Proof this works: TetrisGBRecomp, WarioBlast Recomp, Bigodao, https://packages.msys2.org/base/mingw-w64-SDL2
- Remove the built-in ROM. Bigodao drops
Path B: Full decomp for porting
Game Boy games were assembly, so the matching source is labeled assembly. You start with mgbdis, which writes RGBDS files that rebuild the exact ROM. Then you split code from data, name every routine and document it. Last, you port the game by rewriting each routine in C or Rust. A byte-level test proves each rewrite. No reviewed project built a Game Boy disassembly from zero with AI. So the first half of this path has no AI track record yet.
Method: matching disassembly, then a port by rewrite
Pick this when you want the game on other systems, or deep mods. First check pret and GitHub for an existing disassembly. If one exists, skip to the port steps.
Look for an existing disassembly
Many Game Boy games already have byte-matching disassemblies. Using one saves months. If none exists, you plan your own. Either way, you write the scope.
- Check pret.github.io and search GitHub for "<GAME> disassembly".
- Check if an existing one builds your exact revision. Compare its listed SHA-1 with
sha1sum roms/game.gb. - Write
SCOPE.md: revision, hash, goal (port to which system), language for the port, and the check.
Prompt for ClaudeGOAL: Find prior disassembly work for <GAME> and draft SCOPE.md for Path B. READ FIRST: https://pret.github.io/, GitHub search results for "<GAME> disassembly", my ROM hash `<SHA1>`. FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. Do not clone or download ROMs. CHECK: For each disassembly found, list its URL, last commit date, the SHA-1 it builds, and whether that SHA-1 equals `<SHA1>`. Done means SCOPE.md names the starting point (existing repo or empty) and the port target. BUDGET: Stop after 45 minutes. IF BLOCKED: Stop. Tell me what you could not find or check. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the table of repos and hashes. 3) Files changed. 4) Your recommended start. 5) New parking-lot items.
Done whenSCOPE.mdnames a starting repo, or says none exists, and you approved it.Proof this works: Six Golden Coins, G1R Deluxe, https://github.com/pret/pokered
Make a ROM that rebuilds byte for byte
You turn the ROM into assembly files that rebuild it exactly. This is your first check. From now on, every change must keep the hash the same.
- In WSL, install the basics:
sudo apt install make git python3. - Install RGBDS 1.0.4: download
rgbds-linux-x86_64.tar.xz, thenmkdir rgbds && tar xf rgbds-linux-x86_64.tar.xz -C rgbds && cd rgbds && sudo ./install.sh. Check it withrgbasm -V. - Get
mgbdis:git clone https://github.com/mattcurrie/mgbdis. - Disassemble:
python3 mgbdis/mgbdis.py roms/game.gb. It writes to a newdisassembly/folder. - Build:
cd disassembly && make. The made makefile writesgame.gb. - Write
make compare. It builds and checkssha1sumagainst the hash inSCOPE.md.
Prompt for ClaudeGOAL: Make `make compare` rebuild <GAME> from RGBDS source with the same SHA-1 as my ROM. READ FIRST: `SCOPE.md`, `mgbdis/README.md`, https://rgbds.gbdev.io/install/linux FENCE: Edit only `Makefile` and `tools/`. Do not edit `game.sha1`. Do not commit the ROM; it stays at `<ABSOLUTE_PATH_TO_ROM>`. CHECK: Run `make compare`. Done means it prints OK with the SHA-1 from SCOPE.md. Then change one byte in one .asm file and show `make compare` fails. BUDGET: Stop after 3 failed tries. IF BLOCKED: Stop. Tell me the exact error and the RGBDS version from `rgbasm -V`. 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 whenmake compareprints OK, and fails when one byte is changed.Proof this works: https://github.com/mattcurrie/mgbdis, https://github.com/Kak2X/wl, https://github.com/pret/pokered/blob/master/INSTALL.md
- In WSL, install the basics:
Separate code from data and list every routine
A raw disassembly guesses where code starts. Wrong guesses turn data into fake code. You feed it real evidence of what ran. Then you build the routine ledger.
- Record which addresses ran during play. Use a PyBoy trace or the
gbrecompmetadata file from Path A. TheKak2X/wldisassembly started from a code/data log made by a debug emulator. - Write them into a symbol file as
.codeblocks. Mark tables with.data, text with.textand tile graphics with.image. Entries look like03:47f2 Read_Joypadand0d:4800 .data:200, all in hex. mgbdisonly reads the symbol file if it sits next to the ROM with the same name:roms/game.symforroms/game.gb. Keep your master copy in the repo asnotes/game.symand copy it over. Do not keep it indisassembly/, becausemake cleandeletesdisassembly/game.sym.- Re-run with
python3 mgbdis/mgbdis.py roms/game.gb --overwrite. Without--overwriteit stops, because the folder exists. Runmake compareagain. - Do all re-runs before you hand-edit any
.asmfile. A later re-run with--overwritewould replace your edits. - Write
ledger/routines.csv: bank, address, size, name, status.
Prompt for ClaudeGOAL: Build `ledger/routines.csv` with every routine in <GAME>, and a `notes/game.sym` that marks code, data, text and graphics. READ FIRST: `SCOPE.md`, `mgbdis/README.md` (Symbol Files), `<PATH_TO>_metadata.json` from the recomp (use a script, do not read it whole), `logs/<TRACE>`. FENCE: Edit only `notes/game.sym`, its copy at `<ROM_DIR>/game.sym`, `ledger/routines.csv` and the regenerated `disassembly/` folder. Do not hand-edit any .asm file in this task. CHECK: Run `python3 mgbdis/mgbdis.py <ROM_DIR>/game.gb --overwrite`, then `make compare`. Done means it prints OK, and every routine in the metadata file appears in the ledger with bank, address and status. BUDGET: Stop after 2 hours or 3 failed compares. IF BLOCKED: Stop. List the address ranges you cannot class 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 compare output and ledger row counts. 3) Files changed. 4) Unknown ranges. 5) New parking-lot items.
Done whenmake compareprints OK andledger/routines.csvlists every known routine with a status.Proof this works: https://github.com/mattcurrie/mgbdis, https://github.com/arcanite24/gb-recompiled/blob/main/AGENTS.md, https://github.com/Kak2X/wl
- Record which addresses ran during play. Use a PyBoy trace or the
Finish one routine end to end
You take one small routine all the way. You name it and its RAM, swap raw numbers for labels, and write a comment. The ROM must still match. This proves the whole loop works.
- Pick a short routine, like the joypad reader.
- Use the SameBoy or BGB debugger to watch which RAM it writes.
- Name the routine, its labels and its RAM in
ram.asm. Usehardware.incnames for I/O registers. - Run
make compare. Set the ledger status tonamed.
Prompt for ClaudeGOAL: Fully name and document routine <BANK:ADDRESS> in the <GAME> disassembly. READ FIRST: `ledger/routines.csv` (row for <BANK:ADDRESS>), the routine's .asm file, `notes/ram-map.md`. FENCE: Edit only the routine's .asm lines, `ram.asm`, `notes/ram-map.md` and its ledger row. Do not change any byte the build emits. CHECK: Run `make compare`. Done means OK, no raw ROM or RAM address is left inside the routine, and the ledger row says `named`. BUDGET: Stop after 3 failed compares or 30 minutes. IF BLOCKED: Stop. Say which value you cannot explain and what you checked. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: compare output and the diff. 3) Files changed. 4) Names you are unsure of. 5) New parking-lot items.
Done whenmake compareprints OK and the routine has no raw addresses left.Proof this works: https://github.com/zladx/LADX-Disassembly, https://bgb.bircd.org/
Name every routine, bank by bank
Now you repeat the unit loop many times. Workers each take routines from one bank. Easy leaf routines go first. Each change must keep
make compareat OK.- Sort the ledger: leaf routines first, then routines whose callees are all named.
- Give each worker one bank file and its own git worktree.
- Run
make compareafter every routine, and again after each merge. - Record dead ends in
notes/findings.mdso nobody repeats them.
Prompt for ClaudeGOAL: Name and document the next <N> `raw` routines in bank <BANK>, one at a time. READ FIRST: `ledger/routines.csv` (bank <BANK> rows only), `notes/ram-map.md`, `notes/findings.md`. FENCE: Edit only `<BANK_FILE>.asm`, `ram.asm`, `notes/ram-map.md` and ledger rows for bank <BANK>. Work in worktree `<ABSOLUTE_WORKTREE_PATH>`. Do not touch other banks. CHECK: Run `make compare` after each routine. Done means OK after each one and each finished row says `named`. BUDGET: Skip a routine after 3 failed tries and mark it `hard`. IF BLOCKED: Stop. Tell me which routine and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the last compare output. 3) Files changed. 4) Routines named, routines marked hard. 5) New parking-lot items.
Done whenEvery ledger row saysnamedorhard, andmake compareprints OK on the merged branch.Proof this works: https://github.com/pret/pokered, https://github.com/pret/pokecrystal
Pull out the graphics and tables
Ports need the art and data as real files. You turn tile data into PNG images. The build turns them back into bytes with
rgbgfx. The hash must not change.- Find each tile block in the ledger and the
.symfile. - Easiest way, before any hand edits: mark the block as
.imageinnotes/game.sym, for example02:791a .image:500:w128. Re-runmgbdiswith--overwrite. It writesgfx/<LABEL>.pngand a make rule that runsrgbgfx. - After hand edits, do it by hand. Write the block out as PNG. Swap its bytes for an
INCBINof the built file. Add a make rule that runsrgbgfxon it. - Do the same for level, text and music tables as
.asmdata with labels. - Run
make compareafter each block.
Prompt for ClaudeGOAL: Move graphics block <LABEL> out of the .asm file into `gfx/<NAME>.png`, built back with `rgbgfx`. READ FIRST: `game.sym`, `Makefile`, https://rgbds.gbdev.io/ FENCE: Edit only `Makefile`, `gfx/<NAME>.png` and the lines for <LABEL>. Do not commit any other ROM-derived file outside `gfx/`. CHECK: Run `make clean && make compare`. Done means OK. BUDGET: Stop after 3 failed compares. IF BLOCKED: Stop. Tell me the bit depth or layout you could not match. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: compare output. 3) Files changed. 4) Blocks left. 5) New parking-lot items.
Done whenmake clean && make compareprints OK with graphics built from PNG files.Proof this works: https://github.com/pret/pokered, https://github.com/gbdev/rgbds, https://github.com/mattcurrie/mgbdis
- Find each tile block in the ledger and the
Harden the disassembly
You make the build repeatable for anyone. A clean clone plus the user's own ROM must give OK. Game files never enter git. A second session checks the work.
- Add a CI job that builds the tools and runs the ROM-free checks.
- Run
make compareon a fresh clone withbaserom.gbsupplied by you. - Scan
git ls-filesfor any.gbfile or large binary. - Have a fresh session re-check ten random
namedrows.
Prompt for ClaudeGOAL: Prove the <GAME> disassembly builds from a clean clone and holds no game files. READ FIRST: `PROGRESS.md`, `.gitignore`, `Makefile`. FENCE: Edit only `.github/workflows/`, `.gitignore` and `PROGRESS.md`. CHECK: Clone to `<ABSOLUTE_TEMP_PATH>`, copy in my ROM from `<ABSOLUTE_PATH_TO_ROM>`, run `make compare`. Done means OK, and `git ls-files` lists no .gb, .gbc or .sav file. BUDGET: Stop after 3 failed tries. IF BLOCKED: Stop. Tell me which file the clean build needs that git lacks. 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 whenA clean clone plus your ROM givesmake compareOK, and git holds no game files.Proof this works: https://github.com/pret/pokered, G1R Deluxe
Port one routine at a time
Now you rewrite the game in C or Rust. One routine is one ticket. A script slices the routine out of the disassembly. The test sets up memory, runs the new code, and checks the same bytes the assembly writes.
- Write a slicer script that cuts one routine, with its jump tables and bank switches, into a task file.
- Keep pending tickets in
PORT_QUEUE.json. Move done ones to a separate file. - For each ticket, write the new code and a byte-level test. The test must fail if you revert the line under test.
- Run
cargo test(Rust) or your C test runner after each ticket. - Keep the original memory layout behind a small conversion layer, so tests can compare bytes.
Prompt for ClaudeGOAL: Port routine <BANK:ADDRESS> (<NAME>) to <LANGUAGE> with a byte-level test. READ FIRST: `tasks/<BANK_ADDRESS>.md` (the slice), at most 4 named source files: <FILES>. FENCE: Edit only <PORT_FILE> and <TEST_FILE>. Do not bulk-translate assembly. Do not load `PORT_QUEUE.done.json` or whole data dumps. CHECK: Run `<TEST_COMMAND>`. Done means the new test passes, it fails when you revert the ported line, and all older tests still pass. BUDGET: After 2 failed compile or fix tries, stop and reply ESCALATE with a short summary. IF BLOCKED: Stop. Tell me which instruction or table you could not settle from the slice. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test output, and output with the line reverted. 3) Files changed. 4) What is left. 5) New parking-lot items.
Done whenEvery ticket is closed, and the full test suite passes.Proof this works: Six Golden Coins, https://github.com/patricksmill/sml2-rs/blob/main/PORTING.md
Check the port against the real game
Unit tests prove routines, not the whole game. You add golden runs. The port plays a fixed input and must reach the same points as the original. You compare pictures and RAM. Treat these as a second check, not the main one. Six Golden Coins found capture-based checks unreliable and made its byte-level unit tests the gate.
- Record input routes in an emulator and save RAM at set frames with PyBoy.
- Play the same input in the port and save the same RAM fields.
- Write a compare script that reports each frame and field that differs.
- Load assets from the player's own ROM at first launch. Check its hash first.
Prompt for ClaudeGOAL: Make `tools/golden.sh` replay route <ROUTE> in the port and in PyBoy and compare RAM at frames <FRAMES>. READ FIRST: `PROGRESS.md`, `notes/ram-map.md`, https://docs.pyboy.dk/ FENCE: Edit only `tools/golden.sh`, `tools/compare_ram.py` and `tests/golden/`. Do not change the port's game code in this task. CHECK: Run `bash tools/golden.sh <ROUTE>`. Done means it prints a per-frame table and exits 0 only when every listed field matches. BUDGET: Stop after 3 failed tries. IF BLOCKED: Stop. Tell me which frame cannot be lined up and why. OTHER PROBLEMS: Write differences you find in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the table. 3) Files changed. 4) Differences found. 5) New parking-lot items.
Done whenEvery golden route matches the original at every checked frame.Proof this works: Six Golden Coins, G1R Deluxe, https://github.com/Baekalfen/PyBoy
Tools you need
| Tool | What it does | How to get it | How we know |
|---|---|---|---|
| gb-recompiled (`gbrecomp`) Required both | Turns a Game Boy ROM into a C project with an SDL2 runtime. It also has a fallback interpreter, a differential test and input replay. Path A Path B | Download `gb-recompiled-windows-x64.zip` or `gb-recompiled-linux-x64.tar.gz` from release v0.1.0, or build: `git clone https://github.com/arcanite24/gb-recompiled.git && cd gb-recompiled && cmake -G Ninja -B build . && ninja -C build` (needs CMake 3.20+, Ninja, SDL2 files and a C11/C++20 compiler) | README, RUNTIME.md, AGENTS.md, GROUND_TRUTH_WORKFLOW.md, releases and the flag code in recompiler/src/main.cpp read 2026-11-13; used by Bigodao, TetrisGBRecomp and four omegakatana92 projects. Not run here. |
| CMake and Ninja Required both | Build tools. gbrecomp writes a CMake project, and Ninja compiles it.Path A | WSL: `sudo apt install build-essential cmake ninja-build`. Windows: `pacman -S mingw-w64-ucrt-x86_64-cmake mingw-w64-ucrt-x86_64-ninja` in MSYS2 | gb-recompiled README (CMake 3.20+, Ninja) read 2026-11-13; Ubuntu noble and MSYS2 package pages checked 2026-11-13 |
| SDL2 development files Required both | The window, sound and controller library the runtime uses. Path A | WSL: `sudo apt install libsdl2-dev`. Windows: `pacman -S mingw-w64-ucrt-x86_64-SDL2` in MSYS2 | gb-recompiled README; MSYS2 package page (SDL2 2.32.10) and Ubuntu noble page read 2026-11-13 |
| MSYS2 (UCRT64 GCC) Helpful Windows | A Windows build shell with GCC. Use it to build the Windows version of your recomp. Path A | Run the installer from msys2.org, then `pacman -S mingw-w64-ucrt-x86_64-gcc` in the UCRT64 shell | msys2.org read 2026-11-13; TetrisGBRecomp builds with msys2 GCC |
| Ghidra 11.3.2 Helpful Windows | A free disassembler and decompiler. With GhidraBoy it reads banked Game Boy ROMs. Use 11.3.2, not the newest Ghidra, because the Game Boy add-on stops at 11.4.2 and GhidraMCP 1.4 is built for 11.3.2.Path A Path B | Install a 64-bit JDK 21. Download `ghidra_11.3.2_PUBLIC_20250415.zip` from the Ghidra 11.3.2 release page and unzip it | 11.3.2 release assets and README (JDK 21) read 2026-11-13. Newest Ghidra is 12.2 (2026-10-22). mstan/gbrecompiled GHIDRA_SETUP.md uses Ghidra with GhidraBoy. |
| GhidraBoy Helpful Windows | A Ghidra add-on for the SM83 CPU. It loads banked ROMs and names the hardware registers. Path A Path B | Download `ghidra_11.3.2_PUBLIC_20250830_GhidraBoy.zip` from the GhidraBoy releases page. In Ghidra: File, Install Extensions, plus button, pick the zip, restart | README and release 20250830 read 2026-11-13: repo is archived; supports Ghidra 11.1 to 11.4.2, including 11.3.2; used by mstan/gbrecompiled (TetrisGBRecomp engine) |
| GhidraMCP Helpful Windows | An MCP server that lets Claude ask Ghidra for listings, names and cross-references. MCP is the way Claude Code talks to outside tools. Path A Path B | For Ghidra 11.3.2, use release 1.4: download `GhidraMCP-release-1-4.zip` (it holds the Ghidra plugin and `bridge_mcp_ghidra.py`), install the plugin like GhidraBoy, run `pip install mcp==1.5.0 requests==2.32.3`, then `claude mcp add ghidra -- python <ABSOLUTE_PATH>/bridge_mcp_ghidra.py --ghidra-server http://127.0.0.1:8080/`. Release 2.0 and its `uvx` command need Ghidra 12.1.4, which GhidraBoy does not list as supported | README at tag 1.4 and at 2.0, extension.properties at 1.4 (ghidraVersion=11.3.2), requirements.txt and releases read 2026-11-13. The Claude Code command is built from the 1.4 README's bridge arguments and was not tested. mstan/gbrecompiled .mcp.json points Claude at a Ghidra MCP server. |
| PyBoy Helpful both | A Game Boy emulator you can drive from Python. Use it for traces, and as a reference for RAM values. Path A Path B | `pip install pyboy` (in a virtual environment) | README and PyPI (2.9.0, Python 3.9+) read 2026-11-13; used by gb-recompiled trace tools and by sml2-rs |
| SameBoy Helpful Windows | A very accurate emulator with a text debugger. It has breakpoints, watchpoints and a disassembler. Path A Path B | Download `sameboy_winsdl_v1.0.5.zip` from the releases page and unzip it | README and v1.0.5 release read 2026-11-13; gb-recompiled AGENTS.md names SameBoy as the second hardware reference |
| BGB Helpful Windows | A Windows emulator with a strong debugger. It loads .sym files, so you see your labels while you step.Path B | Download the zip from bgb.bircd.org and unzip it | bgb.bircd.org read 2026-11-13 (version 1.6.7, Windows and Wine); LADX-Disassembly README points to it |
| RGBDS 1.0.4 Required both | The Game Boy assembler, linker, header fixer and graphics converter (rgbasm, rgblink, rgbfix, rgbgfx). It rebuilds the ROM from your assembly.Path B | WSL: download `rgbds-linux-x86_64.tar.xz`, then `mkdir rgbds && tar xf rgbds-linux-x86_64.tar.xz -C rgbds && cd rgbds && sudo ./install.sh`, then check `rgbasm -V`. Windows: unzip `rgbds-win64.zip` into a folder and add that folder to `PATH` | Install pages and v1.0.4 release (2026-09-22) read 2026-11-13; pret/pokered INSTALL.md asks for rgbds 1.0.4 |
| mgbdis Required both | A Python disassembler for Game Boy ROMs. It writes RGBDS files and a makefile that rebuild the ROM. Path B | `git clone https://github.com/mattcurrie/mgbdis` (needs Python 3.6+ and RGBDS 0.8+) | README and mgbdis.py options read 2026-11-13: a second run into the same folder needs --overwrite; the written makefile calls rgbasm, rgblink, rgbfix and rgbgfx and needs make, so run it in WSL |
| Rust (rustup and cargo) Helpful both | A language and build tool for the port. Six Golden Coins rewrote its game in Rust. Path B | WSL: `curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh`. Windows: run `rustup-init.exe` from rustup.rs | rustup.rs read 2026-11-13; Six Golden Coins card (cargo workspace, MSVC toolchain on Windows) |
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.
GhidraMCP
Claude can ask Ghidra what an address really does. The Tetris engine fork makes this its first rule before any crash work.
With Ghidra 11.3.2 and GhidraBoy: install the plugin from `GhidraMCP-release-1-4.zip`, run `pip install mcp==1.5.0 requests==2.32.3`, then `claude mcp add ghidra -- python <ABSOLUTE_PATH>/bridge_mcp_ghidra.py --ghidra-server http://127.0.0.1:8080/`. The 2.0 release and its `uvx` command need Ghidra 12.1.4. Not tested here.
static-recomp-scope-plan
It makes you write down the exact ROM, the scenes to test and the pass marks first. That fits Path A step 1.
npx skills add bgyss/switchrecomp --skill static-recomp-scope-plan -a claude-code
ghidra-headless
It runs Ghidra with no window and saves functions and strings to files. Use it when you do not want an MCP server. It needs GhidraBoy for Game Boy ROMs.
npx skills add trailofbits/skills-curated --skill ghidra-headless -a claude-code
engine-reimplementation
A table of ways to bring a game to new systems, with effort and checks. Read it before you choose how to port in Path B.
npx skills add madpai/game-decomp-compendium --skill engine-reimplementation -a claude-code
verification-before-completion
Claude may not say done without fresh proof. Here that proof is make compare, a zero-fallback log, or a passing route.
npx skills add obra/superpowers --skill verification-before-completion -a claude-code
using-git-worktrees
Keeps each bank worker in its own copy of the repo. Its setup step may run installs, so watch it the first time.
npx skills add obra/superpowers --skill using-git-worktrees -a claude-code
handoff
Writes a short note so a fresh session can carry on. Tell it to save the note inside your project, not in a temp folder.
npx skills add mattpocock/skills --skill handoff -a claude-code
gb-fallback-harvest (write your own)
This is the loop every Game Boy recomp ran by hand. A skill makes each run the same, with the same proof.
A `.claude/skills/gb-fallback-harvest/SKILL.md` that: 1) replays every file in `tests/routes/` with `--log-frame-fallbacks`; 2) runs `summarize_interpreter_log.py`; 3) for each new address, asks Ghidra for the listing; 4) writes proven code addresses to the entry-point list and data or RAM addresses to `notes/map.md`; 5) regenerates and replays. Rule: never add an address without a Ghidra listing.
gb-routine-slice (write your own)
Each port worker then sees one routine, not the whole disassembly. Six Golden Coins used this to keep worker context small.
A `.claude/skills/gb-routine-slice/SKILL.md` plus `tools/slice.py`. The script takes `--addr BANK:ADDR`, follows jumps, jump tables and bank switches in the RGBDS files, and writes `tasks/<addr>.md`. It must print `Slice status: COMPLETE` or list what it could not follow.
Claude setup for this system
Cut Game Boy work by bank and address. On Path A, one unit is one fallback address or one bug, with a recorded input file that shows it. On Path B, one unit is one routine from the ledger. Every unit has one check: a zero-fallback replay, a passing differential run, or make compare. Keep the ROM outside the repo and pass its absolute path.
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 | Pin the exact ROM revision by hash. Look for a disassembly, a .sym file, or an existing recomp. 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 gbrecomp or RGBDS, write tools/check.sh or make compare, and set up .gitignore before any ROM is near the repo. | Opus 5.5 effort medium | Sonnet 5.5 effort medium |
| Study the game file | Find jump tables, rst dispatchers, the bank-switch routine and code copied to RAM. Build the routine ledger from metadata and traces. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for function edges and data layouts. A mistake there spreads. |
| Bulk units | Fallback addresses with clear Ghidra proof. Easy routines to name on Path B. One per worker, with make compare after each. | 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 mix code and data, self-modifying code, and units that failed three times in bulk. | 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 | PPU, sound, timer and MBC behavior in the runtime. Super Game Boy extras. Pan Docs first, SameBoy second. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for timing and graphics bugs. |
| Find a bug | Wrong frames, hangs and timing bugs. Prove the cause with a recorded input, a state dump and a differential run 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 replays all routes, reruns make compare, and looks for edited expected files, stubs and generated-code edits. | 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, the ledger counts, notes/findings.md and the parking lot after each batch. | Haiku 5.5 effort medium | Haiku 5.5 effort medium |
How many sessions at once
Max 20x (now called Ultra)
Path A: 2 to 3 sessions at once, each on its own bug or fallback in its own worktree. The ROM is small, but one runtime file is shared, so keep one writer per file. Path B: 4 to 6 bank workers plus one lead session. Merge often and rerun make compare after each merge.
Max 5x (now called Max)
Path A: 1 session, sometimes 2. Path B: 2 bank workers plus the lead. Send the hard units to one session at a time, and batch docs updates at the end of the day.
Lines for this project's CLAUDE.md
- Read `SCOPE.md` and `PROGRESS.md` at the start of every session. - Never edit generated C under `output/`. Fix the recompiler, the config or the runtime, then regenerate. - Never read a generated file whole. Grep for the bank and address, then read a few lines. - Ghidra before guessing. Paste the listing for any address you make a claim about. - Name every function as bank:address, for example `03:47f2`. The same address in two banks is two functions. - Only add an entry point that executed in a trace or is proven code in Ghidra. Data decoded as code is a failure. - The ROM lives at `<ABSOLUTE_PATH_TO_ROM>`. Never copy it into the repo or a commit. - The one check is `bash tools/check.sh` (Path A) or `make compare` (Path B). No pasted output means not done. - Do not edit `*.sha1`, `tests/routes/*.json` or the check scripts. Ask me first. - In a worktree, use absolute paths for the ROM and the tools. Gitignored files are not copied in. - Write side issues in `notes/parking-lot.md` and dead ends in `notes/findings.md`. Do not act on them. - Start every report with DONE, PARTIAL or BLOCKED.
Keep the context small
- Point Claude at
*_metadata.jsonfor function names and addresses, not at generated C. - Give Claude one route's fallback lines, not the whole log. Logs grow fast.
- Pass a Ghidra address, not a pasted wall of assembly. Let the MCP server fetch it.
- On Path B, give each worker one bank file and the ledger rows for that bank only.
- Add a
Readdeny rule for generated C, such asRead(./output/**/*.c). Do not denylogs/or the metadata file, since prompts need them. Read those withgrepor a script. - Clear the chat after each unit. The ledger and findings file carry the state.
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, to choose Path A, Path B or both for one game.
GOAL: Tell me if <GAME> (<REVISION>, SHA-1 <SHA1>) suits Path A, Path B, or both, and draft SCOPE.md. READ FIRST: the ROM header at `<ABSOLUTE_PATH_TO_ROM>` (bytes $0100-$014F only), https://pret.github.io/, https://github.com/arcanite24/gb-recompiled FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. No code. Do not copy the ROM. CHECK: Done means SCOPE.md lists ROM size, MBC type, Game Boy Color or Super Game Boy flags, prior work with links, chosen path, first milestone with its check, and an out-of-scope list. BUDGET: 45 minutes. IF BLOCKED: Stop. Say what you could not check. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: header fields. 3) Files changed. 4) Risks you see. 5) New parking-lot items.
A good answer: A one-page SCOPE.md with the hash, the MBC, links to any disassembly, and a plain risk list. It says when the game needs Super Game Boy or Color support.
Map the binary
After setup, to build the ledger both paths use.
GOAL: Build `ledger/functions.csv` from `<PATH_TO>_metadata.json` and `notes/map.md`. READ FIRST: `<PATH_TO>_metadata.json` (use a script, do not read it whole), `notes/map.md`. FENCE: Edit only `ledger/functions.csv` and `tools/make_ledger.py`. CHECK: Run `python3 tools/make_ledger.py`. Done means one row per function with bank, address, size, name, status, and the row count equals the metadata count. BUDGET: 3 failed tries. IF BLOCKED: Stop. Tell me which fields are missing. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: both counts. 3) Files changed. 4) Odd entries. 5) New parking-lot items.
A good answer: A CSV with matching counts, built by a script you can rerun. Odd entries, like functions at every byte of bank 0, are flagged, not hidden.
Unit loop: one fallback address (Path A)
For each interpreter fallback in a route replay.
GOAL: Remove the fallback at <BANK:ADDRESS>. READ FIRST: `notes/map.md`, the fallback summary lines for <BANK:ADDRESS>. FENCE: Edit only the entry-point list and `notes/map.md`. Never edit generated C. CHECK: Run `bash tools/check.sh`. Replay `logs/<ROUTE>.input` with `--headless --limit-frames <N> --log-file logs/interp.log --log-frame-fallbacks`. Run `python3 gb-recompiled/tools/summarize_interpreter_log.py logs/interp.log`. Done means <BANK:ADDRESS> no longer appears. BUDGET: 3 tries or 30 minutes. IF BLOCKED: Stop. If it is RAM code or data, say so with the Ghidra listing. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: before and after summary. 3) Files changed. 4) Fallbacks left. 5) New parking-lot items.
A good answer: A Ghidra listing that proves the code, one added entry point, and a summary without that address.
Unit loop: one routine (Path B)
For each raw routine in the ledger.
GOAL: Name and document routine <BANK:ADDRESS>. READ FIRST: the ledger row, the routine's lines in `<BANK_FILE>.asm`, `notes/ram-map.md`. FENCE: Edit only those lines, `ram.asm`, `notes/ram-map.md`, and the ledger row. CHECK: Run `make compare`. Done means OK and the row says `named`. BUDGET: 3 failed compares, then mark it `hard`. IF BLOCKED: Stop. Say what you could not explain. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: compare output. 3) Files changed. 4) Unsure names. 5) New parking-lot items.
A good answer: Clear names, a short comment on what the routine does, no byte changes, and make compare OK.
Batch dispatch
In the lead session, to hand out the next batch of units.
GOAL: Start <N> workers, one per unit, from the top of the ledger. READ FIRST: `ledger/functions.csv` (status column only), `PROGRESS.md`. FENCE: Do not edit code yourself. Give each worker its own worktree, one bank file, and the unit-loop card with its unit filled in. CHECK: Done means every worker returned DONE, PARTIAL or BLOCKED, and `make compare` (or `bash tools/check.sh`) passes on the merged branch. BUDGET: Stop the batch if two workers fail on the same cause. IF BLOCKED: Stop. Tell me the shared cause. OTHER PROBLEMS: Collect workers' parking-lot items in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the merged check output. 3) Units done, partial, blocked. 4) What is next. 5) New parking-lot items.
A good answer: A short table of units and outcomes, and a passing check after the merge, not just in each worktree.
Debug with evidence
When a frame is wrong, the game hangs, or sound drifts.
GOAL: Prove the cause of <BUG>. Do not fix it yet. READ FIRST: `logs/<ROUTE>.input`, `logs/<STATE>.json`, `notes/findings.md`. FENCE: Edit only `notes/findings.md`. You may add one temporary log line, and you must remove it after. CHECK: Done means a cause with an address, a register or memory value, and a replay that shows it every time. BUDGET: 60 minutes or 2 wrong theories. IF BLOCKED: Stop. List what you ruled out and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the replay output. 3) Files changed. 4) Cause in one sentence. 5) New parking-lot items.
A good answer: A cause tied to an address and a value, a replay that shows it, and no code change yet.
Verify and hunt for cheating
In a fresh session after any batch of claimed work.
GOAL: Check that the work in commits <RANGE> is real. READ FIRST: `git log --stat <RANGE>`, `PROGRESS.md`. FENCE: Read only. Do not fix anything. CHECK: Rerun `bash tools/check.sh`, `bash tools/regress.sh` and `make compare` as they apply. Look for edits to generated C, to `*.sha1` or route files, to the check scripts, for stubs that return early, and for entry points with no proof. BUDGET: 45 minutes. IF BLOCKED: Stop. Tell me what you could not rerun. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the check outputs. 3) Each suspicious change with file and line. 4) Verdict per commit. 5) New parking-lot items.
A good answer: Fresh check outputs and a per-commit verdict. Any edit to an expected file or generated code is named.
Status report
At the end of a day, or when you return after a break.
GOAL: Give me the project status from files only. READ FIRST: `PROGRESS.md`, `ledger/functions.csv` (counts by status), `notes/parking-lot.md`. FENCE: Read only. CHECK: Run `bash tools/check.sh` or `make compare` and paste the last lines. BUDGET: 10 minutes. IF BLOCKED: Stop. Say which file is missing. REPORT: 1) Check output. 2) Counts by status. 3) Fallbacks or routines left. 4) Open blockers. 5) Parking-lot items waiting for me.
A good answer: Numbers from the ledger and a fresh check result. No guesses.
Session handoff
Before you close a session or switch plans.
GOAL: Write what the next session needs to continue. READ FIRST: this session's changes (`git diff --stat`), `PROGRESS.md`. FENCE: Edit only `PROGRESS.md` and `notes/findings.md`. CHECK: Done means PROGRESS.md has: the last check output, the unit in progress, the next 3 units, dead ends tried, and exact commands to resume. BUDGET: 10 minutes. IF BLOCKED: Stop and say what you do not know. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the new PROGRESS.md section. 3) Files changed.
A good answer: A short section a new session can follow without the chat history.
Blocked or cannot deliver
When a unit keeps failing, or you suspect it cannot be done.
GOAL: Give me a plain answer on whether <UNIT> can be done now. READ FIRST: `notes/findings.md` entries for <UNIT>. FENCE: Read only. No new attempts. CHECK: Done means you name what blocks it, the evidence, and 2 or 3 options with cost. BUDGET: 15 minutes. IF BLOCKED: Say "I do not know yet" and what would tell us. REPORT: 1) BLOCKED or NOT BLOCKED. 2) Proof. 3) Options with cost. 4) Your pick and why.
A good answer: A plain yes or no, with evidence, like code run from RAM or a missing hardware feature, and real options.
Scope change request
When Claude wants to leave the fence, like adding widescreen or touching the runtime.
GOAL: Ask me before you change scope. READ FIRST: `SCOPE.md`. FENCE: Do not make the change. Write the request only, in `notes/scope-requests.md`. CHECK: Done means the request names the change, why it is needed, files it touches, the risk to the state-hash tests, and how to undo it. BUDGET: 10 minutes. IF BLOCKED: Stop and ask me. REPORT: 1) The request. 2) What you will do if I say no.
A good answer: A short request you can approve or refuse. No code changed.
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 patches the generated C to make a bug go away. The next regenerate wipes it out. | Never edit files under output/. Fix the recompiler, the config or the runtime, then regenerate. Add a hook that blocks edits there. |
| Claude adds guessed entry points so the fallback count drops. Data then turns into fake code. | Add an entry point only with a trace hit or a Ghidra listing that proves it is code. Paste the proof in the report. |
| Claude decodes SM83 bytes by hand or guesses what a routine does. | Ghidra before guessing. If the Ghidra MCP server does not answer, stop and say so. |
| Claude reads whole generated bank files for context and fills its window. | Grep for the bank and address. Read at most 60 lines around it. |
| Claude writes a game-specific hack, like a fake event, to get past a hang. | No per-game hacks or stubs in the runtime without my approval. Find the hardware cause first. |
| Claude edits an expected state file or hash so a test turns green. | Never edit *.sha1, tests/routes/*.json or check scripts. Ask me first, with the reason. |
| On a port, Claude bulk-translates assembly line by line into Rust or C. | One routine per ticket, written by hand from the slice, with a byte test that fails on revert. |
Problems other projects hit
The repo cannot be rebuilt. Generated C files were deleted, and no regenerate command was written down.
Fix: Keep the recompiler version, the exact command and the config in the repo. Pin the recompiler as a submodule.
Seen in: Battle Arena Toshinden GB Recomp, Kirby's Dream Land Recomp, Kirby's Pinball Land: Recomp, Super Mario Land 3: Wario Land — Recomp
Static analysis misses code reached through jump tables, rst calls, or code copied to RAM, like the OAM DMA routine.
Fix: Keep the fallback interpreter on. Log fallbacks during real play, then add proven addresses as entry points. Main gbrecomp runs RAM code on its interpreter. The Tetris fork declares it in its config as an hram_overlay.
Seen in: TetrisGBRecomp, DK94 Recomp, WarioBlast Recomp
Random input mostly reaches menus and early stages. Later code stays unfound.
Fix: Record a real playthrough with --record-input and harvest from that too.
Seen in: WarioBlast Recomp
A release shipped with the wrong expected CRC32 baked in, so it refused every ROM.
Fix: Test the packaged build from a clean folder with the real ROM before you publish. Keep the hash in one place.
Seen in: TetrisGBRecomp
A Windows release failed on other PCs because SDL loads a DLL by name, and the packager missed it.
Fix: Build and test from a clean PATH, and run the zip on a PC without MSYS2.
Seen in: TetrisGBRecomp
Generated code or ROM-derived files were committed by mistake. They stay in git history.
Fix: Put *.gb, *.gbc, output/ and build folders in .gitignore before the first commit. Scan releases for ROM bytes.
Seen in: Battle Arena Toshinden GB Recomp, Super Mario Land 3: Wario Land — Recomp, Bigodao
Widening the view for widescreen changed when enemies spawned. Doors and enemies went missing.
Fix: Keep extras render-only. Draw more of the level, but leave game logic alone, so state-hash tests still pass.
Seen in: Bigodao, Six Golden Coins
Game controllers are not visible to Linux programs under WSL.
Fix: Build and play the Windows version. Use WSL for building and headless tests.
Seen in: DK94 Recomp, WarioBlast Recomp
GhidraBoy is archived and supports Ghidra only up to 11.4.2. The newest GhidraMCP wants Ghidra 12.1.4.
Fix: Install Ghidra 11.3.2 for Game Boy work. GhidraBoy supports it, and GhidraMCP 1.4 is built for it. Use the ghidra-headless skill if the MCP plugin will not load there.
Seen in: TetrisGBRecomp
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 5 weeks | 2 to 12 weeks | 6 to 26 weeks | medium confidence |
| Path B: full decomp | 4 to 9 months | 8 months to 2 years | 2 to 6 years | low confidence |
Read this first
These numbers assume one medium game and one person giving 10 to 15 hours a week. Path A is medium confidence. Three dated AI projects agree on days to weeks, and one person released five gbrecomp builds in six days. But most public histories hide earlier work. Only Bigodao shows its full history, from first commit to release in 4 days. Path B is low confidence. No AI project here built a disassembly from zero with dates. The Path B range mixes the pre-agent disassemblies above with one AI port that began from a finished disassembly. The no-AI Path A range is a guess from the steps, since no pre-agent Game Boy recomp was reviewed.
What makes it faster or slower
- How well
gbrecomphandles the game. Plain games boot in a day. Games with odd jump tables or RAM code need many harvest rounds. - Whether a byte-matching disassembly already exists. With one, Path B starts at the port and drops to weeks.
- ROM size and MBC type. A 32 KB game has one bank. A 512 KB game has 32 banks to map and name.
- Extras like widescreen, Super Game Boy sound or Color support. Each adds runtime work and new tests.
- Your review time. Each unit needs a look at its proof, and that is your 10 to 15 hours a week.
The facts behind these numbers
| Project | What happened | Link |
|---|---|---|
| Bigodao | First commit 2026-10-01. First release 2026-10-05. 135 commits to 2026-11-04. 1 contributor. AI use declared by the developer. | source |
| TetrisGBRecomp | First commit and first release 2026-04-01. Latest release 2026-08-17. 1 contributor. Commits carry Claude co-author trailers. Built on mstan's fork of gb-recompiled, made 2026-04-01 from history that starts 2026-01-03. | source |
| WarioBlast Recomp | Created, built and released on 2026-10-28, reusing the DK94 Recomp engine. 3 commits with Claude co-author trailers. | source |
| Kirby's Dream Land Recomp | Repo created 2026-08-25, release 2026-08-26 with gbrecomp output. 1 contributor. AI use not stated. | source |
| omegakatana92 recomps (5 repos) | One person released five Game Boy recomps between 2026-08-25 and 2026-08-30: Toshinden and Super Mario Land 3 (08-25), Kirby's Dream Land and Kirby's Pinball Land (08-26), Kirby's Dream Land 2 (08-30). Four show gbrecomp output. None says what works or was tested. | source |
| Six Golden Coins (sml2-rs) | First commit 2026-10-13. First release 2026-10-27. v0.3.1 on 2026-11-13 after 880 commits. 1 contributor with agent files. Started from an existing disassembly. | source |
| gb-recompiled | First commit 2026-01-03. v0.0.1 on 2026-01-12. v0.1.0 on 2026-08-03. 5 contributors. | source |
| pret/pokered | First commit 2010-01-05. 78 contributors and 2,839 commits by 2026-11-13. A pre-agent, many-person disassembly. | source |
| Link's Awakening DX Disassembly | First commit 2014-06-28. 39 contributors and 2,525 commits. Still getting commits in 2026 (last push 2026-09-25). | source |
| Wario Land disassembly (Kak2X/wl) | One person. First commit 2021-05-16. Builds a bit-perfect ROM. 69 commits. | source |
Projects reviewed (12)
All 12 Game Boy projects on RecompLauncher with a GitHub repo were reviewed. The catalog lists 13. The 13th, Blue's Alphabet Book, is a Game Boy Color game and was left out. None came from outside RecompLauncher.
What each project teaches
G1R Deluxe
Pokemon Red
Started: 2026-07. First release: 2026-07. Code: Lua.
AI: yes: AIDisclosure.md says about 7 commits were AI-assisted and reviewed by people
Lesson: A finished disassembly can be the spec for a new engine. Each ported script names its source file, and headless tests run real game logic.
AM2RLauncher
Metroid II: Return of Samus
Started: 2021-03. First release: 2021-03. Code: C#.
AI: none stated; the catalog's AI flag was set by the store, and its AGENTS.md refuses autonomous agent contributions Agent files: AGENTS.md.
Lesson: This patches a PC fan remake, not the Game Boy ROM. Its AGENTS.md shows that some projects reject agent-driven pull requests.
Bigodao
Wario Land: Super Mario Land 3
Started: 2026-10. First release: 2026-10. Code: C and C++.
AI: yes: AI use for code declared by the developer to the catalog
Lesson: Keep edits to generated C as a replayable patch map keyed by address. Then regenerating never loses your changes.
Six Golden Coins
Super Mario Land 2: 6 Golden Coins
Started: 2026-10. First release: 2026-10. Code: Rust.
AI: yes: AGENTS.md, ORCHESTRATION.md and PORTING.md describe a supervisor and porter agent workflow Agent files: AGENTS.md, ORCHESTRATION.md, PORTING.md, docs/agent-prompts.md.
Lesson: This is the Path B model on Game Boy. One routine per ticket, a sliced task file, and a byte test that must fail if the line is reverted.
Kirby's Dream Land Recomp
Kirby's Dream Land
Started: 2026-08. First release: 2026-08. Code: C.
AI: none stated
Lesson: gbrecomp output builds on its own, so a release can come in a day. But without the command and config, nobody else can rebuild it.
TetrisGBRecomp
Tetris
Started: 2026-04. First release: 2026-04. Code: C.
AI: yes: commits carry Claude co-author trailers; rules come from the engine's CLAUDE.md
Lesson: A game repo can be tiny: a config, a list of harvested entry points and a hooks file. Play, harvest fallbacks, regenerate, until a run logs zero.
Super Mario Land 3: Wario Land — Recomp
Wario Land: Super Mario Land 3
Started: 2026-08. First release: 2026-08. Code: C.
AI: none stated
Lesson: A broken .gitignore let a whole build folder into git. Check your ignore file before the first commit.
DK94 Recomp
Donkey Kong
Started: 2026-10. First release: 2026-10. Code: C.
AI: none stated; catalog flag set by the store
Lesson: Build the recompiler and the interpreter from one opcode table. Then per-frame state hashes on random seeds become a cheap, strong test.
Battle Arena Toshinden GB Recomp
Battle Arena Toshinden
Started: 2026-08. First release: 2026-08. Code: C.
AI: none stated
Lesson: Its metadata shows functions at nearly every byte of bank 0. That hints at a blind code scan, so check the function list.
Kirby's Pinball Land: Recomp
Kirby's Pinball Land
Started: 2026-08. First release: 2026-08. Code: C.
AI: none stated
Lesson: A 256 KB ROM became 37 function files and 13 dispatch files. That gives you a size estimate before you start.
Kirby's Dream Land 2 Recomp
Kirby's Dream Land 2
Started: 2026-08. First release: 2026-08. Code: none published.
AI: none stated
Lesson: The repo holds only a licence and a release zip. There is nothing to learn from or rebuild, so publish your config and steps.
WarioBlast Recomp
Wario Blast Featuring Bomberman!
Started: 2026-10. First release: 2026-10. Code: C.
AI: yes: all three commits carry a Claude co-author trailer
Lesson: Moving the engine to a new game was mostly rewriting one hints file. Put every game-specific fact in one small file.
What we could not check
- No AI-assisted Game Boy disassembly built from zero, with dates, was found. The Path B times are an estimate.
- Several repos were released on their first day. Their real work hours are unknown.
- Nothing in this guide was run here. No ROM was used. Commands and flags were checked against the tools' docs and source code only.
- Ghidra 11.3.2 with GhidraBoy and
GhidraMCP1.4 together was not tested. The version match comes from their files. Theclaude mcp addcommand for 1.4 is built from its README and was not run. - That
--dump-frameswrites files in--headlessmode was read from the runtime source, not seen in a run. - Whether
--differentialexits with an error code on a mismatch was not checked. Read its output. - A community port of GhidraBoy to Ghidra 12.1.2 exists (
ptthess/GhidraBoy-12.1.2-port). It was not checked. - Whether any commercial Game Boy game was written in C was not checked. This guide assumes assembly, as the official kit was an assembler.
- The
--harvestflag used by TetrisGBRecomp is in themstan/gbrecompiledfork. It was not confirmed in the maingb-recompiledtool. - The
tools/check.sh,tools/regress.sh,make compareand slicer scripts in the steps are scripts you write. Their names are suggestions. - Game Boy emulator MCP servers exist on GitHub, such as
mcp-gameboy. None was used by a reviewed project, and none was checked. - The MSYS2 package names for CMake and Ninja were checked as pages only. They were not installed here.
Sources
- gb-recompiled README
- gb-recompiled RUNTIME.md
- gb-recompiled AGENTS.md
- gb-recompiled GROUND_TRUTH_WORKFLOW.md
- mstan/gbrecompiled CLAUDE.md
- mstan/gbrecompiled GHIDRA_SETUP.md
- Pan Docs: Specifications
- Pan Docs: Memory Map
- Pan Docs: MBCs
- Official Game Boy SDK (retroreversing)
- RGBDS install
- mgbdis README
- pret/pokered INSTALL.md
- GhidraBoy README
- GhidraMCP README
- SameBoy README
- PyBoy README
- BGB emulator
- MSYS2
- Link's Awakening DX Disassembly
- Wario Land disassembly
- RGBDS Linux install
- RGBDS Windows install
- Pan Docs: The Cartridge Header
- MSYS2 SDL2 package
- rustup
- gb-recompiled command-line flags (main.cpp)
- gb-recompiled run_ground_truth.py
- Ghidra 11.3.2 release
- GhidraBoy releases
- GhidraMCP 1.4 release
- GhidraMCP 1.4 extension.properties
- Pan Docs: Interrupt Sources
- RGBDS rgbasm manual (INCBIN)
- sml2-rs PORTING.md