diff --git a/src/AcDream.Core/Physics/TransitionTypes.cs b/src/AcDream.Core/Physics/TransitionTypes.cs index 7364ec4..50dc718 100644 --- a/src/AcDream.Core/Physics/TransitionTypes.cs +++ b/src/AcDream.Core/Physics/TransitionTypes.cs @@ -1247,6 +1247,12 @@ public sealed class Transition this.SpherePath.WalkableAllowance = savedWalkableAllowance; } + // adjustedCenter (sphere slid onto polygon plane) is intentionally + // discarded — ValidateWalkable recomputes contact geometry from the + // world-space plane + foot position, consistent with the outdoor terrain + // path (SampleTerrainWalkable returns only plane + vertices, no adjusted + // sphere). The local is held only to satisfy the out param. + if (!found || hitPoly is null) return false; // Transform hit polygon's plane + vertices to world space. Math is diff --git a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs b/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs index 466c5d0..75f136e 100644 --- a/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs +++ b/tests/AcDream.Core.Tests/Physics/IndoorWalkablePlaneTests.cs @@ -167,6 +167,57 @@ public class IndoorWalkablePlaneTests Assert.False(found); } + [Fact] + public void TryFindIndoorWalkablePlane_WallPolyInBsp_ReturnsFalse() + { + // A polygon with a horizontal normal (Z = 0) is a wall, not a floor. + // walkable_hits_sphere rejects it: dp = dot(UnitZ, (0,1,0)) = 0 <= FloorZ. + // Regression coverage for the previous NoWalkablePolys_ReturnsFalse intent + // (the renamed NoBsp_ReturnsFalse only covers the null-BSP early-return). + Vector3[] wallVerts = + { + new Vector3(0f, 0f, 0f), + new Vector3(1f, 0f, 0f), + new Vector3(1f, 0f, 1f), + new Vector3(0f, 0f, 1f), + }; + var resolved = new Dictionary + { + [0] = new ResolvedPolygon + { + Vertices = wallVerts, + Plane = new Plane(new Vector3(0f, 1f, 0f), 0f), // wall facing +Y + NumPoints = 4, + SidesType = CullMode.None, + }, + }; + + var center = new Vector3(0.5f, 0f, 0.5f); + var bsp = BuildLeafBsp(new ushort[] { 0 }, center, 2f); + + var cell = new CellPhysics + { + BSP = bsp, + WorldTransform = Matrix4x4.Identity, + InverseWorldTransform = Matrix4x4.Identity, + Resolved = resolved, + }; + + var transition = new Transition(); + transition.SpherePath.WalkInterp = 1.0f; + + // Foot sphere positioned to overlap the wall's plane (|Y - 0| = 0 < radius 0.48). + bool found = transition.TryFindIndoorWalkablePlane( + cell, + localFootCenter: new Vector3(0.5f, 0f, 0.5f), + sphereRadius: 0.48f, + out _, + out _, + out _); + + Assert.False(found); + } + [Fact] public void TryFindIndoorWalkablePlane_EmptyResolved_ReturnsFalse() {