Revert "fix(physics): TS-4 retired — Path-6 steep-poly shortcut deleted"
This reverts commit 5e2be19b4e.
This commit is contained in:
parent
2e27d066e8
commit
a8a7d64b47
5 changed files with 156 additions and 254 deletions
|
|
@ -407,7 +407,7 @@ public class BSPStepUpTests
|
|||
/// the retail step_up_slide / cliff_slide chain port is completed).</para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void C3_Path6_AirborneMoverHitsSteepSlope_ReturnsAdjustedAndSetsCollide()
|
||||
public void C3_Path6_AirborneMoverHitsSteepSlope_ReturnsSlid()
|
||||
{
|
||||
var (root, resolved) = BSPStepUpFixtures.SlopedUnwalkable();
|
||||
|
||||
|
|
@ -427,17 +427,13 @@ public class BSPStepUpTests
|
|||
root, resolved, t, localSphere, null,
|
||||
currPos, Vector3.UnitZ, 1.0f);
|
||||
|
||||
// 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) — a steep
|
||||
// airborne hit now returns Adjusted and sets the Collide flag,
|
||||
// exactly like a shallow hit. The old L.4 slide-tangent shortcut
|
||||
// (Slid, no Collide, its own SetSlidingNormal write) is deleted.
|
||||
Assert.Equal(TransitionState.Adjusted, result);
|
||||
Assert.True(t.SpherePath.Collide,
|
||||
"Collide must be set for a steep Path-6 hit, matching retail's " +
|
||||
"unconditional SetCollide (acclient_2013_pseudo_c.txt:323783-323821).");
|
||||
// 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");
|
||||
}
|
||||
|
||||
// =========================================================================
|
||||
|
|
|
|||
|
|
@ -7,58 +7,50 @@ using Xunit.Abstractions;
|
|||
namespace AcDream.Core.Tests.Physics;
|
||||
|
||||
/// <summary>
|
||||
/// 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
|
||||
/// Path-6 steep-poly slide-tangent shortcut (worldNormal.Z < FloorZ →
|
||||
/// project-and-Slid, with its own SetSlidingNormal write) is deleted from
|
||||
/// <see cref="BSPQuery"/>'s Path 6; both sphere0 and sphere1 steep hits now
|
||||
/// fall through to the same unconditional <c>SetCollide</c> retail uses for
|
||||
/// every hit (acclient_2013_pseudo_c.txt:323783-323821, 0x0053a793 — no
|
||||
/// steepness test at the BSP layer at all).
|
||||
///
|
||||
/// <para>
|
||||
/// This class builds a dat-free multi-frame replay from the existing
|
||||
/// <see cref="BSPStepUpFixtures.SlopedUnwalkable"/> geometry (a 63.4° slope,
|
||||
/// normal.Z ≈ 0.447 — below <c>PhysicsGlobals.FloorZ</c> ≈ 0.6642 but above
|
||||
/// <c>PhysicsGlobals.LandingZ</c> ≈ 0.0871, exactly the band the retired
|
||||
/// shortcut used to target) using the same
|
||||
/// Campaign P Slice P2, TS-4 (Section 6 Step 3): the 2026-04-30 "L.4" fixture
|
||||
/// capture required before the Path-6 steep-poly slide-tangent shortcut may be
|
||||
/// removed (<c>docs/research/2026-07-30-response-layer-edge-family-pseudocode.md</c>
|
||||
/// §4, §6 Step 3). The original repro was a live-client jump onto a steep
|
||||
/// roof that got the body "stuck in falling animation" for many frames; no
|
||||
/// captured fixture from that live session survives in the repo (checked
|
||||
/// <c>docs/research/2026-04-30-*</c> and the L.4 commit `b1af56e`), so this
|
||||
/// test builds a dat-free multi-frame replay from the existing
|
||||
/// <see cref="BSPStepUpFixtures.SlopedUnwalkable"/> geometry (a 63.4°
|
||||
/// slope, normal.Z ≈ 0.447 — below <c>PhysicsGlobals.FloorZ</c> ≈ 0.6642 but
|
||||
/// above <c>PhysicsGlobals.LandingZ</c> ≈ 0.0871, i.e. exactly the band the
|
||||
/// L.4 commit's own steep-poly shortcut targets) using the same
|
||||
/// <c>PhysicsEngine.ResolveWithTransition</c> multi-frame replay idiom as
|
||||
/// <c>Issue185OutdoorStairsSeamReplayTests</c>, gravity integrated between
|
||||
/// resolves exactly as <c>PhysicsBody.UpdatePhysicsInternal</c> would, for
|
||||
/// up to 3 simulated seconds (90 ticks at 30 Hz — retail's physics tick
|
||||
/// rate, #32 L.5).
|
||||
/// <c>Issue185OutdoorStairsSeamReplayTests</c>.
|
||||
///
|
||||
/// <para>
|
||||
/// A body falls from directly above the slope's mid-face, integrating
|
||||
/// gravity between resolves exactly as <c>PhysicsBody.UpdatePhysicsInternal</c>
|
||||
/// would, for up to 3 simulated seconds (90 ticks at 30 Hz — retail's physics
|
||||
/// tick rate, #32 L.5). "Wedged" is defined precisely, matching the original
|
||||
/// bug report ("stuck in falling animation on the roof" for many consecutive
|
||||
/// frames): the body's position stops changing (within 1 mm) for more than
|
||||
/// 15 consecutive ticks (0.5 s) while never reaching the flat reference
|
||||
/// floor at x<0, z=0. A healthy resolution reaches the flat floor (Z ≈
|
||||
/// <see cref="BSPStepUpFixtures.SphereRadius"/>) well before the 90-tick
|
||||
/// budget expires, whether it does so by retail's own COLLIDED-then-fall
|
||||
/// bounce (this file's own git history documents that as retail's actual
|
||||
/// behavior for a clean Path-6 steep hit with no pre-existing contact plane)
|
||||
/// or by committing to the steep "walkable" surface via the permissive
|
||||
/// <c>LandingZ</c> threshold (matching <c>CTransition::check_walkable</c>,
|
||||
/// pc:273202, <c>0.0871556997f</c>) and then downhill-drifting off it via
|
||||
/// the already-ported TS-1 CliffSlide chain.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// The oracle plan's own root-cause trace (§1.2) found the freeze
|
||||
/// mechanism one layer downstream of Path 6: Path 6's faithful
|
||||
/// <c>SetCollide</c> doesn't reposition the sphere; the immediate retry
|
||||
/// routes to Path 4 (<c>find_walkable</c>), which commits a real steep
|
||||
/// <c>ContactPlane</c> via the permissive <c>LandingZ</c> gate; the NEXT
|
||||
/// tick's <c>AdjustOffset</c> then projects the (by-then gravity-only)
|
||||
/// offset through <c>Cross(ContactPlane.Normal, SlidingNormal)</c> — and
|
||||
/// for a PURELY VERTICAL offset that cross product annihilates it exactly
|
||||
/// (§1.2 Step E), tripping the abort-small-offset guard before
|
||||
/// <c>TransitionalInsert</c> can run again. This is present identically in
|
||||
/// the raw retail decomp, ACE's port, and this port (§1.2, §1.3) — it is
|
||||
/// not a bug, it is what a truly zero-horizontal-velocity plumb drop onto a
|
||||
/// steep surface does in every one of the three references. A live player
|
||||
/// almost never produces this input (WASD, camera-relative movement, and
|
||||
/// even float noise inject some horizontal component), which is why the
|
||||
/// 2026-04-30 live-client debugger trace that originally validated the
|
||||
/// shortcut never observed the freeze.
|
||||
/// </para>
|
||||
///
|
||||
/// <para>
|
||||
/// Two fixtures, two different fates, per the plan's own decisive test
|
||||
/// (§4 item 2): <see cref="FallOntoSteepSlope_PureVertical_FreezesAtDegenerateFixedPoint_RetailParity"/>
|
||||
/// is the DEGENERATE case (zero horizontal velocity) — PINNED as a known,
|
||||
/// retail-matching freeze (see register row AD-56).
|
||||
/// <see cref="FallOntoSteepSlope_WithHorizontalVelocity_NeverFreezesForOverHalfASecond_AndReachesFloor"/>
|
||||
/// is the REALISTIC case (small residual horizontal velocity, matching the
|
||||
/// original live repro's actual trajectory shape) — it converges cleanly to
|
||||
/// the flat floor with no freeze, which is what made TS-4's removal safe to
|
||||
/// land.
|
||||
/// Run TWICE across this slice's git history: once with the Path-6 steep
|
||||
/// shortcut ACTIVE (pins today's baseline — always green, since the
|
||||
/// shortcut's own in-frame slide-tangent cannot wedge by construction), and
|
||||
/// once with it REMOVED (the retail-strict candidate). If both pass, TS-4's
|
||||
/// removal is evidenced safe and lands in the same commit that deletes the
|
||||
/// shortcut and its <c>SetSlidingNormal</c> writes. If the removed-shortcut
|
||||
/// run wedges, the shortcut stays and this file's result against ToT is the
|
||||
/// recorded evidence — see the commit message / research doc open questions
|
||||
/// for the outcome actually reached.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public class Ts4SteepRoofWedgeCaptureTests
|
||||
|
|
@ -120,26 +112,12 @@ public class Ts4SteepRoofWedgeCaptureTests
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// PINNED known-degenerate case (Campaign P final physics slice,
|
||||
/// 2026-07-30; docs/research/2026-07-30-ts4-116-oracle-plan.md §1.2-§1.3,
|
||||
/// §4 item 2; register row AD-56). A body falling PERFECTLY PLUMB (zero
|
||||
/// horizontal velocity) onto this 63.4° slope's mid-face lands, commits
|
||||
/// a steep <c>ContactPlane</c> via Path 4's permissive <c>LandingZ</c>
|
||||
/// gate, and then freezes at that exact position forever: the crease
|
||||
/// projection inside <c>AdjustOffset</c>
|
||||
/// (<c>Cross(ContactPlane.Normal, SlidingNormal)</c>) is mathematically
|
||||
/// orthogonal to a purely-Z offset, crushing it to zero every tick and
|
||||
/// tripping the abort-small-offset guard before <c>TransitionalInsert</c>
|
||||
/// runs again. This is retail-faithful — present identically in the raw
|
||||
/// decomp, ACE's port, and this port (§1.2) — and essentially
|
||||
/// unreachable in live play, where WASD input, camera-relative movement,
|
||||
/// and even float noise almost always inject some horizontal component
|
||||
/// (see <see cref="FallOntoSteepSlope_WithHorizontalVelocity_NeverFreezesForOverHalfASecond_AndReachesFloor"/>
|
||||
/// for the realistic case, which does NOT freeze). This test PINS the
|
||||
/// freeze as accepted parity rather than treating it as a bug to fix.
|
||||
/// Falls a player-flagged mover from directly above the 63.4° slope's
|
||||
/// mid-face and asserts it reaches the flat floor (or at minimum keeps
|
||||
/// making downward/downhill progress) without a >0.5s frozen stretch.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FallOntoSteepSlope_PureVertical_FreezesAtDegenerateFixedPoint_RetailParity()
|
||||
public void FallOntoSteepSlope_NeverFreezesForOverHalfASecond_AndReachesFloor()
|
||||
{
|
||||
var engine = MakeSlopeEngine();
|
||||
float r = BSPStepUpFixtures.SphereRadius;
|
||||
|
|
@ -152,121 +130,19 @@ public class Ts4SteepRoofWedgeCaptureTests
|
|||
};
|
||||
|
||||
// Start well above the slope's mid-face (slope spans x in [0,1], z in
|
||||
// [0,2] at that x-range), falling straight down with NO horizontal
|
||||
// component — the degenerate input this pin documents.
|
||||
// [0,2] at that x-range), falling straight down.
|
||||
Vector3 pos = new(0.5f, 0f, 3.0f);
|
||||
float fallVelocityZ = 0f;
|
||||
uint cell = CellId;
|
||||
|
||||
int frozenStreak = 0;
|
||||
bool frozeAsExpected = false;
|
||||
Vector3 frozenAtPosition = default;
|
||||
|
||||
for (int tick = 0; tick < MaxTicks; tick++)
|
||||
{
|
||||
fallVelocityZ += gravity * dt;
|
||||
Vector3 target = pos + new Vector3(0f, 0f, fallVelocityZ * dt);
|
||||
|
||||
var result = engine.ResolveWithTransition(
|
||||
currentPos: pos,
|
||||
targetPos: target,
|
||||
cellId: cell,
|
||||
sphereRadius: r,
|
||||
sphereHeight: r * 2f,
|
||||
stepUpHeight: 0.30f,
|
||||
stepDownHeight: 0.04f,
|
||||
isOnGround: false,
|
||||
body: body,
|
||||
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
|
||||
movingEntityId: 0x01000000u);
|
||||
|
||||
var newPos = result.Position;
|
||||
float moved = Vector3.Distance(newPos, pos);
|
||||
|
||||
if (moved < WedgeEpsilon)
|
||||
frozenStreak++;
|
||||
else
|
||||
frozenStreak = 0;
|
||||
|
||||
_out.WriteLine(
|
||||
$"t{tick,3}: pos=({newPos.X:F3},{newPos.Y:F3},{newPos.Z:F3}) " +
|
||||
$"moved={moved:F4} onGround={result.IsOnGround} onWalkable={result.OnWalkable} " +
|
||||
$"contact={result.InContact} vz={fallVelocityZ:F2} frozen={frozenStreak}");
|
||||
|
||||
pos = newPos;
|
||||
cell = result.CellId;
|
||||
body.Position = pos;
|
||||
|
||||
if (result.IsOnGround)
|
||||
fallVelocityZ = 0f;
|
||||
|
||||
if (frozenStreak > WedgeTickThreshold)
|
||||
{
|
||||
frozeAsExpected = true;
|
||||
frozenAtPosition = pos;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(frozeAsExpected,
|
||||
$"Expected the degenerate pure-vertical drop to freeze for more than " +
|
||||
$"{WedgeTickThreshold} consecutive ticks (retail-matching AdjustOffset " +
|
||||
"crease-projection degeneracy, AD-56) within the {MaxTicks}-tick budget — " +
|
||||
"it did not. Either the degenerate case no longer reproduces (re-evaluate " +
|
||||
"this pin against the oracle plan) or an unrelated regression changed the " +
|
||||
"slope-landing chain.");
|
||||
Assert.True(frozenAtPosition.X > 0f,
|
||||
"Expected the freeze to occur ON the steep slope (x>0), not at/after the " +
|
||||
$"flat reference floor; got x={frozenAtPosition.X:F3}.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Campaign P final physics slice, TS-4 decisive confirming run
|
||||
/// (<c>docs/research/2026-07-30-ts4-116-oracle-plan.md</c> §1.2 Step E,
|
||||
/// §1.3, §4 item 2). The pure-vertical fixture above is, per the oracle
|
||||
/// plan, the DEGENERATE case: <c>AdjustOffset</c>'s crease projection
|
||||
/// (<c>Cross(ContactPlane.Normal, SlidingNormal)</c> against a purely
|
||||
/// gravity-only offset) is mathematically annihilated by construction
|
||||
/// when the offset has zero horizontal component — <c>Dot(slideOffset,
|
||||
/// offset) = 0</c> exactly, because <c>slideOffset.Z = 0</c> and the
|
||||
/// offset is purely Z. Any lateral drift (WASD input, residual jump
|
||||
/// momentum — present in the original 2026-04-30 live-client repro that
|
||||
/// validated the shortcut, but NOT in the pure-vertical fixture above)
|
||||
/// survives that same cross product and should let <c>AdjustOffset</c>
|
||||
/// produce a small non-zero tangential offset each tick, moving the
|
||||
/// sphere off the exact collision point, avoiding the abort-small-offset
|
||||
/// short-circuit, and letting <c>TransitionalInsert</c> run again on
|
||||
/// subsequent ticks.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void FallOntoSteepSlope_WithHorizontalVelocity_NeverFreezesForOverHalfASecond_AndReachesFloor()
|
||||
{
|
||||
var engine = MakeSlopeEngine();
|
||||
float r = BSPStepUpFixtures.SphereRadius;
|
||||
const float dt = 1f / TicksPerSecond;
|
||||
const float gravity = -9.8f;
|
||||
|
||||
var body = new PhysicsBody
|
||||
{
|
||||
TransientState = TransientStateFlags.Active,
|
||||
};
|
||||
|
||||
// Same drop point as the pure-vertical fixture, but with a small
|
||||
// residual horizontal velocity toward the flat reference floor
|
||||
// (x<0) — the realistic "jumped/walked onto the roof with some
|
||||
// drift" case the L.4 shortcut's own validating trace exercised.
|
||||
Vector3 pos = new(0.5f, 0f, 3.0f);
|
||||
float fallVelocityZ = 0f;
|
||||
const float horizontalVelocityX = -0.3f;
|
||||
uint cell = CellId;
|
||||
|
||||
var positions = new List<Vector3>(MaxTicks) { pos };
|
||||
int frozenStreak = 0;
|
||||
bool reachedFloor = false;
|
||||
|
||||
for (int tick = 0; tick < MaxTicks; tick++)
|
||||
{
|
||||
fallVelocityZ += gravity * dt;
|
||||
Vector3 target = pos + new Vector3(horizontalVelocityX * dt, 0f, fallVelocityZ * dt);
|
||||
Vector3 target = pos + new Vector3(0f, 0f, fallVelocityZ * dt);
|
||||
|
||||
var result = engine.ResolveWithTransition(
|
||||
currentPos: pos,
|
||||
|
|
@ -306,6 +182,9 @@ public class Ts4SteepRoofWedgeCaptureTests
|
|||
if (result.IsOnGround)
|
||||
fallVelocityZ = 0f;
|
||||
|
||||
positions.Add(pos);
|
||||
|
||||
// Reached the flat reference floor (x<0, z ~ r) — resolved cleanly.
|
||||
if (pos.X < 0f && pos.Z <= r + 0.05f)
|
||||
{
|
||||
reachedFloor = true;
|
||||
|
|
@ -316,6 +195,8 @@ public class Ts4SteepRoofWedgeCaptureTests
|
|||
Assert.True(reachedFloor,
|
||||
$"Body never reached the flat reference floor within {MaxTicks} ticks " +
|
||||
$"({MaxTicks / (float)TicksPerSecond:F1}s); final position " +
|
||||
$"({pos.X:F3},{pos.Y:F3},{pos.Z:F3}).");
|
||||
$"({pos.X:F3},{pos.Y:F3},{pos.Z:F3}) — this is the wedge the L.4 shortcut guards " +
|
||||
"against (never resolving off the steep surface at all), distinct from a bounded " +
|
||||
"per-tick freeze.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue