615ff937eaa2407bf7c259dc858ffc84e27684eb
5
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3a1156d8e1 |
The tfrag morph is not in the data block: positions are verbatim in RAM
The morph was recorded as unverifiable because "reading a weight back out of
RAM does not work -- the positions live inside regenerated VU packets". That
is right about the correspondence and wrong about the cause. The cause is
measurable, and it comes back clean:
patches 833
positions 40,600 disc == ram
positions identical 40,600
block bytes differing 166,465
inside a position unpack 0
inside another unpack 49,484
in a VIF command word 116,981
Range: level 0, all 833 tfrag patches, live PINE RAM against the disc. Two
independent measurements agree. Walking the VIF lists on both copies and
comparing parsed positions gives 40,600 identical and zero changed; a raw
per-byte diff of each patch's own extent classifies every differing byte, and
none of them is in a position unpack. The position ranges total 490,224 bytes.
So the blend is not precomputed into the geometry. The rewrites are packet
framing -- 70% command words -- plus other unpack payloads, which is the loader
rebuilding the VIF packet for the tier it selected while leaving the vertices
alone. The morph happens at draw time in the VU stream.
The standing note becomes a sharper one: there was never a correspondence
problem. The values were identical, so there was nothing to read.
gc-trace gains verify-morph, which does both measurements. TfragGeometry
gains position_ranges and other_ranges so the walk needed to classify a diff
is already paid for, and the ranges come from the same walk the rest of the
parse uses rather than a second one that could drift.
Per-patch extents are the gap to the next patch's data pointer. That is exact
and needs no guess about which header field marks a patch's end; the first
attempt at this compared each patch against the whole rest of the region and
reported 65 million differing bytes, which is what an unbounded comparison
looks like.
tfrag_positions_are_verbatim_and_the_rewrites_are_elsewhere is gated on
GC_SSTATE and asserts the four bounds, plus two guards: the rewrites must be
non-trivial (over 100,000 bytes) and mostly command words, so a RAM image that
was a verbatim copy of the disc -- which would make the zero look good for the
wrong reason -- fails instead.
Suite with the disc and GC_SSTATE: 68 disc tests, 156 gc-formats, 22
gc-trace, 23 gc-engine, 19 gc-game. Clippy clean.
|
||
|
|
4abce518e5 |
Decode the overlay's load base, from its own profiler table
Range measured: all 27 levels' overlays. The overlay carries the game's profiler menu definition -- a run of 8-byte padded names (normal, camera, cam+chr, volume, bars, render, mobys, ties, shrubs, cpu/gpu, occl, ...) followed by runs of absolute RAM addresses that point back at those same names. On every level the four addresses after normal/camera/cam+chr/volume, each minus its target name's overlay offset, give the same number: 1,735,792. That is field_00 - 16, where field_00 is the header word that read 1,735,808 on all 27 levels with nothing attached to it. The overlay is loaded with its 16-byte header stripped, so overlay offset X sits at RAM X + 1,735,792. OverlayHeader::ram_address carries it, and the disc test asserts the derivation holds on all 27 levels. Confirmed, not only derived: level 0's RAM 0x1acf80 holds "normal\0camera\0cam+chr\0volume\0", byte-identical to overlay 0x5310, and 0x5310 + 1735792 == 0x1acf80. All four names land where the overlay's own table says. `gc-trace verify-overlay-base` runs that check, savestate-gated the same way as the tie and tfrag ones. The overlay is relocated, not copied: only 37.67% of level 0's is byte-identical at the base, and the 256-entry sine table -- at overlay 0xb650c -- is not at 0xb650c + base. So the base locates the overlay's image, but individual tables inside it move. That is the shape of a loader applying relocations, and it means the base is necessary but not sufficient for reading the game's tables. The value of the base is that any overlay offset can now be carried to a RAM address, which is the prerequisite for reading the game's live level state -- the thing the tie relocation and tfrag lighting checks had to work around. Two things get deleted rather than finished. The collision u16 table was probed four ways -- magnitude distribution, level-independence, run starts, run lengths -- and every one came back level-dependent: the run-start pattern is identical on 1 of 19 levels. So the first 1024 bytes of the collision payload are data, not a fixed table, and nothing was decoded. Recorded as the negative it is rather than left as four half-findings. |
||
|
|
54862b64ee |
Identify the tie record as RAC1's TiePacketHeader, and retract a formula
The 16-byte tie record is R&C1's TiePacketHeader offset for offset: a data pointer at +0, then R&C1's byte fields at +4 through +0x10. The identification is not a resemblance but R&C1's own rule -- bfc_distance == 5 * shader_count -- holding on all 15,219 records of all 2,177 blocks on all 27 levels. Measured over the same set, and reported rather than asserted where it is not universal: bfc_distance == 5 * shader_count 15219 / 15219 vert_ofs <= vert_size all scissor_size <= scissor_ofs all control_size <= control_count all color_ofs == vert_size 12640 / 15219 color_ofs == vert_ofs + vert_size 0 / 15219 color_count, strip_count, strip_vertex_count zero 15089 / 15077 / 15078 R&C1's rule for the colour offset holding on none of them is the interesting line: the two games use that field differently, so it is named color_ofs for what this game's majority reads as and the 2,579 exceptions are counted rather than averaged away. TieRecord parses all twelve fields, invariants_hold asserts only the universal four, and the disc test counts the rest. A packet-length formula is withdrawn rather than softened. (vert_ofs + vert_size + scissor_ofs + scissor_size) * 16 was measured at 61 of 61 pairs and is not reproduced on the correct blocks -- 0 of 13,042. The block set was the error, not the arithmetic: the script that produced the 61 read packets[2] where packets[1] was meant, so it measured 461 blocks instead of the 2,177 the class tables define, and the formula agreed perfectly with a population that was not the tie blocks. TieRecord::packet_bytes goes with it, and the retraction is recorded in the format doc and in the struct. That misread also explains why the earlier RAM verification saw different numbers for the two inputs -- same lesson as rule 3, arriving through a wrong subscript instead of a wrong span. |
||
|
|
4e90c282ad |
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. |
||
|
|
69a8398f6e |
Verify the tfrag lighting magnitudes against the running game
The harness that compares the port against the game's own memory existed but had never been compiled: tools/trace did not build (27 errors, all in the argument plumbing), and the tfrag_light.rs light-bank correction it motivated was unverified. This finishes it and runs it, and the run finds two real bugs. Result, on level 0 against three inputs -- the live emulator over PINE and two PCSX2 savestates from the same level, agreeing byte for byte: headers matching the disc on invariant bytes: 833/833 data pointers relocated as table + disc offset: 833/833 light bank in RAM at 0x50ab20, byte-identical to the disc's vertices compared: 40600 (162400 colour bytes) equal: 162400 (100.00%) The locator finds the level's tfrags block in RAM by tfrag 0's bounding sphere and accepts a candidate only when every header matches the disc on the bytes the game leaves alone and every data pointer is the relocated form, so "the block was found" is itself a verification rather than an assumption. The bank is searched in RAM by its exact section bytes, which is what settles its framing: offset at +0x04, an i32 set count, twelve bytes of pad, then the sets. The previous reading (offset at +0x00, count at +0x04) was wrong. Two bugs the comparison found, both of which produced plausible output: 1. vertex_normal_bits negated x and y and left z positive, while NormalTable::normal negates all three. dA came out -0.707 where the game had +0.707, so max(dA, dA*wA) took the wrong branch and every vertex lit near its base colour: dark, but not wrong-dark. 95.88% agreement with the bug, 100.00% without. 2. A record whose select names a set the bank lacks drew transparent black. The game writes the record's base colour; 3,130 of level 0's 40,600 vertices do this (select 0x000f, set 15 -- a runtime slot the level's directional bank does not have). The vertex is unlit, not invisible. Both are now regression tests in the engine. The comparison prints the pass's per-vertex terms (light_vertex_terms) for the first mismatches, because a colour mismatch alone cannot be attributed and the comparison is worthless without attribution -- that is how the second bug was identified from a three-line read rather than guessed at. Docs: docs/plan/pcsx2-verification.md records the harness, the result, the two bugs and what to compare next; roadmap and level-assets.md stop carrying lighting magnitudes as an open item. Findings reach tests only as numbers -- no test reads a savestate, a dump or a live emulator. |