acdream/docs/research/2026-05-30-phase-u4-shipped-and-flap-handoff.md
Erik a3ecac5369 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>
2026-05-31 09:16:35 +02:00

10 KiB
Raw Blame History

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) 3916b2b65781f5
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 bf2e559864fc5f
U.4 Unified gated draw pass (ClipFrameAssembler, per-instance slots, EnvCellRenderer.Render wired, terrain Skip/Scissor/Planes) + ResolveEntitySlot tests 7993e06354ca74
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 0x1480x149) → 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 OutsideViewTerrainMode.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.