Make the tie record relocation a command, and verify it on two levels

The relocation rule from the previous commit was established by a script that
lived in a shell history, which means the next person would have had to
rediscover it. It is now `gc-trace verify-tie-records <unpacked-dir> <level>
<class> <lod> <src>`: resolve the class, parse the block, search the RAM image
for the loaded record table, report what it found, and exit 0 only when every
record's pointer has become table + word and every field byte is verbatim.

Swept over every (class, lod) slot of two levels, from PCSX2 savestates:

  level 0: 87 slots with a block, 87 verified, 0 mismatched, 61 absent
  level 1: 104 slots with a block, 104 verified, 0 mismatched, 78 absent

So the rule holds on every LOD block of two levels the game had loaded, which
is what makes the record layout a finding rather than a reading of one block.

Writing the command also caught an off-by-one in the parser it exercises:
TieBlockHeader::records started its slice after the 16-byte header, but the
header IS the first record -- the table runs from the block start to
data_offset, and a block with data_offset 0x50 holds five records, the first
being the header itself and its pointer naming where the payload begins. Found
because the tool's first run relocated nothing while the same check in a script
had passed; the unit test fixture was wrong in the same way and is fixed.

Two operational notes now in the format doc: the live emulator is not a stable
input (verifying level 1 over PINE matched 104 of 104 while the user was on
that planet, and the same command minutes later found no matching copy because
they had returned to level 0 -- a miss there is not a counterexample, so the
report says "no copy matched" rather than "failed"), and a class index past the
table count is an error rather than a mismatch, so a sweep should stop at the
level's class count rather than a round number.
This commit is contained in:
2026-10-12 15:24:48 +02:00
parent e6eecb871f
commit 4e90c282ad
7 changed files with 255 additions and 14 deletions
+5 -1
View File
@@ -280,7 +280,11 @@ Phase 1 hands over the extents; this phase works out what each one contains.
the record table's length -- not a count, which is why the payload never
divides by it. Each record is a leading `u32` pointer **relative to the
table start** plus twelve verbatim field bytes, relocated at load to
`table + word`. Verified in PCSX2's RAM on 16 of 16 level-1 blocks.
`table + word`.
- [x] **The tie relocation verifies on two levels.** `gc-trace
verify-tie-records` checks it against PCSX2's RAM: **87 of 87** blocks on
level 0 and **104 of 104** on level 1, zero mismatches, which is what
makes the record layout a finding rather than a reading of one block.
- [x] **The shrub mesh geometry is in the class blob.** A packet table at
`+0x40` names `count` packets between the table and the palette, and
they tile on **358 of 358 spans** (2,239 packets), which makes `count`