acdream/docs/reports/2026-07-29-overnight-report.md
Erik b023ac95f0 docs: the overnight consolidation report, open questions first
Adds `docs/reports/2026-07-29-overnight-report.md` and brings the Campaign V
report page's status section in line with what is actually true.

The report leads with what needs the user, because most of it is genuinely
blocked rather than merely unfinished: the #259 reboot and the five V11 runtime
gates behind it, the enum campaign's three decisions (adopt `WeenieError`
wholesale? adopt retail's `SoundId` table or record our subset as a divergence?
re-clone the reference repos), the wire audit's one instrumented session, and
the walked-portal follow-up for #256/#257. Then what landed, with numbers, then
Campaign V's final state.

Two notes worth surfacing rather than burying. `references/` is empty **in the
main checkout**, not just in a worktree — which is why 456 of the enum
campaign's 864 property members are single-sourced, and why CLAUDE.md's
"cross-reference at least two of them" is currently unexecutable as written.
And two `MEMORY.md` entries index research documents that exist nowhere: not in
the tree, not under any ref, and the memory directory has no `research/` folder
at all. Two separate sessions hit that wall tonight and both regenerated from
scratch. User-level memory is out of my reach, so it is recorded as a
needs-user item with the two documents that supersede them.

The Campaign V page said V8 was running, V9 pending and V10/V11 ahead. All four
have landed. Its status section now records the deletion (204 files, +1,870 /
-27,607), the three findings worth keeping — Chorizite survives on the pak
format rather than on `IUniformBuffer`, `Studio/SampleData.cs` was production
code, and `WbMeshAdapter.Dispose()`'s GPU-work wait had been silently dead since
V6a — and, plainly, that the runtime gates did not run because this machine
cannot create a Win32 Vulkan surface for any process. A gate that could not run
is not a gate that passed. The #248 and #250 entries are updated to DONE with
their outstanding halves named.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-29 03:54:43 +02:00

21 KiB
Raw Permalink Blame History

Overnight consolidation — 2026-07-29

Branch: claude/git-sync-status-5fb1d2 Started at: d312bd2f (Campaign V slice V11 landed; OpenGL deleted) Sessions merged: the enum verification campaign and the wire-stack audit Machine constraint: #259 — Win32 Vulkan surface creation is broken process-wide, so no client was launched. Everything below is build, test and documentation.


1. What needs you

Open questions first, because several of them gate work that is otherwise ready to start. Nothing here is blocked on more analysis — each one needs a decision or a machine that can make a window.

1.1 Reboot the machine, then run five gates

vulkaninfo --summary — a Khronos tool containing no acdream code — fails at vkGetPhysicalDeviceSurfaceCapabilitiesKHR with ERROR_UNKNOWN. Vulkan itself is healthy (both adapters enumerate and report 1.4); Win32 surface creation is what is broken, and it is broken for every process on the box. It was bisected to a pre-V11 commit whose offline capture had succeeded three hours earlier, so it is transient driver/compositor state of the class a reboot clears.

V11 is committed and statically green. Its runtime evidence is outstanding, and this is the exact outstanding list from campaign §5.5.24:

Gate Command
Offline pixel gate (VK self-differential) tools/run-offline-pixel-gate.ps1 -Out artifacts/v11-post -Baseline artifacts/v11-pre -SkipBuild
Repeat connected gate tools/run-repeat-connected-gate.ps1 -Runs 3
Connected world-lifecycle route tools/run-connected-world-lifecycle-gate.ps1
Validation proven-loaded, zero errors any connected launch with ACDREAM_DEVTOOLS=1
Working-set re-measure the offline scene, once it renders

The pre-deletion baseline for the self-differential is already on disk at artifacts/v11-pre, captured before the deletion. The left-hand side of the comparison survived the fault; only the right-hand side is missing.

Also now runtime-outstanding: #248's near-plane fix landed tonight with a unit test, but its other acceptance criterion — unchanged culling in the offline pixel gate and the connected route — needs the same window. The change can only tighten culling toward the true frustum, which the issue had already established is the safe direction, but it has not been seen running.

1.2 Three decisions the enum campaign could not make

The campaign explicitly refused to guess on these and recorded them rather than inventing an answer. All three are yours.

  1. WeenieError — adopt 362 server-side status codes wholesale? acdream has 16 members; the catalog's StatusMessage has 372, with 10 shared and zero conflicts. Retail's charError (26 members) is a different enum — character-creation errors only — so retail cannot arbitrate this one. Register row AP-15 already tracks that our translation table covers only ~30 common codes. Ideally paired with retail's string_table.bin for the real sentences.
  2. SoundId — adopt retail's table, or record the subset as a divergence? Ours is a curated 23-member local subset of retail's SoundType (206), with acdream-local names (FootstepGrass, BuffApplied) that do not correspond 1:1 to retail members. Either adopt wholesale, or give it a register row. Neither was done.
  3. Re-clone the reference repos. Five of six are empty — and this is true of the main checkout, not just a worktree. It has two concrete costs. The enum campaign left 456 of 864 property members single-sourced (transcribed from the catalog, no independent attestation) that ACE and Chorizite would promote to two-oracle confirmed. And CLAUDE.md's "cross-reference at least two of them" is currently unexecutable as written, so every session re-solves it privately — four duplicate ACE copies under %TEMP% are the evidence. Working copies exist at C:\Users\erikn\source\repos\client\ACE and C:\Users\erikn\source\repos\holtburger; Chorizite.ACProtocol was not found anywhere on disk and needs a genuine re-clone.

A fourth, cheaper lead the campaign flagged but could not pursue: the client DAT's own EnumMapper file type is a genuinely retail oracle for property names, and acclientlib already has a reader for it. That is the most promising unexplored route to attesting the single-sourced members without any re-clone.

1.3 One instrumented connected session

The wire audit's highest-leverage next action, and it needs a connected client:

ACDREAM_DUMP_OPCODES=1

on an otherwise normal Coldeve session. The audit catalogued all 349 opcodes but could not rank the 128 MISSING rows, because nothing tells it which of them the server actually sends during play. One environment variable converts "128 missing parsers, unknown priority" into a real work order. This is the cheapest high-value item on the board.

1.4 Walked-portal follow-up for #256 and #257

Both were observed in the same long live Coldeve session with repeated town-portal-network transits, and both are HIGH:

  • #256 — server-spawned signs and portals go visually missing after repeated transits while remaining interactive, so the entity is alive in the object table and only its render projection is gone.
  • #257 — working set balloons to ~1.5 GB against the ~930 MiB neighborhood the campaign baselines measured, suggesting per-transit accumulation.

Neither has been reproduced since, and neither can be worked without a client. Both are marked "backend attribution pending" — whether they are Vulkan-era or pre-existing is unknown, and a walked portal-network session is the only thing that settles it. Worth doing in the same sitting as §1.1, since the machine will already be up.

1.5 Two MEMORY.md entries point at documents that do not exist

Out of my reach to fix — user-level memory lives outside this repo — so flagging it here instead.

claude-memory/MEMORY.md indexes research/2026-06-04-property-enum-divergence.md (the "929 values across 7 enums" ledger) and research/2026-06-04-magic-number-audit.md. Neither exists — not in the working tree, not under any ref (git log --all --diff-filter=A finds no add), and the memory directory has no research/ subfolder at all. The same pattern holds for the 2026-06-04 combat-math and wire-catalog drops.

This has a real cost, already paid twice tonight. The enum campaign could not reproduce or audit the 929 figure and regenerated from scratch, arriving at 864 property members; the wire audit hit the identical wall. A link that silently resolves to nothing is worse than no link, because a future session plans around a document that does not exist. Suggest either restoring them or repointing those index entries at the two documents that landed tonight, which supersede them:

  • docs/research/2026-07-29-enum-verification-campaign.md
  • docs/research/2026-07-29-wire-stack-audit.md

2. What landed overnight

Six commits on claude/git-sync-status-5fb1d2, d312bd2fHEAD.

2.1 The enum verification campaign (merged)

github/overnight/enums (c19680fd, 5 commits) merged with no conflicts. It was cut at b70b9832, before the deletion, and its subject turned out to be disjoint from V11's: the campaign lives entirely in AcDream.Core and its tests, while V11 emptied AcDream.App. Not one file was touched by both.

  • 17 files, +3,767 / 27 lines
  • +597 tests, verified exactly — the Properties namespace alone runs 597
  • Names for AC's seven property tables, verified against two oracles
  • Corrections to DamageType's rotated bits and ItemType's shifted craft ladder
  • The retail members the equipment and physics enums were missing
  • Names for AmmoType, CombatUse and ItemUseable

2.2 The wire-stack audit (merged)

github/overnight/wire-audit (41f74fcd, 6 commits), same base, one conflict — see §2.3.

  • 14 files, +2,068 / 38 lines
  • +59 tests; AcDream.Core.Net.Tests went 600 → 659
  • Three real parser fixes: ranged speech carries a range float the parser was eating; a chat type that is never sent was silently dropping every transient string on it; xpSpent is a dword on the wire where we were writing eight bytes
  • The transport flag word pinned against ACE across all 23 bits
  • Golden fixtures generated from ACE's own writer rather than hand-typed hex
  • The audit document, covering all 349 opcodes

2.3 The one merge conflict: #255, reconciled

Both branches reopened #255 — the RetailDatLoader concurrency tests that measure the thread pool rather than the loader — on the same day, from different trees, without knowing about each other.

Neither is a duplicate. The V11 closeout saw 2 failures in 5 complete-solution Release runs on the post-deletion tree; the wire-audit session saw 2 in 4 on the pre-deletion tree; both saw 124/124 in isolation every time. They independently reached the same conclusion — TaskCreationOptions.LongRunning is a hint, not a guarantee — and independently proposed the same fix, a rendezvous inside the read stub.

Resolved by merging the two notes into one issue with both evidence sets kept as labelled subsections, rather than one overwriting the other. Four failures across nine runs on two trees is a materially stronger case than either half, and the agreement between two blind observations is the part worth preserving. No assertion was weakened; the fix itself remains open.

My own 20-run baseline tonight independently reproduced it a third time — 4 failures in 20 complete-solution runs — which is consistent with both.

2.4 #248 — the near plane is col3, not col4 + col3

FrustumPlanes.FromViewProjection extracted the near plane with the Gribb-Hartmann form written for OpenGL's [-1,1] clip-space z range, against projections from Matrix4x4.CreatePerspectiveFieldOfView, whose range is [0,1]. Under [-1,1] the near plane is clip.z = -clip.w, which is col4 + col3; under [0,1] it is clip.z = 0, which is col3 alone.

The error put the effective near threshold at -n·f/(2f-n) — about 0.5 m where the retail chase camera asks for 1.0 m — which only ever kept geometry the true frustum would have dropped. Harmless, and still wrong; it is the same mistake that was visible in PortalProjection, where it culled the cell behind a doorway the camera stood close to.

Pinned by a theory over four near/far pairs asserting the extracted plane is unit length, faces down -Z, and stands off the eye by exactly the camera's near value. The test was run against the old formula before commit and fails all four cases, so it measures the fix rather than merely accompanying it. The far plane is unchanged under both conventions and is pinned alongside so a later edit cannot drift it.

Status: DONE for the unit-test half of its acceptance criterion; the gate half is in §1.1.

2.5 #250 — fixed at the measurement, and the family was larger than four

The four named members shared one root: the measured window was never the warmed path. This had been dismissed as inherent noise in GC.GetAllocatedBytesForCurrentThread three separate times. It is not noise.

Test Warmup Measured window
UiDatFontTests 1 call a 10,000-iteration loop written inline
RenderFrameProductTests 8 calls a 1,000-iteration loop written inline
CurrentRenderSceneOracleTests 1 call a 10,000-iteration loop written inline
ArchRenderSceneTests Apply(registrations) Apply(**updates**) — a different switch arm

Two mechanisms follow. A test method is JIT-compiled at tier 0 like anything else, and a long-running loop in tier-0 code is replaced mid-flight by on-stack replacement, which compiles on the thread running the loop — so its bookkeeping is charged to the window being measured. That is the loop-shaped ones. And ArchRenderSceneTests warmed one arm of a switch and measured the other, so the measured call was the first ever into ApplyUpdate and paid that arm's tier-0 JIT, type loads and static initialisation inside the window; RenderFrameProductTests warmed 8 times, below the tier-0 call-counting threshold of 30, so promotion was still pending when measurement began.

That also explains the signature nobody could account for — clean in isolation, failing about one run in three. Alone, the process is quiet and the runtime has finished before the assertion arrives. Alongside eight other test assemblies, tier-0 compilation never stops, the call-counting delay is re-armed continually, and the work slides into the window.

The fix is tests/AcDream.App.Tests/ZeroAllocationProbe.cs. It invokes the step many times before measuring anything, then measures windows that run the same already-warmed loop over the same already-taken path. Each window is a batch of 32 invocations and it reports the minimum across 4 of them. Both halves are load-bearing: the minimum excludes a one-time cost, and the batch is what keeps the assertion as strong as the loops it replaces — minimising over single invocations would report zero for a path that allocates every tenth call. I had written it that way first and the apparatus test caught it.

The bound is untouched: exactly zero, no tolerance, no retry, no assertion relaxed. ZeroAllocationProbeTests proves the apparatus can still fail — a step allocating every call reads above zero and does throw, a first-invocation cost reads as zero, a cost every tenth call is caught, and the one stated limit (the batch must cover the period) is pinned as a test rather than left as prose.

The family was larger than four. The first pass converted only the named four. A 20-run baseline disproved "none of the others has been observed failing" within minutes — LiveEntityRuntimeTests failed in run 2, StaticRenderProjection JournalTests in runs 14 and 18, both the same shape, neither previously recorded. Run 19 was sharper still: the issue named SurfaceOverrideFingerprint_DictionaryHotPathAllocatesNothing, and the first pass had converted a different test in that same file. Matching by file was not matching by test.

All ten strict-zero sites are now on the probe. Two came out stricter rather than merely steadier:

  • StaticRenderProjectionJournalTests was measuring a synchronise whose journal does not coalesce. Repeating it grew the journal by 1,000 entries per call — 192,000 by the end of a probe run — so the steady state it claimed to test did not exist, and the single-call window had been hiding that. Its step is now the whole frame cycle, synchronise and drain, putting DrainTo inside the measured window for the first time.
  • RetailInboundEventDispatcherTests asserted a hard-coded 1,001 callbacks; it now counts its own dispatches and pins the callback count against them.

Four sites asserting a tolerance rather than zero were deliberately left alone (CellViewDedupTests ×2, PortalProjectionTests ×2) — their ceilings already absorb this noise and none has flaked. PortalProjectionTests is worth revisiting: its ceiling exists explicitly to tolerate "a tiered-JIT/ArrayPool bookkeeping transition ... to the first measured batch", which is exactly what the probe removes, so it could probably be tightened to zero now.

Acceptance: the issue asks for 20 consecutive clean Release runs of the App suite. See §4 for the count actually achieved and how to read it.

2.6 Worktree sweep

Removed 9 finished worktrees — the two temp ones (%TEMP%\claude\prerevert, %TEMP%\claude\v4a-verify-base) and seven agent-a* paths including the two whose branches were merged tonight. Pruned afterward. codex/*, .worktrees/* and the named claude/* worktrees were not touched.

Two were deliberately left in place, because "finished" is load-bearing and neither is:

  • agent-a1589ee2f5360c7a2 — has an unresolved merge conflict (UU src/AcDream.Core/Physics/TransitionTypes.cs), three other modified physics files, and an untracked new EdgeSlideTests.cs. This is in-progress L.2.3 step-up/step-down work belonging to some other session, not tonight's.
  • agent-af83539515c87d43e — has an uncommitted 1,073-line edit to WbDrawDispatcher.cs (961/+112), an in-progress GL-removal pass. V11 has since landed the equivalent, so this is probably superseded scratch — but it is a thousand lines of uncommitted work and deleting it is not reversible.

Both are yours to keep or discard.


3. Campaign V — final state

The campaign is complete and the tree is statically green. Its runtime evidence is outstanding, and that is a machine fault rather than a campaign result.

V11 removed the GL backend across 5 commits (844cf092c265b52d): 204 files, +1,870 / 27,607 lines. Gone are Gpu/Gl, ManagedGL*, GLHelpers, GLStateScope, both render-state caches, BindlessSupport, GraphicalGlFunctionProbe, the ImGui project, the Studio tree, the ui-studio verb, RenderBackendKind and the ACDREAM_RENDER_BACKEND escape hatch. Silk.NET.OpenGL and .Extensions.ARB are dropped — final reference count zero. 9/9 shader pairs recompile.

Three findings from the deletion are worth keeping:

  • Chorizite could not be dropped, and not for the reason the risk register predicted. It was assumed to survive only via IUniformBuffer. In fact TextureFormat sits in the IWorldTextureArray.CreateClampedArray signature that the Vulkan path implements, and BoundingBox is a serialized type in the pak format. Dropping it touches the on-disk format and is its own slice.
  • Studio/SampleData.cs was production code, not devtools — InteractionRetainedUiComposition uses it as the character sheet's fallback. Moved, not deleted.
  • One real bug fell out. WbMeshAdapter.Dispose() was still pattern-matching the deleted GpuFrameFlightController to decide whether to wait for submitted GPU work. VulkanFrameFlightController replaced that type at V6a and the site was never updated, so the wait had been silently dead on every Vulkan run since V6a. Deleting the GL type is what turned a no-op into a compile error.

Nothing was relaxed to manufacture a pass and nothing was declared green on a prediction. A gate that could not run is not a gate that passed — the outstanding list is §1.1.


4. Numbers

Complete solution suite

Passed Skipped Total
V11 baseline (d312bd2f) 8,999 5 9,004
Tonight (HEAD) 9,666 5 9,671

The +667 decomposes exactly, with nothing unaccounted for:

Source Tests
Enum verification campaign +597
Wire-stack audit +59
#248 near-plane theory (4 near/far pairs) + far-plane pin +5
#250 ZeroAllocationProbe apparatus guards +6
Total +667

Per project at HEAD: App 3,941/3 · Core 3,898/2 · Core.Net 659 · UI.Abstractions 543 · Runtime 415 · Content 124 · Headless 67 · Bake 15 · Cli 4. Zero failures.

Release build: 0 errors. 21 warnings, all pre-existing — one xUnit1025 (duplicate InlineData in MotionInterpreterTests) and a family of CS8767 nullability mismatches in the App test composition fakes. None cites a file touched tonight; the count is unchanged from the V11 baseline.

The #250 acceptance gate

The issue asks for 20 consecutive clean Release runs of the App suite, and that is the right scope: it isolates #250 from #255, which lives in AcDream.Content.Tests and is still open.

Result: 20 / 20 clean, 3,941 passed / 3 skipped every run.

For contrast, the pre-fix 20-run complete-solution baseline taken earlier the same night, on the same machine:

Failure Runs
#250 family (3 distinct tests) 2, 14, 18, 19
#255 RetailDatLoaderTests 7, 14, 17, 18

So the #250 family went from 4 failing runs in 20 to 0 in 20, and the three tests involved were each converted. #255 is untouched and still flakes — it is a different issue with an open fix, and it will keep appearing in complete-solution runs until the rendezvous lands.

One caveat, stated plainly: 20 consecutive App-suite runs is what the acceptance criterion asks for and what was delivered, but the App suite alone applies less parallel pressure than a complete-solution run. The strongest evidence would be 20 clean complete-solution runs, and that is not currently obtainable while #255 flakes independently. Re-running this gate after #255 is fixed would close that gap.


5. Where to pick up

  1. Reboot, then §1.1's five gates and §1.4's walked portal session — one sitting.
  2. Answer §1.2's three enum decisions; they unblock adoption commits that are otherwise ready.
  3. One ACDREAM_DUMP_OPCODES=1 session (§1.3) turns the wire audit's 128 MISSING rows into a ranked work order.
  4. #255's rendezvous fix — the diagnosis is settled from three independent observations and the fix direction is agreed; it is only unwritten.