docs(spec): Phase A8 — render-frame restructure to WB-faithful order (design)
Brainstorm-approved design for the A8 R3.5 → restructure pivot. Replaces the R3.5 v1+v2 frankenstein (terrain twice + depth-clear workaround) with WB's RenderInsideOut order verbatim: skip initial sky+terrain when inside, delete the depth-clear, add a stencil-gated sky step inside the indoor branch so windows show real sky (closes R4 Issue B). Unifies the two-flag asymmetry (cameraInsideCell lenient + cameraReallyInside strict) into a single strict cameraInside flag via PointInCell. Grace mechanism in CellVisibility stays alive for non-render consumers. Six tasks ahead, in order: RR0 — pre-restructure falsification spike (Issues A + C on main?) RR1 — revert R3.5 v1+v2 (38d5374+2bfeafd) RR2 — restructure render frame to WB-faithful order RR3 — verify SkyRenderer doesn't toggle stencil state RR4 — visual verification matrix (cottage/cellar/inn/dungeon + transitions) RR5 — ship docs (close #78; file new follow-ups if pre-existing on main) Next: superpowers:writing-plans to produce the per-task plan. Note: the design references two predecessor docs that are currently untracked in this worktree (entity-taxonomy + phase-a8-replan). Their contents are read-stable on disk; committing them is a separate concern (they belong to the prior session's work). The handoff doc this design continues from is atf90fa2f. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f90fa2f863
commit
732f766d1b
1 changed files with 445 additions and 0 deletions
445
docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md
Normal file
445
docs/superpowers/specs/2026-05-26-phase-a8-restructure-design.md
Normal file
|
|
@ -0,0 +1,445 @@
|
|||
# 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](../../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](../../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](../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](../../../references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs) — the proven WB `RenderInsideOut` reference
|
||||
|
||||
---
|
||||
|
||||
## 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:
|
||||
|
||||
```csharp
|
||||
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 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
|
||||
|
||||
- **`CellVisibility`** — `PointInCell`, `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 ~7008–7242 (~234 lines, mostly comments + a single if/else). After the restructure:
|
||||
|
||||
- Two existing local declarations renamed (`cameraInsideCell` and `cameraReallyInside` → `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:**
|
||||
|
||||
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 step** — `IndoorCellStencilPipeline.MarkAndPunch` already restores state on exit per Tasks 1–6 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 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:**
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
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):
|
||||
```csharp
|
||||
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 `cameraInsideCell` → `cameraInside`.
|
||||
|
||||
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 ~7125–7148). Replace with a brief comment referencing the design doc.
|
||||
|
||||
6. Delete the depth-clear-if-inside block (lines ~7150–7155).
|
||||
|
||||
7. 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
|
||||
|
||||
8. Outdoor branch (`else` at line 7234): unchanged.
|
||||
|
||||
9. 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:**
|
||||
|
||||
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.
|
||||
Loading…
Add table
Add a link
Reference in a new issue