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>
This commit is contained in:
parent
5ca2f448d4
commit
298b3b92b8
1 changed files with 415 additions and 0 deletions
415
docs/research/2026-06-02-membership-verbatim-port-handoff.md
Normal file
415
docs/research/2026-06-02-membership-verbatim-port-handoff.md
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
# 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 ~6–8) — 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 544–562 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:308742–308831)
|
||||
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:308788–308825): 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 → ~6–8 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** (8–19 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 = 308788–308825; 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 ~3–5s; 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.
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue