docs(render): Phase W session-2 handoff — membership FIXED + render rewrite (Stage 3 done)

Canonical pickup for the next session. Membership root cause (static :1947 re-derive)
FIXED the retail way (find_cell_list interior-wins pick + swept determination, 59f3a13)
and offline-verified (doorway strobe -> one clean transition). T0 made the suite
deterministic (12 known failures, none Phase-W regressions). Stage 3 (render-root
unification) DONE (6a1fbbd->573c555). Remaining: Stage 4 (the seal: sky/landscape inside
the portal-clip bracket + conditional doorway Z-clear = no blue-hole), Stage 5 (entity/
particle clip), green-tests triage, then the single final visual verification. Render is
wire-and-fill-gaps (PView infra exists). Flags a stash discrepancy (1 of 2 stashes missing
from the shared refs/stash) for the user to check against other worktrees.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-02 15:39:41 +02:00
parent 573c5559a0
commit 55e1b30553

View file

@ -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=<path>` 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.
```