From 8e703bef2288ae8588e9364f04d363f1e68b4bf8 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 2 Jun 2026 09:29:30 +0200 Subject: [PATCH] =?UTF-8?q?feat(core):=20UCG=20Stage=201=20=E2=80=94=20pop?= =?UTF-8?q?ulate=20CellGraph=20from=20CacheCellStruct=20+=20AddLandblock?= =?UTF-8?q?=20(inert)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PhysicsDataCache gains a `CellGraph` property (UCG Stage 1). The env-cell hook is placed at the very top of CacheCellStruct — before the idempotency guard and the null-PhysicsBSP early-return — so BSP-less cells are included in the graph even though they are dropped from the legacy _cellStruct map. PhysicsEngine.AddLandblock/RemoveLandblock mirror terrain registration into the graph via a null-guarded DataCache?.CellGraph call. Zero behavior change: CellGraph has no readers this stage. A using-alias (UcgEnvCell / UcgCellGraph) resolves the EnvCell name collision between AcDream.Core.World.Cells and DatReaderWriter.DBObjs. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/AcDream.Core/Physics/PhysicsDataCache.cs | 13 +++++++ src/AcDream.Core/Physics/PhysicsEngine.cs | 6 ++++ .../Physics/CellGraphPopulationTests.cs | 36 +++++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 tests/AcDream.Core.Tests/Physics/CellGraphPopulationTests.cs diff --git a/src/AcDream.Core/Physics/PhysicsDataCache.cs b/src/AcDream.Core/Physics/PhysicsDataCache.cs index c7b65a5..67a8bcf 100644 --- a/src/AcDream.Core/Physics/PhysicsDataCache.cs +++ b/src/AcDream.Core/Physics/PhysicsDataCache.cs @@ -4,6 +4,8 @@ using DatReaderWriter.DBObjs; using DatReaderWriter.Enums; using DatReaderWriter.Types; using Plane = System.Numerics.Plane; +using UcgEnvCell = AcDream.Core.World.Cells.EnvCell; +using UcgCellGraph = AcDream.Core.World.Cells.CellGraph; namespace AcDream.Core.Physics; @@ -23,6 +25,12 @@ public sealed class PhysicsDataCache // ── Phase 2: building portal cache for outdoor→indoor entry ─────────── private readonly ConcurrentDictionary _buildings = new(); + /// + /// UCG Stage 1: the unified cell graph, built alongside the legacy cell caches. + /// Consumed by nobody this stage (zero behavior change). + /// + public UcgCellGraph CellGraph { get; } = new(); + /// /// Extract and cache the physics BSP + polygon data from a GfxObj, /// PLUS always cache a visual AABB from the vertex data regardless of @@ -155,6 +163,11 @@ public sealed class PhysicsDataCache public void CacheCellStruct(uint envCellId, DatReaderWriter.DBObjs.EnvCell envCell, CellStruct cellStruct, Matrix4x4 worldTransform) { + // UCG Stage 1: register in the unified graph for ALL cells — before the + // idempotency + null-BSP guards below, so BSP-less cells are still included. + if (!CellGraph.Contains(envCellId)) + CellGraph.Add(UcgEnvCell.FromDat(envCellId, envCell, cellStruct, worldTransform)); + if (_cellStruct.ContainsKey(envCellId)) return; if (cellStruct.PhysicsBSP?.Root is null) return; diff --git a/src/AcDream.Core/Physics/PhysicsEngine.cs b/src/AcDream.Core/Physics/PhysicsEngine.cs index f2f3b86..b9cf6ec 100644 --- a/src/AcDream.Core/Physics/PhysicsEngine.cs +++ b/src/AcDream.Core/Physics/PhysicsEngine.cs @@ -60,6 +60,9 @@ public sealed class PhysicsEngine float worldOffsetX, float worldOffsetY) { _landblocks[landblockId] = new LandblockPhysics(terrain, cells, portals, worldOffsetX, worldOffsetY); + + // UCG Stage 1: mirror terrain into the unified graph (inert this stage). + DataCache?.CellGraph.RegisterTerrain(landblockId, terrain, new Vector3(worldOffsetX, worldOffsetY, 0f)); } /// @@ -69,6 +72,9 @@ public sealed class PhysicsEngine { _landblocks.Remove(landblockId); ShadowObjects.RemoveLandblock(landblockId); + + // UCG Stage 1: mirror removal into the unified graph (inert this stage). + DataCache?.CellGraph.RemoveLandblock(landblockId); } /// diff --git a/tests/AcDream.Core.Tests/Physics/CellGraphPopulationTests.cs b/tests/AcDream.Core.Tests/Physics/CellGraphPopulationTests.cs new file mode 100644 index 0000000..ec99de6 --- /dev/null +++ b/tests/AcDream.Core.Tests/Physics/CellGraphPopulationTests.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Numerics; +using AcDream.Core.Physics; +using AcDream.Core.World.Cells; +using DatReaderWriter.Types; +using Xunit; +using DatEnvCell = DatReaderWriter.DBObjs.EnvCell; + +namespace AcDream.Core.Tests.Physics; + +public class CellGraphPopulationTests +{ + [Fact] + public void CacheCellStruct_AddsEnvCellToGraph_EvenWhenPhysicsBspIsNull() + { + var cache = new PhysicsDataCache(); + var cellStruct = new CellStruct + { + VertexArray = new VertexArray { Vertices = new Dictionary() }, + Polygons = new Dictionary(), + // PhysicsBSP omitted (defaults to null) — triggers the null-BSP drop from _cellStruct + }; + var dat = new DatEnvCell + { + Flags = (DatReaderWriter.Enums.EnvCellFlags)0, + CellPortals = new List(), + VisibleCells = new List(), + }; + + cache.CacheCellStruct(0xA9B40174u, dat, cellStruct, Matrix4x4.Identity); + + Assert.Null(cache.GetCellStruct(0xA9B40174u)); // dropped from physics cache + Assert.NotNull(cache.CellGraph.GetVisible(0xA9B40174u)); // but present in the graph + Assert.IsType(cache.CellGraph.GetVisible(0xA9B40174u)); + } +}