From 6cba95047c8a1eecf576ea705c3aedab8e52758d Mon Sep 17 00:00:00 2001 From: Erik Date: Thu, 11 Jun 2026 06:27:44 +0200 Subject: [PATCH] BR-2 task 1: phantom-site probe (ACDREAM_PROBE_PHANTOM) The BR-1 pre-check left the #113 phantom residual with two surviving suspects, both cell-side: (a) flood-admitted cells whose shell draws with a pass-all slice (NoClipSlice fallback when the assembler handed no slot, or an assembler slot-0 scissor-fallback slice), and (b) cell entity buckets drawn unclipped + un-viewcone'd by design. [phantom-shell]: per shell-pass cell, print-on-change - clip-enable state, slot presence, every drawn slice's slot + plane count with PASS-ALL flagged. [phantom-objs]: per object-list cell, print-on-change - entity bucket size. Env-gated ACDREAM_PROBE_PHANTOM=1, zero cost off, throwaway (strip when the phantom closes). Repro protocol: launch with the probe on, stand at the hall bisect spot (world ~216,-108 looking at the AAB3 meeting hall west face) where the phantom is visible, read which mechanism fires for stair cells 0xAAB30100..0x106. Shells pass-all -> BR-2/BR-3 close it; statics -> BR-5 closes it. Build green; App suite green. Co-Authored-By: Claude Fable 5 --- .../Rendering/RetailPViewRenderer.cs | 47 ++++++++++++++++++- .../Rendering/RenderingDiagnostics.cs | 18 +++++++ 2 files changed, 64 insertions(+), 1 deletion(-) diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index 4c95260d..be309602 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -385,7 +385,15 @@ public sealed class RetailPViewRenderer _oneCell.Clear(); _oneCell.Add(cellId); - foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId)) + var slices = GetCellSlicesOrNoClip(clipAssembly, cellId); + + // BR-2 phantom-site probe: which cells draw their shell with a + // pass-all slice (NoClipSlice fallback or assembler slot-0)? + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbePhantomEnabled) + EmitPhantomShellProbe(cellId, slices, clipShells, + hadSlot: clipAssembly.CellIdToViewSlices.ContainsKey(cellId)); + + foreach (var slice in slices) { UseShellClipRouting(cellId, slice); _envCells.Render(WbRenderPass.Opaque, _oneCell); @@ -417,6 +425,11 @@ public sealed class RetailPViewRenderer _oneCell.Clear(); _oneCell.Add(cellId); + // BR-2 phantom-site probe: entity buckets draw unclipped + + // un-viewcone'd by design — log the per-cell exposure. + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbePhantomEnabled) + EmitPhantomObjsProbe(cellId, bucket.Count); + UseIndoorMembershipOnlyRouting(); DrawEntityBucket(ctx, bucket, _oneCell); @@ -425,6 +438,38 @@ public sealed class RetailPViewRenderer } } + // BR-2 phantom-site probe state: print-on-change per cell so the log stays + // diffable while the condition persists. Throwaway apparatus — strip when + // the #113 phantom residual closes (plan §BR-2). + private readonly Dictionary _phantomShellSig = new(); + private readonly Dictionary _phantomObjsSig = new(); + + private void EmitPhantomShellProbe(uint cellId, ClipViewSlice[] slices, bool clipShells, bool hadSlot) + { + var sb = new System.Text.StringBuilder(96); + sb.Append(clipShells ? "clip=on" : "clip=OFF"); + sb.Append(hadSlot ? " slot=yes" : " slot=NONE(pass-all)"); + sb.Append(" slices=["); + for (int i = 0; i < slices.Length; i++) + { + if (i > 0) sb.Append(','); + sb.Append(slices[i].Slot).Append(':').Append(slices[i].Planes.Length).Append("pl"); + if (slices[i].Slot == 0) sb.Append("(PASS-ALL)"); + } + sb.Append(']'); + var sig = sb.ToString(); + if (_phantomShellSig.TryGetValue(cellId, out var prev) && prev == sig) return; + _phantomShellSig[cellId] = sig; + Console.WriteLine($"[phantom-shell] cell=0x{cellId:X8} {sig}"); + } + + private void EmitPhantomObjsProbe(uint cellId, int bucketCount) + { + if (_phantomObjsSig.TryGetValue(cellId, out var prev) && prev == bucketCount) return; + _phantomObjsSig[cellId] = bucketCount; + Console.WriteLine($"[phantom-objs] cell=0x{cellId:X8} entities={bucketCount} (drawn unclipped, no viewcone)"); + } + private static ClipViewSlice[] GetCellSlicesOrNoClip( ClipFrameAssembly clipAssembly, uint cellId) diff --git a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs index f219fbb4..7e77496b 100644 --- a/src/AcDream.Core/Rendering/RenderingDiagnostics.cs +++ b/src/AcDream.Core/Rendering/RenderingDiagnostics.cs @@ -198,6 +198,24 @@ public static class RenderingDiagnostics public static bool ProbePortalChurnEnabled { get; set; } = Environment.GetEnvironmentVariable("ACDREAM_PROBE_PORTAL_CHURN") == "1"; + /// + /// BR-2 phantom-site probe (2026-06-11; plan + /// docs/plans/2026-06-11-building-render-port-plan.md §BR-2 first + /// task). The BR-1 pre-check proved the #113 phantom residual cannot be + /// GfxObj portal fills (never extracted); the surviving suspects are + /// cell-side. When true, RetailPViewRenderer emits, print-on-change + /// per cell: [phantom-shell] — per shell-pass cell, the clip-enable + /// state and each drawn slice's slot + plane count, flagging the pass-all + /// cases (NoClipSlice fallback for slot-less cells; assembler slot-0 + /// scissor fallback) — and [phantom-objs] — per object-list cell, + /// the entity-bucket size drawn unclipped/un-viewcone'd. Reproducing the + /// phantom with this on pins which mechanism draws it (shells → BR-2/BR-3; + /// statics → BR-5). Throwaway apparatus — strip when the phantom closes. + /// Initial state from ACDREAM_PROBE_PHANTOM=1. + /// + public static bool ProbePhantomEnabled { get; set; } = + Environment.GetEnvironmentVariable("ACDREAM_PROBE_PHANTOM") == "1"; + // Cell-change gate for EmitVis. The probe fires once per distinct root cell // so launch.log stays readable under motion (the per-frame call is a no-op // when the root is unchanged). Sentinel 0 = "no root yet" — the first real