docs(render): Phase U.4 shipped (indoor rendering verified) + flap handoff
Phase U (U.1-U.4) shipped: the unified retail-faithful render pipeline replacing the abandoned two-pipe split (#103). Indoor rendering VISUALLY VERIFIED — solid walls, no terrain bleed, per-cell clip gating works. Two root-caused EnvCellRenderer self-contained-GL-state fixes landed (uViewProjection stale-matrix; inherited blend/depth-mask). Residual threshold "flap" (OutsideView instability from the per-frame view-dependent portal BFS) is precisely root-caused via ACDREAM_PROBE_VIS and scoped to U.4c (PVS / stab_list grounding, retail-faithful). Handoff captures the [vis] evidence, the retail anchors, and the next-session pickup. U.5 (outdoor->building peering) + U.6 (dungeon scale) remain. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9be9547ddc
commit
a3ecac5369
1 changed files with 162 additions and 0 deletions
162
docs/research/2026-05-30-phase-u4-shipped-and-flap-handoff.md
Normal file
162
docs/research/2026-05-30-phase-u4-shipped-and-flap-handoff.md
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
# Phase U.4 — shipped (unified pipeline + indoor rendering) + the threshold "flap" handoff (2026-05-30)
|
||||
|
||||
## TL;DR
|
||||
|
||||
The **unified retail-faithful render pipeline (Phase U) is built and shipped through U.4**,
|
||||
and **indoor rendering is visually verified correct**: standing inside a Holtburg cottage /
|
||||
cellar / inn, the cell-shell walls render solid, terrain no longer bleeds into interiors,
|
||||
and the per-cell clip gating works. This replaced the abandoned two-pipe (inside/outside)
|
||||
split (#103). Modern code, retail behavior.
|
||||
|
||||
**One residual remains: the threshold "flap"** — crossing a doorway (inside↔outside), terrain
|
||||
+ building-shells briefly vanish leaving only un-gated geometry (particles + live entities)
|
||||
over the bluish clear color. This is **precisely root-caused** (not a mystery): our per-frame
|
||||
view-dependent portal BFS is unstable at multi-hop exit paths, so `OutsideView` flickers empty
|
||||
→ terrain gets `Skip`-ped. The retail-faithful fix is **PVS (stab_list) grounding** — a focused
|
||||
sub-step, **U.4c**. It was deliberately NOT attempted at the end of this (very long) session to
|
||||
avoid thrashing a fragile cell-resolution area (the #98 lesson + the "don't push tired design
|
||||
calls late-session" rule).
|
||||
|
||||
Visual gate status: **PASS for the indoor case; the seamless-threshold criterion is deferred to U.4c.**
|
||||
|
||||
---
|
||||
|
||||
## What shipped this session (Phase U, all committed on `claude/thirsty-goldberg-51bb9b`)
|
||||
|
||||
| Stage | What | Commit(s) |
|
||||
|---|---|---|
|
||||
| Spec + plan | Phase U design + implementation plan | `8601137`, `0f7b395` |
|
||||
| **U.1** | Delete the two-pipe machinery (kept all audited fixes) | `3fc77be` |
|
||||
| **U.2a** | Portal BFS: closest-first ordering + retail fixpoint termination | `d880775` (+ `306cdb0` review fixups) |
|
||||
| **U.2b** | Reciprocal `OtherPortalClip` (+ CRITICAL fix: resolve by `other_portal_id`, not first-`OtherCellId`-scan) | `3916b2b` → `65781f5` |
|
||||
| **U.2c** | `ClipPlaneSet` (NDC convex region → `gl_ClipDistance` planes, 8-cap + scissor fallback) | `a83b430` |
|
||||
| **U.2d** | `ACDREAM_PROBE_VIS` visibility probe (in `RenderingDiagnostics`, Core) | `0b12583` |
|
||||
| **U.3** | GPU gate: `gl_ClipDistance` in mesh+terrain shaders, `ClipFrame`, scoped clip bracket | `bf2e559` → `864fc5f` |
|
||||
| **U.4** | Unified gated draw pass (`ClipFrameAssembler`, per-instance slots, `EnvCellRenderer.Render` wired, terrain Skip/Scissor/Planes) + `ResolveEntitySlot` tests | `7993e06` → `354ca74` |
|
||||
| **U.4 fix 1** | `EnvCellRenderer.Render` uploads its own `uViewProjection` (was inheriting WbDrawDispatcher's → stale → seam flicker) | `d6d4671` |
|
||||
| **U.4 fix 2** | `EnvCellRenderer.Render` sets its own BLEND + DepthMask per pass (was inheriting → opaque walls blended against clear color → "transparent walls") | `9be9547` |
|
||||
|
||||
Build green, App tests 151/151 throughout. Core failures are the documented pre-existing
|
||||
static-leak flakiness (zero Core production files touched by U.4). **Branch is UNPUSHED** —
|
||||
push decision is the user's.
|
||||
|
||||
### Two reviews caught real CRITICALs (the process earned its keep)
|
||||
- U.2b: reciprocal-portal resolved by scanning for the first `OtherCellId` match → mis-resolved
|
||||
when a cell has two portals to one neighbour (real on Holtburg cellar `0x148`↔`0x149`) →
|
||||
hidden geometry. Fixed by plumbing the dat's `OtherPortalId` back-link.
|
||||
- U.3: `GL_CLIP_DISTANCE0..7` enabled globally while 6 non-clip-writing shaders ran → undefined
|
||||
behavior (benign on the dev driver, a portability landmine). Fixed by scoping the enable to
|
||||
the world-geometry draws.
|
||||
|
||||
### The recurring lesson (now 3×): EnvCellRenderer must own its GL state
|
||||
`EnvCellRenderer.Render` was dormant pre-U.4 (only the deleted two-pipe path called it). When
|
||||
U.4 wired it into the live loop, it surfaced THREE inherited-GL-state bugs in sequence:
|
||||
1. (2026-05-28, pre-U.4) cull state → "missing walls".
|
||||
2. (U.4 fix 1) `uViewProjection` → stale-matrix seam flicker.
|
||||
3. (U.4 fix 2) BLEND + DepthMask → opaque walls blending against the clear color.
|
||||
A renderer that runs mid-frame after other consumers MUST establish every GL state it depends
|
||||
on (matrix, blend, depth-mask, cull, front-face, A2C) — never inherit. See the memory note
|
||||
`render-self-contained-gl-state`.
|
||||
|
||||
---
|
||||
|
||||
## The flap — root cause (evidence-based)
|
||||
|
||||
### Symptom
|
||||
Crossing a doorway (the user's screenshot, inside→outside): terrain + building-shells + cell-
|
||||
shells vanish, leaving only particles + live entities (NPCs/doors/items, slot 0 = no-clip) over
|
||||
the bluish clear color (`glClearColor(0.05,0.10,0.18)`).
|
||||
|
||||
### Evidence — `ACDREAM_PROBE_VIS` `[vis]` lines, SAME cell across frames
|
||||
```
|
||||
root=0xA9B40171 cells=4 ids=[...,0xA9B40170] outside(polys=1,planes=4) ← window cell reached → terrain draws
|
||||
root=0xA9B40171 cells=3 ids=[0xA9B40171,75,74] outside(polys=0,planes=0) ← window cell dropped → terrain SKIPPED
|
||||
```
|
||||
Over one cellar traversal: **10 empty-`OutsideView` frames vs 16 non-empty** — for the *same*
|
||||
cells. The ground-floor cell `0xA9B40170` (which holds the window / `0xFFFF` exit portal)
|
||||
**flickers in and out of the visible set** as the camera moves.
|
||||
|
||||
### Mechanism
|
||||
1. Our `PortalVisibilityBuilder` runs a **per-frame, view-dependent** portal BFS. The
|
||||
`CameraOnInteriorSide` portal-side test culls portals based on the camera's exact pose.
|
||||
2. Near a portal boundary, a tiny camera move flips which portals pass the test → the multi-hop
|
||||
path (cellar → ground floor → window) **breaks** in some frames.
|
||||
3. When the exit-portal cell isn't reached, `OutsideView` is empty.
|
||||
4. `ClipFrameAssembler` maps empty `OutsideView` → `TerrainMode.Skip` (the bleed fix) AND
|
||||
`outdoorVisible=false` → building-shells culled.
|
||||
5. Result: terrain + building-shells flap off whenever the exit path momentarily breaks.
|
||||
|
||||
### Why retail is seamless (the fix direction)
|
||||
Retail grounds visibility in a **precomputed potentially-visible-set**: on cell entry,
|
||||
`CEnvCell::grab_visible_cells` populates the `visible_cell_table` from the cell's `stab_list`
|
||||
(a STABLE per-cell PVS), and `seen_outside` is a stable per-cell flag ("this cell is adjacent to
|
||||
the exterior"). The per-frame `PView` clip refines WHERE things draw, but the SET of reachable
|
||||
cells (and thus whether the exit portal is reachable) is stable. Our pure per-frame view-dependent
|
||||
BFS has no such anchor → it flaps. Retail anchors: `CEnvCell::grab_visible_cells` ~311878,
|
||||
`seen_outside` set in `find_cell_list` ~311044, `stab_list` on `CEnvCell`.
|
||||
|
||||
---
|
||||
|
||||
## U.4c — proposed scope (stabilize portal visibility)
|
||||
|
||||
Goal: make the visible-cell set (and therefore `OutsideView` / the terrain-draw decision)
|
||||
**stable** so the threshold is seamless, the retail way — NOT a hysteresis/last-frame band-aid
|
||||
(that's a workaround; forbidden).
|
||||
|
||||
Candidate approaches to settle in a brainstorm (do NOT jump to code — fragile area, #98 history):
|
||||
1. **PVS / stab_list grounding (most retail-faithful).** Load each cell's `stab_list` (the dat
|
||||
has it) into a stable per-cell visible set on cell entry; the per-frame BFS operates within /
|
||||
is anchored by it, so the exit-portal cell never drops out. This is what makes retail seamless
|
||||
by construction. Largest change; needs the stab_list dat read + integration.
|
||||
2. **Stable `seen_outside` terrain-draw decision.** Decouple "should terrain draw" (stable: does
|
||||
the camera cell statically reach a `0xFFFF` exit portal within its building's portal graph?)
|
||||
from "where to clip it" (`OutsideView`). Still needs a clip region when `OutsideView`
|
||||
momentarily empties (else bleed) — likely the raw exit-portal projection as a fallback.
|
||||
3. **Investigate the specific instability first.** Why does `0xA9B40170` drop from the cellar's
|
||||
BFS at certain angles — is it `CameraOnInteriorSide` on the stairwell portal being pose-brittle?
|
||||
A more robust (epsilon / reciprocal-aware) side test might stabilize the common case before a
|
||||
full PVS port. Cheapest; verify it's retail-faithful, not a fudge.
|
||||
|
||||
Recommendation: **brainstorm U.4c** (superpowers:brainstorming) starting from approach 1 vs 3,
|
||||
using the `[vis]` probe as the apparatus. Build a stable visible-set; the clip stays per-frame.
|
||||
|
||||
### Also deferred (not the flap, separately tracked)
|
||||
- **U.5** — outdoor-camera → building-interior peering (retail `outdoor_pview` / `DrawBuilding` /
|
||||
`DrawPortal` / `ConstructView(CBldPortal)`). Standing OUTSIDE looking INTO a house still shows
|
||||
no interior; that's U.5, not the flap. Open data dependency: render-side building-exterior
|
||||
portal geometry (we carry `BldPortalInfo` physics-side).
|
||||
- **U.6** — dungeon-scale validation; close/relate #95 + the residual #102 diamond-clip note.
|
||||
- Minor leftovers flagged in the U.4 review: `AppendSlot` collapses the 3 `Count==0` states (U.4c
|
||||
should branch `IsNothingVisible`/`UseScissorFallback` before calling it); orphaned
|
||||
`LandblockEntriesWithoutAnimatedIndex`; dead `BuildingShellAnchorPass/Reject` counters.
|
||||
|
||||
---
|
||||
|
||||
## Apparatus (use this, evidence-first)
|
||||
|
||||
- **`ACDREAM_PROBE_VIS=1`** — `[vis]` line per cell change: `root` cell, visible-cell count + ids,
|
||||
`outside(polys=N,planes=M)`, per-cell plane counts, scissor `fallbacks`. The flap shows as
|
||||
`outside(polys=0)` frames interleaved with `outside(polys=1)` for the same cell. Owner:
|
||||
`AcDream.Core.Rendering.RenderingDiagnostics.EmitVis`.
|
||||
- Launch block: CLAUDE.md "Running the client" + `ACDREAM_PROBE_VIS=1`, pipe to a fresh log.
|
||||
Read it with PowerShell `Select-String` (the Tee log is UTF-16) or `tr -d '\0' | grep` in bash.
|
||||
|
||||
## Next-session pickup prompt
|
||||
```
|
||||
Phase U.4c — stabilize portal visibility (fix the threshold "flap"). The unified render
|
||||
pipeline (Phase U, U.1-U.4) is shipped and indoor rendering is visually verified correct.
|
||||
The remaining issue is the doorway "flap": terrain + building-shells flicker off when
|
||||
crossing the threshold. Root cause is in
|
||||
docs/research/2026-05-30-phase-u4-shipped-and-flap-handoff.md — READ IT FIRST. It is the
|
||||
per-frame view-dependent portal BFS being unstable at multi-hop exit paths (OutsideView
|
||||
flickers empty → terrain Skip'd). The retail-faithful fix is PVS / stab_list grounding
|
||||
(retail grab_visible_cells / seen_outside). This is a FRAGILE cell-resolution area (#98
|
||||
saga) — start with superpowers:brainstorming on PVS-grounding vs a targeted side-test
|
||||
stabilization; use ACDREAM_PROBE_VIS as the apparatus; NO workarounds (no hysteresis
|
||||
band-aid). Do NOT touch the indoor rendering (it works). U.5 (outdoor→building peering)
|
||||
and U.6 (dungeon scale) remain after U.4c.
|
||||
```
|
||||
|
||||
## Git state
|
||||
- All Phase U work committed on `claude/thirsty-goldberg-51bb9b`, **unpushed** (push is the user's call).
|
||||
- Two `git stash` entries on the branch (`#98/#101` physics WIP, pre-triage backup) — preserve, do not drop.
|
||||
Loading…
Add table
Add a link
Reference in a new issue