From 98d2d43fdc812c970c8b6b83a287f06a2397a091 Mon Sep 17 00:00:00 2001 From: Erik Date: Mon, 31 Aug 2026 04:50:08 +0200 Subject: [PATCH] refactor(render): make the walk sole production visibility owner --- .../Rendering/RetailPViewRenderer.cs | 127 +++++++++--------- 1 file changed, 63 insertions(+), 64 deletions(-) diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 301a41ed..8cf99e66 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -165,70 +165,9 @@ public sealed class RetailPViewRenderer RecycleLookInFrames(); ResetBuildingGroups(); - var pvFrame = PortalVisibilityBuilder.Build( - ctx.RootCell, - ctx.ViewerEyePos, - ctx.Cells.Find, - ctx.ViewProjection, - buildingMembership: null, - reuseFrame: _mainPortalFrameScratch); - - // R-A2: outdoor root — flood each nearby building SEPARATELY from its own entrance and merge - // the small (~2-cell) per-building views into the frame. Retail reaches building interiors via - // the terrain BSP -> DrawPortal -> ConstructView(CBldPortal) (decomp:326881/433895/433827); the - // land root itself has no portals (it floods nothing into buildings). Per-building seeding is - // robust to the eye's ~36 µm rest jitter where the pre-R-A2 single reverse-portal flood - // oscillated as the chase eye grazed a doorway (the indoor flap). - if (ctx.RootCell.IsOutdoorNode && ctx.NearbyBuildingCells is not null) - MergeNearbyBuildingFloods(ctx, pvFrame); - - // #124: interior-root building look-ins. Retail runs the look-in INSIDE - // the landscape stage for ANY root — LScape::draw is the FIRST call of - // DrawCells' outside-view branch (pc:432719), strictly BEFORE the depth - // clear (pc:432732) and the exit-portal seals (pc:432785); a far - // building seen through our doorway floods clipped to the INSTALLED - // outside view (GetClip vs current view, ConstructView(CBldPortal) - // 0x005a59a0). These frames therefore draw in DrawBuildingLookIns - // (inside the landscape stage), NEVER merged into the main frame — a - // merged cell would draw post-clear and z-fail against the root's seal - // (its geometry is beyond the door plane). The eye-side seed test - // self-excludes the root's own building (the eye is on its interior - // side). Outdoor roots keep the MergeNearbyBuildingFloods path above - // (no depth clear under outdoor roots — the merged form is equivalent - // there). - if (!ctx.RootCell.IsOutdoorNode - && ctx.NearbyBuildingCells is not null - && pvFrame.OutsideView.Polygons.Count > 0) - BuildInteriorRootLookIns(ctx, pvFrame); - - var clipAssembly = passes.AssembleClipFrame( - pvFrame, - _clipAssemblyScratch); - passes.AppendLookInClipFrames(_lookInFrames, clipAssembly); - // 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(); - _drawableCellsScratch.UnionWith(pvFrame.OrderedVisibleCells); - var drawableCells = _drawableCellsScratch; - passes.UseIndoorMembershipOnlyRouting(); - - // Campaign FW3.2b-2: the production rooting. A concrete executor is - // required — the walk submits through WbDrawDispatcher.SubmitOrderedStream - // and needs a real GPU frame/encoder (RequireWalkSubmission), which no - // test IRetailPViewPassExecutor fake can supply. Whenever a concrete - // executor IS present, the retained render scene must ALSO be - // wired with all three walk registries — a scene product without the - // walk data (or vice versa) is a production miswiring, not a - // legacy/diagnostic shape, so it fails loud rather than silently - // falling back to the retired static routes (plan §FW3 item 6). + // Campaign FW4 production cutover: decide the walk route before any + // old PView construction. A concrete production executor must have all + // walk registries; otherwise the frame is miswired and fails loud. RetailPViewPassExecutor? walkExecutor = passes as RetailPViewPassExecutor; bool walkRegistriesReady = _walkBuildings is not null @@ -252,6 +191,61 @@ public sealed class RetailPViewRenderer && _renderSceneShadow is not null && walkRegistriesReady; + PortalVisibilityFrame pvFrame; + if (walkActive) + { + // Compatibility carrier only. Production visibility is populated + // exclusively by RetailFrameWalk below; this frame supplies the + // clip assembler's outdoor full-screen seed until that remaining + // shell is deleted later in FW4. + pvFrame = _mainPortalFrameScratch; + pvFrame.ResetForBuild(); + if (ctx.RootCell.IsOutdoorNode) + { + pvFrame.OutsideView.SetFullScreen(); + pvFrame.OrderedVisibleCells.Add(ctx.RootCell.CellId); + } + } + else + { + // Standalone/fake executor compatibility. Production never calls + // PortalVisibilityBuilder after the FW4 cutover above. + pvFrame = PortalVisibilityBuilder.Build( + ctx.RootCell, + ctx.ViewerEyePos, + ctx.Cells.Find, + ctx.ViewProjection, + buildingMembership: null, + reuseFrame: _mainPortalFrameScratch); + + if (ctx.RootCell.IsOutdoorNode && ctx.NearbyBuildingCells is not null) + MergeNearbyBuildingFloods(ctx, pvFrame); + if (!ctx.RootCell.IsOutdoorNode + && ctx.NearbyBuildingCells is not null + && pvFrame.OutsideView.Polygons.Count > 0) + BuildInteriorRootLookIns(ctx, pvFrame); + } + + var clipAssembly = passes.AssembleClipFrame( + pvFrame, + _clipAssemblyScratch); + if (!walkActive) + passes.AppendLookInClipFrames(_lookInFrames, clipAssembly); + // 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(); + _drawableCellsScratch.UnionWith(pvFrame.OrderedVisibleCells); + var 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 @@ -382,6 +376,11 @@ public sealed class RetailPViewRenderer viewportHeight); } + // The walk's visited set replaces PortalVisibilityFrame's old + // OrderedVisibleCells side-channel for every production consumer. + _drawableCellsScratch.Clear(); + _drawableCellsScratch.UnionWith(walkDriver.VisitedCells); + // Phase I cathedral instrumentation (synthesis §Phase I.3): the // continuous rooting line SEPARATES the true root flood // (InteriorFloodCells) from the visited union (root + look-ins —