- Config loader (recompiler/src/config_loader.{h,cpp}): single source of
truth for both BIOS and game runtime settings (debug_port, window_title,
memcard_dir, disc path). psxrecomp-bios and psxrecomp-game both accept
--config <toml>; runtime takes --game <toml> (no more CLI overrides).
- runtime/src/main.cpp: replaced --disc/--memcard-dir/--game-root CLI
overrides with --game <toml>; game.toml is the single source of truth.
Discless launch (no args) goes to BIOS shell.
- runtime/src/dma.c: fixed update_master_irq() re-asserting I_STAT bit 3
on every DICR read. BIOS shell DMA handler at 0x80058628 only clears
DICR bit 27 (ch3 flag), so level-sensitive re-assert pinned the kernel
in the exception loop. Bit 3 is now edge-triggered (one assert per DMA
completion). Discless boot now reaches MAIN MENU / MEMORY CARD / CD
PLAYER. With-disc Tomba boots through to Whoopee Camp splash.
- Audit tooling: codegen_audit.py (BIOS), codegen_audit_game.py (game),
audit_config.py (config loader for tools), verify_audit_schema.py.
Replaces older tools/_codegen_audit.py probe.
- Docs: CURRENT_STATE.md (project state), UNIFICATION_PLAN.md (Phase C
translator merge plan), docs/config_schema.md (TOML schema reference),
docs/audit_inventory.md (audit tier inventory).
- .gitignore: tools/_*.py convention for ad-hoc scratch probes.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
8.6 KiB
UNIFICATION_PLAN — one recompiler, one runtime, TOML-driven
Start date: 2026-05-12 (post-validation-confusion session)
Read first
Before any work in this plan, read CURRENT_STATE.md end-to-end. The
happy-path binary lives at .happypath/tomba-runtime.exe. Every step in
this plan ends with a checkpoint that confirms it still works.
Target state
# ONE recompiler binary
psxrecomp bios/SCPH1001.toml # produces generated/SCPH1001_*.c
psxrecomp games/tomba/game.toml # produces tomba/generated/*.c
# (implicitly also ensures BIOS regen is current)
# ONE runtime binary, parameterized at link time
psx-runtime # discless BIOS only — boots to memcard / CD player
psx-runtime tomba/game.toml # with game — boots BIOS → Tomba → main menu
psx-runtime games/ff7/game.toml --disc 2 # multi-disc — swap to disc N
- Recompiler: one binary, reads a TOML, dispatches to BIOS-path or
game-path based on the config's
[program] type = "bios" | "game". - Runtime: one binary. Always includes BIOS recomp output. Conditionally
includes game recomp output. If
[game]is in the config, runtime calls out to game entry point after BIOS shell finishes its boot sequence. - Multi-disc:
[discs]array in game.toml; runtime exposes a disc-swap command on the debug port.
Why we want this
Listed roughly in priority:
- Forces translator unification. Today
strict_translator.cpp(BIOS) andcode_generator.cpp(game) are parallel implementations that have diverged. Bugs fixed in one don't apply to the other. Memorystrict_translator_stays_strict.mddirects that strict wins; code_generator should be absorbed into strict, not the reverse. - Config-driven everything.
bios/SCPH1001.tomlandTombaRecomp/game.tomlalready exist but are only used by audit tools. The recompiler should read them, not take positionalseeds.jsonarguments. - Always boots BIOS. Today
psx-runtimeis the BIOS-only test runner andtomba-runtimeis the BIOS+game runner — buttomba-runtimeIS the discless BIOS path too (it boots BIOS first; you can in principle ignore the game). One runtime, two modes. - Multi-disc falls naturally out of a
[discs]config array. Today's[game] disc = "tomba.cue"is a special case of a one-element array. - Stop the two-process confusion. Memory
architecture_two_processes_2026_05_05.mdnotes we already havepsx-runtime+psx-beetle(oracle) as parallel processes. Addingtomba-runtimeas a third was a mistake we're cleaning up.
What stays separate
- The Beetle oracle (
psx-beetle.exe) stays its own process. It's an independent reference implementation; unifying it would defeat its purpose.
Incremental phases
Each phase has a checkpoint that must pass before starting the next.
The checkpoint is always: .happypath/tomba-runtime.exe still boots and
reaches Tomba's main menu (or equivalent verification that the working
state has not been broken).
Phase A — Config consolidation (low risk)
Goal: make both existing recompilers read their inputs from TOML, while
keeping --emit-full and friends working as backwards-compatible aliases.
No translator code touched.
A1. Define the unified TOML schema. Today there are two files:
-
bios/SCPH1001.toml— has[program],[audit]. Add[recompiler]with seeds path, output dir. -
TombaRecomp/game.toml— has[game],[recompiler],[runtime],[audit]. Already richer.Unify into one schema with a top-level
type = "bios" | "game"discriminator.
A2. Teach psxrecomp-bios.exe to accept --config <path>.toml as an
alternative to positional ROM+seeds args. Falling-through to existing
positional behaviour if no --config. Verify regen output is byte-for-byte
identical with both invocation styles.
A3. Teach psxrecomp-game.exe the same.
A4. Update CLAUDE.md, PLAN.md, and the helper cmake macro to use the config-style invocation.
Checkpoint A: .happypath/tomba-runtime.exe still boots (untouched);
fresh rebuild of tomba-runtime.exe via the new TOML invocation produces
identical output to a fresh rebuild via the old positional invocation.
Phase B — Runtime consolidation (medium risk)
Goal: collapse psx-runtime + tomba-runtime into one parameterized
runtime. After this phase, psx-runtime is gone as a separate target;
tomba-runtime is renamed to psx-runtime and takes an optional game
config.
B1. Identify the cmake macro psxrecomp_v4_add_runtime_target and audit
how psx-runtime (in this repo) and tomba-runtime (in TombaRecomp)
diverge. They probably mostly agree, but with paths and link inputs
parameterised differently.
B2. Define the merged target shape. Inputs:
- BIOS C source: always
generated/SCPH1001_*.c(this repo). - Game C source (optional): path from
[game] generated_dir. - Game config TOML: embedded as a runtime-readable resource (or path passed at runtime).
B3. Migrate tomba-runtime to be built via the merged macro, with
the game inputs supplied. Do not touch the old tomba-runtime.exe
on disk yet — build the new one alongside, verify it boots, then
swap.
B4. Migrate psx-runtime to be built via the merged macro, with no
game inputs. Confirm discless BIOS boot (or document the existing
psx-runtime-can't-boot-shell bug separately).
Checkpoint B: rebuilt-via-new-macro tomba-runtime.exe boots to
Tomba's main menu identically to .happypath/tomba-runtime.exe.
Phase C — Recompiler translator unification (high risk)
This is the big one. Merge strict_translator.cpp + code_generator.cpp
into a single translator. Memory strict_translator_stays_strict.md
directs that the strict approach wins; code_generator content is
absorbed in.
C1. Inventory both translators' supported opcodes. For each, mark:
- supported on both sides (likely most)
- supported only in strict (BIOS-specific, e.g. CACHE, RFE handling)
- supported only in game (whatever Tomba uses that BIOS doesn't)
- handled differently between the two
C2. For divergences, decide which version wins:
- Hard rule: no fall-through cases sneak in (memory rule).
- Hard rule: no stubs (CLAUDE.md rule 0).
- For each divergence, document the resolution in
docs/translator_unification.mdso this audit is reviewable.
C3. Migrate the merged translator behind a feature flag. Build both binaries (psxrecomp-bios and psxrecomp-game) against the new translator in parallel with the old, run both regens, diff the output.
C4. When outputs match, flip the flag, delete the old translator.
C5. Rename psxrecomp-bios.exe and psxrecomp-game.exe → psxrecomp.exe.
Single binary, dispatches by config type.
Checkpoint C: same regen output, byte-for-byte, before and after the
flip. .happypath/tomba-runtime.exe checkpoint still passes when
relinked against the new regen output.
Phase D — Multi-disc support
Goal: support [discs] paths = [...] in game.toml and runtime
disc-swapping. Not blocking on game work; can land any time after Phase B.
D1. Schema: [game] learns discs = ["disc1.cue", "disc2.cue", ...].
For backwards compatibility, accept either disc = "x.cue" (single)
or discs = [...] (multi).
D2. Runtime exposes a swap_disc <N> debug command. CD-ROM subsystem
gates on which disc is currently mounted.
D3. Boot-time: mount disc 0 by default.
D4. Test on Tomba (single disc) to confirm no regression, then on a real multi-disc game (FF7 / RE2 — TBD).
Rollback / abort criteria
At any phase, if the checkpoint fails:
- Restore
.happypath/tomba-runtime.exeto its build location:cp .happypath/tomba-runtime.exe \ F:/Projects/TombaRecomp/build/tomba-runtime.exe - Stop work, update
CURRENT_STATE.mdwith what was learned, and surface the blocker to the user before retrying.
Decisions made 2026-05-12
- BIOS config location: extend
bios/SCPH1001.tomlin place. Add[recompiler]and[runtime]sections to the existing file. The[program]and[audit]blocks already there stay. - Game configs: stay in sibling game repos (e.g.
TombaRecomp/game.toml). Matches existing sibling-junction +.pinpattern shared with nesrecomp / segagenesisrecomp. - Runtime name: keep
psx-runtime. Becomes parameterized; the sibling-repotomba-runtimetarget retires and the merged binary is invoked aspsx-runtime <game.toml>(optional). - Multi-disc test target: deferred. Phase D designs the
[discs] = [...]schema; we pick a concrete game (FF7 / RE2 / MGS) when we're actually ready to validate disc-swapping.