From 5f7ccdeab57b4fd22fb637f2570150d500cbfd64 Mon Sep 17 00:00:00 2001 From: Erik Date: Sun, 30 Aug 2026 19:28:57 +0200 Subject: [PATCH] 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 --- .../Rendering/RetailPViewRenderer.cs | 20 +++++++++-- .../Scene/RenderScenePViewFrameProduct.cs | 33 ++++++++++++------- 2 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 6f88c0cf..d47d5778 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -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 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); } } diff --git a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs b/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs index 7bd78e5d..22a64027 100644 --- a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs +++ b/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs @@ -1141,6 +1141,10 @@ internal sealed class RenderScenePViewFrameBuilder private readonly Dictionary _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);