diff --git a/docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md b/docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md new file mode 100644 index 0000000..dd2d512 --- /dev/null +++ b/docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md @@ -0,0 +1,485 @@ +# 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`