using System.Collections.Concurrent; using System.Collections.Generic; using System.Numerics; using AcDream.Core.Physics; // TerrainSurface namespace AcDream.Core.World.Cells; /// /// The unified cell graph: the authoritative id->cell resolver and registry. /// Built alongside the legacy render/physics cell systems in Stage 1 and consumed /// by nobody (zero behavior change). Retail anchor: CObjCell::GetVisible (pseudo_c:308209). /// Worker-thread populated; reads are concurrency-safe. /// public sealed class CellGraph { private readonly ConcurrentDictionary _envCells = new(); private readonly ConcurrentDictionary _terrain = new(); /// Player's current cell. Defined for Stage 2; INERT in Stage 1 (no writer). public ObjCell? CurrCell { get; internal set; } public bool Contains(uint envCellId) => _envCells.ContainsKey(envCellId); public void Add(EnvCell cell) => _envCells.TryAdd(cell.Id, cell); /// Any id in the cell's landblock; masked to (id & 0xFFFF0000). public void RegisterTerrain(uint landblockPrefix, TerrainSurface terrain, Vector3 worldOrigin) => _terrain[landblockPrefix & 0xFFFF0000u] = (terrain, worldOrigin); public void RemoveLandblock(uint landblockPrefix) { uint lb = landblockPrefix & 0xFFFF0000u; _terrain.TryRemove(lb, out _); foreach (var id in new List(_envCells.Keys)) if ((id & 0xFFFF0000u) == lb) _envCells.TryRemove(id, out _); } /// The universal id->cell resolver (retail CObjCell::GetVisible). public ObjCell? GetVisible(uint id) { if (id == 0u) return null; if ((id & 0xFFFFu) >= 0x100u) return _envCells.TryGetValue(id, out var env) ? env : null; uint low = id & 0xFFFFu; if (low < 1u || low > 0x40u) return null; if (!_terrain.TryGetValue(id & 0xFFFF0000u, out var t)) return null; int idx = (int)(low - 1u); return LandCell.Synthesize(id, t.Terrain, t.Origin, idx / 8, idx % 8); } public ObjCell? Neighbor(ObjCell cell, in CellPortal portal) => GetVisible(portal.OtherCellId); }