diff --git a/docs/research/2026-08-06-c5c-closeout-handoff.md b/docs/research/2026-08-06-c5c-closeout-handoff.md new file mode 100644 index 00000000..c5c46bb3 --- /dev/null +++ b/docs/research/2026-08-06-c5c-closeout-handoff.md @@ -0,0 +1,225 @@ +# C5c closeout + successor handoff — the placement cutover's automated half is done (2026-08-06) + +**Read this before any C5c or post-campaign work.** It is the successor to +`2026-08-05-c4-closeout-handoff.md` (whose ⚠ BISECT HAZARD block still +applies and is repeated in §6). + +Branch `claude/acdream-physics-divergence-5aa784`, 21 commits from `02578441` +to `7b3e2895`. **Nothing is pushed** — the branch does not exist on the remote, +and there are 388+ unpushed commits ahead of `origin/main`. + +--- + +## 1. One-paragraph state + +Every implementation item in the placement cutover campaign is now landed and +dual-reviewed. **C5b** (classify-before-merge), **#280** (portal destination +prefetch), **#276's remainder**, **AP-22** and **AD-10** all shipped with both +review lenses PASS. **#309** was accepted as a standing divergence by user +decision rather than fixed. What remains for C5c is **entirely connected/visual +work plus the ledger close** — none of it can be done without the user at the +client, and the probe strip cannot be done before it. + +--- + +## 2. What landed, by slice + +| Slice | Commits | Outcome | +|---|---|---| +| **C5b** — classify before merge (#275) | `735f0a72`, `ed806997`, `23aa62f2`, `ff100cf3`, `9ee9c1a1` | Retired **AP-131** and **AD-60**'s legacy half. Seven production lines; five commits, because the review found a headless regression the change itself introduced. | +| **#280** — portal destination prefetch | `3aab05b0`, `73cdb95c`, `bcb66ccd` | Reveal window now derives from the live streaming radii. **D-1**, an unrecoverable portal hang, was found by review and fixed. | +| **#276 remainder** | `408c8e8f`, `fafc0b65` | Settle now adopts the transition's resolved cell across an indoor seam. | +| **#316**, **#317** | `429775d4`, `1d2d4bb8` | Report-only investigations. #316 **cosmetic**; #317 **no retail basis**. | +| **#309** | `43cfdc4a` | **Accepted as a standing divergence** (user decision), not a planned fix. AP-136 is its permanent record. | +| **AP-22** | `bc4679cd`, `619de97a`, `ef976c6d` | Invented collision cylinder deleted in **all three** copies. Row retired. | +| **AD-10** | `fe6ee877`, `886333a2`, `fb454b74`, `2223ed17`, `7b3e2895` | **Retired by deletion** — its stated justification was false at HEAD. | + +--- + +## 2.5 C5c's automated gate — PASS + +Run at `7b3e2895` on the final binary, **after deleting all 44 `bin`/`obj` +directories** (see §7 rule 4 — this session had three stale-artifact +incidents, so an incremental result would not have been evidence): + +``` +dotnet test AcDream.slnx -c Release -m:1 # ACDREAM_PAK_PATH set +``` + +**11,196 passed / 4 skipped / 0 failed**, all nine projects: + +| Project | Passed | Skipped | +|---|---|---| +| AcDream.App.Tests | 4,172 | 3 | +| AcDream.Core.Tests | 4,261 | 1 | +| AcDream.Runtime.Tests | 1,220 | 0 | +| AcDream.Core.Net.Tests | 764 | 0 | +| AcDream.UI.Abstractions.Tests | 546 | 0 | +| AcDream.Content.Tests | 125 | 0 | +| AcDream.Headless.Tests | 89 | 0 | +| AcDream.Bake.Tests | 15 | 0 | +| AcDream.Cli.Tests | 4 | 0 | + +The 4 skips are the pre-existing set; no new skip was added anywhere in the +campaign. **None of the three known load-sensitive flakes (#302, #308, #321) +fired** in this run — but they are separately filed and must never be +conflated if one does. + +Net movement across the campaign: 11,106 at `02578441` → **11,196**, +90. + +--- + +## 3. WHAT C5C STILL OWES — all of it needs the user + +Nothing below can be discharged without a live client. The automated half is +complete. + +### 3.1 Connected gates, batched into one sitting + +1. **#280's reveal gate.** The user's original repro was a **recall**, so the + route needs a **lifestone leg**, not only `/teleloc`, plus a first-login + stop. A/B with `ACDREAM_PROBE_REVEAL_RADIUS`, `ACDREAM_STREAM_RADIUS` + unset. The pre-fix run is *expected to show the defect*; the post-fix hold + must be measurably **longer**. If it isn't, the gate widened nothing. +2. **#280 D-1's two reachability scenarios, never reproduced live**: two + consecutive recalls to the **same** landblock with walking in between, and + a mid-hold quality-preset drop. Both are fixed and unit-covered; neither has + been seen on a running client. +3. **AP-136's six-step park check** — `ACDREAM_PROBE_PARK=1`. **This survives + #309's deferral**: it validates the SHIPPED rollback path + (`restorableOnCancel` in `SubmitPreparedPlacementCore`, the shared core + behind every production placement), not the deferred fix. +4. **Route-7 thickening** — its gate passed on one `cause=propagate` probe line; + the evidence is THIN. +5. **AD-65 / AD-66** need a local-player visual gate before any fix (see §5). +6. **C5c proper**: two-client observation, canonical nine-stop soak, + lifecycle/reconnect route on the final binary, and the user's visual matrix. + +### 3.2 Then, and only then + +**The probe strip.** Six flags — `ACDREAM_PROBE_REMOTE_LANDING`, +`ACDREAM_PROBE_REMOTE_SLIDE`, `ACDREAM_PROBE_PARK`, +`ACDREAM_PROBE_REMOTE_TELEPORT`, `ACDREAM_PROBE_CHILD_CELL`, +`ACDREAM_PROBE_LOCAL_TELEPORT` — plus `ACDREAM_PROBE_REVEAL_RADIUS`. +**Strip as a family, and NOT before the gates above have consumed them.** +Stripping first would delete the instrumentation §3.1's items depend on. + +### 3.3 Then close the ledger + +Register / roadmap / milestones / memory, and the campaign ledger close. + +--- + +## 4. Issues filed this session + +| # | Subject | +|---|---| +| **#321** | `DatSoundCacheTests` concurrent-decode dedup, full-suite load (third load-sensitive flake) | +| **#322** | Two callers compute the same pre-placement flags from the same inputs | +| **#323** | A far-snap store can silently stale a pending initial-create receipt | +| **#324** | Graphical and no-window hosts run parallel, non-shared inbound routes | +| **#325** | **Gate A's teleport test is narrower than retail's** — `==` where retail is "not older" | +| **#326** | acdream has no Viewing Distance option (retail's `Render.LandscapeDrawDistance`) | +| **#327** | No analogue of retail's DDD prefetch progress readout | +| **#328** | Camera far plane hardcoded 5000 f; retail's `zfar` is byte-verified 4000 | +| **#329** | Portal wait cue arms 5 s late; retail emits per tunnel rotation segment | +| **#330** | **Headless registers no live-entity collision at all** — a bot walks through every NPC | +| **#331** | **`ResolveWithTransition` refuses ALL uphill motion with a body supplied** | +| **#332** | Headless bots appear to have no remote dead-reckoning | + +**#331 is the one to look at first.** Severity was raised from UNKNOWN once the +discriminator was found: it is the **`body:` parameter**, not the fixture. With +`body: null` the uphill sweep climbs; with a body it returns `ok=False` and zero +movement — under a call profile identical to the local player's +(`IsPlayer | EdgeSlide`, human two-sphere Setup), on ramps as shallow as **1.1°**. +A diagonal request keeps cross-slope X and zeroes only up-slope Y. Nothing in +the suite asserts uphill progress on a walkable slope, which is why it was +invisible — the test that found it passed **vacuously**. + +--- + +## 5. Register movement + +**Retired:** AP-1, AD-1 (C5a) · AP-145 (C5a) · **AP-131**, **AD-60**'s legacy +half (C5b) · **AP-22** · **AD-10**. + +**Filed:** AP-147 (delta-stream cardinality) · **AP-148** (#325's Gate A +narrowing) · AP-149 (outer ring accepts terrain-only) · **AP-150** (wait-cue +delay is not retail's trigger) · AP-151 (gate stricter than retail on the +GPU-upload axis) · **AP-152** (live path emits primitives *and* BSP additively +where retail is exclusive; 172/5,935 Setups incl. BSP doors) · AD-64 (the +duplicated residency decision) · **AD-65**, **AD-66**. + +**AD-65 deserves attention.** Its magnitude was filed at half the truth: the row +states `cos²θ` but quantified `1−cosθ`. Corrected to **25% short at 30°, 50% at +45°**, confirmed by measurement (#331's probe: 0.0735 m for a 0.1 m request at +30.96° = `cos²(30.96)`). **It is a live lead for #269's slope-slide residual** — +and note that project memory's #269 do-not-retry covers *friction and jump +chains*, which are byte-exonerated; `AdjustOffset` is a different function and +is **not** covered by it. + +--- + +## 6. ⚠ BISECT HAZARD — carried forward + +Commits **`735f0a72..23aa62f2`** contain a live headless defect: every remote +entity's `FullCellId` is frozen at its placement value for the whole session in +`AcDream.Headless`, and the local player loses one of AP-146's three +cell-refresh edges. Introduced by `735f0a72`, fixed at `ff100cf3`. Nothing +throws; no test in the range catches it. + +--- + +## 7. Process findings — stated as rules, each paid for this session + +1. **A blast-radius enumeration only reaches as far as the call graph its author + walked.** C5b's was performed over the graphical `OnPosition` path and missed + `AcDream.Headless` entirely — 11,000 green tests, one frozen host. Ask *which* + traversal, and what it structurally could not reach. Both hosts, every time. +2. **Two independent adversarial reviewers converging is near-proof; a lone + finding is a lead.** It happened three times this session (the headless hole, + the missing payload gate, D-1) and all three were real. +3. **Assume a test does not discriminate until sabotage proves it.** SEVEN green + tests covering nothing were found or avoided: C5b's conservation test, #276's + three settler tests, #280's tautological integration fixture, the atlas-tier + seam 4,170 tests missed, AD-10's only existing test (a dead method with a + hard-coded formula), and AD-10's contract-specified T1 sabotage which came + back green and was rejected rather than shipped. +4. **`bin`/`obj` can serve deleted code even under `--no-incremental` and + `-t:Rebuild`.** Three incidents. Delete all 44 directories before any + verdict-deciding result. A stale artifact does not look like an error — it + produces a plausible failure, or a plausible pass. +5. **Every contract in this campaign has been wrong at least once, and the + implementation is what catches it.** C5b's §3-D2 (a "dead" ternary that was + live), #280's §7 (three false items), AP-22's §7.1 (a literal that was a + three-way condition), AD-10's §7.1 (a non-discriminating sabotage). Brief + implementers to rebut, and treat a reasoned rebuttal as more valuable than a + compliant edit. +6. **Verify a cited address is the construct you claim.** AP-150 mis-cited + `0x004D7064` — a `PStringBase` constructor — as the `SendNotice` call, + *despite* being filed with a byte-level disassembly. Precision of method does + not prevent an error of line. +7. **Binary Ninja drops flag tests.** `if (-((eax_7 - eax_7)) == 0)` renders an + always-true where a real wrap-safe compare lives. Confirmed at Gate A + (0x00454054), `DoVectorUpdate` (0x004521F5), `HandlePlayerTeleport` + (0x00452186), and `FindObjCollisions`. Disassemble the PDB-paired binary + wherever a comparison or constant is load-bearing. +8. **Do not fan out subagents.** Two blowups: six agents spawning their own + children exhausted a session usage limit and killed four tasks mid-flight; + a later three spawned five more. Every brief must say **"do not spawn + subagents"** explicitly — none of the early ones did. One code-writer at a + time; the shared worktree tolerates no more. +9. **A shell failure inside a compound command can leave a commit claiming work + it did not do.** `ef976c6d` was needed because a heredoc invoked `python` + (absent here; `py` is the binary) while the commit still reported success. + +--- + +## 8. Where to start + +- **Post-campaign:** #331 first (§4), then AP-152 and #330 — both are real + collision divergences with user-visible consequences. +- **Domain entry points remain** `claude-memory/project_physics_collision_digest.md` + and `claude-memory/project_render_pipeline_digest.md`. +- **The campaign plan** (`docs/plans/2026-08-02-placement-cutover.md`) item 5 now + records AP-22 and AD-10 as retired; item 3 records #280 as done.