fix(core): correct triangle boundary conditions in TerrainSurface.SampleZ

ROOT CAUSE FIX for persistent slope Z clipping.

The SWtoNE/SEtoNW triangle boundary tests were swapped. AC's naming is
counter-intuitive: "SWtoNE cut" means BL and TR are the ISOLATED vertices
— the shared hypotenuse runs TL(0,1)→BR(1,0), so the dividing test is
tx+ty=1, NOT ty=tx. We had them backwards, causing every cell to sample
from the wrong triangle — up to 7.5 unit Z errors on steep terrain.

Fixed by cross-referencing WorldBuilder-ACME-Edition which has:
- ClientReference.cs: faithful C# port of decompiled AC client code
- TerrainConformanceTests.cs: verified against 25,600 cells
- TerrainGeometryGenerator.GetHeight(): matches the mesh index buffer

Also removes the slope gradient hack from PlayerMovementController —
no longer needed since SampleZ now returns exact triangle-surface Z.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 22:27:16 +02:00
parent 3ae7f5e7ae
commit 131594d91b

View file

@ -204,21 +204,9 @@ public sealed class PlayerMovementController
}
}
// Slope compensation: on tilted terrain the character's collision
// cylinder extends horizontally beyond the center sample point.
// The lowest point of the cylinder is ~0.3 units below the center
// on steep slopes. Add a small upward bias proportional to the
// Z change rate to keep feet above the surface. Only when grounded.
if (!IsAirborne && result.IsOnGround)
{
// Sample Z 1 unit ahead and behind to estimate local gradient.
float aheadZ = _physics.Resolve(
new Vector3(result.Position.X + MathF.Cos(Yaw) * 1f,
result.Position.Y + MathF.Sin(Yaw) * 1f, newZ),
CellId, Vector3.Zero, StepUpHeight).Position.Z;
float gradient = MathF.Abs(aheadZ - newZ);
newZ += gradient * 0.4f; // 40% of the gradient as foot-radius compensation
}
// SampleZ now uses the correct triangle boundary conditions (fixed
// in 3ae7f5e via ACME WorldBuilder conformance test). No slope bias
// needed — the Z matches the visual terrain mesh exactly.
Position = new Vector3(result.Position.X, result.Position.Y, newZ);
CellId = result.CellId;