acdream/docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md
Erik 29e306b0f6 docs: Phase A8 — mark prior restructure design+plan as SUPERSEDED
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>
2026-05-27 10:08:48 +02:00

26 KiB
Raw Blame History

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):


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 14 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 mesh
    • MarkAndPunch(Matrix4x4 viewProjection) — WB Steps 1+2 GL state machine
    • EnableOutdoorPass() — stencil read Equal(1, 0x01); color on; depth normal
    • DisableStencil() — 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 + procedural
    • EntitySet.LiveDynamic — server-spawned (player, NPCs, dropped items)
    • EntitySet.All — pre-A8 behavior for the outdoor else branch
  • CellVisibilityPointInCell, FindCameraCell, ComputeVisibility, grace mechanism. No changes.

  • WorldEntity.IsBuildingShell (R1) — set at LandblockLoader from LandBlockInfo.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 ~70087242 (~234 lines, mostly comments + a single if/else). After the restructure:

  • Two existing local declarations renamed (cameraInsideCell and cameraReallyInsidecameraInside).
  • 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:

  1. Pre-flight check (RR3) before relying on stencil-gated sky — verify SkyRenderer.RenderSky doesn't touch stencil. If it does, wrap.
  2. GL state restoration after each stepIndoorCellStencilPipeline.MarkAndPunch already restores state on exit per Tasks 16 review. EnableOutdoorPass / DisableStencil symmetric. No cleanup gap.
  3. 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.
  4. Null-flow analysis preserved — the existing R3 pattern of restating visibility?.CameraCell is not null inside the if-condition stays; lets the body use visibility.CameraCell without 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 16 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:

  1. 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"
  2. git checkout 60f07bc -- src/AcDream.App/Rendering/GameWindow.cs (R3 baseline render frame only). Rebuild. Relaunch. Reproduce A + C.
  3. git stash or checkout main in 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:

  1. 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.

  2. Sky-PES debug call (line 7032): change cameraInsideCellcameraInside.

  3. Sky pre-scene gate (line 7090): change if (!cameraInsideCell)if (!cameraInside).

  4. Add initial terrain gate (line 7115): wrap _terrain?.Draw(...) in if (!cameraInside) { ... }.

  5. Delete the entire R3.5 comment block + cameraReallyInside declaration (lines ~71257148). Replace with a brief comment referencing the design doc.

  6. Delete the depth-clear-if-inside block (lines ~71507155).

  7. Restructure the indoor branch (lines ~71747233):

    • Gate on cameraInside && _indoorStencilPipeline is not null && visibility?.CameraCell is not null
    • Steps 411 per the data-flow section above
    • Step 7 (new stencil-gated sky) inserted between EnableOutdoorPass and the terrain re-draw
  8. Outdoor branch (else at line 7234): unchanged.

  9. Weather post-scene gate (line 7260): change cameraInsideCellcameraInside.

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:

  1. grep -n "Stencil\|StencilTest\|StencilFunc\|StencilOp\|StencilMask" src/AcDream.App/Rendering/SkyRenderer.cs.
  2. Confirm no matches (or all matches are inside conditional code paths that won't fire at our call site).
  3. If clean: commit a verification note as a code comment at the stencil-gated sky call site in GameWindow.cs referencing the verified line range, no behavior change. Tiny diff.
  4. If dirty: factor a save/restore wrapper at the call site (_gl.GetInteger(StencilFunc...) save, _skyRenderer.RenderSky(...), restore). Or add a RenderSkyKeepStencilState overload. 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:

  1. 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.
  2. 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 cameraInside flag semantics (strict, no grace) are stated once and used consistently. The stencil-gated sky's DepthMask off + on discipline is stated explicitly. The outdoor else branch'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.