diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs
index 33b01447..d17bddbf 100644
--- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs
+++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs
@@ -644,10 +644,6 @@ public sealed class RetailPViewRenderer
// closure invokes it via _walkPreClearDynamics. For an
// OUTDOOR root there is no clear (retail has none) and the
// phase runs after the driver, matching the old order.
- RenderFrameView capturedView = frameView;
- IRenderFrameEntityPassExecutor? capturedPasses = frameEntityPasses;
- InteriorEntityPartition.Result? capturedPartition = partition;
- ViewconeCuller capturedViewcone = viewcone;
Walk.WalkFrameDriver capturedDriver = walkDriver!;
_walkPreClearDynamics = () =>
{
@@ -660,11 +656,7 @@ public sealed class RetailPViewRenderer
ctx,
passes,
clipAssembly,
- capturedPartition,
- capturedViewcone,
- capturedDriver,
- capturedPasses,
- in capturedView);
+ capturedDriver);
};
try
{
@@ -676,11 +668,7 @@ public sealed class RetailPViewRenderer
ctx,
passes,
clipAssembly,
- partition,
- viewcone,
- walkDriver!,
- frameEntityPasses,
- in frameView);
+ walkDriver!);
}
}
finally
@@ -738,14 +726,22 @@ public sealed class RetailPViewRenderer
in frameView);
}
- DrawDynamicsLast(
- ctx,
- passes,
- partition,
- viewcone,
- ctx.RootCell.IsOutdoorNode,
- frameEntityPasses,
- in frameView);
+ // Campaign FW4: the retail walk now owns live objects at their
+ // exact DrawObjCell turns (outdoor cells, look-ins, and the root
+ // flood). The packed DynamicLast route is a legacy duplicate and
+ // must not run on a walk frame. Standalone/fake executors retain
+ // the old path only until the FW4 shell-deletion slice removes it.
+ if (!walkActive)
+ {
+ DrawDynamicsLast(
+ ctx,
+ passes,
+ partition,
+ viewcone,
+ ctx.RootCell.IsOutdoorNode,
+ frameEntityPasses,
+ in frameView);
+ }
// OUTDOOR root: the LScape-boundary alpha drain deferred from the
// landscape stage runs HERE, after punches, interior shells, cell
@@ -1441,21 +1437,15 @@ public sealed class RetailPViewRenderer
/// Campaign FW3.2b-2: the DYNAMICS-only remainder of the old
/// +
- /// split — the walk now draws every STATIC route (see
- /// ); this method keeps ONLY what
- /// stays on the OLD visibility pipeline per the plan's dual-compute
- /// split: outdoor-cell unattached particles, the late per-slice outside-
- /// dynamics/weather loop, and the late particle union submission. Look-in
- /// dynamics and their cell particles are now walk-turn-owned.
+ /// split. The walk now draws every mesh and every attached emitter at its
+ /// own turn. This tail therefore carries only the two landscape services
+ /// that are not scene projections: ownerless outdoor-cell emitters and
+ /// weather. It deliberately submits no packed entity route.
private void DrawLandscapeDynamicsPhase(
RetailPViewFrameInput ctx,
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
- InteriorEntityPartition.Result? partition,
- ViewconeCuller viewcone,
- Walk.WalkFrameDriver walkDriver,
- IRenderFrameEntityPassExecutor? frameEntityPasses,
- in RenderFrameView frameView)
+ Walk.WalkFrameDriver walkDriver)
{
if (clipAssembly.OutsideViewSlices.Length == 0)
return;
@@ -1471,73 +1461,19 @@ public sealed class RetailPViewRenderer
// per-cell DrawCells turn, before the enclosing building shell.
ProbeBuildingLookInFrames(ctx, passes, walkDriver);
- // LATE phase (per slice): outside-stage dynamics' meshes + weather —
- // unchanged from DrawLandscapeThroughOutsideView's own late loop.
- _staticParticleUnionScratch.Clear();
- int probeSliceIndex = 0;
+ // GameSky's weather pass still runs through each active landscape
+ // view so doorway scissor/clip state matches the old executor. The
+ // dynamics collection is intentionally empty: the walk already drew
+ // those records at OnLandscapeCellTurn.
foreach (var slice in clipAssembly.OutsideViewSlices)
{
passes.SetTerrainClip(slice.Planes);
passes.ClearClipRouting();
-
- _outdoorStaticScratch.Clear();
- _lateParticleOwnerScratch.Clear();
- foreach (var e in _outsideStageDynamics)
- {
- EntitySphere(e, out var c, out float r);
- if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
- {
- _outdoorStaticScratch.Add(e);
- if (!InteriorEntityPartition.IsIndoorCellId(e.ParentCellId))
- _lateParticleOwnerScratch.Add(e.Id);
- }
- }
- if (frameEntityPasses is not null)
- {
- RenderFrameRouteOwnerSelector.Union(
- _lateParticleOwnerScratch,
- in frameView,
- RenderFrameCandidateRoute.LandscapeOutsideDynamic,
- probeSliceIndex,
- 0);
- }
- _candidateObserver?.ObservePViewBucket(
- CurrentRenderPViewRoute.LandscapeOutsideDynamic,
- probeSliceIndex,
- 0,
- _outdoorStaticScratch);
- RenderFrameEntityDrawRequest? entityDraw =
- frameEntityPasses is null
- ? null
- : new RenderFrameEntityDrawRequest(
- frameView,
- RenderFrameCandidateRoute.LandscapeOutsideDynamic,
- probeSliceIndex,
- 0,
- ctx.PlayerLandblockId ?? 0);
- probeSliceIndex++;
- _staticParticleUnionScratch.UnionWith(_lateParticleOwnerScratch);
passes.DrawLandscapeSliceLate(
ctx,
- new RetailPViewLandscapeLateSliceContext(slice, _outdoorStaticScratch)
- {
- EntityDraw = entityDraw,
- });
- }
-
- // Late-particle union submission — DynamicLast owners excluded, same
- // as DrawLandscapeThroughOutsideView's own final submission.
- if (frameEntityPasses is not null)
- {
- RenderFrameRouteOwnerSelector.ExceptRoute(
- _staticParticleUnionScratch, in frameView, RenderFrameCandidateRoute.DynamicLast);
- }
- if (_staticParticleUnionScratch.Count > 0)
- {
- passes.DrawLandscapeStaticParticles(
- ctx,
- new RetailPViewLandscapeStaticParticleContext(_staticParticleUnionScratch));
- _staticParticleUnionScratch.Clear();
+ new RetailPViewLandscapeLateSliceContext(
+ slice,
+ Array.Empty()));
}
passes.UseIndoorMembershipOnlyRouting();