Skip to the page
It's Decomplicated
Philips, 1991

Philips CD-i decomp guide

A 68000 game on a disc operating system. You rebuild the OS, not the hardware.

What this system is like

CD-i games run on a Philips SCC68070 chip. It runs the same code as a Motorola 68000. Each game is a set of OS-9 program files on a CD. The game asks the CD-i operating system, called CD-RTOS, for almost everything. So most of your work is a stand-in for that system, not for raw chips.

For Path A, every reviewed port used static recompilation. A recompiler turns machine code into C. The ten tools81 ports share one Python recompiler. HotelMario-Decomp wrote its own. A runtime answers the game's system calls and draws the picture. A built-in interpreter runs any code the recompiler missed. Ten Windows ports by one author shipped in about three weeks from one shared design.

For Path B, two CD-i games already have matching decomps. Link: The Faces of Evil and Zelda: The Wand of Gamelon rebuild byte for byte with Apple's old MPW C 3.0 compiler. Every game function is in C. The compiler's runtime, the startup code and a few drawing routines are still assembly. Games built with Microware's OS-9 C compiler are harder. Nobody in these projects had that compiler, so their C cannot be byte-checked yet.

Path A runs fully on Windows with Visual Studio and Python. Path B needs WSL for the old-Mac compiler route. Expect weeks for Path A and months for Path B.

The OS is the target

Games ask CD-RTOS to load files, play sound and show pictures. A port answers those calls in C. Hotel Mario's port runs with no CD-i BIOS at all. Each new game mostly adds missing OS calls.

No shared recompiler yet

There is no ready tool like N64Recomp. Every reviewed port wrote its own Python recompiler. mstan/cdirecomp is the closest to a shared engine. It needs your own BIOS dump.

Free names in the game files

Many CD-i games ship a .stb symbol file from the linker. Mac-built games keep MacsBug names after each function. These give real function names and entry points. Hotel Mario's .stb gave 479 entry points.

Three compiler families

Microware C 3.2, Microware Ultra C and Apple MPW C 3.0 all appear. MPW C 3.0 runs in a Mac emulator, so those games can match byte for byte. The Microware compilers were not found by any project here.

The Green Book has gaps

The CD-i spec is free to read. But it omits the number codes for many calls. Ports learned the codes from the game's own library code and wrote them down in docs/CDI_NOTES.md.

Code also loads at run time

Some games load more code into RAM from the disc. Examples are level code and compiled sprites. A recompiler cannot see that code ahead of time. So every port keeps an interpreter for it.

Two paths

Path A

Get it running on PC

You pull the game's OS-9 modules off the disc. A Python recompiler you write turns each module's 68000 code into C. A runtime in C stands in for CD-RTOS, the CD file manager, the MCD212 video and the audio. An interpreter runs code that loads at run time. A switch that runs everything in the interpreter gives you a reference to compare against.

Go to the steps

Path B

Full decomp for porting

First you find which compiler built the game. Mac-built games used Apple MPW C 3.0, which runs in a Mac emulator, so their C can match byte for byte. Microware-built games need a compiler nobody here had. For those you rebuild the module from assembly first, then write C checked by tests against the running game. Last, you move the C onto a PC runtime and then onto other systems.

Go to the steps

Path A: Get it running on PC

You pull the game's OS-9 modules off the disc. A Python recompiler you write turns each module's 68000 code into C. A runtime in C stands in for CD-RTOS, the CD file manager, the MCD212 video and the audio. An interpreter runs code that loads at run time. A switch that runs everything in the interpreter gives you a reference to compare against.

Method: static recompilation of the OS-9 modules to C, with a high-level CD-RTOS runtime and an interpreter fallback

Pick this first for any CD-i game. Ten tools81 ports took this route. Nine describe tested gameplay. Wand of Gamelon's README lists no tested screens. If you own a CD-i BIOS and accept a noncommercial licence, mstan/cdirecomp is a second route.

  1. Pick the game and pin the disc

    You choose one game and one exact disc. You hash the disc image so every check uses the same bytes. You look for work that already exists. Many CD-i games already have a port or a decomp.

    • Keep your own disc image outside the repo, for example D:/cdi-discs/<GAME>/<GAME>.bin and its .cue.
    • Hash it on Windows with certutil -hashfile "<GAME>.bin" SHA1, or in WSL with sha1sum "<GAME>.bin".
    • If you have a .chd, convert it with chdman extractcd -i <GAME>.chd -o <GAME>.cue -ob <GAME>.bin. chdman is one of the tools that come with MAME.
    • Note the region: PAL (Europe) or NTSC (USA). PAL games run 50 fields a second and need other video timing.
    • Note if the game needs the MPEG-1 Digital Video cartridge. Those games need a video decoder in the runtime.
    • Search GitHub for the game name plus CD-i. Check tools81, mstan, antonretro, Megamil-Decomp and ByteDecompiler.
    • Write SCOPE.md: game, region, disc SHA-1, goal, technique, first milestone and its check, and what is out of scope.
    Prompt for Claude
    GOAL: Draft SCOPE.md for a Path A port of <GAME> (<REGION>) on Philips CD-i.
    READ FIRST: The SHA-1 of <ABSOLUTE_PATH_TO_BIN> (I will paste it), https://github.com/tools81/hotel-mario-static-recomp , https://github.com/mstan/cdirecomp
    FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. Do not copy any disc file into the repo. Do not write code. Do not download any game, BIOS or disc image.
    CHECK: Run `certutil -hashfile <ABSOLUTE_PATH_TO_BIN> SHA1`. Done means its hash is the one in SCOPE.md, and SCOPE.md lists the disc file name and SHA-1, the region, PAL or NTSC, whether it needs the video cartridge, prior work with links, the first milestone with its check, and an out-of-scope list.
    BUDGET: Stop after 45 minutes of searching.
    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 line and the links you checked. 3) Files changed. 4) Prior work found. 5) New parking-lot items.
    Done whenSCOPE.md names the disc by SHA-1, the region and the first milestone, and you approved it.

    Proof this works: Hotel Mario Decomp, Zelda's Adventure, The Apprentice Recomp, https://docs.mamedev.org/tools/chdman.html

  2. Set up the repo, tools and one check

    You make the repo and install the build tools. You copy the folder layout the reviewed ports use. You write one check command early, even before it has much to check. Game files never enter the repo.

    • Install Visual Studio 2022 Build Tools with the C++ workload: winget install Microsoft.VisualStudio.2022.BuildTools --override "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive". Without --override, no C++ compiler is added.
    • Install CMake with winget install Kitware.CMake. Install Python and Git with winget install Python.Python.3.12 and winget install Git.Git. The ports need Python 3.8 or newer.
    • Make these folders: tools/ (Python), runtime/ (C), test/, docs/ and notes/. Add game.json for the game's modules and load addresses.
    • Put rom/, gen/, extracted/, build/, saves/ and tools/ext/ in .gitignore before any disc file is near the repo.
    • Write build.bat. It runs python tools\gen.py (only if that file exists: you write it in step 5), then cmake -S . -B build\cmake -G "Visual Studio 17 2022" -A x64, then cmake --build build\cmake --config Release --parallel.
    • Write tools\check.bat. It runs the build, the instruction tests and the headless compare from step 6. It prints PASS or FAIL.
    • Write CLAUDE.md, PROGRESS.md and notes/parking-lot.md. Read the tools81 READMEs for ideas. They have no licence, so do not copy their code.
    Prompt for Claude
    GOAL: Make the empty project build with one command and give one PASS or FAIL check.
    READ FIRST: `SCOPE.md`, https://github.com/tools81/hotel-mario-static-recomp (README sections Building and Layout, for design only)
    FENCE: Create only `build.bat`, `tools/check.bat`, `CMakeLists.txt`, `.gitignore`, `game.json`, `CLAUDE.md`, `PROGRESS.md`, `notes/parking-lot.md` and empty `tools/`, `runtime/`, `test/` folders. Do not copy code from any repo with no licence. Do not install anything; tell me what is missing.
    CHECK: Run `build.bat` then `tools\check.bat`. Done means both exit with code 0 and `check.bat` prints PASS on the empty project. `git status` shows no file from `rom/`.
    BUDGET: Stop after 3 failed builds.
    IF BLOCKED: Stop. Paste the error. Do not work around a missing tool.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: paste both outputs. 3) Files changed. 4) What is left. 5) New parking-lot items.
    Done whenbuild.bat and tools\check.bat both exit 0 on a clean clone, and git status lists no game file.

    Proof this works: Hotel Mario Static Recomp, Link: The Faces of Evil Static Recomp, Kether

  3. Pull out the modules and map the code

    You read the Green Book file system and copy the game's OS-9 modules out of the disc. Then you find every function. Symbol files and MacsBug names give you real names. The result is a ledger: one row per function, with a status.

    • Write tools/cdi_extract.py. It reads the disc's file system and writes modules to extracted/. Find the boot program from the disc label.
    • Do not trust file numbers to spot video and sound streams. On several discs every file has file number 1. Use the sector submode bits instead.
    • Write tools/os9mod.py to read each module header. It should recompute the header parity and the CRC-24, so you know the read is right.
    • Look for a <program>.stb symbol module and a <program>_data module. Read both. Hotel Mario's coverage was 21% before this.
    • Open each module in Ghidra 12.2 as raw 68000 code at its load address. Or build the ghidra-os9 loader for your Ghidra version.
    • Write tools/analyze.py: recursive descent from the entry points, plus jump tables, switch tables and the jsr N(a6) stub table.
    • Save the ledger to notes/functions.csv: address, size, name, source of the name, and status.
    Prompt for Claude
    GOAL: Build the function ledger for <MODULE> of <GAME>.
    READ FIRST: `SCOPE.md`, `game.json`, the module at <ABSOLUTE_PATH_TO_EXTRACTED>/<MODULE>, and <MODULE>.stb if it exists.
    FENCE: Edit only `tools/os9mod.py`, `tools/analyze.py`, `notes/functions.csv` and `docs/CDI_NOTES.md`. Never write module bytes or listings into the repo.
    CHECK: Run `python tools/analyze.py <MODULE_INDEX>`. Done means `os9mod.py` reports the header parity and CRC-24 as correct, the ledger lists every function with address and size, and the script prints code coverage as a percent of the code area.
    BUDGET: Stop after 90 minutes or 3 tries at one pattern with no coverage gain.
    IF BLOCKED: Stop. Name the address range you cannot classify as code or data, and why.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the CRC line and the coverage line. 3) Files changed. 4) Patterns still missed. 5) New parking-lot items.
    Done whennotes/functions.csv lists every function, the CRC-24 checks out, and coverage is printed as a number.

    Proof this works: Hotel Mario Static Recomp, Laser Lords static recomp, Flintstones/Jetsons: Timewarp, https://github.com/moralrecordings/ghidra-os9

  4. Boot the game on an interpreter first

    Before any recompiler, you run the game's own code in a 68000 interpreter. Your runtime answers the OS-9 calls. The goal is the first picture, like the Philips logo. This gives you a working reference for every later check.

    • Write your own small interpreter, or fetch Musashi the way HotelMario-Decomp does: git clone --depth 1 https://github.com/kstenerud/Musashi.git tools/ext/Musashi. It has an MIT licence.
    • Write runtime/os9.c. It handles TRAP #0 calls such as F$Load, F$Link, I$Read, F$Fork and F$Sleep.
    • Make every unknown call fail loud. Log the call, the address and the registers to saves/recomp.log, then stop.
    • Build the system globals block that address 0 points to. Keep its tick counter current. Hotel Mario hung at the logo without it.
    • Write runtime/cdfm.c for disc reads and runtime/video.c for the MCD212 display tables. Start with what the logo needs.
    • Add a headless mode: --headless --frames N --dump K writes every Kth frame as a picture. Claude reads these, not a window.
    • Record new call numbers in docs/CDI_NOTES.md, with the game address that proved each one.
    Prompt for Claude
    GOAL: Boot <GAME> in the interpreter until the first logo frame appears in a headless dump.
    READ FIRST: `SCOPE.md`, `docs/CDI_NOTES.md`, `notes/functions.csv`, https://icdia.co.uk/docs/funcspec.html
    FENCE: Edit only `runtime/` and `docs/CDI_NOTES.md`. Do not edit `tools/`. No stubs that return success: an unknown call must stop the run with its number and address.
    CHECK: Run `build\cmake\Release\game.exe --headless --frames 600 --dump 60 --interp`. Done means `saves/` holds a frame that shows the logo and `saves/recomp.log` has no fatal error before field 600.
    BUDGET: Stop after 3 failed tries at the same call, or 60 minutes with no new call answered.
    IF BLOCKED: Stop. Name the call, its address and what the Green Book says. Do not invent its behaviour.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the log tail and the frame file name. 3) Files changed. 4) Calls added, with proof addresses. 5) New parking-lot items.
    Done whenA headless run with --interp writes a frame that shows the first logo, with no fatal error in the log.

    Proof this works: Hotel Mario Decomp, Hotel Mario Static Recomp, https://github.com/kstenerud/Musashi

  5. Write the 68000-to-C recompiler

    Now you translate the mapped functions into C. Each 68000 function becomes one C function that works on a saved register set. Code the recompiler did not find falls back to the interpreter. The output goes to gen/ and is never edited by hand.

    • Write tools/m68k.py, a 68000 decoder. Check it against Capstone with pip install capstone on many instructions.
    • Write tools/gen.py. It reads the ledger and game.json and writes C files to gen/.
    • Compute condition flags only where a later instruction reads them. The Link port calls this flag liveness.
    • Turn calls through the jsr N(a6) stub table into direct C calls, with a guard check.
    • Send a jump to an unknown address back to the interpreter and log it. Do not crash. Voyeur's port did this.
    • List self-modifying code in game.json as interp_ranges, so the interpreter runs it from live bytes.
    • Keep the --interp switch working. It runs every function in the interpreter for comparison.
    Prompt for Claude
    GOAL: Make `tools/gen.py` translate <MODULE> to C so the recompiled build reaches the same logo frame as step 4.
    READ FIRST: `CLAUDE.md`, `notes/functions.csv`, `game.json`, `runtime/` (only the dispatcher and CPU state files)
    FENCE: Edit only `tools/m68k.py`, `tools/gen.py` and the dispatcher file in `runtime/`. Never edit files in `gen/`; regenerate them. Do not change the interpreter.
    CHECK: Run `build.bat`, then `game.exe --headless --frames 600 --dump 60` and the same with `--interp`. Done means both runs write byte-identical frame files and `recomp.log` lists how many calls fell back to the interpreter.
    BUDGET: Stop after 4 failed builds in a row or 2 hours.
    IF BLOCKED: Stop. Paste the first instruction that decodes differently from Capstone.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the compare output and the fallback count. 3) Files changed. 4) Instruction types not yet handled. 5) New parking-lot items.
    Done whenThe recompiled and the --interp runs give byte-identical frames up to the logo, and the fallback count is logged.

    Proof this works: Link: The Faces of Evil Static Recomp, Voyeur (CD-i) static recompilation, Kether, https://pypi.org/project/capstone/

  6. Prove the translation with tests

    A running game is not proof. You need tests that compare the recompiled code with the interpreter, one instruction at a time and over long runs. Every reviewed port did both. After this step, every change must keep these tests green.

    • Write tools/gen_tests.py. It makes random register states and runs each instruction both ways. Build it as insn_test.
    • Write ALU reference vectors with an independent model, as test/gen_alu_vectors.py. Build them as alu_test.
    • Run cmake --build build\cmake --config Release --target insn_test alu_test, then run both test programs.
    • Add scripted input: --script "field:key:dur,...". Add random input: --fuzz SEED. Add sound capture: --wav out.wav.
    • Run the same session twice, once with --interp. Compare every dumped frame and the WAV files byte for byte.
    • Run comparison runs in separate folders. Each run writes saves next to the program, and old saves change the result.
    • Add all of this to tools\check.bat, so one command runs it.
    Prompt for Claude
    GOAL: Add instruction tests, ALU vectors and a headless interp-versus-recompiled compare to `tools\check.bat`.
    READ FIRST: `CLAUDE.md`, `tools/m68k.py`, https://github.com/tools81/hotel-mario-static-recomp (README section Debugging, for design only)
    FENCE: Edit only `tools/gen_tests.py`, `test/`, `tools/diffrun.py` and `tools/check.bat`. Do not change `tools/gen.py` or `runtime/` in this task. Do not loosen a comparison to make it pass.
    CHECK: Run `tools\check.bat`. Done means it runs at least 2,000 instruction tests, all ALU vectors and a 4,000-field fuzzed compare, and prints PASS. Then break one opcode on purpose and show that it prints FAIL.
    BUDGET: Stop after 90 minutes.
    IF BLOCKED: Stop. Paste the first failing instruction with both register sets.
    OTHER PROBLEMS: Real translation bugs go in `notes/parking-lot.md`. Do not fix them here.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the PASS output and the FAIL output from the broken opcode. 3) Files changed. 4) Test counts. 5) New parking-lot items.
    Done whentools\check.bat prints PASS, and it prints FAIL when one opcode is broken on purpose.

    Proof this works: Hotel Mario Static Recomp, Mutant Rampage: Bodyslam Static Recomp, Zelda: Wand of Gamelon Static Recomp, The Apprentice Recomp

  7. Grind: calls, pictures, sound and coverage

    Now you play further, one problem at a time. Each unit is one missing OS call, one picture bug, one sound bug, or one code gap. Every unit has a scripted run that shows it. Each fix must keep tools\check.bat green.

    • Read saves/recomp.log for unknown calls and SetStat or GetStat codes. Each one is a unit.
    • Use --trace for system calls, --ramdump N for RAM and display tables, and --watch ADDR for one address.
    • Learn call numbers from the game's own wrapper code. The Green Book names calls but often gives no number.
    • Raise coverage by teaching analyze.py a new pattern, not by listing guessed addresses. Laser Lords went from 41% to 94% this way.
    • For PAL games set 625 lines, 280-line fields and 50 fields a second. Zelda's Adventure needed this.
    • Add a game-specific piece only through game.json hooks. Keep the shared runtime free of single-game hacks.
    • Mark each unit done in PROGRESS.md with the run that proves it.
    Prompt for Claude
    GOAL: Fix one unit: <UNIT> (a missing call, a picture bug, a sound bug or a coverage gap) seen at field <FIELD>.
    READ FIRST: `CLAUDE.md`, `PROGRESS.md`, `docs/CDI_NOTES.md`, the last 50 lines of `saves/recomp.log`, and the run command: <SCRIPT_OR_FUZZ_SEED>
    FENCE: Edit only <FILES> in `runtime/` or `tools/analyze.py`, plus `docs/CDI_NOTES.md` and `PROGRESS.md`. Never edit `gen/`. No stub that fakes success. No guessed entry points.
    CHECK: Run the scripted run past field <FIELD> and `tools\check.bat`. Done means the unit's symptom is gone in the dumped frames or log, and check.bat prints PASS.
    BUDGET: Stop after 3 failed fixes or 60 minutes.
    IF BLOCKED: Stop. Give the address, the call or register, and what you know. Do not work around it.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`. Do not fix them.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the frame names, log lines and check output. 3) Files changed. 4) The cause, with the address that proves it. 5) New parking-lot items.
    Done whenThe unit's scripted run passes its field with no fatal log line, and tools\check.bat prints PASS.

    Proof this works: Laser Lords static recomp, Zelda's Adventure, Kether, Hotel Mario Static Recomp

  8. Harden it and ship without game code

    You turn your test runs into a regression set, so old fixes stay fixed. You add long fuzz runs. Then you package a build that holds no game code. The player builds it from their own disc, or runs the disc's code in the interpreter.

    • Save each passing scripted run as a regression case in tests/regress/, with the hash of its final frame.
    • Run three long fuzz sessions, for example 30,000 fields each, with no fatal error.
    • If you add quicksaves, check that fields after a load match fields after the save.
    • Make blocking calls like F$Sleep return to the main loop, so the whole machine can be saved between fields.
    • Ship only tools and runtime. Voyeur's release runs the disc's own program in the interpreter, so it contains no game code.
    • Make the packaging script refuse to zip if any file holds game code or disc data. HotelMario-Decomp does this.
    • Build an installer with Inno Setup 6, as the tools81 ports do, or ship a plain zip.
    Prompt for Claude
    GOAL: Build a regression set and a release package that holds no game code.
    READ FIRST: `CLAUDE.md`, `PROGRESS.md`, `tests/regress/`, https://github.com/tools81/voyeur-static-recomp (README, release notes, for design only)
    FENCE: Edit only `tests/regress/`, `tools/regress.bat`, `tools/package.ps1` and `installer/`. Do not edit `runtime/` or `gen/`. Do not change expected hashes of existing cases.
    CHECK: Run `tools\regress.bat` and `powershell -ExecutionPolicy Bypass -File tools\package.ps1`. Done means every regression case prints PASS, and the packager lists the zip contents and confirms no module, stream or `gen/` file is inside.
    BUDGET: Stop after 90 minutes.
    IF BLOCKED: Stop. Name the case that fails and its first differing frame.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: regress output and the zip listing. 3) Files changed. 4) What is left. 5) New parking-lot items.
    Done whenAll regression cases pass on a clean clone, and the release zip holds no game code or disc data.

    Proof this works: Voyeur (CD-i) static recompilation, Hotel Mario Decomp, Hotel Mario Static Recomp, https://jrsoftware.org/isinfo.php

Path B: Full decomp for porting

First you find which compiler built the game. Mac-built games used Apple MPW C 3.0, which runs in a Mac emulator, so their C can match byte for byte. Microware-built games need a compiler nobody here had. For those you rebuild the module from assembly first, then write C checked by tests against the running game. Last, you move the C onto a PC runtime and then onto other systems.

Method: byte-exact rebuild of each OS-9 module, then matching C where the original compiler runs, or tested non-matching C where it does not

Pick this after Path A, or with it. Pick it first only for an Animation Magic game, since two of those are already done. Check ByteDecompiler before you start: your game may already be finished.

  1. Find out which compiler built the game

    You cannot match C without the original compiler. So you find it first. Each compiler leaves marks in the code. You test small functions with each candidate and keep the one that matches.

    • Look for MacsBug names after each rts and globals reached through register A5 at negative offsets. Those mean Apple MPW C.
    • A .stb symbol module means the Microware linker was used. a5 frames, arguments in d0 and d1, and calls to the cio trap library on trap #13 point to Microware C 3.2.
    • Calls to the CSL shared library point to Microware Ultra C. A 1996 user guide, "Preparing for Ultra C", says CSL is to Ultra C what CIO is to C 3.2.
    • Check library names too. HotelMario-Decomp used old _ev_* names and _T$ maths helpers to pick C 3.2.
    • For MPW C, test versions 3.0 to 3.3.1. Only 3.0 matched Link. C 3.1 compares chars as longs. C 3.2 and later clean the stack with addq.w, not addq.l.
    • Write the result and the proof into docs/COMPILER.md.
    Prompt for Claude
    GOAL: Name the compiler (and version, if you can) that built <MODULE> of <GAME>, with proof.
    READ FIRST: `SCOPE.md`, `notes/functions.csv`, a Ghidra listing of 10 small functions (I will give addresses), https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/docs/emulator.md , https://icdia.co.uk/microware/index.html
    FENCE: Create only `docs/COMPILER.md`. No code. Do not download any compiler, ROM or disk image.
    CHECK: Run `python tools/analyze.py <MODULE_INDEX>` and open `build/listing_<MODULE>.asm`. Done means every address in COMPILER.md shows the instruction you quote there, COMPILER.md names one compiler family, lists at least 3 code marks with addresses, and lists what would prove the exact version.
    BUDGET: Stop after 60 minutes.
    IF BLOCKED: Stop. Say which two compilers you cannot tell apart, and what test would decide.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: addresses and instruction lines. 3) Files changed. 4) Risks. 5) New parking-lot items.
    Done whendocs/COMPILER.md names the compiler family with at least three code marks and their addresses.

    Proof this works: Hotel Mario Decomp, https://github.com/ByteDecompiler/link-faces-of-evil, https://discmaster.textfiles.com/view/11868/2014.01.www.rtsi.com.tar/www.rtsi.com/OS9/MM1/TELECOM/linkup12_maint.lzh/ucc.doc

  2. Get the compiler running, or choose tested C

    For MPW games, you run the compiler inside Basilisk II, a Mac emulator, with no clicking. For Microware games, you likely cannot get the compiler. Then you choose non-matching C that is checked by tests instead. Write the choice in SCOPE.md.

    • MPW route, in WSL: build Basilisk II from kanjitalk755/macemu with SDL2 and no GTK: in macemu/BasiliskII/src/Unix run ./autogen.sh --with-gtk=no, then make.
    • You also need a Mac ROM that you own. ByteDecompiler used one from a Quadra 630. You need a Mac disk image too, with System 7.5.3, MPW 3.3 and MPW C 3.0 on it.
    • Run one compile job with a script, like ByteDecompiler's emu/mpwrun.sh <job folder>. One run takes about 12 seconds there.
    • Keep a clean copy of the Mac disk. A forced stop leaves the disk dirty, and the next start halts at a dialog.
    • Microware route: HotelMario-Decomp did not have the C 3.2 compiler. Its C is checked only for being the same as the assembly.
    • os9exec runs OS-9/68000 programs on Windows. Whether it runs a Microware compiler you own was not tested.
    • Write the choice, matching or tested, into SCOPE.md and get the owner's approval.
    Prompt for Claude
    GOAL: Compile one tiny test function with <COMPILER> and compare its bytes with the same function in the game.
    READ FIRST: `docs/COMPILER.md`, https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/docs/emulator.md , https://github.com/kanjitalk755/macemu
    FENCE: Edit only `emu/` scripts, `tools/compile_one.sh` and `docs/COMPILER.md`. Never commit a Mac ROM, a system disk, MPW or any compiler file. Do not download any of them; I supply them at <ABSOLUTE_PATH_TO_EMU_FILES>.
    CHECK: Run `bash tools/compile_one.sh test/<FUNC>.c`. Done means it prints the compiled bytes and a byte compare with the game's function at <ADDRESS> that says SAME or names the first different byte.
    BUDGET: Stop after 2 hours.
    IF BLOCKED: Stop. Say which file or step is missing. Do not swap in a different compiler.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the compare output. 3) Files changed. 4) Run time per job. 5) New parking-lot items.
    Done whenOne test function compiles with no clicks, and the script prints a byte compare against the game's function.

    Proof this works: https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/docs/emulator.md, Hotel Mario Decomp, https://github.com/peacedudes/os9exec

  3. Rebuild the module byte for byte

    Before any C, you rebuild the module from assembly and prove it matches. Each function becomes one assembly file. A script joins them, fixes the header checks and compares the SHA-1. This is your one check for all of Path B.

    • In WSL, build vasm from the mirror: make CPU=m68k SYNTAX=mot. The tool is called vasmm68k_mot.
    • Write tools/split.py. It writes asm/<name>.s per function and data to bin/. Both folders stay out of git.
    • Reassemble each instruction and compare it with the original bytes. HotelMario-Decomp checks every one this way.
    • Write tools/build.py. It joins the pieces, recomputes the OS-9 header parity and CRC-24, and checks the SHA-1.
    • Make it print one clear line, like ByteDecompiler's OK: byte-identical to the original, or list each DIFFERS function.
    Prompt for Claude
    GOAL: Rebuild <MODULE> from per-function assembly so its SHA-1 matches the original.
    READ FIRST: `docs/COMPILER.md`, `notes/functions.csv`, https://github.com/StarWolf3000/vasm-mirror
    FENCE: Edit only `tools/split.py`, `tools/build.py` and `config/`. `asm/`, `bin/` and `build/` are made by scripts and stay out of git. Never edit the expected SHA-1.
    CHECK: Run `python3 tools/split.py && python3 tools/build.py`. Done means it prints `OK: byte-identical to the original`. Then change one byte in one `.s` file and show it prints the function as DIFFERS.
    BUDGET: Stop after 3 hours or 5 failed rebuilds in a row.
    IF BLOCKED: Stop. Paste the first differing offset and the instruction there.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the OK line and the DIFFERS line from the test break. 3) Files changed. 4) Pieces that are data, not code. 5) New parking-lot items.
    Done whenpython3 tools/build.py prints the byte-identical line, and a deliberate one-byte change makes it fail.

    Proof this works: Hotel Mario Decomp, https://github.com/ByteDecompiler/link-faces-of-evil, https://github.com/StarWolf3000/vasm-mirror

  4. Set up names, globals and the ledger

    Matching C needs to know where every global and call target lives. You write config files for these. You also set up a Ghidra project with the names applied. The ledger now tracks each function as asm, matching or tested.

    • Write config/functions.txt with every piece in module order, and config/symbols.txt with globals and their offsets.
    • For MPW games, write config/jumptable.txt. A jsr N(a5) call goes through the jump table to another segment.
    • Put every global in include/game.h as extern, with its A5 offset in a comment.
    • Sort the functions by size. ByteDecompiler lists them with tools/scripts/gamefuncs.py ... --list, smallest first.
    • Write a script that loads the names into Ghidra. ByteDecompiler uses tools\ghidra_setup.ps1 for this.
    • Add a status column to notes/functions.csv: asm, matching, tested or blocked.
    Prompt for Claude
    GOAL: Write the config files and headers so any function can be built from C by name.
    READ FIRST: `notes/functions.csv`, `docs/COMPILER.md`, https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/CONTRIBUTING.md
    FENCE: Edit only `config/`, `include/game.h`, `tools/` name scripts and `notes/functions.csv`. Do not write any `src/*.c` yet. Do not rename a function that has a MacsBug or `.stb` name.
    CHECK: Run `python3 tools/build.py`. Done means it still prints the byte-identical line, and every global used by the 20 smallest functions has a `symbols.txt` line.
    BUDGET: Stop after 2 hours.
    IF BLOCKED: Stop. Name the global or call target you cannot place.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the build line and counts of names. 3) Files changed. 4) Unknowns left. 5) New parking-lot items.
    Done whenThe build still matches, and the ledger has a status column with every function set to asm.

    Proof this works: https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/CONTRIBUTING.md, Hotel Mario Decomp

  5. Match one function end to end

    You take one small function all the way: C file, compile, place, rebuild, check. This proves the whole chain before you run many workers. Start with a function of 10 to 30 instructions.

    • Make src/<name>.c with #include "game.h". ByteDecompiler keeps one function per file.
    • Run python3 tools/build.py. A function that compiles but does not match prints DIFFERS.
    • Add --diff to see the differing lines. Or use asm-differ, which supports m68k.
    • Read the matching tips in ByteDecompiler's CONTRIBUTING.md. Write operations in the same order as the game does. Start docs/MATCHING_TIPS.md with what you learn here.
    • On the Microware route, mark the function tested instead. Its check is the differential run from step 8.
    Prompt for Claude
    GOAL: Turn <FUNC> at <ADDRESS> into C that builds to the same bytes.
    READ FIRST: `asm/<FUNC>.s`, `include/game.h`, `config/symbols.txt`, https://github.com/ByteDecompiler/link-faces-of-evil/blob/main/CONTRIBUTING.md
    FENCE: Edit only `src/<FUNC>.c`, `docs/MATCHING_TIPS.md`, plus new `extern` lines in `include/game.h` and `config/symbols.txt`. No inline assembly. Do not edit `tools/build.py`, `asm/` or the expected SHA-1.
    CHECK: Run `python3 tools/build.py`. Done means it prints the byte-identical line and lists <FUNC> as built from C.
    BUDGET: Stop after 5 meaningfully different attempts.
    IF BLOCKED: Stop. Paste the `--diff` output for your best attempt and your best guess at the cause.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the build output. 3) Files changed. 4) What you learned, for the tips file. 5) New parking-lot items.
    Done whenpython3 tools/build.py prints the byte-identical line with one function built from C.

    Proof this works: https://github.com/ByteDecompiler/link-faces-of-evil, https://github.com/simonlindholm/asm-differ

  6. Match the rest in parallel

    Now many workers each take one function. Each works in its own git worktree. The lead merges one at a time and rebuilds after each merge. Easy functions go first. Sister games on the same engine can give you many functions for free.

    • Start workers with claude --worktree <name>. List small gitignored inputs in .worktreeinclude so each worktree can build.
    • Give each worker one function and its asm file path. Never give it the whole listing.
    • After each merge, run python3 tools/build.py on the main branch. A match in a worktree is not a match after merge.
    • Check sister games. Wand of Gamelon reused 280 of its 359 functions from the Link decomp.
    • Send functions that fail 5 times to the hard queue in the ledger.
    • Add every new compiler habit to docs/MATCHING_TIPS.md as a general rule, not a note about one function.
    Prompt for Claude
    GOAL: As lead, pick the next <N> functions marked asm and give one to each worker.
    READ FIRST: `notes/functions.csv`, `PROGRESS.md`, `docs/MATCHING_TIPS.md`
    FENCE: Edit only `notes/functions.csv`, `PROGRESS.md` and merges of `src/*.c`. Workers edit only their own `src/<FUNC>.c`. No worker edits `include/game.h` without asking you first.
    CHECK: After each merge run `python3 tools/build.py`. Done means all merged functions build from C and the line says byte-identical.
    BUDGET: Each worker stops after 5 attempts. Stop the batch after <HOURS> hours.
    IF BLOCKED: Stop the batch. Report which merge broke the build and revert it.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the final build line. 3) Functions matched, failed and sent to hard. 4) New tips. 5) New parking-lot items.
    Done whenEvery merged function builds from C, and the build on the main branch prints the byte-identical line.

    Proof this works: https://github.com/ByteDecompiler/zelda-wand-of-gamelon, https://github.com/ByteDecompiler/link-faces-of-evil

  7. Name everything and pull out the data

    Matching C with names like func_1234 is not a usable source. You name globals, structs and fields. You document the data files on the disc. Regional discs go into one source with a version header.

    • Name globals and fields after what the code does with them. Keep the game's own function names, typos included.
    • Put numbers that differ between regions in include/version.h, picked at build time.
    • Find each global's offset in other regions with a script, like ByteDecompiler's tools/port_symbols.py --version fr.
    • Write docs/data.md for the disc files, picture formats and save layout.
    • Write extractors for pictures and sound to folders outside git. Never commit what they write.
    Prompt for Claude
    GOAL: Name the globals and struct fields used by <SUBSYSTEM> and document them.
    READ FIRST: `include/game.h`, `src/` files for <SUBSYSTEM>, `notes/functions.csv`
    FENCE: Edit only names and comments in `include/game.h`, `src/` files for <SUBSYSTEM>, and `docs/data.md`. Do not change code shape. Do not rename functions that have original names.
    CHECK: Run `python3 tools/build.py` for every version you support. Done means each prints the byte-identical line.
    BUDGET: Stop after 2 hours.
    IF BLOCKED: Stop. List the names you cannot work out, with the addresses that use them.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: each build line. 3) Files changed. 4) Names still unknown. 5) New parking-lot items.
    Done whenAll supported versions still match, and the subsystem has no placeholder names left.

    Proof this works: https://github.com/ByteDecompiler/link-faces-of-evil, Hotel Mario Decomp

  8. Run the C on a PC runtime with tests

    Matching C still needs a CD-i to run. Now you build the C as a PC program on your Path A runtime. You compare it with the interpreter running the original code. For Microware games, this is the main check.

    • Compile src/*.c with your PC compiler into the Path A runtime, in place of the recompiled module.
    • Replace OS-9 calls with calls into your runtime. Keep the same behaviour and the same RAM layout at first.
    • Run scripted sessions with the C build and with --interp. Compare every frame and the WAV files.
    • HotelMario-Decomp swaps functions from assembly to C one at a time and keeps the port playable throughout.
    • Mark each function tested in the ledger only after a session that calls it passes the compare.
    Prompt for Claude
    GOAL: Build <GAME> from `src/*.c` on the PC runtime and pass the regression set.
    READ FIRST: `CLAUDE.md`, `tests/regress/`, the runtime's module loader, `notes/functions.csv`
    FENCE: Edit only `port/` glue files and `CMakeLists.txt`. Do not change `src/*.c` logic: a fix there must keep `tools/build.py` matching. Never edit expected frame hashes.
    CHECK: Run `tools\regress.bat --c-build`. Done means every case gives the same frames and audio as the `--interp` reference, and `python3 tools/build.py` still matches.
    BUDGET: Stop after 3 failed fixes on one case.
    IF BLOCKED: Stop. Give the first differing field, the function running there and both RAM values.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: regress output and the build line. 3) Files changed. 4) Functions now marked tested. 5) New parking-lot items.
    Done whenThe C build passes every regression case against the interpreter, and the matching build still matches.

    Proof this works: Hotel Mario Decomp, Hotel Mario Static Recomp

  9. Port it to a new system

    You put the CD-i parts behind a small interface: video, sound, disc and input. You fix byte order, because the 68000 is big-endian and most new systems are not. Then you build for the new system and run the same regression set.

    • Use SDL2 for window, input and sound, as HotelMario-Decomp does with SDL 2.32.10.
    • Swap bytes when the C reads big-endian data from the disc or from RAM tables.
    • Check every pointer stored in a 32-bit field. On a 64-bit PC a pointer does not fit.
    • HotelMario-Decomp builds for 3DS with devkitPro. It plays on a real New 3DS, but that build is not released.
    • Builds that embed translated game code cannot be shared. Plan a build-from-your-disc step for every target.
    • Run the regression set on the new system and compare its frames with the PC build.
    Prompt for Claude
    GOAL: Build <GAME> for <TARGET> and pass the regression cases that do not need a mouse.
    READ FIRST: `CLAUDE.md`, `port/` interface headers, `tests/regress/`, <TARGET_SDK_DOCS>
    FENCE: Edit only `port/<TARGET>/` and the build file for <TARGET>. Do not edit `src/` logic or the PC port. Do not put game code or data in any release file.
    CHECK: Run the regression cases on <TARGET> or its emulator. Done means each case's frames match the PC build's frames.
    BUDGET: Stop after 3 hours on one failing case.
    IF BLOCKED: Stop. Name the first case that fails and whether the cause is byte order, pointer size or timing.
    OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
    REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the frame compare. 3) Files changed. 4) Cases still failing. 5) New parking-lot items.
    Done whenThe new build passes the same regression cases as the PC build, with matching frames.

    Proof this works: Hotel Mario Decomp, https://github.com/libsdl-org/SDL/releases/tag/release-2.32.10

Tools you need

ToolWhat it doesHow to get itHow we know
Visual Studio 2022 Build Tools and CMake
Required Windows
The C compiler and build system every reviewed Windows port uses. They build the runtime and the generated C.
Path A Path B
`winget install Microsoft.VisualStudio.2022.BuildTools --override "--add Microsoft.VisualStudio.Workload.VCTools --includeRecommended --passive"`, then `winget install Kitware.CMake`winget show read 2026-11-13: BuildTools 17.14.45, CMake 4.4.4. Workload id Microsoft.VisualStudio.Workload.VCTools and the --add, --includeRecommended, --passive and winget --override switches from Microsoft Learn, read 2026-11-13. The whole line was not run here. Generator Visual Studio 17 2022 and CMake 3.19+ named in the tools81 READMEs
Python 3 and Git
Required both
Python runs every reviewed project's tools: extractors, analyzers, recompilers and build scripts. Git holds your repo and the worktrees for parallel workers.
Path A Path B
`winget install Python.Python.3.12` and `winget install Git.Git`. In WSL for Path B, make a venv with Capstone, as ByteDecompiler does: `python3 -m venv ~/cdi_tools/venv`, then `~/cdi_tools/venv/bin/pip install capstone`winget show read 2026-11-13: Python 3.12.10, Git 2.56.0.4. Python 3.8+ named in the tools81 READMEs, Python 3.11+ in cdirecomp's. The venv line is from the ByteDecompiler Link README, read 2026-11-13
WSL with Ubuntu
Required Windows
Linux inside Windows. Path B needs it: the matching projects run Basilisk II, vasm and their build scripts there.
Path B
In PowerShell as administrator: `wsl --install`, then restart. It installs Ubuntu by defaultMicrosoft Learn WSL install page read 2026-11-13. The ByteDecompiler READMEs tell Windows users to install WSL with Ubuntu
Ghidra 12.2
Helpful both
A free disassembler and decompiler. It reads 68000 code. Use it to read functions and to check every claim about code.
Path A Path B
Install JDK 25 (64-bit), for example from Adoptium. Download `ghidra_12.2_PUBLIC_20261016.zip` from the releases page and unzip itRelease Ghidra_12.2_build (2026-10-22), its README (JDK 25 64-bit) and the Processors/68000 language folder read 2026-11-13. HotelMario-Decomp used Ghidra 10.4 headless for draft C
ghidra-os9
Helpful both
A Ghidra add-on that loads Microware OS-9 for 68k modules, as used by the CD-i. It also has a system call analyzer.
Path A Path B
Clone the repo and build it with Gradle against your Ghidra install. It has no prebuilt releaseRepo, README and source list read 2026-11-13. Last change 2022-02, so a build for Ghidra 12.2 was not tested
Musashi
Helpful both
A 68000 CPU core in plain C. HotelMario-Decomp runs the game's program on it. It is a good reference to test your recompiler against.
Path A Path B
`git clone --depth 1 https://github.com/kstenerud/Musashi.git tools/ext/Musashi`README read 2026-11-13 (version 4.10, MIT-style licence). Clone line from the HotelMario-Decomp card
Capstone 5.0.9
Helpful both
A disassembler library with 68000 support. Use it to check your own instruction decoder.
Path A Path B
`pip install capstone`PyPI read 2026-11-13 (5.0.9). Used by HotelMario-Decomp's tools/recomp.py and by ByteDecompiler's split tools
MAME 0.289 (with chdman)
Helpful both
An emulator with a CD-i driver and a debugger. Use it to watch the real game and compare. Use the cdimono1 (PAL) or cdimono1n (NTSC) machine; MAME marks cdi910, cdimono2 and cdi490a as not working. Its chdman tool turns a .chd into .bin and .cue.
Path A Path B
`winget install MAMEdev.MAME`. It needs a CD-i BIOS that you dumped yourselfRelease mame0289 and src/mame/philips/cdi.cpp read 2026-11-13: cdimono1 and cdimono1n have imperfect graphics and sound; cdimono2, cdi910, cdi490a and gpi1200 are flagged not working. chdman docs read the same day. winget show MAMEdev.MAME gives 0.289
CeDImu
Helpful both
An open CD-i emulator. It can trace system calls, log memory access, and export files and audio from a disc.
Path A
`git clone --recurse-submodules https://github.com/Stovent/CeDImu`, then build with CMake. Needs C++23 and wxWidgets 3.2; on Windows the README gets wxWidgets with `vcpkg.exe install wxwidgets:x64-windows-static`. Needs your own BIOSREADME read 2026-11-13. The README calls it very early. mstan/cdirecomp's PRINCIPLES.md lists it as a development-only tool that never ships
mstan/cdirecomp
Helpful both
A CD-i recompiler and runtime. It recompiles the real CD-RTOS system ROM to C, then the game. It is a second route for Path A.
Path A
`git clone --recurse-submodules https://github.com/mstan/HotelMarioRecomp.git` as an example game project, then `python tools/build.py --bios <BIOS_ROM> --disc <CUE>`. Needs CMake 3.20+, Ninja, Python 3.11+, SDL2, OpenGL and your own BIOS. The README names Windows MinGW and Linux buildsREADME, AGENTS.md, CLAUDE.md, PRINCIPLES.md and the HotelMarioRecomp README read 2026-11-13. PolyForm Noncommercial licence. It calls itself development software
SDL2 2.32.10
Helpful both
A library for windows, input and sound on many systems. HotelMario-Decomp's port uses it on Windows, and devkitPro for 3DS.
Path B
Download `SDL2-devel-2.32.10-VC.zip` (or the MinGW package) from the release page and unpack it into `tools/ext/`Release page and its assets read 2026-11-13. Version named in the HotelMario-Decomp card
pl_mpeg
Helpful both
A one-file C library that decodes MPEG-1 video and audio. Mutant Rampage uses it to stand in for the Digital Video cartridge.
Path A
Copy `pl_mpeg.h` into `runtime/third_party/` and keep its licence textRepo read 2026-11-13. Named in the Mutant Rampage: Bodyslam card as vendored MIT code
Inno Setup 6
Helpful Windows
Builds a Windows installer. Every tools81 port ships one next to a portable zip.
Path A
`winget install JRSoftware.InnoSetup`winget show JRSoftware.InnoSetup read 2026-11-13 (6.7.3). The upstream repo already has a 7.1.1 release; the ports name version 6
vasm (`vasmm68k_mot`)
Required both
A 68000 assembler with Motorola syntax. Path B uses it to rebuild each module from assembly and prove the bytes match.
Path B
In WSL: `git clone https://github.com/StarWolf3000/vasm-mirror`, then `make CPU=m68k SYNTAX=mot` in that folderMirror README read 2026-11-13 (binary name vasm<CPU>_<SYNTAX>, Makefile.Win32 for Windows). The home site sun.hasenbraten.de showed "under maintenance" that day. Used by HotelMario-Decomp
asm-differ
Helpful WSL/Linux
Shows two versions of a function side by side and marks each difference. It supports m68k.
Path B
In WSL: `pip install 'asm-differ @ git+https://github.com/simonlindholm/asm-differ.git'`README read 2026-11-13: supports m68k; pip line copied from it. Not used by a reviewed CD-i project; ByteDecompiler has its own compare.py
Basilisk II (kanjitalk755/macemu)
Required WSL/Linux
A 68k Mac emulator. Path B runs Apple MPW C 3.0 inside it, for games built on a Mac.
Path B
In WSL: `git clone https://github.com/kanjitalk755/macemu`, then in `macemu/BasiliskII/src/Unix` run `./autogen.sh --with-gtk=no` and `make`. Needs SDL2 2.0.14 or newer. You need a Mac ROM and MPW that you ownREADME, autogen.sh and configure.ac (--with-gtk) read 2026-11-13. ByteDecompiler's emulator doc (SDL2, JIT, --with-gtk=no) read the same day. Only for MPW-built games
os9exec 4.1.1
Helpful both
Runs OS-9/68000 programs on Windows, Linux or macOS. It might run a Microware compiler you own. Nobody here has tried that.
Path B
Download `os9exec-windows-x64.exe` from the v4.1.1 releaseREADME and release v4.1.1 (2026-09-29) read 2026-11-13. GPL-2.0. Running a Microware C compiler on it was not tested

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

MCP server

Claude can ask Ghidra what an address does. The cdirecomp rules call the Ghidra 68000 listing the literal answer. Release 2.0 targets Ghidra 12.1.4, not 12.2.

Install `uv`. Install the extension from release 2.0 in Ghidra 12.1.4, then run `claude mcp add ghidra -- uvx --from git+https://github.com/LaurieWired/GhidraMCP ghidra-mcp --ghidra-server http://127.0.0.1:8080/`

reverse-engineer-anything (REA)

skills.sh

The Zelda CD-i macOS recomp used REA with Ghidra to find static entry points. It needs the REA MCP server and Node.js. Its fallback runs npx -y rea-agents@latest, which fetches the newest package, so install REA yourself first.

npx skills add morluto/rea --skill reverse-engineer-anything -a claude-code

ghidra-headless

skills.sh

Runs Ghidra with no window and saves functions and strings to files. HotelMario-Decomp exported draft C per function with its own headless Ghidra scripts. On Windows the skill needs Git Bash, JDK 17 or newer, and GHIDRA_HOME set by you.

npx skills add trailofbits/skills-curated --skill ghidra-headless -a claude-code

static-recomp-scope-plan

skills.sh

Makes you write down the exact disc, the scenes to test and the numbers to hit before work starts. That is step 1 of Path A.

npx skills add bgyss/switchrecomp --skill static-recomp-scope-plan -a claude-code

matcher

skills.sh

A careful method for matching one C function. It reports fake matches as not done. Use it on the MPW route of Path B. It calls five other lemiur skills by name, so install those too.

npx skills add lemiur/game-decompilation-skill-for-claude --skill matcher -a claude-code

verification-before-completion

skills.sh

Claude may not say done without fresh proof. Here that is tools\check.bat PASS or the byte-identical line.

npx skills add obra/superpowers --skill verification-before-completion -a claude-code

systematic-debugging

skills.sh

No fix until the cause is found. CD-i hangs, like the logo that waited on a tick counter, need exactly this.

npx skills add obra/superpowers --skill systematic-debugging -a claude-code

using-git-worktrees

skills.sh

Keeps each Path B 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

skills.sh

Writes a short note so a fresh session can carry on. Tell it to save the note in notes/, not the temp folder.

npx skills add mattpocock/skills --skill handoff -a claude-code

Plan mode and subagents

built-in

Plan the runtime layout before any code. Send a subagent to search a big listing or log and report back short.

Built into Claude Code. A subagent with `isolation: worktree` works in its own temporary git worktree.

cdi-interp-diff (write your own)

write-your-own

The interp compare is the strongest check every CD-i port used. A skill makes every session run it the same way. No vetted skill on skills.sh is made for CD-i or the 68000, so you write this one.

A `.claude/skills/cdi-interp-diff/SKILL.md` that: 1) runs `build.bat`; 2) runs `game.exe --headless --frames <N> --dump <K> --fuzz <SEED>` in a fresh folder, then the same with `--interp` in another; 3) compares frames and WAVs byte for byte; 4) on a difference, prints the first field and runs `--trace` around it; 5) writes the result to `notes/findings.md`. Rule: never change the seed, field count or compare.

cdi-os9-call (write your own)

write-your-own

New OS calls are the main Path A unit. The Zelda CD-i macOS project splits its own skills the same way: development, REA and validation.

A `.claude/skills/cdi-os9-call/SKILL.md` for adding one OS call: find the game's wrapper and the number it passes, read the Green Book entry, write the handler in `runtime/os9.c` or `runtime/cdfm.c`, add a line to `docs/CDI_NOTES.md` with the proving address, then run `tools\check.bat`. Rule: no handler that returns success without doing the work.

Claude setup for this system

Cut CD-i work by OS call and by function. On Path A, one unit is one missing call, one picture or sound bug, or one code gap. Each has a scripted run that shows it. On Path B, one unit is one function from the ledger. Every unit has one check: tools\check.bat with the interp compare, or python3 tools/build.py printing byte-identical. Keep disc images outside the repo and give Claude their absolute paths.

Which model and effort for each job

The model and effort names come from one shared table, so they stay the same on every guide. See Claude Setup for the reasons.

JobWhat it means on this systemMax 20x (now called Ultra)Max 5x (now called Max)
Plan the projectPin the disc by SHA-1 and region. Check for the video cartridge. Look for prior ports and decomps of the same game. Guess the compiler. Write SCOPE.md.Fable 5.2
effort high
One session. This choice shapes months of work.
Opus 5.5
effort high
Use Fable 5.2 for this one session if you have weekly room.
Set up the projectInstall Build Tools and CMake. Lay out tools/, runtime/, test/, game.json. Write build.bat and tools\check.bat. Fill .gitignore before any disc file is near.Opus 5.5
effort medium
Sonnet 5.5
effort medium
Study the game fileRead the Green Book file system, module headers, .stb files and MacsBug names. Find jump tables and stub tables. Build the function ledger with coverage.Opus 5.5
effort high
Opus 5.5
effort medium
Raise to high for function edges and data layouts. A mistake there spreads.
Bulk unitsPath A: simple OS calls with a clear wrapper in the game. Path B: small functions to match, one per worker, each with the build check.Sonnet 5.5
effort medium
Haiku 5.5 for the plainest units.
Sonnet 5.5
effort medium
Fewer workers at once. Haiku 5.5 for the plainest units.
Hard unitsSelf-modifying code, code loaded into RAM, odd call tables, and functions that failed five match attempts.Fable 5.2
effort high
Go to xhigh only after a failed try at high.
Opus 5.5
effort high
Send only the last few that still fail to Fable 5.2.
Build the runtimeThe CD-RTOS stand-in: OS-9 calls, the CD file manager, MCD212 display tables, ADPCM sound, timers, and the MPEG cartridge if needed.Opus 5.5
effort high
Opus 5.5
effort medium
Raise to high for timing and graphics bugs.
Find a bugHangs at a logo, silent sound, wrong colours, PAL timing. Prove the cause with --trace, --ramdump or a frame compare before any fix.Opus 5.5
effort high
After two failed tries with full evidence, move to Fable 5.2.
Opus 5.5
effort high
One bug at a time.
Check the workA fresh session reruns tools\check.bat or tools/build.py. It looks for edits in gen/, stubbed calls, changed seeds or hashes, and hidden interpreter use.Opus 5.5
effort medium
Always a new session, never the one that did the work.
Sonnet 5.5
effort high
Always a new session, never the one that did the work.
Keep the recordsUpdate PROGRESS.md, ledger counts, the interpreted share, docs/CDI_NOTES.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 while the runtime is young. Give each one subsystem, like video, sound or the file manager, in its own worktree. One writer per runtime file. Path B: 4 to 6 workers on separate functions plus one lead. Rebuild after every merge.

Max 5x (now called Max)

Path A: 1 session, plus a second only for a separate tool like the test generator. Path B: 2 workers plus the lead. Send 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

Add to CLAUDE.md
- Read `SCOPE.md` and `PROGRESS.md` at the start of every session.
- Never edit files in `gen/`. They are made from the disc. Fix `tools/gen.py`, `tools/analyze.py` or `runtime/`, then regenerate.
- Disc images live at `<ABSOLUTE_PATH_TO_DISCS>`. Never copy them, extracted modules, RAM dumps or frame dumps into the repo.
- Never download a game, BIOS, Mac ROM, compiler or disc image. Ask me.
- The one check is `tools\check.bat` (Path A) or `python3 tools/build.py` (Path B). No pasted output means not done.
- No stubs. An unknown OS call or register must stop the run with its number and address. Never return a made-up success.
- Report how much code ran in the interpreter. Never present interpreter fallback as recompiled code.
- Give the address and the Ghidra or listing lines for every claim about code. Ghidra draft C is a guess until checked against the assembly.
- Do not edit expected hashes, fuzz seeds, regression cases or the check scripts. Ask me first.
- `game.json` holds real facts about the game only. Do not use it to hide a recompiler bug.
- Write each new call number to `docs/CDI_NOTES.md` with the game address that proves it.
- Do not copy code from repos with no licence. Read them for ideas only.
- In a worktree, use absolute paths for discs and 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.
- Do not commit or push without my OK. Start every report with DONE, PARTIAL or BLOCKED.

Keep the context small

  • Never let Claude read gen/. Hotel Mario alone has 34,045 translated instructions. Have it grep for one address instead.
  • Give Claude 50 lines of saves/recomp.log, not the whole log. Turn on one trace option at a time.
  • Pass a Ghidra address, not pasted assembly. Let the MCP server or a subagent fetch it.
  • Keep docs/CDI_NOTES.md as the one place for call numbers, so Claude does not search the Green Book each time.
  • On Path B, give each worker its function's asm/<name>.s path and its ledger row only.
  • Add Read deny rules for gen/**, extracted/**, saves/** and the disc folder.
  • Clear the chat after each unit. The ledger and the 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.

Prompt for Claude
GOAL: Tell me if <GAME> (<REGION>) suits Path A, Path B, or both, and draft SCOPE.md.
READ FIRST: The disc SHA-1 (I will paste it), the list of files on the disc from `python tools/cdi_extract.py --list <ABSOLUTE_PATH_TO_CUE>`, https://github.com/ByteDecompiler , https://github.com/tools81
FENCE: Create only `SCOPE.md` and `notes/prior-work.md`. No code. Do not copy or download any disc, BIOS or ROM.
CHECK: Run `certutil -hashfile <ABSOLUTE_PATH_TO_BIN> SHA1` and `python tools/cdi_extract.py --list <ABSOLUTE_PATH_TO_CUE>`. Done means SCOPE.md matches both outputs and lists the disc SHA-1, region, module names, `.stb` files found, video cartridge yes or no, compiler guess with proof, prior work with links, the chosen path, the first milestone with its check, and an out-of-scope list.
BUDGET: 60 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: tool outputs. 3) Files changed. 4) Risks you see. 5) New parking-lot items.

A good answer: A one-page SCOPE.md with the hash, the module list and a plain list of risks. Risks include PAL timing, the video cartridge, RAM-loaded code or an unknown compiler.

Map the binary

After setup, to build the function ledger for one module.

Prompt for Claude
GOAL: List every function in <MODULE> with address, size, name and how the name was found.
READ FIRST: `game.json`, <ABSOLUTE_PATH_TO_EXTRACTED>/<MODULE>, <MODULE>.stb if present, `docs/CDI_NOTES.md`
FENCE: Edit only `tools/analyze.py` and `notes/functions.csv`. Read-only everywhere else. No listings or module bytes in the repo.
CHECK: Run `python tools/analyze.py <MODULE_INDEX>`. Done means the ledger has one row per function, names come from `.stb` or MacsBug where present, and coverage of the code area is printed as a percent.
BUDGET: 90 minutes, or 3 tries at one pattern with no gain.
IF BLOCKED: Stop. Name the address range you cannot classify.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: coverage line and row count. 3) Files changed. 4) Patterns still missed. 5) New parking-lot items.

A good answer: A ledger with real names for most functions and a coverage number. It also lists missed patterns, such as a stub table or a switch table.

Unit loop: one OS call (Path A)

When saves/recomp.log stops the run at an unknown OS-9 or CD-RTOS call.

Prompt for Claude
GOAL: Handle OS call <CALL_NAME_OR_NUMBER> that stops <GAME> at field <FIELD>.
READ FIRST: The last 50 lines of `saves/recomp.log`, `docs/CDI_NOTES.md`, the game's wrapper at <ADDRESS>, https://icdia.co.uk/docs/funcspec.html
FENCE: Edit only `runtime/os9.c` or `runtime/cdfm.c`, and `docs/CDI_NOTES.md`. Never edit `gen/`. No handler that returns success without doing the work.
CHECK: Run `game.exe --headless --frames <FIELD+600> --dump 60` and `tools\check.bat`. Done means the run passes field <FIELD> with no fatal line and check.bat prints PASS.
BUDGET: Stop after 3 failed tries or 45 minutes.
IF BLOCKED: Stop. Give the call, its registers and what the Green Book says.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: log tail and check output. 3) Files changed. 4) The call number and its proving address. 5) New parking-lot items.

A good answer: A handler that follows the Green Book, one new line in CDI_NOTES.md with an address, and a run that goes past the old stop.

Unit loop: one code gap (Path A)

When the log shows a jump to an address the recompiler did not find.

Prompt for Claude
GOAL: Make the recompiler find the code at <ADDRESS> by teaching `analyze.py` the pattern that reaches it.
READ FIRST: The fallback lines in `saves/recomp.log`, the Ghidra listing around the caller at <CALLER_ADDRESS>, `tools/analyze.py`
FENCE: Edit only `tools/analyze.py`. Do not add the address to `extra_entries` unless you prove no general pattern reaches it. Never edit `gen/`.
CHECK: Run `build.bat` and `tools\check.bat`. Done means the fallback for <ADDRESS> is gone, coverage did not fall, and check.bat prints PASS.
BUDGET: Stop after 3 tries or 60 minutes.
IF BLOCKED: Stop. Show the instructions that reach the address and say why no rule fits.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: coverage before and after. 3) Files changed. 4) The pattern you added. 5) New parking-lot items.

A good answer: One general rule, such as a new switch-table shape, that removes this fallback and maybe others, with coverage numbers before and after.

Unit loop: one function to match (Path B)

For each function on the MPW route, after step 5 of Path B works.

Prompt for Claude
GOAL: Write `src/<FUNC>.c` so `tools/build.py` builds <FUNC> from C with the same bytes.
READ FIRST: `asm/<FUNC>.s`, `include/game.h`, `config/symbols.txt`, `docs/MATCHING_TIPS.md`
FENCE: Edit only `src/<FUNC>.c`, plus new `extern` lines in `include/game.h` and `config/symbols.txt`. No inline assembly. Never edit `tools/`, `asm/` or the expected SHA-1.
CHECK: Run `python3 tools/build.py`. Done means it prints the byte-identical line and <FUNC> is no longer listed as DIFFERS.
BUDGET: 5 meaningfully different attempts, each in a new file `scratch/<FUNC>_<N>.c`.
IF BLOCKED: Stop. Paste the `--diff` of your best attempt and the cause you suspect.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: build output. 3) Files changed. 4) A general tip for `MATCHING_TIPS.md`, if any. 5) New parking-lot items.

A good answer: A short C file that reads like real game code, the byte-identical line, and maybe one general tip about the compiler.

Batch dispatch

When the lead session hands out the next set of units to workers.

Prompt for Claude
GOAL: Pick the next <N> units from the ledger and start one worker per unit, each in its own worktree.
READ FIRST: `notes/functions.csv` or the open-units list in `PROGRESS.md`, `CLAUDE.md`
FENCE: Edit only `PROGRESS.md` and the ledger status column. Each worker edits only the files its card names. Do not give two workers the same runtime file.
CHECK: After each worker reports, rerun its check on the main branch after merge. Done means each merged unit passes there, and the ledger shows the new status.
BUDGET: Stop the batch after <HOURS> hours or 2 broken merges.
IF BLOCKED: Stop. Revert the bad merge and report it.
OTHER PROBLEMS: Collect workers' parking-lot items into `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output after each merge. 3) Units done, failed and sent to hard. 4) Ledger counts. 5) New parking-lot items.

A good answer: A table of units with DONE, PARTIAL or BLOCKED, the check output after each merge, and updated counts.

Debug with evidence

When the game hangs, goes silent or draws wrong, and the cause is not known.

Prompt for Claude
GOAL: Find the cause of <SYMPTOM> at field <FIELD>. Do not fix it yet.
READ FIRST: The run command <COMMAND>, the last 50 lines of `saves/recomp.log`, `docs/CDI_NOTES.md`, `notes/findings.md`
FENCE: Read-only, except new notes in `notes/findings.md`. You may run `--trace`, `--ramdump <FIELD>`, `--watch <ADDR>` and `--interp` runs.
CHECK: Run <COMMAND> and <COMMAND> `--interp` in two fresh folders, then compare the dumps with `fc /b`. Done means you show the first field where the two runs differ, or prove they do not differ, and name the code or runtime part that causes it, with an address.
BUDGET: 60 minutes.
IF BLOCKED: Stop. List what you ruled out and what data you still need.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: trace lines or RAM bytes. 3) Files changed. 4) The cause and a proposed fix. 5) New parking-lot items.

A good answer: A cause tied to one address or one runtime function, shown with log lines, plus a short fix plan for the next session.

Verify: hunt for cheating

After any batch of claimed work, in a fresh session that did not do the work.

Prompt for Claude
GOAL: Check that the work in commits <RANGE> is real.
READ FIRST: `git diff <RANGE>`, `CLAUDE.md`, `PROGRESS.md`
FENCE: Read-only. Do not fix anything. Write findings only to `notes/verify-<DATE>.md`.
CHECK: Rerun `tools\check.bat` or `python3 tools/build.py` on a clean checkout. Done means you report the check output and whether the diff touches `gen/`, expected hashes, seeds, regression cases, check scripts, or adds a handler that fakes success, or more interpreter use.
BUDGET: 45 minutes.
IF BLOCKED: Stop. Say what you could not run.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: check output. 3) Files changed (should be only the notes file). 4) Each problem with file and line. 5) New parking-lot items.

A good answer: Either a clean report with fresh check output, or a list of exact problems, such as a call that returns success with no work.

Status report

At the end of a day or a week, to see where the project stands.

Prompt for Claude
GOAL: Give me the status of <GAME> in numbers.
READ FIRST: `PROGRESS.md`, `notes/functions.csv`, `saves/recomp.log` from the last regression run, `notes/parking-lot.md`
FENCE: Read-only.
CHECK: Run `tools\check.bat` (Path A) or `python3 tools/build.py` (Path B) and paste the summary lines. Done means you report: coverage percent, interpreted share, open OS calls, regression cases passing out of total, and on Path B functions matched, tested and asm, all copied from files or fresh command output.
BUDGET: 15 minutes.
IF BLOCKED: Stop. Say which number you could not get.
OTHER PROBLEMS: None; list them in the report.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the numbers and where each came from. 3) Files changed: none. 4) Top three risks. 5) Parking-lot count.

A good answer: A short table of numbers with sources and three risks. No praise and no guesses.

Session handoff

Before you stop for the day or when the chat gets long.

Prompt for Claude
GOAL: Write what the next session needs into the ledger files.
READ FIRST: This session's work, `PROGRESS.md`, `notes/findings.md`
FENCE: Edit only `PROGRESS.md`, `notes/findings.md` and `notes/parking-lot.md`.
CHECK: Run `git diff --stat` and `git status`. Done means only the three files above changed, and PROGRESS.md has: what works now with the proving command, the unit in progress and its state, the next three units, and dead ends added to findings.md.
BUDGET: 15 minutes.
IF BLOCKED: Stop. Say what you could not confirm.
OTHER PROBLEMS: Add them to `notes/parking-lot.md`.
REPORT: 1) DONE, PARTIAL or BLOCKED. 2) Proof: the new PROGRESS.md section. 3) Files changed. 4) Next three units. 5) New parking-lot items.

A good answer: A fresh session can start from PROGRESS.md alone, with no need for this chat.

Blocked or cannot deliver

When a unit keeps failing, or a goal may not be possible, such as matching a Microware game.

Prompt for Claude
GOAL: Give me a plain answer on whether <GOAL> can be done, and what the options are.
READ FIRST: `SCOPE.md`, `docs/COMPILER.md`, `notes/findings.md`, the reports of failed tries
FENCE: Read-only, except a new section in `notes/findings.md`.
CHECK: Run the last failing check once more and paste its output. Done means you answer CAN, CANNOT or UNKNOWN, with the facts behind it, and list 2 or 3 options with their cost in time and what each gives up.
BUDGET: 30 minutes.
IF BLOCKED: Say UNKNOWN and what would decide it.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) CAN, CANNOT or UNKNOWN. 2) Proof: the facts. 3) Files changed. 4) Options. 5) New parking-lot items.

A good answer: A direct answer such as: CANNOT match without Microware C 3.2; options are tested C, finding the compiler, or stopping at the byte-exact assembly.

Scope change request

When Claude finds work outside the fence that seems needed.

Prompt for Claude
GOAL: Ask me before you leave the fence for <CHANGE>.
READ FIRST: `SCOPE.md`, the current task card
FENCE: Make no change. Write only the request.
CHECK: Run `git status`. Done means it shows no changed files, and you give me: the change, why the current unit needs it, the files it touches, the risk to the checks, and a smaller choice if one exists.
BUDGET: 10 minutes.
IF BLOCKED: Stop and say so.
OTHER PROBLEMS: Write them in `notes/parking-lot.md`.
REPORT: 1) BLOCKED (waiting for my answer). 2) Proof: the facts. 3) Files changed: none. 4) The request. 5) New parking-lot items.

A good answer: A short request you can answer yes or no, with a smaller option where one exists.

Keep Claude on task

These are the detours Claude tends to take on this system, and the rule that stops each one. The full method is on Stay on Task.

The detourThe rule to give Claude
Claude edits the generated C in gen/ to make a bug go away. The next build wipes the fix.Never edit gen/. Fix tools/gen.py, tools/analyze.py or runtime/, then regenerate. Add a hook that blocks edits there.
Claude makes an unknown OS call return success so the game moves on. The bug shows up much later.No stubs. An unknown call stops the run with its number and address. A handler must do the call's real work.
Claude lets more code fall back to the interpreter and reports the port as recompiled.Report the interpreted share in every report. A rise in interpreted code fails the unit unless I approved it.
Claude adds a game-specific entry to game.json or the runtime to hide a recompiler bug.game.json holds real facts about the game only. Fix the general bug in the tools, so every CD-i game gains.
Claude trusts Ghidra's draft C and copies its arguments. HotelMario-Decomp found wrong arguments in several drafts.Ghidra draft C is a guess. Check every argument against the assembly and give the address.
Claude copies runtime code from a public CD-i port that has no licence.Read repos with no licence for ideas only. Write your own code. Note any borrowed code and its licence in PROVENANCE.md.
Claude offers to find a BIOS, a Mac ROM or a compiler online to unblock a step.Never download a game, BIOS, ROM, compiler or disc image. Stop and ask me.
On Path B, Claude calls a function done because it compiles, or edits the expected SHA-1.Only the byte-identical line from python3 tools/build.py counts. Block edits to the hash file with a hook.

Problems other projects hit

Every file on the disc, the program too, has file number 1. Extraction that relies on file numbers grabs the wrong files.

Fix: Spot real-time streams by the sector submode bits: real-time 0x40 or Form 2 0x20.

Seen in: Kether, The Apprentice Recomp, Voyeur (CD-i) static recompilation, Zelda's Adventure, Flintstones/Jetsons: Timewarp

The game hangs at the Philips logo. It reads a tick counter straight from memory.

Fix: Build the system globals block at the address that the long at 0 points to, and keep D_Ticks current.

Seen in: Hotel Mario Decomp, Hotel Mario Static Recomp

A jump to an address the analyzer missed kills the recompiled build.

Fix: Teach analyze.py the pattern, or add the address to extra_entries with proof. Better: fall back to the interpreter and log it.

Seen in: Hotel Mario Static Recomp, Laser Lords static recomp, Zelda: Wand of Gamelon Static Recomp, Voyeur (CD-i) static recompilation

Code loaded into RAM from the disc, or code that rewrites itself, cannot be recompiled ahead of time.

Fix: Run it in the interpreter. List self-modifying ranges in game.json as interp_ranges.

Seen in: Kether, Hotel Mario Static Recomp, The Apprentice Recomp, Link: The Faces of Evil Static Recomp

Skipping an animation hangs the game. SS_Abort did not end the play the way CD-RTOS does.

Fix: End the play with error 0x060A (E$Abort) and send the play's signal.

Seen in: Kether, Flintstones/Jetsons: Timewarp

The Green Book names calls but leaves out many number codes.

Fix: Read the numbers from the game's library wrappers, such as the d2 value passed to I$SetStt. Mark each one seen only when a game proves it.

Seen in: Hotel Mario Static Recomp, Zelda: Wand of Gamelon Static Recomp

Sound is silent or effects are missing. The game streams effects into a looping sound map, and one port had the audio channel meaning backwards.

Fix: Read a looping sound map again on each pass (SD_Loop). Check what each channel field means against the Green Book.

Seen in: Zelda's Adventure, Hotel Mario Decomp

A PAL game runs at the wrong speed or draws the wrong number of lines.

Fix: Use 625 lines, 280-line fields and 50 fields a second for PAL. Set it per game in game.json.

Seen in: Zelda's Adventure, Zelda: Wand of Gamelon Static Recomp

Parts of the picture show as noise or in the wrong place. The game uses display commands the runtime ignored.

Fix: Run the MCD212 display tables from emulated RAM. Handle DC_NOP. Let plane registers be written only by their own channel.

Seen in: Kether, Voyeur (CD-i) static recompilation, Hotel Mario Static Recomp, The Apprentice Recomp

A Balboa game plays its intro, then ignores all input.

Fix: Emulate the cdi_bpsys ROM extension that handles the pointer and hot spots.

Seen in: Flintstones/Jetsons: Timewarp

A CD-i Ready disc stored as .chd keeps the CD-i track as byte-swapped audio, so the disc label is not found.

Fix: Swap the bytes back so the CD-i label sits at sector 16, as The Apprentice's tools/chd.py does.

Seen in: The Apprentice Recomp

There is no matching C for Microware-built games, because the compiler is not available.

Fix: Rebuild the module byte for byte from assembly first. Then write C checked by differential runs, and mark it tested, not matching.

Seen in: Hotel Mario Decomp

How long it takes

Max 20x (now called Ultra)Max 5x (now called Max)One person, no AIHow sure
Path A: running on PC2 to 8 weeks5 to 20 weeks6 months to 2 yearsmedium confidence
Path B: full decomp2 to 6 months4 to 18 months1 to 4 yearslow confidence

Read this first

These numbers assume one medium commercial game and one person giving 10 to 15 hours a week. Path A is medium confidence. Two AI-assisted projects have real dates (cdirecomp and zelda-cdi-recomp). The fast tools81 and HotelMario-Decomp ports hide or do not state their AI use and early history. None of the dated projects states its weekly hours or its plan. No pre-agent CD-i recomp exists, so the no-AI range is a guess from the steps and from CeDImu's pace. Path B is low confidence. Two MPW decomps are finished, but their real duration is hidden, and Microware games may never match.

What makes it faster or slower

  • Whether you reuse a design. The tools81 ports shipped ten games in about three weeks once the first one worked.
  • How many OS calls the game uses. Zelda's Adventure needed many more than Link, such as extra processes and events.
  • Extra hardware. The MPEG cartridge, a cdi_bpsys extension or PAL timing each add runtime work.
  • Code loaded at run time and self-modifying code. Each needs interpreter handling and more test runs.
  • The compiler, on Path B. MPW C 3.0 runs in a Mac emulator. The Microware compilers were not found.
  • Sister games. Wand of Gamelon reused 280 of 359 matched functions from Link.
  • 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

ProjectWhat happenedLink
HotelMario-DecompFirst commit 2026-10-19. Notes say the port reached playable stages in about two days. First release 2026-11-09. 75 commits, 1 contributor. AI use not stated.source
tools81 CD-i ports (10 games)Link and Mutant Rampage released 2026-10-21, then eight more games up to Voyeur on 2026-11-12, all from one template. One to four public commits each, so early work is hidden. AI flagged by the store only.source
cdirecomp (outside)First commit 2026-05-29. v0.0.1 on 2026-07-15. 146 commits by 2026-11-13. It has CLAUDE.md and AGENTS.md. Its README points to a Discord for AI-assisted retro work.source
HotelMarioRecomp (outside, on cdirecomp)First commit 2026-07-15. Release v0.0.3 on 2026-11-01, v0.0.4 on 2026-11-13. Still a research preview; full campaigns not verified after about four months.source
zelda-cdi-recomp (outside)First commit 2026-10-12. Private build 0.1.7 on 2026-10-29 with first areas of three games. 15 commits. Uses REA and Ghidra, has AGENTS.md.source
Link: The Faces of Evil decomp (outside)357 of 357 functions match with MPW C 3.0. Public commits 2026-10-23 to 2026-10-24 only, so the real duration is hidden. AI use not stated.source
Zelda: The Wand of Gamelon decomp (outside)One commit on 2026-10-24. 359 functions match, 280 of them reused unchanged from the Link decomp.source
CeDImu (outside, no AI, emulator)First commit 2018-11-08. 914 commits by 2026. Its README still calls it very early. Shows how slow one person's CD-i hardware work was before agents.source

Projects reviewed (11)

RecompLauncher lists fewer than 10 games for CD-i: 10 projects for 9 games. All 10 were reviewed. One project came from outside it (Voyeur), which makes 10 games. Eight more CD-i game repos were found, all for games already covered. Some of them appear here as outside evidence.

What each project teaches

Hotel Mario Static Recomp

Hotel Mario

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-10. First release: 2026-10. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: Reading the .stb symbols and the split data module raised coverage from 21%. Runs were checked bit for bit against an interpreter-only run over 10,000 fields.

Link: The Faces of Evil Static Recomp

Link: The Faces of Evil

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/gen.pytools/analyze.pyMSVC 2022CMakeDirect3D 11

Started: 2026-10. First release: 2026-10. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: The first port, and the template for the rest. It used MacsBug names for free and tested every instruction against an interpreter.

Zelda: Wand of Gamelon Static Recomp

Zelda: The Wand of Gamelon

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-10. First release: 2026-10. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: Per-game facts live in game.json, so one set of tools serves many games. Its README lists no tested screens, so check before you trust it.

Kether

Kether

Top rated on RecompLauncherstatic recompplayable
Python 3tools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-11. First release: 2026-11. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: Two self-modifying sprite loops had to run in the interpreter. --hash then --btrace found the first block where runs differed.

Mutant Rampage: Bodyslam Static Recomp

Mutant Rampage: Bodyslam

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/gen.pyMSVC 2022CMakeDirect3D 11pl_mpeg

Started: 2026-10. First release: 2026-10. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: The MPEG video cartridge can be stood in for with a small MPEG-1 decoder. Loader-to-game chaining needed F$Chain.

The Apprentice Recomp

The Apprentice

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/chd.pytools/gen.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-11. First release: 2026-11. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: ROM trap libraries like cio on trap #13 must be emulated too. A CD-i Ready disc in a .chd needs its bytes swapped back.

Voyeur (CD-i) static recompilation

Voyeur

From outside RecompLauncherstatic recompplayable
Pythontools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-11. First release: 2026-11. Code: C and Python.

AI: none stated for coding; an optional AI upscaler makes an HD video pack

Lesson: The release runs the disc's program in the interpreter, so it ships no game code. Unknown jumps fall back to the interpreter instead of crashing.

Hotel Mario Decomp

Hotel Mario

Lower rated on RecompLaunchermatching decompin progress
MusashivasmCapstoneGhidra 10.4 headlessMinGW-w64 gccSDL2 2.32.10

Started: 2026-10. First release: 2026-11. Code: C and Python.

AI: none stated Agent files: .claude/launch.json.

Lesson: The module rebuilds byte for byte from assembly, but its C cannot match without Microware C 3.2. It shows the honest route: tested C, clearly marked.

Flintstones/Jetsons: Timewarp

The Flintstones/The Jetsons: TimeWarp

Top rated on RecompLauncherstatic recompplayable
Pythontools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-11. First release: 2026-11. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: Reading the separate data module took coverage from 47% to 90%. A Balboa ROM extension had to be emulated before input worked.

Laser Lords static recomp

Laser Lords

Top rated on RecompLauncherstatic recompplayable
Python 3.8+tools/gen.pytools/analyze.pyMSVC 2022CMakeDirect3D 11

Started: 2026-11. First release: 2026-11. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: New analyzer patterns raised coverage from 41% to 94%. CPU speed had to be set from a recording of the real game, not left at the template value.

Zelda's Adventure

Zelda's Adventure

Top rated on RecompLauncherstatic recompin progress
Python 3.8+tools/gen.pytools/analyze.pyMSVC 2022CMake 3.19+Direct3D 11

Started: 2026-10. First release: 2026-10. Code: C and Python.

AI: yes: flagged for code by the store; the repo says nothing

Lesson: A game that runs a second process needs a fixed-order scheduler. PAL timing and streamed sound maps both had to be added.

What we could not check

  • How much AI the tools81 ports used. Only the store flags them; the repos say nothing and show one commit each.
  • Whether HotelMario-Decomp or the ByteDecompiler decomps used AI. None of them says.
  • How long the two ByteDecompiler matching decomps really took. Their public history is one to three commits.
  • Whether a Microware OS-9 C 3.2 or Ultra C compiler can be found today, or whether os9exec can run one.
  • Whether ghidra-os9 builds against Ghidra 12.2. Its last change was in 2022.
  • GhidraMCP 2.0 targets Ghidra 12.1.4, while the newest Ghidra is 12.2. Mixing them was not tried.
  • Whether chdman extractcd handles the byte-swapped track of a CD-i Ready disc. The Apprentice wrote its own reader for this.
  • Whether asm-differ works with these OS-9 modules. No reviewed CD-i project used it.
  • The scripts tools\check.bat, tools\regress.bat, tools/diffrun.py, tools/compile_one.sh and the headless flag names are suggestions modeled on the tools81 READMEs. You write them.
  • Which CD-i BIOS each emulator needs for a given game. Never download one; dump your own player.
  • The tools81 repos and HotelMario-Decomp state no licence, so reusing their code is not allowed by default.
  • The CD-i release year (1991) and CPU clock (15.5 MHz) come from Wikipedia only.
  • The winget line with --override for the C++ workload was put together from Microsoft docs. It was not run during this check.
  • Whether the winget MAME package includes chdman. The MAME docs list it as a tool that comes with MAME.
  • No step, prompt or script in this guide was run on a real CD-i disc during the check. Commands were checked against each tool's docs only.
  • The Voyeur card says its release date is unknown. The live repo shows release v1.0.0 on 2026-11-12, so the guide uses that.

Sources