diff --git a/tests/AcDream.Core.Tests/Physics/BSPQueryTests.cs b/tests/AcDream.Core.Tests/Physics/BSPQueryTests.cs index 31c175b..a80c743 100644 --- a/tests/AcDream.Core.Tests/Physics/BSPQueryTests.cs +++ b/tests/AcDream.Core.Tests/Physics/BSPQueryTests.cs @@ -418,4 +418,96 @@ public class BSPQueryTests Assert.False(found); } + + // ========================================================================= + // FindCollisions — world-origin / world-rotation correctness (spec 2026-05-20) + // ========================================================================= + + /// + /// Build a single-leaf BSP with one horizontal floor polygon at the given + /// cell-local Z. Vertex array forms a 4x4 m square centered at cell-local + /// (0, 0). + /// + private static (PhysicsBSPNode root, Dictionary resolved) + BuildSingleFloorBsp(float floorZ) + { + var verts = new[] + { + new Vector3(-2f, -2f, floorZ), + new Vector3( 2f, -2f, floorZ), + new Vector3( 2f, 2f, floorZ), + new Vector3(-2f, 2f, floorZ), + }; + + var root = new PhysicsBSPNode + { + Type = BSPNodeType.Leaf, + BoundingSphere = new Sphere + { + Origin = new Vector3(0f, 0f, floorZ), + Radius = 3f, + }, + }; + root.Polygons.Add(0); + + var resolved = new Dictionary + { + [0] = new ResolvedPolygon + { + Vertices = verts, + Plane = new Plane(Vector3.UnitZ, -floorZ), + NumPoints = 4, + SidesType = CullMode.None, + }, + }; + + return (root, resolved); + } + + [Fact] + public void FindCollisions_StepDown_TranslatedWorldOrigin_WritesWorldSpacePlane() + { + // Cell is translated +94 m in world Z (i.e., the cell-local floor at + // Z=0 sits at world Z=94). This mirrors the Holtburg cottage scenario + // captured in launch-cp-probe.log (cell 0xA9B40150, floor world + // Z≈94.02). + var (root, resolved) = BuildSingleFloorBsp(floorZ: 0f); + + var transition = new Transition(); + transition.SpherePath.WalkableAllowance = PhysicsGlobals.FloorZ; + transition.SpherePath.WalkInterp = 1.0f; + transition.SpherePath.StepDown = true; + transition.SpherePath.StepDownAmt = 0.5f; + + // Foot sphere in cell-local space: overlapping the floor (Z=0.4 with + // radius 0.48 → dist 0.4 < radius−epsilon = 0.4798, so the precise + // overlap test accepts the contact). + var localSphere = new Sphere + { + Origin = new Vector3(0f, 0f, 0.4f), + Radius = 0.48f, + }; + + var state = BSPQuery.FindCollisions( + root, + resolved, + transition, + localSphere, + localSphere1: null, + localCurrCenter: localSphere.Origin, + localSpaceZ: Vector3.UnitZ, + scale: 1.0f, + localToWorld: Quaternion.Identity, + engine: null, + worldOrigin: new Vector3(0f, 0f, 94f)); + + // Path 3 step-down should fire and adjust the sphere onto the floor. + Assert.Equal(TransitionState.Adjusted, state); + + // ContactPlane must be in world space: normal stays (0,0,1) since the + // cell rotation is identity; D = -world_floor_Z = -94. + Assert.True(transition.CollisionInfo.ContactPlaneValid); + Assert.Equal(1.0f, transition.CollisionInfo.ContactPlane.Normal.Z, precision: 3); + Assert.Equal(-94.0f, transition.CollisionInfo.ContactPlane.D, precision: 2); + } }