fix(physics): TS-4 retired — Path-6 steep-poly shortcut deleted
Per docs/research/2026-07-30-ts4-116-oracle-plan.md §1, §4 item 2 (the
decisive TS-4 confirming run). Retail's BSP layer has NO steepness test at
all (acclient_2013_pseudo_c.txt:323783-323821, 0x0053a793) — every airborne
hit, steep or shallow, falls through to the same unconditional
`SetCollide` + `Adjusted`. The L.4 slide-tangent shortcut (worldNormal.Z <
FloorZ -> project-and-Slid, with its own SetSlidingNormal write) is deleted
from both BSPQuery.cs's and FlatBspQuery.cs's Path 6 sphere0 branch.
Fixing FlatBspQuery.cs (the flat/indexed engine Slice I6/I7 made
production-authoritative) was necessary in this same commit: it carried an
exact structural duplicate of the shortcut, caught by
FlatBspQueryDifferentialTests.InstalledDat_LargeRandomizedSweep_HasZeroBitMismatch
(graph=Adjusted vs flat=Slid) once the graph side was fixed alone. Its
sphere1 branch is also brought in line with the #116 shape-1 fix landed
in db2889af (direct Collided + SetCollisionNormal instead of the deferred
SetCollide/shortcut treatment) — that parity gap existed since shape-1's
commit only touched BSPQuery.cs and the randomized differential sweep
didn't happen to exercise the narrow foot-clear/head-hit case until this
session's broader change surfaced it.
DECISIVE CONFIRMING RUN (Ts4SteepRoofWedgeCaptureTests, per the plan's own
required test-first order): added
FallOntoSteepSlope_WithHorizontalVelocity_NeverFreezesForOverHalfASecond_AndReachesFloor
— the same steep-roof drop as the existing pure-vertical fixture, but with
a small residual horizontal velocity (vx=-0.3 m/s), matching the realistic
live-play input (WASD, jump momentum) that validated the shortcut on
2026-04-30. With the shortcut removed, this variant converges cleanly to
the flat floor with zero freeze. The pure-vertical fixture, run
shortcut-removed, DOES still freeze — per the oracle plan's root-cause
trace (§1.2 Step E), this is the DEGENERATE case: AdjustOffset's crease
projection (Cross(ContactPlane.Normal, SlidingNormal)) is mathematically
orthogonal to a purely-Z gravity offset, crushing it to zero every tick
before TransitionalInsert can run again — present identically in the raw
decomp, ACE's port, and this port. Renamed and re-asserted as a PINNED
known-degenerate test
(FallOntoSteepSlope_PureVertical_FreezesAtDegenerateFixedPoint_RetailParity)
rather than treated as a bug. Filed as register row AD-56.
BSPStepUpTests.C3_Path6_AirborneMoverHitsSteepSlope_ReturnsSlid pinned the
OLD shortcut's Slid-no-Collide behavior directly; renamed to
...ReturnsAdjustedAndSetsCollide and corrected to the retail-faithful
Adjusted/Collide=true outcome.
Register: TS-4 row retired (struck through, retirement note); AD-56 filed
for the pure-vertical degenerate case; the retire-next shortlist's TS-4
entry removed and renumbered.
Full AcDream.Core.Tests suite: 4060 passed / 2 skipped (D4 stays Skip-tagged
in this commit; its own un-skip is a separate, dependent test-only commit
for #116 shape-2), no regressions.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
db2889afda
commit
5e2be19b4e
5 changed files with 252 additions and 154 deletions
|
|
@ -1979,6 +1979,17 @@ public static class BSPQuery
|
|||
FindWalkableInternal(root, resolved, path, ref validPos, movement, localSpaceZ,
|
||||
ref hitPoly, ref _hitPolyId, ref changed);
|
||||
|
||||
// #116 shape-2 instrumentation (Campaign P final physics slice,
|
||||
// 2026-07-30; docs/research/2026-07-30-ts4-116-oracle-plan.md
|
||||
// §3.3 step 1): names whether Path 4's find_walkable probe found
|
||||
// a candidate for this specific poly set, settling the D4
|
||||
// first-airborne-frame routing question without a live cdb trace.
|
||||
if (PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
{
|
||||
Console.WriteLine(System.FormattableString.Invariant(
|
||||
$"[path4-diag] changed={changed} hitPoly={(hitPoly is not null)}"));
|
||||
}
|
||||
|
||||
if (changed && hitPoly is not null)
|
||||
{
|
||||
// ACE: var offset = LocalToGlobalVec(validPos.Center - localSphere.Center) * scale
|
||||
|
|
@ -2196,57 +2207,35 @@ public static class BSPQuery
|
|||
|
||||
var worldNormal0 = L2W(hitPoly0!.Plane.Normal);
|
||||
|
||||
// L.4 slide-tangent for steep airborne hits (2026-04-30).
|
||||
// TS-4 RETIRED (Campaign P final physics slice, 2026-07-30;
|
||||
// docs/research/2026-07-30-ts4-116-oracle-plan.md §1, §4 item 2).
|
||||
// The L.4 steep-poly slide-tangent shortcut that used to live
|
||||
// here (worldNormal0.Z < FloorZ → project-and-Slid,
|
||||
// SetCollisionNormal + SetSlidingNormal) is deleted. Retail's
|
||||
// BSP layer never differentiates by slope here — it calls
|
||||
// SetCollide unconditionally for every hit, steep or shallow
|
||||
// (acclient_2013_pseudo_c.txt:323783-323821, 0x0053a793;
|
||||
// no steepness test at this layer at all), and never writes
|
||||
// collision_info.sliding_normal from the BSP/sphere layer —
|
||||
// only validate_transition (0x0050ac21/0x0050aa70) does.
|
||||
//
|
||||
// For polygons too steep to walk on (worldNormal.Z < FloorZ),
|
||||
// skip the SetCollide → Path-4 → ContactPlane landing chain.
|
||||
// That chain commits the body to the steep surface, leading
|
||||
// to the "stuck in falling animation on the roof" bug — once
|
||||
// grounded with a steep ContactPlane, our step_up_slide /
|
||||
// cliff_slide / edge_slide chain can't produce smooth
|
||||
// descent and the body wedges or "falls a bit at a time"
|
||||
// when bumped.
|
||||
// The decisive confirming run (Ts4SteepRoofWedgeCaptureTests):
|
||||
// with the shortcut removed, a steep-roof fall carrying a
|
||||
// small residual horizontal velocity (the realistic case —
|
||||
// WASD input, jump momentum) converges cleanly to the flat
|
||||
// floor with no freeze
|
||||
// (FallOntoSteepSlope_WithHorizontalVelocity_...). Only the
|
||||
// DEGENERATE pure-vertical drop (zero horizontal velocity)
|
||||
// still freezes — and that freeze is retail-faithful too
|
||||
// (see the pinned FallOntoSteepSlope_NeverFreezesFor... test
|
||||
// and AD-56 for the mechanism: AdjustOffset's crease
|
||||
// projection, Cross(ContactPlane.Normal, SlidingNormal), is
|
||||
// mathematically orthogonal to a purely-Z input offset,
|
||||
// crushing it to zero every tick and short-circuiting before
|
||||
// TransitionalInsert runs again — present identically in the
|
||||
// raw decomp, ACE's port, and this port).
|
||||
//
|
||||
// Instead: project the move along the steep face (remove
|
||||
// the into-wall displacement), set CollisionNormal +
|
||||
// SlidingNormal, return Slid. Same shape as Path 5's
|
||||
// step-up fallback (line 1545-1547) and CylinderCollision
|
||||
// (TransitionTypes.cs:1518-1522). Position is updated in-
|
||||
// place; on the next resolver iteration the sphere is
|
||||
// outside the poly, FindCollisions returns OK, and
|
||||
// ValidateTransition commits the new position. Body stays
|
||||
// airborne, falling animation continues, and gravity's
|
||||
// tangent component drifts the body downhill until it
|
||||
// slides off the slope's edge.
|
||||
//
|
||||
// This is a deliberate deviation from retail (retail uses
|
||||
// SetCollide unconditionally and lets find_walkable +
|
||||
// step_up_slide produce the slide). Validated against
|
||||
// retail debugger trace 2026-04-30: retail body did not
|
||||
// wedge; our retail-faithful port DID wedge because we're
|
||||
// missing implementation details of the step_up_slide /
|
||||
// cliff_slide chain on grounded-steep movement. The
|
||||
// slide-tangent here produces user-acceptable behavior
|
||||
// (slides off naturally) while the deeper chain port is
|
||||
// researched. Filed as L.5+ followup for retail-strict.
|
||||
if (worldNormal0.Z < PhysicsGlobals.FloorZ)
|
||||
{
|
||||
Vector3 currWorld = path.GlobalCurrCenter[0].Origin;
|
||||
Vector3 endWorld = path.GlobalSphere[0].Origin;
|
||||
Vector3 gDelta = endWorld - currWorld;
|
||||
float diff = Vector3.Dot(worldNormal0, gDelta);
|
||||
if (diff < 0f)
|
||||
path.AddOffsetToCheckPos(-worldNormal0 * diff);
|
||||
|
||||
collisions.SetCollisionNormal(worldNormal0);
|
||||
collisions.SetSlidingNormal(worldNormal0);
|
||||
// L.2d slice 1 (2026-05-13): diagnostic side-channel.
|
||||
if (PhysicsDiagnostics.ProbeBuildingEnabled || PhysicsDiagnostics.ProbeIndoorBspEnabled)
|
||||
PhysicsDiagnostics.LastBspHitPoly = hitPoly0;
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
// ─── SetCollide response (shallow / walkable) ───────────
|
||||
// ─── SetCollide response (unconditional, retail-faithful) ──
|
||||
// Per retail (acclient_2013_pseudo_c.txt:323783-323821).
|
||||
path.SetCollide(worldNormal0);
|
||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||
|
|
|
|||
|
|
@ -2034,24 +2034,18 @@ internal static class FlatBspQuery
|
|||
localToWorld);
|
||||
}
|
||||
|
||||
// TS-4 RETIRED (Campaign P final physics slice, 2026-07-30;
|
||||
// docs/research/2026-07-30-ts4-116-oracle-plan.md §1, §4 item 2).
|
||||
// Retail's BSP layer has no steepness test at this layer at all
|
||||
// (acclient_2013_pseudo_c.txt:323783-323821, 0x0053a793) — every
|
||||
// hit (steep or shallow) falls through to the same unconditional
|
||||
// SetCollide. The old L.4 slide-tangent shortcut (worldNormal0.Z
|
||||
// < FloorZ -> project-and-Slid with its own SetSlidingNormal
|
||||
// write) is deleted; see BSPQuery.cs's Path 6 for the mirrored
|
||||
// fix and full citation. Kept in parity with BSPQuery.cs per the
|
||||
// FlatBspQueryDifferentialTests graph-vs-flat comparison.
|
||||
Vector3 worldNormal0 = LocalToWorld(
|
||||
tree.PolygonTable.Polygons[defaultHitPolygonIndex0].Plane.Normal);
|
||||
if (worldNormal0.Z < PhysicsGlobals.FloorZ)
|
||||
{
|
||||
Vector3 currentWorld =
|
||||
path.GlobalCurrCenter[0].Origin;
|
||||
Vector3 endWorld =
|
||||
path.GlobalSphere[0].Origin;
|
||||
Vector3 globalDelta = endWorld - currentWorld;
|
||||
float difference = Vector3.Dot(worldNormal0, globalDelta);
|
||||
if (difference < 0f)
|
||||
path.AddOffsetToCheckPos(-worldNormal0 * difference);
|
||||
|
||||
collisions.SetCollisionNormal(worldNormal0);
|
||||
collisions.SetSlidingNormal(worldNormal0);
|
||||
RecordDiagnosticHit(tree, defaultHitPolygonIndex0);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
path.SetCollide(worldNormal0);
|
||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||
|
|
@ -2073,30 +2067,22 @@ internal static class FlatBspQuery
|
|||
|
||||
if (defaultHit1 || defaultHitPolygonIndex1 >= 0)
|
||||
{
|
||||
// #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)
|
||||
// find_collisions dispatch, when the FOOT sphere is
|
||||
// completely clear but the HEAD sphere hits or near-misses,
|
||||
// records the head polygon's normal directly and hard-stops
|
||||
// rather than deferring through SetCollide/Adjusted:
|
||||
// pc:323824-323834 (0x0053a793/0x0053a7a4), cross-checked
|
||||
// against ACE BSPTree.cs:221-230. See BSPQuery.cs's Path 6
|
||||
// for the mirrored fix and full citation.
|
||||
Vector3 worldNormal1 = LocalToWorld(
|
||||
tree.PolygonTable.Polygons[defaultHitPolygonIndex1].Plane.Normal);
|
||||
if (worldNormal1.Z < PhysicsGlobals.FloorZ)
|
||||
{
|
||||
Vector3 currentWorld =
|
||||
path.GlobalCurrCenter[0].Origin;
|
||||
Vector3 endWorld =
|
||||
path.GlobalSphere[0].Origin;
|
||||
Vector3 globalDelta = endWorld - currentWorld;
|
||||
float difference =
|
||||
Vector3.Dot(worldNormal1, globalDelta);
|
||||
if (difference < 0f)
|
||||
path.AddOffsetToCheckPos(-worldNormal1 * difference);
|
||||
|
||||
collisions.SetCollisionNormal(worldNormal1);
|
||||
collisions.SetSlidingNormal(worldNormal1);
|
||||
RecordDiagnosticHit(tree, defaultHitPolygonIndex1);
|
||||
return TransitionState.Slid;
|
||||
}
|
||||
|
||||
path.SetCollide(worldNormal1);
|
||||
path.WalkableAllowance = PhysicsGlobals.LandingZ;
|
||||
collisions.SetCollisionNormal(worldNormal1);
|
||||
RecordDiagnosticHit(tree, defaultHitPolygonIndex1);
|
||||
return TransitionState.Adjusted;
|
||||
return TransitionState.Collided;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue