diff --git a/docs/launch-options.md b/docs/launch-options.md index 4da25126..44e936b6 100644 --- a/docs/launch-options.md +++ b/docs/launch-options.md @@ -315,7 +315,7 @@ issue is closed, the strip was missed; delete both. | `ACDREAM_PROBE_CHILD_CELL` | c4 route 7 | `=1` | gates one `[child-cell]` line per Runtime committed-child canonical-cell write in `RuntimeLiveEntitySessionController`, `RuntimeEntityObjectLifetime`, `RuntimeEntityDirectory` (parent/child guid, old/new cell, cause tag) | print-only | `PhysicsDiagnostics.ProbeChildCellEnabled` | | `ACDREAM_PROBE_CLIPROUTE` | "throwaway apparatus — strip once §4 ships" | `=1` | print-on-change `[clip-route]` / `[clip-route-disp]` / `[clip-route-scis]` lines: outside-slice clip routing, region-SSBO bytes, terrain-UBO head, actual GL/RHI scissor state | print-only | `RenderingDiagnostics.ProbeClipRouteEnabled` | | `ACDREAM_PROBE_WALK_SHADOW` | Campaign FW3.2b-2 flip apparatus — dies with the flip commit | `=1` | runs the PRODUCTION retail frame walk per frame in SHADOW (no draws change) and prints one `[walk-shadow]` line per frame whose visited cell/building sets diverge from the old visibility path (the I5 dual-shadow pattern applied to the FW3 static cutover) | print-only; adds the per-frame CPU cost of the shadow walk while set | `RenderingDiagnostics.ProbeWalkShadowEnabled` | -| `ACDREAM_PROBE_WALK_ROOT` | FW3 visual-gate apparatus (stairwell-transition defect) — dies with that fix | `=1` | after every interior/outdoor root flip, prints 8 `[walk-root]` lines (one per frame): viewer cell, root cell, camera-cell resolution, slice count, eye position, and the walk's visited cell list — pins which crossing frames root wrong or flood short | print-only | `RenderingDiagnostics.ProbeWalkRootEnabled` | +| `ACDREAM_PROBE_WALK_ROOT` | FW3 visual-gate apparatus (stairwell-transition + cathedral-falls defects) — dies with those fixes | `=1` | after every interior/outdoor root flip, prints 8 `[walk-root]` lines (one per frame): viewer cell, root cell, camera-cell resolution, slice count, eye position, and the walk's visited cell list; also prints `[walk-part]` lines at every particle submission route (route name, owner ids, pre/post-depth-clear phase) and `[walk-alpha]` lines at every retail alpha-queue drain — together they pin which crossing frames root wrong, flood short, or submit particles after the interior depth clear | print-only; chatty near particle-heavy scenes while set | `RenderingDiagnostics.ProbeWalkRootEnabled` | | `ACDREAM_PROBE_CONTACT_PLANE` | spike-only, 2026-05-20 | `=1` | gates one `[cp-write]` line per write to `CollisionInfo.ContactPlane*`/`LastKnownContactPlane*` fields (field, old→new, caller method via stack walk, source line); only logs on actual value changes | print-only, but performs a stack walk to identify the caller method when firing — real CPU cost per write while on (not just a string format); suppresses no-op writes to bound volume | `PhysicsDiagnostics.ProbeContactPlaneEnabled` | | `ACDREAM_PROBE_ENT` | #138 | `="1"` | Traces the persistent player entity across teleport streaming churn: presence in the render draw-set flat view vs. survival of the dynamics cull, to distinguish "missing from draw set" vs "present but culled" | print-only, "Observation-only — emits no behavior change" (doc comment). `LogPlayerDynOnChange` dedupes by transition to avoid per-frame spam. Marked STRIP-once-root-caused (like the dense-town FPS apparatus). | `EntityVanishProbe.Enabled` (`EntityVanishProbe.cs:23-24`), issue #138-B | | `ACDREAM_PROBE_FLAP` | "throwaway apparatus — strip once the flap mechanism is confirmed" | `=1` | EVERY FRAME (unthrottled, not change-gated) while the camera root is indoor: `[flap]` from `PortalVisibilityBuilder.Build` (portal side-test/traverse/cull/projection) + paired `[flap-cam]` from `PhysicsCameraCollisionProbe`/`[flap-sweep]` (FindCameraCell resolution, eye positions) | print-only, but unthrottled per-frame `StringBuilder` allocation + `Console.WriteLine` on multiple call sites while indoor — heavy log volume/allocation under sustained indoor play; does not alter rendered output | `RenderingDiagnostics.ProbeFlapEnabled` | diff --git a/docs/plans/2026-08-30-campaign-fw-frame-walk.md b/docs/plans/2026-08-30-campaign-fw-frame-walk.md index fbfaf8d2..25962b39 100644 --- a/docs/plans/2026-08-30-campaign-fw-frame-walk.md +++ b/docs/plans/2026-08-30-campaign-fw-frame-walk.md @@ -506,6 +506,26 @@ SEALS + cell slices still assemble from the old per-cell views (identical dat portal polygons; only the visible SET can differ), and interior-root look-in seeding still reads the old outside view. +**FW4 SLICE 2 — the walk-flood seal cutover (2026-08-30, pulled forward +from the FW3 visual gate's cathedral round).** The owner's cathedral +report (waterfall particles shining through from the terrace — toggling +with which side of the #456 seam the CAMERA resolves to) probe-pinned via +the `[walk-part]`/`[walk-alpha]` phase tags: the falls emitters ride the +cell-owners route and submit POST-depth-clear on interior-rooted frames +(retail's own timing — cell-scope alpha drains at the end-of-frame flush +against seal-owned depth), but our seal SET still came from the old +apparatus's flood, which misses exit portals at the #456 seam band (its +never-drawn panel family) — unsealed aperture depth let the end-of-frame +drain z-pass across the whole opening. `DrawWalkExitPortalMasks` now +stamps THE WALK'S OWN ordered interior flood +(`WalkFrameDriver.InteriorFloodCells`, captured at +`OnInteriorFloodDrawTurn` — retail pc:432785-432786's reverse +cell_draw_list), with the old assembly's per-cell slice clips where +present and unclipped seals (benign: exact dat aperture polygon, +z-tested) where the old apparatus had no entry. The legacy !walkActive +path keeps the old seal draw. Suites: hermetic 6,762/0, Walk lane 213/1, +InstalledDat walk conformance 40/1. + - Entities gate per view via `Render::viewconeCheck` at their cell's walk turn; dynamics-last and alpha interleave per the walk's stages (`RetailAlphaQueue` becomes the stream's alpha stage or is absorbed — diff --git a/src/AcDream.App/Rendering/ParticleRenderer.cs b/src/AcDream.App/Rendering/ParticleRenderer.cs index 9766089b..75f60578 100644 --- a/src/AcDream.App/Rendering/ParticleRenderer.cs +++ b/src/AcDream.App/Rendering/ParticleRenderer.cs @@ -266,7 +266,15 @@ public sealed unsafe partial class ParticleRenderer : IDisposable if (_submissionScratch.Count == 0) return; - if (renderPass == ParticleRenderPass.Scene && _alphaQueue?.IsCollecting == true) + bool defers = renderPass == ParticleRenderPass.Scene && _alphaQueue?.IsCollecting == true; + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) + { + Console.WriteLine( + $"[walk-part] finish pass={renderPass} n={_submissionScratch.Count} " + + $"branch={(defers ? "defer" : "immediate")} " + + $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"); + } + if (defers) DeferToRetailAlphaQueue(camera); else DrawOrdered(camera); diff --git a/src/AcDream.App/Rendering/RetailAlphaQueue.cs b/src/AcDream.App/Rendering/RetailAlphaQueue.cs index 3f828f0a..cdcc97f2 100644 --- a/src/AcDream.App/Rendering/RetailAlphaQueue.cs +++ b/src/AcDream.App/Rendering/RetailAlphaQueue.cs @@ -146,6 +146,12 @@ internal sealed class RetailAlphaQueue : IWorldSceneAlphaFrame { if (!IsCollecting) throw new InvalidOperationException("Retail alpha flush requires an active frame."); + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled && _submissions.Count > 0) + { + Console.WriteLine( + $"[walk-alpha] flush n={_submissions.Count} " + + $"phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"); + } Exception? drawFailure = null; List? resetFailures = null; diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs index b7feeabf..2aaac5b9 100644 --- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs +++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs @@ -180,6 +180,8 @@ internal sealed partial class RetailPViewPassExecutor : public void BeginFrame() { _particleClassifications.BeginFrame(); + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) + AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "pre"; } /// Campaign FW3.2b-2: the shared dispatcher, for @@ -510,6 +512,7 @@ internal sealed partial class RetailPViewPassExecutor : DisableClipDistances(); _particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds); + ProbeWalkParticleRoute("static-owners", _particleClassifications.Outdoor); if (_particleClassifications.Outdoor.Count > 0 && _particles is not null && _particleRenderer is not null) @@ -569,7 +572,12 @@ internal sealed partial class RetailPViewPassExecutor : DisableClipDistances(); } - public void ClearInteriorDepth() => _surface.ClearInteriorDepth(); + public void ClearInteriorDepth() + { + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) + AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase = "post"; + _surface.ClearInteriorDepth(); + } public void DrawExitPortalMask( RetailPViewFrameInput frame, @@ -598,6 +606,8 @@ internal sealed partial class RetailPViewPassExecutor : // hardware clip slot made effects vanish by view direction (zero // outside slices in view = zero submissions) — invented behavior. DisableClipDistances(); + ProbeWalkParticleRoute( + outdoorCells ? "unattached-outdoor" : "unattached-interior", null); _particleRenderer.DrawForOwners( frame.Camera, frame.CameraWorldPosition, @@ -610,6 +620,30 @@ internal sealed partial class RetailPViewPassExecutor : : UnattachedEmitterCellScope.InteriorCells); } + /// ACDREAM_PROBE_WALK_ROOT companion (throwaway, dies with the + /// cathedral falls investigation): logs each particle submission route + /// with its owner count + the pre/post-depth-clear phase tag. + private static void ProbeWalkParticleRoute(string route, IReadOnlySet? owners) + { + if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeWalkRootEnabled) + return; + string ids = ""; + if (owners is not null) + { + var sb = new System.Text.StringBuilder(); + int shown = 0; + foreach (uint id in owners) + { + if (shown++ == 6) { sb.Append(",…"); break; } + if (sb.Length > 0) sb.Append(','); + sb.Append(id.ToString("x")); + } + ids = $" n={owners.Count} ids=[{sb}]"; + } + Console.WriteLine( + $"[walk-part] {route}{ids} phase={AcDream.Core.Rendering.RenderingDiagnostics.WalkRootPhase}"); + } + public void FlushLandscapeAlpha() => _alpha.Flush(); public void FlushLandscapeAlphaFartherThan(float minViewerDistance) => @@ -633,6 +667,7 @@ internal sealed partial class RetailPViewPassExecutor : return; DisableClipDistances(); + ProbeWalkParticleRoute("cell-owners", visible); // Retail never clips cell particles to a portal view: the owner // cell's walls own occlusion via the depth test at the alpha flush. _particleRenderer.DrawForOwners( @@ -658,6 +693,7 @@ internal sealed partial class RetailPViewPassExecutor : return; DisableClipDistances(); + ProbeWalkParticleRoute("dyn-owners", dynamics); _particleRenderer.DrawForOwners( frame.Camera, frame.CameraWorldPosition, diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index decc9296..6f88c0cf 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -333,8 +333,11 @@ public sealed class RetailPViewRenderer passes.FlushLandscapeAlpha(); passes.ClearInteriorDepth(); }; + // FW4 slice 2: the seals stamp the WALK'S OWN flood cells (see + // DrawWalkExitPortalMasks). walkDriver is assigned below, before + // any Replay can fire this closure. Action drawExitSeals = () => - DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells); + DrawWalkExitPortalMasks(ctx, passes, clipAssembly, walkDriver!); var leafRenderer = new WalkProductionLeafRenderer( walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals); @@ -2035,6 +2038,39 @@ public sealed class RetailPViewRenderer } } + /// Campaign FW4 slice 2 — the walk-flood seal draw. Retail's + /// PView::DrawCells stamps every exit portal of THE FLOOD'S OWN + /// cells (pc:432785-432786, reverse cell_draw_list far→near) — one + /// visibility structure decides the flood, the seals, and the terrain + /// views alike. The old apparatus's flood misses exit portals at the + /// #456 cathedral seam band (its never-drawn panel family), leaving + /// aperture depth unsealed after the interior clear; the end-of-frame + /// alpha drain (cell-owned emitters — retail's own timing) then + /// z-passes across the whole opening (the falls shine-through, + /// probe-pinned via ACDREAM_PROBE_WALK_ROOT's phase tags). Per-cell + /// slice clips still come from the old assembly where present; a cell + /// the old apparatus missed seals unclipped (the depth fan is the exact + /// dat aperture polygon and z-tests, so over-coverage is benign). + private void DrawWalkExitPortalMasks( + RetailPViewFrameInput ctx, + IRetailPViewPassExecutor passes, + ClipFrameAssembly clipAssembly, + Walk.WalkFrameDriver driver) + { + List floodCells = driver.InteriorFloodCells; + for (int i = floodCells.Count - 1; i >= 0; i--) + { + uint cellId = floodCells[i]; + foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId)) + passes.DrawExitPortalMask( + ctx, + new RetailPViewCellSliceContext( + cellId, + slice, + NoParticleOwners)); + } + } + private void DrawEnvCellShells( IRetailPViewPassExecutor passes, PortalVisibilityFrame pvFrame) diff --git a/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs b/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs index 475f0a1a..588b63f3 100644 --- a/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs +++ b/src/AcDream.App/Rendering/Walk/WalkFrameDriver.cs @@ -386,6 +386,17 @@ internal sealed class WalkFrameDriver : IWalkEventSink // walk pass dedicated to nothing but set-gathering. internal HashSet VisitedCells { get; } = new(); + /// FW4 slice 2: the interior root's ORDERED flood cell list, + /// exactly as retail's PView::DrawCells iterates it for the + /// exit-portal seals (pc:432785-432786) — captured at + /// . Empty for an + /// outdoor-rooted frame. The production seal draw iterates THIS list + /// (the walk's own flood), not the old visibility apparatus's — at the + /// #456 seam band the old builder's flood misses exit portals the walk + /// provably floods, leaving unsealed aperture depth the end-of-frame + /// alpha drain splats through (the cathedral falls shine-through). + internal List InteriorFloodCells { get; } = new(); + internal List VisitedBuildings { get; } = new(); internal HashSet VisitedLandscapeCellIds { get; } = new(); @@ -498,6 +509,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink VisitedCells.Clear(); VisitedBuildings.Clear(); VisitedLandscapeCellIds.Clear(); + InteriorFloodCells.Clear(); } /// Records the final segment mark (plan §FW3.2b-1's "at frame @@ -691,6 +703,13 @@ internal sealed class WalkFrameDriver : IWalkEventSink MarkIfGrown(); _events.Add(WalkFrameEvent.ExitSeals()); + // FW4 slice 2: retain the ordered flood for the seal draw (the + // DrawExitSeals leaf runs at Replay, when Collect has long filled + // this) — see the property's own doc comment. + InteriorFloodCells.Clear(); + for (int i = 0; i < cells.Count; i++) + InteriorFloodCells.Add(cells[i]); + for (int i = 0; i < cells.Count; i++) EmitCellTurn(WalkDrawStage.CellStatic, cells[i]); } diff --git a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs index c2d39323..607cc39f 100644 --- a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs +++ b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs @@ -182,6 +182,13 @@ public static class RenderingDiagnostics public static bool ProbeWalkRootEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_ROOT") == "1"; + /// Companion scratch for + /// (throwaway, same investigation): the frame phase relative to the + /// interior depth clear ("pre"/"post"), stamped by the pass executor so + /// particle/alpha probe lines can tag WHEN a submission or drain ran. + /// Written only while the probe is on. + public static string WalkRootPhase = ""; + /// /// Bounded-propagation port apparatus (2026-06-08). When true, PortalVisibilityBuilder.Build emits /// one [portal-churn] summary line per call: per-cell pop count (re-pops = churn), total re-enqueues,