The guard-band clip test: implemented and unit-tested ahead of the camera

1aa94ee moved the camera hunt behind a PCSX2 session, because no two savestates on
hand are consecutive frames. But the camera is only part of this item -- the test
itself is a pure function over a camera and a frustum, so it can be written and
tested now, and then drop straight in when the camera is found rather than being
written under time pressure later.

New gc_formats::guard_band implements docs/plan/tfrag_rac1.md 3b.2 steps 1-7,
returning Cull, Normal or ClipAtLod0. Seven unit tests: directly ahead, behind
camera, beyond far, straddling the side planes, all-corners-outside-one-plane, the
integer sqrt, and a rotation.

This is not speculative plumbing. The inputs are already parsed and verified:
TfragGeometry::bsphere, clip_corners (8 corners, median 1.000x the bounding radius
from their centroid) and draw_mode (matching the reference table).

WHAT THE FIRST VERSION GOT WRONG

The reference culls when ONE plane has every corner outside it. I first wrote
"every corner is outside some plane", which culls patches that merely straddle
*different* planes. The test caught it at once: a patch whose corners were beyond
far in one axis and the side plane in another returned Cull where it should be
ClipAtLod0. Fixed by carrying a per-corner 4-bit mask of which planes it is
outside and testing each plane across all corners.

That is what the guard band is for. A patch outside the frustum but inside the
guard band must still draw, via the clipping program, so objects partly off-screen
do not pop. Collapsing the two turns "4x guard band" into "immediate cull", and
the symptom is objects vanishing at the screen edge rather than any error.

NOT VERIFIED AGAINST THE GAME

Steps 1-4 are implemented from the reference and have not been compared to a real
frame, for the reason above. The unit tests check the algorithm implements the
documented rule, not that the rule is what the game does. Two scales are my
reading of the fixed-point convention and are NOT established: tan = 1024 for 1.0,
and the clip-cube scale of 64. Both are flagged in the module doc so they are not
mistaken for measured facts.

293 passing with the disc and savestate present, clippy clean.
This commit is contained in:
2026-10-15 03:09:57 +02:00
parent 1aa94ee288
commit 3dbd384b59
4 changed files with 325 additions and 1 deletions
+8 -1
View File
@@ -86,7 +86,14 @@ early, while they are still cheap to retire.
the normal's z was not negated (dark but plausible, 95.88% agreement),
and records with no light set drew transparent black instead of the base
colour. See `docs/plan/pcsx2-verification.md`.
- [ ] Then the GS register set and the guard-band clip test.
- [~] Then the GS register set and the guard-band clip test. **The clip test is
implemented** as `gc_formats::guard_band` -- steps 1-7 of the R&C1
algorithm, returning Cull / Normal / ClipAtLod0, with seven unit tests.
Its geometry inputs (`bsphere`, `clip_corners`, `draw_mode`) are parsed and
verified; the only missing piece is the per-frame camera, which needs a
PCSX2 session because no two savestates on hand are consecutive frames
(`1aa94ee`). **Not yet compared against a real frame**, and the `tan` and
clip-cube scales are unverified.
The disc being the exact build the reference projects pin is the single
biggest favourable fact so far: no disc conversion, no regional timing work,