From 5280950806a66949cd7eac015ebb17fba14e31bb Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 12 Apr 2026 15:05:54 +0200 Subject: [PATCH] =?UTF-8?q?fix(core):=20Phase=20B.2=20=E2=80=94=20require?= =?UTF-8?q?=20indoor=20floor=20below=20terrain=20for=20outdoor=E2=86=92ind?= =?UTF-8?q?oor=20transition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previous cellId-mask fix was necessary but insufficient: the engine correctly identified the player as outdoor, but then immediately transitioned to an indoor cell because a CellSurface floor polygon covered the player's XY at a Z within stepUpHeight. The floor polygon was a roof or upper floor of a nearby building that happens to sit at terrain level — not a walkable indoor floor the player should snap to. Fix: outdoor→indoor transition now requires bestCellZ < terrainZ - 1. A genuine indoor transition is into a cell whose floor is BELOW the terrain surface (basement, ground floor of elevated building). Cells at or above terrain Z are roofs/upper floors viewed from outside and must not capture the player. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/AcDream.Core/Physics/PhysicsEngine.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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; }