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>
This commit is contained in:
parent
4fa3390592
commit
3e9ff7accb
1 changed files with 485 additions and 0 deletions
485
docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md
Normal file
485
docs/research/2026-05-27-a8-rr7-reverted-wb-port-handoff.md
Normal file
|
|
@ -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`
|
||||
Loading…
Add table
Add a link
Reference in a new issue