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 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-11 12:46:46 +02:00
parent 529dfcfee9
commit 88f3ce1fa0
2 changed files with 32 additions and 15 deletions

View file

@ -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<LoadedCell> _outdoorNodeBuildingCells = new();
private readonly HashSet<uint> _outdoorNodeSeenLbs = new();
private readonly HashSet<uint> _outdoorSceneParticleEntityIds = new();
private readonly HashSet<uint> _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;

View file

@ -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<uint, List<LoadedCell>> _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,