diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 925a50ce..f2f82e2c 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -510,8 +510,14 @@ public sealed class RetailPViewRenderer IRenderFrameEntityPassExecutor? capturedPasses = frameEntityPasses; InteriorEntityPartition.Result? capturedPartition = partition; ViewconeCuller capturedViewcone = viewcone; + Walk.WalkFrameDriver capturedDriver = walkDriver!; _walkPreClearDynamics = () => { + // Retail inserts static-owner emitters (candle flames) + // during their cells' landscape walk turns — submit + // BEFORE the pre-clear drain so they composite against + // still-true landscape depth, never the seals. + SubmitWalkLandscapeStaticParticles(ctx, walkExecutor!, capturedDriver); passes.UseIndoorMembershipOnlyRouting(); DrawLandscapeDynamicsPhase( ctx, @@ -1243,20 +1249,19 @@ public sealed class RetailPViewRenderer /// routes it replaced used to ride, from the driver's own visited /// sets (plan §FW3 "FW3.2b-2 — the production rooting", items 2 and /// 4). - private void DrawWalkDrivenStatics( + /// The landscape-stage static-owner particle submission: the + /// union of every outdoor-static record from a landscape cell the walk + /// visited this frame, plus every shell record from a building the walk + /// visited (plan §FW3 item 4 — retail gates particles per cell turn, + /// ShouldDrawParticles @0x0050FE60, so this is MORE + /// retail-faithful than the old per-slice sphere filter it replaced). + /// Interior roots invoke this at the walk's pre-clear boundary; outdoor + /// roots post-replay. + private void SubmitWalkLandscapeStaticParticles( RetailPViewFrameInput ctx, RetailPViewPassExecutor passes, Walk.WalkFrameDriver driver) { - var (frame, encoder) = passes.RequireWalkSubmission(); - driver.Replay(frame, encoder); - - // Landscape-stage particle owners: the union of every outdoor-static - // record from a landscape cell the walk visited this frame, plus - // every shell record from a building the walk visited (plan §FW3 - // item 4 — retail gates particles per cell turn, ShouldDrawParticles - // @0x0050FE60, so this is MORE retail-faithful than the old per- - // slice sphere filter it replaces). _staticParticleUnionScratch.Clear(); foreach (uint cellId in driver.VisitedLandscapeCellIds) UnionRecordOwners(_walkWorldData!.GetOutdoorStatics(cellId), _staticParticleUnionScratch); @@ -1269,12 +1274,36 @@ public sealed class RetailPViewRenderer new RetailPViewLandscapeStaticParticleContext(_staticParticleUnionScratch)); _staticParticleUnionScratch.Clear(); } + } + + private void DrawWalkDrivenStatics( + RetailPViewFrameInput ctx, + RetailPViewPassExecutor passes, + Walk.WalkFrameDriver driver) + { + var (frame, encoder) = passes.RequireWalkSubmission(); + driver.Replay(frame, encoder); + + // Landscape-stage static-owner particles (candle flames on lamp and + // candle statics): for an OUTDOOR root they submit here, post-replay + // (drained at the frame-end flush over finished depth — the #132 + // placement). For an INTERIOR root they must submit at the walk's + // PRE-CLEAR boundary instead — retail inserts each one during its + // cell's landscape walk turn, before the clear+seals; a post-seal + // drain z-fails them at the aperture (the visual-gate "no candle + // flames looking out" report) — so SubmitWalkLandscapeStaticParticles + // is invoked from _walkPreClearDynamics in that case, and skipped + // here. + if (ctx.RootCell.IsOutdoorNode) + SubmitWalkLandscapeStaticParticles(ctx, passes, driver); // Cell-stage particle owners for the interior root's OWN flood cells // (non-look-in — the walk draws their statics too, via // OnInteriorFloodDrawTurn/EmitCellTurn, so the retired CellStatic // route's cell-particle submission needs the same re-sourcing). - // Look-in cells get their OWN per-cell union in + // These stay POST-replay: interior emitters draw in the final world + // scope where the cell walls already own depth (retail's cell-walk + // insertion). Look-in cells get their OWN per-cell union in // DrawBuildingLookInDynamics so a static owner is never submitted // twice. _cellParticleOwnerScratch.Clear();