Files
BFM-decomp/docs/proto-correspondence.md
T
Drew T 3207d52491 feat(phase-3.5): prototype research spike — go/no-go (protos don't accelerate Gen1 matching)
- extract_proto_exe.py: subdir-aware proto main-EXE extraction (reuses iso9660); extracted
  sep8 SLUS_007.26 (413,696 B) + aug31 MUSASHI/USA_DEMO.EXE (415,744 B, base 0x80018000)
- ghidra_import.sh: reusable headless import; both protos imported into the bfm project
  (sep8 1726 funcs, aug31 1737 funcs, both PsyQ 4.0.0), R9-verified
- DumpFunctionSignatures.java + match_protos.py: 3-tier (exact/norm/seq) cross-binary
  correspondence; MIPS normalizer validated by anchors
- FINDINGS: (T2) no debug symbols in either proto — Hidden Palace "no symbols" VERIFIED;
  recon "Sep-8 less-stripped" REFUTED by per-file byte compare. (T3) Sep-8 99.6% byte-identical
  to retail (only 3 funcs differ: DebugMenuHandler, CdReadSectorReadyCB, SaveLoadRoutine);
  Aug-31 66% norm-identical, 862 1:1 correspondences. (T4) demo shares retail's 18-entry
  game-mode dispatch architecture but the handler code diverged — Q#10 resolved
- VERDICT (docs/proto-correspondence.md): NO-GO as a Gen1 label/symbol accelerator; GO to retain
  Sep-8 (Phase-6 compiler corroborant) + both (Gen2 assets); proto-side labels in
  config/symbols.proto-{sep8,demo}.txt (R13-tagged, never feed symbols.us.txt)
- reorg extracted/ into per-ROM subfolders (extracted/retail/, extracted/proto/); nested
  gitignore allowlist; extractor defaults updated; manifest --verify PASS
- memory-map.md: Phase-3.5 block + demo dispatch anchors; Q#10 RESOLVED; §5 proto notes VERIFIED
- ghidra_mcp_start.sh: PROG arg to serve a prototype
- rules R13 (proto-provenance/confidence tagging), R14 (verify recon counts vs bytes)
- bumps project version 1.3.0 -> 1.3.1
2026-06-14 03:19:23 -06:00

101 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prototype Research Spike — Correspondence & Go/No-Go (Phase 3.5)
> **Date:** 2026-06-14 · **Evolvable `docs/` layer.** The go/no-go deliverable for Phase 3.5.
> **Question (Drew's directive):** do the two CRC-verified US prototypes *accelerate Gen1 matching*?
> Bytes are the only truth (P9/G3): proto bytes ≠ retail, so proto matches never transfer as matches.
## TL;DR — Verdict: **SPLIT**
| Claim | Verdict | Why |
|---|---|---|
| Protos accelerate Gen1 matching via **symbols / debug-info / labels / readability** | **NO-GO** | Refuted: *neither* proto carries any symbols, source paths, or labels retail lacks (Finding 1). Both have **fewer** names than retail's 952 — the label flow is retail→proto, not proto→retail. |
| Protos are worth **retaining as project assets** | **GO (modest, targeted)** | Sep-8 is a near-duplicate of retail (99.6%) → a **Phase-6 compiler-determinism corroborant**. Aug-31 is the same engine rebuilt (862 1:1 correspondences, shared dispatch architecture) → a **Gen2 overlay/engine asset** (it covers the SC02 region) + a marginal differential-analysis aid. |
**Actions:** (1) Do **not** build proto-based labeling into the Gen1 matching loop — there is no payoff. (2) Keep the imported Ghidra programs + the correspondence artifacts as committed assets. (3) The bounded lift (T6) is **one-way, retail→proto** (name the proto programs from retail to make them navigable for Phase-6/Gen2); nothing is pushed onto the retail DB (G6). (4) All proto-derived data is provenance/confidence-tagged (R13) so a later full-lift phase can verify/extend it without risk to `config/symbols.us.txt`.
## The prototypes
| Build | Main EXE | Size | Load addr | SHA1 | Funcs | PsyQ | Source |
|---|---|---|---|---|---|---|---|
| Retail (SLUS-00726) | `SLUS_007.26` | 413,696 | 0x80010000 | `143dbb89…` | 1726 | 4.0.0 | redump |
| **Sep-8 "Musashi Master"** | `SLUS_007.26` (root) | 413,696 | 0x80010000 | `43006a31…` | 1726 | 4.0.0 | Hidden Palace, CRC 5C24728E |
| **Aug-31 "Musashi Demo"** | `MUSASHI/USA_DEMO.EXE` | 415,744 | **0x80018000** | `6150b0f8…` | 1737 | 4.0.0 | Hidden Palace, CRC 1369DE07 |
- The Aug-31 demo loads 0x8000 higher; its root `LAUNCH.EXE` (24,576 B) is a bootloader owning the low 32 KB. The demo ships `MAIN.CD` (= retail size) + `SC02.CD` only → it is the **SC02 region** (tutorial/Allucaneet area). The Sep-8 master is structurally retail-identical (27 files, full MAIN+SC01–07).
- **All three share PsyQ 4.0.0** (same compiler family — independent corroboration for the Phase-6 fingerprint).
## Method (reproducible)
1. Extract proto main EXEs: `tools/bfm_extract/extract_proto_exe.py` (reuses the general `iso9660` reader; `--dir` for the demo's `MUSASHI/` subdir; magic-only validation) → `extracted/proto/` (gitignored).
2. Import into the `bfm` Ghidra project: `tools/ghidra_import.sh <exe>` (PSX loader auto-detect → auto-analysis incl. PsyQ signatures + DetectPsyQ → PsyQ-4.0 types → DumpProgramInfo).
3. Fingerprint every function (read-only): `tools/ghidra_scripts/DumpFunctionSignatures.java` → `.run/sig.<prog>.jsonl`. Three tiers so the match count is **bracketed**:
- `h_exact` — SHA1 of raw instruction bytes (strict).
- `h_norm` — SHA1 of a MIPS-normalized stream: neutralize `j`/`jal` absolute targets, `lui` high-halves, `%lo`/gp-relative address immediates (detected via Ghidra's analyzed references), branch targets → PC-relative offsets; **keep registers (regalloc) + true constants**. The cross-build "same source, relocated" tier.
- `h_seq` — SHA1 of the mnemonic-only sequence (loose).
4. Correspondence: `tools/match_protos.py` → per-tier counts + 1:1 unique `h_norm` pairs → `.run/correspondence.<proto>-vs-retail.tsv`.
Normalizer validated by anchors: `LzssDecodeSector` and `CdReadRequest` both `h_norm`-match across the Aug-31 address shift + rebuild (0x80018730→0x80020c20, 0x80019a24→0x80021ed0).
## Finding 1 — No debug symbols in either prototype (T2)
Hidden Palace's "no symbols" is now **verified**, not assumed. Per-file byte comparison (the three main EXEs and the three disc images):
- **Main EXEs are stripped identically.** Each carries only two "debug" strings: `\DEBUG.BIN;1` (the known dead loader path) and `SetGraphDebug:level:%d…` (a stock PsyQ format string, present in retail). No source paths, no symbol tables, no function names in any main EXE.
- **Disc-wide, retail and Sep-8 are debug-string-identical.** `^_dbg_RE1L@55;` is in **retail too** (not Sep-8-only); `C:\TIMPACK\OPDEMO0/1.PAT` is in both (shipped opening-demo data); `DEBUG`-substring counts are retail 63 vs sep8 59 — both pure garbage (coincidental ASCII in compressed data).
- **Correction:** an early reconnaissance pass reported "Sep-8 is less-stripped (11× DEBUG + a `dbg_` linker symbol)" as a strong go-signal. That was a **false positive** from naive disc-wide `strings|grep` counting coincidental `X:\`/`DEBUG` byte sequences. Per-file comparison shows parity. (→ rule candidate R14: verify sub-agent/recon summary *counts* against the bytes before relying on them.)
## Finding 2 — Code correspondence (T3)
Retail real functions (≥1 instruction): 1569; substantial (≥8 ins): 1179.
| Proto vs retail | h_exact (subst.) | h_norm (subst.) | h_seq (subst.) | 1:1 unique norm pairs |
|---|---|---|---|---|
| **Sep-8** | **99.4%** | **99.7%** | 99.7% | 1072 (≥8 ins) |
| **Aug-31** | 16.0% | **65.9%** | 99.0% | 690 (≥8 ins) |
- **Sep-8 is a near-duplicate of retail.** Only **3 substantial functions differ at every tier** — the pre-release diff: `DebugMenuHandler` (0x80011144, 30 ins), `CdReadSectorReadyCB` (0x8001a338, **424 ins** — the inline-LZSS-decode CD-read callback), `SaveLoadRoutine` (0x8002b154, 60 ins) — plus 3 pure-relocation diffs. Everything else is **byte-identical at the same address.** Sep-8 therefore adds essentially no new matching information; its worth is corroboration (below).
- **Aug-31 is the same engine, genuinely recompiled.** Low `h_exact` (address-shifted + rebuilt) but **66% of substantial functions are normalized-identical** and 99% share the opcode sequence — two-thirds of the engine is literally the same source→codegen, the rest minor codegen drift. **862 confident 1:1 correspondences** (`.run/correspondence.aug31_USA_DEMO.EXE-vs-retail.tsv`).
## Finding 3 — Shared dispatch architecture, diverged handlers (T4; resolves Open Q#10)
Anchor-bootstrap from the retail debug-menu anchors:
- The demo's game-mode dispatcher is `FUN_80018b48` (matched by opcode sequence to retail `GameModeDispatch` 0x80010b40; relative offset +0xB48 vs retail +0xB40 — the demo mirrors retail's early-code layout, shifted +0x8000). It decompiles to the same pattern: `(*(code*)(&handlerTable)[gameMode])()`.
- **Both handler tables have exactly 18 entries**, in the same order, **with the same internal duplicate** (index[10] == index[15] in both). retail `gameModeHandlerTable` @0x800629F4 ↔ demo table @0x8006AE8C.
- **Slot [7]** (the menu): retail `DebugMenuHandler` 0x80011144 ↔ demo 0x800194cc — **same slot, but the handler code diverged** (no norm/seq match in the demo *or* in Sep-8).
**Q#10 answer:** the demo's "press-Start scene-select" and retail's "L3 debug menu" are the **same architectural feature** (game mode #7 of an identical 18-mode dispatch table) — but the handler is **reimplemented**, and the demo is **unnamed**, so there are **no labels to lift onto retail.** The shared architecture is real; the "free labels" payoff is not.
## Go/No-Go rationale
The spike's hypotheses, each tested against the bytes:
1. *Protos carry symbols / debug info* → **refuted** (Finding 1).
2. *Protos are more readable / easier to decomp* → **no** — equally stripped; no symbols (Finding 1).
3. *The demo's title menu shares retail's debug-menu code → free labels* → **refuted** — same architecture, diverged + unnamed handler (Finding 3).
4. *A second build helps matching* → **only marginally** — Sep-8 ≈ retail (no new info); Aug-31 gives a same-source-different-address differential that can disambiguate a stubborn function, but it is not a force-multiplier and carries no names (Finding 2).
So for the *stated question* — accelerate Gen1 matching — the answer is **NO-GO**. The protos do not shorten the matching loop.
What they **are** worth (hence retain, don't discard):
- **Sep-8 → Phase 6.** A second build that is 99.6% byte-identical to retail is strong evidence the toolchain is deterministic, and it pins exactly which 3 functions changed pre-release. When the compiler triple is pinned and retail is rebuilt, Sep-8 is a free second cross-check.
- **Aug-31 → Gen2.** A second, navigable build of the engine (once named from retail) covering the **SC02 region**, with 862 mapped functions and a confirmed shared dispatch architecture — a head-start for Gen2 overlay/engine RE. Its overlays (MAIN.CD = retail size; SC02.CD) are a second sample for the Gen2 overlay work.
## Actionable outcomes
- **Keep** the imported Ghidra programs (`sep8_SLUS_007.26`, `aug31_USA_DEMO.EXE`) in the `bfm` project and the committed correspondence artifacts.
- **T6 (bounded lift, one-way retail→proto):** apply retail's names onto the proto programs via the 1:1 `h_norm` correspondences (proto-side only), export `config/symbols.proto-sep8.txt` / `config/symbols.proto-demo.txt`. This makes the protos navigable for Phase-6/Gen2 without touching `config/symbols.us.txt`.
- **Do NOT** push proto-derived names/comments onto the retail DB (the protos have nothing retail lacks; G6).
- **Deferred (Drew):** the exhaustive exact+norm+fuzzy adjudication (Version Tracking + manual near-miss review) is a *later* phase. Every proto-derived datum is tagged `unverified` unless independently confirmed against retail, so that phase can verify/extend safely (R13).
## Provenance & confidence (R13)
| Datum | Region | Confidence |
|---|---|---|
| Proto EXE sizes / SHA1s / load addrs / func counts / PsyQ version | PROTO | **verified** (read from the binaries) |
| Correspondence tier counts; 862/1072 1:1 pairs | PROTO↔US | **verified** (deterministic over the dumps) |
| Sep-8 pre-release diff = {DebugMenuHandler, CdReadSectorReadyCB, SaveLoadRoutine} | PROTO↔US | **verified** |
| Demo dispatch anchors: `GameModeDispatch` 0x80018b48, `gameModeHandlerTable` 0x8006ae8c (18), `gameMode` 0x800c1eb6, slot[7] 0x800194cc | DEMO | **verified** (decompiled + table read from the demo binary) — DEMO addresses, NOT valid for US retail |
| Individual retail↔proto function name assignments (T6 output) | PROTO | **unverified** until confirmed vs retail — quarantined to `config/symbols.proto-*.txt` |