fix(physics): port retail stair edge backprobe

This commit is contained in:
Erik 2026-07-31 09:26:28 +02:00
parent 5a0f9868a6
commit 2dcb4f1d94
6 changed files with 218 additions and 6 deletions

View file

@ -177,6 +177,48 @@ public class Issue185OutdoorStairsSeamReplayTests
"A continuous walkable ramp seam must not persist a horizontal sliding normal (#137 family).");
}
/// <summary>
/// #271 live capture, quantum 310: a forward/uphill displacement that also
/// presses into the staircase's side wall must keep its uphill tangent.
/// Pre-fix the composite retry path reversed that tangent, moving from
/// Y=75.539 to Y=75.199 and rapidly carrying the player back down the stairs.
/// </summary>
[Fact]
public void OutdoorStairs_SideWallContact_DoesNotReverseUphillTangent()
{
var engine = BuildStairEngine();
var body = GroundedOnTread();
body.Position = new Vector3(133.03775f, 75.53931f, 59.608147f);
body.ContactPlane = new Plane(
new Vector3(3.2782555e-07f, -0.62469506f, 0.78086877f),
0.75193405f);
ResolveResult result = engine.ResolveWithTransition(
currentPos: body.Position,
targetPos: body.Position + new Vector3(0.30007935f, 0.8837738f, 0f),
cellId: StairCellId,
sphereRadius: 0.48f,
sphereHeight: 1.835f,
stepUpHeight: 0.6f,
stepDownHeight: 1.5f,
isOnGround: true,
body: body,
moverFlags: ObjectInfoState.IsPlayer | ObjectInfoState.EdgeSlide,
movingEntityId: 0x01000000u);
_out.WriteLine(
$"out=({result.Position.X:F6},{result.Position.Y:F6},{result.Position.Z:F6}) " +
$"collision={result.CollisionNormalValid} " +
$"normal=({result.CollisionNormal.X:F3},{result.CollisionNormal.Y:F3},{result.CollisionNormal.Z:F3})");
Assert.True(result.Position.Y > body.Position.Y + 0.25f,
$"Side-wall response failed to preserve meaningful uphill motion: " +
$"{body.Position.Y:F6} -> {result.Position.Y:F6}.");
Assert.True(result.Position.Z >= body.Position.Z - 0.001f,
$"Side-wall response dropped the grounded player downhill: " +
$"{body.Position.Z:F6} -> {result.Position.Z:F6}.");
}
private static string SolutionRoot()
{
var dir = AppContext.BaseDirectory;