# 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](../ISSUES.md) — 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](../ISSUES.md)'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`, `d312bd2f` → `HEAD`. ### 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 (`844cf092` → `c265b52d`): 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.