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.