diff --git a/docs/superpowers/plans/2026-05-26-phase-a8-restructure.md b/docs/superpowers/plans/2026-05-26-phase-a8-restructure.md new file mode 100644 index 0000000..7dac1f1 --- /dev/null +++ b/docs/superpowers/plans/2026-05-26-phase-a8-restructure.md @@ -0,0 +1,1140 @@ +# Phase A8 — Render-Frame Restructure to WB-Faithful Order Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the R3.5 v2 "frankenstein" render frame (initial terrain + depth-clear-if-inside + stencil pipeline, three workarounds stacked) with WorldBuilder's `RenderInsideOut` order verbatim. Close R4 Issue B (sky doesn't render through windows). Match the proven reference. Pre-restructure falsification spike (RR0) determines whether R4 Issues A + C are pre-existing on `main` (out of A8 scope) or A8-caused (re-brainstorm before continuing). + +**Architecture:** Skip initial sky + terrain at the start of the render frame when the camera is inside a cell; delete the depth-clear-if-inside block entirely; inside the indoor branch insert a stencil-gated `_skyRenderer.RenderSky` step (DepthMask off) between `EnableOutdoorPass` and the terrain re-draw so windows show real sky; unify the two-flag asymmetry (`cameraInsideCell` lenient + `cameraReallyInside` strict) into a single strict `cameraInside` flag computed via `PointInCell`. Grace mechanism in `CellVisibility` stays alive for non-render consumers. + +**Tech Stack:** C# .NET 10, Silk.NET (OpenGL 4.3 + GL_ARB_bindless_texture + GL_ARB_shader_draw_parameters), xUnit. + +**Predecessor context (REQUIRED reading before starting):** +- [docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md](../specs/2026-05-26-phase-a8-restructure-design.md) — the approved design this plan implements +- [docs/research/2026-05-26-a8-r3.5-restructure-handoff.md](../../research/2026-05-26-a8-r3.5-restructure-handoff.md) — full handoff covering the R3.5 v1+v2 saga and architectural mismatch +- [references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-239](../../../references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs) — the proven WB reference; the restructure must match this verbatim through Step 4. Step 5 (3-stencil-bit cross-building) stays deferred. + +**Infrastructure preserved (consume as-is — these are already shipped + tested):** +- `src/AcDream.App/Rendering/IndoorCellStencilPipeline.cs` — `UploadPortalMesh` / `MarkAndPunch` / `EnableOutdoorPass` / `DisableStencil` +- `src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs` — `EntitySet.{IndoorPass, OutdoorScenery, LiveDynamic, All}` partition (R2) +- `src/AcDream.Core/World/WorldEntity.cs` — `IsBuildingShell` flag (R1) +- `src/AcDream.App/Rendering/CellVisibility.cs` — `PointInCell` static, `ComputeVisibility`, `FindCameraCell` with grace mechanism + +**HEAD at start of session:** `2bfeafd` (R3.5 v2) +**Clean revert points:** `60f07bc` (R3 baseline) or further back to `55f26f2` (R2) + +--- + +## File Structure + +| File | What changes | Why | +|---|---|---| +| `docs/research/2026-05-26-a8-rr0-falsification-findings.md` | NEW doc capturing RR0 outcome on three branches | Decision evidence for whether A+C are A8-caused or pre-existing | +| `src/AcDream.App/Rendering/GameWindow.cs` | Render-frame block, lines ~7011–7260: gate-flag rename + skip-sky+terrain when inside + delete-depth-clear + add stencil-gated sky inside indoor branch | The restructure itself | +| `src/AcDream.App/Rendering/SkyRenderer.cs` | READ ONLY in RR3; possibly a small wrapper invocation in `GameWindow.cs` if dirty | Pre-flight verify stencil state contract | +| `docs/ISSUES.md` | Move #78 to "Recently closed"; file new follow-ups (cross-cell-portal, cellar Z-fight from outside, and A/C if RR0 confirms pre-existing on main) | Ship docs | +| `CLAUDE.md` | Update A8 paragraph: `PAUSED` → `SHIPPED 2026-05-2X` with the restructure commit list | Roadmap discipline | + +**Decomposition rationale:** the restructure is localized to one render-frame block in one file. The other files are docs. Six tasks because of the mandatory pre-spike (RR0) and the SkyRenderer verification (RR3); the core restructure is one commit (RR2). + +--- + +## Task RR0: Falsification spike — are Issues A + C pre-existing or A8-caused? + +**Goal:** Determine whether R4 Issues A ("objects through ground + walls missing on exit") and C ("transparent floor showing cellar on entry") reproduce on `main` (no A8 work) and on `60f07bc` (R3 baseline, before R3.5 patches). This is the decision gate for whether the restructure as designed is sufficient or whether scope must expand. + +**Files:** +- Create: `docs/research/2026-05-26-a8-rr0-falsification-findings.md` + +**Method:** three side-by-side launches. Human visual observation per scenario. Findings doc records what was seen on each branch and the decision outcome. + +- [ ] **RR0-S1: Launch HEAD (2bfeafd) and observe Issues A + C** + +Build + launch in the foreground (the human watches the client window): + +```powershell +$proc = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue +if ($proc) { + $proc.CloseMainWindow() | Out-Null + if (-not $proc.WaitForExit(5000)) { $proc | Stop-Process -Force } +} +Start-Sleep -Seconds 3 + +$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call" +$env:ACDREAM_LIVE = "1" +$env:ACDREAM_TEST_HOST = "127.0.0.1" +$env:ACDREAM_TEST_PORT = "9000" +$env:ACDREAM_TEST_USER = "testaccount" +$env:ACDREAM_TEST_PASS = "testpassword" + +dotnet build src\AcDream.App\AcDream.App.csproj -c Debug --nologo +dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "launch-a8-rr0-head.log" +``` + +Human walks `+Acdream` through these scenarios: +1. Stand outside Holtburg cottage. Walk into cottage. Watch the entry transition for ~3 seconds. Observe: does the cottage floor go transparent showing cellar below, with "wrong texture" flicker? +2. Stand inside cottage. Walk back outside. Watch the exit transition for ~3 seconds. Observe: do objects appear visible through the ground? Do walls of other buildings (adjacent cottages) appear missing? + +Record per-scenario: +- Issue C (entry transparent floor): YES / NO / INTERMITTENT +- Issue A (exit through-ground): YES / NO / INTERMITTENT + +Graceful close (use `$proc.CloseMainWindow()` pattern). + +- [ ] **RR0-S2: Test against R3 baseline by checking out 60f07bc's GameWindow.cs only** + +`60f07bc` is the R3 baseline (before R3.5 v1+v2 patches). It includes R1+R2 infrastructure (which we want), but the render-frame block is pre-patch. Single-file checkout keeps all other R1+R2 code intact: + +```bash +git checkout 60f07bc -- src/AcDream.App/Rendering/GameWindow.cs +dotnet build src\AcDream.App\AcDream.App.csproj -c Debug --nologo +``` + +Expected: build green (R1+R2 deps unchanged between 60f07bc and HEAD). + +Launch + repro the same two scenarios from S1, logging to `launch-a8-rr0-r3.log`: + +```powershell +dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "launch-a8-rr0-r3.log" +``` + +Record per-scenario: Issue C, Issue A outcomes. + +Graceful close. Restore HEAD's `GameWindow.cs`: + +```bash +git checkout HEAD -- src/AcDream.App/Rendering/GameWindow.cs +dotnet build src\AcDream.App\AcDream.App.csproj -c Debug --nologo +``` + +Expected: build green; working tree clean. + +- [ ] **RR0-S3: Test against main using a side worktree** + +Main has no A8 work at all. Need a separate worktree to test it without disturbing the current worktree: + +```bash +git fetch origin main +git worktree add ../tmp-main-baseline main +``` + +Expected: new worktree created at `../tmp-main-baseline` checked out to `main`. + +Build + launch from the side worktree: + +```powershell +Push-Location ..\tmp-main-baseline + +$proc = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue +if ($proc) { + $proc.CloseMainWindow() | Out-Null + if (-not $proc.WaitForExit(5000)) { $proc | Stop-Process -Force } +} +Start-Sleep -Seconds 3 + +dotnet build src\AcDream.App\AcDream.App.csproj -c Debug --nologo +dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "launch-a8-rr0-main.log" + +Pop-Location +``` + +Record per-scenario: Issue C, Issue A outcomes. + +Graceful close. Remove the side worktree: + +```bash +git worktree remove ../tmp-main-baseline +``` + +- [ ] **RR0-S4: Write findings doc and commit** + +Create `docs/research/2026-05-26-a8-rr0-falsification-findings.md` with this template (fill in the actual observations from S1–S3): + +```markdown +# A8 RR0 falsification — are Issues A and C pre-existing or A8-caused? + +**Date:** 2026-05-26 +**Method:** three-branch launch + visual repro at Holtburg cottage entry / exit transitions. + +## Observations + +| Branch | Commit | Issue A (exit through-ground) | Issue C (entry transparent floor) | +|---|---|---|---| +| HEAD | `2bfeafd` (R3.5 v2) | [YES / NO / INTERMITTENT] | [YES / NO / INTERMITTENT] | +| R3 baseline | `60f07bc` | [YES / NO / INTERMITTENT] | [YES / NO / INTERMITTENT] | +| main | [current main SHA] | [YES / NO / INTERMITTENT] | [YES / NO / INTERMITTENT] | + +## Decision + +Based on the table: + +- [ ] **Outcome 1:** All three reproduce → A + C are pre-existing on main. Restructure proceeds RR1→RR5 unchanged. RR5 files A + C as new ISSUES.md entries. + +- [ ] **Outcome 2:** Only R3 + HEAD reproduce → A and/or C caused by R3 work specifically. **PAUSE** the plan. Re-brainstorm via `superpowers:brainstorming` to address them; update the design doc; resume. + +- [ ] **Outcome 3:** Only HEAD reproduces → R3.5 v1/v2 patches caused them. Revert in RR1 clears them. Restructure proceeds; RR4 confirms cleanup. + +Selected: [outcome #] + +## Logs + +- `launch-a8-rr0-head.log` +- `launch-a8-rr0-r3.log` +- `launch-a8-rr0-main.log` +``` + +Commit the findings: + +```bash +git add docs/research/2026-05-26-a8-rr0-falsification-findings.md +git commit -m "$(cat <<'EOF' +docs(research): Phase A8 RR0 — falsification findings (A + C pre-existing?) + +Side-by-side launch on HEAD (2bfeafd) / R3 baseline (60f07bc) / main. +Visual repro at Holtburg cottage entry + exit transitions. Records +whether Issues A and C are A8-caused or pre-existing. + +Decision gate for whether the restructure as designed is sufficient +or whether RR2 scope must expand. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **RR0-S5: Decision gate — proceed or pause** + +If selected outcome is 1 or 3 → proceed to RR1. + +If selected outcome is 2 → **STOP**. Re-invoke `superpowers:brainstorming` to expand the design. Do NOT proceed to RR1. + +--- + +## Task RR1: Revert R3.5 v1 + v2 as two new commits + +**Goal:** Clean diff against R3 baseline for the restructure that follows. The v1+v2 patches are subsumed by RR2 (the stencil branch will use `cameraInside`; depth-clear gets deleted entirely). + +**Files:** +- Modify (via `git revert`): `src/AcDream.App/Rendering/GameWindow.cs` + +- [ ] **RR1-S1: Revert R3.5 v2 (depth-clear gate)** + +```bash +git revert 2bfeafd --no-edit +``` + +Expected: one new commit lands. `git log -1` shows `Revert "fix(render): Phase A8 R3.5 v2 — gate depth-clear on cameraReallyInside too"`. + +- [ ] **RR1-S2: Revert R3.5 v1 (stencil branch gate)** + +```bash +git revert 38d5374 --no-edit +``` + +Expected: one new commit lands. `git log -1` shows `Revert "fix(render): Phase A8 R3.5 — gate stencil branch on PointInCell containment"`. + +- [ ] **RR1-S3: Verify the working tree matches the R3 baseline render frame** + +```bash +git diff 60f07bc HEAD -- src/AcDream.App/Rendering/GameWindow.cs +``` + +Expected: empty diff. The render-frame block at HEAD now matches `60f07bc`'s GameWindow.cs verbatim. + +If the diff is NOT empty, investigate — likely a non-A8 commit landed between 60f07bc and 2bfeafd that touched GameWindow.cs unrelated to R3.5 (uncommon but possible). + +- [ ] **RR1-S4: Build + test green** + +```bash +dotnet build -c Debug --nologo +dotnet test --nologo +``` + +Expected: build green; test failures within the documented 14–23 flaky window (PhysicsResolveCapture/Diagnostics static-leak issues, pre-existing). + +--- + +## Task RR2: Restructure render frame to WB-faithful order + +**Goal:** Re-wire `GameWindow.cs`'s render-frame block to match `VisibilityManager.RenderInsideOut` Steps 1–4 verbatim, unify the two-flag asymmetry, and add the stencil-gated sky step. + +**Files:** +- Modify: `src/AcDream.App/Rendering/GameWindow.cs` — render-frame block, lines ~7011–7260 + +The exact line numbers below are approximations from the worktree state at the time this plan was written (HEAD = `2bfeafd`). After RR1 reverts the line numbers shift; locate each edit by the exact `old_string` content, not by line number. + +- [ ] **RR2-S1: Rename `cameraInsideCell` → `cameraInside` with strict semantics** + +In `src/AcDream.App/Rendering/GameWindow.cs`, find this line (currently ~7012): + +```csharp + bool cameraInsideCell = visibility?.CameraCell is not null; +``` + +Replace with: + +```csharp + // Phase A8 restructure (2026-05-26): SINGLE source of truth for + // "rendering as if camera is inside a cell." Strict PointInCell + // check, no grace. Drives sky, terrain, stencil branch, weather, + // sky-PES debug. `playerInsideCell` (line below) STAYS unchanged + // — it tracks player position, not camera, for lighting. + // + // The grace mechanism in CellVisibility.FindCameraCell stays + // alive; non-render consumers (IsInsideAnyCell, etc.) may still + // benefit. Only the render-frame consumers use this strict flag. + // + // See: docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md + bool cameraInside = visibility?.CameraCell is not null + && CellVisibility.PointInCell(camPos, visibility.CameraCell); +``` + +Note: the `—` em-dash in the comment above is just for the doc renderer; in the actual file write em-dashes directly (or hyphens; not load-bearing). + +- [ ] **RR2-S2: Update sky-PES debug call to use cameraInside** + +Find this line (currently ~7032): + +```csharp + if (_options.EnableSkyPesDebug) + UpdateSkyPes((float)WorldTime.DayFraction, _activeDayGroup, camPos, cameraInsideCell); +``` + +Replace with: + +```csharp + if (_options.EnableSkyPesDebug) + UpdateSkyPes((float)WorldTime.DayFraction, _activeDayGroup, camPos, cameraInside); +``` + +- [ ] **RR2-S3: Update sky pre-scene gate to use cameraInside** + +Find this line (currently ~7090): + +```csharp + if (!cameraInsideCell) + { + _skyRenderer?.RenderSky(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + if (_particleSystem is not null && _particleRenderer is not null) + _particleRenderer.Draw(_particleSystem, camera, camPos, + AcDream.Core.Vfx.ParticleRenderPass.SkyPreScene); + } +``` + +Replace with: + +```csharp + if (!cameraInside) + { + _skyRenderer?.RenderSky(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + if (_particleSystem is not null && _particleRenderer is not null) + _particleRenderer.Draw(_particleSystem, camera, camPos, + AcDream.Core.Vfx.ParticleRenderPass.SkyPreScene); + } +``` + +- [ ] **RR2-S4: Gate initial terrain draw on `!cameraInside`** + +Find this block (currently ~7111–7123): + +```csharp + // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup + // (gated on ACDREAM_WB_DIAG=1, same env var as [WB-DIAG]). Stopwatch + // is cheap; only the periodic Console.WriteLine is gated. + _terrainCpuStopwatch.Restart(); + _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb); + _terrainCpuStopwatch.Stop(); +``` + +Replace with: + +```csharp + // Phase N.5b: wrap Draw in CPU stopwatch for [TERRAIN-DIAG] rollup + // (gated on ACDREAM_WB_DIAG=1, same env var as [WB-DIAG]). Stopwatch + // is cheap; only the periodic Console.WriteLine is gated. + // + // Phase A8 restructure: skip initial terrain when cameraInside. + // WB renders terrain ONLY at the stencil-gated step inside the + // indoor branch (see line ~7215). Drawing terrain unconditionally + // here would require a depth-clear-if-inside workaround (deleted + // by this restructure). + _terrainCpuStopwatch.Restart(); + if (!cameraInside) + _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb); + _terrainCpuStopwatch.Stop(); +``` + +The diag stopwatch + sample buffer + MaybeFlushTerrainDiag below stay unchanged — they record sub-microsecond when skipped, which is correct. + +- [ ] **RR2-S5: Delete the R3.5 long comment block + cameraReallyInside declaration + depth-clear-if-inside** + +Find this entire block (currently ~7125–7155): + +```csharp + // Phase A8 R3.5 — transition-flicker fix. `cameraInsideCell` + // stays true for ~3 grace frames after the camera physically + // exits a cell (see CellVisibility._cellSwitchGraceFrames). + // The grace mechanism prevents sky/lighting flicker at the + // doorway threshold, but the render-frame mechanisms that + // touch depth (depth-clear AND the stencil pipeline) MUST be + // gated on the stricter PointInCell containment so they don't + // fire during grace frames when the camera is actually outside. + // + // cameraInsideCell — lenient, grace-aware → sky, lighting + // cameraReallyInside — PointInCell, no grace → depth-clear, + // stencil pipeline branch + // + // R3.5 v1 only gated the stencil branch on `cameraReallyInside`; + // depth-clear stayed on `cameraInsideCell`. Result: during grace + // frames the depth-clear ran but the outdoor branch ran (because + // !cameraReallyInside), so terrain depth was destroyed AND + // everything below terrain (cellars, basement geometry) won the + // depth test in the outdoor pass → "objects visible through + // ground." R3.5 v2 unifies the depth-related gates on + // `cameraReallyInside` so terrain depth is preserved during + // grace, eliminating the through-ground artifact. + bool cameraReallyInside = visibility?.CameraCell is not null + && CellVisibility.PointInCell(camPos, visibility.CameraCell); + + // Conditional depth clear: when camera is ACTUALLY inside a cell + // volume (not just in the grace window), clear depth (not color) + // so interior geometry writes fresh Z values on top of the + // terrain color buffer. Matches ACME GameScene.cs pattern. + if (cameraReallyInside) + _gl!.Clear(ClearBufferMask.DepthBufferBit); +``` + +Delete the entire block. No replacement — the depth-clear was a workaround for the now-skipped initial terrain. RR2-S4 (terrain gated on `!cameraInside`) replaces both functions. + +Note: after RR1 reverts have run, the `cameraReallyInside` declaration is GONE (it was added by R3.5 v1). The remaining lines you need to delete in this step are smaller — just the depth-clear-if-inside block. The full block above is what HEAD looks like BEFORE RR1 reverts; after RR1 the matching `old_string` shrinks to roughly: + +```csharp + // Conditional depth clear: when camera is ACTUALLY inside a cell + // volume (not just in the grace window), clear depth (not color) + // so interior geometry writes fresh Z values on top of the + // terrain color buffer. Matches ACME GameScene.cs pattern. + if (cameraInsideCell) + _gl!.Clear(ClearBufferMask.DepthBufferBit); +``` + +(Note: post-RR1, the variable name is `cameraInsideCell` again — but RR2-S1 already renamed it to `cameraInside`. So at this step's execution time, the gate is `cameraInside`. Locate by the `_gl!.Clear(ClearBufferMask.DepthBufferBit)` line and the comment above it; delete the whole 4-line block.) + +- [ ] **RR2-S6: Modify the indoor branch — switch condition to cameraInside, prepare for stencil-gated sky insertion** + +Find the indoor branch condition (currently ~7174): + +```csharp + // The `visibility?.CameraCell is not null` repeat is for the + // compiler's null-flow analysis: `cameraReallyInside` already + // implies it, but flow doesn't propagate through a separate + // local. Restating it inside the if condition lets the branch + // body use the un-`?`d form without null-forgiving. + if (cameraReallyInside && _indoorStencilPipeline is not null + && visibility?.CameraCell is not null) + { +``` + +Replace with: + +```csharp + // The `visibility?.CameraCell is not null` repeat is for the + // compiler's null-flow analysis: `cameraInside` already implies + // it, but flow doesn't propagate through a separate local. + // Restating it inside the if condition lets the branch body use + // the un-`?`d form without null-forgiving. + if (cameraInside && _indoorStencilPipeline is not null + && visibility?.CameraCell is not null) + { +``` + +(Post-RR1, the variable is `cameraInsideCell` and is then renamed by RR2-S1 to `cameraInside`. Locate this step's edit by the `_indoorStencilPipeline is not null` text — that's stable across the renames.) + +- [ ] **RR2-S7: Insert stencil-gated sky step between EnableOutdoorPass and the terrain re-draw** + +Find this block (currently ~7209–7215): + +```csharp + // 5. Stencil-gated outdoor pass. + _indoorStencilPipeline.EnableOutdoorPass(); + + // 5a. Re-draw terrain — at portal-silhouette pixels only, + // terrain Z (with the f48c74a -0.01 nudge) wins over the + // punched 1.0 depth. Color writes through window. + _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb); +``` + +Replace with: + +```csharp + // 5. Stencil-gated outdoor pass. + _indoorStencilPipeline.EnableOutdoorPass(); + + // 5a. Stencil-gated SKY. DepthMask OFF so sky color writes + // through the punched depth=1.0 but depth STAYS at the + // punch value, letting the next step's terrain re-draw + // win the depth test wherever closer terrain exists. + // EnableOutdoorPass left stencil at Equal(1, 0x01) so + // sky color only writes where bit 1 is set (portal + // silhouettes). This is acdream's enhancement over WB + // (which shows fog clear color through windows); see + // design doc §Q2 + brainstorm outcomes. + // + // PRE-FLIGHT (RR3): SkyRenderer.RenderSky must NOT + // toggle stencil state internally. Verified in commit + // . + _gl!.DepthMask(false); + _skyRenderer?.RenderSky(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + _gl!.DepthMask(true); + + // 5b. Re-draw terrain — at portal-silhouette pixels only, + // terrain Z (with the f48c74a -0.01 nudge) wins over the + // punched 1.0 depth. Color writes through window, + // overwriting the sky's color writes where terrain is + // closer (near-field landscape through the window). + _terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb); +``` + +The step numbering in the comments shifts: what was "5a" terrain re-draw is now "5b" with sky as "5a." Outdoor scenery below stays as "5c" (was "5b"). Update consistently below: + +Find the OutdoorScenery comment immediately following: + +```csharp + // 5b. Outdoor scenery — same stencil gating. + _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum, + neverCullLandblockId: playerLb, + visibleCellIds: visibility.VisibleCellIds, + animatedEntityIds: animatedIds, + set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery); +``` + +Replace the comment marker: + +```csharp + // 5c. Outdoor scenery — same stencil gating. + _wbDrawDispatcher!.Draw(camera, _worldState.LandblockEntries, frustum, + neverCullLandblockId: playerLb, + visibleCellIds: visibility.VisibleCellIds, + animatedEntityIds: animatedIds, + set: AcDream.App.Rendering.Wb.WbDrawDispatcher.EntitySet.OutdoorScenery); +``` + +- [ ] **RR2-S8: Update animated-id-set comment to reference cameraInside** + +Find this comment block (currently ~7157–7160): + +```csharp + // L-fix1 (2026-04-28): animated-entity id set. Required by both + // the cameraReallyInside branch (to route them to LiveDynamic + // pass) and the outdoor path (where it preserves visibility + // across landblock frustum culling). +``` + +Replace with: + +```csharp + // L-fix1 (2026-04-28): animated-entity id set. Required by both + // the cameraInside branch (to route them to LiveDynamic pass) + // and the outdoor path (where it preserves visibility across + // landblock frustum culling). +``` + +(Post-RR1 the comment may already say `cameraInsideCell` instead of `cameraReallyInside`. Adjust the `old_string` to match whatever's actually there.) + +- [ ] **RR2-S9: Update weather post-scene gate** + +Find this block (currently ~7252–7267): + +```csharp + // Bug A fix (post-#26 worktree, 2026-04-26): weather sky + // meshes (Properties & 0x04, e.g. the 815m-tall rain + // cylinder 0x01004C42/0x01004C44) render AFTER the scene so + // the additive rain streaks overlay terrain and entities + // instead of being painted over by them. This is the second + // half of retail's LScape::draw split — GameSky::Draw(1) + // fires after the DrawBlock loop. Same indoor gate as the + // sky pass: weather is suppressed inside cells. + if (!cameraInsideCell) + { + _skyRenderer?.RenderWeather(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + if (_particleSystem is not null && _particleRenderer is not null) + _particleRenderer.Draw(_particleSystem, camera, camPos, + AcDream.Core.Vfx.ParticleRenderPass.SkyPostScene); + } +``` + +Replace the gate line only: + +```csharp + if (!cameraInside) + { + _skyRenderer?.RenderWeather(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + if (_particleSystem is not null && _particleRenderer is not null) + _particleRenderer.Draw(_particleSystem, camera, camPos, + AcDream.Core.Vfx.ParticleRenderPass.SkyPostScene); + } +``` + +(Comment block stays. Only the `if (!cameraInsideCell)` → `if (!cameraInside)` line.) + +- [ ] **RR2-S10: Build to verify all edits compile** + +```bash +dotnet build -c Debug --nologo +``` + +Expected: green, 0 errors, 0 warnings. If the compiler reports an undefined `cameraInsideCell` anywhere, find that remaining reference and rename it to `cameraInside`. + +- [ ] **RR2-S11: Run full test suite** + +```bash +dotnet test --nologo +``` + +Expected: failures within the documented 14–23 flaky window only (PhysicsResolveCapture/Diagnostics static-leak from unrelated tests, pre-existing). + +If a test fails with `cameraInsideCell` in its name or assertion message, that's a stale reference — investigate and update. + +- [ ] **RR2-S12: Commit** + +```bash +git add src/AcDream.App/Rendering/GameWindow.cs +git commit -m "$(cat <<'EOF' +feat(render): Phase A8 RR2 — restructure render frame to WB-faithful order + +Replaces the R3.5 v1+v2 frankenstein (terrain twice + depth-clear +workaround + two-flag asymmetry) with WB's RenderInsideOut order +verbatim: + + Outdoor frame (cameraInside == false): unchanged from pre-A8. + sky -> terrain -> Draw(All) -> weather + + Indoor frame (cameraInside == true): + 1. skip initial sky + 2. skip initial terrain + 3. NO depth-clear (deleted) + 4. MarkAndPunch (camera-cell exit portals) + 5. IndoorPass (cell mesh + statics + building shells) + 6. EnableOutdoorPass + 7. stencil-gated sky (DepthMask off; closes R4 Issue B) + 8. stencil-gated terrain re-draw + 9. stencil-gated OutdoorScenery + 10. DisableStencil + 11. LiveDynamic + 12. skip weather + +Unifies the two-flag asymmetry into a single strict `cameraInside` +flag computed via PointInCell. Grace mechanism in CellVisibility +stays alive for non-render consumers. + +Closes the architectural mismatch flagged in the 2026-05-26 R3.5 +restructure handoff. R1 (#78 fix) + R2 (taxonomy partition) + R3 +(stencil pipeline) shipped earlier; this commit replaces R3.5's +patches. + +Design: docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md +Plan: docs/superpowers/plans/2026-05-26-phase-a8-restructure.md +WB reference: references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:73-160 + +RR3 verifies the SkyRenderer-stencil-contract precondition for the +stencil-gated sky step. RR4 visual-verifies. RR5 ships docs. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +--- + +## Task RR3: Verify SkyRenderer doesn't toggle stencil state + +**Goal:** Confirm the stencil-gated sky step's precondition: `_skyRenderer.RenderSky` does NOT touch `EnableCap.StencilTest`, `StencilFunc`, `StencilOp`, or `StencilMask` between EnableOutdoorPass's setup and the sky fragment writes. If it does, our gate is lost. + +**Files:** +- Read: `src/AcDream.App/Rendering/SkyRenderer.cs` +- Possibly modify: `src/AcDream.App/Rendering/GameWindow.cs` (only if SkyRenderer is dirty) + +- [ ] **RR3-S1: Grep SkyRenderer for stencil-state calls** + +```bash +grep -n "Stencil\|stencil" src/AcDream.App/Rendering/SkyRenderer.cs +``` + +Possible outcomes: +- **No matches at all** → SkyRenderer is clean. Proceed to RR3-S2 (note-only commit). +- **Matches found** → inspect each. If they are inside conditional code paths that aren't reachable from our `RenderSky` call site (e.g., dead code, debug-only), still clean. If they touch live state during a normal `RenderSky` call, dirty — proceed to RR3-S3 (wrapper required). + +Most likely outcome: no matches. SkyRenderer was written for outdoor rendering with no stencil concerns. + +Also grep for any other GL state SkyRenderer may touch that could affect our step (DepthFunc, ColorMask): + +```bash +grep -n "DepthFunc\|ColorMask\|DepthMask" src/AcDream.App/Rendering/SkyRenderer.cs +``` + +If SkyRenderer changes `DepthMask` internally and restores, our outer `DepthMask(false)` + `DepthMask(true)` brackets may be redundant but safe. If SkyRenderer permanently changes `DepthMask` without restoring, we have a different problem (also worth knowing). + +- [ ] **RR3-S2: If SkyRenderer is clean, commit a verification note** + +If RR3-S1 found no stencil-state writes, update the comment in `GameWindow.cs` at the stencil-gated sky step to record the verification. Find this comment (added in RR2-S7): + +```csharp + // PRE-FLIGHT (RR3): SkyRenderer.RenderSky must NOT + // toggle stencil state internally. Verified in commit + // . +``` + +Replace `` with this commit's SHA (you'll know it once you run `git commit`; first commit with the placeholder, then `git commit --amend` after seeing the SHA, OR write the comment with the line-range reference to SkyRenderer.cs instead of a SHA reference). Preferred form (line-range, more durable than a SHA): + +```csharp + // PRE-FLIGHT (RR3): SkyRenderer.RenderSky must NOT + // toggle stencil state internally. Verified by reading + // src/AcDream.App/Rendering/SkyRenderer.cs at commit + // time — no Stencil*/EnableCap.StencilTest calls in + // the file. +``` + +Commit: + +```bash +git add src/AcDream.App/Rendering/GameWindow.cs +git commit -m "$(cat <<'EOF' +chore(render): Phase A8 RR3 — verify SkyRenderer doesn't touch stencil + +Pre-flight precondition for the RR2 stencil-gated sky step: confirms +SkyRenderer.RenderSky doesn't enable/disable stencil test, change +stencil function/op/mask. Verified by grep over +src/AcDream.App/Rendering/SkyRenderer.cs — no matches. + +Comment at the stencil-gated sky call site updated to record the +verification + reference the verified source. + +No behavior change. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **RR3-S3: If SkyRenderer is dirty, add a state save/restore wrapper** + +This step only runs if RR3-S1 found stencil writes inside `SkyRenderer.RenderSky` that would interfere. + +In `GameWindow.cs`, replace the stencil-gated sky block (RR2-S7's insertion) with a state-saving variant: + +```csharp + // 5a. Stencil-gated SKY with state save/restore wrapper. + // SkyRenderer.RenderSky touches stencil state (see + // RR3-S1 finding); save before, restore after. + _gl!.GetInteger(GLEnum.StencilFunc, out int savedStencilFunc); + _gl!.GetInteger(GLEnum.StencilRef, out int savedStencilRef); + _gl!.GetInteger(GLEnum.StencilValueMask, out int savedStencilValueMask); + _gl!.GetInteger(GLEnum.StencilWritemask, out int savedStencilWritemask); + bool savedStencilTest = _gl!.IsEnabled(EnableCap.StencilTest); + + _gl!.DepthMask(false); + _skyRenderer?.RenderSky(camera, camPos, (float)WorldTime.DayFraction, + _activeDayGroup, kf, environOverrideActive); + _gl!.DepthMask(true); + + // Restore stencil state to what EnableOutdoorPass left us with. + if (savedStencilTest) _gl!.Enable(EnableCap.StencilTest); + else _gl!.Disable(EnableCap.StencilTest); + _gl!.StencilFunc((StencilFunction)savedStencilFunc, savedStencilRef, (uint)savedStencilValueMask); + _gl!.StencilMask((uint)savedStencilWritemask); +``` + +Commit: + +```bash +git add src/AcDream.App/Rendering/GameWindow.cs +git commit -m "$(cat <<'EOF' +fix(render): Phase A8 RR3 — wrap stencil-gated sky with state save/restore + +SkyRenderer.RenderSky internally touches stencil state (verified in +RR3-S1). The stencil-gated sky step inside the A8 indoor branch needs +the EnableOutdoorPass stencil setup intact for the subsequent terrain +re-draw and OutdoorScenery passes. Wrap RenderSky with glGetInteger +save + restore so the outer stencil context survives. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +--- + +## Task RR4: Visual verification matrix + +**Goal:** Confirm the restructure delivers the design's promises across the four building types + both transitions + sky-through-windows. Human runs the scenarios; Claude collects logs; per-scenario PASS/FAIL recorded against the acceptance criteria. + +**Files:** +- Create: `docs/research/2026-05-26-a8-rr4-visual-verification.md` — outcome doc + +- [ ] **RR4-S1: Build for verification** + +```bash +dotnet build src\AcDream.App\AcDream.App.csproj -c Debug --nologo +``` + +Expected: green. + +- [ ] **RR4-S2: Pre-launch — close any running client; launch fresh** + +```powershell +$proc = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue +if ($proc) { + $proc.CloseMainWindow() | Out-Null + if (-not $proc.WaitForExit(5000)) { $proc | Stop-Process -Force } +} +Start-Sleep -Seconds 3 + +$env:ACDREAM_DAT_DIR = "$env:USERPROFILE\Documents\Asheron's Call" +$env:ACDREAM_LIVE = "1" +$env:ACDREAM_TEST_HOST = "127.0.0.1" +$env:ACDREAM_TEST_PORT = "9000" +$env:ACDREAM_TEST_USER = "testaccount" +$env:ACDREAM_TEST_PASS = "testpassword" + +dotnet run --project src\AcDream.App\AcDream.App.csproj --no-build -c Debug 2>&1 | + Tee-Object -FilePath "launch-a8-rr4-verify.log" +``` + +Background run (so the tool returns; human keeps the client window foreground). + +- [ ] **RR4-S3: Scenario A — Holtburg cottage interior** + +Human walks `+Acdream` into a Holtburg cottage near the network portal. Stand in the middle of the room. Look at each wall in turn. Look out a window. + +**Acceptance:** +- All walls SOLID — no see-through to outdoor terrain +- Outdoor terrain visible ONLY through windows / open doors +- **SKY visible through windows** (the R4 Issue B closure) +- Player character body visible (no head-backwards, no missing limbs) +- No "transparent rectangles around buildings" regression (#100 stays closed) + +Record per criterion: PASS / FAIL with notes. + +- [ ] **RR4-S4: Scenario B — Holtburg cottage cellar** + +Walk to a cottage cellar (descend stairs). + +**Acceptance:** +- Cellar walls + floor + ceiling SOLID +- Cellar stairs SOLID from inside view — no grass/terrain overlay through the stair geometry +- (Out-to-in stair-grass artifact is NOT A8 scope; will be filed as #103 by RR5 if observed) + +Record per criterion: PASS / FAIL. + +- [ ] **RR4-S5: Scenario C — Holtburg Inn (multi-room cell mesh)** + +Walk into the Holtburg inn. Move through its rooms. + +**Acceptance:** +- All inn walls SOLID +- Adjacent rooms not visible through walls (no "I can see the door of the next room") +- Furniture (cell statics) visible and properly positioned +- Sky visible through inn windows + +Record per criterion: PASS / FAIL. + +- [ ] **RR4-S6: Scenario D — A dungeon (portal-entry indoor world)** + +Pick any reachable dungeon (network portal to a known dungeon). + +**Acceptance:** +- Corridor walls SOLID +- Adjacent corridors not visible through walls +- Lighting reads as indoor (sun zeroed, indoor ambient applied) +- No outdoor stab/terrain leak +- (Dungeons typically have no exit portals; no sky-through-window expected. If a dungeon HAS exit portals, sky should show through them.) + +Record per criterion: PASS / FAIL. + +- [ ] **RR4-S7: Transition scenarios — exit and entry** + +Walk through an entry/exit transition at the cottage door several times. + +**Exit transition (indoor → outdoor):** +- Clean transition; no through-ground objects; no missing walls +- (If A reproduces AND RR0 confirmed pre-existing on main, this is documented limitation; PASS with note) + +**Entry transition (outdoor → indoor):** +- Clean transition; no floor transparent showing cellar +- (If C reproduces AND RR0 confirmed pre-existing on main, documented limitation; PASS with note) + +Record per scenario: PASS / FAIL with notes. + +- [ ] **RR4-S8: Graceful close + write findings doc** + +```powershell +$proc = Get-Process -Name AcDream.App -ErrorAction SilentlyContinue +if ($proc) { + $proc.CloseMainWindow() | Out-Null + if (-not $proc.WaitForExit(5000)) { $proc | Stop-Process -Force } +} +``` + +Create `docs/research/2026-05-26-a8-rr4-visual-verification.md` capturing each scenario's PASS/FAIL outcome with observations. + +Template: + +```markdown +# A8 RR4 visual verification — render-frame restructure outcome + +**Date:** 2026-05-26 +**HEAD:** [restructure-commit SHA from RR2] +**Build:** green + +## Scenario outcomes + +| Scenario | Acceptance | Outcome | Notes | +|---|---|---|---| +| A: cottage interior | walls solid + sky through windows | PASS / FAIL | | +| B: cottage cellar | walls solid + stairs solid from inside | PASS / FAIL | | +| C: inn (multi-room) | walls solid + sky through windows | PASS / FAIL | | +| D: dungeon | walls solid + indoor lighting | PASS / FAIL | | +| Exit transition | clean | PASS / FAIL / DOCUMENTED PRE-EXISTING | | +| Entry transition | clean | PASS / FAIL / DOCUMENTED PRE-EXISTING | | + +## Gate decision + +- [ ] All four building-type scenarios PASS → proceed to RR5 +- [ ] Any building-type scenario FAILED → STOP, invoke /investigate +- [ ] Transition scenarios FAILED but RR0 confirmed pre-existing → file as new ISSUES.md entries in RR5 +- [ ] Transition scenarios FAILED and RR0 did NOT confirm pre-existing → STOP, re-brainstorm +``` + +Commit the findings: + +```bash +git add docs/research/2026-05-26-a8-rr4-visual-verification.md +git commit -m "$(cat <<'EOF' +docs(research): Phase A8 RR4 — visual verification outcome + +Cottage / cellar / inn / dungeon + exit/entry transition + sky-through- +windows scenarios per the design's acceptance matrix. Gate decision +recorded for RR5 ship-or-stop. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +- [ ] **RR4-S9: Gate decision** + +Per the findings: +- All building-type scenarios PASS AND transition scenarios are clean OR documented-pre-existing → proceed to RR5. +- Any building-type scenario FAILED → STOP. Open `/investigate` to triage. Do NOT ship RR5. +- Transition scenarios failed and NOT pre-existing → STOP. Re-brainstorm scope expansion. + +--- + +## Task RR5: Ship docs (close #78, file follow-ups, update CLAUDE.md) + +**Goal:** Move #78 to closed, file new ISSUES for known limitations + any RR0-confirmed pre-existing transition issues, update CLAUDE.md's A8 paragraph from "PAUSED" → "SHIPPED." + +**Files:** +- Modify: `docs/ISSUES.md` +- Modify: `CLAUDE.md` + +- [ ] **RR5-S1: Read ISSUES.md to confirm next available ID** + +```bash +grep -n "^## #1[0-9][0-9]" docs/ISSUES.md +``` + +Expected: shows the highest #1xx ID currently filed. Tentative next available was #102 at plan-write time (highest filed = #101). Verify and adjust. + +- [ ] **RR5-S2: Move #78 to Recently closed** + +Find #78's current entry in `docs/ISSUES.md` (OPEN). Move it to the "Recently closed" section with format: + +```markdown +**#78 — Outdoor stabs/buildings visible through the rendered floor** — CLOSED 2026-05-26 by Phase A8 (R1+R2+R3+RR1+RR2 across commits ed72704 → [RR2 SHA]). Restructure ports WB's RenderInsideOut stencil pipeline with a corrected entity taxonomy (WorldEntity.IsBuildingShell). Visual-verified at cottage interior, cellar, inn, and dungeon. Sky visible through windows (Issue B closure). +``` + +- [ ] **RR5-S3: File #102 — Cross-cell-portal far-side visibility (Step 5 deferral)** + +Add this OPEN issue (use the actual next available ID confirmed in RR5-S1): + +```markdown +## #102 — Far-side portal visibility through walls (WB Step 5 deferral) + +**Status:** OPEN (low priority; first ship of A8 deferred this). + +**Description:** When standing inside a multi-room building, looking at a wall between rooms, portals on the FAR side of the room (e.g. a doorway opening to outdoors on the other side of the wall) may have their silhouette stencil-marked by Phase A8. This lets outdoor terrain leak through the wall at that silhouette. The first-ship approximation in A8 RR2 stencil-marks ONLY the camera's own cell's exit portals (not BFS-extended VisibleCellIds), which AVOIDS the leak in most cases but loses cross-cell-portal visibility. + +**Acceptance:** Inside Holtburg Inn looking at the wall between two rooms, no visible terrain or scenery shows through. WB Step 5's 3-stencil-bit cross-building pipeline is the reference fix (see references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs:156-232). + +**Files:** +- src/AcDream.App/Rendering/IndoorCellStencilPipeline.cs — currently single-bit stencil; would extend to bits 1+2. +- src/AcDream.App/Rendering/GameWindow.cs — render frame would gain a per-far-portal pass. +``` + +- [ ] **RR5-S4: File #103 — Outdoor-to-indoor cellar terrain Z-fight (out-to-in artifact)** + +Add this OPEN issue: + +```markdown +## #103 — Outdoor-to-indoor cellar terrain Z-fight (out-to-in artifact) + +**Status:** OPEN (low priority; pre-existing). + +**Description:** Looking from OUTSIDE a cottage cellar at the stair geometry from above, grass/terrain may overlap the stair triangles. Pre-existing; not addressed by A8 (no stencil work runs when the camera is outside). #100's 1cm terrain Z nudge is insufficient because cellar geometry sits multiple meters below terrain Z — depth-precision artifacts persist at oblique angles. + +**Acceptance:** From outside a cottage, looking at the cellar entrance, stair geometry reads as solid stone (no grass overlay) regardless of camera angle. + +**Files:** likely a deeper terrain-occlusion mechanism (per-cell terrain mask, or proper outdoor portal culling) — beyond the scope of A8. +``` + +- [ ] **RR5-S5: File R4 Issue A as #104 IF RR0 confirmed pre-existing on main** + +Read `docs/research/2026-05-26-a8-rr0-falsification-findings.md` written in RR0-S4. If outcome was "all three reproduce" (Issue A pre-existing on main), file: + +```markdown +## #104 — Exit transition: objects visible through ground (pre-existing) + +**Status:** OPEN (medium priority; pre-existing on main, confirmed by A8 RR0 falsification). + +**Description:** Briefly after the camera exits a cell (transitioning indoor → outdoor), objects below terrain Z (cellar geometry, basement entities) may render visible through the ground for 1–3 frames. Falsified against `main` in [a8-rr0-falsification-findings.md](../research/2026-05-26-a8-rr0-falsification-findings.md) — reproduces on `main` without any A8 work. Likely root cause: terrain Z + grace-frame interaction with entity depth tests at certain camera angles. + +**Acceptance:** Exit a cottage; no objects visible through the ground at any camera angle during the transition. Steady-state outdoor view: terrain occludes below-ground entities cleanly. + +**Files:** +- Likely terrain shader (terrain_modern.vert) or the cell-grace mechanism (`CellVisibility.FindCameraCell`). +- May need a "true outside" gate that ignores grace for depth-test correctness. +``` + +(If RR0 did NOT confirm pre-existing, skip this step. The expected handling was already decided in RR0-S5.) + +- [ ] **RR5-S6: File R4 Issue C as #105 IF RR0 confirmed pre-existing on main** + +Same condition as RR5-S5. If RR0 confirmed Issue C pre-existing: + +```markdown +## #105 — Entry transition: floor transparent showing cellar (pre-existing) + +**Status:** OPEN (medium priority; pre-existing on main, confirmed by A8 RR0 falsification). + +**Description:** Briefly during the camera entry into a cottage (outdoor → indoor transition), the cottage floor may go transparent showing the cellar geometry below, with the cellar ceiling's texture flickering at the floor pixels. Falsified against `main` in [a8-rr0-falsification-findings.md](../research/2026-05-26-a8-rr0-falsification-findings.md) — reproduces on `main` without any A8 work. Likely root cause: Z-fight between cottage floor mesh and cellar ceiling mesh at adjacent world Z values (both with +0.02 EnvCell render lift). + +**Acceptance:** Enter a cottage; floor reads as solid floor texture throughout the transition. No glimpse of cellar geometry through the floor. + +**Files:** +- The +0.02m EnvCell render lift might need per-cell adjustment. +- Alternative: depth-fight-resolving via polygon offset on indoor cell meshes. +``` + +- [ ] **RR5-S7: Update CLAUDE.md A8 paragraph** + +Find the current A8 paragraph in `CLAUDE.md` (begins with "**A8.P3 —" or "**Phase A8 —"; locate by grep). Find the "PAUSED" / "R3.5" / "restructure" mentions. Replace with a SHIPPED summary: + +```markdown +**Phase A8 — Indoor-cell visibility culling — SHIPPED 2026-05-26.** Closes +issue #78. Six tasks across the restructure (after R1+R2+R3 shipped earlier +in the same week and R3.5 v1+v2 paused for restructure): +- RR0: falsification spike — confirmed R4 Issues A + C [pre-existing on main / A8-specific] (per `docs/research/2026-05-26-a8-rr0-falsification-findings.md`). +- RR1: reverted R3.5 v1 + v2 patches (`38d5374`, `2bfeafd`) as two new revert commits. +- RR2: restructured render frame to WB-faithful RenderInsideOut order. Skipped initial sky+terrain when inside; deleted depth-clear-if-inside; added stencil-gated sky inside the indoor branch (closes R4 Issue B). Unified the two-flag asymmetry into a single strict `cameraInside` flag via PointInCell. +- RR3: verified `SkyRenderer.RenderSky` doesn't toggle stencil state internally — stencil-gated sky step's precondition holds. +- RR4: visual-verified at Holtburg cottage interior + cellar + Inn + dungeon, plus exit/entry transitions and sky-through-windows. +- RR5 (this ship-docs commit). + +Five deferred follow-ups remain open: +- #102 — Cross-cell-portal far-side visibility (WB Step 5). +- #103 — Cellar terrain Z-fight from outside (pre-existing). +- #104 — Exit transition through-ground (filed only if RR0 confirmed pre-existing). +- #105 — Entry transition transparent floor (filed only if RR0 confirmed pre-existing). +- Grace-mechanism cleanup (Q4 third option from brainstorm). + +Full design: [docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md](docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md). +Plan: [docs/superpowers/plans/2026-05-26-phase-a8-restructure.md](docs/superpowers/plans/2026-05-26-phase-a8-restructure.md). +Restructure handoff (historical): [docs/research/2026-05-26-a8-r3.5-restructure-handoff.md](docs/research/2026-05-26-a8-r3.5-restructure-handoff.md). +``` + +(Customize the "Five deferred follow-ups" list based on which of #104 + #105 were actually filed — if RR0 confirmed neither pre-existing, both come off the list; if just A, drop #105; etc.) + +Also find the "Currently working toward" line + the A8 references in the Milestone Discipline section. Update them to reflect ship status (e.g. "M1.5 — Indoor world feels right — partially advanced; A8 shipped 2026-05-26; remaining indoor work continues at [next phase]"). + +- [ ] **RR5-S8: Commit ship docs** + +```bash +git add docs/ISSUES.md CLAUDE.md +git commit -m "$(cat <<'EOF' +ship(render): Phase A8 — indoor-cell visibility culling SHIPPED + +Closes #78 (outdoor stabs/terrain visible through indoor walls). Files +#102 (cross-cell-portal Step 5 deferral), #103 (cellar Z-fight from +outside, pre-existing), and [#104/#105 if RR0 confirmed Issues A/C +pre-existing on main]. + +Visual-verified at Holtburg cottage interior + cellar, Holtburg Inn, +and dungeon. Sky visible through windows. Exit/entry transitions +[clean / documented pre-existing per RR0 findings]. + +Architecture: WB RenderInsideOut order verbatim. Entity taxonomy +partition (WorldEntity.IsBuildingShell from R1) drives +WbDrawDispatcher.EntitySet (R2) into IndoorPass / OutdoorScenery / +LiveDynamic. Stencil-gated sky inside the indoor branch is acdream's +enhancement over WB's reference. + +Co-Authored-By: Claude Opus 4.7 (1M context) +EOF +)" +``` + +--- + +## Self-review + +**Spec coverage check:** + +- [x] Q1 (initial-terrain conditional) → RR2-S4 gates terrain on `!cameraInside`. +- [x] Q2 (sky through windows) → RR2-S7 inserts stencil-gated sky step. +- [x] Q3 (Issue C investigation) → RR5-S6 files as #105 if RR0 confirmed pre-existing. +- [x] Q4 (gate unification) → RR2-S1 introduces single `cameraInside`; RR2-S2/S3/S6/S8/S9 propagate. +- [x] Q5 (R3.5 revert) → RR1-S1/S2 revert. +- [x] RR0 spike (pre-restructure falsification) → entire Task RR0 + decision gate RR0-S5. +- [x] SkyRenderer precondition check → Task RR3. +- [x] Visual verification matrix (cottage/cellar/inn/dungeon + transitions + sky-through-windows) → RR4 Scenarios A–D + S7 transitions. +- [x] Close #78 + file new issues → RR5-S2 through RR5-S6. + +**Placeholder scan:** + +- No "TBD" or "implement later" — every step has actual code or actual commands. +- Two intentional placeholder substitutions: `[RR2 SHA]` in the #78 closure entry (filled in at RR5 execution time) and `[a SHA / pre-existing / not pre-existing]` strings in the CLAUDE.md update template (filled in based on RR0 outcome). These are NOT spec failures — they're explicit "fill in at execution time based on observed state." +- "Customize" appears once in RR5-S7 for the deferred-follow-ups list — accompanied by a clear conditional that the executor fills in. + +**Type consistency:** + +- `cameraInside` (single source of truth) used consistently across RR2-S1/S2/S3/S6/S8/S9; never spelled `cameraInsideCell` or `cameraReallyInside` in any post-RR1 step. +- `_indoorStencilPipeline` is the field name referenced everywhere (matches R3's already-shipped code). +- `EntitySet.IndoorPass` / `OutdoorScenery` / `LiveDynamic` enum values match R2's already-shipped code. +- `IndoorCellStencilPipeline` method names — `UploadPortalMesh`, `MarkAndPunch`, `EnableOutdoorPass`, `DisableStencil` — match the dormant infrastructure shipped in Tasks 1–6. + +**Task granularity:** + +- RR0: 5 steps, each ~5–15 minutes (most are launch-and-observe). Visual repro takes the longest. +- RR1: 4 steps, each <2 minutes. +- RR2: 12 steps, each 2–5 minutes (mechanical edits with exact code shown). +- RR3: 3 steps (S2 OR S3 depending on S1 outcome), each 5–10 minutes. +- RR4: 9 steps, each 3–10 minutes (mostly observation). +- RR5: 8 steps, each 5–10 minutes (mostly edits to two markdown files). + +Total estimated time: ~2.5–3 hours assuming RR0 doesn't trigger scope expansion. + +**Outcome contingencies:** + +- RR0 outcome 2 (A8-caused) → STOP at RR0-S5. Re-brainstorm. +- RR3-S1 dirty → divert to RR3-S3 wrapper. +- RR4 building-type FAILED → STOP at RR4-S9. Open `/investigate`. +- RR4 transition FAILED + not pre-existing → STOP, re-brainstorm. + +All gates explicit; no dead-ends.