From 88f3ce1fa0ae23fee7c82054fda29e4196023e9e Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 11 Jun 2026 12:46:46 +0200 Subject: [PATCH] T2 slice 3 (BR-4): draw-driven flood gating - per-building frustum pre-gate, 48m seed cap DELETED Retail floods a building's interior exactly when its shell DRAWS and an aperture survives the view: DrawBuilding (Ghidra 0x0059f2a0) -> per-view viewconeCheck on the shell -> portal-BSP walk -> ConstructView(CBldPortal) side test + GetClip-vs-view + GetVisible. There is NO distance constant anywhere on that chain (verifier-confirmed, flood-gate-shape adjusted). Port: - GameWindow's outdoor-node gather: per-BUILDING frustum pre-gate on the aperture bounds (Building.PortalBounds - the tight flood-purposes equivalent of the shell viewconeCheck), iterating the per-landblock BuildingRegistries. Replaces the Chebyshev<=1 landblock cell-sweep. Also the proper fix for the 2026-06-07 'FPS drops when I look out' problem the Chebyshev hack approximated: dozens of AABB tests instead of an O(all loaded cells) portal sweep. - OutdoorBuildingSeedDistance 48f -> infinity (the binary visibility pop at ~48 m - the confirmed #109 mechanism candidate - is gone; admission is now the screen clip per portal, retail's GetClip gate). - The legacy clipRoot==null look-in path keeps its 48 m: it is T4 deletion scope; improving doomed code wastes effort. Closes the building-flood-seeding-48m-cutoff divergence (culling area, adjusted-confirmed). Suites: App 226 green (flood gates included). Co-Authored-By: Claude Fable 5 --- src/AcDream.App/Rendering/GameWindow.cs | 39 ++++++++++++------- .../Rendering/RetailPViewRenderer.cs | 8 +++- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index 782c5374..aa6af66b 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -192,7 +192,6 @@ public sealed class GameWindow : IDisposable // yet rooted; the clipRoot flip + OutsideView terrain integration is the cutover step). private LoadedCell? _outdoorNode; private readonly List _outdoorNodeBuildingCells = new(); - private readonly HashSet _outdoorNodeSeenLbs = new(); private readonly HashSet _outdoorSceneParticleEntityIds = new(); private readonly HashSet _visibleSceneParticleEntityIds = new(); private string? _lastRenderSignature; @@ -7464,26 +7463,38 @@ public sealed class GameWindow : IDisposable if (viewerRoot is null && viewerCellId != 0u) { _outdoorNodeBuildingCells.Clear(); - _outdoorNodeSeenLbs.Clear(); - int onGridX = playerLb.HasValue ? (int)((playerLb.Value >> 24) & 0xFFu) : -1; - int onGridY = playerLb.HasValue ? (int)((playerLb.Value >> 16) & 0xFFu) : -1; - foreach (var onEntry in _worldState.LandblockEntries) + // T2 (BR-4): draw-driven flood gating. Retail floods a building's + // interior exactly when its shell DRAWS and an aperture survives + // the view (DrawBuilding Ghidra 0x0059f2a0: per-view viewconeCheck + // → portal-BSP walk → ConstructView's GetClip; NO distance + // constant anywhere on the chain). Port: a per-BUILDING frustum + // pre-gate on the aperture bounds (Building.PortalBounds — the + // tight equivalent of the shell viewconeCheck for FLOOD purposes), + // replacing the old Chebyshev≤1 landblock cell-sweep; the 48 m + // seed cap dies with it (RetailPViewRenderer seeds at ∞). The + // per-portal admission stays BuildFromExterior's screen clip + // (empty clip = no seed) — retail's GetClip-vs-view gate. + // Per-building iteration is also the FPS fix the 2026-06-07 + // Chebyshev hack approximated: dozens of AABB tests instead of an + // O(all loaded cells) portal sweep. + foreach (var registry in _buildingRegistries.Values) { - uint onLb = (onEntry.LandblockId >> 16) & 0xFFFFu; - if (playerLb.HasValue) + foreach (var b in registry.All()) { - int gX = (int)((onLb >> 8) & 0xFFu); - int gY = (int)(onLb & 0xFFu); - if (Math.Max(Math.Abs(gX - onGridX), Math.Abs(gY - onGridY)) > 1) continue; + if (b.HasPortalBounds + && !AcDream.App.Rendering.FrustumCuller.IsAabbVisible( + frustum, b.PortalBounds.Min, b.PortalBounds.Max)) + continue; + + foreach (uint cid in b.EnvCellIds) + if (_cellVisibility.TryGetCell(cid, out var bc) && bc is not null) + _outdoorNodeBuildingCells.Add(bc); } - if (!_outdoorNodeSeenLbs.Add(onLb)) continue; - foreach (var onCell in _cellVisibility.GetCellsForLandblock(onLb)) - _outdoorNodeBuildingCells.Add(onCell); } _outdoorNode = AcDream.App.Rendering.OutdoorCellNode.Build(viewerCellId); if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled) Console.WriteLine(System.FormattableString.Invariant( - $"[outdoor-node] cell=0x{viewerCellId:X8} nearbyCells={_outdoorNodeBuildingCells.Count} (R-A2 per-building floods)")); + $"[outdoor-node] cell=0x{viewerCellId:X8} nearbyCells={_outdoorNodeBuildingCells.Count} (T2 frustum-gated per-building floods)")); } uint playerCellId = _physicsEngine.DataCache?.CellGraph.CurrCell?.Id ?? 0u; diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 93b045bc..e2faaf6a 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -26,7 +26,13 @@ public sealed class RetailPViewRenderer // R-A2: per-building flood grouping, reused across frames (inner lists cleared each frame). private readonly Dictionary> _buildingGroups = new(); - private const float OutdoorBuildingSeedDistance = 48f; + + // T2 (BR-4): retail has NO distance constant on the flood-admission chain + // (DrawBuilding → portal walk → ConstructView: viewconeCheck + side test + + // GetClip + GetVisible only). The old 48 m seed cap is replaced by the + // caller's per-building frustum pre-gate on aperture bounds (GameWindow's + // gather); seeds themselves are unbounded. + private const float OutdoorBuildingSeedDistance = float.PositiveInfinity; public RetailPViewRenderer( GL gl,