fix #435 (part 1): delete 17 probes that outlived their closed investigations

Each of these was temporary apparatus added to chase one bug, and each was
supposed to be deleted in the commit that fixed it. Fourteen closed issues
later they were still here: #337's support/wire-mesh trio, #171's sticky
timeline, #119's viewer and entity dumps, #113's phantom probe, and a dozen
more. 3,493 lines removed; the client now reads 144 environment variables
instead of 161, and 47 temporary probes remain instead of 64.

This is not only tidying. Every probe leaves a branch on its hot path when
unset, several re-read the environment per call rather than caching, and
the volume buries the diagnostics that are actually load-bearing. It is
also a headless correctness matter: HeadlessStaticStateAudit reflects over
PhysicsDiagnostics' flags to refuse a multi-session host when any is set,
and cannot see probes that live outside that owner.

Four files went entirely — WalkMissDiagnostic.cs, CollisionMeshWireframe.cs
and two test files whose only subject was a deleted probe.
TransitionTypes.SetContactPlane also sheds its CallerMemberName /
CallerLineNumber parameters, which existed solely for #337's cpSrc=
attribution and carried the instruction to strip them with the probe
family; no call site passed them, so no behavior changes. F2's collision
overlay survives and reverts to its proxy-cylinder form, which is what
removing the ACDREAM_WIRE_MESH upgrade means.

LaunchOptionsDocumentationTests earned its keep here: it refused the
deletion until docs/launch-options.md moved the 17 rows into Retired and
the frozen direct-read counts came down (PhysicsEngine.cs to zero,
TransitionTypes.cs 3 to 2). The documentation could not drift during a
cleanup this wide.

The 14 probes that name no owning issue are deliberately NOT deleted.
Nothing records when they became safe to remove, and guessing is how a
future investigation loses apparatus it needed; #435 stays open for their
attribution.

Full hermetic suite 15,321 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-24 11:41:20 +02:00
parent 05bfe8d162
commit 0c5057c9ff
34 changed files with 57 additions and 3495 deletions

View file

@ -99,33 +99,6 @@ public static class RenderingDiagnostics
public static bool ProbeVisibilityEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIS") == "1";
/// <summary>
/// #119-residual viewer/flood capture (2026-06-11): one <c>[viewer]</c>
/// line per CHANGE of (root cell, flood size, OutsideView poly count,
/// player cell), with the projection EYE at mm precision on every line —
/// the capture half of the tower-ascent capture→replay loop
/// (TowerAscentReplayTests replays the captured pairs deterministically).
/// Light: silent while the visibility state is stable; a tower climb
/// emits a few dozen lines. Initial state from
/// <c>ACDREAM_PROBE_VIEWER=1</c>.
/// </summary>
public static bool ProbeViewerEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_VIEWER") == "1";
/// <summary>
/// #131 (2026-06-12) outside-stage dynamics probe. When true, the renderer
/// emits one <c>[outstage]</c> line per CHANGE of the outside-stage
/// routing + per-slice cone verdict set under an interior root (which
/// outdoor dynamics were routed to the landscape slice, which survived the
/// slice viewcone), and GameWindow emits one <c>[outstage-pt]</c> line per
/// change of the slice Scene-particle id set + matched-emitter count.
/// Built for the portal-swirl-missing-through-doorway capture. Light:
/// silent while the set is stable. Initial state from
/// <c>ACDREAM_PROBE_OUTSTAGE=1</c>.
/// </summary>
public static bool ProbeOutStageEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_OUTSTAGE") == "1";
/// <summary>
/// Phase U.4c (2026-05-31) flap-convergence probe. When true, the portal
/// visibility pass emits, EVERY frame the camera root is an indoor cell, a
@ -144,21 +117,6 @@ public static class RenderingDiagnostics
public static bool ProbeFlapEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_FLAP") == "1";
/// <summary>
/// Issue #78 (2026-05-31) cell-shell render probe. When true,
/// <c>EnvCellRenderer.Render</c> emits one <c>[shell]</c> line per opaque-pass
/// call: per visible (filtered) cell — is it present in the prepared snapshot,
/// how many gfxObjs + instances, and per-gfxObj batch count / index count /
/// translucent / zero-bindless-handle (missing texture) — plus the pass totals.
/// This directly answers WHY interior walls/ceilings don't appear: no geometry
/// prepared for the cell (cell absent / 0 instances), drawn-but-invisible
/// (zeroHandle / translucent against the clear color), or prepared+drawn (so the
/// fault is elsewhere — depth/occlusion). Throwaway apparatus — strip once the
/// indoor-enclosure render is fixed. Initial state from <c>ACDREAM_PROBE_SHELL=1</c>.
/// </summary>
public static bool ProbeShellEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_SHELL") == "1";
/// <summary>
/// Flap root-cause apparatus (2026-06-07). When true, the indoor render path emits ONE
/// <c>[pv-input]</c> line per frame with the EXACT PortalVisibilityBuilder.Build inputs at HIGH
@ -202,21 +160,6 @@ public static class RenderingDiagnostics
public static bool ProbeClipRouteEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CLIPROUTE") == "1";
/// <summary>
/// #105 white-indoor-textures apparatus (2026-06-10). When true, <c>WbMeshAdapter.Tick</c>
/// emits one <c>[tex-flush]</c> line whenever the staged-texture-update picture changes:
/// pending layer updates across all shared atlases BEFORE and AFTER the per-frame
/// <c>ObjectMeshManager.GenerateMipmaps()</c> flush, plus arrays-with-pending / total-array
/// counts. The broken contract this pins: <c>TextureAtlasManager.AddTexture</c> only STAGES
/// pixel data (PBO + pending list); without the per-frame flush (WB GameScene.cs:975) the
/// data never reaches the GL texture and the batch samples undefined content behind a valid
/// bindless handle — the classic white walls. A healthy run shows <c>after=0</c> on every
/// line; a stuck <c>before==after&gt;0</c> at standstill is the #105 mechanism live.
/// Initial state from <c>ACDREAM_PROBE_TEXFLUSH=1</c>.
/// </summary>
public static bool ProbeTexFlushEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_TEXFLUSH") == "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,
@ -228,52 +171,6 @@ public static class RenderingDiagnostics
public static bool ProbePortalChurnEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_PORTAL_CHURN") == "1";
/// <summary>
/// BR-2 phantom-site probe (2026-06-11; plan
/// <c>docs/plans/2026-06-11-building-render-port-plan.md</c> §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, <c>RetailPViewRenderer</c> emits, print-on-change
/// per cell: <c>[phantom-shell]</c> — 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 <c>[phantom-objs]</c> — 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 <c>ACDREAM_PROBE_PHANTOM=1</c>.
/// </summary>
public static bool ProbePhantomEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_PHANTOM") == "1";
/// <summary>
/// #133 A7 (2026-06-13) dungeon-lighting objective probe. When true,
/// the per-frame scene-lighting build emits ONE <c>[light]</c> line
/// roughly every second (wall-clock rate-limited like WB-DIAG) via
/// <see cref="EmitLight"/>:
/// <code>
/// [light] insideCell=&lt;bool&gt; ambient=(r,g,b) sun=&lt;intensity&gt;
/// registeredLights=&lt;N&gt; activeLights=&lt;uCellAmbient.w&gt; playerCell=0x&lt;id&gt;
/// </code>
/// This is the self-verification signal for the dungeon-dim question:
/// <list type="bullet">
/// <item><description><c>insideCell=true ambient=(0.20,0.20,0.20) sun=0</c>
/// confirms the indoor branch fired (retail flat ambient, sun killed).</description></item>
/// <item><description><c>registeredLights</c> is the count of dat-baked
/// point/spot lights (<c>Setup.Lights</c>) registered with the
/// <c>LightManager</c> — if this is 0 in a dungeon, the cell's static
/// objects carry no baked torches (so the only illumination IS the
/// 0.2 ambient → dim).</description></item>
/// <item><description><c>activeLights</c> is <c>uCellAmbient.w</c> — the
/// shader's active-slot count, which INCLUDES the (zeroed) sun slot
/// indoors. So <c>activeLights=1 registeredLights=0</c> = "only the dead
/// sun slot, no torches in range".</description></item>
/// </list>
/// Output-only, inert when off. Initial state from <c>ACDREAM_PROBE_LIGHT=1</c>.
/// </summary>
public static bool ProbeLightEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_LIGHT") == "1";
/// <summary>
/// A7.L1 (2026-07-06) light-pool SET-COMPOSITION probe — the apparatus the
/// <c>[light]</c> counts could not provide (the #176/#177 discriminator: the bug
@ -496,94 +393,8 @@ public static class RenderingDiagnostics
/// </summary>
internal static void ResetVisibilityProbeForTests() => _lastVisRootCellId = 0;
// Wall-clock rate-limit gate for EmitLight. Ticks (100 ns) is plenty —
// we only need ~1 Hz and avoid a Stopwatch allocation/field. Sentinel 0
// = "never emitted" so the first call always fires.
private static long _lastLightEmitTicks;
private const long LightEmitIntervalTicks = 10_000_000; // 1 s in 100-ns ticks
/// <summary>
/// #133 A7 — emit ONE rate-limited <c>[light]</c> line describing the
/// current scene-lighting state, followed (when <paramref name="lights"/>
/// is supplied) by up to three <c>[light-detail]</c> lines for the nearest
/// ACTIVE point/spot lights. Cheap no-op when
/// <see cref="ProbeLightEnabled"/> is false; otherwise fires at most
/// once per second. Pull the values from the spot where
/// <c>GameWindow.UpdateSunFromSky</c> set <c>Lighting.CurrentAmbient</c>
/// / <c>Lighting.Sun</c> and where <c>SceneLightingUbo.Build</c> computed
/// the active-slot count.
/// <para>
/// The <c>[light-detail]</c> lines are the answer to the "candle-spotlight"
/// question — they expose each torch's REAL dat-derived runtime values
/// (<c>range=</c> Falloff metres, <c>intensity=</c>, <c>cone=</c> radians,
/// <c>color=</c>, <c>distToViewer=</c>) so it is visible in launch.log
/// whether dungeon torches are tiny-range points or wide cones and at what
/// intensity — without a screenshot:
/// <code>
/// [light-detail] kind=Point range=&lt;Falloff m&gt; intensity=&lt;I&gt; cone=&lt;rad&gt; color=(r,g,b) distToViewer=&lt;m&gt;
/// </code>
/// </para>
/// </summary>
/// <param name="insideCell">The <c>playerInsideCell</c> value driving the indoor branch.</param>
/// <param name="ambientR">Cell ambient red (xyz of <c>uCellAmbient</c>).</param>
/// <param name="ambientG">Cell ambient green.</param>
/// <param name="ambientB">Cell ambient blue.</param>
/// <param name="sunIntensity">The sun <c>LightSource.Intensity</c> (0 indoors).</param>
/// <param name="registeredLights">Total point/spot lights registered with the LightManager.</param>
/// <param name="activeLights"><c>uCellAmbient.w</c> — shader active-slot count (includes the zeroed sun slot indoors).</param>
/// <param name="playerCellId">The player's current cell id (0 if unresolved → outside).</param>
/// <param name="lights">The ticked <c>LightManager</c> (its <c>Active</c> list, sorted nearest-first by the
/// just-completed Tick). When non-null, drives the <c>[light-detail]</c> lines. Optional so existing call
/// sites / tests that only want the aggregate line keep compiling.</param>
public static void EmitLight(bool insideCell,
float ambientR, float ambientG, float ambientB,
float sunIntensity,
int registeredLights,
int activeLights,
uint playerCellId,
AcDream.Core.Lighting.LightManager? lights = null)
{
if (!ProbeLightEnabled) return;
long now = DateTime.UtcNow.Ticks;
if (_lastLightEmitTicks != 0 && (now - _lastLightEmitTicks) < LightEmitIntervalTicks)
return;
_lastLightEmitTicks = now;
var ci = System.Globalization.CultureInfo.InvariantCulture;
Console.WriteLine(string.Format(ci,
"[light] insideCell={0} ambient=({1:0.###},{2:0.###},{3:0.###}) sun={4:0.###} registeredLights={5} activeLights={6} playerCell=0x{7:X8}",
insideCell, ambientR, ambientG, ambientB, sunIntensity,
registeredLights, activeLights, playerCellId));
// #133 A7 (2026-06-13) — per-light detail for the "spotlight bubble"
// question. Dump the actual runtime dat-derived values of the nearest
// ~3 ACTIVE point/spot lights so the real Falloff/Intensity/ConeAngle
// are visible in launch.log (are torch ranges 1m or 10m? points or
// spots? what intensity?). The sun (Directional, slot 0) is skipped —
// it carries no Range/cone meaning. DistSq is already cached by
// LightManager.Tick this frame, so the active list is sorted nearest-
// first; we just take the first few non-directional entries.
if (lights is null) return;
var active = lights.Active;
int shown = 0;
const int MaxDetail = 3;
for (int i = 0; i < active.Length && shown < MaxDetail; i++)
{
var ls = active[i];
if (ls is null) continue;
if (ls.Kind == AcDream.Core.Lighting.LightKind.Directional) continue;
float dist = ls.DistSq >= 0f ? MathF.Sqrt(ls.DistSq) : 0f;
Console.WriteLine(string.Format(ci,
"[light-detail] kind={0} range={1:0.###} intensity={2:0.###} cone={3:0.####} color=({4:0.###},{5:0.###},{6:0.###}) distToViewer={7:0.###} owner=0x{8:X8} cell=0x{9:X8} dyn={10}",
ls.Kind, ls.Range, ls.Intensity, ls.ConeAngle,
ls.ColorLinear.X, ls.ColorLinear.Y, ls.ColorLinear.Z, dist,
ls.OwnerId, ls.CellId, ls.IsDynamic ? 1 : 0));
shown++;
}
}
// Wall-clock rate-limit gate for EmitIndoorLight (shares the 1 s interval).
private static long _lastIndoorLightEmitTicks;
@ -694,27 +505,6 @@ public static class RenderingDiagnostics
/// </summary>
public static bool IsEnvCellId(ulong id) => (id & 0xFFFFu) >= 0x0100u;
/// <summary>
/// #119 tower-staircase decisive probe (2026-06-11). Comma-separated
/// Setup / GfxObj source ids (hex, optional 0x prefix) from
/// <c>ACDREAM_DUMP_ENTITY</c>. Any <c>WorldEntity</c> whose
/// <c>SourceGfxObjOrSetupId</c> is in this set emits:
/// (a) a <c>[dump-entity] HYDRATE</c> dump at MeshRef construction time
/// (<c>GameWindow.BuildInteriorEntitiesForStreaming</c>) — per-part
/// placement-frame translations + dropped-part accounting — discriminating
/// hydration-time corruption (H-A: SetupMesh.Flatten identity fallback /
/// silent gfx-null part drops under degraded dat reads);
/// (b) a <c>[dump-entity] DRAW</c> dump in <c>WbDrawDispatcher</c> at first
/// draw — live MeshRefs translations + Tier-1 classification cache state —
/// re-emitted compactly whenever that state changes (H-B: stale/partial
/// cached batch set); and
/// (c) rate-limited <c>[dump-entity] WALK-REJECT</c> lines when the
/// dispatcher's walk filters the entity out (absence-of-draw attribution).
/// Empty set = probe off; every call site early-outs on <c>Count == 0</c>.
/// </summary>
public static IReadOnlySet<uint> DumpEntitySourceIds { get; } =
ParseDumpEntityIds(Environment.GetEnvironmentVariable("ACDREAM_DUMP_ENTITY"));
/// <summary>
/// Parse the <c>ACDREAM_DUMP_ENTITY</c> value: comma-separated hex ids,
/// optional 0x prefix, whitespace tolerated, malformed segments ignored
@ -794,52 +584,4 @@ public static class RenderingDiagnostics
/// </summary>
public static string? FrameHistoryPath { get; } =
Environment.GetEnvironmentVariable("ACDREAM_FRAME_HISTORY");
// ── #337 collision-mesh wireframe (2026-08-06 — TEMPORARY) ──────────────
//
// The F2 collision overlay already existed, but for a BSP object it drew a
// proxy cylinder sized from the REGISTERED BROADPHASE RADIUS. That shows
// where the collision system thinks the object roughly is; it cannot show
// where the collision SURFACES are, which is the only thing that answers
// "is the collision geometry where the visual geometry is". The knobs
// below turn F2 into the real answer: the actual physics-BSP polygon
// edges, in world space, next to the same object's visual mesh box.
//
// Off by default, so F2 keeps its old cheap behaviour for anyone who wants
// it and this costs nothing until asked for.
/// <summary>
/// When true, the F2 collision overlay draws each nearby object's REAL
/// physics-BSP polygon edges (cyan) and, beside them, the same object's
/// visual mesh bounding box (magenta), plus the terrain triangle under the
/// player (yellow). Any separation between the cyan surfaces and the
/// object you can see is the "collision is not where the visual is"
/// defect, read directly off the screen instead of inferred from a log.
/// Initial state from <c>ACDREAM_WIRE_MESH=1</c>.
/// TEMPORARY — strip with the #337 probe family.
/// </summary>
public static bool CollisionMeshWireframeEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_WIRE_MESH") == "1";
/// <summary>
/// Radius in metres around the player within which
/// <see cref="CollisionMeshWireframeEnabled"/> resolves polygon geometry.
/// A whole landblock of rock is far more geometry than a line list wants;
/// 30 m covers everything you can wedge against. Override with
/// <c>ACDREAM_WIRE_RADIUS=&lt;metres&gt;</c>.
/// </summary>
public static float CollisionMeshWireframeRadius { get; set; } =
ParsePositiveFloat(
Environment.GetEnvironmentVariable("ACDREAM_WIRE_RADIUS"),
fallback: 30f);
private static float ParsePositiveFloat(string? raw, float fallback)
=> float.TryParse(
raw,
System.Globalization.NumberStyles.Float,
System.Globalization.CultureInfo.InvariantCulture,
out float value)
&& value > 0f
? value
: fallback;
}