Verify the Phase 2 gate, and give every blocked item an unblock condition

Starting from the phase's stated exit criteria rather than the checklist, because
"complete" should mean the gate, not the feeling of the list.

THE GATE IS MET. Both halves, checked by inspection:

  * Golden tests. Every module in gc-formats/src is referenced by a test or another
    module's test path; none is unreferenced. gc-extract/tests/disc.rs carries 50
    real-disc tests, and overlay_reloc's mask test resolves a level-01 function on
    all 26 other overlays.

  * Counts are failures, not warnings. No parser writes warning-style output
    outside #[cfg(test)] -- the only eprintln! calls in gc-formats are inside test
    modules. Count and length problems surface as typed errors: 42 uses of
    ErrorKind::Invalid / InvalidLength, including global_wad's "pair table is not a
    whole number of entries", which is precisely a header count disagreeing with
    the data.

THE GOAL IS NOT MET, and saying otherwise would be the thing AGENTS.md forbids.

The goal reads "every asset type parses into plain Rust values". Four do not:
collision cells, volumes, moby_anim and the tfrag morph are located or framed but
have no decoded fields. The gate was written to be checkable and it does not test
for that, which is a gap in the gate rather than a reason to declare victory.

So the remaining gap in the phase's own discipline is that the four blocked items
did not say what would unblock them, which AGENTS.md requires and which none of
them did. Closed now, each with the specific unblock condition:

  collision cells  the game addresses its assert string by something other than a
                   $gp-relative load -- a %hi/%lo split that does not straddle
                   zero, a pointer table, or another section. A plain $gp search
                   returns zero and always will.
  volumes          ground truth: a count of level 0's trigger volumes from a map,
                   to match against the 144-byte table's population. No class name
                   containing "volume" exists on the disc, so the dispatch route is
                   closed.
  moby_anim        needs moby class records decoded past the header -- a
                   dependency, not a measurement.
  tfrag morph      needs a frame captured at a KNOWN camera distance, so the
                   expected blend is computable. Requires driving the running game,
                   not analysing a static savestate.

None of these is an approximation, and none is marked done.

258 tests, 50 in the disc suite, clippy clean on my own code.
This commit is contained in:
2026-10-14 02:52:09 +02:00
parent 67ce068c7f
commit 1e90c7de6d
+46
View File
@@ -540,6 +540,52 @@ Phase 1 hands over the extents; this phase works out what each one contains.
**Gate:** every parser has a golden test against bytes from the real disc, and **Gate:** every parser has a golden test against bytes from the real disc, and
reports mismatched header counts as failures rather than warnings. reports mismatched header counts as failures rather than warnings.
### Gate status, checked
Both halves verified by inspection rather than assertion:
* **Golden tests.** Every module in `gc-formats/src` is referenced by a test or
another module's test path; none is unreferenced. `gc-extract/tests/disc.rs`
carries 50 real-disc tests, and the `overlay_reloc` mask test resolves a level-01
function on all 26 other overlays.
* **Counts are failures, not warnings.** No parser writes warning-style output
outside `#[cfg(test)]` -- the only `eprintln!` calls in `gc-formats` are inside
test modules. Count and length problems surface as typed errors: 42 uses of
`ErrorKind::Invalid` / `InvalidLength`, including `global_wad`'s "pair table is
not a whole number of entries", which is exactly a header count disagreeing with
the data.
**The gate is met.**
### Blocked, and what would unblock each
AGENTS.md requires a blocked item to say what would unblock it. These four did
not, which is itself a gap; this is that gap closed. None of these is a shortcut
or an approximation -- each is a measurement this repository currently cannot
make.
- **Collision cell layout.** The payload is verbatim in RAM and does not walk as
fixed-stride cells; every disc-side segmentation is excluded. Needs the game's
own `Camera_CollPrimTest`, which lives in `.lit`. The route that would work:
the code addresses the assert string by something other than a `$gp`-relative
load -- a `%hi`/`%lo` split that does not straddle zero, a pointer table, or a
different section. A naive `$gp` search returns zero hits and will keep doing so.
- **Volumes.** A 144-byte float record table with world positions at `+0x0c`
exists in every level and is not referenced by any `data.bin`, so it is reached
by walking structure, and nothing ties it to volumes. Needs ground truth: a
count of level 0's trigger volumes from a map or guide, to match against a
table's population. No class name containing "volume" exists on the disc, so the
dispatch-table route is closed.
- **`moby_anim`.** The instance-table reading is disproved and nothing replaces
it. Needs the class-dispatch table for the classes that reference the animation
data, which needs `moby` class records decoded past the header -- a dependency,
not a measurement.
- **tfrag morph.** All patch bytes are rewritten by LOD-selected VU-packet
regeneration, so the morph is invisible in a savestate. Needs a frame captured
at a *known* camera distance, so the expected blend is computable and can be
compared. Requires running the game and driving the camera, not analysing a
static savestate.
**Discipline:** bound every read. Typed errors, no panics, no reads past the **Discipline:** bound every read. Typed errors, no panics, no reads past the
buffer. buffer.