From 4679134d66d17225c543d58cbb597d909a08a218 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 20 May 2026 14:25:20 +0200 Subject: [PATCH] fix(physics): fall through to outdoor cell when indoor BSP doesn't contain player (A1.7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ISSUES #83 Phase A1.7. CellTransit.FindCellList returns currentCellId when no candidate cell's CellBSP contains the sphere center — but this also fires when the player has walked OUTSIDE the entire portal-connected indoor graph (e.g., breached a missing wall poly, walked through a doorway gap). The player's CellId stays stuck on the old indoor cell whose BSP is now far away, NodeIntersects fails at the BSP root for every collision query, and no walls block in their actual location. Probe evidence (launch-stairs.utf8.log, A1.6 verify): - Cell 0xA9B40164: 646 indoor-bsp queries, 644 returned OK (99.7%). No walls firing. - Player local positions in cell 0xA9B40164 ranged X[-0.66..33.18], Y[10.68..63.53] — a 34x53m envelope. The player was geometrically ~62m from the cell's world origin while CellId never updated. Compare to adjacent cells where collision works: - Cell 0xA9B4015A: 230 queries, 32% hit rate - Cell 0xA9B40157: 131 queries, 38% hit rate Fix: after CellTransit.FindCellList returns, verify the resolved cell's CellBSP actually contains the sphere center via BSPQuery.PointInsideCellBsp. If not, fall through to the existing outdoor cell resolution branch (terrain grid + CheckBuildingTransit for re-entry into a different building). Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.Core/Physics/PhysicsEngine.cs | 27 ++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index 5061f34..eab87de 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -261,7 +261,32 @@ public sealed class PhysicsEngine // Indoor branch needs DataCache to look up cells; outdoor uses // _landblocks (no DataCache dependency). if (DataCache is null) return fallbackCellId; - return CellTransit.FindCellList(DataCache, worldPos, sphereRadius, fallbackCellId); + uint indoorResult = CellTransit.FindCellList(DataCache, worldPos, sphereRadius, fallbackCellId); + + // ISSUES #83 / Phase A1.7 (2026-05-21): verify the indoor result + // actually contains the player. CellTransit.FindCellList falls back + // to currentCellId when no candidate cell's CellBSP contains the + // sphere center — but this happens even when the player has walked + // OUTSIDE the entire portal-connected indoor cell graph (e.g., + // exited through an unblocked wall or doorway gap). In that state + // the player's CellId is stuck on an indoor cell whose BSP is + // far away, every indoor-bsp query returns OK (NodeIntersects + // fails at root), and no walls block. + // + // If the resolved indoor cell's BSP does NOT contain the sphere + // center, fall through to the outdoor cell resolution below — it + // will compute the correct landcell from the terrain grid and + // optionally re-enter an indoor cell via CheckBuildingTransit. + var indoorCell = DataCache.GetCellStruct(indoorResult); + if (indoorCell?.CellBSP?.Root is null) + return indoorResult; // Can't verify (no CellBSP); trust FindCellList. + + var localCenter = Vector3.Transform(worldPos, indoorCell.InverseWorldTransform); + if (BSPQuery.PointInsideCellBsp(indoorCell.CellBSP.Root, localCenter)) + return indoorResult; + + // Fall through to outdoor resolution: player has left the indoor + // portal-connected graph entirely. } // Outdoor seed: use terrain grid to compute the prefixed cell id.