diff --git a/docs/research/2026-06-02-phase-w-membership-fixed-render-handoff.md b/docs/research/2026-06-02-phase-w-membership-fixed-render-handoff.md new file mode 100644 index 0000000..9d68fe4 --- /dev/null +++ b/docs/research/2026-06-02-phase-w-membership-fixed-render-handoff.md @@ -0,0 +1,174 @@ +# Phase W (session 2) — Membership FIXED + render-rewrite handoff (2026-06-02) + +> **Canonical pickup for the next session.** This session found the **root cause** of the +> indoor cell-strobe and **fixed it the retail way** (offline-verified), then planned + began +> the render rewrite. Read this first, then the design doc + the render plan. The membership +> half is done; the render half is planned + partly executing. + +## 0. Git / safety state — READ FIRST +- **Branch `claude/thirsty-goldberg-51bb9b` — UNPUSHED.** Do not push without the user. +- **⚠ STASH DISCREPANCY — investigate.** Session START had **2** stashes; now there is **1** + (`stash@{0}: …issue98-pre-triage-backup-20260523-145942`). The **`#98/#101/A8-culling WIP`** + stash (originally `stash@{0}`) is no longer listed. `refs/stash` is **shared repo-wide** across + the user's many worktrees, and that stash's note said "pop to restore" — so it may have been + legitimately popped by another worktree, OR dropped by a base-comparison `git stash` test in a + subagent this session. A `git fsck --unreachable` search did NOT surface it in this repo's + dangling objects. **Action for the user:** check your other worktrees / `git reflog` for it; + recovery window is ~2 weeks (gc). **Lesson:** never let subagents run `git stash` while + pre-existing stashes exist — instruct them to use a fresh worktree or commit-to-temp-branch + for base comparisons. +- HEAD at handoff time = **`573c555`** (Stage 3 complete). **Stage 3 (render-root unification) + DONE** — `6a1fbbd`→`573c555`: deleted the `FindCameraCell` AABB grace-frame fallback, added the + `seen_outside` sky/terrain gate (retail `CellManager::ChangePosition`), added + `CellGraph.FindVisibleChildCell`, 6 `CellGraphRootTests` green. Build 0/0, App 160/160, Core + 12-failure baseline unchanged (no new failures). **Next: Stage 4.** (`git log --oneline -20`.) +- Throwaway untracked (delete freely): `baseline-w2a-*.png`, `shot-*.png`, `launch*.log`. + `doorway-capture.jsonl` (719 MB) was **deleted** this session — the committed fixture + `tests/AcDream.Core.Tests/Fixtures/issue98/doorway-threshold-capture.jsonl` replaces it. + +## 1. Commit ledger (session 2, oldest→newest) +| SHA | What | +|---|---| +| `840c1b6` | 4-model research (3 in-tree + Codex) + Phase W design (transition-membership + PView render) | +| `50b168b` | chunk-1 plan (membership flicker fix) | +| `851cecc` | Stage 0 — `[cell-swept]` diagnostic (zero behavior change) | +| `3e1d502` | Stage 1 — return swept `sp.CurCellId` from `ResolveWithTransition` (kept) | +| `2acd8f9`→`d23d1f4` | W2b stab-list hysteresis (SHIPPED then **reverted** — wrong mechanism, wrong place) | +| `ed00719` | design §1a — Stage-1 gate finding: deeper root is `FindEnvCollisions:1947` | +| `59f3a13` | **MEMBERSHIP FIX** — faithful `find_cell_list` (interior-wins pick + swept determination, drop static `:1947`) | +| `a06226f` | render-rewrite plan (Stages 3-5), grounded | +| `21ee5e1`,`fcea816` | T0 — static-leak test isolation + portable doorway fixture | +| `6a1fbbd`,`3520860`,`38a52a7`,`573c555` | **Stage 3 DONE** — render-root unification (delete `FindCameraCell` fallback; `seen_outside` gate; `FindVisibleChildCell`; 6 tests; no regressions) | + +## 2. What's SOLVED — cell membership (the root cause) +**The indoor `0170↔0031` doorway strobe (and `0170↔0171`, cellar) is fixed**, offline-verified. + +- **Root cause (airtight, code analysis):** acdream re-derived the cell from the **static** sphere + position twice — once in `ResolveWithTransition`'s return (Stage 1 fixed that) and, the deeper + one, in `Transition.FindEnvCollisions` at `TransitionTypes.cs:1947`, which called + `engine.ResolveCellId(staticOrigin,…)` and clobbered `sp.CheckCellId` every sweep pass. Retail + never re-derives statically — it carries `sphere_path.curr_cell` through the swept transition + (`validate_transition` advances on an accepted move, reverts on a block) and commits it in + `SetPositionInternal`. +- **The fix (`59f3a13`), the retail way:** + 1. `CellTransit.BuildCellSetAndPickContaining` — ported retail `find_cell_list`'s containing-cell + pick (`pseudo_c:308788-308819`): **interior-wins** (first EnvCell whose BSP contains the + center, stop), else the **outdoor landcell** via the XY-column test (which acdream had been + *skipping* — the missing half that forced the `:1947` bolt-on). + 2. `FindEnvCollisions:1947` — replaced the static `ResolveCellId` with the **swept** + `CellTransit.FindCellSet(...)` (production / `DataCache != null`; the old `ResolveCellId` + stays only as a test-engine fallback). Gated downstream by the existing accept-on-move in + `ValidateTransition` (`TransitionTypes.cs:3404`). +- **Offline verification:** replaying the real captured doorway trajectory (committed fixture) + through the fixed engine gives **one clean `0170→0031` transition, no ping-pong** (was a + per-tick strobe). `LiveCompare_FirstCap` (#98 cottage-floor-cap) still passes. **Not yet + live-verified** — folds into the single final visual gate. +- **A-vs-B is settled:** "B" (transition-owned membership) was correct AND turned out *small* + (the sweep already tracks the cell; the bug was the static re-derive). The "A" (post-sweep + portal-crossing detector) was a non-retail invention — **off the table**. + +## 3. Canonical artifacts +- **Design:** `docs/superpowers/specs/2026-06-02-phase-w-transition-membership-and-pview-render-design.md` + (§1 root cause, §1a the `:1947` finding, §2 render target, §5 risks, §6 acceptance). +- **Render plan (per-step, grounded):** `docs/superpowers/plans/2026-06-02-phase-w-render-rewrite.md` + — T0 (done), Stage 3-5 + the green-tests triage. **This is what the new session executes.** +- **Membership plan:** `docs/superpowers/plans/2026-06-02-phase-w-membership-flicker-fix.md` (done). +- **Research:** `docs/research/2026-06-02-retail-cell-render-study-{opus48-a,opus48-b,sonnet46,codex}.md` + + the shared prompt `…-research-prompt.md` + the evidence `…-render-cell-membership-evidence.md`. + +## 4. Render rewrite — state + what's next +**Key reframe (good news):** the render half is **wire-and-fill-gaps, NOT a from-scratch port.** +The PView infrastructure already exists — `PortalVisibilityBuilder` (visible-set BFS + +`OutsideView`, enqueue-once gate, so #102's `MaxReprocessPerCell` is already gone), `ClipFrame`/ +`ClipFrameAssembler` (the doorway clip), `EnvCellRenderer` GL_BLEND fix (already shipped U.4), +`WbDrawDispatcher` cell gate — and the old `RenderInsideOut`/`ACDREAM_A8_INDOOR_BRANCH` stencil +split is already gone (comments only). The gaps: +- **Stage 3 (DONE `573c555`)** — root render at `CellGraph.CurrCell` + `seen_outside`; deleted the + AABB `FindCameraCell` grace-frame fallback; `seen_outside` sky/terrain gate (retail + `CellManager::ChangePosition`); `CellGraph.FindVisibleChildCell` added. **Interim state (expected, + no user sees it before the final gate): sky may draw full-screen indoors until Stage 4 clips it.** +- **Stage 4 (the seal)** — draw sky/landscape *inside* the portal-clip bracket + the conditional + doorway **Z-clear** (depth only, not color → **no blue-hole**); verify ceilings + opaque walls. + Key gap found: sky currently draws BEFORE the clip bracket (`GameWindow.cs:~7268`); move it in. +- **Stage 5** — clip entities/particles to the PView visible set (kills NPC/door/smoke bleed). +- **Green-tests triage** — see §5. + +The plan has exact file:line change-points (verify — lines shift): `physicsRoot`/visibility +~`GameWindow.cs:7162-7166`; sky gate ~`:7267`; terrain gate ~`:7406`; `FindCameraCell` +`CellVisibility.cs:389`; grace `:214`; `ComputeVisibilityFromRoot` `:356`; `EnvCellRenderer` +GL_BLEND `:1004-1023`; `PortalVisibilityBuilder.Build` BFS+OutsideView `:1-239`. + +## 5. The directives (CRITICAL — from the user, this session) +- **NO intermediate user visual gates.** Drive Stages 3→4→5 + triage to done. The per-stage + "visual gates" in the plan are **internal build+test-green checkpoints** only. +- **Single final visual verification** — cottage AND a dungeon (sealed, sky/rain through the door, + no blue-hole, no transparent walls, no bleed). That is the ONLY time to bring the user in. +- **GREEN tests at verification** — no broken/red tests when handed over. **T0 made the suite + deterministic: exactly 12 failures.** None are Phase-W regressions (the membership fix only + changed the returned cell *id*, not `ResolveResult.Position`). Of the 12: + - **3 document-the-bug** (`DoorBugTrajectoryReplayTests` ×2, `DoorCollisionApparatusTests`) — + reconcile per their stage. + - **9 pre-existing tech debt** (red before Phase W, unrelated): `MotionInterpreterTests.GetMaxSpeed` + ×3 (method doesn't branch on command), `BSPStepUpTests` B1/C3/D4 (step-up gaps), + `PlayerMovementControllerTests.Update_ForwardInput…`, `PositionManagerTests.ComputeOffset_BothActive…`, + `DispatcherToMovementIntegrationTests.Dispatcher_W_held…`. + - **Triage-to-green pass before the final gate:** fix the stale ones (e.g. GetMaxSpeed test if + the no-branch behavior is intended), and for any genuine pre-existing gap that's out of Phase-W + scope (step-up), **transparently flag it to the user** — do NOT silently `[Skip]` real-bug tests. +- **Process:** `superpowers:writing-plans` (done) + `superpowers:subagent-driven-development` + (each stage = a bounded subagent task + review). Sonnet implementers; Opus for load-bearing review. + +## 6. Apparatus / how to reproduce +- Launch (per CLAUDE.md "Running the client") with `ACDREAM_PROBE_CELL=1` (decisive `[cell-transit]`, + low volume) and/or `ACDREAM_PROBE_SWEPT=1` (`[cell-swept]` — swept cell per resolve, this session's + diagnostic). Pipe via `Tee-Object`; the log is **UTF-16** — read with PowerShell `Get-Content` + or the ripgrep Grep tool, NOT GNU grep. Screenshots: PowerShell `CopyFromScreen` on `AcDream.App`. +- **Cottage cells:** `0171` room (Z94), `0175` stairs (Z93), `0174` cellar (Z90), `0170` vestibule, + `0031/0032` outdoor. ACE respawns at last logout (sometimes outside `0031`, sometimes in `0171`). +- **Offline membership verification (no walk):** replay `…/Fixtures/issue98/doorway-threshold-capture.jsonl` + (57 records, the `0170↔0031` seam) through `ResolveWithTransition`, chaining the cell — see + `DoorwayMembershipReplayTests`. `ACDREAM_CAPTURE_RESOLVE=` captures a fresh trajectory if needed. + +## 7. Do-NOT-repeat / settled facts +- **Membership is fixed** — the `find_cell_list` port (`59f3a13`). Don't re-investigate the strobe + root; it's `:1947` (the static re-derive), now replaced by the swept pick. Don't reintroduce a + static `ResolveCellId` in the sweep. +- **A-vs-B settled: B, and it was small.** No portal-crossing-detector invention ("A"). +- **Render infra already exists** (PView builder, clip frame, GL_BLEND fix, cell gate); the A8 + stencil split is gone. Render = wire + 3 gaps (root, sky-inside-clip + Z-clear, entity clip). + Don't rebuild the PView builder or rip out a stencil pipeline that isn't there. +- **The 12 test failures are NOT Phase-W regressions** (position unchanged). Don't chase them as + regressions; triage per §5. +- Earlier-disproven (do not revisit): camera/eye as root, stencil-mask, flag-based per-entity gate + routing, W2b's stab-list-prune-in-ResolveCellId. + +## 8. Pickup prompt (copy-paste) +``` +PHASE W (Unified Cell Graph) — continue on branch claude/thirsty-goldberg-51bb9b (do NOT +branch/worktree; do NOT push without asking). FIRST: git log --oneline -20 + git status to see +where Stage 3 landed; AND check the stash discrepancy in the handoff §0 (one of 2 stashes is +missing — check other worktrees before doing anything that gc's objects). + +READ FIRST: docs/research/2026-06-02-phase-w-membership-fixed-render-handoff.md, then the design +docs/superpowers/specs/2026-06-02-phase-w-transition-membership-and-pview-render-design.md and the +render plan docs/superpowers/plans/2026-06-02-phase-w-render-rewrite.md. + +STATE: M1.5 "indoor world feels right." Cell-membership root cause FIXED the retail way +(find_cell_list interior-wins pick + swept determination, static :1947 removed; commit 59f3a13; +offline-verified — doorway strobe → one clean transition). T0 made the suite deterministic (12 +known failures, none Phase-W regressions). Stage 3 (render-root unification) was dispatched. + +DO NEXT, subagent-driven (superpowers:subagent-driven-development), NO intermediate user gates: +1. Stage 3 (render-root unification) is DONE (`6a1fbbd`→`573c555`, build green, 6 tests). START at Stage 4. +2. Stage 4 — the seal: sky/landscape inside the portal-clip bracket + conditional doorway Z-clear + (no blue-hole); verify ceilings/opaque walls. The big one. +3. Stage 5 — entity/particle cell-clip. +4. Green-tests triage (handoff §5): fix stale tests, reconcile document-the-bug, flag genuine + pre-existing gaps; full suite GREEN. +5. THEN — and only then — the SINGLE final visual verification (user): cottage + a dungeon, sealed + and seamless. Update roadmap + flip Phase W shipped. + +EVIDENCE-FIRST for render/visual: launch with ACDREAM_PROBE_CELL/SWEPT, read the UTF-16 log with +PowerShell/ripgrep (not GNU grep). Membership is verifiable offline via the committed +doorway-threshold fixture (no walk). Render is wire+fill-gaps (infra exists); do not rebuild it. +```