diff --git a/src/AcDream.App/Rendering/CellVisibility.cs b/src/AcDream.App/Rendering/CellVisibility.cs index d3cf6f1..af53139 100644 --- a/src/AcDream.App/Rendering/CellVisibility.cs +++ b/src/AcDream.App/Rendering/CellVisibility.cs @@ -208,6 +208,23 @@ public sealed class CellVisibility _cellLookup[cell.CellId] = cell; } + /// + /// Phase A8 (2026-05-28): enumerates the loaded cells that belong to a + /// landblock prefix. Used by GameWindow.ApplyLoadedTerrainLocked when + /// building the per-landblock BuildingRegistry — the per-frame + /// drainedCells dict misses cells loaded on prior frames, so the + /// stamping loop in needs access to + /// every cell currently in the landblock to ensure BuildingId is set. + /// + /// Upper 16 bits of the landblock key (e.g. 0xA9B4 + /// for landblock 0xA9B40000). NOT the full 32-bit landblock id. + public IReadOnlyList GetCellsForLandblock(uint lbId) + { + return _cellsByLandblock.TryGetValue(lbId, out var list) + ? list + : System.Array.Empty(); + } + /// /// Removes all cells belonging to (upper 16 bits of /// the landblock key, e.g. 0xA9B4 for landblock 0xA9B40000). Called when a diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 141f0ad..b0dacd3 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -5878,18 +5878,38 @@ public sealed class GameWindow : IDisposable _physicsEngine.AddLandblock(lb.LandblockId, terrainSurface, cellSurfaces, portalPlanes, origin.X, origin.Y); - // Phase A8 (2026-05-26): build per-landblock BuildingRegistry from - // LandBlockInfo.Buildings, stamping LoadedCell.BuildingId for each cell - // in a building's cell set. Uses the already-drained drainedCells dict - // (LoadedCells registered this frame) so stamping and registry build - // happen in the same render-thread pass — no extra dat reads required. - // Cells without a building stay at BuildingId == null (outdoor surface - // cells; dungeon cells not enumerated in LandBlockInfo.Buildings). + // Phase A8 (2026-05-26 / fix 2026-05-28): build per-landblock + // BuildingRegistry from LandBlockInfo.Buildings, stamping + // LoadedCell.BuildingId for each cell in a building's cell set. + // + // FIX 2026-05-28: previously passed `drainedCells` only — that's the + // dict of cells drained THIS frame. Cells loaded on prior frames + // were missed, leaving their BuildingId null and the + // `cameraInsideBuilding` gate FALSE even when the player was inside + // a tagged cottage. (First visual-gate launch showed 8737 [vis] + // lines with inside=True really=True but ZERO [buildings] probe + // emissions — same root cause as the RR7.1 / RR7.2 saga.) The fix: + // merge `drainedCells` with every cell currently registered with + // _cellVisibility for this landblock prefix. BuildingLoader's BFS + // + stamping pass then sees the complete cell set. + // + // Cells without a building stay at BuildingId == null (outdoor + // surface cells; dungeon cells not enumerated in LandBlockInfo.Buildings). if (lbInfo is not null) { + // Merge: previously-loaded cells + freshly-drained cells. + // drainedCells wins on key collision (it has the same instance + // anyway — both dicts reference the same LoadedCell objects). + var lbStampCells = new System.Collections.Generic.Dictionary(drainedCells); + uint lbPrefix = (lb.LandblockId >> 16) & 0xFFFFu; + foreach (var c in _cellVisibility.GetCellsForLandblock(lbPrefix)) + { + if (!lbStampCells.ContainsKey(c.CellId)) + lbStampCells[c.CellId] = c; + } _buildingRegistries[lb.LandblockId] = AcDream.App.Rendering.Wb.BuildingLoader.Build( - lbInfo, lb.LandblockId, drainedCells); + lbInfo, lb.LandblockId, lbStampCells); } // Phase A8: finalize EnvCellRenderer's per-landblock instance store.