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);