Virtual Boy decomp guide
One free framework recompiles Virtual Boy games to PC. Nobody has made a full decomp yet.
What this system is like
The Virtual Boy is a small Nintendo system from 1995. It shows 3D pictures in red, one picture per eye. One status table lists just 28 store cartridges, counting each region. Its chip is the NEC V810, a 32-bit CPU. The reviewed games are 512 KB to 1 MB.
For Path A, a free framework called vbrecomp turns a game into C code. A framework is a shared set of tools and code that each game builds on. It also runs the Beetle VB emulator as a referee. Three games run on PC this way: Mario's Tennis, Zero Racers and SD Gundam Dimension War.
Path B is new ground. No one has made a full decomp of any Virtual Boy game. No byte-diff tool supports this chip. Nintendo's own compiler, VUCC, is an old DOS program, and nobody knows which games used it. So this guide gives a route that checks your C against the recompiled game.
Almost all of this runs on Windows. The build scripts use PowerShell and MSYS2. You do not need WSL. Expect weeks for Path A. Expect a year or more for Path B.
A referee comes with the tools
vbrecomp can run the Beetle VB emulator as a second program. A script feeds both the same buttons. Then it compares the CPU, memory, both eyes and sound at set frames. This is called a differential test, and it is your main check here.
Missed code still runs, and is counted
The runtime runs the generated C. When it reaches code the recompiler missed, a built-in interpreter runs it. An interpreter reads machine code and runs it one step at a time. The runtime counts those steps. So "zero fallback" proves the C covers that route.
The screen draws in blocks
The VIP draws 8 rows at a time. Games swap tiles between blocks. A renderer that draws all 224 rows at once mixes them up. One early Mario's Tennis recomp hit exactly this.
A tiny scene around one framework
Only three game projects exist, all by one author on one framework. So a fix goes into the framework, and every game must still pass after it. A second, separate recompiler exists too, with status for 76 ROMs.
No decomp tools for this chip
The common diff tools do not list the V810. Ghidra needs a third-party add-on to read it. Nobody has shown which compiler built each game. Path B means building some of your own tools.
Two paths
Get it running on PC
You copy a working game repo built on vbrecomp and point it at your own cartridge dump. A Python tool finds the code and writes C. The C links into a runtime that acts like the console. You check each screen against the Beetle VB referee. Then you close coverage gaps until the routes run with zero fallback.
Full decomp for porting
Nobody has done this for any Virtual Boy game. So this path is a plan, built from tools that exist. You keep the Path A build as your test bed. You rewrite the game one function at a time into readable C. Each function must give the same memory and registers as the recompiled one, from the same saved states.
Path A: Get it running on PC
You copy a working game repo built on vbrecomp and point it at your own cartridge dump. A Python tool finds the code and writes C. The C links into a runtime that acts like the console. You check each screen against the Beetle VB referee. Then you close coverage gaps until the routes run with zero fallback.
Method: static recompilation
Pick this first, for any Virtual Boy game. All three reviewed games used it. Path B also needs this build as its test bed.
Pick the game and write the scope
You pin down the exact cartridge dump. A different dump is a different program. You also look for work that already exists. A game that is already done saves you months.
- Keep your ROM outside the repo, for example
C:\Games\<game>.vb. - Hash it in PowerShell:
Get-FileHash -Algorithm SHA256 C:\Games\<game>.vb. - Get the CRC32:
python -c "import zlib,sys;print(hex(zlib.crc32(open(sys.argv[1],'rb').read())))" C:\Games\<game>.vb. - Get the size:
(Get-Item C:\Games\<game>.vb).Length. - Look for prior work on RecompLauncher, in the
mstanGitHub repos, and inCOMPATIBILITY.mdofsp00nznet/vbrecomp. - Write
SCOPE.md: game, region, SHA-256, CRC32, size, goal, technique, first milestone and its check, and what is out of scope.
Prompt for ClaudeGOAL: Draft SCOPE.md for a Virtual Boy static recomp of <GAME> (<REGION>). READ FIRST: https://github.com/mstan/vbrecomp (README), https://github.com/sp00nznet/vbrecomp/blob/main/COMPATIBILITY.md, the RecompLauncher Virtual Boy list. FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. Do not copy, move or open the ROM except to hash it. Do not write code. CHECK: Run `Get-FileHash -Algorithm SHA256 <ABSOLUTE_PATH_TO_ROM>` and paste it. Done means SCOPE.md lists game, region, SHA-256, CRC32, size in bytes, goal, technique (vbrecomp hybrid recomp with Beetle VB referee), 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 hash or a region. 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: the hash output. 3) Files changed. 4) Prior work found, with links and status. 5) New parking-lot items.
Done whenSCOPE.mdnames the ROM by SHA-256, CRC32 and size, and you approved it.Proof this works: SD Gundam: Dimension War Recompiled, MarioTennisVirtualBoyRecomp, https://github.com/sp00nznet/vbrecomp
- Keep your ROM outside the repo, for example
Install the tools and build the framework alone
You install the compilers and build
vbrecompwith no game. Its own rules make this the first milestone. You also build the Beetle VB referee. Nothing game-specific happens yet.- Install MSYS2 from msys2.org. In its MINGW64 terminal run
pacman -S --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-make mingw-w64-x86_64-SDL2. - Install a normal Windows Python 3.11 or newer, and Git.
- In PowerShell:
$env:PATH = "C:\msys64\mingw64\bin;$env:PATH". git clone --recurse-submodules https://github.com/mstan/vbrecomp.gitthencd vbrecomp.- Run the tests:
python -m unittest discover recompiler/tests. - Build the runtime:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Releasethencmake --build build --target vb-runtime. - Start
.\build\runtime\vb-runtime.exe --port 4390. In a second window runpython tools/_ping.py --port 4390. - Build the referee into a new folder:
.\tools\prepare-oracle.ps1 -Destination ..\beetle-vb. It expects Git atC:\Program Files\Git\cmd\git.exe. If yours is elsewhere, add-Git <path to git.exe>. - Check the referee library exists:
Test-Path ..\beetle-vb\libmednafen_vb.ashould printTrue. This is the filevbrecomp's CMake looks for. You build thevb-beetle.exeprogram from it in the next step. - Rule 11 of
vbrecomp/CLAUDE.mdalso wants a coverage report from the decoder. You make that from your own ROM in step 4.
Prompt for ClaudeGOAL: Build mstan/vbrecomp with no game and prepare the Beetle VB referee. READ FIRST: `vbrecomp/README.md`, `vbrecomp/docs/BRINGUP.md`, `vbrecomp/CLAUDE.md` section 11. FENCE: Edit nothing inside `vbrecomp/`. Create only `tools/setup-notes.md`. Do not install anything; if a tool is missing, list it and stop. CHECK: Run `python -m unittest discover recompiler/tests`, build `vb-runtime`, start it with `--port 4390`, then run `python tools/_ping.py --port 4390`. Done means the tests pass and ping returns ok. Then run `.\tools\prepare-oracle.ps1 -Destination <ABSOLUTE_NEW_FOLDER>`. Done also means it exits with code 0 and `Test-Path <ABSOLUTE_NEW_FOLDER>\libmednafen_vb.a` prints True. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Do not edit CMake files or scripts to get past an error. Paste the first error line and the command. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: test summary line, ping reply, last lines of the referee build. 3) Files changed. 4) Tool versions found. 5) New parking-lot items.
Done whenThe Python tests pass,_ping.pygets an ok reply, andTest-Path ..\beetle-vb\libmednafen_vb.aprintsTrue.Proof this works: https://github.com/mstan/vbrecomp/blob/master/docs/BRINGUP.md, ZeroRacersVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/CLAUDE.md
- Install MSYS2 from msys2.org. In its MINGW64 terminal run
Make your game repo from a working host
You copy a game repo that already works and make it yours. SD Gundam was made this way from Zero Racers. You also write the rules file, the progress ledger and the one check script.
git clone --recurse-submodules https://github.com/mstan/ZeroRacersVirtualBoyRecomp.git <GAME>Recomp.- Remove the Zero Racers color and multiplayer code you do not need. Keep
tools/build.ps1, thevbrecomp/andrecomp-ui/submodules and their.pinfiles. - Make
<game>.toml. Put in only facts you have checked. SD Gundam's file is two comment lines, because discovery starts from the cart's vectors. - Edit
game-build.json. It has three fields:module(your module name),ui_option(the CMake switch for the launcher) andsha256(your ROM's SHA-256 in lower case). The build refuses any ROM whose hash differs. - Set
VBRECOMP_GAMEinCMakeLists.txtto the same module name, and rename theui_optionswitch there to match. - Build with the referee:
.\tools\build.ps1 -Rom <ROM> -Oracle <ABSOLUTE_PATH>\beetle-vb. The referee is built only when you pass-Oracle. It lands atbuild\vbrecomp\runtime\vb-beetle.exe. - Add
roms/,generated/,validation/,reference/and save files to.gitignore. - Write
CLAUDE.mdfrom the lines in this guide, plusPROGRESS.mdandnotes/parking-lot.md. - Write
tools/check.ps1. It runs the Python tests, then.\tools\build.ps1 -Rom <ROM> -Oracle <BEETLE_FOLDER>, which also runs CTest. Later it runs the route suite too. It prints PASS or FAIL.
Prompt for ClaudeGOAL: Turn a clone of ZeroRacersVirtualBoyRecomp into a host for <GAME> that builds. READ FIRST: `SCOPE.md`, `README.md`, `tools/build.ps1`, `vbrecomp/docs/ASSISTED-COVERAGE.md` (first section), https://github.com/mstan/SDGundamDimensionWarVirtualBoyRecomp (README layout section). FENCE: Edit only the top-level host files, `src/`, `<game>.toml`, `game-build.json`, `CMakeLists.txt`, `.gitignore`, `CLAUDE.md`, `PROGRESS.md` and `tools/check.ps1`. Never edit `vbrecomp/` or `recomp-ui/`. Never put the ROM in the repo. CHECK: Run `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>`. Done means it builds the game executable and prints PASS for the build and test parts. BUDGET: Stop after 3 failed builds with the same error. IF BLOCKED: Stop. Do not patch the submodules. Tell me the error and which file you think needs a change. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the check output. 3) Files changed. 4) Zero Racers code you removed and why. 5) New parking-lot items.
Done whentools/check.ps1prints PASS,Test-Path build\vbrecomp\runtime\vb-beetle.exeprintsTrue, andgit statusshows no ROM orgenerated/file.Proof this works: SD Gundam: Dimension War Recompiled, ZeroRacersVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/docs/INTEGRATION-SD-GUNDAM.md
Map the cartridge
You list every function and check the list two ways. The recompiler finds code on its own. Ghidra, with a V810 add-on, finds code too. Where they disagree, you have a lead.
- In
vbrecomp/:python -m recompiler.cli.vbrecomp_coverage --rom C:\Games\<game>.vb. It reports opcode coverage and unknown bytes. - Generate C once:
python -m recompiler.cli.vbrecomp_codegen --rom C:\Games\<game>.vb --module <module> --out ..\generated --seeds-toml ..\<game>.toml. Note how many functions it found. - Install Ghidra 12.2 and the matching
ghidra-v810zip with File > Install Extensions. - Write
tools\ghidra\export_functions.pylike the one insp00nznet/vbrecomp/scripts/ghidra/. It writes address, size and name. That script is marked@runtime Jython. In Ghidra 12.2, Jython is an optional add-on inGhidra\Extensions\Jython, so install it too, or write the script in Java. - Import the ROM headless:
<GHIDRA_DIR>\support\analyzeHeadless.bat C:\work\ghidra vb -import C:\Games\<game>.vb -loader BinaryLoader -loader-baseAddr 0x07000000 -processor V810:LE:32:default -scriptPath <ABSOLUTE_REPO>\tools\ghidra -postScript export_functions.py <ABSOLUTE_REPO>\notes\ghidra_funcs.tsv. Ghidra finds a post-script only by its name, so-scriptPathmust name the folder. - Merge both lists into
notes/functions.tsv: address, size, found by, status.
Prompt for ClaudeGOAL: Build the function ledger for <GAME> from vbrecomp discovery and Ghidra. READ FIRST: `SCOPE.md`, `vbrecomp/docs/HARDWARE_NOTES.md` (memory map), https://github.com/aleph-void/ghidra-v810 (README). FENCE: Create only `notes/functions.tsv`, `notes/map.md` and `tools/ghidra/export_functions.py`. Never edit `vbrecomp/` or `generated/`. Treat Ghidra as a reference for bytes and labels, not for behavior. CHECK: Run `python tools/ledger_check.py notes/functions.tsv` (write it first: it checks every row has a hex address, a size and a status, and that no addresses repeat). Done means it prints OK, and notes/map.md lists the counts from vbrecomp, from Ghidra, and the addresses found by only one side. BUDGET: Stop after 90 minutes, or after 3 failed Ghidra imports. IF BLOCKED: Stop. Do not guess function edges. List the address ranges you could not decode. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: ledger_check output and the three counts. 3) Files changed. 4) Unknown bytes or opcodes from vbrecomp_coverage. 5) New parking-lot items.
Done whennotes/functions.tsvpasses the ledger check, andnotes/map.mdlists the addresses that only one tool found.Proof this works: https://github.com/sp00nznet/vbrecomp/blob/main/scripts/ghidra/export_functions.py, SD Gundam: Dimension War Recompiled, https://github.com/mstan/vbrecomp/blob/master/PLAN.md
- In
Boot to the title screen and match the referee
You build and run the whole game. Then you compare its title screen with the referee, pixel by pixel. Mario's Tennis matched with zero pixels off. This is your first light.
- Build:
.\tools\build.ps1 -Rom 'C:\Games\<game>.vb' -Oracle <BEETLE_FOLDER>. It regenerates the C, builds and runs CTest. - Run:
.\build\vbrecomp\runtime\<GAME_EXE>.exe --launcher --rom 'C:\Games\<game>.vb'. - Read
dispatch_misses.lognext to the executable. If it lists functions, fix discovery first. - Write
routes/title.json: a list such as[{"frames": 600, "pad": 0}]that reaches the title screen. Eachpadis a button mask held for that many frames. - Compare both eyes with the referee:
python vbrecomp/tools/cosim.py compare --runtime <EXE> --oracle build\vbrecomp\runtime\vb-beetle.exe --rom <ROM> --route routes/title.json --planes video,video_right --out validation\<new-folder>. It starts both programs itself. - To see it live, start each program with
--rom <ROM> --paused, the game on--port 4390andvb-beetle.exeon--port 4391. Sendpython vbrecomp/tools/debug_client.py --port <PORT> run_frames frames=600to each. - Then run
python vbrecomp/tools/_framebuf_diff.py --tolerance 0. It printsframebuf_diff: 0/86016 pixels differon a perfect match.
Prompt for ClaudeGOAL: Show <GAME> reaching its title screen with a pixel match against the Beetle VB referee. READ FIRST: `SCOPE.md`, `notes/map.md`, `vbrecomp/DEBUG.md` (rules 0 and 0a), `vbrecomp/docs/PARITY.md`. FENCE: Edit only `<game>.toml`, `PROGRESS.md` and `routes/title.json`. Never edit `generated/`. Never add a stub, a fake frame or a printf. CHECK: Run `.\tools\build.ps1 -Rom <ABSOLUTE_PATH_TO_ROM> -Oracle <BEETLE_FOLDER>`, then `python vbrecomp/tools/cosim.py compare --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/title.json --planes video,video_right --out validation/<NEW_FOLDER>`. Done means its report says `"passed": true`, and `dispatch_misses.log` is empty. BUDGET: Stop after 3 failed builds with the same error, or 60 minutes with no new screen. IF BLOCKED: Stop. Do not patch generated C or the runtime. Tell me the `vb_stub_abort` banner or the first error, 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 diff output and both screenshot paths. 3) Files changed. 4) Fallback counts. 5) New parking-lot items.
Done whencosim.py compare --planes video,video_rightonroutes/title.jsonreports"passed": true, anddispatch_misses.logis empty.Proof this works: MarioTennisVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/DEBUG.md, https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md
- Build:
Close the coverage gaps
Now you hunt the code the recompiler missed. Each fallback address is one unit of work. You fix the finder in the framework, never the output. Then the next game benefits too.
- Run native only:
python vbrecomp/tools/cosim.py compare --a-mode native --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ROM> --route routes/<route>.json --out validation\<new-folder>. Native mode stops at the first missing code. SD Gundam'stools/validate.ps1 -NativeOnlywraps this same command. - List fallback addresses: run the game in its normal hybrid mode with
--port 4390, thenpython vbrecomp/tools/debug_client.py fallback_coverage.execution_statsgives the totals. - Fix the cause in
vbrecomp/recompiler/v810/analysis.pyon your own framework branch. Add a test inrecompiler/tests/. - Regenerate, rebuild and run. A recompiler change is not done until all three pass.
- Add a seed to
<game>.tomlonly for code proven by a run and a ROM reference.
Prompt for ClaudeGOAL: Remove the fallback at guest address <PC> on route <ROUTE>. READ FIRST: `CLAUDE.md`, `notes/functions.tsv`, `vbrecomp/CLAUDE.md` sections 4 and 16, `vbrecomp/PRINCIPLES.md` (Hints Are Not Correctness). FENCE: Edit only `vbrecomp/recompiler/` on branch `<BRANCH>` and its tests, plus `notes/functions.tsv`. Never edit `generated/`. No per-game hint unless a class fix is blocked; ask me first. CHECK: Run `python -m unittest discover recompiler/tests` in `vbrecomp/`, then `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>`, then `python vbrecomp/tools/cosim.py compare --a-mode native --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/<ROUTE>.json --out validation/<NEW_FOLDER>`. Done means all pass, the compare report says `"passed": true`, and <PC> no longer shows in `fallback_coverage`. BUDGET: Stop after 4 tries without progress, or 45 minutes. IF BLOCKED: Stop. Do not work around it. Tell me the instruction pattern at <PC> 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: test output and the fallback count before and after. 3) Files changed. 4) Other addresses the same fix covered. 5) New parking-lot items.
Done whencosim.py compare --a-mode nativeon the route reports"passed": true, andfallback_coveragelists no address from it.Proof this works: SD Gundam: Dimension War Recompiled, ZeroRacersVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/docs/ASSISTED-COVERAGE.md, https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md
- Run native only:
Fix hardware bugs with proof
Some bugs are in the console stand-in, not the code. Graphics, sound, timers and interrupts live there. You find the first frame where your build and the referee split. Then you trace the write that caused it.
- Write the route as JSON: a list of
{"frames": 300, "pad": 0}steps. - Compare:
python vbrecomp/tools/cosim.py compare --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ROM> --route routes/<route>.json --out validation\<new-folder>. It stops at the first checkpoint that differs. - Zoom in:
python vbrecomp/tools/cosim.py trace --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ROM> --frames 10 --instructions 2900000 --out validation\<new-folder>. - Walk the frame ring back to the first bad frame. Then use the
wtracecommands to find the first bad write. - Fix it in the runtime, such as
vbrecomp/runtime/src/vip.cortimer.c. If you need to see more state, add a TCP command, not a print.
Prompt for ClaudeGOAL: Find and fix why <GAME> differs from the referee at <FRAME> on route <ROUTE>. READ FIRST: `vbrecomp/DEBUG.md`, `vbrecomp/docs/PARITY.md`, `vbrecomp/docs/HARDWARE_NOTES.md`, `notes/findings.md`. FENCE: Edit only `vbrecomp/runtime/` on branch `<BRANCH>` and its tests, plus `notes/findings.md`. Never edit `generated/`. No printf, no log files, no game-specific shim. CHECK: Run `python vbrecomp/tools/cosim.py compare --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/<ROUTE>.json --out validation/<NEW_FOLDER>`. Done means every checkpoint agrees, and `ctest --test-dir build --output-on-failure` passes. BUDGET: Stop after 2 fixes that do not move the first bad checkpoint. IF BLOCKED: Stop. Do not change the route or the referee. Tell me the first differing field, the frame, and your best proven lead. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: compare output before and after. 3) Files changed. 4) The cause, with the address and frame. 5) New parking-lot items.
Done whencosim.py compareon the route reports no differing checkpoint, and CTest passes.Proof this works: ZeroRacersVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md, https://github.com/sp00nznet/vb-mariotennis
- Write the route as JSON: a list of
Build a regression set
You save routes that reach many parts of the game. A script replays them all. It runs your build twice, once mostly native and once fully interpreted, and compares the two. The gates and route compares add the referee. Old fixes then stay fixed. Mario's Tennis has 14 cases and Zero Racers has 16.
- List route cases in
tests/coverage/suite.json: menus, each mode, and late parts of the game. - Run:
python vbrecomp/tools/run_assisted_suite.py --runtime <EXE> --rom <ROM> --suite tests/coverage/suite.json --out validation\<new-folder>. By default it needs zero fallback. It picks free ports by itself. It does not start the referee. - Run the gates:
python vbrecomp/tools/cosim.py gates --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ROM> --route routes/<route>.json --out validation\<new-folder>. It checks that each mode gives the same result twice. It also flips one WRAM byte on purpose and must catch it. - Run
cosim.py compareagainst the referee on each saved route, as in step 7. - For runs with nobody watching, set
SDL_AUDIODRIVER=dummyandSDL_VIDEODRIVER=dummy. - After any framework change, rerun the suites of the other games you own.
- Add the suite to
tools/check.ps1.
Prompt for ClaudeGOAL: Add route cases so the suite covers <AREA_OF_GAME> with zero fallback. READ FIRST: `tests/coverage/suite.json`, `vbrecomp/docs/ASSISTED-COVERAGE.md` (assisted surveys), `PROGRESS.md`. FENCE: Edit only `tests/coverage/suite.json`, `routes/` and `PROGRESS.md`. Do not edit expected results of existing cases. Do not count black screens or menu-only routes as coverage. CHECK: Run `python vbrecomp/tools/run_assisted_suite.py --runtime <EXE> --rom <ABSOLUTE_PATH_TO_ROM> --suite tests/coverage/suite.json --out validation/<NEW_FOLDER>`. Done means every case passes with zero fallback and each new case has a screenshot that shows <AREA_OF_GAME>. BUDGET: Stop after 90 minutes or 5 failed route attempts. IF BLOCKED: Stop. A test-only WRAM write is allowed only in a route, never in shipped code. Ask me before using one. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the suite summary. 3) Files changed. 4) Areas still not covered. 5) New parking-lot items.
Done whenrun_assisted_suite.pypasses every case with zero fallback, and thegates.jsonthatcosim.py gateswrites shows"passed": trueand"injected_fault_detected": true.Proof this works: MarioTennisVirtualBoyRecomp, ZeroRacersVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md
- List route cases in
Build for players and ship without the game
You make a release build with the debug server off. It checks the player's ROM by hash and refuses others. The release holds your code only. Extras like color go in optional mod packages.
- Build:
.\tools\build.ps1 -Rom 'C:\Games\<game>.vb' -Build build-release -Production. - Confirm the build refuses a ROM with a different hash.
- Write
build-info.jsonandSHA256SUMS.txtwith source commits and file hashes, as Zero Racers does. - Pack extras as
.vbmodfiles:python vbrecomp/tools/pack_mod.py <mod-source-folder> <name>.vbmod. They must never patch the ROM. - From a fresh clone, run
tools/check.ps1again before you publish.
Prompt for ClaudeGOAL: Produce a release build of <GAME> that ships no game data. READ FIRST: `SCOPE.md`, `vbrecomp/docs/MODS.md`, `.gitignore`, Zero Racers `docs/` release notes. FENCE: Edit only `tools/package-release.py`, `build-info.json`, `README.md` and `PROGRESS.md`. Never add ROM bytes, saves or captures to the release. CHECK: Run `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>` in a fresh clone, then build with `-Production`, then list the release zip. Done means check prints PASS, the zip has no `.vb` file and no `generated/` folder, and a wrong ROM is refused. BUDGET: Stop after 3 failed packaging runs. IF BLOCKED: Stop. Do not loosen the ROM check. Tell me what failed. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output, zip listing, wrong-ROM message. 3) Files changed. 4) Known limits for the README. 5) New parking-lot items.
Done whenA fresh clone passestools/check.ps1, the release zip holds no.vbfile, and a wrong ROM is refused.Proof this works: ZeroRacersVirtualBoyRecomp, MarioTennisVirtualBoyRecomp, https://github.com/mstan/vbrecomp/blob/master/docs/MODS.md
- Build:
Path B: Full decomp for porting
Nobody has done this for any Virtual Boy game. So this path is a plan, built from tools that exist. You keep the Path A build as your test bed. You rewrite the game one function at a time into readable C. Each function must give the same memory and registers as the recompiled one, from the same saved states.
Method: non-matching decompilation
Pick this only after Path A works for your game. Pick it if you want readable source to port to other systems. Plan for a year or more, and for building some tools yourself.
Confirm nobody has done it, and set the rules
You check again that no decomp exists for your game. You write down that this is a non-matching decomp. That means the C behaves the same but is not byte-checked. You pick the check: the same state in, the same state out.
- Search GitHub for the game name plus
decomp,disassemblyandV810. - This guide searched GitHub, the Planet Virtual Boy git server, four community lists and the web. It found no Virtual Boy decomp as of November 2026.
- Confirm your Path A build passes its suite. Path B uses it.
- Write
SCOPE.mdfor the decomp: game, hash, technique, the per-function check, the whole-game check, and the target system for the port.
Prompt for ClaudeGOAL: Draft SCOPE.md for a non-matching decomp of <GAME> (Virtual Boy). READ FIRST: the Path A `SCOPE.md` and `PROGRESS.md`, https://github.com/mstan/vbrecomp (README), https://github.com/aleph-void/ghidra-v810 (README). FENCE: Create only `SCOPE.md` and `notes/prior-work.md` in the new repo `<GAME>-decomp`. Do not write code. Do not touch the Path A repo. CHECK: Done means SCOPE.md names the ROM by SHA-256, says the technique is non-matching, defines the per-function check (same WRAM, SRAM and registers from the same captured state) and the whole-game check (the Path A route suite against the Beetle VB referee), and lists out-of-scope items. Paste your search queries and results into notes/prior-work.md. BUDGET: Stop after 45 minutes of searching. IF BLOCKED: Stop. If you find an existing decomp, stop and tell me at once. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the search log. 3) Files changed. 4) Risks you see. 5) New parking-lot items.
Done whenSCOPE.mdis approved, andnotes/prior-work.mdshows the searches you ran and what they found.Proof this works: https://github.com/mstan/vbrecomp, SD Gundam: Dimension War Recompiled
- Search GitHub for the game name plus
Test whether byte matching could work
Byte matching needs the original compiler. Nintendo's
VUCCfrom 1995 survives in a community dev kit. Nobody knows which games it built. You spend a short, fixed time comparing its code style with your game. The answer goes in a file.- Download the VUCC Dev Kit from virtual-boy.com. Its 16-bit parts need DOSBox, per the kit page.
- Build the kit's Hello World program, if it runs.
- Open both that ROM and your game in Ghidra with the V810 add-on.
- Compare how functions start and end, how registers are saved, and how
switchtables look. - Write
notes/compiler-probe.mdwith three side-by-side examples and a verdict: VUCC-like, not VUCC-like, or unknown.
Prompt for ClaudeGOAL: Decide, with evidence, whether <GAME>'s code looks like VUCC output. READ FIRST: https://www.virtual-boy.com/tools/vucc-dev-kit/, `notes/functions.tsv`, the Ghidra project for <GAME>. FENCE: Create only `notes/compiler-probe.md` and files under `probe/`. Do not edit the decomp source. Do not download any game file. CHECK: Done means notes/compiler-probe.md has three pairs of listings (VUCC output vs <GAME>) with addresses, a list of matching and differing traits, and one verdict: VUCC-like, not VUCC-like, or unknown. BUDGET: Stop after 2 hours in total. This is a probe, not the project. IF BLOCKED: Stop. If the kit will not run, write that down with the error. Unknown is a valid answer. Do not guess. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the three pairs. 3) Files changed. 4) Verdict and how sure you are. 5) New parking-lot items.
Done whennotes/compiler-probe.mdhas three addressed examples and one verdict. If the verdict is not VUCC-like or unknown, you stay non-matching.Proof this works: https://www.virtual-boy.com/tools/vucc-dev-kit/, https://hytopoulos.github.io/lost-compiler
Set up the decomp repo and its test harness
Your decomp lives in its own repo.
vbrecomp's rules ban C rewrites of game code inside the framework. You write a small harness. It runs one function two ways from a saved state and compares the results.- Make
<GAME>-decompwithsrc/for your C,include/for types,harness/andcaptures/(ignored by git). - Use the runtime headers
cpu_state.handmemory.hfromvbrecomp/runtime/include/so your C sees the same registers and bus. - Capture states: run the Path A build with
--port 4390 --paused. Stop at a function's entry withpython vbrecomp/tools/debug_client.py breakpoint pc=<ADDR>. Save the state withget_registersandread_ram addr=0x05000000 len=65536, which is all 64 KB of WRAM. - The harness loads a state, runs the generated function, saves the result, reloads, runs your C, and compares WRAM, SRAM and registers.
- Write
tools/check.ps1: build, run the harness on every converted function, print PASS or FAIL.
Prompt for ClaudeGOAL: Build the per-function differential harness for <GAME>-decomp. READ FIRST: `SCOPE.md`, `vbrecomp/TCP.md`, `vbrecomp/runtime/include/cpu_state.h`, `vbrecomp/runtime/include/memory.h`. FENCE: Edit only `harness/`, `tools/` and `CMakeLists.txt` in <GAME>-decomp. Never edit the Path A repo or `vbrecomp/`. Keep captures in the ignored `captures/` folder. CHECK: Run `powershell -File tools/check.ps1`. Done means the harness runs a known-good pair (the generated function against itself) and passes, and a deliberately broken copy that changes one WRAM byte fails with that byte's address. BUDGET: Stop after 3 hours or 4 failed designs. IF BLOCKED: Stop. Tell me which runtime part you could not reuse, and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the pass run and the planted-fault run. 3) Files changed. 4) Limits of the harness. 5) New parking-lot items.
Done whenThe harness passes a function against itself and fails a planted one-byte fault, naming the byte.Proof this works: https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md, https://github.com/mrperson007/MarioTennisDS, https://github.com/mstan/vbrecomp/blob/master/CLAUDE.md
- Make
Map, name and type the game
You turn the function ledger into a map of the game. You name functions, find data tables and write struct types. Small helpers that appear many times get done once. Good names make every later step faster.
- Start from the Path A
notes/functions.tsv. Add columns: name, module, callers, status. - In Ghidra, label functions and globals. Export them with your headless script after each session.
- Group functions into modules by what they touch: VIP setup, sound, input, game logic.
- Write struct types in
include/for objects found in WRAM. - Mark leaf functions. A leaf function calls no other function.
Prompt for ClaudeGOAL: Name and group the functions in address range <START>-<END> of <GAME>. READ FIRST: `notes/functions.tsv`, `notes/map.md`, `vbrecomp/docs/HARDWARE_NOTES.md`. FENCE: Edit only `notes/functions.tsv`, `notes/map.md`, `include/` and the Ghidra project. Do not write function bodies. CHECK: Run `python tools/ledger_check.py notes/functions.tsv`. Done means it prints OK and every function in <START>-<END> has a name, a module, and one line of proof (a register it writes, a string, or a caller). BUDGET: Stop after 90 minutes. IF BLOCKED: Stop. Name a function `func_<ADDR>` when unsure. A placeholder beats a guess. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: ledger_check output. 3) Files changed. 4) Count named, count still placeholder. 5) New parking-lot items.
Done whenThe ledger check passes, and every function in the range has a name, a module and one line of proof.Proof this works: https://github.com/aleph-void/ghidra-v810, https://github.com/sp00nznet/vbrecomp/blob/main/scripts/ghidra/export_functions.py
- Start from the Path A
Finish one function end to end
You rewrite one small leaf function in readable C. You test it against 100 saved states. This proves the whole loop works before you scale up.
- Pick a short leaf function from the ledger.
- Capture 100 states at its entry across different routes.
- Write the C in
src/<module>.c. Use names and types, not register names. - Run
powershell -File tools/check.ps1 -Function <ADDR>. - Mark it
donein the ledger with the pass count.
Prompt for ClaudeGOAL: Write readable C for function <NAME> at <ADDR> that passes the harness on all captured states. READ FIRST: `CLAUDE.md`, `notes/functions.tsv` row for <ADDR>, the Ghidra listing at <ADDR>, `include/`. FENCE: Edit only `src/<MODULE>.c`, its header and the ledger row. Never edit `harness/`, `captures/` or `tools/check.ps1`. CHECK: Run `powershell -File tools/check.ps1 -Function <ADDR>`. Done means 100 of 100 captured states pass, and the C has no raw register names like `r10`. BUDGET: Stop after 5 attempts that are each meaningfully different. IF BLOCKED: Stop. Tell me which state fails, the first differing byte, and your theory. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the harness output. 3) Files changed. 4) Types or names you added. 5) New parking-lot items.
Done whentools/check.ps1 -Function <ADDR>passes on all 100 captured states.Proof this works: https://github.com/mrperson007/MarioTennisDS, https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md
Convert the game, leaves first
You repeat the unit loop for every function. Start with leaves, then their callers. Many workers can run at once, one function each. The ledger shows the score.
- Each worker gets one function and its own git worktree.
- A worker may not edit the harness, the captures or another worker's file.
- Commit each passing function by itself. Undo any failed try.
- Every 20 functions, rerun the full check on the merged branch.
- Send functions that fail 5 tries to a hard-job session with notes.
Prompt for ClaudeGOAL: Convert function <NAME> at <ADDR> to readable C that passes the harness. READ FIRST: `CLAUDE.md`, the ledger row for <ADDR>, the C of its callees in `src/`, the Ghidra listing. FENCE: Edit only `src/<MODULE>.c` and the ledger row. Work only in worktree `<ABSOLUTE_WORKTREE_PATH>`. Never edit `harness/`, `captures/`, `tools/` or other modules. CHECK: Run `powershell -File tools/check.ps1 -Function <ADDR>`. Done means every captured state passes. BUDGET: Stop after 5 attempts. IF BLOCKED: Stop. Write the first failing state and your best theory in the ledger notes column. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: harness output. 3) Files changed. 4) Commit hash. 5) New parking-lot items.
Done whenThe ledger shows every functiondone, and the full check passes on the merged branch.Proof this works: https://github.com/mstan/vbrecomp/blob/master/CLAUDE.md, https://github.com/mstan/SDGundamDimensionWarVirtualBoyRecomp
Pull out the data and graphics
Tables, tiles and text sit in the ROM. You write scripts that read them from the player's own ROM at build time. A script that packs the data back must give the exact same bytes. That is a true byte check.
- List data ranges in
notes/data.md: address, size, kind. - Write
tools/extract.pyto turn each range into an editable file under the ignoredassets/folder. - Write
tools/pack.pyto turn it back into bytes. - For images, install Pillow with
python -m pip install Pillow, as the SD Gundam tools do. - Compare packed bytes with the ROM range.
Prompt for ClaudeGOAL: Extract and repack data range <START>-<END> (<KIND>) of <GAME> with a byte-exact round trip. READ FIRST: `notes/data.md`, `include/` types for this data. FENCE: Edit only `tools/extract.py`, `tools/pack.py` and `notes/data.md`. Write output only to the ignored `assets/` folder. Never commit ROM bytes. CHECK: Run `python tools/extract.py <ABSOLUTE_PATH_TO_ROM>` then `python tools/pack.py --verify <ABSOLUTE_PATH_TO_ROM>`. Done means it prints OK for <START>-<END>, with 0 differing bytes. BUDGET: Stop after 4 failed formats. IF BLOCKED: Stop. Tell me the first differing offset and what you think the format is. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the verify output. 3) Files changed. 4) The format you found. 5) New parking-lot items.
Done whentools/pack.py --verifyreports 0 differing bytes for every listed data range.Proof this works: SD Gundam: Dimension War Recompiled, MarioTennisVirtualBoyRecomp
- List data ranges in
Run the whole game from your C
Now you build a version where every game function is your C. You replay the Path A routes against the referee. Matching single functions is not enough. The whole game must agree too.
- Build a host in your decomp repo that calls your C for game logic and reuses the VIP, sound and timer parts.
- Run the Path A route suite against
vb-beetle.exewithcosim.py compare. - Fix any split with the first-divergence method from Path A step 7.
- Add the whole-game run to
tools/check.ps1.
Prompt for ClaudeGOAL: Make the all-C build of <GAME> pass route <ROUTE> against the Beetle VB referee. READ FIRST: `SCOPE.md`, `vbrecomp/docs/PARITY.md`, `notes/findings.md`. FENCE: Edit only `src/` and `host/` in <GAME>-decomp. Never edit the routes, the referee, `harness/` or `captures/`. CHECK: Run `python <PATH_A_REPO>/vbrecomp/tools/cosim.py compare --runtime <DECOMP_EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/<ROUTE>.json --out validation/<NEW_FOLDER>`. Done means every checkpoint agrees. BUDGET: Stop after 2 fixes that do not move the first bad checkpoint. IF BLOCKED: Stop. Tell me the first differing field and frame, and which function wrote it. 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) Functions fixed. 5) New parking-lot items.
Done whenThe all-C build passes every Path A route against the referee with no differing checkpoint.Proof this works: https://github.com/mstan/vbrecomp/blob/master/docs/PARITY.md, ZeroRacersVirtualBoyRecomp
Port to a new system
You put the console parts behind a small interface: screen, sound, buttons and timer. Then you build for the new system. One person already ran Mario's Tennis's recompiled C on a Nintendo DS this way. They checked memory and pictures against the PC build.
- Define
platform.h: draw a frame for each eye, play sound, read buttons, wait for the next frame. - Keep the PC version as the reference build.
- The V810 is little-endian. Check your target's byte order, and add byte swaps only if it is big-endian.
- Build for the target with its own kit, such as devkitPro for the DS.
- Record random input on PC. Replay it on the target. Compare WRAM and the picture each frame.
Prompt for ClaudeGOAL: Run <GAME>-decomp on <TARGET_SYSTEM> with the same game state as the PC build. READ FIRST: `SCOPE.md`, `platform.h`, https://github.com/mrperson007/MarioTennisDS (README). FENCE: Edit only `platform/<TARGET_SYSTEM>/`, `platform.h` and the build files. Do not change game logic in `src/`. Do not hide an emulator inside the port. CHECK: Replay `<INPUT_FILE>` on PC and on <TARGET_SYSTEM> (or its emulator) for 12,000 frames. Done means WRAM and the left-eye picture match at every compared frame. BUDGET: Stop after 3 hours or 4 failed builds. IF BLOCKED: Stop. Tell me the first frame that differs and what changed. 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 speed. 3) Files changed. 4) What is missing, such as sound. 5) New parking-lot items.
Done whenA 12,000-frame random-input replay gives the same WRAM and picture on the target as on PC.Proof this works: https://github.com/mrperson007/MarioTennisDS
- Define
Tools you need
| Tool | What it does | How to get it | How we know |
|---|---|---|---|
| vbrecomp (mstan) Required Windows | A Python V810-to-C recompiler plus a C runtime that acts like the console. It has a TCP debug server, a referee harness and test scripts. Path A Path B | Comes as the `vbrecomp/` submodule of a game host. Alone: `git clone --recurse-submodules https://github.com/mstan/vbrecomp.git` | README, CLAUDE.md, PLAN.md, DEBUG.md, docs/BRINGUP.md, docs/PARITY.md, docs/ASSISTED-COVERAGE.md and CLI arguments read 2026-11-13. Also builds on macOS and Linux per README. Used by all three reviewed games. Not run here. |
| recomp-ui Required Windows | A shared launcher and in-game settings screen. Game hosts include it as a submodule. Path A | Comes with the game host clone (`git submodule update --init --recursive`) | README read 2026-11-13 (repo moved from mstan/recomp-ui). Used by Mario's Tennis, Zero Racers and SD Gundam. |
| Beetle VB referee (`vb-beetle`) Required Windows | The Beetle VB emulator core, run as a separate program with the same debug commands. It is the referee your build is compared with. Path A Path B | From `vbrecomp/`: `.\tools\prepare-oracle.ps1 -Destination ..\beetle-vb` (a new folder; add `-Git <path>` if Git is not in `C:\Program Files\Git`). Then build the game host with `.\tools\build.ps1 -Rom <ROM> -Oracle <absolute beetle-vb folder>`, which makes `build\vbrecomp\runtime\vb-beetle.exe` | vbrecomp docs/PARITY.md, docs/BRINGUP.md, docs/ASSISTED-COVERAGE.md, tools/prepare-oracle.ps1 and tools/build_game.ps1 read 2026-11-13: pins upstream commit 1275bd7b, applies an observation-only patch, and builds vb-beetle only when -Oracle is passed. The vb-beetle.exe path is from SD Gundam's tools/validate.ps1. Upstream repo has no README; its description and GPL-2.0 licence read. Used by all three reviewed games. Not run here. |
| MSYS2 (MinGW64 GCC, CMake, Ninja, SDL2) Required Windows | Gives you GCC, CMake, Ninja and SDL2 on Windows. The host build scripts expect it at C:\msys64.Path A Path B | Run the installer from msys2.org, then in the MINGW64 terminal: `pacman -S --needed mingw-w64-x86_64-gcc mingw-w64-x86_64-cmake mingw-w64-x86_64-ninja mingw-w64-x86_64-make mingw-w64-x86_64-SDL2` | msys2.org read 2026-11-13 (installer msys2-x86_64-20260927.exe, Windows 10 1809 or newer). Package list from the Zero Racers README. vbrecomp/tools/build_game.ps1 (read 2026-11-13) checks for cmake.exe, ninja.exe, gcc.exe, g++.exe, ctest.exe and SDL2.dll in C:\msys64\mingw64\bin; prepare-oracle.ps1 uses mingw32-make.exe from the make package. Used by all three reviewed games. |
| Python 3.11+ (Windows build) Required Windows | Runs the recompiler, the compare tools and your ledger scripts. Path A Path B | Install from python.org. Use the Windows build, not an MSYS2 or Cygwin one. Pass `-Python <path>` to `build.ps1` if the wrong one is on PATH. | vbrecomp docs/BRINGUP.md (3.10 needs tomli, 3.11+ uses tomllib) and the SD Gundam README read 2026-11-13. |
| Git Required both | Clones the hosts with their submodules and runs parallel worktrees. Path A Path B | `winget install --id Git.Git -e --source winget`, or the installer from git-scm.com | git-scm.com Windows install page read 2026-11-13 (gives this winget command). Every reviewed repo uses submodules (cards' build steps). prepare-oracle.ps1 expects Git at C:\Program Files\Git\cmd\git.exe by default. Install command not run here. |
| Ghidra Helpful both | A free tool that shows the machine code as assembly and rough C. You use it to find and name functions. It is a reference, not the judge of behavior. Path A Path B | Install JDK 25 (64-bit), download `ghidra_12.2_PUBLIC_20261016.zip` from the `Ghidra_12.2_build` release, unzip, run `ghidraRun.bat`. For Jython scripts, also install the bundled Jython add-on. | README install section and releases API read 2026-11-13 (Ghidra_12.2_build, 2026-10-22, asset ghidra_12.2_PUBLIC_20261016.zip). Headless flags -loader BinaryLoader -loader-baseAddr -processor -scriptPath -postScript read in analyzeHeadlessREADME.md. Jython sits under Ghidra/Extensions/Jython in the 12.2 source tree. Named as source of truth 2 in vbrecomp CLAUDE.md. |
| ghidra-v810 (V810 processor add-on) Helpful both | Teaches Ghidra the V810 instruction set. Without it Ghidra cannot read Virtual Boy code. Import the raw ROM at its base address. Path A Path B | Download `ghidra_12.2_PUBLIC_20261027_V810.zip` (or the 12.1.3 or 12.1.4 zip) from the `ghidra-rare-v1.0.0` release, then File > Install Extensions in Ghidra | README and release assets read 2026-11-13. Based on the open Ghidra PR #6412 and 20Enderdude20/Ghidra_v810_v830 (README read). vbrecomp CLAUDE.md names a third-party V810/V830 SLEIGH module. Not run here. |
| BizHawk Helpful both | A multi-system emulator with Lua scripting and a Virtual Boy core. A second recompiler project uses it as its picture and memory reference. Path A | Download the Windows release zip (2.11.1) from GitHub releases and run `EmuHawk.exe` | README (lists Virtual Boy, Windows and Linux builds) and releases API read 2026-11-13. Used by sp00nznet/vbrecomp scripts/reference/. |
| vbrecomp (sp00nznet) Helpful Windows | A separate V810-to-C recompiler written in C. Its status table covers 76 ROMs, so it shows how far a game gets elsewhere. Path A | `git clone https://github.com/sp00nznet/vbrecomp.git`, then run `Setup.cmd`, or follow `docs/GETTING_STARTED.md` (Visual Studio 2022 Build Tools, CMake, vcpkg SDL2) | README, CHANGELOG and scripts/reference/README.md read 2026-11-13. Its instruction check tools/conformance.py runs in CI. Not run here. |
| v810-gcc Helpful both | A GCC 4.9 cross compiler and binutils for the V810. Use it to build small test ROMs and V810 objects. Path B | In MSYS2: `pacman -S --needed git base-devel autoconf gperf` and `pacman -S mingw-w64-x86_64-gcc`. Then `git clone https://github.com/jbrandwood/v810-gcc.git`, `cd v810-gcc` and `./build_compiler.sh` | README read 2026-11-13 (binutils 2.27, gcc 4.9.4, newlib 2.2.0-1; Windows via MSYS2, Linux, macOS). No reviewed game used it. |
| llvm-v810 Helpful both | A Clang compiler for the V810 with ready-made builds. Another way to build test ROMs. Path B | Download `llvm-v810-windows-main.7z` from the v810-llvm releases and unzip it | Repo data, the releases API (dated tags such as llvm-v810-2026-11-12, each with llvm-v810-windows-main.7z) and the sample project README read 2026-11-13. No reviewed game used it. |
| VUCC Dev Kit Helpful Windows | A community kit with Nintendo's 1995 C compiler, VUCC, and its helper tools. You need it only for the matching probe.Path B | Download from the virtual-boy.com tools page. Run its 16-bit programs in DOSBox. | Kit page read 2026-11-13. Background from hytopoulos.github.io/lost-compiler. Not run here, and no project has used it for a decomp. |
| devkitPro (for a DS port) Helpful both | The toolchain one project used to build Mario's Tennis's recompiled C for the Nintendo DS. Path B | Follow the Getting Started page on devkitpro.org and pick NDS Development, as the MarioTennisDS README says | Named in the MarioTennisDS README (read 2026-11-13). devkitpro.org returned 403 to every request here, so the install steps were not read and the URL points to devkitPro's GitHub page, which answers. |
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
It makes you write the exact ROM, the scenes to test and the pass marks before work starts. That fits Path A step 1 and the route suite.
npx skills add bgyss/switchrecomp --skill static-recomp-scope-plan -a claude-code
reverse-engineer-binary
Claude must read the assembly first and give an address with every claim. That matches vbrecomp's rule against guessing. Install the Ghidra add-on yourself instead of letting it pick one.
npx skills add xsyetopz/skills --skill reverse-engineer-binary -a claude-code
ghidra-headless
It runs Ghidra with no window and saves functions and symbols to files. Install ghidra-v810 first, because a raw .vb file needs it. Set GHIDRA_HOME yourself on Windows.
npx skills add trailofbits/skills-curated --skill ghidra-headless -a claude-code
verification-before-completion
Claude may not say done without fresh proof. On this system the proof is a cosim.py or suite run, pasted in the report.
npx skills add obra/superpowers --skill verification-before-completion -a claude-code
systematic-debugging
No fix until the cause is found. It pairs with the first-divergence method in vbrecomp/DEBUG.md.
npx skills add obra/superpowers --skill systematic-debugging -a claude-code
native-platform-porting
For Path B's last step. It warns Claude not to hide an emulator inside the port.
npx skills add lemiur/game-decompilation-skill-for-claude --skill native-platform-porting -a claude-code
handoff
Writes a short note so a fresh session can carry on. Tell it to save in your repo, not the temp folder.
npx skills add mattpocock/skills --skill handoff -a claude-code
GhidraMCP
Claude can ask Ghidra what an address holds and rename it. vbrecomp names Ghidra as its second source of truth. None of the reviewed Virtual Boy repos say they used this server.
Install `uv`. With Ghidra 12.1.4 and the matching `ghidra-v810` zip, add the GhidraMCP 2.0 plugin zip with File > Install Extensions and enable it. Then run `claude mcp add ghidra -- uvx --from git+https://github.com/LaurieWired/GhidraMCP ghidra-mcp --ghidra-server http://127.0.0.1:8080/`. Not tested here with the V810 add-on.
Beads (`bd`)
An issue tracker made for coding agents. The SD Gundam and vbrecomp docs cite Beads issue IDs like beads-vb.4 to order the work.
`npm install -g @beads/bd`, then `bd init` in your repo and `bd setup claude`
vb-cosim (write your own)
Every Path A unit ends in the same compare. A skill keeps the commands and the rules the same in every session.
Make `.claude/skills/vb-cosim/SKILL.md`. It should hold: the port pairs to use per worktree; the exact `cosim.py compare`, `trace` and `gates` commands with absolute paths; a rule that every run uses a new `validation/` folder; how to read the first differing field; and a rule that a hybrid pass is not native proof.
vb-func-diff (write your own)
It turns the Path B unit loop into one steady routine that bulk workers can follow.
Make `.claude/skills/vb-func-diff/SKILL.md`. It should hold: how to capture states at a PC breakpoint over TCP; how to run `tools/check.ps1 -Function <ADDR>`; the 5-try limit; the ban on editing `harness/` and `captures/`; and how to log a failed unit in the ledger.
Claude setup for this system
Path A units are one fallback address, one referee split, or one route case. Each one ends with a cosim.py or suite run. Path B units are one function, tested from 100 saved states. Framework fixes are their own unit, done on a branch. Keep each unit to one session, and write the result in the ledger.
Which model and effort for each job
The model and effort names come from one shared table, so they stay the same on every guide. See Claude Setup for the reasons.
| Job | What it means on this system | Max 20x (now called Ultra) | Max 5x (now called Max) |
|---|---|---|---|
| Plan the project | Pick the cart and dump, check for prior work, and choose the host to copy. For Path B, decide matching or non-matching after the VUCC probe. | 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 | Install MSYS2 and Python, build vbrecomp alone, prepare the referee, copy the Zero Racers host, and write tools/check.ps1. | Opus 5.5 effort medium | Sonnet 5.5 effort medium |
| Study the game file | Run vbrecomp_coverage and Ghidra with the V810 add-on. Merge the function lists, name functions and write struct types. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for function edges and data layouts. A mistake there spreads. |
| Bulk units | Path A: fallback addresses of the same pattern, and new route cases. Path B: short leaf functions with the harness check. | 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 | Discovery gaps like shared-tail tables and callbacks. Path B functions that failed 5 tries. Bit-string and float instructions. | 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 | VIP block drawing, sound channels, timer and interrupts in vbrecomp/runtime/. For a port, the platform.h layer. | Opus 5.5 effort high | Opus 5.5 effort mediumRaise to high for timing and graphics bugs. |
| Find a bug | Find the first checkpoint where your build and the referee split, then the first bad write. Use the frame ring and wtrace, never prints. | Opus 5.5 effort highAfter two failed tries with full evidence, move to Fable 5.2. | Opus 5.5 effort highOne bug at a time. |
| Check the work | A fresh session reruns the suite and the gates and reads the diff. It hunts for stubs, edited routes, fallback hidden as native, and edits to generated/. | Opus 5.5 effort mediumAlways a new session, never the one that did the work. | Sonnet 5.5 effort highAlways a new session, never the one that did the work. |
| Keep the records | Update PROGRESS.md, notes/functions.tsv, notes/findings.md and the parking lot after each unit. | Haiku 5.5 effort medium | Haiku 5.5 effort medium |
How many sessions at once
Max 20x (now called Ultra)
Run one lead session and 3 to 4 workers. Each worker gets its own git worktree and its own port pair. Pass --port 4400 to cosim.py and it uses 4400 and 4401. Each compare starts two programs, so watch your RAM. In Path B, 4 to 6 workers can each take one leaf function. Run one verify session after every few merges.
Max 5x (now called Max)
Run one lead session and 1 to 2 workers. Give workers bulk units only. Do framework fixes and hard bugs in one session at a time. Batch verify runs at the end of each day.
Lines for this project's CLAUDE.md
- At the start of every session, read `SCOPE.md`, `PROGRESS.md` and `notes/parking-lot.md`. Only the owner changes `SCOPE.md`. - Never edit files in `generated/`. They are build output. Fix `vbrecomp/recompiler/` or `<game>.toml`, then regenerate, rebuild and run. - There are no stubs. `return 0;`, `// TODO` and a faked frame are stubs. A missing piece aborts with `vb_stub_abort(...)`. - No HLE shims and no C rewrites of game functions inside `vbrecomp/`. Decomp code lives only in the separate decomp repo. - No printf and no log files for debugging. Add a TCP debug command instead. Runtime port 4390, referee port 4391. - Sources of truth, in order: the V810 manual and Sacred Tech Scroll, Ghidra for bytes, the Beetle VB referee for behavior. If none answers, say "I don't know yet". - The one check is `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>`. Done means it prints PASS. Paste its output in every report. - A hybrid pass with fallback is not proof of native coverage. Report native and fallback counts every time. - Read-only: the ROM folder, `vbrecomp.pin`, `recomp-ui.pin`, `tests/coverage/suite.json` expected results, `harness/`, `captures/` and `tools/check.ps1`. - Every validation run writes to a new folder under `validation/`. - Record findings and dead ends in `notes/findings.md` with the address, frame and the path of the proof. - In a worktree, use absolute paths and your own port pair: `cosim.py --port <N>` uses N and N+1, and its default is 4490. Never run two compares on the same ports. - A framework change is done only when every other game you own still passes its suite.
Keep the context small
- Give Claude an address and a route name, not pasted assembly. It can query Ghidra or the TCP server itself.
- Use a subagent to read
cosim.pyreports and return only the first differing field and frame. - Keep generated C out of Claude's view. Its files are huge, and it is never edited by hand.
- One unit per session. Clear the chat after each report and start fresh from the ledger.
- Point
CLAUDE.mdatvbrecomp/DEBUG.mdanddocs/PARITY.mdby topic instead of copying them in.
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 see if your game can be done and how.
GOAL: Tell me if <GAME> (Virtual Boy, SHA-256 <HASH>) can be recompiled with mstan/vbrecomp, and draft SCOPE.md. READ FIRST: https://github.com/mstan/vbrecomp (README, PLAN.md), https://github.com/sp00nznet/vbrecomp/blob/main/COMPATIBILITY.md, the RecompLauncher Virtual Boy list. FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. Write no code. Do not open the ROM except to hash it. CHECK: Done means SCOPE.md has the ROM hash and size, the prior work found with links, how far <GAME> gets in the sp00nznet status table, the first milestone, and a risk list. BUDGET: Stop after 45 minutes. IF BLOCKED: Stop. Say plainly if the game looks too hard and why. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: links and the hash. 3) Files changed. 4) Your recommendation in one sentence. 5) New parking-lot items.
A good answer: A short SCOPE.md with real links, the game's status in other tools, and a plain yes, no or not yet.
Map the binary
After setup, to build the function ledger.
GOAL: Produce `notes/functions.tsv` for <GAME> from vbrecomp discovery and Ghidra with the V810 add-on. READ FIRST: `SCOPE.md`, `vbrecomp/docs/HARDWARE_NOTES.md` (memory map), https://github.com/aleph-void/ghidra-v810. FENCE: Create only files under `notes/` and `tools/ghidra/`. Never edit `vbrecomp/` or `generated/`. CHECK: Run `python tools/ledger_check.py notes/functions.tsv`. Done means OK, plus counts from each tool and a list of addresses only one tool found. BUDGET: Stop after 90 minutes. IF BLOCKED: Stop. List the ranges you could not decode. Do not guess function edges. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: checker output and counts. 3) Files changed. 4) Odd ranges. 5) New parking-lot items.
A good answer: A clean ledger, two counts, and a short list of disputed addresses to look at.
Unit loop: one fallback address (Path A)
During the grind, for each address in fallback_coverage.
GOAL: Remove the interpreter fallback at <PC> on route <ROUTE>. READ FIRST: `CLAUDE.md`, `notes/functions.tsv`, `vbrecomp/CLAUDE.md` sections 4 and 16. FENCE: Edit only `vbrecomp/recompiler/` and its tests on branch `<BRANCH>`, plus the ledger. Never edit `generated/`. CHECK: Run `python -m unittest discover recompiler/tests`, then `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>`, then a native-only run of <ROUTE>. Done means all pass and <PC> is gone from `fallback_coverage`. BUDGET: Stop after 4 tries without progress. IF BLOCKED: Stop. Show me the instruction pattern at <PC>. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: fallback counts before and after. 3) Files changed. 4) Other addresses fixed. 5) New parking-lot items.
A good answer: DONE with a smaller fallback count, a new recompiler test, and no change to generated files.
Unit loop: one function (Path B)
During the Path B grind, one function per session.
GOAL: Write readable C for <NAME> at <ADDR> that passes the differential harness. READ FIRST: `CLAUDE.md`, the ledger row for <ADDR>, the Ghidra listing at <ADDR>, `include/`. FENCE: Edit only `src/<MODULE>.c` and the ledger row, in worktree `<ABSOLUTE_WORKTREE_PATH>`. Never edit `harness/`, `captures/` or `tools/`. CHECK: Run `powershell -File tools/check.ps1 -Function <ADDR>`. Done means every captured state passes. BUDGET: Stop after 5 attempts that are each different. IF BLOCKED: Stop. Give the first failing state, the first differing byte and your theory. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: harness output. 3) Files changed. 4) Commit hash. 5) New parking-lot items.
A good answer: Readable C with real names, all states passing, one commit.
Batch dispatch
In the lead session, to hand out the next set of units.
GOAL: Start <N> workers on the next <N> open units from the ledger. READ FIRST: `PROGRESS.md`, `notes/functions.tsv` (or `fallback_coverage` output), `CLAUDE.md`. FENCE: Edit only `PROGRESS.md` and the ledger status column. Make one git worktree per worker at `<ABSOLUTE_WORKTREE_ROOT>/<UNIT>`. Give each worker its own port pair starting at 4400. CHECK: Done means each worker returned a report, and the full `tools/check.ps1` passes on the merged branch after each merge. BUDGET: Stop after one round of <N> units. IF BLOCKED: Stop. Do not merge a unit whose check failed. List it as open. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the check output after merging. 3) Files changed. 4) Units done, failed and sent to hard. 5) New parking-lot items.
A good answer: A table of units with DONE, PARTIAL or BLOCKED, and a green check on the merged branch.
Debug with evidence
When your build and the referee split, or the game crashes.
GOAL: Find the cause of <SYMPTOM> at frame <FRAME> on route <ROUTE>. Do not fix it yet. READ FIRST: `vbrecomp/DEBUG.md`, `vbrecomp/docs/PARITY.md`, `notes/findings.md`. FENCE: Read-only. You may add a TCP debug command on branch `<BRANCH>` if you need to see more state. No printf, no log files. CHECK: Run `python vbrecomp/tools/cosim.py compare --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/<ROUTE>.json --out validation/<NEW_FOLDER>`. Done means you name the first differing field, the frame, the instruction that wrote it, and one test that proves it. BUDGET: Stop after 60 minutes. IF BLOCKED: Stop. Tell me what you know, what you do not, and what tool is missing. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: compare and trace output. 3) Files changed. 4) The cause and how sure you are. 5) New parking-lot items.
A good answer: A named cause with an address, a frame and a proof, and no code change to game logic.
Verify and hunt for cheating
In a fresh session after a batch of units, before you trust them.
GOAL: Check that the work in commits <RANGE> is real. READ FIRST: `CLAUDE.md`, `git log -p <RANGE>`. FENCE: Read-only. Do not fix anything. CHECK: Run `powershell -File tools/check.ps1 -Rom <ABSOLUTE_PATH_TO_ROM>` and `python vbrecomp/tools/cosim.py gates --runtime <EXE> --oracle <VB_BEETLE_EXE> --rom <ABSOLUTE_PATH_TO_ROM> --route routes/<ROUTE>.json --out validation/<NEW_FOLDER>`. Done means you list: any edit in `generated/`, `harness/`, `captures/`, routes or expected results; any stub, `return 0;` or TODO; any fallback reported as native; any change outside the fence; and whether the gates caught the injected fault. BUDGET: Stop after 60 minutes. IF BLOCKED: Stop. Say which check you could not run. OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check and gates output. 3) Files changed: none. 4) Each problem with file and line. 5) New parking-lot items.
A good answer: A short list of real problems with file and line, or a plain statement that none were found, with proof.
Status report
At the end of a day or week.
GOAL: Give me a status report for <GAME> from the ledgers only. READ FIRST: `PROGRESS.md`, `notes/functions.tsv`, the newest folder in `validation/`. FENCE: Read-only. CHECK: Done means every number comes from a file: functions found, Path B functions done, route cases passing, native and fallback counts, open parking-lot items. BUDGET: Stop after 15 minutes. IF BLOCKED: Stop. Say which file is missing or stale. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the files and dates used. 3) Files changed: none. 4) The numbers in a short table. 5) The top three risks.
A good answer: A small table of numbers with their source files, and no padding.
Session handoff
Before you stop for the day, or when the session gets long.
GOAL: Write down what the next session needs to carry on. READ FIRST: `PROGRESS.md`, `notes/findings.md`, `git status`, `git log -5`. FENCE: Edit only `PROGRESS.md` and `notes/findings.md`. CHECK: Done means PROGRESS.md has: what works with proof paths, the unit in progress and its state, the next three units, dead ends tried, and the exact commands to resume. BUDGET: Stop after 15 minutes. IF BLOCKED: Stop. Say 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: the diff of PROGRESS.md. 3) Files changed. 4) Uncommitted work. 5) New parking-lot items.
A good answer: A PROGRESS.md a new session can follow with no chat history.
Blocked or cannot deliver
When a unit keeps failing, or Claude keeps saying it almost works.
GOAL: Give me a plain answer on <UNIT>: can it be done, and how. READ FIRST: the ledger notes for <UNIT>, `notes/findings.md`. FENCE: Read-only. CHECK: Done means you answer with one word, YES, NO or UNKNOWN, then the proof. List what was tried, what failed, and what each option would cost. BUDGET: Stop after 20 minutes. IF BLOCKED: Say UNKNOWN and what evidence would settle it. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the failing check output. 3) Files changed: none. 4) Options, each with its cost. 5) Your pick and why.
A good answer: One honest word, the evidence, and two or three real options.
Scope change request
When Claude finds it needs to leave the fence, such as editing the framework.
GOAL: Ask me before you work outside the fence for <UNIT>. READ FIRST: `SCOPE.md`, the task card for <UNIT>. FENCE: Change nothing until I answer. CHECK: Done means you sent me: the file you want to change, why the fence blocks you, the smallest change, how it will be checked, and what could break (including other games on the framework). BUDGET: Stop after 10 minutes. IF BLOCKED: Stop and wait for my yes or no. REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the failing check that led here. 3) Files changed: none. 4) The request. 5) The risk.
A good answer: A short request you can answer yes or no, with the risk to other games spelled out.
Keep Claude on task
These are the detours Claude tends to take on this system, and the rule that stops each one. The full method is on Stay on Task.
| The detour | The rule to give Claude |
|---|---|
| Claude edits the generated C to get past a crash, because it is the fastest fix. | Never edit files in generated/. Fix the recompiler or the cart toml, then regenerate, rebuild and run. |
Claude writes a stub, such as return 0; or a faked frame, so the game moves on. | No stubs. A missing piece must abort with vb_stub_abort(...) and a clear banner. |
Claude adds printf lines and log files to see what happens. | No printf and no log files. Add a TCP debug command and query the always-on ring buffers. |
| Claude calls a run that used the interpreter a success for the recompiled code. | Report native and fallback counts every time. Coverage claims need a native-only run or zero fallback. |
| Claude adds a per-game hint or seed to hide a recompiler bug. | Fix the class of bug in the recompiler with a test. A seed needs proof from a run and a ROM reference. |
| Claude says "probably" and builds on a guess about the hardware. | Check the manuals, then Ghidra, then the referee. If none answers, write "I don't know yet" and stop. |
| When the screenshot or compare tool breaks, Claude works around it with other evidence. | A broken check tool is fixed first. Two builds that agree on a wrong value are not proof. |
| In Path B, Claude puts C rewrites of game functions into the framework. | Decomp code lives only in the separate decomp repo. vbrecomp bans C rewrites of game code. |
Problems other projects hit
The build picked up an MSYS2 or Cygwin Python and failed in odd ways.
Fix: Install a normal Windows Python and pass -Python <path> to tools/build.ps1.
Seen in: SD Gundam: Dimension War Recompiled
Build scripts expect MSYS2 at C:\msys64\mingw64.
Fix: Install MSYS2 there, or pass -Toolchain <path> to the build script.
Seen in: MarioTennisVirtualBoyRecomp, ZeroRacersVirtualBoyRecomp
An old Beetle build would not work with the compare tools.
Fix: Make a fresh referee with prepare-oracle.ps1 into a new folder.
Seen in: MarioTennisVirtualBoyRecomp
Native-only runs failed on code discovery missed: shared table tails, calls through pointers, and callbacks.
Fix: Fix discovery in the framework and add a regression test. Do not hand-edit generated C.
Seen in: SD Gundam: Dimension War Recompiled
A hybrid pass looked like full coverage, but the interpreter was doing part of the work.
Fix: Require zero fallback or a native-only run before you claim coverage.
Seen in: SD Gundam: Dimension War Recompiled, ZeroRacersVirtualBoyRecomp
Routes that only reached menus were counted as coverage. A menu limit also stopped routes from reaching all 15 courses.
Fix: Check screenshots and selectors, not only counters. For tests only, write the course ID into WRAM in the route. Never ship that.
Seen in: MarioTennisVirtualBoyRecomp, ZeroRacersVirtualBoyRecomp
Comparing the same frame number on two emulators measured timing, not drawing. One game looked 37% wrong but was a perfect match.
Fix: Compare the best-matching pair of frames, or drive both with the same routes and checkpoints.
Seen in: vbrecomp (sp00nznet)
A renderer drew all 224 rows at once, so tiles swapped between 8-row blocks got mixed up.
Fix: Draw in 8-row blocks like the real VIP does.
Seen in: vb-mariotennis (sp00nznet)
A color mod guessed which character was which from screen position. Mario's hair turned beige.
Fix: Pick colors by the tile data itself, a content fingerprint, not by position.
Seen in: MarioTennisVirtualBoyRecomp
The first clean-checkout build failed because one generated header lacked a build dependency.
Fix: Test every release from a fresh clone, and add the missing target dependency.
Seen in: SD Gundam: Dimension War Recompiled
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 | 2 to 8 weeks | 5 to 20 weeks | 6 to 18 months | low confidence |
| Path B: full decomp | 4 months to 1 year | 9 months to 2.5 years | 2 to 6 years | low confidence |
Read this first
These numbers assume one medium-sized commercial game and one person giving 10 to 15 hours a week. Path A rests on three dated AI-assisted game repos, all by one author who also wrote the framework. Those repos went public after the game worked, so the real effort is hidden, and a newcomer will be slower. Nobody has timed a newcomer on a new cart, so Path A's confidence is low too. A second recompiler gets only 7 of 28 store cartridges into play. No Virtual Boy recomp or decomp predates AI agents, so the no-AI range is borrowed from a PS1 recompiler built without AI. Nobody has done Path B on this system, so its range is a low-confidence estimate.
What makes it faster or slower
- Whether your cart has a host to copy. Starting from Zero Racers saves the setup. A cart that stalls at boot in other tools needs runtime work first.
- How much code the finder misses. SD Gundam needed no seeds. Carts with callbacks or pointer tables need many framework fixes.
- Game size. Early discovery found 113 functions in Mario's Tennis. SD Gundam has 3,180.
- For Path B, how fast your harness and naming get good. You must build the harness yourself.
- Your review time. Each unit needs a look at its proof, and that comes out of your 10 to 15 hours a week.
The facts behind these numbers
| Project | What happened | Link |
|---|---|---|
| vbrecomp (mstan) | Repo created 2026-05-20. Its CLAUDE.md says the plan was revised 2026-05-19. PLAN.md dates its phase 2 and phase 3 results 2026-05-20. First GitHub prerelease 2026-10-19. 158 commits by 2026-11-13. 1 contributor. | source |
| MarioTennisVirtualBoyRecomp | First commit 2026-05-20. First release 2026-05-21. Latest release 2026-11-13. 1 contributor. The store flags AI use for code. | source |
| ZeroRacersVirtualBoyRecomp | First commit and first release 2026-09-10. 43 commits to 2026-11-01. 1 contributor. The store flags AI use for code. | source |
| SD Gundam: Dimension War Recompiled | First commit and prerelease 2026-09-12, two days after Zero Racers, by copying its host. 3,180 functions found with no manual seeds. 18 commits to 2026-11-01. | source |
| vbrecomp (sp00nznet) | First commit 2026-03-30. 220 commits to 2026-10-21. 1 contributor. 7 retail games reach gameplay out of a 76-ROM corpus. Its changelog removes a committed Claude Code settings file. | source |
| Recomp & AI: 5 months later (mstan's blog) | Posted 2026-07-19. Lists Virtual Boy with at least one commercial game running. Says the first game takes weeks to months, and later ones days to weeks. | source |
| MarioTennisDS | Created 2026-11-13. 2 commits. Boots and plays a match in the melonDS emulator. A rally runs at about 34% of full DS speed. WRAM and picture match the PC build for up to 12,000 frames of random input. | source |
Projects reviewed (10)
RecompLauncher has fewer than 10 games for this system: just 2. One more game project came from outside it. That makes 3 real game projects in all, and fewer than 10 exist anywhere. The other 7 entries are related repos we read live: three recompilers, a second Mario's Tennis, a stub, a DS build and a converter.
What each project teaches
MarioTennisVirtualBoyRecomp
Mario's Tennis
Started: 2026-05. First release: 2026-05. Code: C.
AI: yes: the store flags it for code; the README promotes an AI-assisted retro dev Discord; the framework it pins has a CLAUDE.md
Lesson: The title screen matches the referee with zero pixels off. A ROM-free route suite with zero fallback backs each coverage claim.
vbrecomp (mstan)
none: the framework under the three games
Started: 2026-05. First release: 2026-10. Code: C and Python.
AI: yes: CLAUDE.md in the repo, adapted from the author's PS1 recompiler rules Agent files: CLAUDE.md, PRINCIPLES.md, DEBUG.md.
Lesson: Its CLAUDE.md is the best rule set for this system: no stubs, no edits to generated code, no printf, and three sources of truth.
Zero Racers Recompiled
Zero Racers
Started: 2026-09. First release: 2026-09. Code: Python and C++.
AI: yes: the store flags it for code; the README promotes an AI-assisted retro dev Discord; the framework it pins has a CLAUDE.md
Lesson: Its host became the template for the next game. A menu limit hid later courses until a test-only route wrote the course ID into memory.
SD Gundam: Dimension War Recompiled
SD Gundam Dimension War
Started: 2026-09. First release: 2026-09. Code: Python and C++.
AI: none stated in the game repo; the framework it uses has a CLAUDE.md
Lesson: A new cart went up in two days by copying a working host. Only the opening mission is checked, and the README says so.
vbrecomp (sp00nznet)
many: a 76-ROM corpus, 7 retail games reach gameplay
Started: 2026-03. First release: none. Code: C++.
AI: yes: its changelog removes a committed .claude/settings.local.json
Lesson: Running every game through one driver shows where each gets stuck. Its notes warn that comparing fixed frame numbers measures timing, not drawing.
vb-mariotennis (sp00nznet)
Mario's Tennis
Started: 2026-03. First release: none. Code: C.
AI: none stated
Lesson: A second take on the same game. Drawing all rows at once broke the picture, because the real chip draws 8 rows at a time.
VirtualBoyRecomp (boubou666)
none: a recompiler with no game configs
Started: 2026-10. First release: none. Code: C++.
AI: none stated
Lesson: A third design with no interpreter at all: it stops and names any missed address. One commit, and no game shown working.
Teleroboxer Recomp
Teleroboxer
Started: 2026-09. First release: 2026-09. Code: Shell.
AI: none stated
Lesson: A config file and a release are not a working game. Its notes still list reaching the cart's start code as a goal, and it has had no commits since 2026-09-17.
Mario's Tennis for Nintendo DS
Mario's Tennis
Started: 2026-11. First release: none. Code: Python and C.
AI: none stated
Lesson: Recompiled C can move to another system. It checks memory and pictures against the PC build, but runs at about a third of full speed.
VBRecomp Tools
Mario's Tennis and Zero Racers (profiles only)
Started: 2026-11. First release: 2026-11. Code: C.
AI: none stated
Lesson: A converter can only do games that already have a host. It says so, and lists every other game as needing review.
What we could not check
- No source says which retail Virtual Boy games were built with VUCC and which in assembly.
- The VUCC matching probe in Path B step 2 has not been tried by anyone we found.
- The Path B harness, all-C host and port steps are a plan. No Virtual Boy project has done them.
- Whether you can swap hand-written C into the
vbrecompdispatch without forking it. Its rules ban that inside the framework. - The exact JDK version for Ghidra 12.1.4, which GhidraMCP's headless mode needs. Only Ghidra 12.2's JDK 25 was read.
- Whether GhidraMCP works with the
ghidra-v810add-on. Not tested here. - Whether the three
game-build.jsonfields andVBRECOMP_GAMEare all you must change when copying the Zero Racers host. Other host files, such assrc/and the CMake UI switch, were not read in full. - Whether the dev build of a host accepts
--headless --frames. The docs name it for production builds. - Whether the sp00nznet Jython export script runs headless in Ghidra 12.2 with the Jython add-on. Not run here.
- The name
libmednafen_vb.afor the referee library comes fromvbrecomp's CMake search list.prepare-oracle.ps1was not run here. - Whether the Ghidra base address
0x07000000gives the same addresses asvbrecompfor 1 MB and 2 MB carts. It is the default ofvbrecomp_coverage, but the two lists were not compared here. - devkitPro install steps. devkitpro.org returned 403 to every request.
- The licence of the game host repos. GitHub shows NOASSERTION. The Mario's Tennis README says MIT for recompiler, runtime and tools.
- Whether Teleroboxer Recomp boots. Its release zip was not downloaded.
- Whether decomp.dev ever tracked a Virtual Boy project. Its index is disabled.
- No tool in this guide was run on this machine.
Sources
- mstan/vbrecomp README
- vbrecomp CLAUDE.md
- vbrecomp DEBUG.md
- vbrecomp docs/PARITY.md
- vbrecomp docs/ASSISTED-COVERAGE.md
- vbrecomp docs/BRINGUP.md
- vbrecomp PLAN.md
- vbrecomp TCP.md (debug commands)
- vbrecomp tools/cosim.py
- vbrecomp tools/build_game.ps1
- SD Gundam tools/validate.ps1
- sp00nznet/vbrecomp COMPATIBILITY.md
- Ghidra headless analyzer README
- MarioTennisVirtualBoyRecomp
- ZeroRacersVirtualBoyRecomp
- SDGundamDimensionWarVirtualBoyRecomp
- sp00nznet/vbrecomp
- sp00nznet/vbrecomp reference scripts
- boubou666/VirtualBoyRecomp
- MarioTennisDS
- recomp-ui
- ghidra-v810
- Ghidra_v810_v830
- Ghidra
- GhidraMCP
- MSYS2
- v810-gcc
- v810-llvm sample project
- VUCC Dev Kit (Planet Virtual Boy)
- ISAS/ISLK Dev Kit (Planet Virtual Boy)
- Virtual Boy Sacred Tech Scroll
- Synthesizing the Forgotten SNES C Compiler (VUCC history)
- Recomp & AI: 5 months later
- BizHawk
- beetle-vb-libretro
- Beads