# Handoff — membership Stage 1 + the doorway blue-hole are FIXED; render residuals A/B/C next — 2026-06-03 > **Canonical pickup for the next session.** Read this FIRST. This session **fixed the cottage > doorway "flap"** — it had **two** independent causes (a membership pick bug AND a render-root > clobbering bug), both now resolved and visual-verified by the user (inside-looking-out is correct). > What remains are **three separate, already-known render phases** (A camera-collision, B particles, > C outside-looking-in) and the **Stage 2** membership faithfulness rearchitecture. Branch: > `claude/thirsty-goldberg-51bb9b`. PowerShell on Windows; launch logs are UTF-16 > (`Select-String` / ripgrep `--encoding utf-16-le`, NOT GNU grep). --- ## 0. TL;DR - **FIXED (visual-verified):** the doorway flap. Inside-looking-out now renders correctly — solid walls, wood floor, sky/terrain only through the doorway. No more full-screen bluish void. - **The flap had TWO causes** (this is the key lesson — the prior handoff's "it's the pick" was only half of it): 1. **Membership pick ping-pong** — the unordered `HashSet` + the pre-pick fork. Fixed by the verbatim ordered-`CELLARRAY` pick + retail's collide-then-pick order (Stage 1, faithful port). 2. **Render-root clobbering** — `CellGraph.CurrCell` (the render root, "the player's cell") was written by the **per-entity** `ResolveWithTransition`/`ResolveCellId`, so a jumping NPC near the doorway overwrote the player's render root every tick → the render rooted at the NPC's tiny connector cell → only its ~8-triangle shell drew, rest = GL clear color = the blue void. Fixed by making the `CurrCell` write **player-only** (`UpdatePlayerCurrCell` via `UpdateCellId`). - **REMAINING (next sessions):** render residuals **A → C → B** (a fresh render session), then membership **Stage 2** (uniform collision + intrinsic building entry), gated on a payoff (see §6). - **Test state:** Core 1295 pass / 5 fail = the documented baseline (2 step-up + 3 door-collision), **zero new breakage**. App 174 green. --- ## 1. Commit ledger (this session, branch `claude/thirsty-goldberg-51bb9b`) | SHA | What | Layer | |---|---|---| | `b44dd14` | `CellArray` — ordered/deduped cell collection (retail `CELLARRAY::add_cell` @701036) | Core | | `bc56545` | widen cell-candidate helpers `HashSet` → `ICollection` (non-behavioral) | Core | | `22a184c` | **verbatim ordered-CELLARRAY membership pick** (`find_cell_list` @308742); deletes the `5ca2f44` pre-check | Core | | `e5457f9` | **collide-then-pick** — remove the pre-pick fork; `RunCheckOtherCellsAndAdvance` post-step (`find_env_collisions`→`check_other_cells`) | Core | | `79fb6e7` | **doorway blue-hole** — `CurrCell` render-root write made player-only (`UpdatePlayerCurrCell`) | Core + App | (`5ca2f44`, the prior session's current-first pre-check, was **deleted** by `22a184c` as the handoff directed.) Working tree clean except untracked launch logs + the prior session's screenshots. --- ## 2. What shipped — Stage 1 membership port (faithful) Ported verbatim from the decomp (`docs/research/named-retail/acclient_2013_pseudo_c.txt`): - **`find_cell_list` ordered pick** (`CObjCell::find_cell_list @ 0x52b4e0`, pc:308742): an ordered `CellArray` (= retail `CELLARRAY`), current cell at **index 0** (`add_cell` @701036, pc:308766), single forward-walk expansion via `find_transit_cells` (pc:308775-308785), then **in-order, interior-wins-break** pick (pc:308788-308825). Replaced the unordered `HashSet`. - **Collide-then-pick** (`CEnvCell::find_env_collisions @ 0x52c130` pc:309573 + `CTransition::check_other_cells @ 0x50ae50` pc:272717): the primary collision runs against the **carried** cell (the seed); the new cell is picked + all other cells collided **after**, in the new shared `Transition.RunCheckOtherCellsAndAdvance`. Removed acdream's pre-pick (line ~1958) which retail does NOT have — that pre-pick was the engine of the position oscillation (it swapped the collision geometry with the cell mid-tick → a bistable feedback loop). - **Persistence** carried via the seed; committed by `ValidateTransition` (= `validate_transition` `curr_cell=check_cell`, pc:272612); applied to the player's object-state cell via `UpdateCellId` (= `SetPositionInternal`/`change_cell`, pc:283403/281192). - Pseudocode: [`docs/research/2026-06-03-cell-membership-ordered-cellarray-pseudocode.md`](2026-06-03-cell-membership-ordered-cellarray-pseudocode.md). - Plan: [`docs/superpowers/plans/2026-06-03-membership-ordered-cellarray-port.md`](../superpowers/plans/2026-06-03-membership-ordered-cellarray-port.md). **Verified:** `[cell-transit]` dropped 47 → 13 → DELTA=0 while standing still (no oscillation); the deterministic membership net is green (`CellTransit|FindEnvCollisions|CellGraph|Doorway|ResolveCellId`). ## 2b. What shipped — the doorway blue-hole fix (the real flap) The ordered-pick fix alone did NOT clear the visual flap ("exactly the same"). Diagnosed via the render probes (`ACDREAM_PROBE_FLAP`/`_VIS`/`_SHELL` + `ACDREAM_PROBE_CELL`): ``` [cell-transit] teleport -> 0xA9B40171 (player spawned in the ROOM, then stood still — no more transitions) [flap-cam] root=0xA9B40170 ×77,951 frames (render rooted at the VESTIBULE — the wrong cell) [shell] filter=1 [0xA9B40170:gfx=1 idx=24] (only 0170's ~8-tri shell drew → rest = blue clear color) ``` The player's cell was stable at `0171` (the room), but the render rooted at `0170` (a tiny connector) because a Holtburg NPC (`0x000F4240`) jump-looping near the doorway clobbered `CellGraph.CurrCell` every tick. `CurrCell` is documented as *"the player's cell"* (CellGraph.cs:19) and roots the indoor render (GameWindow.cs:7172), but it was written by the **per-entity** `SetCurrAndReturn` inside `ResolveWithTransition` (+ 4 sites in `ResolveCellId`). **Fix (`79fb6e7`):** `CurrCell` is now written **only by the player** — new `PhysicsEngine.UpdatePlayerCurrCell(cellId)` called from `PlayerMovementController.UpdateCellId` (the single player chokepoint for CellId: teleport / server snap @ `SetPosition` + per-frame resolver). Removed the write from `SetCurrAndReturn` (inlined the 2 resolve call sites) and the 4 `ResolveCellId` sites. NPCs no longer touch the render root. `CellGraphMembershipTests` rewritten to the new contract (3 tests: `UpdatePlayerCurrCell` writes the root; `ResolveCellId` does NOT — the blue-hole guard; stale-beats-null preserved). **Verified:** `[flap-cam]` now shows `root=` the player's cell every frame (0171 in the room, 0170 in the vestibule), `terrain=Planes/Skip` consistent, never stuck at an NPC's cell. User confirmed inside-looking-out is correct. > **Durable lesson (memory written):** a *single-owner* field (the player's render root) written from > a *per-entity* loop is a clobbering bug. Membership/physics state that "belongs to the player" must > be written at the player-only chokepoint, never in the shared per-entity resolve. --- ## 3. REMAINING ISSUES (the full list) ### Render residuals — visual, observed this session (do these next, a fresh render session) - **A — interior walls go grey/transparent *while inside*; particles/NPCs visible through them.** Cause: the 3rd-person chase eye sits OUTSIDE the player's cell (`[flap-cam] eyeInRoot=n`), so near walls back-face/clip away. Fix: **camera collision** — port retail `SmartBox::update_viewer` spring-arm to keep the eye in the cell. Partially present (`[flap-sweep]` runs: `desiredBack`/ `eyeBack`/`pulledIn`) but not fully containing the eye. Handoff-flagged "highest-leverage." **Shares its root mechanism (eye-outside-cell) with C — do A first to shrink C.** - **B — particles bleed through the ground/floor** (looking out from inside). → **R1b / issue #104** (scene particles aren't cell-clipped; needs a cell link on the `ParticleEmitter`). Deferred in the R1 plan. Smallest of the three. - **C — outside-looking-in: interior walls transparent + ground texture over the floor** (the cottage renders as a see-through box from the street; includes a detached floating wall piece). → **R2 / `PView::DrawPortal @ 0x5a5ab0`** — the outdoor→interior portal render, not yet built. The biggest (a whole render phase). **Recommended order: A → C → B** (A first = highest-leverage + shrinks C; C second = the big one, fresh; B last = small, self-contained). ### Membership Stage 2 — faithfulness debt, NOT a visible bug (do after A/B/C; gate on payoff — §6) - **#4 Forked collision (§4.4 #3):** still branches `cellLow >= 0x0100` (indoor cell-BSP vs outdoor terrain-triangles) + `TryFindIndoorWalkablePlane`. Retail does one uniform sphere-sweep over all cells. "A real rearchitecture" (handoff's words) — risky; the membership WORKS without it. - **#5 Building entry is a bridge (§4.4 #4):** `CellTransit.CheckBuildingTransit` + the `#90` `0x90` stickiness, instead of retail's intrinsic `find_building_transit_cells` (pc:318309). - **#6 Outdoor `point_in_cell`:** the pick's outdoor fallback uses a `gx/gy` XY-column adaptation (acdream landcells lack retail's `CLandCell::point_in_cell` BSP). Documented adaptation. ### Separate physics — UNVERIFIED this session - **#7 Stairs Z-oscillation** (~0.2 m/tick on the cellar stairs, #98 family). The prior handoff §8 said it MAY be mooted by the membership fix. **Not re-tested** — needs a cellar-stairs walk. **This is the gate for Stage 2:** if the stairs are still broken, uniform collision (#4) plausibly fixes it and Stage 2 earns its risk; if the stairs are fine, Stage 2 stays optional. ### Test baseline — NOT introduced this session (do not "fix" blindly) - **5 Core failures:** `BSPStepUpTests.B1`, `BSPStepUpTests.D4` (2 step-up gaps) + `DoorCollisionApparatusTests.Apparatus_Grounded_50cmOffCenter_FrontApproach_DocumentsBug` + `DoorBugTrajectoryReplayTests.LiveCompare_DoorOffCenterWalkthrough_Tick13558` + `…LiveCompare_DoorBlocksFromOutside_Tick22760` (3 door-collision). Verified pre-existing; the with-change failure set is a strict match to the prior baseline. --- ## 4. KEEP / DON'T-REDO **KEEP (correct, verified — do not reopen):** - The Stage-1 membership port: `CellArray`, the ordered `find_cell_list` pick in `BuildCellSetAndPickContaining`, the collide-then-pick `RunCheckOtherCellsAndAdvance`. Membership is stable (DELTA=0 while still; flap gone). - The blue-hole fix: `UpdatePlayerCurrCell` (player-only render-root write). Do NOT re-add a `CurrCell` write inside `ResolveWithTransition`/`ResolveCellId` (that was the clobbering bug). - The R1 render (per-cell `DrawInside`, binary decision) — inside-looking-out is correct. **DON'T:** - Don't re-add the `5ca2f44` pre-check (the ordered array subsumes it; the prior handoff's "ordered pick is too shallow" framing was itself superseded — the pick WAS necessary, it just wasn't the whole flap). - Don't treat Stage 2 as mandatory — it's faithfulness debt; the membership works. Gate it on #7. - Don't conflate A and C as one fix — A is camera-collision (eye containment while inside), C is the separate `DrawPortal` outdoor→interior phase. They share a *mechanism* (eye/viewpoint vs cell), not a code path. --- ## 5. KEY FILES + ANCHORS ``` MEMBERSHIP (Stage 1 — shipped) src/AcDream.Core/Physics/CellArray.cs ← retail CELLARRAY (ordered+dedup) src/AcDream.Core/Physics/CellTransit.cs BuildCellSetAndPickContaining ← the ordered find_cell_list pick src/AcDream.Core/Physics/TransitionTypes.cs FindEnvCollisions (~1939) ← primary-collide-against-seed (no pre-pick) RunCheckOtherCellsAndAdvance (~after FindEnvCollisions)← the check_other_cells post-step src/AcDream.Core/Physics/PhysicsEngine.cs UpdatePlayerCurrCell (~259) ← player-only render-root write (blue-hole fix) ResolveWithTransition (~608, returns sp.CurCellId) src/AcDream.App/Input/PlayerMovementController.cs UpdateCellId (~774) ← calls _physics.UpdatePlayerCurrCell tests/AcDream.Core.Tests/Physics/CellGraphMembershipTests.cs ← new contract (3 tests) tests/AcDream.Core.Tests/Physics/CellTransitFindCellSetTests.cs ← ordered-pick conformance RENDER (residuals A/B/C — next) src/AcDream.App/Rendering/GameWindow.cs (OnRender ~7300-7600) ← binary decision, landscape-thru-door, Z-clear src/AcDream.App/Rendering/InteriorRenderer.cs ← per-cell DrawInside loop src/AcDream.App/Rendering/PhysicsCameraCollisionProbe.cs ← [flap-sweep] camera-collision (A lives near here) src/AcDream.App/Rendering/CellVisibility.cs ← ComputeVisibilityFromRoot (root=player cell) src/AcDream.App/Rendering/ClipFrameAssembler.cs ← TerrainMode / HasOutsideView (consistent) src/AcDream.App/Rendering/PortalVisibilityBuilder.cs ← the PView BFS ([flap] probe) (R2/DrawPortal does not exist yet — C builds it) RETAIL DECOMP (docs/research/named-retail/acclient_2013_pseudo_c.txt) CObjCell::find_cell_list 0x52b4e0 pc:308742 (pick 308788-308825; add_cell @701036) CEnvCell::find_env_collisions 0x52c130 pc:309573 (primary BSP vs the carried cell — no pre-pick) CTransition::check_other_cells 0x50ae50 pc:272717 (build array + collide others + advance) CTransition::validate_transition 0x50aa70 pc:272547 (curr_cell = check_cell @272612) CPhysicsObj::SetPositionInternal 0x515330 pc:283399 (change_cell iff this->cell != curr_cell) SmartBox::update_viewer (camera spring-arm — A) ; PView::DrawPortal 0x5a5ab0 (C) PROBES ACDREAM_PROBE_CELL=1 [cell-transit] (player CellId changes) ACDREAM_PROBE_FLAP=1 [flap-cam] root/eyeInRoot/terrain/eye-vs-player + [flap] PView BFS + [flap-sweep] camera-collision ACDREAM_PROBE_VIS=1 [vis] visible cells + OutsideView ACDREAM_PROBE_SHELL=1 [shell] per-cell shell draw (NOSNAP/gfx/idx/zh/tr) ``` --- ## 6. SEQUENCING DECISION (agreed with the user, 2026-06-03) 1. **This session: wrap** (this handoff + doc/roadmap updates). DONE. 2. **Next session: render residuals A → C → B** (a fresh render session — load the render design spec `docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md` first; render is a different subsystem than this session's physics). 3. **After A/B/C: membership Stage 2** (#4 uniform collision + #5 intrinsic entry), **gated on #7** — first re-test the cellar stairs; if still oscillating, Stage 2 (uniform collision) is the likely fix and earns its risk; if fine, Stage 2 stays optional faithfulness debt. --- ## 7. RUNNING THE CLIENT (PowerShell; `+Acdream` spawns at/near the Holtburg cottage) ```powershell $env:ACDREAM_DAT_DIR="$env:USERPROFILE\Documents\Asheron's Call"; $env:ACDREAM_LIVE="1" $env:ACDREAM_TEST_HOST="127.0.0.1"; $env:ACDREAM_TEST_PORT="9000" $env:ACDREAM_TEST_USER="testaccount"; $env:ACDREAM_TEST_PASS="testpassword" $env:ACDREAM_PROBE_FLAP="1"; $env:ACDREAM_PROBE_CELL="1" # for A: watch eyeInRoot + the camera sweep dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug *>&1 | Tee-Object -FilePath launch.log ``` Build green BEFORE launching. Logs are UTF-16. Close gracefully (CloseMainWindow, not Stop-Process) so ACE clears the session in ~3-5s. --- ## 8. PICKUP PROMPT (copy-paste for the next session) ``` RENDER RESIDUALS A → C → B at the Holtburg cottage doorway. The cottage doorway "flap" is FIXED this session (membership Stage 1 + the blue-hole render-root clobbering) — inside-looking-out renders correctly, user-verified. Continue on branch claude/thirsty-goldberg-51bb9b (do NOT branch/worktree; do NOT push without asking; NEVER git stash/gc — a shared stash is under investigation). PowerShell on Windows; launch logs are UTF-16 (Select-String / ripgrep --encoding utf-16-le, NOT GNU grep). READ FIRST: 1. docs/research/2026-06-03-membership-and-bluehole-shipped-handoff.md (THIS handoff — what shipped, the full remaining-issues list §3, KEEP/DON'T-REDO §4, key files §5, sequencing §6). 2. docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md (the render redesign — R1 shipped + correct; R1b/R2 are the remaining phases; §2 the binary model, §4 the seal mechanics). THE JOB — three known render phases, in order (render is a DIFFERENT subsystem than the physics this handoff describes — load the render design spec, work in GameWindow.OnRender / CellVisibility / PhysicsCameraCollisionProbe / PortalVisibilityBuilder / ParticleRenderer): - A (camera collision — highest-leverage, do FIRST): interior walls go grey/transparent WHILE INSIDE because the 3rd-person chase EYE sits outside the player's cell ([flap-cam] eyeInRoot=n) → near walls back-face/clip away. Port retail SmartBox::update_viewer spring-arm to keep the eye in the cell. A [flap-sweep] camera-collision already runs (desiredBack/eyeBack/pulledIn) but doesn't fully contain the eye — refine it. Shares the eye-outside-cell mechanism with C, so A shrinks C. - C (R2 outside-looking-in, do SECOND — the big one): from the street the cottage renders as a see-through box (interior walls transparent + ground texture over the floor). Build the outdoor→interior portal render — retail PView::DrawPortal @ 0x5a5ab0 (ConstructView(CBldPortal) → recurse → DrawCells the interior through the door's clip). Not built yet. - B (R1b / #104, do LAST — smallest): particles bleed through the ground/floor when looking out. Cell-clip scene particles (give the ParticleEmitter a cell link via ParentCellId; clip per-cell in the DrawInside loop). WORKFLOW: render is verified ON SCREEN (user's eyes) + the probes (ACDREAM_PROBE_FLAP/_VIS/_SHELL/ _CELL), never off the test suite (CLAUDE.md). For AC-specific render algorithms use the WB pipeline + the retail decomp as the oracle (design spec). superpowers:writing-plans → executing-plans; verify each phase at a user visual gate. Get a screenshot EARLY for render bugs (memory: render-one-gate). DO NOT RE-LITIGATE: the flap is FIXED (membership + render-root, both committed + verified — KEEP §4). Do NOT re-add a CurrCell write inside the per-entity ResolveWithTransition/ResolveCellId (that was the blue-hole clobbering bug). Do NOT re-add the 5ca2f44 pre-check. AFTER A/B/C: membership Stage 2 (uniform collision #4 + intrinsic building entry #5), but GATE it on issue #7 first — re-test the cellar stairs (ACDREAM_PROBE_CELL + ACDREAM_CAPTURE_RESOLVE): if the ~0.2m/tick Z-oscillation persists, uniform collision is the likely fix and Stage 2 earns its risk; if the stairs are fine, Stage 2 stays optional faithfulness debt (the membership works without it). TEST BASELINE: Core 1295 pass / 5 fail (2 step-up BSPStepUp B1/D4 + 3 door-collision DoorCollision/ DoorBug) = pre-existing, NOT yours. App 174 green. ``` ```