fix(render): FW4 slice 2 - exit seals stamp the walk''s own flood

The cathedral falls shine-through (owner report at the #456 terrace
seam, toggling with the camera''s side of the seam): the probe''s new
phase tags pinned the falls emitters to the cell-owners route
submitting POST-depth-clear on interior roots - retail''s own timing,
safe only because seals own the aperture depth by the end-of-frame
alpha drain. 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), so the drain z-passed across the unsealed opening.

DrawWalkExitPortalMasks now stamps the walk''s own ordered interior
flood (WalkFrameDriver.InteriorFloodCells, captured at
OnInteriorFloodDrawTurn - retail PView::DrawCells pc:432785-432786),
keeping the old assembly''s per-cell slice clips where present and
sealing unclipped where the old apparatus had no entry (benign: the
exact dat aperture polygon, z-tested). The legacy path keeps the old
seal draw. Also: ACDREAM_PROBE_WALK_ROOT gains [walk-part]/[walk-alpha]
phase-tagged submission/drain lines (the apparatus that pinned this).

Hermetic 6,762/0, Walk lane 213/1, InstalledDat walk conformance 40/1.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 19:22:56 +02:00
parent 37febd1fe6
commit c40aecfc8c
8 changed files with 136 additions and 4 deletions

View file

@ -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` |

View file

@ -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 —

View file

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

View file

@ -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<Exception>? resetFailures = null;

View file

@ -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";
}
/// <summary>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);
}
/// <summary>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.</summary>
private static void ProbeWalkParticleRoute(string route, IReadOnlySet<uint>? 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,

View file

@ -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
}
}
/// <summary>Campaign FW4 slice 2 — the walk-flood seal draw. Retail's
/// <c>PView::DrawCells</c> 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).</summary>
private void DrawWalkExitPortalMasks(
RetailPViewFrameInput ctx,
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
Walk.WalkFrameDriver driver)
{
List<uint> 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)

View file

@ -386,6 +386,17 @@ internal sealed class WalkFrameDriver : IWalkEventSink
// walk pass dedicated to nothing but set-gathering.
internal HashSet<uint> VisitedCells { get; } = new();
/// <summary>FW4 slice 2: the interior root's ORDERED flood cell list,
/// exactly as retail's <c>PView::DrawCells</c> iterates it for the
/// exit-portal seals (pc:432785-432786) — captured at
/// <see cref="IWalkEventSink.OnInteriorFloodDrawTurn"/>. 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).</summary>
internal List<uint> InteriorFloodCells { get; } = new();
internal List<WalkBuilding> VisitedBuildings { get; } = new();
internal HashSet<uint> VisitedLandscapeCellIds { get; } = new();
@ -498,6 +509,7 @@ internal sealed class WalkFrameDriver : IWalkEventSink
VisitedCells.Clear();
VisitedBuildings.Clear();
VisitedLandscapeCellIds.Clear();
InteriorFloodCells.Clear();
}
/// <summary>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]);
}

View file

@ -182,6 +182,13 @@ public static class RenderingDiagnostics
public static bool ProbeWalkRootEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_WALK_ROOT") == "1";
/// <summary>Companion scratch for <see cref="ProbeWalkRootEnabled"/>
/// (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.</summary>
public static string WalkRootPhase = "";
/// <summary>
/// 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,