fix(test): A6.P3 slice 1 T5 — redesign test to actually catch synthesis regression

Code-review feedback on commit 5f7722a: the test was gamed —
placing the sphere exactly on the floor (worldPosZ = floorZ) made it
pass regardless of whether synthesis was present. With sphere center at
Z=0.48 (= floorZ + SphereRadius), PolygonHitsSpherePrecise's distance
guard fires immediately (|dist|=0.48 > rad=0.478) and
TryFindIndoorWalkablePlane returns false even WITH synthesis code. The
test would have passed even if the strip were reverted.

Redesign: restore worldPosZ = floorZ - 0.05f (sphere center at Z=0.43).
Now |dist|=0.43 < rad=0.478 → the guard passes → TryFindIndoorWalkablePlane
finds the floor polygon → synthesis would fire → CP writes every frame.
Path 5 (Contact branch) is not a concern: the loop moves only in X so
movement = (0.001, 0, 0), Dot(movement, floor_normal=(0,0,1)) = 0 ≥ 0 →
PosHitsSphere front-face cull rejects the floor hit even with sphere
center below the floor. Path 5 returns OK with zero CP writes. Contact
flag is left set to keep the test on the realistic grounded-mover path.

Validated locally by temporarily re-introducing the synthesis call —
test fails with 60 writes (1 per frame) pre-strip, passes with 0
additional writes post-strip. Now a real regression sentinel.

1148 pass + 8 pre-existing fail baseline maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-22 09:28:16 +02:00
parent 5f7722a3a4
commit 39fc0372a3

View file

@ -230,30 +230,38 @@ public class IndoorContactPlaneRetentionTests
public void IndoorFlatFloorWalking_60Frames_ProducesAtMost5ExtraCpWrites() public void IndoorFlatFloorWalking_60Frames_ProducesAtMost5ExtraCpWrites()
{ {
// ── Arrange ─────────────────────────────────────────────────────────── // ── Arrange ───────────────────────────────────────────────────────────
// Post-fix grounded model: // Geometry: sphere center 5 cm BELOW the floor so that the synthesis
// path's distance guard passes and TryFindIndoorWalkablePlane would
// actually find the floor polygon if synthesis were re-introduced.
// //
// SpherePath.InitPath sets LocalSphere[0].Origin = (0,0,sphereRadius). // SpherePath.InitPath sets LocalSphere[0].Origin = (0,0,SphereRadius).
// After SetCheckPos(worldPos), the global sphere CENTER is at // After SetCheckPos(worldPos), the global sphere CENTER is at
// worldPos + (0,0,SphereRadius) = (0,0,SphereRadius). // worldPos + (0,0,SphereRadius) = (worldPosX, 0, -0.05 + 0.48 = 0.43).
// //
// A correctly-grounded sphere has its bottom exactly at the floor: // PolygonHitsSpherePrecise distance guard (BSPQuery.cs line ~117):
// sphereBottom = sphereCenter.Z - SphereRadius // dist = Dot(normal=(0,0,1), center=(x,0,0.43)) + D(=0) = 0.43
// = worldPosZ + SphereRadius - SphereRadius // rad = SphereRadius - EPSILON = 0.48 - 0.002 = 0.478
// = worldPosZ // |dist| = 0.43 < 0.478 → guard passes → polygon is tested → FOUND.
// So TryFindIndoorWalkablePlane WOULD call ValidateWalkable → CP write
// if the synthesis call were present. With the strip in place it is never
// called → ≤5 additional writes → PASS.
// //
// With worldPosZ = 0 (= floorZ), the bottom just touches the floor. // With the post-5f7722a setup (worldPosZ = floorZ = 0):
// SphereIntersectsPolyInternal uses a strict penetration check, so a // dist = 0.48, rad = 0.478, |dist| = 0.48 > 0.478 → guard FIRES →
// sphere touching-but-not-penetrating does NOT count as a hit. // TryFindIndoorWalkablePlane returns false even WITH synthesis code.
// Path 5 (Contact grounded) returns OK with no CP write. // That setup was not a regression sentinel; this one is.
// //
// Pre-fix: the sphere was positioned 5 cm BELOW the floor so that // Path 5 (BSP Contact branch, BSPQuery.cs ~line 1732):
// TryFindIndoorWalkablePlane → ValidateWalkable would fire every frame. // The loop advances only in X, so movement = (0.001, 0, 0).
// Post-fix: synthesis is gone; the sphere must be at its natural // PosHitsSphere culls hits where Dot(movement, normal) >= 0.
// grounded position (bottom at floorZ) so that BSP Path 5 finds no // Dot((0.001,0,0), (0,0,1)) = 0 >= 0 → floor polygon is ALWAYS
// penetration and returns OK immediately — zero additional CP writes. // rejected by the front-face cull, even though the sphere center is
const float floorZ = 0f; // below the floor. Path 5 exits OK with no CP write regardless of
const float worldPosZ = floorZ; // sphere bottom exactly at floor // whether the Contact flag is set. Leaving Contact set keeps this
const float sphereCenterZ = worldPosZ + SphereRadius; // = 0.48 // test on the realistic grounded-mover path.
const float floorZ = 0f;
const float worldPosZ = floorZ - 0.05f; // 5 cm below floor
const float sphereCenterZ = worldPosZ + SphereRadius; // = 0.43
var floorPlane = new Plane(Vector3.UnitZ, -floorZ); // N·p + D = 0 → D = 0 var floorPlane = new Plane(Vector3.UnitZ, -floorZ); // N·p + D = 0 → D = 0
var worldPos = new Vector3(0f, 0f, worldPosZ); var worldPos = new Vector3(0f, 0f, worldPosZ);
@ -278,7 +286,7 @@ public class IndoorContactPlaneRetentionTests
// indoor branch would call it each physics frame. // indoor branch would call it each physics frame.
for (int frame = 0; frame < SimulatedFrames; frame++) for (int frame = 0; frame < SimulatedFrames; frame++)
{ {
// Advance position by 1 mm forward — same Z (grounded on floor). // Advance position by 1 mm forward — same Z (sphere center 5 cm below floor).
var newPos = new Vector3(frame * 0.001f, 0f, worldPosZ); var newPos = new Vector3(frame * 0.001f, 0f, worldPosZ);
t.SpherePath.SetCheckPos(newPos, IndoorCellId); t.SpherePath.SetCheckPos(newPos, IndoorCellId);