Both documents retained for historical reference. The new full-WB-port design + plan (2026-05-26-phase-a8-wb-full-port-design.md + plan,ea60d1f+651e7e2) replace them. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
26 KiB
Phase A8 — Render-frame restructure to WB-faithful order (design)
Date: 2026-05-26
Phase: A8 — render-frame restructure (continuation after R3.5 v1+v2 pause)
Status: Design approved 2026-05-26. Ready for superpowers:writing-plans.
Branch: claude/strange-albattani-3fc83c (worktree)
HEAD at start of restructure session: 2bfeafd (R3.5 v2)
Predecessor docs (REQUIRED reading before execution):
- docs/research/2026-05-26-a8-r3.5-restructure-handoff.md — full story of why we paused; the architectural mismatch
- docs/research/2026-05-26-a8-entity-taxonomy.md — approved entity-taxonomy fix-shape (already shipped as R1+R2)
- docs/superpowers/plans/2026-05-26-phase-a8-replan.md — the R1+R2+R3 plan; R1+R2+R3 already shipped, restructure replaces R3.5
- references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-239 — the proven WB
RenderInsideOutreference
TL;DR
Replace the R3.5 v2 "frankenstein" render frame (initial terrain + depth-clear-if-inside + stencil pipeline, three workarounds layered on top of each other) with WB's RenderInsideOut order verbatim: skip initial sky+terrain when cameraInside, delete the depth-clear block, and add a stencil-gated sky step inside the indoor branch so windows show real sky (closing R4 Issue B). Unify the two gate flags (cameraInsideCell lenient + cameraReallyInside strict) into a single strict cameraInside flag computed via PointInCell. Pre-restructure falsification spike (RR0) determines whether R4 Issues A + C are pre-existing on main (out of A8 scope, file as separate issues) or A8-caused (expand A8 scope and re-brainstorm).
Six tasks: RR0 falsification spike → RR1 revert R3.5 v1+v2 → RR2 restructure render frame → RR3 verify sky-renderer state contract → RR4 visual verification matrix → RR5 ship docs. ~2.5 hours assuming RR0 doesn't trigger scope expansion.
Brainstorm outcomes (the five settled questions)
Q1 — initial terrain when inside: WB-faithful (skip when cameraInside).
Q2 — sky through windows: add stencil-gated sky step between MarkAndPunch and the terrain re-draw.
Q3 — Issue C (entry transparent floor): defer to post-restructure investigation; file as separate issue if pre-existing.
Q4 — gate-flag asymmetry: unify on cameraReallyInside (renamed cameraInside); grace mechanism in CellVisibility stays alive for non-render consumers.
Q5 — R3.5 v1+v2 mechanics: revert as two new commits before restructure (clean diff against R3 baseline).
Plus pre-restructure RR0 spike: falsify R4 Issues A and C against 60f07bc (R3 baseline) and main to settle whether they're A8-caused before committing the restructure.
Architecture
One new gate flag (replaces the two-flag split)
Computed once at the start of the per-frame render pass, next to the existing visibility computation:
var visibility = _cellVisibility.ComputeVisibility(camPos);
bool cameraInside = visibility?.CameraCell is not null
&& CellVisibility.PointInCell(camPos, visibility.CameraCell);
This becomes the SINGLE source of truth for "are we rendering as if the camera is inside a cell?" Drives:
- Sky pre-scene gate (line ~7090)
- Initial terrain draw gate (line ~7115)
- Stencil branch entry (was at ~7174)
- Weather post-scene gate (line ~7260)
- Sky-PES debug call (line 7032)
Previous flags cameraInsideCell (lenient, grace-aware) and cameraReallyInside (strict, no grace) both DISAPPEAR — replaced by the single cameraInside.
playerInsideCell (line 7023, used for lighting) STAYS UNCHANGED — it has different semantics (third-person chase camera enters interiors before player body does; lighting must follow player) and uses IsInsideAnyCell(_playerController.Position) which is grace-independent.
The grace mechanism in CellVisibility.FindCameraCell (3-frame stickiness via _cellSwitchGraceFrames) stays alive — only the render-frame consumers stop benefiting from it. Non-render consumers (e.g. IsInsideAnyCell for player-side checks) may still depend on grace; auditing them is out of scope for this phase.
Render frame when cameraInside == true (WB-faithful)
Matches VisibilityManager.RenderInsideOut Steps 1–4 verbatim:
1. Skip initial sky (gate inverted from `!cameraInsideCell` to `!cameraInside`)
2. Skip initial terrain (NEW gate: `if (!cameraInside) _terrain?.Draw(...)`)
3. NO depth-clear (block deleted)
4. MarkAndPunch — stencil bit 1 + depth=1.0 at camera-cell exit portals
5. IndoorPass — cell mesh + cell statics + building shells
(stencil OFF after MarkAndPunch cleanup; DepthFunc.Less; depth writes ON)
6. EnableOutdoorPass — StencilFunc.Equal(1, 0x01) read-only; DepthFunc.Less
7. Stencil-gated sky — _skyRenderer.RenderSky with DepthMask OFF
(sky color writes through punched depth=1.0 only at portal silhouettes;
DepthMask off so depth stays at the punch value, letting the next step's
terrain re-draw win the depth test wherever closer terrain exists)
8. Stencil-gated terrain re-draw — _terrain?.Draw with stencil still Equal(1)
(terrain at portal silhouettes overwrites sky color with terrain color
where terrain Z is closer than the punched 1.0)
9. Stencil-gated OutdoorScenery — WbDrawDispatcher with EntitySet.OutdoorScenery
(stabs/procedural at portal silhouettes; depth-tested against terrain)
10. DisableStencil — restores normal stencil/color/depth state
11. LiveDynamic — WbDrawDispatcher with EntitySet.LiveDynamic
(server-spawned entities, depth-tested against everything else)
12. Skip weather pass (line 7260, gate inverted to `!cameraInside`)
Render frame when cameraInside == false (unchanged from pre-A8)
1. Sky
2. Terrain
3. Single Draw(set: All)
4. Weather
The else branch in the current R3.5 v2 implementation is preserved 1:1.
Components
Existing infrastructure consumed as-is (no changes)
-
IndoorCellStencilPipeline(src/AcDream.App/Rendering/IndoorCellStencilPipeline.cs)UploadPortalMesh(IEnumerable<LoadedCell>)— triangle-fan portal meshMarkAndPunch(Matrix4x4 viewProjection)— WB Steps 1+2 GL state machineEnableOutdoorPass()— stencil read Equal(1, 0x01); color on; depth normalDisableStencil()— restores normal stencil/color/depth state
-
WbDrawDispatcher(src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs)EntitySet.IndoorPass— cell mesh + cell statics + building shells (Q2-R2 taxonomy)EntitySet.OutdoorScenery— outdoor stabs + proceduralEntitySet.LiveDynamic— server-spawned (player, NPCs, dropped items)EntitySet.All— pre-A8 behavior for the outdoorelsebranch
-
CellVisibility—PointInCell,FindCameraCell,ComputeVisibility, grace mechanism. No changes. -
WorldEntity.IsBuildingShell(R1) — set atLandblockLoaderfromLandBlockInfo.Buildings. No changes.
Net code surface in this restructure
Only GameWindow.cs changes structurally. The change is bounded to the render-frame block at lines ~7008–7242 (~234 lines, mostly comments + a single if/else). After the restructure:
- Two existing local declarations renamed (
cameraInsideCellandcameraReallyInside→cameraInside). - Two existing comment blocks pared back (the long R3.5 saga explanation deleted).
- One block deleted (the depth-clear
if (cameraReallyInside) _gl!.Clear(...)). - Two gate inversions (the sky pre-scene gate at ~7090, the initial terrain gate added at ~7115).
- One new step added (stencil-gated sky between MarkAndPunch and the terrain re-draw).
Estimated net diff: ~80 LOC removed, ~30 LOC added.
Pre-flight check on SkyRenderer.RenderSky
The stencil-gated sky step relies on the inherited stencil state from EnableOutdoorPass surviving the _skyRenderer.RenderSky call. If SkyRenderer toggles EnableCap.StencilTest internally for any reason, our gate is lost.
RR3 reads src/AcDream.App/Rendering/SkyRenderer.cs and confirms no internal Enable(StencilTest), Disable(StencilTest), StencilFunc, StencilOp, or StencilMask calls. If clean, RR3 commits a verification note + a one-line comment in GameWindow.cs at the sky-step call site referencing the verified line range. If dirty, RR3 introduces a save/restore wrapper at the call site (cheap fixed-function state save).
Data flow
Indoor frame, step-by-step
Pre-frame: visibility computed; cameraInside == true; player-lb computed; particle-systems ticked.
[ glClear(Color|Depth) — happens earlier in the render frame ]
[ Step 1: skip sky pre-scene ]
[ Step 2: skip initial terrain ]
[ Step 3: no depth-clear ]
[ depth buffer state: cleared to 1.0 from the frame's glClear; no terrain yet ]
[ stencil buffer state: cleared by IndoorCellStencilPipeline.MarkAndPunch ]
Step 4: MarkAndPunch(viewProjection)
- GL state on entry: arbitrary (whatever the prior frame left)
- GL state on exit (per IndoorCellStencilPipeline cleanup):
StencilTest disabled, StencilMask 0xFF, ColorMask all, DepthMask on,
DepthFunc.Less
- Effects on buffers:
depth buffer = 1.0 at portal-silhouette pixels (rest unchanged from glClear)
stencil buffer = 1 at portal-silhouette pixels (rest = 0)
Step 5: IndoorPass — WbDrawDispatcher.Draw(set: IndoorPass)
- GL state: stencil OFF, DepthFunc.Less, DepthMask on
- Effects:
depth buffer writes wall/floor Z values at indoor pixels
these depths now occlude the punched 1.0 for any pixel where the
indoor mesh is closer (which is most of them, since the punch is
only at portal silhouettes which are themselves where indoor mesh
is typically NOT)
Step 6: EnableOutdoorPass()
- GL state on exit: StencilTest enabled, StencilFunc.Equal(1, 0x01),
StencilMask 0x00 (read-only), ColorMask color, DepthMask on,
DepthFunc.Less
Step 7: stencil-gated sky — _skyRenderer.RenderSky(...)
- Pre-step GL state change: DepthMask off
- Pre-step assumption: SkyRenderer does NOT touch stencil (verified by RR3)
- Effects:
color writes sky at pixels where stencil = 1 (portal silhouettes)
depth UNCHANGED (DepthMask off) — still at punched 1.0
- Post-step GL state change: DepthMask on (restore)
Step 8: stencil-gated terrain re-draw — _terrain?.Draw(...)
- GL state: same as step 7 minus the DepthMask off; depth writes on
- Effects:
color writes terrain at portal silhouettes WHERE terrain Z < 1.0 (i.e.
all of them in practice — terrain is closer than the far plane)
depth writes terrain Z at those pixels (overwriting the 1.0 punch and
the sky's writes)
RESULT: at portal silhouettes, sky shows ONLY where terrain doesn't
occlude (sky beyond the terrain horizon); terrain shows where it
does (near-field landscape through the window)
Step 9: stencil-gated OutdoorScenery — WbDrawDispatcher.Draw(set: OutdoorScenery)
- GL state: inherited from step 8
- Effects: scenery stabs + procedural depth-test against terrain at
portal silhouettes; what's not occluded by terrain or walls writes
correctly
Step 10: DisableStencil()
- GL state on exit: StencilTest disabled, normal masks, DepthFunc.Less
Step 11: LiveDynamic — WbDrawDispatcher.Draw(set: LiveDynamic)
- GL state: stencil off, depth normal
- Effects: server-spawned entities (player, NPCs, dropped items)
depth-test against everything else and write where visible
Step 12: skip weather pass
Outdoor frame, step-by-step
Step 1: sky pre-scene — _skyRenderer.RenderSky(...) [existing call]
Step 2: terrain — _terrain?.Draw(...) [existing call]
Step 3: dispatcher single call — WbDrawDispatcher.Draw(set: All) [existing call]
Step 4: weather post-scene — _skyRenderer.RenderWeather(...) [existing call]
Outdoor path is structurally unchanged. The outdoor else branch in R3.5 v2 is preserved verbatim.
Error handling
This is fixed-function GL state machine surgery. There's no error path in the traditional sense — wrong state → wrong pixels.
Defensive measures in the design:
- Pre-flight check (RR3) before relying on stencil-gated sky — verify
SkyRenderer.RenderSkydoesn't touch stencil. If it does, wrap. - GL state restoration after each step —
IndoorCellStencilPipeline.MarkAndPunchalready restores state on exit per Tasks 1–6 review.EnableOutdoorPass/DisableStencilsymmetric. No cleanup gap. - No-op when
_indoorStencilPipeline is null— keep the existing R3 guard at line 7174 (&& _indoorStencilPipeline is not null). Restructured branch falls through to outdoor path if the pipeline failed to initialize. - Null-flow analysis preserved — the existing R3 pattern of restating
visibility?.CameraCell is not nullinside the if-condition stays; lets the body usevisibility.CameraCellwithout null-forgiving.
No new try/catch. Per CLAUDE.md "don't add error handling for scenarios that can't happen." GL state machine errors are debugger / RenderDoc territory, not runtime exception handling.
Testing strategy
Unit tests
No new unit tests required for the restructure itself. The restructure only changes GL state and call ordering in GameWindow.cs; these are visual-verification-only by nature.
Existing test surfaces that lock the non-GL bits:
- R1 LandblockLoader IsBuildingShell tagging — 2 tests
- R2 WbDrawDispatcher EntitySet partition — 7 tests
- Tasks 1–6 infrastructure — 26 dispatcher + 5 IndoorCellStencilPipeline + 2 PortalPolygons + 1 ProbeVisibility = 34 tests
All shipped, all passing in the documented flaky window. The restructure consumes them but doesn't change them.
Possible new test (RR3 contingent): if SkyRenderer.RenderSky requires a no-state-touch overload, one test asserting it doesn't enable/disable stencil. Likely not needed.
Visual verification matrix (RR4)
| Scenario | Acceptance | Action if fails |
|---|---|---|
| Cottage interior (ground floor) | Walls solid; sky visible through windows | Investigate stencil-gated sky step or IndoorPass partition |
| Cottage cellar | Walls solid; no grass overlay through stairs from inside | Check if pre-existing on R3 baseline (use RR0 evidence) |
| Holtburg Inn (multi-room) | Walls solid; no see-through to adjacent rooms | Check IndoorPass partition; cross-cell-portal limitation (#102) |
| Dungeon | Corridor walls solid; indoor lighting; no terrain leak | Same as cottage interior; dungeons have no building shells |
| Exit transition (indoor → outdoor) | Clean: sky + terrain + entities depth-test correctly; no through-ground objects; no missing walls | If A reproduces and was confirmed pre-existing in RR0 → documented limitation, not blocker |
| Entry transition (outdoor → indoor) | Clean OR if Issue C reproduces and was confirmed pre-existing in RR0 → documented limitation, not blocker | Same as Issue C path |
Risk register
| Risk | Likelihood | Mitigation |
|---|---|---|
| SkyRenderer toggles stencil internally, breaking stencil-gated sky | Low | RR3 pre-flight check; wrap if needed |
| Stencil-gated sky costs noticeable GPU time | Very low | One quad/skybox per frame; negligible against existing draw load |
| Removing depth-clear exposes unexpected Z artifacts | Low | RR4 catches; if widespread → revisit Q1 design; if isolated → file as separate issue |
| Issue A turns out to be A8-caused per RR0 | Medium | RR0 explicitly handles this branch; we re-brainstorm if it fires |
| Sky-through-window writes with DepthMask off but sky's own depth-test rejects fragments | Low | If sky uses DepthFunc.Less + far plane, may need DepthFunc.Always or DepthFunc.Lequal for the sky step |
| Cell-id flicker at doorway threshold after dropping grace gate | Very low | PointInCell epsilon + cached-cell fast-path provide hysteresis; only relevant if camera oscillates at AABB edge |
Tasks
Task RR0 — Falsification spike
Goal: determine whether R4 Issues A and C are pre-existing on main (out of A8 scope) or A8-caused (in scope).
Steps:
- Launch
2bfeafd(current HEAD). Stand inside Holtburg cottage. Walk outside. Re-enter. Reproduce or rule out:- Issue A: exit transition "objects through ground + walls missing"
- Issue C: entry transition "floor transparent showing cellar + wrong texture"
git checkout 60f07bc -- src/AcDream.App/Rendering/GameWindow.cs(R3 baseline render frame only). Rebuild. Relaunch. Reproduce A + C.git stashor checkoutmainin a side worktree. Rebuild. Relaunch. Reproduce A + C.
Outcomes:
- All three reproduce → pre-existing on
main. File A + C as new ISSUES.md entries. Restructure goes ahead unchanged. - Only R3 + HEAD reproduce → A8-caused (specifically by R3 work). Re-brainstorm A and/or C in the same session; expand RR2 scope.
- Only HEAD reproduces → R3.5 v1/v2 patches caused them. Revert clears them. Restructure goes ahead; RR4 confirms.
Restore working tree to 2bfeafd HEAD before proceeding.
Task RR1 — Revert R3.5 v1 + v2
git revert 2bfeafd 38d5374 --no-edit
Two new commits land. HEAD logically equivalent to 60f07bc (R3 baseline) plus two revert commits. Build green expected.
Task RR2 — Restructure render frame to WB-faithful order
File: src/AcDream.App/Rendering/GameWindow.cs
Changes:
-
Compute the unified gate next to the visibility call (~line 7011):
bool cameraInside = visibility?.CameraCell is not null && CellVisibility.PointInCell(camPos, visibility.CameraCell);Delete the previous
bool cameraInsideCell = visibility?.CameraCell is not null;line. -
Sky-PES debug call (line 7032): change
cameraInsideCell→cameraInside. -
Sky pre-scene gate (line 7090): change
if (!cameraInsideCell)→if (!cameraInside). -
Add initial terrain gate (line 7115): wrap
_terrain?.Draw(...)inif (!cameraInside) { ... }. -
Delete the entire R3.5 comment block +
cameraReallyInsidedeclaration (lines ~7125–7148). Replace with a brief comment referencing the design doc. -
Delete the depth-clear-if-inside block (lines ~7150–7155).
-
Restructure the indoor branch (lines ~7174–7233):
- Gate on
cameraInside && _indoorStencilPipeline is not null && visibility?.CameraCell is not null - Steps 4–11 per the data-flow section above
- Step 7 (new stencil-gated sky) inserted between EnableOutdoorPass and the terrain re-draw
- Gate on
-
Outdoor branch (
elseat line 7234): unchanged. -
Weather post-scene gate (line 7260): change
cameraInsideCell→cameraInside.
Build: dotnet build -c Debug --nologo
Test: dotnet test --nologo — failures within the documented 14-23 flaky window only.
Commit: feat(render): Phase A8 RR2 — restructure render frame to WB-faithful order
Task RR3 — Verify SkyRenderer state contract
File: src/AcDream.App/Rendering/SkyRenderer.cs (read-only)
Steps:
grep -n "Stencil\|StencilTest\|StencilFunc\|StencilOp\|StencilMask" src/AcDream.App/Rendering/SkyRenderer.cs.- Confirm no matches (or all matches are inside conditional code paths that won't fire at our call site).
- If clean: commit a verification note as a code comment at the stencil-gated sky call site in
GameWindow.csreferencing the verified line range, no behavior change. Tiny diff. - If dirty: factor a save/restore wrapper at the call site (
_gl.GetInteger(StencilFunc...)save,_skyRenderer.RenderSky(...), restore). Or add aRenderSkyKeepStencilStateoverload. Adapt design accordingly.
Commit: chore(render): Phase A8 RR3 — verify SkyRenderer doesn't touch stencil state
Task RR4 — Visual verification matrix
Per the matrix in the testing strategy section above. For each scenario, record PASS/FAIL with notes. Append to a follow-up handoff doc or a section of this design doc.
Acceptance gate for shipping RR5:
- All four building-type scenarios (cottage interior, cellar, inn, dungeon) PASS
- Sky-through-windows visible (Issue B closed)
- Exit transition: A reproduces only if RR0 confirmed pre-existing
- Entry transition: C reproduces only if RR0 confirmed pre-existing
If any scenario fails an acceptance criterion AND RR0 ruled out pre-existing → STOP, open a /investigate skill session for the failure.
Task RR5 — Ship docs
Files: docs/ISSUES.md, CLAUDE.md
Changes:
-
docs/ISSUES.md:- Move #78 to "Recently closed" with the restructure commit SHA.
- If RR0 found Issue A pre-existing → file as new issue (next sequential ID, likely #104).
- If RR0 found Issue C pre-existing → file as new issue (next sequential ID, likely #105 or #106).
- #102 (cross-cell-portal far-side visibility) and #103 (cellar terrain Z-fight from outside) remain open from RR2's predecessor work.
-
CLAUDE.md:- Update the A8 paragraph from "PAUSED for restructure" → "SHIPPED 2026-05-2X" with the restructure commit list.
- Update "currently working toward" if M1.5 sub-target needs refinement.
Commit: ship(render): Phase A8 — render-frame restructure SHIPPED
Alternatives considered and rejected
Alt 1 — Hybrid: keep initial terrain unconditional + remove depth-clear
The Q1 alternative. Per the handoff's analysis, this BREAKS the #78 primary fix: cottage floor at world Z=+0.02 (EnvCell render lift) would lose to terrain at world Z=-0.01 (zFightTerrainAdjust nudge), re-exposing outdoor visibility through the floor. Rejected.
Alt 2 — Status quo: keep R3.5 v2's frankenstein
CLAUDE.md's "no workarounds" rule explicitly forbids this kind of layered patch-on-patch design. The R3.5 v1 + v2 patches were band-aids; per the handoff, "Two patch attempts (R3.5 v1 and R3.5 v2) papered over parts of the symptom but kept producing new edge cases — the exact 'patching symptoms' anti-pattern CLAUDE.md and the predecessor revert handoff explicitly call out." Rejected.
Alt 3 — Full WB port including BuildingPortalGPU + 3-bit stencil + occlusion queries
Matches WB completely, including Step 5 (cross-cell-portal visibility) and the 3-stencil-bit cross-building pipeline. Multi-week work. Already explicitly deferred in the R1+R2+R3 entity-taxonomy approval (filed as #102). Not in this restructure's scope.
Alt 4 — Investigate retail's polygon-clip scissor approach before designing
Retail uses screen-space polygon clipping (not stencil) for visibility (per acclient_2013_pseudo_c.txt:432709 PView::DrawCells). Different mechanism, equivalent observable behavior. The original Phase A8 investigation already settled on the WB stencil approach for acdream's modern GL pipeline. Re-opening this would be a major detour. Rejected.
Alt 5 — Unify gate on cameraInsideCell (lenient) instead of cameraReallyInside (strict)
Q4 alternative. Would re-introduce the grace-frame issues that R3.5 v1+v2 were trying to patch (depth-clear runs while camera is outside, stencil pipeline marks a cell the camera isn't in). Rejected.
Alt 6 — Unify AND delete the grace mechanism entirely
Q4 third option. Stronger: also rips out _cellSwitchGraceFrames from CellVisibility. Saves ~10 LOC but requires auditing all non-render consumers (player-cell logic, audio, lighting). Likely overkill for this restructure; deferred. The render frame doesn't depend on grace under the chosen design.
Open follow-ups (post-RR5)
Highest ISSUES.md ID as of design time: #101 (DONE 2026-05-25). RR5 files new issues from #102 onward. Tentative numbering:
- #102 (to be filed by RR5) — Cross-cell-portal far-side visibility (WB Step 5 deferral). Source: the entity-taxonomy approval doc's "first ship approximation" of marking only camera's-own-cell portals.
- #103 (to be filed by RR5) — Outdoor-to-indoor cellar terrain Z-fight (out-to-in artifact). Source: the predecessor handoff's "deep-cell terrain occlusion" note.
- R4 Issue A (filed by RR5 if RR0 confirms pre-existing on
main) — exit "objects through ground + walls of other buildings missing." - R4 Issue C (filed by RR5 if RR0 confirms pre-existing on
main) — entry "transparent floor showing cellar + wrong texture." - Grace-mechanism cleanup — Q4 third option; future cleanup phase if no consumer regresses.
- Retail polygon-clip port — long-term alternative to WB stencil if visual gaps surface; out of milestone scope.
RR5 reads docs/ISSUES.md at filing time to confirm the next available ID and adjusts the numbering above accordingly.
Self-review notes
- Placeholder scan: no TBDs, no TODOs, no "similar to" or "TODO: figure out" strings. Every section has actual content.
- Internal consistency: task RR0 outcome branches are explicit; RR2 changes match the architecture section step-by-step; RR3 contingency is named; RR4 acceptance gate references RR0 evidence.
- Scope check: six tasks, well-bounded, ~2.5 hours estimated. The restructure is one render-frame block in one file. RR0 + RR3 + RR4 are non-code tasks. No scope sprawl.
- Ambiguity check: the
cameraInsideflag semantics (strict, no grace) are stated once and used consistently. The stencil-gated sky'sDepthMask off + ondiscipline is stated explicitly. The outdoorelsebranch's verbatim preservation is named explicitly.
SUPERSEDED 2026-05-26 (PM) by 2026-05-26-phase-a8-wb-full-port-design.md.
After RR0 falsification (docs/research/2026-05-26-a8-rr0-falsification-findings.md) showed R4 Issues A + C were caused by R3 (not pre-existing on main), the "restructure" approach in this design was insufficient — the structural bug is rendering all 16 BFS-reachable cells at full screen extent, not just the depth-clear workaround. The new design ports WB's full RenderInsideOut + RenderOutsideIn with per-building cell scoping.
This document is retained for historical reference and roadmap discipline.