feat(phys): A6.P3 slice 5 — [place-fail] probe + sharpened #98 diagnosis

Add ACDREAM_PROBE_PLACEMENT_FAIL gate + LogPlacementFail emitter +
side-channel polygon attribution in PhysicsDiagnostics. Wire into
BSPQuery.FindCollisions Path 1 (Placement/Ethereal) on Collided
returns; wire into Transition.DoStepDown after the placement_insert
TransitionalInsert(1) call; wire into Transition.FindObjCollisions
to emit per-static-object [place-fail-obj] lines.

Run scen4 cellar-up with the probe → 168 [place-fail] events. 80 of
81 BSPQuery Path 1 placement rejections cite polygon 0x0020 in
cellar cell 0xA9B40147's BSP: n=(0,0,-1) d=-0.2, world Z=93.82 —
the cellar ceiling (underside of cottage main floor thickness layer).
0 [place-fail-obj] lines, confirming the failure source is the cell
BSP not a static object.

The probe-driven evidence INVALIDATES the 2026-05-22 morning
handoff's "Path 5 vs Path 6 in BSPQuery.FindCollisions" diagnosis.
Retail's BP4 trace shows every find_collisions hit has collide=0 —
retail enters the same Contact branch we do, no outer-dispatcher
divergence. Retail's BP5 fires 17+ times on the cellar ramp polygon,
not "30 hits all on flat planes" as morning claimed.

The actual divergence is downstream in cell-promotion: retail's
check_cell transitions to cottage cell 0xA9B40146 during the ascent
(BP7 sets ContactPlane to the cottage main floor poly, which lives
in cottage cell's BSP not cellar's). Ours stays at cellar 0xA9B40147,
where the ceiling poly 0x0020 correctly rejects the lifted sphere.

No fix attempted this session per CLAUDE.md discipline check
(3+ failed fixes = handoff). Full slice 5 evidence + concrete
next-session pickup steps at docs/research/2026-05-22-a6-p3-slice5-handoff.md.
ISSUES.md #98 updated with the corrected diagnosis.

Test baseline: 1148 + 8 pre-existing fail. Maintained.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-22 20:02:15 +02:00
parent c479ea68a3
commit cf3deff7c2
7 changed files with 26489 additions and 9 deletions

View file

@ -843,13 +843,31 @@ public static class BSPQuery
if (node.Type == BSPNodeType.Leaf)
{
if (node.Polygons.Count == 0) return false;
if (centerCheck && node.Solid != 0) return true;
if (centerCheck && node.Solid != 0)
{
// A6.P3 slice 5 (2026-05-22): record the solid-leaf cause for
// the [place-fail] probe. Side-channel pattern matches
// LastBspHitPoly — gated on the probe flag so the production
// path pays only one boolean check.
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
PhysicsDiagnostics.LastPlacementFailSolidLeaf = true;
return true;
}
if (!NodeIntersects(node, sphere)) return false;
foreach (ushort polyId in node.Polygons)
{
if (!resolved.TryGetValue(polyId, out var poly)) continue;
if (HitsSphere(poly, sphere)) return true;
if (HitsSphere(poly, sphere))
{
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
{
PhysicsDiagnostics.LastPlacementFailPolyId = poly.Id;
PhysicsDiagnostics.LastPlacementFailPolyNormal = poly.Plane.Normal;
PhysicsDiagnostics.LastPlacementFailPolyD = poly.Plane.D;
}
return true;
}
}
return false;
}
@ -1647,12 +1665,40 @@ public static class BSPQuery
{
const bool clearCell = true;
// A6.P3 slice 5 (2026-05-22) — reset the placement-fail side-channel
// before each SphereIntersectsSolidInternal call so a leftover
// value from a prior call (or from sphere0 if sphere1 is the actual
// failure) doesn't leak into the [place-fail] log.
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
{
PhysicsDiagnostics.LastPlacementFailPolyId = 0;
PhysicsDiagnostics.LastPlacementFailSolidLeaf = false;
}
if (SphereIntersectsSolidInternal(root, resolved, sphere0, clearCell))
{
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
PhysicsDiagnostics.LogPlacementFail(
"Path1.sphere0", sphere0.Center, sphere0.Radius, 0,
path.CheckCellId, worldOrigin, obj.Ethereal);
return TransitionState.Collided;
}
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
{
PhysicsDiagnostics.LastPlacementFailPolyId = 0;
PhysicsDiagnostics.LastPlacementFailSolidLeaf = false;
}
if (sphere1 is not null &&
SphereIntersectsSolidInternal(root, resolved, sphere1, clearCell))
{
if (PhysicsDiagnostics.ProbePlacementFailEnabled)
PhysicsDiagnostics.LogPlacementFail(
"Path1.sphere1", sphere1.Center, sphere1.Radius, 1,
path.CheckCellId, worldOrigin, obj.Ethereal);
return TransitionState.Collided;
}
return TransitionState.OK;
}