From efe35201fc0fb92b1a23e40b8fc044e95d406551 Mon Sep 17 00:00:00 2001 From: Erik Date: Wed, 27 May 2026 12:00:28 +0200 Subject: [PATCH] =?UTF-8?q?fix(render):=20Phase=20A8=20RR7.2=20=E2=80=94?= =?UTF-8?q?=20=5FbuildingRegistries=20key=20mismatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RR7.1 fixed cell-timing but the indoor branch STILL fired 0 times in the v2 visual gate (125,476 inside=True frames, all routed outdoor). Real root cause: a key-form mismatch between storage and lookup. Storage at line ~5886 used `_buildingRegistries[lb.LandblockId]`. But lb.LandblockId is the LandBlock dat-file id (e.g. 0xA9B4FFFF — the 0xFFFF low word identifies the file as terrain). Lookups at the gate (line ~7090) and the drain late-stamp (line ~5708) used `cell.CellId & 0xFFFF0000u` (e.g. 0xA9B40000). 0xA9B4FFFF ≠ 0xA9B40000 so TryGetValue always missed; camBuildings stayed empty; the gate fell to the outdoor branch unconditionally. Fix: normalize all four sites to the masked form (`& 0xFFFF0000u`) — storage at the build call, both Remove callbacks in the streaming-controller setup, and the lookups (already correct). User-visible symptom that surfaced the v2 launch: - sky + ground missing through windows - buildings + objects still visible This pattern (stencil-gated outdoor passes failing while ungated indoor pass works) was actually the OUTDOOR branch running with the indoor visibility set — `visibleCellIds` filtered out terrain cells and the sky pre-scene was gated off too because cameraInsideBuilding was True (correctly) but camBuildings was empty (incorrectly). Wait — re-reading the indoor branch's gate: it requires camBuildings.Count > 0 too, so with the key mismatch it took the outdoor branch. The sky+terrain visibility pattern user reported is the outdoor branch where sky-pre-scene was correctly gated off by !cameraInsideBuilding (cameraInsideBuilding is what computes the ROUTING; it doesn't have to match the actual branch taken when the extra `camBuildings.Count > 0` filter trips). So initial-sky was skipped (cameraInsideBuilding=true) but indoor branch didn't fire either — outdoor branch with no initial sky = the dark window visual. RR7.2 closes both. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 6c26170..ac17ed8 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -1863,7 +1863,7 @@ public sealed class GameWindow : IDisposable _terrain?.RemoveLandblock(id); _physicsEngine.RemoveLandblock(id); _cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu); - _buildingRegistries.Remove(id); // Phase A8 + _buildingRegistries.Remove(id & 0xFFFF0000u); // Phase A8 RR7.2: masked key matches storage }); // A.5 T22.5: apply max-completions from resolved quality. _streamingController.MaxCompletionsPerFrame = _resolvedQuality.MaxCompletionsPerFrame; @@ -5872,7 +5872,7 @@ public sealed class GameWindow : IDisposable _physicsEngine.AddLandblock(lb.LandblockId, terrainSurface, cellSurfaces, portalPlanes, origin.X, origin.Y); - // Phase A8 (2026-05-26, fixed 2026-05-27 RR7.1): build per-landblock + // Phase A8 (2026-05-26, fixed 2026-05-27 RR7.2): build per-landblock // BuildingRegistry from LandBlockInfo.Buildings, stamping // LoadedCell.BuildingId for each cell in a building's cell set. // Uses _cellVisibility.AllLoadedCells (every cell loaded so far, @@ -5881,9 +5881,19 @@ public sealed class GameWindow : IDisposable // pass get stamped at drain time (see _pendingCells loop above). // Cells without a building stay at BuildingId == null (outdoor // surface cells; dungeon cells not in LandBlockInfo.Buildings). + // + // KEY NORMALIZATION (RR7.2): lb.LandblockId is the LandBlock file + // id (e.g. 0xA9B4FFFF — the 0xFFFF low word is the dat-file + // discriminator), but cell ids like 0xA9B40150 mask to + // 0xA9B40000. All lookups (drain late-stamp at line ~5708, gate + // check at line ~7090) use `& 0xFFFF0000u`, so storage MUST use + // the same masked form or every lookup misses — which silently + // routed every indoor frame through the outdoor branch in the + // RR7.1 launch. if (lbInfo is not null) { - _buildingRegistries[lb.LandblockId] = + uint lbRegistryKey = lb.LandblockId & 0xFFFF0000u; + _buildingRegistries[lbRegistryKey] = AcDream.App.Rendering.Wb.BuildingLoader.Build( lbInfo, lb.LandblockId, _cellVisibility.AllLoadedCells); } @@ -9083,7 +9093,7 @@ public sealed class GameWindow : IDisposable _terrain?.RemoveLandblock(id); _physicsEngine.RemoveLandblock(id); _cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu); - _buildingRegistries.Remove(id); // Phase A8 + _buildingRegistries.Remove(id & 0xFFFF0000u); // Phase A8 RR7.2: masked key }); _streamingController.MaxCompletionsPerFrame = newResolved.MaxCompletionsPerFrame;