docs: launch-options reference + the test that keeps it honest

The client reads 161 ACDREAM_* environment variables across 79 files. Only
about 25 were written down, and the audit found the documentation drifting
in both directions: CLAUDE.md still advertised ACDREAM_RUN_SKILL /
ACDREAM_JUMP_SKILL (deleted; skills are server-authoritative now, and the
jump fallback is 300, not the documented 200), while flags with real
side effects had no description at all.

docs/launch-options.md documents every one by lifecycle — production,
command line, measurement, automation, permanent diagnostics, temporary
probes, deprecated, retired — with a mandatory side-effects column. That
column is the point: #432 cost three days of taxed measurements because
ACDREAM_AUTOMATION_ARTIFACT_DIR reads like an output path and also builds
a per-frame diagnostics referee, and ACDREAM_STREAM_RADIUS silently
measures a streaming window production never uses. Rows now say so. Other
surprises the audit surfaced and recorded: ACDREAM_DUMP_SCENERY_Z swaps in
a duplicate scenery-placement path rather than only logging,
ACDREAM_PROBE_VIS silently also enables ACDREAM_PROBE_ENVCELL, and
ACDREAM_DUMP_ENTITY's id list doubles as an unrelated probe's watchlist.

LaunchOptionsDocumentationTests enforces it, because a hand-maintained list
of 161 flags is stale within a week: an undocumented flag fails, and so
does a documented row whose read site was deleted. It scans string literals
rather than GetEnvironmentVariable call shapes — the startup path reads
through an injected delegate, so a call-shaped pattern silently missed
ACDREAM_LIVE, ACDREAM_PAK_PATH and every other production flag. A third
test freezes per-file direct-read debt by exact count (20 files outside the
owner classes), so structure rules 4 and 5 can be paid down but not
regressed.

CLAUDE.md's 94-line env-var section becomes a 16-line pointer, and its
stale test-character paragraph is corrected.

Also fixed, all doc-vs-code mismatches the audit proved:
- RenderingDiagnostics.FrameProfEnabled described a GPU-query self-disable
  that Campaign V slice V11 deleted.
- Two comments named ACDREAM_RENDER_BACKEND as a live co-requisite; it died
  with the OpenGL backend.
- EnvCellRenderer.CollectCellAuditLines and its ACDREAM_A8_AUDIT doc: the
  method had no caller anywhere and its documented caller never existed.

Filed rather than fixed, to keep this a documentation change: #434 (the
DebugPanel/DebugVM surface is never constructed, so ~40 "runtime-toggleable"
comments are false and 35 env reads are unreachable) and #435 (17 temporary
probes outlived their closed investigations; 14 more name no owner).

Full hermetic suite 12,202 passed / 0 failed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-24 10:28:59 +02:00
parent 92999b0101
commit e77dd7c413
9 changed files with 744 additions and 165 deletions

View file

@ -7,8 +7,10 @@ using Silk.NET.Windowing;
namespace AcDream.App.Rendering.Gpu.Vk;
/// <summary>
/// The Vulkan capability-probe and bring-up harness. Reached only when
/// <c>ACDREAM_RENDER_BACKEND=vulkan</c> <b>and</b> <c>ACDREAM_VULKAN_PROBE=1</c>.
/// The Vulkan capability-probe and bring-up harness. Reached when
/// <c>ACDREAM_VULKAN_PROBE=1</c>. (This previously also required
/// <c>ACDREAM_RENDER_BACKEND=vulkan</c>; that variable died with the OpenGL
/// backend at Campaign V and is read nowhere — Vulkan is the only backend.)
///
/// <para><b>What it is for.</b> Answering two questions without starting the
/// client: does this machine pass the Vulkan capability gate, and does the RHI

View file

@ -193,61 +193,6 @@ public sealed partial class EnvCellRenderer :
return (poolTotal, hwm);
}
/// <summary>
/// Phase A8 audit probe (2026-05-28 visual-gate-#1 follow-up).
/// One-shot per (cellId, gfxObjId) pair: dumps batch counts + CullModes +
/// transparency flags + bindless-handle-non-zero status, so the operator
/// can read offline and identify why specific polys (e.g., floors) aren't
/// rendering. Set <c>ACDREAM_A8_AUDIT=1</c> to enable.
/// <para>Returns a deduplicated audit-line list per Render snapshot
/// (one entry per (cellId, gfxObjId) seen in BatchedByCell). The caller
/// (GameWindow EmitEnvCellProbe) prints these and tracks which pairs
/// have already been logged.</para>
/// </summary>
public IReadOnlyList<string> CollectCellAuditLines(HashSet<(uint cellId, ulong gfxObjId)> alreadyLogged)
{
var lines = new List<string>();
lock (_renderLock)
{
var snap = _activeSnapshot;
foreach (var (cellId, gfxDict) in snap.BatchedByCell)
{
foreach (var (gfxObjId, transforms) in gfxDict)
{
var key = (cellId, gfxObjId);
if (alreadyLogged.Contains(key)) continue;
alreadyLogged.Add(key);
var rd = _meshManager.TryGetRenderData(gfxObjId);
if (rd is null)
{
lines.Add($"[a8-audit] cell=0x{cellId:X8} gfx=0x{gfxObjId:X10} instances={transforms.Count} renderData=null");
continue;
}
int totalIdx = 0;
var cullModes = new HashSet<DatReaderWriter.Enums.CullMode>();
int translucent = 0;
int additive = 0;
int zeroHandle = 0;
foreach (var b in rd.Batches)
{
totalIdx += b.IndexCount;
cullModes.Add(b.CullMode);
if (b.IsTransparent) translucent++;
if (b.IsAdditive) additive++;
if (!b.TextureSlot.IsAssigned) zeroHandle++;
}
var cullList = string.Join(",", cullModes);
lines.Add(
$"[a8-audit] cell=0x{cellId:X8} gfx=0x{gfxObjId:X10} instances={transforms.Count} " +
$"isSetup={rd.IsSetup} batches={rd.Batches.Count} totalIdx={totalIdx} " +
$"cull=[{cullList}] translucent={translucent} additive={additive} zeroHandle={zeroHandle}");
}
}
}
return lines;
}
// ---------------------------------------------------------------------------
// Constructor
// Campaign V slice V11: the raw-GL constructor + Initialize(Shader) two-step

View file

@ -215,11 +215,12 @@ public sealed record RuntimeOptions(
// can be exercised on hardware that actually supports everything.
VulkanForcedUnsupportedFeature:
NullIfEmpty(env("ACDREAM_VULKAN_FORCE_UNSUPPORTED")),
// Campaign V slice V6h: with ACDREAM_RENDER_BACKEND=vulkan, run the
// V5/V6c bring-up harness — capability gate plus the synthetic
// verification scenes — instead of the real composition host. A
// diagnostic for "does this machine pass the Vulkan gate, and does
// the backend draw?"; ignored on OpenGL.
// Campaign V slice V6h: run the V5/V6c bring-up harness —
// capability gate plus the synthetic verification scenes — instead
// of the real composition host. A diagnostic for "does this machine
// pass the Vulkan gate, and does the backend draw?". (The former
// ACDREAM_RENDER_BACKEND=vulkan co-requisite died with the OpenGL
// backend at Campaign V; this flag alone gates the harness.)
VulkanCapabilityProbe:
IsExactlyOne(env("ACDREAM_VULKAN_PROBE")),
// Campaign V slice V9: bound the probe harness to a frame budget so

View file

@ -764,10 +764,12 @@ public static class RenderingDiagnostics
/// (swap-to-swap), whole-frame GPU time, per-stage CPU attribution,
/// per-frame allocation counters, reported as one <c>[frame-prof]</c>
/// line every ~5 s. Permanent apparatus (every MP-track gate reads it) —
/// do NOT strip with session probes. The whole-frame GPU query is
/// self-disabled while <c>ACDREAM_WB_DIAG=1</c> (GL forbids nested
/// TimeElapsed queries; WbDrawDispatcher owns per-pass queries under
/// that flag — the 2026-06-23 "separate flags" measurement lesson).
/// do NOT strip with session probes. This paragraph previously claimed
/// the whole-frame GPU query self-disables under <c>ACDREAM_WB_DIAG=1</c>;
/// Campaign V slice V11 deleted that self-disable along with the GL query
/// ring it protected, and the two flags are now independent — see
/// <c>FrameProfiler</c>'s own class doc. Every backend reports GPU time
/// through <c>FrameProfiler.RecordGpuSample</c>.
/// Initial state from <c>ACDREAM_FRAME_PROF=1</c>; runtime-toggleable
/// via the DebugPanel mirror (<c>DebugVM.FrameProf</c>).
/// Spec: docs/superpowers/specs/2026-07-05-modern-pipeline-design.md §5.