using System.Collections.Generic; using System.Numerics; using AcDream.Core.Physics; using DatReaderWriter.Types; using Xunit; using DatEnvCell = DatReaderWriter.DBObjs.EnvCell; namespace AcDream.Core.Tests.Physics; // 2026-06-03 blue-hole fix: CellGraph.CurrCell IS "the player's cell" (CellGraph.cs:19) — it roots // the indoor render (GameWindow.OnRender). It is written ONLY by the player-only // PhysicsEngine.UpdatePlayerCurrCell (called from PlayerMovementController.UpdateCellId, the single // player chokepoint for CellId), NOT by the per-entity ResolveCellId / ResolveWithTransition. Before // the fix, those per-entity paths wrote CurrCell, so a Holtburg NPC jump-looping near the cottage // doorway clobbered the player's render root every tick → the render rooted at the NPC's tiny // connector cell → the cottage doorway "blue-hole" flap. public class CellGraphMembershipTests { private static PhysicsEngine MakeEngineWithCell(uint cellId) { var engine = new PhysicsEngine(); var cache = new PhysicsDataCache(); engine.DataCache = cache; var cs = new CellStruct { VertexArray = new VertexArray { Vertices = new Dictionary() }, Polygons = new Dictionary(), PhysicsBSP = null, }; var dat = new DatEnvCell { Flags = (DatReaderWriter.Enums.EnvCellFlags)0, CellPortals = new List(), VisibleCells = new List(), }; cache.CacheCellStruct(cellId, dat, cs, Matrix4x4.Identity); // registers in the graph (W1) return engine; } [Fact] public void UpdatePlayerCurrCell_Resolved_WritesCurrCellTrackingTheId() { var engine = MakeEngineWithCell(0xA9B40174u); engine.UpdatePlayerCurrCell(0xA9B40174u); Assert.NotNull(engine.DataCache!.CellGraph.CurrCell); Assert.Equal(0xA9B40174u, engine.DataCache.CellGraph.CurrCell!.Id); } [Fact] public void UpdatePlayerCurrCell_UnresolvableId_LeavesCurrCellUnchanged() { var engine = MakeEngineWithCell(0xA9B40174u); engine.UpdatePlayerCurrCell(0xA9B40174u); // CurrCell = 0174 engine.UpdatePlayerCurrCell(0xDEADBEEFu); // not in the graph → stale beats null Assert.NotNull(engine.DataCache!.CellGraph.CurrCell); Assert.Equal(0xA9B40174u, engine.DataCache.CellGraph.CurrCell!.Id); } // Guards the blue-hole fix: the per-entity ResolveCellId must NOT touch the render root. // (Only the player's UpdatePlayerCurrCell does.) Before the fix this wrote CurrCell, so a // nearby NPC's ResolveCellId/ResolveWithTransition clobbered the player's render root. [Fact] public void ResolveCellId_DoesNotWriteTheRenderRoot() { var engine = MakeEngineWithCell(0xA9B40174u); uint result = engine.ResolveCellId(new Vector3(0, 0, 0), 0.5f, 0xA9B40174u); Assert.Equal(0xA9B40174u, result); // still resolves the cell correctly Assert.Null(engine.DataCache!.CellGraph.CurrCell); // but does NOT write the render root } }