docs: wrap session — doorway flap FIXED (membership + blue-hole); A/B/C render residuals next

Canonical handoff: docs/research/2026-06-03-membership-and-bluehole-shipped-handoff.md
(what shipped: membership Stage 1 ordered-CELLARRAY port + the blue-hole render-root
clobbering fix; the full remaining-issues list — A camera-collision, B R1b particles,
C R2 outside-looking-in, Stage 2 membership, #7 stairs, the 5-test baseline; KEEP/
DON'T-REDO; key files + decomp anchors; copy-paste pickup prompt for next session).

- ISSUES.md: recorded the cottage doorway flap DONE (both causes) in Recently closed.
- render design spec §7: R1 + flap marked DONE; A/B/C mapped to the next render phases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-06-03 11:09:57 +02:00
parent 79fb6e7c23
commit a1b49f9b24
3 changed files with 326 additions and 0 deletions

View file

@ -3674,6 +3674,33 @@ Unverified. The likely culprits, ranked by suspected probability:
# Recently closed
## Cottage doorway "flap" — [DONE 2026-06-03 · 22a184c + e5457f9 + 79fb6e7] membership pick + render-root clobbering (the TWO causes)
**Status:** DONE (user-verified inside-looking-out)
**Closed:** 2026-06-03
**Commits:** `b44dd14`/`bc56545`/`22a184c`/`e5457f9` (membership Stage 1) + `79fb6e7` (blue-hole render-root)
**Component:** physics/membership, rendering
**Resolution:** The cottage doorway flap (full-screen bluish void + flicker) had TWO independent
causes, both fixed this session:
1. **Membership pick ping-pong**`CellTransit.BuildCellSetAndPickContaining` used an unordered
`HashSet` + a pre-pick fork in `FindEnvCollisions`. Ported retail's verbatim ordered `CELLARRAY`
`find_cell_list` pick (current cell at index 0, interior-wins-break) + the collide-then-pick order
(`find_env_collisions``check_other_cells`, removing the pre-pick that swapped collision geometry
with the cell mid-tick). `[cell-transit]` 47→13→DELTA=0 while standing still. (Stage 1; faithful.)
2. **Render-root clobbering**`CellGraph.CurrCell` ("the player's cell", the render root) was
written by the PER-ENTITY `ResolveWithTransition`/`ResolveCellId`. A jumping Holtburg NPC near the
doorway overwrote the player's render root every tick → render rooted at the NPC's tiny connector
cell (0170) instead of the player's room (0171) → only its ~8-tri shell drew, rest = GL clear color
= the blue void. Fixed: `CurrCell` is now written ONLY by the player
(`PhysicsEngine.UpdatePlayerCurrCell` via `PlayerMovementController.UpdateCellId`).
Diagnosed via `[flap-cam]`/`[shell]`/`[cell-transit]` (player stable in 0171, render rooted at 0170
for 77,951 frames). **Residuals are NOT the flap** — three known render phases remain (A
camera-collision: walls grey while inside; B R1b/#104 particles through ground; C R2 outside-looking-in
transparent walls) + membership Stage 2 (uniform collision + intrinsic entry, faithfulness debt). Full
record: [`docs/research/2026-06-03-membership-and-bluehole-shipped-handoff.md`](research/2026-06-03-membership-and-bluehole-shipped-handoff.md).
## Phase U.4c doorway "flap" — [DONE 2026-05-31 · 0ee328a] indoor visibility rooted at the camera eye
**Status:** DONE (Phase U.4c flap sub-step)

View file

@ -0,0 +1,289 @@
# 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<uint>``ICollection<uint>` (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.
```
```

View file

@ -236,6 +236,16 @@ the closest-first order (early-Z) are the mitigations. Measure at the R1 gate; d
> (handoff §9). Phases are NOT batched past a gate.
### R1 — Unified per-cell `DrawInside` (the core) — THE make-or-break phase
> **STATUS 2026-06-03: R1 + the doorway flap are DONE (user-verified inside-looking-out).** The flap
> had TWO causes, both fixed: (1) the membership pick — ported the verbatim ordered-`CELLARRAY`
> `find_cell_list` pick + collide-then-pick (Stage 1); (2) **render-root clobbering**`CellGraph.CurrCell`
> was written by the per-entity `ResolveWithTransition`, so a jumping NPC near the doorway overwrote the
> player's render root → the "blue hole"; fixed by making `CurrCell` player-only (`UpdatePlayerCurrCell`).
> Inside-looking-out renders correctly. **Remaining residuals are the next phases, NOT R1 regressions:**
> A = camera-collision (walls grey while inside; eye outside the cell) → fold into R4 or a focused phase;
> B = particles through the floor → **R1b** (#104); C = transparent walls from the street → **R2** below.
> Canonical: [`docs/research/2026-06-03-membership-and-bluehole-shipped-handoff.md`](../../research/2026-06-03-membership-and-bluehole-shipped-handoff.md).
**Retail anchors:** `RenderNormalMode @ 0x453aa0` (binary decision), `PView::DrawInside @ 0x5a5860`,
`ConstructView @ 0x5a57b0`, `DrawCells @ 0x5a4840` (the seal + the three per-cell loops), fact 8
(visibility is the cull).