fix(physics): #32 L.2c precipice edge-slide context

Port the first retail precipice-slide slice from named retail/ACE: terrain and BSP walkable hits now preserve polygon vertices, failed step-down edges back-probe to rediscover the walkable polygon, and edge-slide can run precipice/cliff slide instead of only hard-stopping.

Adds pseudocode anchors plus regression coverage for terrain polygon context and loaded-terrain boundary edge-slide.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-04-30 08:04:37 +02:00
parent 1ec40f2a4f
commit 261322b48e
10 changed files with 559 additions and 60 deletions

View file

@ -231,6 +231,36 @@ public class PhysicsEngineTests
Assert.Equal(0x0025u, result.CellId);
}
[Fact]
public void ResolveWithTransition_EdgeSlideStopsAtLoadedTerrainBoundary()
{
var engine = MakeFlatEngine(terrainZ: 50f);
var body = new PhysicsBody
{
Position = new Vector3(191.25f, 96f, 50f),
TransientState = TransientStateFlags.Contact | TransientStateFlags.OnWalkable,
ContactPlaneValid = true,
ContactPlane = new Plane(Vector3.UnitZ, -50f),
ContactPlaneCellId = 0x003Du,
};
var result = engine.ResolveWithTransition(
currentPos: new Vector3(191.25f, 96f, 50f),
targetPos: new Vector3(193f, 96f, 50f),
cellId: 0x003Du,
sphereRadius: 0.5f,
sphereHeight: 1.2f,
stepUpHeight: 0.4f,
stepDownHeight: 0.4f,
isOnGround: true,
body: body,
moverFlags: ObjectInfoState.EdgeSlide);
Assert.True(result.IsOnGround);
Assert.InRange(result.Position.X, 190.75f, 192.0001f);
Assert.Equal(50f, result.Position.Z, precision: 2);
}
[Fact]
public void ResolveWithTransition_LandblockBoundary_UpdatesFullOutdoorCellId()
{

View file

@ -67,6 +67,20 @@ public class TerrainSurfaceTests
Assert.Equal(42f, surface.SampleZ(300f, 300f));
}
[Fact]
public void SampleSurfacePolygon_ReturnsContainingTriangleVertices()
{
var heights = FlatHeightmap(50);
var surface = new TerrainSurface(heights, LinearHeightTable(), landblockX: 0, landblockY: 0);
var sample = surface.SampleSurfacePolygon(2f, 2f);
Assert.Equal(3, sample.Vertices.Length);
Assert.All(sample.Vertices, v => Assert.Equal(50f, v.Z));
Assert.Equal(1f, sample.Normal.Z, precision: 3);
Assert.Contains(sample.Vertices, v => v.X == 0f && v.Y == 0f);
}
[Fact]
public void ComputeOutdoorCellId_Origin_ReturnsFirst()
{