fix(core): Phase B.2 — require indoor floor below terrain for outdoor→indoor transition

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) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-12 15:05:54 +02:00
parent 05d835ff33
commit 5280950806

View file

@ -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;
}