diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index 29cdf9f..4f2b8dd 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -129,9 +129,20 @@ public sealed class PhysicsEngine targetCellId = physics.Terrain.ComputeOutdoorCellId(localCandX, localCandY); } else if (!currentlyIndoor && bestCellZ is not null - && MathF.Abs(bestCellZ.Value - currentPos.Z) < stepUpHeight + 2f) + && MathF.Abs(bestCellZ.Value - currentPos.Z) < stepUpHeight + 2f + && bestCellZ.Value < terrainZ - 1f) { // Walked into an indoor cell from outdoor — transition to indoor. + // The extra guard `bestCellZ < terrainZ - 1` prevents transitioning + // into cells whose floor is AT or ABOVE terrain level — those are + // typically roofs, upper floors, or building footprints that overlap + // the outdoor terrain. A genuine indoor transition is into a cell + // whose floor is BELOW the terrain surface (basements, ground floors + // of buildings that sit on elevated terrain). Without this guard, + // standing near any building with a floor polygon covering the + // player's XY triggers an indoor transition and snaps Z to the + // cell's floor — which for multi-story buildings can be 30+ units + // below the outdoor terrain. targetZ = bestCellZ.Value; targetCellId = bestCell!.CellId & 0xFFFFu; }