mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
e6ce5006b4
The repository root carried seven markdown files that were not onboarding
material, plus two that had been superseded by the code they described. This
sorts them: reader-facing docs to docs/, working notes to docs/internal/, and
the two superseded ones merged into the docs that replaced them.
Moved to docs/: WIDESCREEN.md, ENHANCEMENTS.md, TCP_COMMANDS.md
Moved to docs/internal/: PRINCIPLES.md, ISSUES.md, DEBUG.md, GTE_LM_FIX.md,
and from docs/: bios-hardening.md, overlay-status.md,
overlay-plan.md, overlay-recompilation-design.md,
overlay-discovery.md, TOMBA2_EXC_CONSULT_RESPONSE.md,
CASE_A_AOT_GAP.md, audit_inventory.md
Moved to docs/internal/upstream/: UPSTREAM_2026-08-20.md
The root keeps only what belongs there: README.md, CONTRIBUTING.md, LICENSE,
CLAUDE.md, THIRD_PARTY_ATTRIBUTION.md, RELEASE_NOTES.md, the dotfiles and
psxrecomp_cli.py.
MERGES
SPLITGEN_MIGRATION.md folded into docs/BUILDING.md as "Split generated C". Its
migration instructions were stale in a way that mattered: it told every game
repo to hand-edit its CMakeLists and add a file(GLOB ...) over
<serial>_full*.c, but runtime/runtime.cmake now does that itself. GEN_FULL_GLOB
is a multi-value argument of psxrecomp_add_game_runtime(); the loop at
runtime.cmake:1985 makes each pattern absolute against CMAKE_CURRENT_SOURCE_DIR,
expands it with file(GLOB), and concatenates the hits into
GAME_GENERATED_FULL_C. So a title names the pattern and gets however many shards
a regeneration produced. The rewritten section describes that, the two-step
fallback when the globs match nothing (GEN_FULL_FALLBACK, else <serial>_full.c
derived from the dispatch marker by substituting _dispatch.c), and the one
caveat the code actually has: that file(GLOB) is plain, not CONFIGURE_DEPENDS,
so a regeneration which changes the shard COUNT needs a re-configure to be seen.
psxrecomp-toml-readme.md folded into docs/GAME_PROJECT_SETUP.md as "EXE-only
autofill (psxrecomp-toml)", placed after "Disc autofill (probe_disc.py)" and
before "Project Studio" so the two autofill paths sit together. Its documented
flags were checked against the usage text in recompiler/src/main_toml.cpp:28-42
and against the argument parser at :150-165 -- all seven options match with no
drift, so the flag list is reproduced as-is. Added a pointer that probe_disc.py
is preferable when a cue exists, since that tool does the same EXE-header work
plus disc identity and netplay gates.
FUNCTIONAL FIXES
tools/gen_tcp_commands.py wrote to REPO/"TCP_COMMANDS.md". After the move that
path does not exist, so the generator would have created a new stray root file
and --check would have failed against it forever. Retargeted to
REPO/"docs"/"TCP_COMMANDS.md", along with the seven docstring and message
strings that name the path.
Running it surfaced two further problems, both fixed here:
* The doc was genuinely stale -- pre-existing drift, not introduced by this
commit (the moved file is byte-identical to origin/master, and no
debug-server command table is touched here). Four commands registered since
the last regeneration were missing from the index -- cdrom_timing_dump,
savestate_status, ws_ui_groups, xprobe_watch -- and scanline had gained a
backend. Regenerated: 307 -> 311 commands, and the change is confined to the
AUTOGEN block (the hand-written head and tail are byte-identical to master).
tools/gen_tcp_commands.py --check now exits 0.
* DOC.write_text() has no newline argument, so Python translated every newline
to os.linesep. On Windows that rewrote all 585 lines of the file to CRLF and
turned a four-line index update into a whole-file diff -- measured: 589 CRLF
/ 0 LF after a run, against 0 CRLF / 585 LF in the repo. Now written through
an explicit newline="" handle, verified to leave the file at 0 CRLF.
runtime/include/debug_server.h:9 and runtime/src/debug_server.c:8 cited "TCP.md
and DEBUG.md". TCP.md has never existed in this repo under that name; both now
name docs/TCP_COMMANDS.md and docs/internal/DEBUG.md.
LINK AND REFERENCE FIXES
Every inbound reference to a moved file was located by grepping each basename
across the whole tracked tree, not just the markdown. Markdown hrefs in
README.md, CONTRIBUTING.md, docs/ARCHITECTURE.md, docs/COMPILING_OVERLAYS.md,
docs/AOT_OVERLAY_PLAN.md, docs/RELOCATION_MANIFEST_FORMAT.md,
docs/config_schema.md, docs/OVERLAY_XREF.md, docs/TCP_COMMANDS.md and several
docs/internal/ files now point at the new locations; where two files became
siblings under docs/internal/ the path was shortened rather than lengthened
(bios-hardening.md's reference to overlay-discovery.md, for instance).
docs/COMPILING_OVERLAYS.md's "in this folder" phrasing was wrong once two of the
three files it listed moved, so it now names internal/ explicitly.
24 source-comment mentions of ENHANCEMENTS.md sections G1.x across 19 files
(recompiler, runtime, cmake) were retargeted to docs/ENHANCEMENTS.md with the
section numbers untouched. docs/SHADOW_ENHANCEMENTS.md is a DIFFERENT file and
was deliberately excluded.
20 rationale strings citing docs/CASE_A_AOT_GAP.md across the four
recompiler/seeds/*.json files were updated. Done as a byte-level substring
replacement rather than a JSON round-trip: two of those files are stored with
CRLF endings and two with LF, and re-serializing them reformatted 5,700 lines.
The byte-level version changes exactly 20 lines and each file still parses.
References that look similar but point elsewhere were deliberately left alone,
and are worth naming so a future pass does not "fix" them:
* "recomp-template PRINCIPLES.md" / "NES recomp template PRINCIPLES.md" in
CLAUDE.md:43, docs/SHADOW_ENHANCEMENTS.md:28,
docs/internal/HLE_SCHEDULER_CARVEOUT_PLAN.md:5,
runtime/src/debug_server.c:4934 and tools/parity_diff.py:8 are a different
repository's file. Only the five that mean this repo's PRINCIPLES.md were
updated.
* Every remaining bare ISSUES.md mention is a GAME repo's tracker
(MegaManX6Recomp 2/7/8/9, TombaRecomp 6), not this repo's.
* docs/disc-speed.md:4,54,56 and docs/internal/bios-hardening.md:5 name
overlay-discovery as a BRANCH, not a file, so no path applies.
INDEXES
README.md's "New here?" table gains rows for docs/WIDESCREEN.md,
docs/ENHANCEMENTS.md and docs/TCP_COMMANDS.md. docs/internal/README.md gains
sections indexing all fourteen documents that moved in, plus an "Upstream
contributor reviews" section describing upstream/ and the new sweep file, plus a
"Removed" bullet for CYCLE_TIMING_ARCH.md (deleted in the previous commit)
pointing at FAITHFUL_TIMING_PLAN.md, CYCLE_MODEL_BEETLE.md and
PRECISE_IRQ_SLICE.md -- following the SLJIT_PERSIST_CACHE.md precedent already
in that file.
docs/ENHANCEMENTS.md:7 claimed framework bugs live in "game repos' ISSUES.md
until a framework tracker exists". A framework tracker does exist; it now says
so and names the Beads epic.
VERIFICATION
A link checker was written to scan every tracked *.md for relative links,
resolving each against the tree being scanned. Baseline at origin/master: 129
files, 137 relative links, 3 broken. After both commits: 127 files, 159 relative
links, 3 broken -- the SAME three, all pre-existing and untouched here:
docs/DATA_SHARDS.md -> INSTANT_LOADS_PARKED.md (never existed)
docs/DATA_SHARDS.md -> INSTANT_LOADS_SPIKE2.md (never existed)
docs/NETPLAY.md -> ../lib/recomp-net/README.md (inside a submodule)
Zero new broken links. The checker's own coverage was checked against a raw grep
for markdown link openers: the 3-link difference is C++ lambdas inside code
blocks, correctly not treated as links.
Also verified: py_compile over all 113 tracked tools/*.py plus psxrecomp_cli.py;
all four seed JSONs still parse; the test-registration guard still reports 123
test files all registered; recompiler/tests/test_coverage_vault.py still passes;
and no file in this commit had its line endings flipped (checked mechanically
across all 50 modified files, after two files were caught and redone).
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
253 lines
12 KiB
Markdown
253 lines
12 KiB
Markdown
# 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`, Kernel
|
|
`0x10000-0x16760`, Shell `0x18000-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:
|
|
1. Build a tiny test harness that takes MIPS bytes + initial CPUState,
|
|
runs the recompiler → C → execute, returns final CPUState.
|
|
2. For each instruction, supply known input registers and assert output
|
|
matches the R3000A spec.
|
|
3. 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`:
|
|
1. Predicate-init audit (verifies 2026-05-04 fix).
|
|
2. Direct `psx_dispatch(cpu, 0xXXXX)` targets in dispatch table.
|
|
3. Indirect `psx_dispatch(cpu, gpr[N])` distribution.
|
|
4. Tail-call `cpu->pc = ...; return;` targets in dispatch table.
|
|
5. Chained-branch-with-delay-slot-label population.
|
|
6. 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.cpp` uses 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.c` TX/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 from
|
|
`game.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.py` returned all zeros — its regexes are BIOS-emit-
|
|
specific and don't match the game `full_function_emitter` shape
|
|
(direct `func_X(cpu)` calls, switch-based dispatch, `_bc_X` branch
|
|
conds). **Required a sibling tool.**
|
|
- `gte_audit.py` reported **29 findings** in Tomba text segment
|
|
(8 GTE + 21 LWC2/SWC2). See TombaRecomp ISSUES Issue #2.
|
|
- `scan_branch_delay_hazards.py` correctly 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.py` surfaced 7 previously-masked findings in Shell code:
|
|
4 missing `gte_execute` emits at `0xBFC34FF8`, `0xBFC3502C`,
|
|
`0xBFC35064`, `0xBFC350C4` (all `0x4A480012` = MVMVA), plus
|
|
3 missing LWC2/SWC2 emits. None of these PCs appear in
|
|
`generated/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:
|
|
1. Reproduce / characterize the class (not the single instance).
|
|
2. **Build the audit FIRST** — find every instance of the class in the
|
|
codebase via tooling, not by manual repro of each.
|
|
3. Fix the underlying recompiler / runtime issue.
|
|
4. Re-run the audit, confirm class is now empty.
|
|
5. 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.
|