fix(phys L.2d slice 1.5): probe captures hit poly under StepSphereUp recursion

First Holtburg-doorway capture showed all 191 [resolve-bldg] entries
labeled "n/a (cylinder)" — including hits attributed to the building
0xA9B47900 which [entity-source] confirmed was registered as type=BSP.
The label was a probe bug, not a real cylinder route.

Root cause: BSPQuery's grounded-path (Path 5) returns early via
`StepSphereUp(transition, worldNormal, engine)` when no step is already
in progress. The slice-1 side-channel write at line 1546 came AFTER
that early return, so it never fired for the dominant grounded-player
case. Compounding: StepSphereUp recurses into ResolveWithTransition →
FindObjCollisions, whose per-entity `LastBspHitPoly = null` clear
wiped any earlier write before the outer attribution emitter read it.

Fix:
1. BSPQuery Path 5: move LastBspHitPoly write to the top of
   `if (hit0 || hitPoly0 != null)` blocks (both foot- and head-sphere),
   BEFORE the StepSphereUp early return. Recursion-safe — the inner
   resolve's BSP writes will overwrite with the inner entity's poly,
   but for the dominant case (same wall hit on both outer and inner)
   that's still the correct attribution.
2. TransitionTypes.FindObjCollisions: drop the per-entity clear of
   LastBspHitPoly. With BSPQuery now writing at hit-detection time
   instead of response-computation time, the side-channel value is
   reliable without per-iteration zeroing.
3. TransitionTypes [resolve-bldg] emission: key the "n/a (cylinder)"
   label on `obj.CollisionType` directly, not on LastBspHitPoly being
   null. A BSP entity with a null poly now logs "n/a (BSP path —
   side-channel not written, missing BSPQuery wire site)" so any
   future BSPQuery path that's missing the wire is visible in the
   trace rather than being silently mis-labeled.

Verified: build green, the 2 slice-1 tests still pass, 8 pre-existing
failures unchanged.

Spec: docs/superpowers/specs/2026-05-13-l2d-cbuildingobj-collision-design.md
First capture (showing the label bug): launch-l2d-slice1.log lines
12086-12120 (representative [resolve-bldg] entries for obj=0xA9B47900).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-12 19:25:32 +02:00
parent 66dc23e087
commit 8bacef0598
2 changed files with 34 additions and 13 deletions

View file

@ -1544,6 +1544,16 @@ public static class BSPQuery
if (hit0 || hitPoly0 is not null)
{
// L.2d slice 1.5 (2026-05-13): record the hit poly EARLY,
// before the StepSphereUp branch can recurse into
// ResolveWithTransition → FindObjCollisions and clobber the
// side-channel via the inner call's per-resolve clear. Path 5
// is the dominant grounded-player path; without this the
// probe's [resolve-bldg] line for every grounded BSP hit was
// mis-labeled as "n/a (cylinder)".
if (PhysicsDiagnostics.ProbeBuildingEnabled)
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
var worldNormal = L2W(hitPoly0!.Plane.Normal);
// L.2.3b (2026-04-29): recursion guard. Retail
// (acclient_2013_pseudo_c.txt:272954) gates step_sphere_up on
@ -1559,9 +1569,6 @@ public static class BSPQuery
// back to wall-slide so the inner sphere doesn't recurse.
collisions.SetCollisionNormal(worldNormal);
collisions.SetSlidingNormal(worldNormal);
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
if (PhysicsDiagnostics.ProbeBuildingEnabled)
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
return TransitionState.Slid;
}
@ -1575,6 +1582,12 @@ public static class BSPQuery
if (hit1 || hitPoly1 is not null)
{
// L.2d slice 1.5 (2026-05-13): same early-record as foot
// sphere — head-sphere wall hits also recurse via
// StepSphereUp on the grounded path.
if (PhysicsDiagnostics.ProbeBuildingEnabled)
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
var worldNormal = L2W(hitPoly1!.Plane.Normal);
// L.2.3b: same recursion guard as the foot-sphere branch.
if (engine is not null && !path.StepUp && !path.StepDown)
@ -1582,9 +1595,6 @@ public static class BSPQuery
collisions.SetCollisionNormal(worldNormal);
collisions.SetSlidingNormal(worldNormal);
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
if (PhysicsDiagnostics.ProbeBuildingEnabled)
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
return TransitionState.Slid;
}
}