The tfrag clip cube is at +0x38, and +0x2e is the mini-sphere array

TfragHeader carried a field named `cube_ofs` at +0x2e. R&C1 documents that
slot as `msphere_ofs` and puts the 8-corner clip cube at +0x38. The disc is on
R&C1's side for both.

Reading +0x2e as an array of `msphere_count` (+0x2c) Vec4f entries gives 833 of
833 well-formed arrays: four finite floats per entry, every radius
non-negative. +0x2c takes 4, 3, 5, 6, 16 and others. Reading that same slot as
eight s16 clip corners gives data that is not corner-shaped, which is how the
error was visible at all.

The real cube at +0x38 is non-zero on 833 of 833 headers and its eight points
sit inside the patch's own block on every one.

So: `cube_ofs` at +0x2e is renamed to `msphere_ofs`, the count it pairs with is
parsed and the array is read into TfragGeometry as `mini_spheres`, and
`clip_cube_ofs` at +0x38 is new, read into `clip_corners`.

What the corners are, measured over all 833:

    corners, all with 8 distinct x values       833 of 833
    |centroid - bsphere centre| / r             median 0.147, max 0.595
    corner distance from centroid / r           median 1.000

The median is exactly the bounding radius, so the eight points lie on the
bounding sphere. They are not an axis-aligned box: every tfrag has eight
distinct x values, so if it is a box it is oriented. Whether they form a cube
needs the corner ordering, which is not recoverable from the data -- assuming
opposite pairs are (0,7)(1,6)(2,5)(3,4) matches body-diagonal == 2r on only 99
of 833, so either that pairing is wrong or the array is not a cube. Recorded as
unknown.

This was picked to unblock the guard-band clip test from the roadmap. The
algorithm is from the vendored R&C1 reference and is recorded with its
attribution: camera-space sphere, near/far truncation, side-plane cull, then
the 8-corner re-test against the frustum unscaled and against a volume with xy
x (0.25, 0.25) -- the 4x guard band -- with any corner outside the guard volume
sending the tfrag to the clipping path at LOD 0 with no morphing. draw_mode
already matches that table. What the test still needs is the camera matrix and
the frustum as a clip volume, both per-frame state, so the camera has to be
pinned at a savestate. The geometry side is now parsed and asserted, which
leaves only the camera side outstanding.

The s16 x 64 corner scale is R&C1's documented one and is not verified against
the game; the assertion covers that the parsed corners sit inside the patch's
own block and lie on the bounding sphere, not that the scale is right.

Suite with the disc: 69 disc tests, 156 gc-formats, 22 gc-trace, 23
gc-engine, 19 gc-game. Clippy clean.
This commit is contained in:
2026-10-14 13:09:54 +02:00
parent 3a1156d8e1
commit 66e83f1665
4 changed files with 291 additions and 6 deletions
+6 -1
View File
@@ -713,7 +713,12 @@ the fidelity rule.
(TBP0/TBP1 and so on), for a renderer that emulates the GS rather than
resolving texture addresses on the host.
- **The guard-band clip test** that sends patches crossing the guard band to
the clipping program.
the clipping program. The geometry side is now in place -- the 8-corner clip
cube at header `+0x38` is parsed and tested (`core-data-regions.md`, the
clip-cube section). What is outstanding is the **camera matrix** and the
view frustum as a clip volume, both per-frame game state, so the test itself
needs a savestate with the camera pinned. `draw_mode` is unchanged and already
matches the reference's mode table.
- ~~**Lighting magnitudes.**~~ **Settled 2026-10-11** by the PCSX2
comparison loop (`docs/plan/pcsx2-verification.md`): level 0's 162,400
colour bytes match the game's EE RAM exactly. Two bugs the comparison found