acdream/docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md
Erik 3e9ff7accb docs(research): A8 RR7 reverted — full WB-port handoff for next session
Four RR7 variants shipped + reverted in one session (RR7, RR7.1, RR7.2,
RR7.3). The root architectural mismatch: RR7 routed cell-mesh rendering
through ObjectMeshManager / WbDrawDispatcher.Draw(IndoorPass) — a per-
GfxObj batched pipeline. WB uses a separate EnvCellRenderManager (862
LOC) for cells; we never extracted it. Indoor branch fires correctly
after RR7.2 + RR7.3 but interior cell geometry doesn't render.

User direction (verbatim, 2026-05-27): port WB verbatim. No band-aids.
Visual test launch only when fix is ready; probe data verified first.

Handoff captures:
- Session log of all four RR7 attempts + why each failed
- Why WB over retail (modern GL fit + existing Phase N.4/N.5/O
  commitment to WB as rendering base)
- The full WB RenderInsideOut algorithm spec (Steps 1-5, line refs)
- 5-phase next-session plan (extract EnvCellRenderManager + deps,
  wire into landblock load, replicate RenderInsideOut byte-for-byte,
  probe trail mandatory before visual gate, single visual gate)
- Process rules carved from this session's mistakes (no visual gate
  without probe data first, no partial WB ports, no conceptual
  adaptations, trust-but-verify, slow at brainstorm not implement)

RR3-RR6 infrastructure remains shipped + tested in isolation
(Building/Registry/Loader/Dispatcher cellIds overload/Stencil pipeline).
Branch is at pre-A8 visual ("looks good") with infrastructure dormant.

Next session opens cold against the pickup prompt at the bottom of
the handoff doc.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-27 14:10:33 +02:00

485 lines
24 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.

# Phase A8 RR7 reverted — full WB port handoff (2026-05-27)
## TL;DR for next session
RR7 (render-frame integration) shipped 4 times in one session; all 4 broke
the visual differently. **All four are reverted.** Branch is back to the
pre-A8 visual ("looks good"). RR3-RR6 infrastructure (`Building`,
`BuildingRegistry`, `BuildingLoader`, `WbDrawDispatcher.Draw(cellIds:)`
overload, `IndoorCellStencilPipeline` 3-bit + occlusion-query) remains
shipped + tested in isolation.
**The fundamental mistake:** RR3-RR7 ported WB's RenderInsideOut Steps 1-4
**conceptually** but routed cell-mesh rendering through our
`ObjectMeshManager` / `WbDrawDispatcher.Draw(IndoorPass)` pipeline. WB
doesn't do that — WB has a separate `EnvCellRenderManager` (862 LOC) that
renders cells via a different path. Without extracting that, the indoor
branch fires (gate works post-RR7.2) but cell interiors never render →
flat fog-color floors.
**Next session's mission:** port WB **verbatim**, including extracting
`EnvCellRenderManager.cs` + dependencies into our tree. No conceptual
adaptations. No "modern equivalent" decisions. Follow WB byte-for-byte
where the algorithm runs, just as Phase O extracted WB's mesh path.
User direction (verbatim, 2026-05-27):
> "Either we port exact behavior from retail or we port exact behavior
> from WB. ... Make a detailed plan to port WB verbatim behaviour to fix
> this. No quickfixes or fixes that might cause issues down the line ...
> use superpowers but DONT stop me for questions, be perfect, no
> band-aids. When you have a visual test ready with all rendering fix
> for this you launch the client for me to verify."
User decision: **WB**. (See decision rationale in
"Why WB and not retail" below.)
## Session log — what was tried and why it failed
This session opened picking up RR2 (BuildingInfo data-shape spike,
shipped clean) and then drove RR3 → RR4 → RR5 → RR6 → RR7 as planned.
The four RR7-variant fix attempts came after the user reported broken
visuals at the first visual gate.
### Commits shipped this session, before revert
| SHA | Phase | Status now | What it did |
|---|---|---|---|
| `f44a9bf` | RR2 | **KEPT** | Findings doc — `BuildingInfo` data shape + WB walk algorithm |
| `f125fdb` | RR3 | **KEPT** | `Building` + `BuildingRegistry` + `BuildingLoader` + 10 unit tests |
| `f8d0499` | RR4 | **KEPT** | `LoadedCell.BuildingId` + landblock-load wiring + 1 test |
| `3361933` | RR5 | **KEPT** | `WbDrawDispatcher.Draw(cellIds:)` overload + 2 tests |
| `6a7894a` | RR6 | **KEPT** | `IndoorCellStencilPipeline` 3-bit + 9 occlusion-query/state methods |
| `3d28d70` | RR7 | **REVERTED** by `4fa3390` | GameWindow render-frame restructure |
| `a1a3e0e` | RR7.1 | **REVERTED** by `21dc72b` | `AllLoadedCells` + late-stamp on drain |
| `efe3520` | RR7.2 | **REVERTED** by `9aaae02` | `_buildingRegistries` key normalization |
| `56673e1` | RR7.3 | **REVERTED** by `07c5981` | Dat-driven BFS in BuildingLoader |
Net infrastructure shipped: 5 commits, ~1100 LOC of production + 13
unit tests. All correct in isolation. None of the integration code
remains on the branch.
### Visual-gate launches and what they revealed
**Launch v1 — RR7 alone (commit `3d28d70`)**
- User reported: "Yes looks good!"
- `[vis]` log: `branch=indoor` count = **0** (out of 47,266 outdoor
decisions). 17,748 frames had `inside=True really=True` (camera in an
indoor cell) — but the gate's `BuildingId is not null` check failed
every time.
- **Why "looks good" was misleading:** RR7's call site used
`drainedCells` (the per-frame `_pendingCells` drain). Cells streamed
in over many frames, but `BuildingLoader.Build` ran once per landblock
load with whatever was in drainedCells THAT frame. Most building cells
were stamped on a frame when they weren't yet drained, so
`BuildingId` stayed null. Then `cameraInsideBuilding=false`, the
outdoor branch ran with full sky + initial terrain. Visually
indistinguishable from pre-A8.
- **My process failure:** declared visual gate passed without reading
the `[vis]` data first. "Looks good" without diagnostic correlation
is not verification.
**Launch v2 — RR7 + RR7.1 (`a1a3e0e`)**
- User reported: "All textures are missing, ground, sky only buildings
and objects are visible. Looks much worse."
- `[vis]` log: `branch=indoor` STILL 0 of 163,670 (with 125,476
`inside=True`).
- **Why it got worse:** RR7.1 made `BuildingLoader.Build` use
`_cellVisibility.AllLoadedCells` (every loaded cell, not just the
drain) which stamped MORE cells with `BuildingId`. That made
`cameraInsideBuilding=true` for more frames. But the registry-key
lookup at the gate STILL missed (storage at `0xA9B4FFFF`, lookup at
`0xA9B40000` — see RR7.2 below). So `cameraInsideBuilding=true`
sky + initial terrain GATED OFF → indoor branch's inner gate
(`camBuildings.Count > 0`) FAILED → outdoor branch ran WITHOUT sky
and terrain → black through windows.
**Launch v3 — RR7 + RR7.1 + RR7.2 (`efe3520`)**
- User reported: missing texture indoors (screenshot shows light-grey
fog-color areas where cell interior surfaces should be).
- `[vis]` log: `branch=indoor` = **119,471** vs outdoor 2,910. Indoor
branch finally fires.
- **Why it still broke:** RR7.2 fixed the registry key. Indoor branch
fires, `MarkAndPunch` runs, `Draw(IndoorPass, cellIds: camCellIds)`
runs. Building shells (cottage walls / inn walls — the
`IsBuildingShell` entities) render. But cell-mesh entities
(registered with `MeshRef(envCellId, ...)`) don't produce a textured
floor. The `[vis]` data confirms the gate works; the visual confirms
the cell-mesh path doesn't.
**Launch v4 — RR7 + RR7.1 + RR7.2 + RR7.3 (`56673e1`)**
- User reported: still flat grey areas.
- **Why it still broke:** RR7.3 made BFS dat-driven so building
EnvCellIds is complete regardless of cell load timing. Confirmed
BFS short-circuiting was NOT the cause — `camCellIds` contains the
user's current cell, the cell-mesh entity is walked, but the floor
doesn't appear.
### Root cause (only fully understood at session end)
WB's `VisibilityManager.RenderInsideOut`
(`references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-239`)
renders the inside-building cells via:
```csharp
envCellManager!.Render(pass1RenderPass, _currentEnvCellIds);
```
This calls into a **separate manager class**
`EnvCellRenderManager.cs`, 862 LOC, also in WB — that handles cell
rendering with its own GL pipeline, separate from
`ObjectMeshManager.cs`. The two managers exist because cell rendering
has different requirements (per-cell texture batching, different
transparency handling, cell-portal-aware geometry) from per-GfxObj
rendering.
Our RR7 collapsed Steps 3 (cell rendering) and Step 4 (stencil-gated
outdoor) into:
```csharp
_wbDrawDispatcher!.Draw(camera, ..., cellIds: camCellIds,
set: EntitySet.IndoorPass);
```
The dispatcher's `IndoorPass` walks entities including cell-mesh
entities (created in `GameWindow.BuildInteriorEntitiesForStreaming` at
line ~5441 with `MeshRefs = new[] { cellMeshRef }` where
`cellMeshRef.GfxObjId = envCellId`). But `ObjectMeshManager`'s draw
path is fundamentally per-GfxObj batched + MDI; it has a dat-side
`PrepareEnvCellMeshData` path (line ~1184 of WB's ObjectMeshManager,
also in our extracted copy) but that path's output isn't wired into
the dispatcher's instance-buffer layout the same way GfxObj meshes
are. Building shells render (they ARE GfxObj entities with proper
mesh refs after hydration at line ~5160). Cell meshes don't render
correctly.
In short: **the cell-mesh entity scheme we use is an architectural
mismatch with WB's render algorithm.** WB renders cells through
`EnvCellRenderManager.Render(cellIdSet)` — a per-cell rendering call.
We render cells through `Dispatcher.Draw(set: IndoorPass)` — a
per-entity batched call. The two are not interchangeable.
## Why WB and not retail
User asked decisively: "Either we port exact behavior from retail or we
port exact behavior from WB. What do you want?"
I chose WB. Reasons:
1. **Retail's algorithm doesn't fit modern GL.** Retail's
`PView::DrawCells` at `acclient_2013_pseudo_c.txt:432709` uses
software polygon-clip rects (set per portal during recursive cell
traversal). Porting verbatim requires either (a) inventing a
modern-equivalent — which is what WB already did — or (b)
implementing per-fragment shader-discard against portal polygons,
which is expensive and non-trivial.
2. **WB is already our rendering base.** Phase N.4 (2026-05-08) adopted
WB as our rendering oracle. Phase N.5 made WB's bindless +
`glMultiDrawElementsIndirect` mandatory. Phase O (2026-05-21)
extracted WB's mesh + dat-handling code into our tree
(`references/WorldBuilder/` remains as read-reference, but the
actual pipeline files live at `src/AcDream.App/Rendering/Wb/`).
Adopting WB's `EnvCellRenderManager` + `VisibilityManager` is the
natural continuation.
3. **Modern code, retail behavior** — WB is the existing "modern code,
retail-equivalent behavior" port. WB's stencil-based RenderInsideOut
is the modern-GL realization of retail's polygon-clip algorithm.
The observable behavior matches.
4. **Same exact stack.** WB is MIT-licensed Silk.NET + .NET 10 +
DatReaderWriter — verbatim our stack. No translation cost.
5. **Tested by WB's developers.** WB's RenderInsideOut works in their
tool. Faithful porting means we inherit their validation.
## What WB's render frame actually does (the spec for the redo)
The render frame algorithm lives at
`references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-239`.
The `RenderInsideOut` method takes managers as parameters
(`portalManager`, `envCellManager`, `terrainManager`, `sceneryManager`,
`staticObjectManager`, `sceneryShader`). Each step:
### Step 1: Stencil bit 1 at our building's portals (lines 78-97)
- `Enable(StencilTest)`, `ClearStencil(0)`, `Clear(StencilBufferBit)`.
- `Disable(CullFace)`, `StencilFunc.Always(1, 0xFF)`,
`StencilOp(Keep, Keep, Replace)`, `StencilMask(0x01)`,
`ColorMask(false×4)`, `DepthMask(false)`, `Enable(DepthTest)`,
`DepthFunc.Always`.
- For each building containing the current cell: `portalManager?.RenderBuildingStencilMask(building, snapshotVP, false)`.
### Step 2: Punch depth at portals (lines 99-105)
- `DepthMask(true)`, `DepthFunc.Always`.
- For each building containing the current cell: `RenderBuildingStencilMask(building, snapshotVP, true)`.
### Step 3: Render OUR cells (stencil OFF) (lines 107-127)
- `ColorMask(true, true, true, false)` (note: alpha bit OFF — WB intentional choice).
- `DepthMask(true)`, `Disable(StencilTest)`, `DepthFunc.Less`.
- `sceneryShader?.Bind()`.
- Collect `_currentEnvCellIds` from `_buildingsWithCurrentCell.SelectMany(b => b.EnvCellIds)`.
- `envCellManager!.Render(pass1RenderPass, _currentEnvCellIds)`.
- If transparency enabled: `DepthMask(false)`, render transparent pass, `DepthMask(true)`.
### Step 4: Stencil-gated outdoor — terrain + scenery + static objects (lines 129-154)
- If `didInsideStencil` (we had buildings): `Enable(StencilTest)`,
`StencilFunc.Equal(1, 0x01)`, `StencilOp(Keep, Keep, Keep)`,
`StencilMask(0x00)`, `ColorMask(true, true, true, false)`,
`DepthMask(true)`, `Enable(CullFace)`, `DepthFunc.Less`.
- `terrainManager.Render(snapshotView, snapshotProj, snapshotVP, snapshotPos, snapshotFov)`.
- `sceneryShader?.Bind()`.
- If scenery enabled: `sceneryManager?.Render(pass1RenderPass)`.
- If static-objects/buildings shown: `staticObjectManager?.Render(pass1RenderPass)`.
### Step 5: Other-buildings' cells through portals (lines 156-232)
- Collect `_otherBuildings` from `_visibleBuildingPortals` filtering OUT
buildings that contain `currentEnvCellId`.
- For each other-building (per `_otherBuildings`):
- Read back previous frame's occlusion query
(`GetQueryObject(building.QueryId, ResultAvailable)`,
`GetQueryObject(... Result)`). Update `building.WasVisible`.
- Start new query: `BeginQuery(SamplesPassed, building.QueryId)`,
`building.QueryStarted = true`.
- **a. Mark Bit 2 (Ref=3, Mask=0x02) where Bit 1 set**
(`StencilFunc.Equal(3, 0x01)`, `StencilOp Replace`,
`StencilMask 0x02`, `ColorMask off`, `DepthMask off`,
`Disable(CullFace)`).
`portalManager?.RenderBuildingStencilMask(building, snapshotVP, false)`.
- `EndQuery(SamplesPassed)`.
- **b. Clear depth where Stencil == 3** (`StencilFunc.Equal(3, 0x03)`,
`StencilMask 0x00`, `DepthMask true`, `DepthFunc.Always`).
`RenderBuildingStencilMask(building, snapshotVP, true)`.
- **c. Render other-building's EnvCells gated by Stencil == 3**
(`ColorMask(true, true, true, false)`, `DepthFunc.Less`,
`Enable(CullFace)`). `sceneryShader.Bind()`.
`envCellManager.Render(pass1RenderPass, building.EnvCellIds)` (+ transparent).
- **d. Reset Bit 2 back to 0** for next iteration
(`StencilMask 0x02`, `StencilFunc.Always(1, 0x02)`,
`StencilOp Replace`, `ColorMask off`, `DepthMask off`).
`RenderBuildingStencilMask(building, snapshotVP, false)`.
### Cleanup (lines 234-238)
- `Disable(StencilTest)`, `StencilMask(0xFF)`, `ColorMask(true×3, false)`.
## Why our RR7 didn't match this
1. **No `envCellManager.Render(...)` call.** We routed cells through
`Dispatcher.Draw(IndoorPass)`, which is per-GfxObj-batched, not
per-cell.
2. **No separate transparency pass for cells.** Step 3's
`DepthMask(false) + Render(Transparent)` was missing.
3. **No `sceneryShader.Bind()` between passes.** WB's algorithm
assumes a specific shader is bound at each step; we never did.
4. **Step 5 missing entirely.** Cross-building visibility (cottage
cellar visible from cottage above, inn rooms visible through doors)
not implemented. Would have shipped in RR9 but RR7 should have at
least scaffolded the order.
5. **ColorMask alpha-bit pattern not preserved.** WB uses
`ColorMask(true, true, true, false)` deliberately — alpha-bit OFF.
Our outdoor branch's `Draw(All)` doesn't toggle alpha bit, but
WB's path does. Could affect alpha-to-coverage downstream.
## The plan for the next session
### Phase 1: Extract `EnvCellRenderManager` into our tree (~862 LOC)
Mirror Phase O's pattern:
1. Read `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/EnvCellRenderManager.cs`
in full.
2. Identify its dependencies — likely `GlobalMeshBuffer`,
`ObjectMeshManager` (already extracted), `TextureAtlasManager`,
`IRenderManager`, `RenderPass`, `SceneData`. Extract any missing
dependencies.
3. Copy `EnvCellRenderManager.cs` to
`src/AcDream.App/Rendering/Wb/EnvCellRenderManager.cs`.
4. Adapt namespaces (`Chorizite.OpenGLSDLBackend.Lib`
`AcDream.App.Rendering.Wb`).
5. Resolve any references to types we don't have. Stub or extract as
needed.
6. Build green. No tests yet at this step.
### Phase 2: Wire `EnvCellRenderManager` into the existing landblock load
`EnvCellRenderManager.Register(envCell, cellStruct, worldTransform, ...)` is
how cells join its registry. Currently we call `CellMesh.Build` at
`GameWindow.BuildInteriorEntitiesForStreaming` (line ~5423). Replace
that with the `EnvCellRenderManager` registration path — cell meshes
flow through ITS pipeline, not through ObjectMeshManager via fake-
GfxObj-id MeshRefs.
The `WorldEntity` we create with `MeshRefs = [cellMeshRef]` (line 5441)
becomes irrelevant for cell rendering — the EnvCellRenderManager owns
the cells, the dispatcher renders only entities that have real GfxObj
mesh refs.
### Phase 3: Replicate `VisibilityManager.RenderInsideOut` byte-for-byte
In `GameWindow.cs` render frame (after the per-frame `glClear` +
visibility computation), replace the `if (cameraInsideBuilding)
{ ... } else { ... }` block we shipped + reverted with a call to a
new method `RenderInsideOutAcdream` that follows WB's Steps 1-5 line by
line.
`PortalRenderManager.RenderBuildingStencilMask(building, vp, punch)` is
the other dependency. Extract from
`references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/PortalRenderManager.cs`
(702 LOC) — at minimum the stencil-mask method + its mesh upload path.
The plumbing may be reusable for our existing `IndoorCellStencilPipeline`.
Our `IndoorCellStencilPipeline` already implements WB's Steps 1+2 +
Step 5 a/b/c/d. The mismatch is **what calls them** — our code calls
them with `_indoorStencilPipeline.MarkAndPunch(...)` etc. WB calls them
via `portalManager.RenderBuildingStencilMask(building, vp, punch)`.
The pipelines are equivalent in spirit but the entry point differs.
Map our pipeline methods onto WB's interface signature so the
RenderInsideOut algorithm can call them by name.
### Phase 4: Probes BEFORE visual launches
Mandatory before any visual gate. Add (gated on
`ACDREAM_PROBE_VIS=1` or a new `ACDREAM_PROBE_ENVCELL=1` flag):
- **`[envcells]` per frame**: count of cells walked by
`EnvCellRenderManager.Render`, count of triangles drawn, the
cellId set being rendered.
- **`[stencil]` per frame**: vertex count uploaded for MarkAndPunch
(the existing pipeline emits this internally — surface it).
- **`[draworder]` per frame**: assertion that the algorithm ran each
step in the right order with the right GL state on entry.
When a visual gate fires:
- ALWAYS read the probe data FIRST. Confirm indoor branch fired,
envcells were rendered, stencil mask was non-empty.
- Compare probe data to expected (the design doc has the algorithm
spelled out).
- ONLY THEN ask the user for visual confirmation.
### Phase 5: Visual gate (single)
Once Phases 1-4 done + probe data confirms correct behavior:
launch the client for the user to verify. ONE gate. Not four.
## Open questions for the next session to investigate
These DON'T require user input — investigate during execution:
1. **`PortalRenderManager.RenderBuildingStencilMask` mesh upload.**
Does WB upload exit portal polygons differently than we do? Our
`UploadBuildingPortalMesh` (Phase A8 RR6) might map cleanly to
WB's expectation, or might need adjustment.
2. **`EnvCellRenderManager.Register` API.** What does it accept?
Compare to our `_pendingCellMeshes[envCellId] = cellSubMeshes`.
Identify the seam.
3. **Transparency pass.** WB's Step 3 has an `if
(state.EnableTransparencyPass)` second `Render(Transparent)` call.
We don't have a state object yet; need to either add one or pick
the default (likely enabled, since indoor transparency matters for
stained glass, ornate furniture).
4. **Occlusion queries (RR9 scope).** RR7's job was Steps 1-4 only;
RR9 was supposed to add Step 5. But WB's RenderInsideOut has Step 5
inline — we shouldn't split it. Land Steps 1-5 together in the
next attempt. RR9 becomes a no-op or absorbed.
5. **`OutdoorScenery` EntitySet.** WB's Step 4 calls
`sceneryManager.Render(pass1RenderPass)` and
`staticObjectManager.Render(pass1RenderPass)` separately. We've
collapsed both into `Draw(EntitySet.OutdoorScenery)`. Need to
verify our `OutdoorScenery` partition matches what WB's two
managers cover, OR split them into two dispatch calls.
## Process rules for the next session (carved from this session's mistakes)
1. **No visual-gate launch without probe data first.** If the probe
says branch=indoor count = 0, the user's "looks good" doesn't
confirm A8 is working. Read the probe BEFORE asking the user.
2. **No partial WB ports.** Extract the manager. Wire it. Implement
the algorithm in full. No "Steps 1-4 now, Step 5 later." The steps
are interdependent; partial implementations have wrong cumulative
state.
3. **No conceptual adaptations of WB.** If WB does X, do X. If our
stack has a different way of doing it, either extract the WB way
into our stack OR use the existing analog 1:1 without "improvement."
No new abstractions invented mid-port.
4. **Trust-but-verify after every subagent dispatch.** Subagents
compile + pass tests in their isolation but don't verify visual
correctness. The harness pattern from #98 saga applies: build the
apparatus first, then trust evidence over plausible-looking code.
5. **Acknowledge the cost-of-failure asymmetry.** Each "fix" that
doesn't work costs the user a launch cycle, screenshot review,
bug-report write-up. Three wrong fixes in a row > one fully-thought
fix. Slow down at the brainstorming step, not at the implementation
step.
## Files that remain shipped (RR3-RR6 infrastructure)
These work in isolation and stay on the branch:
| File | LOC | Tested |
|---|---|---|
| `src/AcDream.App/Rendering/Wb/Building.cs` | 57 | 2 tests |
| `src/AcDream.App/Rendering/Wb/BuildingRegistry.cs` | 73 | 4 tests |
| `src/AcDream.App/Rendering/Wb/BuildingLoader.cs` | 144 | 5 tests |
| `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` (additions: `Draw(cellIds:)` overload + `WalkEntitiesForTestByCellIds`) | +153 | 2 tests |
| `src/AcDream.App/Rendering/IndoorCellStencilPipeline.cs` (additions: 4 stencil-3-bit methods + 4 occlusion-query methods + UploadBuildingPortalMesh) | +243 | 0 tests (GL required) |
The `LoadedCell.BuildingId` field also persists (from RR4) — that's a
1-property addition to `CellVisibility.cs`. RR4's wire-in in
`GameWindow.cs` (the `_buildingRegistries` dict + the
`BuildingLoader.Build(...)` call at line ~5876 + the RemoveLandblock
callbacks) is **also reverted** by the RR7 revert chain — the dict and
all references to it are gone now. Confirm via:
```
grep -n _buildingRegistries src/AcDream.App/Rendering/GameWindow.cs
```
If zero matches, the revert is complete. If matches remain, RR4 needs
manual cleanup (likely a stray field declaration the revert didn't
catch).
## Pickup prompt for next session
> Read this entire handoff doc, then read these in order:
>
> 1. `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-239` (the RenderInsideOut algorithm we're porting verbatim)
> 2. `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/EnvCellRenderManager.cs` (the manager to extract — 862 LOC)
> 3. `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/PortalRenderManager.cs` (the other dependency — extract the stencil-mask method + any infrastructure)
> 4. `docs/architecture/worldbuilder-inventory.md` (what we've already extracted from WB and where it lives)
> 5. `docs/superpowers/plans/2026-05-26-phase-a8-wb-full-port.md` (the original A8 plan — IGNORE its RR7 design, follow this handoff doc's plan instead)
>
> Then brainstorm + write a fresh detailed plan covering:
> - The exact extraction list (every WB file to copy into our tree)
> - The exact wire-in points in GameWindow.cs
> - The probe trail with format specifications
> - The expected visual outcomes per step
> - The order of execution (extraction → wiring → probes → visual gate)
>
> Use the superpowers:writing-plans skill. The plan goes to
> `docs/superpowers/plans/2026-05-28-phase-a8-wb-render-inside-out-port.md`.
>
> Once the plan is written, execute it without stopping. No questions
> to the user mid-flight. When the visual test is ready, launch the
> client for visual confirmation. Read probe data BEFORE accepting any
> "looks good" report.
>
> User authorization (verbatim 2026-05-27): "use superpowers but DONT
> stop me for questions, be perfect, no bandaids."
## Key references
- Plan we deviated from: `docs/superpowers/plans/2026-05-26-phase-a8-wb-full-port.md`
- Design doc: `docs/superpowers/specs/2026-05-26-phase-a8-wb-full-port-design.md`
- WB extraction precedent (Phase O): commit `6a7894a`'s parent chain
- WB code root: `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/`
- This session's RR1 handoff (still relevant for project context):
`docs/research/2026-05-26-a8-wb-full-port-rr1-shipped-handoff.md`
- RR2 findings (BuildingInfo data shape — still accurate, useful for
understanding the building model):
`docs/research/2026-05-26-a8-buildings-data-shape.md`