- 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>
12 KiB
Audit Inventory — psxrecomp-v4
Baseline as of 2026-05-12. Captures what audit / test tooling exists, what scope it covers, what each tool currently reports, and where the gaps are.
This document is the input for Phase B (codegen-audit expansion) and Phase C (L2-semantic execute tier).
Tier structure
Layer 5 Runtime behavioral differential vs Beetle (continuous RAM diff) NONE
Layer 4 Hardware-sim conformance (SIO/DMA/MDEC/GTE vs no$psx) PARTIAL
Layer 3 Codegen / discovery audits (static analysis of generated C) PARTIAL
Layer 2b L2-semantic — execute emitted code and verify register state NONE
Layer 2a L2-structural — emit pattern matching on generated C HAVE
Layer 1 Per-instruction decode correctness vs Rabbitizer HAVE
Layer 0 Build & link integrity HAVE
Tools inventory
Layer 1 — Decode conformance
recompiler/tests/l1_decoder_test.cpp (built: recompiler/build/l1_decoder_test.exe)
- Scope: BIOS only. Code regions Boot
0x00000-0x0DC60, Kernel0x10000-0x16760, Shell0x18000-0x42800. - Compares
MipsDecoder::decode()mnemonic vs Rabbitizer for every word in those regions. - Buckets mismatches by
oracle_mnem -> our_mnem.
Baseline result: widespread mismatches. Notable buckets:
| Count | Rabbitizer says | We say | First addr | Comment |
|---|---|---|---|---|
| 181 | cache |
??? |
0xBFC04300 | Real gap. CACHE (op 0x2F) is an R3000A instruction we don't decode. |
| 31 | mvmva |
gte |
0xBFC34FF8 | Granularity. We map all GTE cmds to generic gte. |
| 13 | rtpt |
gte |
0xBFC35DF0 | Same. |
| 13 | nclip |
gte |
0xBFC361B4 | Same. |
| 11 | tge |
special? |
0xBFC10190 | R4000 trap — likely data-in-code or kernel trap encoding. |
| 10 | dadd |
special? |
0xBFC101D4 | 64-bit (R4000+), likely data. |
| 9 | rtps |
gte |
0xBFC35DAC | GTE granularity. |
| ~ | mov.d, mtc1, lwc1, add.d |
??? |
0xBFC02358 | FP — R3000A has no FPU; likely data-in-code in Boot region. |
Two distinct classes:
- Real decode gap:
cache(op 0x2F). 181 sites. Decoder needs a case. - Granularity gap: GTE sub-mnemonics. Not a correctness bug — our
emit treats all GTE cmds via
gte_execute. But audit clarity benefits from matching Rabbitizer's specificity. - False positives (data-in-code): dadd, dmult, dsll32, mov.d, mtc1, c.eq.d, etc. These are R4000+ / FPU instructions that don't exist on R3000A. They appear because we scan the entire region without skipping inline data or jump tables. Filter improvement, not a decoder bug.
Layer 2a — Structural emit conformance
recompiler/tests/l2_structural_test.cpp (built: l2_structural_test.exe)
- For each MIPS instruction form, build word, translate via
StrictTranslator, verify emitted C contains expected patterns (e.g.,gpr[2],gpr[4],<< 1u). - Snesrecomp-style. Note in source: "does NOT verify semantic correctness (that requires L2-semantic with actual execution)."
Baseline result: 42/42 ok.
Gap: 42 forms / ~150 R3000A instructions ≈ 28% coverage. No coverage for: load-delay-slot semantics, mult/div HI-LO timing, LWL/ LWR/SWL/SWR unaligned partial-word, JALR same-source/dest, shift-by-32 edge case, COP0 RFE / SR-bit semantics.
Layer 2b — Semantic execute conformance
Status: does not exist.
The gap. To know "does our emit do the right thing when run", we'd:
- Build a tiny test harness that takes MIPS bytes + initial CPUState, runs the recompiler → C → execute, returns final CPUState.
- For each instruction, supply known input registers and assert output matches the R3000A spec.
- Cross-check against mednafen-PSX's CPU as a reference impl.
This is Phase C.
Layer 3 — Codegen / discovery audits
tools/_codegen_audit.py (BIOS only)
- 6 passes against
generated/SCPH1001_full.c+_dispatch.c:- Predicate-init audit (verifies 2026-05-04 fix).
- Direct
psx_dispatch(cpu, 0xXXXX)targets in dispatch table. - Indirect
psx_dispatch(cpu, gpr[N])distribution. - Tail-call
cpu->pc = ...; return;targets in dispatch table. - Chained-branch-with-delay-slot-label population.
- Highest-frequency missing dispatch targets.
Baseline result:
- Predicate decl audit:
buggy=0 zero_init=6104 assigns=6104✓ - Direct psx_dispatch: 796 unique, 0 missing ✓
- Indirect sites: 88 (across many registers)
- Tail-call missing: 1 site →
0x00000CF0— the install-at-runtime SIO data-byte handler stub. Expected, handled by dirty-RAM interpreter. Not a regression. - Chained-branch with delay-slot label: 27 sites, all protected ✓
tools/gte_audit.py (BIOS only)
- Scans ROM for COP2/GTE instructions, compares against generated code.
Baseline result: "missing" hits in 0xBFC75xxx..0xBFC7Fxxx. These
are in the BIOS DATA region (code ends at 0xBFC42800). All false
positives from scanning data bytes that happen to decode as COP2 ops.
Need to filter by code region.
tools/scan_branch_delay_hazards.py (BIOS only)
- For every conditional branch in walked code, checks whether the delay slot writes to the branch's rs/rt operand.
- Verifies
strict_translator.cppuses the snapshot pattern.
Baseline result:
- Branches scanned: 4617
- Delay slot writes branch operand: 441 (intrinsic, informational)
- Incorrect in current translator: 0 ✓
- Delay-slot unreachable in walk: 0 ✓
tools/check_dispatch.py — one-off probe with hardcoded address
list. Not generally useful. Skip from active inventory.
Layer 4 — Hardware-sim conformance
runtime/tests/test_sio_card_protocol.c
- Drives
sio.cTX/RX state machine through a 137-byte memcard READ command, asserts each RX byte matches no$psx. - Not run as part of this Phase A pass (built but not invoked here).
Gap: other MMIO regions — DMA, MDEC, GPU command stream, timers, IRQ controller — have no protocol conformance tests.
Layer 5 — Runtime differential
Does not exist. Both psx-runtime (port 4370) and psx-beetle (port 4380) speak the same wire protocol, so the infrastructure for paired wtrace queries is present; what's missing is the diff-and-cluster tool itself.
Tool generalization status
All five Layer 1-3 tools are hardcoded to BIOS paths. They were written during Phase 1-3 BIOS bring-up and have not been generalized to game code. For Phase B, generalize them via:
- Argument:
--full-c PATH --dispatch-c PATH --rom PATH(or read fromgame.toml-style config). - Run each tool once for BIOS, once for Tomba.
Phase A findings — what to do with them
| Finding | Class | Priority | Phase |
|---|---|---|---|
L1: cache (op 0x2F) not decoded — 181 sites |
Real decoder gap | High | Phase B |
| L1: GTE sub-mnemonic granularity | Audit clarity | Low | Phase B |
| L1: FPU / 64-bit FPs in data regions | Audit filter | Low | Phase B |
| L2: 42 forms / ~150 instructions = 28% coverage | Test gap | High | Phase C |
| L2-semantic tier does not exist | Architectural | High | Phase C |
| All audits BIOS-only | Tool scope | High | Phase B |
| gte_audit.py false-positives in data region | Audit filter | Low | Phase B |
| Tail-call miss at 0x00000CF0 | Expected stub | None | — |
Progress log
Phase B2 (2026-05-12) — First audit pass against Tomba
Ran all three Python audits against Tomba's SCUS_942.36. Mixed
outcome — tool gaps surfaced and real bugs surfaced.
Initial run findings:
codegen_audit.pyreturned all zeros — its regexes are BIOS-emit- specific and don't match the gamefull_function_emittershape (directfunc_X(cpu)calls, switch-based dispatch,_bc_Xbranch conds). Required a sibling tool.gte_audit.pyreported 29 findings in Tomba text segment (8 GTE + 21 LWC2/SWC2). See TombaRecomp ISSUES Issue #2.scan_branch_delay_hazards.pycorrectly skipped walker (no function_starts file for Tomba) and ran translator-source check.
codegen_audit_game.py created (per the principle: separate tool
sharing patterns only at the margins). Audits the 6 passes that
matter for game-emit style. After fixing my dispatch-table regex
(Tomba uses case 0xADDRu: func_X(cpu); return 1; not BIOS's array
form) and adding code-region filtering for call_by_address literals
(331 of 333 are RAM-loaded overlays handled by dirty_ram_dispatch,
expected):
| Pass | Result |
|---|---|
| 1. Direct func_X(cpu) calls | 2,973 calls, 840 unique → all resolve ✓ |
| 2. Literal call_by_address | 333 unique → 2 in-code misses ⚠ |
| 3. Indirect call_by_address | 360 sites (informational) |
| 4. Tail-call cpu->pc=X | 0 sites (game emit doesn't use this idiom) ✓ |
| 5. Branch-cond declarations | 6,708 (informational) |
| 6. goto block_X resolution | 20,172 sites, all resolve ✓ |
Real bug surfaced: Issue #5 — mid-function split targets not
registered in dispatch table. Three emit sites in
code_generator.cpp (lines 970, 981, 998) emit call_by_address for
mid-func continuations without calling register_cross_function_target.
This is the same bug class as today's jump-table fix. Tomba has 2
manifestations (0x800905E4, 0x80090600 inside func_800905DC).
B2's first pass against Tomba found a real recompiler bug.
Phase B0 (2026-05-12) — Audit-config schema
Defined a TOML schema for audit configs. Both BIOS and games consume
the same shape; field names are tolerant ([program] or [game],
rom or exe).
Artifacts:
tools/audit_config.py— shared loader.tools/verify_audit_schema.py— schema validator.bios/SCPH1001.toml— BIOS config (3 regions, 2 address remaps).TombaRecomp/game.toml— extended with[audit]block (1 region, 0 remaps).
Phase B1 (2026-05-12) — Python audit generalization
Migrated three Python audits to consume the audit-config schema. All three pass BIOS regression with output matching the Phase A baseline.
| Tool | Status | BIOS regression |
|---|---|---|
tools/codegen_audit.py |
migrated | identical to Phase A (CLEAN, 0 real findings) |
tools/gte_audit.py |
migrated | 73 false-positives eliminated by code-region filter; 7 real findings surfaced |
tools/scan_branch_delay_hazards.py |
migrated | identical to Phase A (CLEAN); also runs translator-source check for non-BIOS configs that lack a function_starts file |
recompiler/tests/l1_decoder_test.cpp |
deferred | C++ TOML parsing not yet wired; the test still runs against hardcoded BIOS kRegions[]. Generalize in a later sub-phase. |
recompiler/tests/l2_structural_test.cpp |
not needed | no per-program input; already generic. |
Side effects of generalization:
gte_audit.pysurfaced 7 previously-masked findings in Shell code: 4 missinggte_executeemits at0xBFC34FF8,0xBFC3502C,0xBFC35064,0xBFC350C4(all0x4A480012= MVMVA), plus 3 missing LWC2/SWC2 emits. None of these PCs appear ingenerated/SCPH1001_full.c— either a discovery gap (function containing them not recompiled) or an emit gap. Investigate in Phase B3 or B4.
Methodology going forward
Every new bug class encountered should follow:
- Reproduce / characterize the class (not the single instance).
- Build the audit FIRST — find every instance of the class in the codebase via tooling, not by manual repro of each.
- Fix the underlying recompiler / runtime issue.
- Re-run the audit, confirm class is now empty.
- Add the audit to the standing audit suite so the class can't regress.
The audit suite forms the regression net: "0 findings, audited N sites" is the only acceptable state for a known class.