acdream/docs/research/2026-06-02-membership-verbatim-port-handoff.md
Erik 298b3b92b8 docs(physics): handoff — verbatim find_cell_list port (the R1 membership flap fix)
Canonical pickup for a fresh session. R1 (per-cell DrawInside render) shipped + is
correct (cellar seals); it exposed a pre-existing cell-membership ping-pong (the
flap). Root cause: CellTransit.BuildCellSetAndPickContaining picks from an UNORDERED
HashSet, dropping retail find_cell_list's current-cell-first ordering (CELLARRAY
index-0 + interior-wins-break, pc:308742-308825). Next job: verbatim port of that
ordered pick, replacing the HashSet + the 5ca2f44 pre-check approximation. User
authorized breaking any physics to get membership faithful. Full diagnosis, verbatim
retail source, fix plan, KEEP/don't-redo, test baseline, and a copy-paste pickup
prompt in the doc.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 22:24:22 +02:00

415 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Handoff — Verbatim port of retail `find_cell_list` (the R1 "flap" fix) — 2026-06-02
> **Canonical pickup for the next (fresh) session.** Read this FIRST, then the linked
> design docs. This session shipped **R1 — the per-cell `DrawInside` render redesign** (the
> interior seal works: the cellar is solid), and that render redesign **exposed a pre-existing
> cell-MEMBERSHIP ping-pong** — the visual "flap" the user sees at every cottage threshold. The
> render is **correct**; the membership answer it consumes is **unstable**. The next job is a
> **verbatim port of retail `CObjCell::find_cell_list`'s containing-cell pick** to make
> membership stable.
---
## 0. THE MANDATE + the new authorization (user, 2026-06-02 — non-negotiable)
- The standing render mandate is unchanged: **fully working outdoor + indoor + dungeon rendering,
no shortcuts/bandaids, port from retail, architecturally-correct even if slower.** (See the
master render handoff + design spec, §13 links.)
- **NEW, EXPLICIT authorization (this is the key enabler):** *"You are allowed to break whatever
you want in the code to get the engine and membership working, it's OK!"* — i.e. **do the
faithful verbatim port even if it breaks other physics/movement tests or behavior.** Don't
tiptoe around the #98-area accumulated logic. Get the membership right, faithfully; fix any
breakage afterward. Run the physics suite to SEE what breaks (eyes open), don't avoid the port.
- The user also clarified the fidelity split: **physics/membership = strict, line-by-line
faithful decomp port** (this task); **rendering = retail-structured orchestration over the kept
WorldBuilder pipeline** (already done in R1 — do NOT re-port rendering from decomp).
- The user is **tired of probe-driven debug cycles** — do NOT ask them to run manual probe walks.
Diagnose from existing data + the existing apparatus (trajectory-replay harness, auto-logging
probes); verify the fix with a **normal visual test** (their eyes) + the deterministic harness.
---
## 1. THE MENTAL MODEL (render vs membership) — internalize this first
Two separate systems, in a strict producer→consumer relationship:
1. **Membership** (physics, `AcDream.Core.Physics`): every tick, answers ONE question —
*"which cell (room/space) is the player standing in?"* → a single `currentCell` per tick.
The world is carved into cells: cellar `0174`, stairs `0175`, main room `0171` (+ sub-cells
`0172`/`0173`), vestibule `0170`, outdoors `0031` (note: low id `0x31 < 0x100` ⇒ outdoor
landcell).
2. **Render** (`AcDream.App.Rendering`): `draw(currentCell)` — strictly **downstream**. If
`currentCell` is jittery, render faithfully redraws the jitter.
**The flap = render correctly drawing an oscillating membership answer.** Proof it's membership,
not render: when membership is **stable** (standing still in the cellar) the render seals
perfectly (solid walls/floor, no bleed — user confirmed). The flap appears **only** at the
boundaries where the `[cell-transit]` log shows the cell answer oscillating. **Fix membership →
render goes stable; the seal already works.**
> Confidence note (honest): *very high* the flap is membership (the cellar-seals proof + the
> flap-tracks-cell-flips evidence). NOT a claimed 100% that zero render residual exists — a single
> *clean* outdoor→indoor transition has never been observed (the ping-pong masks it), so a small
> render tear on a genuine one-time crossing *could* exist. If so it's separate + isolated, and
> becomes visible once membership is stable. The membership fix is unambiguously the right next
> step regardless.
---
## 2. WHAT THIS SESSION SHIPPED (commit ledger, branch `claude/thirsty-goldberg-51bb9b`)
| SHA | What | Keep? |
|---|---|---|
| `7aca79f` | R0 — locked render-redesign **design spec** (brainstorm outcome) | ✅ authority |
| `ce7404b` | R1 **implementation plan** (per-cell DrawInside, TDD) | ✅ authority |
| `cf85ea4` | R1 Task 1 — `InteriorEntityPartition` (3-bucket entity split, TDD, 3 tests) | ✅ correct |
| `4b75c68` | R1 Task 2 — `InteriorRenderer` per-cell `DrawInside` loop | ✅ correct |
| `c4fd711` | R1 Task 3 — **binary render decision** in `GameWindow.OnRender` (indoor = DrawInside only) | ✅ correct |
| `58822fe` | R1 Task 4 — repurpose the `WbDrawDispatcher.cs:1756` `ParentCellId==null` bypass (#78) | ✅ correct |
| `5ca2f44` | **membership pre-check approximation** (current-cell-first explicit check) | ⚠️ **REPLACE** (see §5) |
Working tree is **clean** at `5ca2f44` (untracked = session screenshots + launch logs only).
R1 (the render redesign) is done and **proven correct by the visual gate** (cellar seals). The
only open R1 blocker is the membership flap (this handoff).
**Design + plan docs (read after this handoff):**
- Design spec: [`docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md`](../superpowers/specs/2026-06-02-render-pipeline-redesign-design.md)
- R1 plan: [`docs/superpowers/plans/2026-06-02-render-r1-per-cell-drawinside.md`](../superpowers/plans/2026-06-02-render-r1-per-cell-drawinside.md)
- Master render handoff: [`docs/research/2026-06-02-render-pipeline-redesign-handoff.md`](2026-06-02-render-pipeline-redesign-handoff.md)
- Retail render reference: [`docs/research/2026-06-02-retail-render-pipeline-full-reference.md`](2026-06-02-retail-render-pipeline-full-reference.md)
---
## 3. THE PROVEN DIAGNOSIS (the flap = membership ping-pong)
Captured from the user's R1 walk (`ACDREAM_PROBE_CELL`, `[cell-transit]` lines). **59 cell
transitions in one cottage walk** (a clean walk should be ~68) — oscillating at every boundary:
- **Stairs ↔ cellar** (`0175 ↔ 0174`) at ~(154.3, 9.4, 93.1). Crucially, the **foot Z oscillates
~0.2 m/tick** (93.07 ↔ 93.27; low Z = cellar, high Z = stairs). ⇒ membership is faithfully
following a *bouncing position* — this looks like a **SEPARATE stairs/ramp physics instability**
(step-up/step-down, #98 family), not (only) the pick. **See §8 — do not conflate.**
- **Room ↔ room** (`0171 ↔ 0173 ↔ 0172`) at **constant Z = 94.0**, tiny X/Y movement. ⇒ pure
membership-**pick** non-determinism (the unordered HashSet). This is the verbatim-port target.
- **Vestibule ↔ outdoors** (`0170 ↔ 0031`) at ~(155, 16). `0031` is an outdoor landcell, so each
flip swings the binary render decision to the *outdoor* path → the full sky/world/NPCs flash
("bluish background"). Fixed by the same current-first hysteresis (vestibule wins while it
contains you).
The `[vis]` log confirms the mechanism: as the root cell flips, the `OutsideView` + visible-cell
set flip with it (e.g. room `0171``outside(polys=0)` sealed vs stairs `0175`
`outside(polys=1)` sky-through-portal), so the through-door landscape appears/disappears = the flap.
Evidence files (this session, may still be present in the worktree root): `launch-r1.log`,
`launch-fix.log` (UTF-16 — read with `Select-String` / ripgrep `--encoding utf-16-le`, NOT GNU grep).
---
## 4. ROOT CAUSE + the verbatim retail target
### 4.1 acdream (the bug)
`CellTransit.BuildCellSetAndPickContaining` (`src/AcDream.Core/Physics/CellTransit.cs`, the body
of `FindCellSet`):
- The candidate set is an **unordered `HashSet<uint>`** (declared ~line 433).
- The current cell IS added first (~line 447, indoor seed) — **but `HashSet` does not preserve
insertion order**, and the set's contents churn tick-to-tick at a boundary, so the enumeration
can surface a neighbour before the current cell.
- The pick (the `foreach (uint candId in candidates)` interior pass, ~lines 544562 post-`5ca2f44`):
iterates the HashSet in arbitrary order, returns the **first** interior cell whose `CellBSP`
contains the sphere center (interior-wins). Outdoor fallback = a `gx/gy` XY-column computation
(NOT retail's `point_in_cell` on landcells — acdream landcells have no `point_in_cell`).
- The player's `CellId` is set from this: `ResolveWithTransition` (`PhysicsEngine.cs:608`) returns
the swept `sp.CurCellId` (lines 880/901), which `FindEnvCollisions` (`TransitionTypes.cs:1958`)
sets from `FindCellSet`; `PlayerMovementController.cs:1296` does `UpdateCellId(resolveResult.CellId,
"resolver")`. **A1 (swept membership) IS ported — the divergence is purely the pick ordering.**
### 4.2 retail (the verbatim source) — `CObjCell::find_cell_list @ 0x52b4e0` (pc:308742308831)
Reads verbatim this session. The structure:
```c
// edi = arg4 = the CELLARRAY (an ORDERED DArray<CELLINFO{cell_id, cell*}>)
num_cells = 0; added_outside = 0;
objcell_id = arg1->objcell_id; // the CURRENT cell
cell0 = (objcell_id >= 0x100) ? CEnvCell::GetVisible(objcell_id) : CLandCell::GetVisible(objcell_id);
if (objcell_id >= 0x100) CELLARRAY::add_cell(edi, objcell_id, cell0); // CURRENT at INDEX 0 (pc:308766)
else CLandCell::add_all_outside_cells(arg1, arg2, arg3, edi);
// expand: for each cell already in the array, call its find_transit_cells (vtable[0x80]) (pc:308782)
for (i = 0; i < edi->num_cells; i++)
if (edi->cells[i].cell) edi->cells[i].cell->vtable[0x80](arg1, arg2, arg3, edi, arg6);
// THE PICK (pc:308788308825): iterate the array IN ORDER from index 0, interior-wins-break
*arg5 = nullptr;
for (i = 0; i < edi->num_cells; i++) {
cell = edi->cells[i].cell;
if (cell) {
// point relative to the cell's block offset:
Vector3 p = arg3->center - LandDefs::get_block_offset(arg1->objcell_id, cell->cell_id);
if (cell->vtable[0x84](&p) != 0) { // point_in_cell (pc:308810)
*arg5 = cell; // set result on ANY containing cell
if ((int16_t)cell->cell_id >= 0x100) { // interior?
arg6->hits_interior_cell = 1;
break; // INTERIOR-WINS — stop
}
}
}
}
// (then do_not_load_cells prune, pc:308829+ — out of scope for the flap)
```
**The load-bearing facts:** (a) the current cell is at **index 0**; (b) the pick iterates **in
order** and **breaks on the first interior-containing cell**. So **the current cell is tested
FIRST — if you're still inside it, it wins and the search stops.** That ordered, current-first
iteration IS the hysteresis: *you stay in your current cell until the center genuinely leaves it,
never flipping to an overlapping neighbour.* acdream's unordered `HashSet` discarded that ordering.
> `CELLARRAY::add_cell` definition was not located by grep (`void CELLARRAY::add_cell(` → no match;
> only call sites at pc:279012/288076/308766/309860/309960/310030/310054/310208/317064/317218).
> Behaviorally it appends to the ordered array; treat the candidate collection as **ordered +
> deduped** (the HashSet already dedups; an ordered-dedup collection is the faithful model). The
> fresh session can locate add_cell via Ghidra MCP (`/decompile_function`) or `acclient.h` (CELLARRAY
> = `acclient.h:31574`) if exact dedup semantics are wanted.
### 4.3 The fix (the fresh session's task)
Replace the unordered `HashSet` candidate set with an **ordered, deduped collection** (mirror
retail `CELLARRAY`: current cell at index 0, neighbours in BFS add-order, unique) and port the
pick **verbatim**: iterate from index 0; for each **interior** cell, `point_in_cell` via
`BSPQuery.PointInsideCellBsp(cell.CellBSP.Root, localCenter)`; first interior-containing →
return (break). This is the current-first hysteresis that stops the ping-pong.
- Thread the new ordered-collection type through the methods that build candidates:
`BuildCellSetAndPickContaining`, `FindTransitCellsSphere`, `AddAllOutsideCells`,
`CheckBuildingTransit` (they currently take `HashSet<uint> candidates`). Changing the type is the
invasive part the user authorized.
- **Honest scope:** the **interior** pick is fully verbatim-portable. The **outdoor fallback**
(the `gx/gy` XY-column) stays an acdream adaptation — acdream landcells lack retail's
`CLandCell::point_in_cell` (they're a terrain grid). Mark it clearly. The flap is all
interior/boundary, so the verbatim interior pick covers it.
- Suggested collection: a small `CellArray` class — `List<uint>` (order) + `HashSet<uint>` (O(1)
dedup); `Add(id)` appends iff new; ordered enumeration; `Contains`; `Count`. Exposes its list as
`IReadOnlyCollection<uint>` for the `out cellSet` return.
---
## 5. REPLACE the committed pre-check approximation (`5ca2f44`)
`5ca2f44` added an **explicit current-cell-first pre-check** in `BuildCellSetAndPickContaining`
(before the interior-pass `foreach`): if the current cell is interior and its `CellBSP` contains
the center, return it. This achieves the *property* but NOT retail's ordered-array *structure*, and
it still diverges on the multi-neighbour edge (hash-order among non-current candidates). **The user
wants this replaced by the verbatim ordered-`CELLARRAY` port (§4.3).** When you implement the
ordered pick, **delete the pre-check** (it becomes redundant).
- **Keep** the regression guard test added in `5ca2f44`:
`CellTransitFindCellSetTests.TwoOverlappingCells_CurrentCellWinsTheStraddle` (two-direction
`[Theory]`). It documents the current-cell-first invariant and passes under the verbatim port.
Note: it does **not** go RED against the bug statically (the HashSet happens to enumerate the
current cell first when the set is small/unchurned — see §7), so it's a guard, not the RED repro.
The real verification is the harness + the visual flap gate.
---
## 6. THE WORKFLOW for the fresh session (this is a PHYSICS port)
Per CLAUDE.md's mandatory faithful-port workflow (the triangle-Z / frame-swap lessons):
1. **Grep named — DONE.** `find_cell_list` found at pc:308742.
2. **Read decomp — DONE.** §4.2 has the verbatim pseudo-C of the pick.
3. **WRITE PSEUDOCODE** (the step skipped this session): translate retail `find_cell_list`'s
candidate-build + ordered pick to readable pseudocode in `docs/research/*_pseudocode.md` before
porting. This catches misreads.
4. **PORT FAITHFULLY** line-by-line (§4.3): ordered `CellArray` + in-order current-first
interior-wins pick. Same control flow as retail. Don't "improve."
5. **CONFORMANCE TEST**: extend `CellTransitFindCellSetTests` — a multi-neighbour straddle where
the current cell wins by *order* (not just by containment), and an indoor↔outdoor straddle
(vestibule stays vestibule while it contains you).
6. **VERIFY** (no manual probe walks): the deterministic harness (`CellTransitFindCellSetTests`,
`CellarUpTrajectoryReplayTests`, `DoorBugTrajectoryReplayTests`), then **run the FULL physics
suite to see breakage** (`dotnet test tests/AcDream.Core.Tests`), then the **visual flap gate**
(user walks normally; `ACDREAM_PROBE_CELL` auto-logs so you can confirm the transition count
drops 59 → ~68 without asking them to do anything extra).
**Superpowers:** you may go straight to `superpowers:writing-plans``superpowers:executing-plans`
for the port (the design is clear — a heavy `brainstorming` pass is probably unnecessary). Use
`superpowers:systematic-debugging` if the flap doesn't fully clear and you need to chase a residual.
Use `superpowers:test-driven-development` for the conformance tests.
---
## 7. WHY the static unit test alone won't catch it (important nuance)
The pre-check / the bug interplay: `.NET HashSet<uint>` with a small, unchurned set tends to
enumerate in insertion order, and the current cell is added first — so a *static* single-tick
test where the current cell contains the center already returns the current cell (the
`TwoOverlappingCells` guard PASSES even against the unfixed pick). The production ping-pong is
**dynamic** — it arises from (a) the candidate set's contents churning tick-to-tick (reordering
the enumeration), and/or (b) the foot position genuinely oscillating across a boundary (the stairs
Z-jitter, §8). The **ordered `CELLARRAY`** removes (a) by construction (deterministic current-first
order every tick). (b) is the separate physics issue. ⇒ verify dynamically (harness + visual),
not just by the static guard.
---
## 8. The SEPARATE stairs-physics suspicion (don't conflate with the pick)
On the stairs the foot **Z oscillated ~0.2 m/tick** while membership flipped `0175↔0174`. The room
flips were at **constant Z** (pure pick). So:
- The **room/vestibule** flips → fixed by the verbatim current-first pick (this task).
- The **stairs** flip may be a **separate physics-movement instability** (step-up/step-down on the
cellar stairs/ramp — the #98 family: "stuck at the last step", `Transition.AdjustOffset` /
`DoStepUp`). The current-first hysteresis will *dampen* it if the wobble stays inside the current
cell's BSP, but if the Z-oscillation is large enough to cross the cell boundary it will persist.
- **If the stairs still flap after the membership port lands and the room/door flaps are gone**,
that's the physics-movement target (a #98-area follow-up) — diagnose it the same evidence-first
way (the existing `ACDREAM_CAPTURE_RESOLVE` + the trajectory-replay harness; the user's
cellar-stairs is the repro). Do not block the membership port on it.
---
## 9. KEEP / DON'T-REDO (avoid re-litigating settled work)
**KEEP (correct, do not reopen for the flap):**
- All R1 render code: `InteriorEntityPartition`, `InteriorRenderer` (per-cell DrawInside loop),
the binary decision in `GameWindow.OnRender`, the `WbDrawDispatcher` gate fix. The cellar seals
— render is correct.
- `PortalVisibilityBuilder`, `ClipFrameAssembler`/`ClipFrame`, `EnvCellRenderer`, `TerrainModernRenderer`,
the WB mesh pipeline. The render design spec + R1 plan.
- The swept-membership chain (A1): `ResolveWithTransition` returning `sp.CurCellId`. Do NOT revert
to a static `ResolveCellId` re-derive.
- The `5ca2f44` regression test (`TwoOverlappingCells_CurrentCellWinsTheStraddle`).
**DON'T:**
- Don't reopen R1's render code chasing the flap — the flap is membership.
- Don't re-port *rendering* from the decomp (use the WB pipeline — CLAUDE.md).
- Don't add a render-side debounce/grace-period for the flap (bandaid — forbidden).
- Don't ask the user for manual probe walks (diagnose from existing data + harness; verify visually).
---
## 10. TEST STATE (baseline for regression judgement)
- **Pre-existing Core failures (NOT yours — verified):** the handoff's "5" — 2 step-up gaps (incl.
an A6.P4 door regression) + 3 door-collision apparatus / A6.P5. The **2
`DoorBugTrajectoryReplayTests` failures** (`TransientState live=0x87 harness=0x83`, e.g.
`LiveCompare_DoorBlocksFromOutside_Tick22760`) were **verified pre-existing this session** (they
fail without the membership change too). Plus the documented PhysicsResolveCapture/PhysicsDiagnostics
**static-leak flakiness** (819 failures across runs of identical code). ⇒ the deterministic
**membership net** (`CellTransit|FindEnvCollisions|CellGraph|Doorway|Cellar|DoorBug`) is the
reliable signal: it was **66 pass + 2 pre-existing DoorBug** with the pre-check.
- `tests/AcDream.App.Tests`: **174 green** (incl. `InteriorEntityPartitionTests` ×3, the flipped
`EntityClipTests` gate test).
- **Breakage is authorized** (§0): when the verbatim port lands, run the full physics suite, diff
the failure set against this baseline, and fix genuinely-new breakage (the port may legitimately
change membership-dependent test expectations — update them with retail-cited reasoning, don't
pin wrong values).
---
## 11. After the membership port: the remaining render arc
Once the flap is gone and the seal holds, resume the R1→R7 plan (design spec §7):
- **R1b** — per-cell particles (#104): "particles bleed through the ground when looking out the
door" — Scene-pass particles aren't cell-clipped (needs a cell link on `ParticleEmitter`; the
per-cell `DrawInside` loop makes this tractable).
- **R2** — outside-looking-in (`DrawPortal`): "interior walls are transparent when looking in a
window/door from outside" — no outdoor→interior portal render yet.
- **R3** — dungeons. **R4** — polish (the `CullMode.Landblock→None` winding; remove dormant WB
two-pipe scaffolding `Building`/`BuildingLoader`; conformance).
- The cottage seal (R1) is **not signed off** until the flap is gone — the membership port is the
R1 gate's remaining blocker.
---
## 12. KEY FILES + ANCHORS (quick index)
```
MEMBERSHIP (the task)
src/AcDream.Core/Physics/CellTransit.cs
FindCellSet (entry, ~388 single-sphere, ~412 multi-sphere)
BuildCellSetAndPickContaining (the candidate build + pick — THE function to port)
~433 candidates = new HashSet<uint>() ← the unordered set to replace
~447 candidates.Add(currentCellId) ← current added first (indoor seed)
~520 the 5ca2f44 pre-check ← DELETE when the ordered pick lands
~544 foreach interior pass (the pick) ← port verbatim (ordered, current-first)
~558 gx/gy outdoor fallback ← acdream adaptation (landcells lack point_in_cell)
FindTransitCellsSphere / AddAllOutsideCells / CheckBuildingTransit ← take `HashSet<uint> candidates` (re-type)
src/AcDream.Core/Physics/TransitionTypes.cs:1958 ← FindEnvCollisions calls FindCellSet (the swept membership)
src/AcDream.Core/Physics/PhysicsEngine.cs:608/880/901← ResolveWithTransition returns swept sp.CurCellId
src/AcDream.App/Input/PlayerMovementController.cs:1296 ← UpdateCellId(resolveResult.CellId, "resolver")
tests/AcDream.Core.Tests/Physics/CellTransitFindCellSetTests.cs ← conformance home (+ the kept guard)
(line numbers ≈ — shifted by 5ca2f44's ~22 lines; grep by method name)
RETAIL DECOMP (the verbatim source)
CObjCell::find_cell_list 0x52b4e0 pc:308742 (pick = 308788308825; add_cell @308766; point_in_cell vtable[0x84] @308810; find_transit_cells vtable[0x80] @308782)
CEnvCell::find_transit_cells 0x52c820 pc:309968
acclient.h: CELLARRAY 31574 ; CELLINFO 31925 ; SPHEREPATH 32625
Ghidra MCP (port 8081, patchmem.gpr) for /decompile_function on add_cell if exact dedup wanted.
RENDER (correct — context only)
src/AcDream.App/Rendering/InteriorRenderer.cs ← per-cell DrawInside loop
src/AcDream.App/Rendering/InteriorEntityPartition.cs ← 3-bucket entity split
src/AcDream.App/Rendering/GameWindow.cs (OnRender ~7530)← the binary decision
src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs:~1744 ← EntityPassesVisibleCellGate (gate fix)
```
---
## 13. RUNNING THE CLIENT + apparatus (no manual probe walks)
Per CLAUDE.md "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_CELL="1" # auto-logs [cell-transit]; lets you confirm the count drops, no manual walk
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 (`Select-String` / ripgrep `--encoding utf-16-le`).
- A client from THIS session may still be running (graceful close clears ACE in ~35s; a hard kill
leaves the session stuck ~3 min — see CLAUDE.md). Close gracefully before relaunching.
- Verify the flap visually (user's eyes) + the `[cell-transit]` count. Walk: outside `0031` → door
`0170` → room `0171` → stairs `0175` → cellar `0174`, and back. Room/door flap should be GONE.
---
## 14. PICKUP PROMPT (copy-paste for the fresh session)
```
VERBATIM PORT of retail CObjCell::find_cell_list's containing-cell pick — to fix the cell-MEMBERSHIP
ping-pong that the R1 render redesign exposed (the cottage "flap"). 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).
AUTHORIZATION (user, explicit): you may BREAK ANY physics/movement code or tests to get the engine +
membership working faithfully — breakage is OK, fix later. Do the faithful line-by-line port; don't
tiptoe around the #98-area logic. Run the physics suite to SEE what breaks. The user is tired of
probe walks — diagnose from existing data + the deterministic trajectory-replay harness; verify with
a normal VISUAL test (their eyes) + the auto-logging ACDREAM_PROBE_CELL.
READ FIRST (in order):
1. docs/research/2026-06-02-membership-verbatim-port-handoff.md (THIS handoff — full diagnosis,
the verbatim retail source §4.2, the fix §4.3, replace-the-pre-check §5, workflow §6, KEEP §9).
2. docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md (the render redesign — context; render is CORRECT + downstream of membership).
3. docs/superpowers/plans/2026-06-02-render-r1-per-cell-drawinside.md (R1 plan — what shipped).
THE JOB:
- Replace the UNORDERED HashSet candidate set in CellTransit.BuildCellSetAndPickContaining with an
ORDERED, deduped collection (retail CELLARRAY: current cell at index 0, BFS add-order) and port the
pick VERBATIM: iterate from index 0, point_in_cell per interior cell, first interior-containing wins
(break) — retail find_cell_list pc:308788-308825. This is the current-first hysteresis that stops
the ping-pong. Thread the new type through FindTransitCellsSphere/AddAllOutsideCells/CheckBuildingTransit.
- DELETE the 5ca2f44 current-first pre-check (the ordered pick supersedes it). KEEP its regression
test (TwoOverlappingCells_CurrentCellWinsTheStraddle).
- Outdoor fallback (gx/gy XY-column) stays an acdream adaptation (landcells lack point_in_cell) — mark it.
WORKFLOW (physics port — mandatory): grep named (done) → read decomp (done, §4.2) → WRITE PSEUDOCODE
(docs/research/) → PORT FAITHFULLY line-by-line → CONFORMANCE TEST → run full physics suite (see
breakage vs the §10 baseline; fix new breakage) → VISUAL flap gate (room/door flap GONE; [cell-transit]
count 59→~6-8). Use superpowers:writing-plans → executing-plans; test-driven-development for conformance.
PROVEN, DON'T RE-LITIGATE: the flap is MEMBERSHIP not render (cellar seals when membership is stable;
flap tracks the [cell-transit] ping-pong). R1 render is correct — do NOT reopen it. The 2 DoorBug
TransientState failures are PRE-EXISTING (verified). The stairs flip additionally shows the foot Z
oscillating ~0.2m/tick = a SEPARATE physics issue (#98 family, §8) — if it persists after the
membership port (room/door fixed), that's the next target; don't block on it.
GOAL: stable membership → the R1 seal holds with no flap. Then resume R1b (particles) → R2
(outside-looking-in) → R3/R4 per the design spec.
```