feat(render) Campaign FW3.2b-2: THE STATIC CUTOVER - the walk drives production statics

The retail frame walk now drives every production static draw. In
RetailPViewRenderer.DrawInside, when the concrete executor + the
packed product + the FW3.1 walk registries are all wired (all
production compositions - anything less throws):

- A pre-walk events-only collection pass (the shadow sink generalized
  to WalkVisitedSetCollector) gathers the frame's visited cells,
  buildings, and landscape-cell turns; the visited cells union into
  prepareCells so EnvCellRenderer prepares every shell the driver
  draws.
- DrawWalkDrivenStatics runs the WalkFrameDriver over the production
  world data (WalkProductionWorldData over RenderSceneQuery + the
  building registry): sky, terrain slices, outdoor statics at their
  landscape-cell turns, buildings (alpha barrier -> punch/look-in
  passes -> shell) in retail order, interior clear+seals as leaf
  closures (the old tail block's drain reasoning moves with them),
  flood cells shell-then-contents. Landscape/cell-stage particle
  owners re-source from the walk's visited sets - retail gates
  particles per cell turn (ShouldDrawParticles @0x0050FE60), which
  this is; the old sphere filter was the approximation.
- DrawLandscapeDynamicsPhase + DrawBuildingLookInDynamics carry the
  dynamics-only remainder (LookInObject now dynamic-classified,
  late outside-dynamics + weather, particle unions); DrawDynamicsLast
  and the outdoor flush are unchanged.
- The product builder stops emitting LandscapeOutdoorStatic /
  LandscapeBuildingShell / CellStatic (methods deleted, dead index
  tracking removed); LookInObject loads cells with
  includeStatics: false.

The old static path survives ONLY behind !walkActive for the
standalone/diagnostic executor-fake path that keeps 15 retail-ordering
regression tests exercising the barrier/punch/seal machinery; no
production composition can reach it. Its deletion is FW4 scope (the
plan's "deleting the patch apparatus") - recorded in the plan.

Transitional risks recorded in code/report: the two-pass walk cost
(FW3.4 measures), the interior slice-count reconciliation between the
old clip assembly and the walk's own exit-view survival, and the
outdoor merged-flood punch coverage now riding the walk's own
building-BSP punches (retail-faithful per FW1; the owner visual gate
verifies).

Suites (lead-verified): full Release build 0 warnings; hermetic
6,750/0 (baseline minus the three deleted route tests); Walk lane
201/1; InstalledDat Walk conformance 40/1 untouched. The two
IL-branch tests the implementation round reported failing pass in
every lead run - the recurring parallel-load flake pair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 15:43:22 +02:00
parent 878533597d
commit 4918677b45
8 changed files with 919 additions and 612 deletions

View file

@ -122,6 +122,11 @@ internal sealed class RenderScenePViewFrameProductController :
{
private readonly RenderSceneShadowRuntime _shadow;
private readonly CurrentRenderSceneOracle? _current;
/// <summary>Campaign FW3.2b-2: the frame's retained-scene read view for
/// the walk's world-data provider (the same query
/// <see cref="BuildAndBorrow"/> reads its routes from).</summary>
internal RenderSceneQuery SceneQuery => _shadow.Query;
private readonly WbDrawDispatcher? _dispatcher;
private readonly RenderScenePViewFrameBuilder _builder = new();
private readonly RenderFrameExchange _exchange = new();
@ -1123,7 +1128,6 @@ internal sealed class RenderScenePViewFrameBuilder
private const byte EnvCellProjectionDomain = 2;
private readonly HashSet<RenderProjectionId> _projectionIds = [];
private RenderProjectionRecord[] _outdoor = [];
private RenderProjectionRecord[] _dynamics = [];
private RenderProjectionRecord[] _cell = [];
private RenderProjectionRecord[] _dirty = [];
@ -1134,14 +1138,9 @@ internal sealed class RenderScenePViewFrameBuilder
// marks every non-player part): an object whose cell drew with a look-in
// must not enter the final dynamics route again.
private readonly HashSet<uint> _lookInCellScratch = new();
private RenderProjectionRecord[] _cellRoute = [];
private readonly Dictionary<RenderProjectionId, int>
_outdoorPositions = [];
private readonly Dictionary<RenderProjectionId, int>
_dynamicPositions = [];
private int _outdoorCount;
private int _dynamicCount;
private int _cellRouteCount;
private int _dirtyCount;
private RenderSceneGeneration _indexGeneration;
private ulong _indexRevision;
@ -1164,7 +1163,13 @@ internal sealed class RenderScenePViewFrameBuilder
_projectionIds.Clear();
LoadSceneIndices(input.Scene);
BuildOutdoorRoutes(writer, in input);
// Campaign FW3.2b-2: LandscapeOutdoorStatic, LandscapeBuildingShell,
// and CellStatic no longer emit here — WalkFrameDriver draws every
// outdoor static, building shell, and cell static (including
// look-in cell statics) directly through OrderedDrawStream (plan
// §FW3 "FW3.2b-2 — the production rooting", item 3). LookInObject
// keeps emitting, but BuildLookInRoutes below is now filtered to
// DYNAMIC candidates only — the walk owns that route's statics.
int lookInRouteIndex = 0;
for (int frameIndex = 0;
frameIndex < input.LookInFrames.Count;
@ -1175,13 +1180,8 @@ internal sealed class RenderScenePViewFrameBuilder
in input,
frameIndex,
ref lookInRouteIndex);
BuildLookInBuildingShellRoutes(
writer,
in input,
frameIndex);
}
BuildOutsideDynamicRoutes(writer, in input);
BuildCellStaticRoute(writer, in input);
BuildDynamicLastRoute(writer, in input);
writer.Publish();
AcknowledgeCachedDirtyRecords();
@ -1200,18 +1200,6 @@ internal sealed class RenderScenePViewFrameBuilder
if (scene.Generation != _indexGeneration
|| revision != _indexRevision)
{
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
_outdoorCount = scene.CopyIndexTo(
RenderSceneIndex.OutdoorStatic,
_outdoor);
_outdoorCount = CompactAndSort(
_outdoor,
_outdoorCount);
BuildPositionIndex(
_outdoor,
_outdoorCount,
_outdoorPositions);
EnsureCapacity(ref _dynamics, counts.Dynamic);
_dynamicCount = scene.CopyIndexTo(
RenderSceneIndex.Dynamic,
@ -1235,12 +1223,6 @@ internal sealed class RenderScenePViewFrameBuilder
for (int index = 0; index < _dirtyCount; index++)
{
RenderProjectionRecord record = _dirty[index];
if (_outdoorPositions.TryGetValue(
record.Id,
out int outdoorPosition))
{
_outdoor[outdoorPosition] = record;
}
if (_dynamicPositions.TryGetValue(
record.Id,
out int dynamicPosition))
@ -1255,16 +1237,6 @@ internal sealed class RenderScenePViewFrameBuilder
for (int index = 0; index < _dirtyCount; index++)
{
RenderProjectionId id = _dirty[index].Id;
if (_outdoorPositions.TryGetValue(
id,
out int outdoorPosition))
{
_outdoor[outdoorPosition] =
_outdoor[outdoorPosition] with
{
DirtyMask = RenderDirtyMask.None,
};
}
if (_dynamicPositions.TryGetValue(
id,
out int dynamicPosition))
@ -1289,103 +1261,13 @@ internal sealed class RenderScenePViewFrameBuilder
positions.Add(records[index].Id, index);
}
private void BuildOutdoorRoutes(
RenderFrameWriter writer,
in RenderScenePViewBuildInput input)
{
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
{
int count = 0;
EnsureCapacity(ref _survivors, _outdoorCount);
for (int i = 0; i < _outdoorCount; i++)
{
RenderProjectionRecord record = _outdoor[i];
if (record.EntityPayload.IsBuildingShell
&& RetailPViewRenderer.FindLookInFrameIndex(
record.Source.BuildingShellAnchorCellId,
input.LookInFrames,
input.Cells) >= 0)
{
continue;
}
Sphere(in record, out Vector3 center, out float radius);
if (!input.Viewcone.SphereVisibleInOutsideSlice(
sliceIndex,
in center,
radius))
{
continue;
}
_survivors[count++] = record;
writer.AddOutdoor(in record);
AddProjection(
writer,
in record,
input.AnimatedEntityIds);
}
writer.AddRouteRange(
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
sliceIndex,
0,
_survivors.AsSpan(0, count));
}
}
private void BuildLookInBuildingShellRoutes(
RenderFrameWriter writer,
in RenderScenePViewBuildInput input,
int frameIndex)
{
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
{
int count = 0;
EnsureCapacity(ref _survivors, _outdoorCount);
for (int i = 0; i < _outdoorCount; i++)
{
RenderProjectionRecord record = _outdoor[i];
if (!record.EntityPayload.IsBuildingShell
|| RetailPViewRenderer.FindLookInFrameIndex(
record.Source.BuildingShellAnchorCellId,
input.LookInFrames,
input.Cells) != frameIndex)
{
continue;
}
Sphere(in record, out Vector3 center, out float radius);
if (!input.Viewcone.SphereVisibleInOutsideSlice(
sliceIndex,
in center,
radius))
{
continue;
}
_survivors[count++] = record;
writer.AddOutdoor(in record);
AddProjection(
writer,
in record,
input.AnimatedEntityIds);
}
int routeIndex =
RetailPViewRenderer.LookInBuildingShellRouteIndex(
frameIndex,
sliceCount,
sliceIndex);
writer.AddRouteRange(
RenderFrameCandidateRoute.LandscapeBuildingShell,
routeIndex,
0,
_survivors.AsSpan(0, count));
}
}
/// <summary>Campaign FW3.2b-2: <c>LookInObject</c> now carries DYNAMIC
/// candidates only — the walk draws every look-in cell's STATIC content
/// directly (<c>WalkFrameDriver</c>'s <c>LookInStatic</c> turn, via
/// <c>WalkProductionWorldData.GetCellStatics</c>), so loading this
/// route's cell contents with <c>includeStatics: false</c> is what keeps
/// the two draws from doubling a look-in room's furniture (plan §FW3
/// item 3).</summary>
private void BuildLookInRoutes(
RenderFrameWriter writer,
in RenderScenePViewBuildInput input,
@ -1408,6 +1290,7 @@ internal sealed class RenderScenePViewFrameBuilder
int count = LoadCell(
input.Scene,
cellId,
includeStatics: false,
includeDynamics: true);
for (int sliceIndex = 0; sliceIndex < slices.Length; sliceIndex++)
{
@ -1475,61 +1358,6 @@ internal sealed class RenderScenePViewFrameBuilder
}
}
private void BuildCellStaticRoute(
RenderFrameWriter writer,
in RenderScenePViewBuildInput input)
{
_cellRouteCount = 0;
IReadOnlyList<uint> ordered = input.PortalFrame.OrderedVisibleCells;
for (int i = ordered.Count - 1; i >= 0; i--)
{
uint cellId = ordered[i];
if (!input.DrawableCells.Contains(cellId))
continue;
int loaded = LoadCell(
input.Scene,
cellId,
includeDynamics: false);
int count = 0;
for (int index = 0; index < loaded; index++)
{
RenderProjectionRecord record = _cell[index];
Sphere(in record, out Vector3 center, out float radius);
if (!input.Viewcone.SphereVisibleInCell(
cellId,
in center,
radius))
{
continue;
}
_cell[count++] = record;
AddProjection(
writer,
in record,
input.AnimatedEntityIds);
}
writer.AddCellRange(
cellId,
i,
_cell.AsSpan(0, count));
EnsureCapacity(
ref _cellRoute,
checked(_cellRouteCount + count));
_cell.AsSpan(0, count).CopyTo(
_cellRoute.AsSpan(_cellRouteCount));
_cellRouteCount += count;
}
writer.AddRouteRange(
RenderFrameCandidateRoute.CellStatic,
0,
0,
_cellRoute.AsSpan(0, _cellRouteCount));
}
private void BuildDynamicLastRoute(
RenderFrameWriter writer,
in RenderScenePViewBuildInput input)
@ -1593,18 +1421,25 @@ internal sealed class RenderScenePViewFrameBuilder
private int LoadCell(
RenderSceneQuery scene,
uint cellId,
bool includeDynamics)
bool includeDynamics,
bool includeStatics = true)
{
int staticCount = scene.GetCellStaticCount(cellId);
int staticCount = includeStatics
? scene.GetCellStaticCount(cellId)
: 0;
int dynamicCount = includeDynamics
? scene.GetCellDynamicCount(cellId)
: 0;
EnsureCapacity(
ref _cell,
checked(staticCount + dynamicCount));
int count = scene.CopyCellStaticsTo(
cellId,
_cell.AsSpan(0, staticCount));
int count = 0;
if (staticCount > 0)
{
count = scene.CopyCellStaticsTo(
cellId,
_cell.AsSpan(0, staticCount));
}
if (dynamicCount > 0)
{
count += scene.CopyCellDynamicsTo(