fix(physics): #116 shape-1 — Path-6 head-sphere direct Collided return
Per docs/research/2026-07-30-ts4-116-oracle-plan.md §2.3-§2.4: retail's
airborne (not-yet-Contact) BSPTREE::find_collisions dispatch, when the
FOOT sphere is completely clear but the HEAD sphere hits or near-misses,
does not defer through SetCollide/Adjusted (nor the steep-poly
slide-tangent shortcut) — it records the head polygon's normal directly
and hard-stops: pc:323824-323834 (0x0053a793/0x0053a7a4), independently
cross-checked against ACE BSPTree.cs:221-230 (`SetCollisionNormal` +
`return TransitionState.Collided;`), an exact structural match confirming
this isn't a BN misdecompile. BSPQuery.cs's Path 6 `hasSphere1` branch now
does the same: `collisions.SetCollisionNormal(worldNormal1); return
TransitionState.Collided;`, replacing the old steep-shortcut-or-deferred-
SetCollide handling. This mechanically retires one of TS-4's two
`SetSlidingNormal` write sites (sphere1's) ahead of TS-4's own item.
Added two permanent diagnostics gated on the existing
PhysicsDiagnostics.ProbeIndoorBspEnabled flag (`[path-dispatch]` at
FindCollisionsCore entry, `[path5-diag]` inside Path 5) to make future
BSPQuery dispatch tracing cheaper.
HONEST RESULT of the plan's own confirming instrumentation (re-run of
DoorBugTrajectoryReplayTests.Diagnostic_Tick22760_DumpEngineInternals):
this fix does NOT change the tick-22760 outcome (harness still cn=(0,0,1)
vs live cn=(0,+1,0)). The new dispatch-entry probes show the tick-22760
mover is GROUNDED (Contact set), so it never reaches Path 6 at all — it
dispatches Path 5 -> StepSphereDown (Path 3, both DoStepDown half-steps
fail) -> EdgeSlideAfterStepDownFailed -> SpherePath.PrecipiceSlide, whose
find_crossed_edge-false fallback returns Collided with NO collision-normal
write. A fresh byte-level read of retail's SPHEREPATH::precipice_slide
(pc:274316-274326, 0x0050cc80) confirms this is byte-exact retail
behavior (`if (eax == 0) { walkable = 0; return 2; }`, no
set_collision_normal call) — not a bug. The real tick-22760 divergence is
further upstream, most likely this test's simplified door registration
(BuildEngineWithDoorFixture) not placing the door's BSP where live retail
actually intersected it, or a walkable-polygon state-capture gap — see
the research doc's Addendum 2 for the full trace and open candidates.
#116 shape-1 is therefore NARROWED, not closed: the Path-6 fix is a real,
independent retail-faithfulness improvement; the tick-22760 acceptance
criterion is not met by it and needs further harness/geometry work before
any further code change.
Full AcDream.Core.Tests suite: 4059 passed / 2 skipped, no regressions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7e1be3def0
commit
db2889afda
2 changed files with 158 additions and 22 deletions
|
|
@ -1854,6 +1854,17 @@ public static class BSPQuery
|
|||
|
||||
var movement = sphere0.Center - localCurrCenter;
|
||||
|
||||
// #116 shape-1 instrumentation (Campaign P final physics slice,
|
||||
// 2026-07-30; docs/research/2026-07-30-ts4-116-oracle-plan.md §2.4):
|
||||
// dispatcher-entry probe naming which of Path 1-6 this call will
|
||||
// take, so the tick-22760 replay's per-call path sequence can be
|
||||
// read off directly instead of inferred from later probes.
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[path-dispatch] insertType={path.InsertType} obstructionEthereal={path.ObstructionEthereal} checkWalkable={path.CheckWalkable} stepDown={path.StepDown} collide={path.Collide} contact={((obj.State & ObjectInfoState.Contact) != 0)} hasSphere1={hasSphere1}"));
|
||||
}
|
||||
|
||||
// A6.P1: snapshot dispatcher entry for the [push-back-disp] probe.
|
||||
// Emitted before path selection so the captured state reflects
|
||||
// the inputs the dispatcher routes on. The returnState=-1 sentinel
|
||||
|
|
@ -2039,6 +2050,19 @@ public static class BSPQuery
|
|||
bool hit0 = SphereIntersectsPolyInternal(root, resolved, sphere0, movement,
|
||||
ref hitPoly0, ref contact0);
|
||||
|
||||
// #116 shape-1 instrumentation (Campaign P final physics slice,
|
||||
// 2026-07-30; docs/research/2026-07-30-ts4-116-oracle-plan.md
|
||||
// §2.4): the tick-22760 divergence turned out to be a GROUNDED
|
||||
// mover (Contact set), so it dispatches here (Path 5), not the
|
||||
// not-yet-Contact Path 6 the oracle plan's shape-1 hypothesis
|
||||
// targeted. This probe pins exactly which of the four Path-5
|
||||
// sub-branches fires for the failing capture.
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[path5-diag] hit0={hit0} hitPoly0={(hitPoly0 is not null)} hasSphere1={hasSphere1}"));
|
||||
}
|
||||
|
||||
// A6.P4 door inside-out fix (2026-05-25). Retail distinguishes
|
||||
// FULL HIT (eax_10 != 0 → step_sphere_up early-return) from
|
||||
// NEAR-MISS (eax_10 == 0 but var_5c != 0 → set_neg_poly_hit).
|
||||
|
|
@ -2107,6 +2131,14 @@ public static class BSPQuery
|
|||
bool hit1 = SphereIntersectsPolyInternal(root, resolved, sphere1, movement,
|
||||
ref hitPoly1, ref contact1);
|
||||
|
||||
// #116 shape-1 instrumentation (see the hit0/hitPoly0 probe
|
||||
// above for the full citation).
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[path5-diag] hit1={hit1} hitPoly1={(hitPoly1 is not null)}"));
|
||||
}
|
||||
|
||||
if (hit1)
|
||||
{
|
||||
// Sphere 1 (head) full hit → slide_sphere.
|
||||
|
|
@ -2234,33 +2266,38 @@ public static class BSPQuery
|
|||
|
||||
if (hit1 || hitPoly1 is not null)
|
||||
{
|
||||
// #116 shape-1 fix (Campaign P final physics slice,
|
||||
// 2026-07-30; docs/research/2026-07-30-ts4-116-oracle-plan.md
|
||||
// §2.3-§2.4). Retail's airborne (not-yet-Contact)
|
||||
// BSPTREE::find_collisions dispatch does NOT defer a
|
||||
// foot-clear/head-hit through SetCollide+Adjusted (nor
|
||||
// through the steep-poly slide-tangent shortcut) — it
|
||||
// records the head polygon's normal directly and hard-
|
||||
// stops, unconditionally, regardless of the poly's
|
||||
// steepness:
|
||||
// localtoglobalvec(sphere_path.localspace_pos, &n, &poly->plane.N);
|
||||
// COLLISIONINFO::set_collision_normal(&collision_info, &n);
|
||||
// return 2; // COLLIDED_TS
|
||||
// (acclient_2013_pseudo_c.txt:323824-323834,
|
||||
// 0x0053a793/0x0053a7a4). Independently cross-checked
|
||||
// against ACE's clean-language port, BSPTree.cs:221-230
|
||||
// (`collisions.SetCollisionNormal(collisionNormal);
|
||||
// return TransitionState.Collided;`) — an exact
|
||||
// structural match confirming this isn't a BN
|
||||
// misdecompile. This was the tick-22760 divergence:
|
||||
// acdream's foot-clear/head-hit case previously fell
|
||||
// through to SetCollide (deferred Adjusted, no
|
||||
// CollisionNormal write) or the steep-poly Slid
|
||||
// shortcut, so ValidateTransition's UnitZ default
|
||||
// clobbered the real door-face normal before the
|
||||
// caller ever saw it.
|
||||
var worldNormal1 = L2W(hitPoly1!.Plane.Normal);
|
||||
|
||||
// L.4 slide-tangent: same steep-poly slide for head-sphere.
|
||||
if (worldNormal1.Z < PhysicsGlobals.FloorZ)
|
||||
{
|
||||
Vector3 currWorld = path.GlobalCurrCenter[0].Origin;
|
||||
Vector3 endWorld = path.GlobalSphere[0].Origin;
|
||||
Vector3 gDelta = endWorld - currWorld;
|
||||
float diff = Vector3.Dot(worldNormal1, gDelta);
|
||||
if (diff < 0f)
|
||||
path.AddOffsetToCheckPos(-worldNormal1 * diff);
|
||||
|
||||
collisions.SetCollisionNormal(worldNormal1);
|
||||
collisions.SetSlidingNormal(worldNormal1);
|
||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
// Head sphere hit shallow surface: SetCollide.
|
||||
path.SetCollide(worldNormal1);
|
||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||
collisions.SetCollisionNormal(worldNormal1);
|
||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly1;
|
||||
return TransitionState.Adjusted;
|
||||
return TransitionState.Collided;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue