refactor(render): detach packed pview product from production

This commit is contained in:
Erik 2026-08-31 04:10:10 +02:00
parent ef12170a3c
commit c9fb7e7d4c
4 changed files with 54 additions and 180 deletions

View file

@ -347,20 +347,13 @@ internal sealed class FrameRootCompositionPhase
live.EntityEffects, live.EntityEffects,
d.Log); d.Log);
IWorldSceneFramePhase? worldSceneRenderer = null; IWorldSceneFramePhase? worldSceneRenderer = null;
CurrentRenderSceneOracle? currentRenderSceneOracle = // Campaign FW4: the retail walk reads RenderSceneShadowRuntime
interaction.RetainedUi?.Screenshots is not null // directly. The old current-partition oracle, shadow comparison, and
&& d.Options.AutomationArtifactDirectory is not null // packed PView frame product no longer participate in production or
? new CurrentRenderSceneOracle() // automation; their null snapshot slots remain until the diagnostic
: null; // contract cleanup later in this campaign.
RenderSceneShadowComparisonController? renderSceneShadowComparison = CurrentRenderSceneOracle? currentRenderSceneOracle = null;
currentRenderSceneOracle is not null RenderSceneShadowComparisonController? renderSceneShadowComparison = null;
&& live.RenderSceneShadow is not null
? new RenderSceneShadowComparisonController(
live.RenderSceneShadow,
currentRenderSceneOracle,
message => d.Log("[UI-PROBE] " + message),
acknowledgeDirty: false)
: null;
RenderScenePViewFrameProductController? renderFrameProduct = null; RenderScenePViewFrameProductController? renderFrameProduct = null;
{ {
// Campaign V slice V6j: the world scene is composed on BOTH arms. // Campaign V slice V6j: the world scene is composed on BOTH arms.
@ -487,17 +480,8 @@ internal sealed class FrameRootCompositionPhase
live.SkyRenderer, live.SkyRenderer,
content.ParticleSystem, content.ParticleSystem,
live.ParticleRenderer); live.ParticleRenderer);
renderFrameProduct = // The retained scene is the walk's production object source. The
live.RenderSceneShadow is not null // old dispatcher/selection observer remains detached.
? new RenderScenePViewFrameProductController(
live.RenderSceneShadow,
currentRenderSceneOracle,
message => d.Log("[UI-PROBE] " + message),
live.DrawDispatcher!)
: null;
// After G4 the retained product is the production object source.
// The old dispatcher/selection observer is intentionally detached;
// automation still compares the independently built PView route list.
live.DrawDispatcher!.SetCurrentRenderSceneObserver(null); live.DrawDispatcher!.SetCurrentRenderSceneObserver(null);
live.SelectionScene.SetCurrentRenderSceneObserver(null); live.SelectionScene.SetCurrentRenderSceneObserver(null);
worldSceneRenderer = new WorldSceneRenderer( worldSceneRenderer = new WorldSceneRenderer(
@ -512,7 +496,7 @@ internal sealed class FrameRootCompositionPhase
new WorldScenePViewRenderer( new WorldScenePViewRenderer(
new RetailPViewRenderer( new RetailPViewRenderer(
currentRenderSceneOracle, currentRenderSceneOracle,
renderFrameProduct, live.RenderSceneShadow,
// Campaign FW3.2b-2: the walk's production world // Campaign FW3.2b-2: the walk's production world
// data, published/retired with each landblock by the // data, published/retired with each landblock by the
// render publisher (FW3.1). // render publisher (FW3.1).

View file

@ -15,8 +15,7 @@ public sealed class RetailPViewRenderer
{ {
private readonly InteriorEntityPartition.IObserver? _partitionObserver; private readonly InteriorEntityPartition.IObserver? _partitionObserver;
private readonly ICurrentRenderPViewObserver? _candidateObserver; private readonly ICurrentRenderPViewObserver? _candidateObserver;
private readonly RenderScenePViewFrameProductController? private readonly RenderSceneShadowRuntime? _renderSceneShadow;
_sceneFrameProduct;
private readonly PortalVisibilityFrame _mainPortalFrameScratch = new(); private readonly PortalVisibilityFrame _mainPortalFrameScratch = new();
private readonly ClipFrameAssembly _clipAssemblyScratch = new(); private readonly ClipFrameAssembly _clipAssemblyScratch = new();
private readonly ViewconeCuller _viewconeScratch = new(); private readonly ViewconeCuller _viewconeScratch = new();
@ -133,7 +132,7 @@ public sealed class RetailPViewRenderer
internal RetailPViewRenderer( internal RetailPViewRenderer(
InteriorEntityPartition.IObserver? partitionObserver, InteriorEntityPartition.IObserver? partitionObserver,
RenderScenePViewFrameProductController? sceneFrameProduct = null, RenderSceneShadowRuntime? renderSceneShadow = null,
Walk.WalkBuildingRegistry? walkBuildings = null, Walk.WalkBuildingRegistry? walkBuildings = null,
Walk.WalkLandscapeAssembler? walkLandscape = null, Walk.WalkLandscapeAssembler? walkLandscape = null,
CellVisibility? walkCellRegistry = null) CellVisibility? walkCellRegistry = null)
@ -146,7 +145,7 @@ public sealed class RetailPViewRenderer
: null; : null;
_partitionObserver = partitionObserver; _partitionObserver = partitionObserver;
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver; _candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
_sceneFrameProduct = sceneFrameProduct; _renderSceneShadow = renderSceneShadow;
} }
// T2 (BR-4): retail has NO distance constant on the flood-admission chain // T2 (BR-4): retail has NO distance constant on the flood-admission chain
@ -225,7 +224,7 @@ public sealed class RetailPViewRenderer
// required — the walk submits through WbDrawDispatcher.SubmitOrderedStream // required — the walk submits through WbDrawDispatcher.SubmitOrderedStream
// and needs a real GPU frame/encoder (RequireWalkSubmission), which no // and needs a real GPU frame/encoder (RequireWalkSubmission), which no
// test IRetailPViewPassExecutor fake can supply. Whenever a concrete // test IRetailPViewPassExecutor fake can supply. Whenever a concrete
// executor IS present, the packed entity-route product must ALSO be // executor IS present, the retained render scene must ALSO be
// wired with all three walk registries — a scene product without the // wired with all three walk registries — a scene product without the
// walk data (or vice versa) is a production miswiring, not a // walk data (or vice versa) is a production miswiring, not a
// legacy/diagnostic shape, so it fails loud rather than silently // legacy/diagnostic shape, so it fails loud rather than silently
@ -237,12 +236,12 @@ public sealed class RetailPViewRenderer
&& _walkCellRegistry is not null && _walkCellRegistry is not null
&& _walkWorldData is not null; && _walkWorldData is not null;
if (walkExecutor is not null if (walkExecutor is not null
&& _sceneFrameProduct is not null && _renderSceneShadow is not null
&& !walkRegistriesReady) && !walkRegistriesReady)
{ {
throw new InvalidOperationException( throw new InvalidOperationException(
"RetailPViewRenderer has a concrete pass executor and a " "RetailPViewRenderer has a concrete pass executor and a "
+ "RenderScenePViewFrameProductController but the walk registries " + "RenderSceneShadowRuntime but the walk registries "
+ "(WalkBuildingRegistry/WalkLandscapeAssembler/CellVisibility) are " + "(WalkBuildingRegistry/WalkLandscapeAssembler/CellVisibility) are "
+ "not all wired — the Campaign FW3.2b-2 static cutover requires " + "not all wired — the Campaign FW3.2b-2 static cutover requires "
+ "every piece together; see FrameRootComposition's " + "every piece together; see FrameRootComposition's "
@ -250,7 +249,7 @@ public sealed class RetailPViewRenderer
} }
bool walkActive = bool walkActive =
walkExecutor is not null walkExecutor is not null
&& _sceneFrameProduct is not null && _renderSceneShadow is not null
&& walkRegistriesReady; && walkRegistriesReady;
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/ // Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
@ -320,7 +319,7 @@ public sealed class RetailPViewRenderer
} }
_walkWorldData!.BeginFrame( _walkWorldData!.BeginFrame(
_sceneFrameProduct!.SceneQuery, _renderSceneShadow!.Query,
ctx.PlayerLandblockId ?? 0u, ctx.PlayerLandblockId ?? 0u,
ctx.RenderCenterLbX, ctx.RenderCenterLbX,
ctx.RenderCenterLbY); ctx.RenderCenterLbY);
@ -496,16 +495,11 @@ public sealed class RetailPViewRenderer
ctx.ViewProjection, ctx.ViewProjection,
_viewconeScratch); _viewconeScratch);
// Packed frame routes no longer participate in production. These
// placeholders keep the standalone legacy/fake path self-contained
// until FW4 removes that shell completely.
IRenderFrameEntityPassExecutor? frameEntityPasses = null; IRenderFrameEntityPassExecutor? frameEntityPasses = null;
if (_sceneFrameProduct is not null)
{
frameEntityPasses = passes as IRenderFrameEntityPassExecutor
?? throw new InvalidOperationException(
"The production frame product requires a packed entity-pass executor.");
}
RenderFrameView frameView = default; RenderFrameView frameView = default;
bool frameViewBorrowed = false;
bool entityFrameOpen = false;
_candidateObserver?.BeginPViewFrame(); _candidateObserver?.BeginPViewFrame();
try try
{ {
@ -531,32 +525,11 @@ public sealed class RetailPViewRenderer
_rootFloodSetScratch.Add(cellId); _rootFloodSetScratch.Add(cellId);
} }
if (_sceneFrameProduct is not null) // The walk reads the retained scene directly. Rebuild the former
{ // WorldEntity partition only for the standalone fallback and
frameView = _sceneFrameProduct.BuildAndBorrow( // explicitly enabled comparison/probe paths.
pvFrame,
clipAssembly,
viewcone,
walkActive ? walkDriver!.LookInCellTurns : [],
walkActive ? walkDriver : null,
outsideStageFlood,
ctx.Cells,
ctx.AnimatedEntityIds,
ctx.RootCell.IsOutdoorNode,
// Non-walk frames (diagnostic fakes / legacy fallback)
// keep the pre-split admission: drawableCells WAS the
// flood in the legacy meaning.
walkActive ? _rootFloodSetScratch : drawableCells);
frameViewBorrowed = true;
frameEntityPasses!.BeginEntityFrame(in frameView);
entityFrameOpen = true;
}
// The retained scene product is the production object source.
// Rebuild the former WorldEntity partition only for the standalone
// fallback and explicitly enabled comparison/probe paths.
InteriorEntityPartition.Result? partition = null; InteriorEntityPartition.Result? partition = null;
if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled) if (!walkActive || LegacyPartitionDiagnosticsEnabled)
{ {
InteriorEntityPartition.Partition( InteriorEntityPartition.Partition(
_partitionResult, _partitionResult,
@ -568,11 +541,14 @@ public sealed class RetailPViewRenderer
partition = _partitionResult; partition = _partitionResult;
} }
RenderFrameDiagnosticCounts counts = frameViewBorrowed RenderProjectionCounts retainedCounts = walkActive
? frameView.DiagnosticCounts ? _renderSceneShadow!.Counts
: default;
RenderFrameDiagnosticCounts counts = walkActive
? WalkDiagnosticCounts(retainedCounts)
: LegacyDiagnosticCounts(partition!); : LegacyDiagnosticCounts(partition!);
RenderProjectionCounts sourceCounts = frameViewBorrowed RenderProjectionCounts sourceCounts = walkActive
? frameView.SourceDigest.Counts ? retainedCounts
: LegacySourceCounts(partition!); : LegacySourceCounts(partition!);
// prepareCells is exactly "main flood look-in cells" — the cells // prepareCells is exactly "main flood look-in cells" — the cells
// this traversal actually reached, i.e. retail's in-view set. // this traversal actually reached, i.e. retail's in-view set.
@ -761,27 +737,14 @@ public sealed class RetailPViewRenderer
// Outdoor-cell unattached emitters drew in the landscape stage. // Outdoor-cell unattached emitters drew in the landscape stage.
passes.DrawUnattachedSceneParticles(ctx, outdoorCells: false); passes.DrawUnattachedSceneParticles(ctx, outdoorCells: false);
if (entityFrameOpen)
{
frameEntityPasses!.CompleteEntityFrame(in frameView);
entityFrameOpen = false;
}
_candidateObserver?.CompletePViewFrame(); _candidateObserver?.CompletePViewFrame();
_sceneFrameProduct?.CompleteProduction(in frameView);
return result; return result;
} }
catch catch
{ {
if (entityFrameOpen)
frameEntityPasses!.AbortEntityFrame();
_candidateObserver?.AbortPViewFrame(); _candidateObserver?.AbortPViewFrame();
throw; throw;
} }
finally
{
if (frameViewBorrowed)
_sceneFrameProduct!.Release(in frameView);
}
} }
// R-A2: group the nearby building cells by BuildingId and run one per-building flood per group // R-A2: group the nearby building cells by BuildingId and run one per-building flood per group
@ -2408,6 +2371,27 @@ public sealed class RetailPViewRenderer
|| AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled || AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled
|| AcDream.App.Streaming.EntityVanishProbe.Enabled; || AcDream.App.Streaming.EntityVanishProbe.Enabled;
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);
}
internal static RenderFrameDiagnosticCounts LegacyDiagnosticCounts( internal static RenderFrameDiagnosticCounts LegacyDiagnosticCounts(
InteriorEntityPartition.Result partition) InteriorEntityPartition.Result partition)
{ {

View file

@ -43,12 +43,7 @@ public sealed class FrameRootCompositionTests
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose); IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
int resources = CallIndex(calls, typeof(RenderFrameResourceController), ".ctor"); int resources = CallIndex(calls, typeof(RenderFrameResourceController), ".ctor");
int comparison = CallIndex( int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", resources + 1);
calls,
typeof(RenderSceneShadowComparisonController),
".ctor",
resources + 1);
int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", comparison + 1);
int automation = CallIndex( int automation = CallIndex(
calls, calls,
typeof(WorldLifecycleAutomationController), typeof(WorldLifecycleAutomationController),

View file

@ -197,95 +197,6 @@ public sealed class RetailPViewPassExecutorTests
}); });
} }
[Fact]
public void DrawInside_production_product_supplies_the_ordered_entity_routes()
{
// Campaign FW3.2b-2: no cell-static entity here (adjudicated
// 2026-08-30) — CellStatic no longer flows through the packed
// product at all (WalkFrameDriver owns every cell static now, via
// OrderedDrawStream, not this route); a static entity registered on
// the LEGACY side with nothing on the packed side would report a
// permanent MismatchCount, which is not what this test is proving.
// The route-consumption-order assertion below still exercises the
// one route that DID survive the cutover (DynamicLast).
LoadedCell interior = InteriorWithExit(0xA9B40100u);
WorldEntity dynamic = Entity(
31u,
serverGuid: 0x80000031u,
parentCellId: interior.CellId);
uint landblockId = interior.CellId & 0xFFFF0000u;
var oracle = new CurrentRenderSceneOracle();
using var shadow = new RenderSceneShadowRuntime(
RenderSceneGeneration.FromRaw(1));
RenderProjectionRecord dynamicRecord =
RenderProjectionRecordFactory.ProjectEntity(
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
RenderProjectionClass.LiveDynamicRoot,
RenderOwnerIncarnation.FromRaw(dynamic.Id),
landblockId,
interior.CellId,
dynamic,
spatiallyVisible: true);
shadow.Journal.Register(in dynamicRecord);
shadow.DrainUpdateBoundary();
var product = new RenderScenePViewFrameProductController(
shadow,
oracle);
var renderer = new RetailPViewRenderer(oracle, product);
using var executor = new RecordingExecutor();
renderer.DrawInside(
Frame(interior, [dynamic]),
executor);
AssertAppearsInOrder(
string.Join('|', executor.Operations),
"entity-frame-begin",
"entity-route:DynamicLast:0:00000000",
"entity-frame-complete");
Assert.Equal(0, product.Snapshot.MismatchCount);
Assert.Equal(
product.Snapshot.ExpectedDigest,
product.Snapshot.ActualDigest);
}
[Fact]
public void DrawInside_production_product_uses_retained_routes_without_legacy_partition_when_diagnostics_are_disabled()
{
LoadedCell interior = InteriorWithExit(0xA9B40100u);
WorldEntity dynamic = Entity(
32u,
serverGuid: 0x80000032u,
parentCellId: interior.CellId);
uint landblockId = interior.CellId & 0xFFFF0000u;
using var shadow = new RenderSceneShadowRuntime(
RenderSceneGeneration.FromRaw(1));
RenderProjectionRecord dynamicRecord =
RenderProjectionRecordFactory.ProjectEntity(
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
RenderProjectionClass.LiveDynamicRoot,
RenderOwnerIncarnation.FromRaw(dynamic.Id),
landblockId,
interior.CellId,
dynamic,
spatiallyVisible: true);
shadow.Journal.Register(in dynamicRecord);
shadow.DrainUpdateBoundary();
var product = new RenderScenePViewFrameProductController(shadow);
var renderer = new RetailPViewRenderer(null, product);
using var executor = new RecordingExecutor();
renderer.DrawInside(Frame(interior, [dynamic]), executor);
AssertAppearsInOrder(
string.Join('|', executor.Operations),
"entity-frame-begin",
"entity-route:DynamicLast:0:00000000",
"entity-frame-complete");
Assert.Equal(0uL, product.Snapshot.ComparisonCount);
Assert.Equal(1, product.Snapshot.ActualCandidateCount);
}
[Fact] [Fact]
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch() public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
{ {