diag(render): §4 flap [clip-route] probe — slot routing + clip-buffer content + landscape scissor

The decisive probe between the two surviving suspects from the 2026-06-09
building-flood-merge handoff (docs/research/2026-06-09-flap-outdoor-fullworld-
building-flood-merge-handoff.md section 1), gated by ACDREAM_PROBE_CLIPROUTE=1,
all print-on-change:

- [clip-route] (RetailPViewRenderer.DrawLandscapeThroughOutsideView): the
  outside slice slot + NDC AABB + planes, the CellIdToSlot routing table, the
  region-SSBO bytes DECODED at the routed slot, and the terrain-UBO head —
  captured after SetTerrainClip + UploadClipFrame + SetClipRouting, i.e.
  exactly what the landscape draws consume. Pins/refutes suspect (b) and the
  slot-repack half of suspect (a).
- [clip-route-disp] (WbDrawDispatcher.Draw, routed draws only): per-slot
  instance histogram exactly as staged for binding=3 plus the count of
  entities dropped by ResolveSlotForFrame CULL. Pins/refutes the
  instance-routing half of suspect (a).
- [clip-route-scis] (GameWindow.DrawRetailPViewLandscapeSlice): the ACTUAL GL
  scissor enable + box read back right after BeginDoorwayScissor — the whole
  landscape pass (sky + terrain + outdoor entities + player) draws inside this
  box, so a doorway-sized box here IS the full-world kill by construction.

Code-reading findings recorded while building the probe: the landscape pass is
scissored to slice.NdcAabb end-to-end (GameWindow.cs DrawRetailPViewLandscapeSlice),
and ResolveEntitySlot CULLs server entities with null ParentCellId while routing
is active — both now directly observable under the probe.

Throwaway apparatus — strip once §4 ships.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-10 08:37:09 +02:00
parent d877e4329a
commit 682cba36f1
4 changed files with 194 additions and 0 deletions

View file

@ -208,11 +208,15 @@ public sealed class RetailPViewRenderer
if (clipAssembly.OutsideViewSlices.Length == 0)
return;
int probeSliceIndex = 0;
foreach (var slice in clipAssembly.OutsideViewSlices)
{
_clipFrame.SetTerrainClip(slice.Planes);
UploadClipFrame(ctx.SetTerrainClipUbo);
_entities.SetClipRouting(clipAssembly.CellIdToSlot, slice.Slot, outdoorVisible: true);
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeClipRouteEnabled)
EmitClipRouteProbe(clipAssembly, slice, probeSliceIndex);
probeSliceIndex++;
ctx.DrawLandscapeSlice(new RetailPViewLandscapeSliceContext(slice, partition.Outdoor));
}
@ -222,6 +226,91 @@ public sealed class RetailPViewRenderer
UseIndoorMembershipOnlyRouting();
}
// §4 flap [clip-route] probe state (2026-06-10, throwaway): print-on-change signature +
// monotonic sequence so held-flap vs healthy frames diff cleanly in one capture.
private string? _lastClipRouteSig;
private long _clipRouteSeq;
private readonly List<uint> _clipRouteCellKeys = new();
// §4 flap apparatus (2026-06-10): the decisive probe between the surviving suspects
// (handoff 2026-06-09 §1). Emits the EXACT clip inputs the landscape pass draws under:
// the outside slice's slot + NDC AABB + planes (CPU side), the region-SSBO bytes decoded
// at that slot (what mesh_modern.vert reads for routed instances), the terrain-UBO head
// (what terrain/sky gate against), and the CellIdToSlot routing table. Fires AFTER
// SetTerrainClip + UploadClipFrame + SetClipRouting, BEFORE DrawLandscapeSlice — so the
// printed bytes are exactly what this slice's draws consume.
private void EmitClipRouteProbe(ClipFrameAssembly clipAssembly, ClipViewSlice slice, int sliceIndex)
{
var sb = new System.Text.StringBuilder(256);
sb.Append(System.FormattableString.Invariant(
$"slice={sliceIndex}/{clipAssembly.OutsideViewSlices.Length} slot={slice.Slot}"));
sb.Append(System.FormattableString.Invariant(
$" ndc=({slice.NdcAabb.X:F3},{slice.NdcAabb.Y:F3},{slice.NdcAabb.Z:F3},{slice.NdcAabb.W:F3})"));
sb.Append(System.FormattableString.Invariant($" planes={slice.Planes.Length}["));
for (int i = 0; i < slice.Planes.Length; i++)
{
var p = slice.Planes[i];
if (i > 0) sb.Append(' ');
sb.Append(System.FormattableString.Invariant($"({p.X:F3},{p.Y:F3},{p.Z:F3},{p.W:F3})"));
}
// CellIdToSlot sorted by cell id so dictionary enumeration order can't fake a change.
sb.Append("] cells={");
_clipRouteCellKeys.Clear();
foreach (uint key in clipAssembly.CellIdToSlot.Keys)
_clipRouteCellKeys.Add(key);
_clipRouteCellKeys.Sort();
for (int i = 0; i < _clipRouteCellKeys.Count; i++)
{
if (i > 0) sb.Append(',');
sb.Append(System.FormattableString.Invariant(
$"0x{_clipRouteCellKeys[i]:X8}:{clipAssembly.CellIdToSlot[_clipRouteCellKeys[i]]}"));
}
sb.Append('}');
// Region-SSBO content decoded at the routed slot, from the packed bytes UploadClipFrame
// just uploaded — slot stride 144: count uint at +0, planes[8] at +16.
var rb = _clipFrame.RegionBytesForTest;
int off = slice.Slot * ClipFrame.CellClipStrideBytes;
if (off >= 0 && off + ClipFrame.CellClipStrideBytes <= rb.Length)
{
uint ssboCount = System.BitConverter.ToUInt32(rb.Slice(off, 4));
sb.Append(System.FormattableString.Invariant($" ssbo[{slice.Slot}]: n={ssboCount}"));
int planeN = (int)System.Math.Min(ssboCount, (uint)ClipFrame.MaxPlanes);
for (int i = 0; i < planeN; i++)
{
int po = off + ClipFrame.CellClipPlanesOffset + i * 16;
float px = System.BitConverter.ToSingle(rb.Slice(po, 4));
float py = System.BitConverter.ToSingle(rb.Slice(po + 4, 4));
float pz = System.BitConverter.ToSingle(rb.Slice(po + 8, 4));
float pw = System.BitConverter.ToSingle(rb.Slice(po + 12, 4));
sb.Append(System.FormattableString.Invariant($" ({px:F3},{py:F3},{pz:F3},{pw:F3})"));
}
}
else
{
sb.Append(System.FormattableString.Invariant(
$" ssbo[{slice.Slot}]: OUT-OF-RANGE len={rb.Length}"));
}
// Terrain-UBO head as uploaded (std140: int count at +0, planes[8] at +16).
var tb = _clipFrame.TerrainBytesForTest;
int uboCount = System.BitConverter.ToInt32(tb.Slice(0, 4));
float u0 = System.BitConverter.ToSingle(tb.Slice(16, 4));
float u1 = System.BitConverter.ToSingle(tb.Slice(20, 4));
float u2 = System.BitConverter.ToSingle(tb.Slice(24, 4));
float u3 = System.BitConverter.ToSingle(tb.Slice(28, 4));
sb.Append(System.FormattableString.Invariant(
$" ubo: n={uboCount} p0=({u0:F3},{u1:F3},{u2:F3},{u3:F3})"));
string sig = sb.ToString();
_clipRouteSeq++;
if (sig == _lastClipRouteSig)
return;
_lastClipRouteSig = sig;
Console.WriteLine($"[clip-route] n={_clipRouteSeq} {sig}");
}
private void DrawExitPortalMasks(
IRetailPViewCellDrawCallbacks ctx,
PortalVisibilityFrame pvFrame,