acdream/docs/research/2026-05-31-render-architecture-reset-handoff.md
Erik 0013819fa1 docs(render): ARCHITECTURE RESET — indoor render is a 3-gate patchwork; handoff + unified-PView target
A week on the indoor render (Phase U.4 → U.4c → 2026-05-31) fixed the flap but
produced NO shippable progress: walls/ceiling don't seal, outdoor terrain is
visible from inside (#78), the enclosure reads grey/transparent. Root cause is
ARCHITECTURAL, not a bug.

Evidence this session (direct, via the new [shell] probe + screenshots) RULED OUT
every subsystem except the gating architecture: the interior cell shells render
fine (geometry/texture/opaque/depth all correct, zh=0 tr=0); the visibility
traversal computes correct sets + non-empty portal clips; cull mode is fine; the
camera/eye thread was a detour. The residual is that OUTDOOR geometry is not gated
to portal openings when indoors, and acdream enforces visibility THREE inconsistent
ways (TerrainClipMode / per-cell shell clip / entity ParentCellId filter with an
outdoor-stab bypass) instead of retail's ONE PView gate.

This commit is the reset handoff + documentation, not a code fix:
- docs/research/2026-05-31-render-architecture-reset-handoff.md — canonical: honest
  state, evidence ledger (ruled-out / do-not-repeat), the mapped 3-gate patchwork,
  the retail PView target (one traversal → one gate for ALL geometry), the reset
  mission, and a copy-paste pickup prompt.
- docs/architecture/acdream-architecture.md — new "Render Pipeline" SSOT section
  (current divergence + unified-PView target + the one rule: compute visibility
  once, enforce it once). (Doc has pre-existing corruption below this section —
  flagged for separate cleanup.)
- Apparatus: ACDREAM_PROBE_SHELL → [shell] (EnvCellRenderer per-cell prepared/drawn
  geometry + flags) added to RenderingDiagnostics + EnvCellRenderer. Throwaway.
- docs/superpowers/specs/2026-05-31-camera-collision-indoor-engagement-design.md —
  spec for e099b4c (camera collision; now parked as orthogonal to the seam).

Next session: STOP point-fixing; do the architecture reset to a single PView gate.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 21:35:55 +02:00

14 KiB
Raw Blame History

Render Architecture Reset — assessment + handoff (2026-05-31)

Read this first, in full, before touching any render code. This is a deliberate reset after a week of point-fixing the indoor render with no shippable progress. The next session's job is NOT another symptom fix — it is to bring acdream's render pipeline up to a single, coherent, retail-faithful design (PView) and close the indoor seams by construction.

1. The honest state

A full week (Phase U.4 → U.4c → this session) has gone into the indoor render. Net result: the doorway "flap" is fixed (real, kept), but the indoor world is still broken — walls/ceiling don't visually seal, you see outdoor terrain from inside the cellar ("the world from below"), and the enclosure reads as a grey/transparent void.

The reason there's been no progress is architectural, not a bug: the render pipeline is a patchwork of three inconsistent visibility gates bolted onto a half-finished port of retail's PView. Seams are structural — fixing one place (the flap, the camera, the shells) pops a seam in another. We have been chasing symptoms across different subsystems (visibility → camera collision → shell rendering) instead of fixing the architecture. That stops now.

Mandate from the user (2026-05-31): "produce me a solid engine that produces a retail-faithful experience… look over what we have done, check our design and if it matches a solid engine design, and solve all our issues." This handoff is the assessment; the next session executes the reset.

2. Evidence ledger — what is RULED OUT and CONFIRMED (do not re-litigate)

This week was not all waste: it eliminated, with direct evidence, every subsystem except the gating architecture. Do not re-investigate these.

CONFIRMED (direct evidence):

  • The interior cell shells RENDER correctly. [shell] probe (ACDREAM_PROBE_SHELL): every visible cell (0xA9B40170-0175) is prepared and drawn — gfx=1, idx 24270, tr=0 (not translucent), zh=0 (textures present), opaque pass, depth-on. So the shells are NOT the problem (geometry, textures, cull, depth all fine).
  • The visibility traversal computes correct results. [flap] probe: portals traverse (TRV), project, and clip to non-empty screen regions (clip=4/5), vis=3 cells, terrain=Skip/outPolys=0 correct for a windowless cellar.
  • The residual is OUTDOOR geometry not being gated to portal openings when indoors (issue #78): screenshots show outdoor terrain + scenery rendered and visible from inside the cellar (from below), and the interior not sealing. The cell mesh renders but does not occlude / the outside is not clipped to the window.

RULED OUT (with evidence) — do NOT chase again:

  • The camera / eye position as the root (eye-displacement was reduced by e099b4c but the residuals persisted unchanged — the camera thread was a detour).
  • Cull mode (cell shells already render double-sided, EnvCellRenderer.cs:1168).
  • Cell-shell geometry missing / textures missing ([shell] proves both present).
  • The portal side-test / PortalSide (b5f2bf2, byte-identical no-op).
  • PVS / stab_list grounding (H1, 639f20f plumbed, not the fix).
  • Camera zoom as the cause (errors persist at minimum zoom).

3. The current render architecture (mapped) — the patchwork

Per-frame, inside GameWindow.OnRender (~:7290-7471):

CellVisibility.ComputeVisibility(visRootPos = PLAYER pos)   → visible-cell SET + VisibleCellIds + CameraCell
        │
PortalVisibilityBuilder.Build(clipRoot, visRootPos=PLAYER, envCellViewProj=EYE)   ← a partial port of retail ConstructView
        │   → PortalVisibilityFrame { OutsideView, per-cell CellViews, OrderedVisibleCells }
        │   (side-test + ordering use the PLAYER; projection uses the EYE — a split)
        │
ClipFrameAssembler.Assemble(...)   → TerrainClipMode {Skip|Scissor|Planes} + CellIdToSlot (per-cell clip regions) + OutdoorVisible
        │
        ├── TerrainModernRenderer.Draw       gated by TerrainClipMode  (GATE #1)
        ├── EnvCellRenderer.Render(shells)   gated by envCellShellFilter + per-cell clip slot  (GATE #2)
        └── WbDrawDispatcher.Draw(entities)  gated by ParentCellId ∈ visibleCellIds … BUT outdoor stabs have ParentCellId==null and ALWAYS PASS  (GATE #3)

Why this is structurally fragile (the root of all the seams):

  1. Three separate gates (terrain / shells / entities) must independently agree to produce a seamless interior. They don't. Retail has one gate (PView).
  2. Outdoor geometry is not gated to portal openings. Terrain has its own Skip/Scissor/Planes path (and the Scissor fallback over-includes), and outdoor stabs/scenery (ParentCellId==null) bypass the gate entirely → you see the outdoor world from inside (#78). Retail draws the landscape only through exit portals, clipped to the opening.
  3. No single source of truth. "What is visible and where on screen" is reassembled from three consumers of one builder, instead of being the builder's authoritative, uniformly-enforced output.
  4. Player-rooted set vs eye-projected clip split adds a second axis of fragility (the flap, the residual degeneracies) — secondary, but real.

This is the crux: we compute visibility once but enforce it three different, inconsistent ways.

4. The retail-faithful target — PView: one traversal, one gate

Retail (decomp docs/research/named-retail/acclient_2013_pseudo_c.txt) renders the world through PView, a single portal-clipped traversal:

  • Root at the viewer's cell (CellManager::ChangePosition tracks curr_cell).
  • PView::ConstructView (:433750) walks the portal graph; InitCell (:432896) computes each cell's screen-space clip region (the portal opening it's seen through, recursively intersected with the parent's region); cells go on cell_draw_list closest-first.
  • Exit portals add the outside (landscape) to the view, clipped to the exit-portal opening.
  • PView::DrawCells (:432715) renders each cell clipped to its region, and the landscape only where an exit portal reveals it.
  • The viewer (eye) is collided (SmartBox::update_viewer :92761) so the projection stays well-conditioned.
  • CEnvCell::find_visible_child_cell (:311397) + seen_outside gate the landscape-keep.
  • Outdoors, PView is trivial (the LandCell + neighbours, no indoor clipping).

The defining property: ONE visibility structure gates ALL geometry — interior cell shells, interior statics, and the outside (terrain + scenery) — all clipped to their PView-derived regions. That is why retail is seamless by construction.

Reference port to crib (acdream owns it but never invokes it): WorldBuilder's RenderInsideOut / VisibilityManager (references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs).

5. The solid design for acdream (the reset target)

We already have the hard part (the PortalVisibilityBuilder ConstructView port + the ClipFrame clip-plane machinery + per-cell clip slots). The reset is a consolidation, not a rewrite: make the PView output the single gate for every geometry type.

  1. One gate. Every draw — cell shells, interior statics, terrain, outdoor scenery — is clipped to its PView region:
    • Interior cell C → its per-cell clip region (already ≈done in EnvCellRenderer).
    • Interior static in cell C → cell C's region (route via the same per-instance clip slot).
    • Outside (terrain + outdoor scenery + ParentCellId==null stabs) → the OutsideView region (union of exit-portal openings). Empty OutsideView (windowless interior) ⇒ draw NO outdoor geometry. No ParentCellId==null bypass.
    • Outdoor root (camera/player in a LandCell, no indoor cell) ⇒ gate = everything (today's normal path).
  2. Delete the ad-hoc gates. Collapse GATE #1/#2/#3 into the single clip-to-region. Remove the ParentCellId==null always-pass in WbDrawDispatcher. Remove the terrain Scissor over-include fallback in favour of retail's exact portal clip.
  3. Resolve the set/clip split. Decide one rooting story (the flap fix proved the set must be player-rooted; the clip/projection is the eye's — confirm this is retail-faithful under the unified gate, or collide the eye so they coincide).
  4. U.5 (eye outside, looking into a building) is the same machinery with the root at the eye's cell and the interior shown through the building's portals — design it in, don't special-case it.

Result, by construction: #78 (outdoor gated to openings), transparent walls (interior uniformly clipped), grey enclosure (no spurious ungated gaps), terrain-through-floor (no outdoor outside the window) all resolve together — because there is one consistent rule instead of three.

6. Next-session mission (the reset — in order)

  1. Audit the current render data-flow against §3; confirm the three-gate divergence in the live code.
  2. Verify the target against retail: read PView in full (ConstructView/InitCell/ClipPortals/AddViewToPortals/DrawCells/ grab_visible_cells/find_visible_child_cell) and WB RenderInsideOut. Do not trust this doc's summary alone — confirm the one-gate model from the source.
  3. Design the unified gate (superpowers:brainstorming → spec). This is architectural; brainstorm it, don't free-solo.
  4. Implement the single gate; delete the ad-hoc gates/bypasses.
  5. Verify at the visual gate: flap stays fixed; walls/ceiling solid; no terrain-through-floor; no grey; outdoor visible only through windows/doors, clipped.

Operating rules for the reset: evidence-first; no point-fixes; no new gate bolted on to mask a seam (that is exactly what produced this mess). If a fix starts to look like a fourth special-case gate, stop — it belongs in the one gate.

7. What the week produced (status of each artifact)

  • KEEP — 0ee328a flap fix: root indoor visibility at the player's cell. A correct, retail-faithful PView piece (the set must be player-rooted). Keep.
  • ORTHOGONAL — e099b4c camera-collision viewer-gate-exemption: retail-faithful but not the seam fix (the camera thread was a detour). Open decision: keep (it's a real, faithful improvement) or revert to keep the reset branch focused. Recommendation: park it — keep the commit but treat camera collision as out-of-scope for the reset; revisit only after the unified gate lands. Its sibling diagnosis (3066460) + spec (docs/superpowers/specs/2026-05-31-camera-collision-indoor-engagement-design.md)
    • the CameraCollisionIndoorTests are accurate for what they cover but are about an orthogonal subsystem.
  • APPARATUS (throwaway, gated, useful for the reset):
    • ACDREAM_PROBE_FLAP[flap]/[flap-cam]/[flap-sweep] (visibility + eye + camera sweep).
    • ACDREAM_PROBE_SHELL[shell] (per-cell shell render: prepared? geometry? flags?). Strip both once the reset lands.
  • Disproven hypotheses (do NOT repeat): H1 (PVS grounding), H2 (PortalSide), zoom-confound, back-face-cull, eye-displacement-as-root, shell-geometry/texture-missing.

8. Git / safety

  • Branch claude/thirsty-goldberg-51bb9b, UNPUSHED (push pending with the user).
  • Two git stash entries preserved (#98/#101 WIP) — do NOT drop.
  • Throwaway capture logs u4c-*.log are untracked — ignore/delete, do not commit.

9. Pickup prompt (copy-paste for the next session)

RENDER ARCHITECTURE RESET — acdream indoor pipeline. Continue on branch
claude/thirsty-goldberg-51bb9b (do NOT branch/worktree; preserve the 2 git stashes).

READ FIRST, in full: docs/research/2026-05-31-render-architecture-reset-handoff.md, then
the "Render Pipeline" section of docs/architecture/acdream-architecture.md.

State both altitudes: Working toward M1.5 "indoor world feels right." After a week of
point-fixing with no shippable progress, this is an ARCHITECTURE RESET, not a symptom fix.

THE PROBLEM (root cause, evidence in the handoff): acdream's render pipeline is a
patchwork of THREE inconsistent visibility gates (terrain TerrainClipMode / shell
per-cell clip / entity ParentCellId filter with an outdoor-stab bypass) bolted onto a
half-finished PView port. Seams are structural. The interior cell shells RENDER FINE
(proven); the residual is that OUTDOOR geometry isn't gated to portal openings when
indoors (#78) and the three gates don't agree.

THE TARGET (retail PView, §4-5 of the handoff): ONE portal-visibility traversal whose
output is the SINGLE GATE for ALL geometry — interior shells, interior statics, AND the
outside (terrain + scenery) all clipped to their PView regions; outside drawn ONLY
through exit-portal openings; empty OutsideView ⇒ no outdoor; no ParentCellId==null
bypass; outdoor root ⇒ gate=everything. We already own the builder (PortalVisibilityBuilder
= ConstructView port) + ClipFrame; this is CONSOLIDATION, not a rewrite.

MISSION (in order): (1) audit the current 3-gate data-flow in GameWindow.OnRender
~:7290-7471; (2) VERIFY the one-gate model against retail PView (ConstructView :433750,
InitCell :432896, DrawCells :432715, find_visible_child_cell :311397) + WB RenderInsideOut
(references/WorldBuilder/.../VisibilityManager.cs) — confirm from source, not the handoff
summary; (3) brainstorm + spec the unified gate (superpowers:brainstorming); (4) implement
the single gate, delete the ad-hoc gates/bypasses; (5) visual-verify ALL indoor issues
resolve together.

DO NOT REPEAT (evidence-disproven, handoff §2): camera/eye as root, cull mode, shell
geometry/texture missing, H1 PVS grounding, H2 PortalSide, zoom-confound. DO NOT bolt on
a fourth gate to mask a seam. Evidence-first; no point-fixes; brainstorm the architecture
before coding; only stop for visual verification.

Apparatus ready: ACDREAM_PROBE_FLAP ([flap]/[flap-cam]/[flap-sweep]) + ACDREAM_PROBE_SHELL
([shell]). Open decision: keep or revert e099b4c (camera collision, orthogonal — parked).
Launch per CLAUDE.md "Running the client"; Holtburg cottage cellar/stairs is the test
scenario; visual verification at that threshold is the acceptance gate.