using System; using System.Collections.Generic; using System.Numerics; using AcDream.App.Rendering.Scene; using AcDream.Core.Physics; using AcDream.Core.World; namespace AcDream.App.Rendering; /// /// App-layer port of the retail indoor render orchestration: /// SmartBox::RenderNormalMode -> RenderDeviceD3D::DrawInside -> /// PView::DrawInside -> ConstructView -> DrawCells. /// internal sealed class RetailPViewRenderer { private readonly RenderSceneShadowRuntime _renderSceneShadow; private readonly ClipFrameAssembly _clipAssemblyScratch = new(); private readonly RetailPViewFrameResult _frameResultScratch = new(); private static readonly ClipViewSlice NoClipSlice = new(0, new Vector4(-1f, -1f, 1f, 1f), Array.Empty()); private static readonly IReadOnlySet NoParticleOwners = new HashSet(); private readonly HashSet _cellParticleOwnerScratch = new(); // MP-Alloc (2026-07-05): DrawInside's drawable-cell set, reused across // frames instead of `new HashSet(pvFrame.OrderedVisibleCells)` every // call. Every walk consumer reads it synchronously in this frame. private readonly HashSet _drawableCellsScratch = new(); private readonly HashSet _visibleCellsScratch = new(); // FW3 visual-gate fix: the interior root's dynamics phase, invoked by // the driver's clearInteriorDepth closure at the walk's pre-clear // boundary (retail draws outside objects inside LScape::draw, before // the clear+seals). Assigned per frame around the Replay, always // cleared in finally. private Action? _walkPreClearDynamics; // Output-only Facility Hub staircase probe. This counter is consulted // only while ACDREAM_PROBE_FACILITY_STAIRS=1 and never affects the walk. private ulong _probeFacilityStairFrame; private ulong _probeCathedralShellOrderFrame; private string? _probeFacilityStairRootSignature; private string? _probeCathedralStairRootSignature; // FW6 allocation closeout: the walk's large event/view/route scratch, // frame context, and one-cell leaf collections are renderer-lifetime // owners. Only their frame-local bindings change. Before this cutover all // three were constructed inside DrawInside and accounted for the measured // ~1.5 MiB/frame dense-town tail. private Walk.WalkFrameDriver? _walkFrameDriverScratch; private Walk.WalkProductionFrameContext? _walkFrameContextScratch; private WalkProductionLeafRenderer? _walkLeafRendererScratch; private readonly Action _walkClearInteriorDepthAction; private readonly Action _walkDrawExitSealsAction; private RetailPViewPassExecutor? _activeWalkPasses; private RetailPViewFrameInput? _activeWalkFrame; private ClipFrameAssembly? _activeWalkClipAssembly; // Campaign FW3.2b-2: the walk's production world-data registries // (published/retired by LandblockRenderPublisher) plus the per-frame // driver state. Null until the composition passes them; the static // cutover requires all three. private readonly Walk.WalkBuildingRegistry _walkBuildings; private readonly Walk.WalkLandscapeAssembler _walkLandscape; private readonly CellVisibility _walkCellRegistry; // Campaign FW3.2b-2: the production IWalkFrameWorldData over the retained // scene — owned here (not just inside the driver) because DrawInside also // reads it directly to re-source particle owners for the routes the walk // now draws (plan §FW3 item 4). Non-null exactly when _walkBuildings is. private readonly Walk.WalkProductionWorldData _walkWorldData; internal RetailPViewRenderer( RenderSceneShadowRuntime renderSceneShadow, Walk.WalkBuildingRegistry walkBuildings, Walk.WalkLandscapeAssembler walkLandscape, CellVisibility walkCellRegistry, ShadowObjectRegistry shadows, Func? findParentLocalId = null) { _renderSceneShadow = renderSceneShadow ?? throw new ArgumentNullException(nameof(renderSceneShadow)); _walkBuildings = walkBuildings ?? throw new ArgumentNullException(nameof(walkBuildings)); _walkLandscape = walkLandscape ?? throw new ArgumentNullException(nameof(walkLandscape)); _walkCellRegistry = walkCellRegistry ?? throw new ArgumentNullException(nameof(walkCellRegistry)); _walkWorldData = new Walk.WalkProductionWorldData( _walkBuildings, shadows ?? throw new ArgumentNullException(nameof(shadows)), findParentLocalId); _walkClearInteriorDepthAction = ClearWalkInteriorDepth; _walkDrawExitSealsAction = DrawWalkExitSeals; } // 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; internal RetailPViewFrameResult DrawInside( RetailPViewFrameInput ctx, RetailPViewPassExecutor passes) { ArgumentNullException.ThrowIfNull(ctx); ArgumentNullException.ThrowIfNull(passes); passes.BeginFrame(); // Campaign FW4: there is one production renderer and one visibility // owner. The fake/legacy executor path was useful during the cutover, // but retaining it here kept the retired PortalVisibilityBuilder and // look-in seed machinery alive. Fail loudly if composition ever tries // to reintroduce that split. RetailPViewPassExecutor walkExecutor = passes as RetailPViewPassExecutor ?? throw new InvalidOperationException( "The retail frame walk requires RetailPViewPassExecutor."); ClipFrameAssembly clipAssembly = passes.BeginWalkClipFrame( ctx.RootCell.IsOutdoorNode, _clipAssemblyScratch); // FW4 slice 1: PrepareClipFrame (the one clip-region publication) // moved BELOW the walk block — an interior-rooted walk frame // re-derives the outside-view slices from the walk's own views // first, so the appended slots join the same single publication. // R1: draw EVERY visible cell (retail cell_draw_list), not only the cells the // assembler handed a clip-slot. This feeds the Prepare filter + entity partition, // so every visible cell's shell has a prepared batch and seals — killing the grey // (the old clipAssembly.CellIdToSlot.Keys filter silently dropped slot-less cells). // Per-slice trim still applies in DrawEnvCellShells (Task 4 makes it self-contained). _drawableCellsScratch.Clear(); HashSet drawableCells = _drawableCellsScratch; passes.UseIndoorMembershipOnlyRouting(); // Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/ // walkCameraCell exactly as the pre-FW3.4a pre-walk collection pass // did, then drives WalkFrameDriver.Collect — a SINGLE RetailFrameWalk // pass that both learns the flood/visited-cell set (needed below, // BEFORE prepareCells is finalized, so EnvCellRenderer prepares // batches for every shell the driver will draw later in this same // DrawInside call) and records the walk's draw events for Replay // further down, in DrawWalkDrivenStatics. The former SECOND walk pass // (a dedicated set-collecting sink, run again through this same // driver machinery just to submit) is gone — see WalkFrameDriver's // own doc comment for the FW3.4 perf numbers that motivated this. // _walkWorldData.BeginFrame precedes Collect deliberately: Collect's // stream appends classify records immediately (WalkStaticStreamPopulator // runs at append time, not at Replay time), so the world data must // already be rebuilt for this frame before the walk starts. Walk.WalkFrameDriver walkDriver; { Matrix4x4 view = ctx.CameraView; var forward = Vector3.Normalize(new Vector3(-view.M13, -view.M23, -view.M33)); (int Width, int Height)? attachment = walkExecutor!.WalkAttachmentExtent; // Fallback matches the FW3.2b-2 shadow probe's own comment: every // screen projection shares the same constants, so this is only // reached before the world pass has published its scope. float viewportWidth = attachment?.Width ?? 1024f; float viewportHeight = attachment?.Height ?? 720f; if (_walkFrameContextScratch is null) { _walkFrameContextScratch = new Walk.WalkProductionFrameContext( _walkCellRegistry, _walkBuildings, ctx.ViewerEyePos, forward, ctx.ViewProjection, viewportWidth, viewportHeight); } else { _walkFrameContextScratch.Reset( ctx.ViewerEyePos, forward, ctx.ViewProjection, viewportWidth, viewportHeight); } Walk.WalkProductionFrameContext walkContext = _walkFrameContextScratch; _walkLandscape!.SetViewer(ctx.ViewerCellId, ctx.ViewerEyePos); Walk.WalkLandscape walkLandscape = _walkLandscape.Landscape; Walk.WalkCell? walkCameraCell = null; if ((ctx.ViewerCellId & 0xFFFFu) >= 0x100) { walkCameraCell = _walkCellRegistry!.TryGetCell(ctx.ViewerCellId, out LoadedCell? loaded) ? loaded?.Walk : null; if (walkCameraCell is null) { throw new InvalidOperationException( $"walk root=0x{ctx.ViewerCellId:X8}: the interior camera cell has " + "no committed walk data — the Campaign FW3.2b-2 static cutover " + "requires the walk registry to already hold the viewer's own cell " + "(fail-loud rule; a silently skipped root would leave the frame " + "with no static draws at all)."); } } if (ctx.RootCell.IsOutdoorNode && clipAssembly.OutsideViewSlices.Length != 1) { throw new InvalidOperationException( "walk static cutover: an outdoor root's clip assembly produced " + $"{clipAssembly.OutsideViewSlices.Length} outside-view slices, not the " + "expected 1 — the outdoor root draws through the full-screen default " + "view (retail set_default_view; the walk fans exactly its own 1), and " + "the outdoor slice data still comes from the assembler (plan §FW3 item " + "2c's pinned assumption; assert rather than silently coercing to 1)."); } _walkWorldData!.BeginFrame( _renderSceneShadow!.Query, ctx.PlayerLandblockId ?? 0u, ctx.RenderCenterLbX, ctx.RenderCenterLbY); _activeWalkPasses = walkExecutor; _activeWalkFrame = ctx; _activeWalkClipAssembly = clipAssembly; if (_walkLeafRendererScratch is null) { _walkLeafRendererScratch = new WalkProductionLeafRenderer( walkExecutor, ctx, clipAssembly, _walkClearInteriorDepthAction, _walkDrawExitSealsAction); } else { _walkLeafRendererScratch.Reset( walkExecutor, ctx, clipAssembly, _walkClearInteriorDepthAction, _walkDrawExitSealsAction); } if (_walkFrameDriverScratch is null) { _walkFrameDriverScratch = new Walk.WalkFrameDriver( walkExecutor.Dispatcher, _walkLeafRendererScratch, _walkWorldData, clipFrame: clipAssembly.Frame); } else { _walkFrameDriverScratch.RebindFrame( _walkLeafRendererScratch, clipAssembly.Frame); } walkDriver = _walkFrameDriverScratch; try { walkDriver.Collect( _frameWalk, ctx.ViewerCellId, walkCameraCell, walkLandscape, walkContext, ctx.ViewProjection, ctx.CameraWorldPosition); } catch { ClearWalkFrameBindings(); throw; } // FW4 slice 1: an interior root's terrain/sky/punch clip slices // come from THE WALK'S OWN outside_view — retail's one // visibility structure. See ClipFrameAssembler. // ReassembleOutsideViewFromWalk's doc comment for the boundary- // frame desync (the stairwell/grass flash) this retires. The // outdoor root keeps the assembler's single full-screen slice // (asserted ==1 above; identical content by construction). if (walkCameraCell is not null) { ClipFrameAssembler.ReassembleOutsideViewFromWalk( clipAssembly, _frameWalk.InteriorOutsideView, viewportWidth, viewportHeight); } // The walk's visited set replaces PortalVisibilityFrame's old // OrderedVisibleCells side-channel for every production consumer. _drawableCellsScratch.Clear(); _drawableCellsScratch.UnionWith(walkDriver.VisitedCells); walkDriver.CopyVisibleCellsTo(_visibleCellsScratch); if (AcDream.Core.Rendering.RenderingDiagnostics .ProbeCathedralShellOrderEnabled && ((ctx.ViewerCellId & 0xFFFF0000u) == 0xF4180000u || (ctx.PlayerCellId & 0xFFFF0000u) == 0xF4180000u)) { _probeCathedralShellOrderFrame++; walkDriver.TraceCathedralShellOrder( _probeCathedralShellOrderFrame, ctx.ViewerCellId, ctx.PlayerCellId, ctx.RootCell.CellId, ctx.ViewerEyePos); } if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeFacilityStairsEnabled) { static int TargetMembership(IReadOnlyCollection cells) { const uint a = 0x8A02015Eu; const uint b = 0x8A02015Fu; const uint c = 0x8A0201C1u; int mask = 0; foreach (uint cell in cells) { if (cell == a) mask |= 1; if (cell == b) mask |= 2; if (cell == c) mask |= 4; } return mask; } _probeFacilityStairFrame++; int floodMask = TargetMembership(walkDriver.InteriorFloodCells); int visitedMask = TargetMembership(walkDriver.VisitedCells); int turnMask = TargetMembership(walkDriver.LookInCells); string signature = $"{ctx.ViewerCellId:X8}:{ctx.PlayerCellId:X8}:" + $"{ctx.RootCell.CellId:X8}:{floodMask}:{visitedMask}:{turnMask}:" + $"{walkDriver.InteriorFloodCells.Count}:{walkDriver.VisitedCells.Count}:" + $"{walkDriver.LookInCellTurns.Count}"; bool changed = !string.Equals( _probeFacilityStairRootSignature, signature, StringComparison.Ordinal); _probeFacilityStairRootSignature = signature; if (changed) { static string DescribeMask(int mask) => $"15e={((mask & 1) != 0 ? 1 : 0)}," + $"15f={((mask & 2) != 0 ? 1 : 0)}," + $"1c1={((mask & 4) != 0 ? 1 : 0)}"; Console.WriteLine( $"[facility-root] f={_probeFacilityStairFrame} changed={(changed ? 1 : 0)} " + $"viewer=0x{ctx.ViewerCellId:X8} player=0x{ctx.PlayerCellId:X8} " + $"root=0x{ctx.RootCell.CellId:X8} res={ctx.CameraCellResolution} " + $"eye=({ctx.ViewerEyePos.X:F4},{ctx.ViewerEyePos.Y:F4},{ctx.ViewerEyePos.Z:F4}) " + $"playerPos=({ctx.PlayerViewPosition.X:F4},{ctx.PlayerViewPosition.Y:F4},{ctx.PlayerViewPosition.Z:F4}) " + $"rootFlood={walkDriver.InteriorFloodCells.Count}" + $"[{DescribeMask(floodMask)}] " + $"visited={walkDriver.VisitedCells.Count}" + $"[{DescribeMask(visitedMask)}] " + $"turns={walkDriver.LookInCellTurns.Count}" + $"[{DescribeMask(turnMask)}]"); } static int CathedralMembership(IReadOnlyCollection cells) { const uint oldBuilding = 0xF4180107u; const uint stairParent = 0xF4180112u; int mask = 0; foreach (uint cell in cells) { if (cell == oldBuilding) mask |= 1; if (cell == stairParent) mask |= 2; } return mask; } static uint BuildingAnchor(Walk.WalkBuilding building) { foreach (Walk.WalkBldPortal portal in building.Portals) { if (portal.OtherCellId != 0xFFFFFFFFu) return portal.OtherCellId; } return 0u; } int cathedralFlood = CathedralMembership(walkDriver.InteriorFloodCells); int cathedralVisited = CathedralMembership(walkDriver.VisitedCells); int cathedralLookIn = CathedralMembership(walkDriver.LookInCells); bool bucket107 = _walkWorldData.StaticBucketContains( 0xF4180107u, 0x020009A2u); bool bucket112 = _walkWorldData.StaticBucketContains( 0xF4180112u, 0x020009A2u); string buildingAnchors = string.Join(",", walkDriver.VisitedBuildings .ConvertAll(building => $"0x{BuildingAnchor(building):X8}")); string cathedralSignature = $"{ctx.ViewerCellId:X8}:{ctx.PlayerCellId:X8}:{ctx.RootCell.CellId:X8}:" + $"{cathedralFlood}:{cathedralVisited}:{cathedralLookIn}:" + $"{(bucket107 ? 1 : 0)}:{(bucket112 ? 1 : 0)}:{buildingAnchors}"; bool cathedralChanged = !string.Equals( _probeCathedralStairRootSignature, cathedralSignature, StringComparison.Ordinal); _probeCathedralStairRootSignature = cathedralSignature; if (cathedralChanged && ((ctx.ViewerCellId & 0xFFFF0000u) == 0xF4180000u || (ctx.PlayerCellId & 0xFFFF0000u) == 0xF4180000u)) { static string DescribeCathedralMask(int mask) => $"107={((mask & 1) != 0 ? 1 : 0)}," + $"112={((mask & 2) != 0 ? 1 : 0)}"; Console.WriteLine( $"[cathedral-stair] f={_probeFacilityStairFrame} " + $"viewer=0x{ctx.ViewerCellId:X8} player=0x{ctx.PlayerCellId:X8} " + $"root=0x{ctx.RootCell.CellId:X8} res={ctx.CameraCellResolution} " + $"eye=({ctx.ViewerEyePos.X:F4},{ctx.ViewerEyePos.Y:F4},{ctx.ViewerEyePos.Z:F4}) " + $"playerPos=({ctx.PlayerViewPosition.X:F4},{ctx.PlayerViewPosition.Y:F4},{ctx.PlayerViewPosition.Z:F4}) " + $"rootFlood=[{DescribeCathedralMask(cathedralFlood)}] " + $"visited=[{DescribeCathedralMask(cathedralVisited)}] " + $"lookIn=[{DescribeCathedralMask(cathedralLookIn)}] " + $"buckets=[107={(bucket107 ? 1 : 0)},112={(bucket112 ? 1 : 0)}] " + $"buildings=[{buildingAnchors}]"); } } } // FW4 slice 1: the ONE clip-region publication, after any walk // reassembly so the walk-derived outside-view slots are included // (moved from directly after AssembleClipFrame; the count is // reservation metadata the RHI arm ignores). int terrainUploadCount = checked(1 + clipAssembly.OutsideViewSlices.Length * 2); passes.PrepareClipFrame(terrainUploadCount); // Production prepares exactly the one walk's visited-cell set. HashSet prepareCells = drawableCells; // (#176 correction, 2026-07-06: the flood-scoped light-pool rebuild that ran // here was the seam-floor flicker mechanism — retail's visible_cell_table is // the RESIDENT-cell registry, not the frame flood — and is deleted. The pool // is built once per frame in GameWindow, player-anchored.) passes.PrepareCellBatches(ctx, prepareCells); { RenderProjectionCounts retainedCounts = _renderSceneShadow.Counts; RenderFrameDiagnosticCounts counts = WalkDiagnosticCounts(retainedCounts); RenderProjectionCounts sourceCounts = retainedCounts; RetailPViewFrameResult result = _frameResultScratch.Reset( clipAssembly, drawableCells, _visibleCellsScratch, counts, sourceCounts, diagnosticPartition: null); passes.EmitDiagnostics(ctx, result); // The one collected walk owns terrain, cell shells, buildings, // statics, dynamics, particles, punches, and alpha barriers at // their retail turns. Interior-root landscape services run at the // pre-clear callback; outdoor roots have no clear and run after // replay. _walkPreClearDynamics = () => { passes.UseIndoorMembershipOnlyRouting(); DrawLandscapeDynamicsPhase( ctx, passes, clipAssembly); }; try { DrawWalkDrivenStatics(ctx, walkExecutor, walkDriver!); passes.UseIndoorMembershipOnlyRouting(); if (ctx.RootCell.IsOutdoorNode) { DrawLandscapeDynamicsPhase( ctx, passes, clipAssembly); } } finally { _walkPreClearDynamics = null; ClearWalkFrameBindings(); } // OUTDOOR root: the LScape-boundary alpha drain deferred from the // landscape stage runs HERE, after punches, interior shells, cell // objects, and the dynamics pass — the frame's complete opaque // world. Retail's walk draws all of those before its boundary // flush (LScape::draw includes every cell's objects, // DrawSortCell 0x005A17C0), so this is the same one-list far→near // composite over finished depth; draining at the stage end instead // let every later opaque mesh overwrite the flames (#132). if (ctx.RootCell.IsOutdoorNode) passes.FlushLandscapeAlpha(); // Interior-cell UNATTACHED emitters (spell ground effects and // swirls anchored in EnvCells) draw in this final world scope — // the cells' walls and the seals already own the depth buffer, so // one unclipped submission matches retail's cell-walk insertion. // Outdoor-cell unattached emitters drew in the landscape stage. passes.DrawUnattachedSceneParticles(ctx, outdoorCells: false); return result; } } private readonly Walk.RetailFrameWalk _frameWalk = new(); private void ClearWalkInteriorDepth() { RetailPViewPassExecutor passes = _activeWalkPasses ?? throw new InvalidOperationException( "The retained walk leaf has no active pass binding."); // FW3 visual-gate fix (owner report: doors/candles invisible looking // out; the crossing vanish): retail draws outside objects inside // LScape::draw, before the clear+seals. _walkPreClearDynamics?.Invoke(); passes.FlushLandscapeAlpha(); passes.ClearInteriorDepth(); } private void DrawWalkExitSeals() { RetailPViewFrameInput frame = _activeWalkFrame ?? throw new InvalidOperationException( "The retained walk leaf has no active frame binding."); RetailPViewPassExecutor passes = _activeWalkPasses ?? throw new InvalidOperationException( "The retained walk leaf has no active pass binding."); Walk.WalkFrameDriver driver = _walkFrameDriverScratch ?? throw new InvalidOperationException( "The retained walk leaf has no active driver binding."); DrawWalkExitPortalMasks(frame, passes, driver); } private void ClearWalkFrameBindings() { _walkFrameDriverScratch?.AbortFrame(); _activeWalkPasses = null; _activeWalkFrame = null; _activeWalkClipAssembly = null; } /// Campaign FW3.2b-2 — THE PRODUCTION ROOTING; Campaign FW3.4a — /// REPLAY ONLY. already ran its Collect pass /// earlier in (before PrepareCellBatches); /// this method's job is now just /// (terrain/sky, every building's shell + punch + look-in cell statics, /// and the interior root's own flood shells + statics, with retail's /// depth-clear/exit-seal turn — all in walk order, replacing /// 's static half, /// 's old top-level call, /// , and 's /// static half for this frame) plus re-sourcing the particle owners the /// routes it replaced used to ride, from the driver's own visited /// sets (plan §FW3 "FW3.2b-2 — the production rooting", items 2 and /// 4). private void DrawWalkDrivenStatics( RetailPViewFrameInput ctx, RetailPViewPassExecutor passes, Walk.WalkFrameDriver driver) { var (frame, encoder) = passes.RequireWalkSubmission(); passes.SetWalkTerrainInViewLandcells(driver.VisitedLandscapeCellIds); try { driver.Replay(frame, encoder); } finally { passes.SetWalkTerrainInViewLandcells(null); } // Landscape-stage static-owner particles (candles, the cathedral // falls) submit AT THEIR OWN WALK TURNS inside Replay // (WalkFrameEventKind.StaticParticles) for BOTH root kinds — the // #132 positional invariant (e102fb36's user-verified rule ported // walk-natively): each owner cell's emitters enter the one alpha // queue at that cell's far-to-near position, so every nearer // building's pre-punch AlphaBarrier drains the farther content // against still-true depth before its punch stamps far-Z. // Cell-stage particle owners for the interior root's OWN flood cells // (non-look-in — the walk draws their statics too, via // OnInteriorFloodDrawTurn/EmitCellTurn, so the retired CellStatic // route's cell-particle submission needs the same re-sourcing). // These stay POST-replay: interior emitters draw in the final world // scope where the cell walls already own depth (retail's cell-walk // insertion). Look-in cells get their OWN per-cell union in // DrawBuildingLookInDynamics so a static owner is never submitted // twice. _cellParticleOwnerScratch.Clear(); foreach (uint cellId in driver.VisitedCells) { if (driver.LookInCells.Contains(cellId)) continue; UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch); } if (_cellParticleOwnerScratch.Count > 0) { passes.DrawCellParticles( ctx, new RetailPViewCellSliceContext(0u, NoClipSlice, _cellParticleOwnerScratch)); } } private static void UnionRecordOwners( Walk.WalkFrameStaticRecords records, HashSet destination) { foreach (RenderProjectionRecord record in records.Records) { if (record.Source.LocalEntityId != 0) destination.Add(record.Source.LocalEntityId); } } /// Campaign FW3.2b-2: the DYNAMICS-only remainder of the old /// + /// split. The walk now draws every mesh and every attached emitter at its /// own turn. This tail therefore carries only the two landscape services /// that are not scene projections: ownerless outdoor-cell emitters and /// weather. It deliberately submits no packed entity route. private void DrawLandscapeDynamicsPhase( RetailPViewFrameInput ctx, RetailPViewPassExecutor passes, ClipFrameAssembly clipAssembly) { if (clipAssembly.OutsideViewSlices.Length == 0) return; // Ownerless OUTDOOR-cell emitters — now unconditional: the old // hasBuildingLookIns gate only existed to sequence this submission // around the OLD static barrier drains, which no longer run here // (the walk owns its own alpha barriers — WalkFrameDriver.OnBuildingTurn). passes.DrawUnattachedSceneParticles(ctx, outdoorCells: true); // GameSky's weather pass still runs through each active landscape // view so doorway scissor/clip state matches the old executor. The // dynamics collection is intentionally empty: the walk already drew // those records at OnLandscapeCellTurn. foreach (var slice in clipAssembly.OutsideViewSlices) { passes.SetTerrainClip(slice.Planes); passes.ClearClipRouting(); passes.DrawLandscapeSliceLate( ctx, new RetailPViewLandscapeLateSliceContext( slice, Array.Empty())); } passes.UseIndoorMembershipOnlyRouting(); } /// Campaign FW4 slice 2 — the walk-flood seal draw. Retail's /// PView::DrawCells stamps every exit portal of THE FLOOD'S OWN /// cells (pc:432785-432786, reverse cell_draw_list far→near) — one /// visibility structure decides the flood, the seals, and the terrain /// views alike. The old apparatus's flood misses exit portals at the /// #456 cathedral seam band (its never-drawn panel family), leaving /// aperture depth unsealed after the interior clear; the end-of-frame /// alpha drain (cell-owned emitters — retail's own timing) then /// z-passes across the whole opening (the falls shine-through). Each /// portal is stamped once per exact walk-owned view captured for that /// flood cell, matching retail's CEnvCell::setup_view loop. The /// legacy visibility assembly has no production role here. private void DrawWalkExitPortalMasks( RetailPViewFrameInput ctx, RetailPViewPassExecutor passes, Walk.WalkFrameDriver driver) { List floodCells = driver.InteriorFloodCells; for (int i = floodCells.Count - 1; i >= 0; i--) { uint cellId = floodCells[i]; int sliceCount = driver.InteriorFloodViewSliceCountAt(i); for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++) { passes.DrawExitPortalMask( ctx, cellId, driver.InteriorFloodViewClipPlanesAt(i, sliceIndex)); } } } private static RenderFrameDiagnosticCounts WalkDiagnosticCounts( RenderProjectionCounts source) { int dynamics = checked( source.LiveDynamicRoot + source.ActiveAnimatedStatic + source.EquippedChild); return new RenderFrameDiagnosticCounts( source.OutdoorStatic, source.IndoorCellStatic, dynamics, TransformCount: source.Total, OpaqueClassificationCount: 0, AlphaClassificationCount: 0, LightSetCount: 0, SelectionPartCount: 0, RouteCandidateCount: source.Total, EntityCandidateCount: source.Total, MeshPartCount: 0); } } public interface IRetailPViewCellSource { LoadedCell? Find(uint cellId); } /// /// Typed execution seam for the GL passes ordered by /// . Implementations execute the requested /// pass only; visibility construction and draw ordering remain renderer-owned. /// All frame inputs and results are borrowed for the duration of the call. /// public sealed class RetailPViewFrameInput { public LoadedCell RootCell { get; private set; } = null!; /// R-A2: nearby building cells (BuildingId-tagged) flooded per-building when the root is the /// outdoor node. Null for interior roots. Grouped by BuildingId inside . public IReadOnlyList? NearbyBuildingCells { get; private set; } public Vector3 ViewerEyePos { get; private set; } public Matrix4x4 ViewProjection { get; private set; } public IRetailPViewCellSource Cells { get; private set; } = null!; public ICamera Camera { get; private set; } = null!; public Vector3 CameraWorldPosition { get; private set; } public FrustumPlanes? Frustum { get; private set; } public uint? PlayerLandblockId { get; private set; } public HashSet? AnimatedEntityIds { get; private set; } public int RenderCenterLbX { get; private set; } public int RenderCenterLbY { get; private set; } public int RenderRadius { get; private set; } public IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList Entities, IReadOnlyDictionary? AnimatedById)> LandblockEntries { get; private set; } = Array.Empty<(uint, Vector3, Vector3, IReadOnlyList, IReadOnlyDictionary?)>(); // Pass-presentation and diagnostic values consumed synchronously by the // typed executor. This input is data-only and is never retained. public bool RenderSky { get; private set; } public bool RenderWeather { get; private set; } public float DayFraction { get; private set; } public DayGroupData? ActiveDayGroup { get; private set; } public SkyKeyframe SkyKeyframe { get; private set; } public bool EnvironOverrideActive { get; private set; } public uint ViewerCellId { get; private set; } public uint PlayerCellId { get; private set; } public Vector3 PlayerViewPosition { get; private set; } public Matrix4x4 CameraView { get; private set; } public CameraCellResolution CameraCellResolution { get; private set; } internal RetailPViewFrameInput Reset( LoadedCell rootCell, IReadOnlyList? nearbyBuildingCells, Vector3 viewerEyePos, Matrix4x4 viewProjection, IRetailPViewCellSource cells, ICamera camera, Vector3 cameraWorldPosition, FrustumPlanes? frustum, uint? playerLandblockId, HashSet? animatedEntityIds, int renderCenterLbX, int renderCenterLbY, int renderRadius, IReadOnlyList<(uint LandblockId, Vector3 AabbMin, Vector3 AabbMax, IReadOnlyList Entities, IReadOnlyDictionary? AnimatedById)> landblockEntries, bool renderSky, bool renderWeather, float dayFraction, DayGroupData? activeDayGroup, SkyKeyframe skyKeyframe, bool environOverrideActive, uint viewerCellId, uint playerCellId, Vector3 playerViewPosition, Matrix4x4 cameraView, CameraCellResolution cameraCellResolution) { RootCell = rootCell; NearbyBuildingCells = nearbyBuildingCells; ViewerEyePos = viewerEyePos; ViewProjection = viewProjection; Cells = cells; Camera = camera; CameraWorldPosition = cameraWorldPosition; Frustum = frustum; PlayerLandblockId = playerLandblockId; AnimatedEntityIds = animatedEntityIds; RenderCenterLbX = renderCenterLbX; RenderCenterLbY = renderCenterLbY; RenderRadius = renderRadius; LandblockEntries = landblockEntries; RenderSky = renderSky; RenderWeather = renderWeather; DayFraction = dayFraction; ActiveDayGroup = activeDayGroup; SkyKeyframe = skyKeyframe; EnvironOverrideActive = environOverrideActive; ViewerCellId = viewerCellId; PlayerCellId = playerCellId; PlayerViewPosition = playerViewPosition; CameraView = cameraView; CameraCellResolution = cameraCellResolution; return this; } } /// /// Borrowed renderer scratch valid only until the next /// call. Collections and nested /// frame objects are deliberately reused to keep the render loop allocation /// free; consumers must copy any state they need to retain asynchronously. /// public sealed class RetailPViewFrameResult { public ClipFrameAssembly ClipAssembly { get; private set; } = null!; public HashSet DrawableCells { get; private set; } = null!; /// /// The production retail walk's exact visited-cell set. This is the one /// visibility answer consumed by EnvCell preparation, particles, lights, /// and directional-shadow filtering. /// public HashSet VisibleCells { get; private set; } = null!; internal RenderFrameDiagnosticCounts DiagnosticCounts { get; private set; } internal RenderProjectionCounts SourceCounts { get; private set; } internal InteriorEntityPartition.Result? DiagnosticPartition { get; private set; } internal RetailPViewFrameResult Reset( ClipFrameAssembly clipAssembly, HashSet drawableCells, HashSet visibleCells, RenderFrameDiagnosticCounts diagnosticCounts, RenderProjectionCounts sourceCounts, InteriorEntityPartition.Result? diagnosticPartition) { ClipAssembly = clipAssembly; DrawableCells = drawableCells; VisibleCells = visibleCells; DiagnosticCounts = diagnosticCounts; SourceCounts = sourceCounts; DiagnosticPartition = diagnosticPartition; return this; } } /// /// Scene-particle owners for ONE unclipped landscape-stage submission (the /// union of every outside slice's cone survivors). Mesh alpha for the same /// owners is already queued by the entity routes; retail inserts each /// emitter's polys into the single alpha list once, during its owner cell's /// walk turn, with no portal-view clip. /// public readonly record struct RetailPViewLandscapeStaticParticleContext( IReadOnlySet ParticleOwnerIds); /// #131/#132: the late landscape phase's per-slice payload — /// outside-stage dynamics to mesh-draw, plus the particle owners not already /// submitted at a pre-building barrier. public readonly record struct RetailPViewLandscapeLateSliceContext( ClipViewSlice Slice, IReadOnlyList Dynamics); public readonly record struct RetailPViewCellSliceContext( uint CellId, ClipViewSlice Slice, IReadOnlySet ParticleOwnerIds);