Step 3 of the apparatus plan. Adds Issue98CellarUpReplayTests, a 7-test
harness that loads the three real-geometry cell fixtures captured in
commit 3f56915 and drives the failing-frame sphere through the same
nearest-walkable algorithm the live client uses in
Transition.LogNearestWalkableCandidate.
The tests reproduce the live failure deterministically in under 1ms
each — the issue #98 cellar-up bug is now visible to a unit-test run,
no client launch required.
Tests:
- Fixtures_AllThreeCellsLoadAndShareOrigin — sanity check the cells
loaded with the expected (130.5, 11.5, 94.0) origin.
- Cellar_HasMostPolygons_CottageNeighborBIsSparse — confirms the
surprising finding: 0xA9B40146 is too sparse to be a "cottage main
floor" cell (slice 5 handoff inference was wrong; 0xA9B40143 with 14
polys is the better candidate).
- FailingFrame_CellarPrimary_HasCellarRampAsNearestWalkable — the
ramp polygon IS reachable when the player is on top of it
(sanity: this should always be true).
- FailingFrame_CottageNeighborA_NearestWalkableIsOutsideSphereAndEdges
— at the failing-frame sphere position, the nearest walkable in
0xA9B40143 (poly 0x0004, the cottage floor triangle at world Z=94)
reports BOTH insideEdges=false AND overlapsSphere=false. The sphere
XY is beyond the triangle edge, and the sphere is too far below the
plane. THIS IS THE BUG'S SHAPE.
- FailingFrame_CottageNeighborB_HasNoWalkableCandidate — 0xA9B40146
has NO walkable polygon close enough to the failing-frame sphere.
- FailingFrame_NoCottageNeighbourYieldsAcceptedWalkable — composite:
across both cottage cells, no walkable passes both edge + sphere
tests → step-up has nothing to step onto → player stuck.
- FailingFrame_CottageNeighborA_Poly0x0004_HasExpectedShape — pins the
exact polygon shape so a future fixture re-capture failure is loud.
What this gives us:
1. The bug is now ALWAYS reproducible in test, no live client iteration.
2. Any fix to BSPQuery.FindCrossedEdge / polygon containment / the
cell transform will instantly show whether it changes the failing-
frame outcome.
3. Step 4 (retail cdb capture) will tell us what retail finds at the
same sphere position; Step 5 (comparison doc) will name the
divergence; the eventual fix is then evidence-driven, not a guess.
The tests document the CURRENT (failing) behavior. They WILL pass
after the fix — at which point they need to flip to assert the
retail-correct behavior. This intentional brittleness is the point:
the test is the bug's gravestone, and a fix that doesn't match retail
should not satisfy the test.
Verification:
- dotnet build: green, 0 errors.
- dotnet test: 1167 passed + 8 pre-existing failed (was 1160+8 before
this commit; +7 from the replay tests). Same pre-existing failures,
no new regressions.
- Each Issue98 test runs in under 1ms; loads JSON, calls one internal
predicate per polygon, asserts.
Next: tools/cdb/issue98-cellar-up-find-walkable.cdb (Step 4).