fix(physics): restore retail path-6 collision response

This commit is contained in:
Erik 2026-07-31 13:44:55 +02:00
parent acec33eca8
commit 75b6f6b6c9
8 changed files with 523 additions and 223 deletions

View file

@ -396,18 +396,12 @@ public class BSPStepUpTests
/// <summary>
/// Airborne mover descending toward a steep slope (normal.Z &lt; FloorZ):
/// Path 6 returns <see cref="TransitionState.Slid"/> and does NOT set
/// the Collide flag — the steep-normal slide-tangent branch (L.4,
/// commit b1af56e, 2026-04-30) intercepts the hit before SetCollide is
/// called and projects the move along the steep face instead, keeping the
/// body airborne with the falling animation.
///
/// <para>This is a documented intentional deviation from retail (retail calls
/// set_collide unconditionally; our interim port uses slide-tangent while
/// the retail step_up_slide / cliff_slide chain port is completed).</para>
/// retail Path 6 still calls SetCollide, installs LandingZ, and returns
/// Adjusted. Polygon steepness is handled by the outer transition chain,
/// not by a BSP-layer tangent shortcut.
/// </summary>
[Fact]
public void C3_Path6_AirborneMoverHitsSteepSlope_ReturnsSlid()
public void C3_Path6_AirborneMoverHitsSteepSlope_DefersThroughSetCollide()
{
var (root, resolved) = BSPStepUpFixtures.SlopedUnwalkable();
@ -427,13 +421,11 @@ public class BSPStepUpTests
root, resolved, t, localSphere, null,
currPos, Vector3.UnitZ, 1.0f);
// L.4 slide-tangent (b1af56e, 2026-04-30): steep polygon hit by
// airborne sphere returns Slid (not Adjusted) and does NOT set
// the Collide flag — the into-wall displacement is removed and
// CollisionNormal/SlidingNormal are set instead.
Assert.Equal(TransitionState.Slid, result);
Assert.False(t.SpherePath.Collide,
"Collide must NOT be set when the L.4 steep-slope slide-tangent fires");
Assert.Equal(TransitionState.Adjusted, result);
Assert.True(t.SpherePath.Collide);
Assert.Equal(PhysicsGlobals.LandingZ, t.SpherePath.WalkableAllowance);
Assert.False(t.CollisionInfo.CollisionNormalValid);
Assert.False(t.CollisionInfo.SlidingNormalValid);
}
// =========================================================================
@ -594,17 +586,7 @@ public class BSPStepUpTests
/// every frame replays the same hard stop and the character hangs in falling
/// animation until another correction breaks the loop.
/// </summary>
[Fact(Skip = "Issue #116 shape-2 — the engine slides IN-FRAME to Z=1.92 " +
"on the first airborne wall frame; this pin expects an L.2c hard stop " +
"at Z=2.0. Ghidra (2026-06-12) confirms retail CSphere::slide_sphere " +
"(0x00537440) applies the slide IN-FRAME (add_offset_to_check_pos → " +
"SLID_TS), so our 1.92 is faithful TO slide_sphere and the Z=2.0 " +
"expectation is the SUSPECT half — but whether retail's first " +
"airborne frame REACHES slide_sphere (→1.92) or hard-stops upstream " +
"(collide_with_environment dispatch / no last-known plane) needs a " +
"cdb trace of an airborne wall hit before flipping the assertion. The " +
"#116 threshold fix (EpsilonSq→F_EPSILON) did NOT change this — the D4 " +
"offset is a real slide, not degenerate. See docs/ISSUES.md #116.")]
[Fact]
public void D4_AirborneMover_TallWall_PersistsSlidingNormalAcrossFrames()
{
var (root, resolved) = BSPStepUpFixtures.TallWall();
@ -630,6 +612,8 @@ public class BSPStepUpTests
Assert.True(body.TransientState.HasFlag(TransientStateFlags.Sliding),
"First airborne wall hit should cache SlidingNormal for the next frame.");
// Path 6's primary-sphere SetCollide hard-stops this first frame; the
// persisted normal then permits the downward tangent on frame two.
Assert.Equal(2.0f, frame1.Position.Z, precision: 3);
var frame2 = engine.ResolveWithTransition(