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:
parent
878533597d
commit
4918677b45
8 changed files with 919 additions and 612 deletions
|
|
@ -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(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue