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:
parent
d877e4329a
commit
682cba36f1
4 changed files with 194 additions and 0 deletions
|
|
@ -9415,6 +9415,8 @@ public sealed class GameWindow : IDisposable
|
|||
{
|
||||
var slice = sliceCtx.Slice;
|
||||
bool scissor = BeginDoorwayScissor(true, slice.NdcAabb);
|
||||
if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeClipRouteEnabled)
|
||||
EmitClipRouteScissorProbe(scissor, slice.NdcAabb);
|
||||
|
||||
_gl!.BindBufferBase(BufferTargetARB.UniformBuffer,
|
||||
ClipFrame.TerrainClipUboBinding, _clipFrame!.TerrainUbo);
|
||||
|
|
@ -9600,6 +9602,30 @@ public sealed class GameWindow : IDisposable
|
|||
_glStateStable = 0;
|
||||
}
|
||||
|
||||
// §4 flap [clip-route-scis] probe (2026-06-10, throwaway): the ACTUAL GL scissor state
|
||||
// the landscape pass (sky + terrain + outdoor entities + the player) draws under, read
|
||||
// back right after BeginDoorwayScissor. The whole pass is scissored to slice.NdcAabb —
|
||||
// if the box reads doorway-sized here, the full-world flap is the scissor by
|
||||
// construction, no RenderDoc needed. Print-on-change.
|
||||
private string? _lastClipRouteScisSig;
|
||||
private long _clipRouteScisSeq;
|
||||
|
||||
private void EmitClipRouteScissorProbe(bool applied, System.Numerics.Vector4 ndcAabb)
|
||||
{
|
||||
var gl = _gl;
|
||||
if (gl is null) return;
|
||||
Span<int> sbox = stackalloc int[4];
|
||||
gl.GetInteger(GetPName.ScissorBox, sbox);
|
||||
bool enabled = gl.IsEnabled(EnableCap.ScissorTest);
|
||||
string sig = System.FormattableString.Invariant(
|
||||
$"applied={(applied ? 1 : 0)} scis={(enabled ? 1 : 0)} box=({sbox[0]},{sbox[1]},{sbox[2]},{sbox[3]}) ndc=({ndcAabb.X:F3},{ndcAabb.Y:F3},{ndcAabb.Z:F3},{ndcAabb.W:F3})");
|
||||
_clipRouteScisSeq++;
|
||||
if (sig == _lastClipRouteScisSig)
|
||||
return;
|
||||
_lastClipRouteScisSig = sig;
|
||||
Console.WriteLine($"[clip-route-scis] n={_clipRouteScisSeq} {sig}");
|
||||
}
|
||||
|
||||
private void EnableClipDistances()
|
||||
{
|
||||
for (int i = 0; i < ClipFrame.MaxPlanes; i++)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -343,6 +343,52 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
_outdoorVisible = false;
|
||||
}
|
||||
|
||||
// §4 flap [clip-route-disp] probe state (2026-06-10, throwaway): print-on-change
|
||||
// signature + monotonic sequence + reusable histogram. See RenderingDiagnostics
|
||||
// .ProbeClipRouteEnabled for the full probe contract.
|
||||
private string? _lastClipRouteDispSig;
|
||||
private long _clipRouteDispSeq;
|
||||
private readonly SortedDictionary<uint, int> _clipRouteHist = new();
|
||||
|
||||
// §4 flap apparatus (2026-06-10): per-slot instance histogram as staged for binding=3.
|
||||
// grp.Slots is laid out 1:1 with grp.Matrices (binding=0), so this IS the slot content
|
||||
// the GPU reads per instance — if outdoor instances land on the wrong slot (or vanish
|
||||
// into cullEnt) when the building flood merges, this line shows it directly.
|
||||
private void EmitClipRouteDispatchProbe(int culledEntities)
|
||||
{
|
||||
_clipRouteHist.Clear();
|
||||
int total = 0;
|
||||
foreach (var grp in _groups.Values)
|
||||
{
|
||||
var slots = grp.Slots;
|
||||
for (int i = 0; i < slots.Count; i++)
|
||||
{
|
||||
_clipRouteHist.TryGetValue(slots[i], out int c);
|
||||
_clipRouteHist[slots[i]] = c + 1;
|
||||
total++;
|
||||
}
|
||||
}
|
||||
|
||||
var sb = new System.Text.StringBuilder(128);
|
||||
sb.Append(System.FormattableString.Invariant(
|
||||
$"outdoorSlot={_outdoorSlot} outdoorVis={(_outdoorVisible ? 'Y' : 'n')} inst={total} cullEnt={culledEntities} slots={{"));
|
||||
bool first = true;
|
||||
foreach (var kv in _clipRouteHist)
|
||||
{
|
||||
if (!first) sb.Append(',');
|
||||
first = false;
|
||||
sb.Append(System.FormattableString.Invariant($"{kv.Key}:{kv.Value}"));
|
||||
}
|
||||
sb.Append('}');
|
||||
|
||||
string sig = sb.ToString();
|
||||
_clipRouteDispSeq++;
|
||||
if (sig == _lastClipRouteDispSig)
|
||||
return;
|
||||
_lastClipRouteDispSig = sig;
|
||||
Console.WriteLine($"[clip-route-disp] n={_clipRouteDispSeq} {sig}");
|
||||
}
|
||||
|
||||
// Phase U.4 CULL sentinel returned by ResolveEntitySlot: the entity's instances
|
||||
// are dropped entirely (not emitted into the binding=0 instance buffer NOR the
|
||||
// binding=3 slot buffer), matching the existing frustum / visible-cell cull.
|
||||
|
|
@ -752,6 +798,11 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
uint? populateEntityId = null;
|
||||
uint populateLandblockId = 0;
|
||||
|
||||
// §4 flap [clip-route-disp] probe (2026-06-10, throwaway): entities dropped by
|
||||
// ResolveSlotForFrame's CULL sentinel this Draw. One increment per culled entity —
|
||||
// cheap enough to count unconditionally; emission below is probe-gated.
|
||||
int probeCulledEntities = 0;
|
||||
|
||||
// Tier 1 cache (#53) — fast-path one-shot tracker. The cache stores a
|
||||
// FLAT list of batches across all MeshRefs of an entity, so a single
|
||||
// ApplyCacheHit call already drew every batch. _walkScratch yields
|
||||
|
|
@ -844,6 +895,8 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
(_currentEntitySlot, _currentEntityCulled) = ResolveSlotForFrame(
|
||||
_clipRoutingActive, entity.ServerGuid, entity.ParentCellId,
|
||||
_cellIdToSlot, _outdoorSlot, _outdoorVisible);
|
||||
if (_currentEntityCulled)
|
||||
probeCulledEntities++;
|
||||
}
|
||||
prevTupleEntityId = entity.Id;
|
||||
|
||||
|
|
@ -1067,6 +1120,15 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
|||
// null) or when no entities walked at all.
|
||||
FinalFlushPopulate(populateEntityId, populateLandblockId, _cache, _populateScratch);
|
||||
|
||||
// §4 flap [clip-route-disp] probe (2026-06-10, throwaway): the per-slot instance
|
||||
// histogram exactly as it will be uploaded to binding=3 (grp.Slots) plus the
|
||||
// culled-entity count. Routed draws only (the landscape pass under DrawInside) so the
|
||||
// unrouted per-cell bucket draws don't oscillate the print-on-change signature.
|
||||
// Emitted BEFORE the anyVao / totalInstances early-outs so an all-culled frame still
|
||||
// reports (inst=0).
|
||||
if (RenderingDiagnostics.ProbeClipRouteEnabled && _clipRoutingActive)
|
||||
EmitClipRouteDispatchProbe(probeCulledEntities);
|
||||
|
||||
// Nothing visible — skip the GL pass entirely.
|
||||
if (anyVao == 0)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -155,6 +155,23 @@ public static class RenderingDiagnostics
|
|||
public static bool ProbeGlStateEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_GLSTATE") == "1";
|
||||
|
||||
/// <summary>
|
||||
/// §4 outdoor full-world flap apparatus (2026-06-10) — the decisive probe between the two
|
||||
/// surviving suspects (handoff 2026-06-09 §1): (a) per-instance clip-slot routing under
|
||||
/// outdoor roots, (b) terrain/sky UBO content at draw time — plus the landscape-pass scissor
|
||||
/// box as a third ground truth. When true: RetailPViewRenderer.DrawLandscapeThroughOutsideView
|
||||
/// emits one <c>[clip-route]</c> line (print-on-change) with the outside slice's slot + NDC
|
||||
/// AABB + planes, the CellIdToSlot routing table, the region-SSBO bytes decoded at the routed
|
||||
/// slot, and the terrain-UBO head as uploaded; WbDrawDispatcher.Draw emits one
|
||||
/// <c>[clip-route-disp]</c> line (print-on-change, routed draws only) with the per-slot
|
||||
/// instance histogram exactly as uploaded to binding=3 plus the culled-entity count; and
|
||||
/// GameWindow.DrawRetailPViewLandscapeSlice emits one <c>[clip-route-scis]</c> line
|
||||
/// (print-on-change) with the ACTUAL GL scissor enable + box the landscape pass draws under.
|
||||
/// Throwaway apparatus — strip once §4 ships. Initial state from <c>ACDREAM_PROBE_CLIPROUTE=1</c>.
|
||||
/// </summary>
|
||||
public static bool ProbeClipRouteEnabled { get; set; } =
|
||||
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1";
|
||||
|
||||
/// <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,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue