fix(render): FW4 slice 3 - the outside-stage predicate reads the walk flood

The cathedral falls persisted through the slice-2 seal fix; the probe
already held the tell: the same two emitter entities (f4243/f4285) draw
phase=pre on good frames and phase=post on bad ones. They are
interior-parented, so DynamicDrawsInOutsideStage takes the indoor arm,
whose first gate is flood membership - and that still read the OLD
apparatus flood, which drops cells at the #456 seam band. On those
frames the falls fell to the post-clear last pass and splatted across
the cleared depth (terrain and water alike).

Both predicate call sites (the frame product's BuildOutsideDynamicRoutes
via BuildAndBorrow's input, and the renderer's own outside-stage
classification) now receive the walk's VisitedCells on walk frames -
root flood plus look-in floods, all of whose objects retail draws inside
LScape::draw, pre-clear. drawableCells keeps its other roles. Also adds
the probe-gated [walk-dyn] per-entity classification dump (parent cell,
outside/cone verdicts) so a surviving repro pins the failing gate
directly.

Hermetic 6,762/0, Walk lane 213/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 19:28:57 +02:00
parent c40aecfc8c
commit 5f7ccdeab5
2 changed files with 40 additions and 13 deletions

View file

@ -465,6 +465,18 @@ public sealed class RetailPViewRenderer
_candidateObserver?.BeginPViewFrame();
try
{
// FW4 slice 3: the outside-stage predicate's flood-membership set
// is THE WALK'S visited cells on walk frames (root flood +
// look-in floods — retail draws all of their objects inside
// LScape::draw, pre-clear). The old apparatus's drawableCells
// misses cells at the #456 seam band, dropping interior-parented
// outdoor emitters (the cathedral falls weenies) to the
// post-clear last pass, where the cleared depth lets them splat
// across terrain and water. drawableCells keeps its other roles
// (prepare filter, frame result) unchanged.
HashSet<uint> outsideStageFlood =
walkActive ? walkDriver!.VisitedCells : drawableCells;
if (_sceneFrameProduct is not null)
{
frameView = _sceneFrameProduct.BuildAndBorrow(
@ -472,7 +484,7 @@ public sealed class RetailPViewRenderer
clipAssembly,
viewcone,
_lookInFrames,
drawableCells,
outsideStageFlood,
ctx.Cells,
ctx.AnimatedEntityIds,
ctx.RootCell.IsOutdoorNode);
@ -544,7 +556,11 @@ public sealed class RetailPViewRenderer
foreach (var e in partition.Dynamics)
{
EntitySphere(e, out var c, out float r);
if (DynamicDrawsInOutsideStage(e.ParentCellId, c, r, drawableCells, ctx.Cells))
// FW4 slice 3: same walk-flood membership set as the
// frame product's BuildOutsideDynamicRoutes — the two
// predicate call sites must agree or routed and drawn
// stages diverge.
if (DynamicDrawsInOutsideStage(e.ParentCellId, c, r, outsideStageFlood, ctx.Cells))
_outsideStageDynamics.Add(e);
}
}

View file

@ -1141,6 +1141,10 @@ internal sealed class RenderScenePViewFrameBuilder
private readonly Dictionary<RenderProjectionId, int>
_dynamicPositions = [];
private int _dynamicCount;
// ACDREAM_PROBE_WALK_ROOT companion (throwaway): rate limiter for the
// [walk-dyn] outside-stage classification dump.
private uint _probeDynFrameCounter;
private int _dirtyCount;
private RenderSceneGeneration _indexGeneration;
private ulong _indexRevision;
@ -1320,6 +1324,9 @@ internal sealed class RenderScenePViewFrameBuilder
return;
int sliceCount = input.ClipAssembly.OutsideViewSlices.Length;
bool probeDynamics =
AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled
&& ++_probeDynFrameCounter % 120 == 0;
for (int sliceIndex = 0; sliceIndex < sliceCount; sliceIndex++)
{
int count = 0;
@ -1328,19 +1335,23 @@ internal sealed class RenderScenePViewFrameBuilder
{
RenderProjectionRecord record = _dynamics[i];
Sphere(in record, out Vector3 center, out float radius);
if (!RetailPViewRenderer.DynamicDrawsInOutsideStage(
ParentCell(in record),
center,
radius,
input.DrawableCells,
input.Cells)
|| !input.Viewcone.SphereVisibleInOutsideSlice(
sliceIndex,
in center,
radius))
bool outside = RetailPViewRenderer.DynamicDrawsInOutsideStage(
ParentCell(in record),
center,
radius,
input.DrawableCells,
input.Cells);
bool cone = input.Viewcone.SphereVisibleInOutsideSlice(
sliceIndex, in center, radius);
if (probeDynamics)
{
continue;
Console.WriteLine(
$"[walk-dyn] slice={sliceIndex} id={record.Source.LocalEntityId:x} "
+ $"parent={ParentCell(in record)?.ToString("x8") ?? "null"} "
+ $"r={radius:F1} outside={(outside ? 1 : 0)} cone={(cone ? 1 : 0)}");
}
if (!outside || !cone)
continue;
_survivors[count++] = record;
writer.AddDynamic(in record);