refactor(render): make the walk sole production visibility owner

This commit is contained in:
Erik 2026-08-31 04:50:08 +02:00
parent 2a3ec09a98
commit 98d2d43fdc

View file

@ -165,70 +165,9 @@ public sealed class RetailPViewRenderer
RecycleLookInFrames(); RecycleLookInFrames();
ResetBuildingGroups(); ResetBuildingGroups();
var pvFrame = PortalVisibilityBuilder.Build( // Campaign FW4 production cutover: decide the walk route before any
ctx.RootCell, // old PView construction. A concrete production executor must have all
ctx.ViewerEyePos, // walk registries; otherwise the frame is miswired and fails loud.
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).
RetailPViewPassExecutor? walkExecutor = passes as RetailPViewPassExecutor; RetailPViewPassExecutor? walkExecutor = passes as RetailPViewPassExecutor;
bool walkRegistriesReady = bool walkRegistriesReady =
_walkBuildings is not null _walkBuildings is not null
@ -252,6 +191,61 @@ public sealed class RetailPViewRenderer
&& _renderSceneShadow is not null && _renderSceneShadow is not null
&& walkRegistriesReady; && 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/ // Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
// walkCameraCell exactly as the pre-FW3.4a pre-walk collection pass // walkCameraCell exactly as the pre-FW3.4a pre-walk collection pass
// did, then drives WalkFrameDriver.Collect — a SINGLE RetailFrameWalk // did, then drives WalkFrameDriver.Collect — a SINGLE RetailFrameWalk
@ -382,6 +376,11 @@ public sealed class RetailPViewRenderer
viewportHeight); 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 // Phase I cathedral instrumentation (synthesis §Phase I.3): the
// continuous rooting line SEPARATES the true root flood // continuous rooting line SEPARATES the true root flood
// (InteriorFloodCells) from the visited union (root + look-ins — // (InteriorFloodCells) from the visited union (root + look-ins —