From 0fc6003c2a1f1bc975341027c67e06a5c1df7f8d Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 27 May 2026 15:18:07 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20=E2=80=94=20stamp?= =?UTF-8?q?=20BuildingId=20on=20already-loaded=20cells=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit First visual-gate launch showed 8,737 [vis] lines (player at Holtburg cottage cell 0xA9B40143, inside=True really=True) but ZERO [buildings] / [envcells] / [stencil] / [draworder] probe emissions. Root cause: same as the original RR7.1 saga — BuildingLoader.Build was passed only the per-frame drainedCells dict, missing cells loaded on PRIOR frames. Those cells stayed with BuildingId=null, the strict cameraInsideBuilding gate returned false, the indoor branch never fired. Fix: in ApplyLoadedTerrainLocked, merge drainedCells with the cells already registered in _cellVisibility for the same landblock prefix before passing to BuildingLoader. The richer dict ensures the stamping loop in BuildingLoader.Build covers EVERY cell in this landblock. Added IReadOnlyList GetCellsForLandblock(uint lbId) on CellVisibility — minimal API expose; existing _cellsByLandblock dict was already the right shape (lbId = upper 16 bits). Build green. Tests unchanged. Next: relaunch the client. With the fix, [buildings] probe should fire with camBldgs=[0x1,...] when the player is inside a Holtburg cottage, [envcells] should report cells>=1 tris>=1 per indoor frame, and the indoor branch should be exercising the WB-faithful Steps 1-5 pipeline. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/CellVisibility.cs | 17 ++++++++++ src/AcDream.App/Rendering/GameWindow.cs | 36 ++++++++++++++++----- 2 files changed, 45 insertions(+), 8 deletions(-) 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.