acdream/docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md
Erik 75b1df9cc3 docs: abandon two-pipe render approach; scope Phase U (unified retail-faithful pipeline)
Decision (2026-05-30, with user): the WB-inherited two-pipe (inside/outside) render
split is the root cause of the indoor seam bugs (flap, missing/transparent walls,
terrain bleed) and cannot be seamless. Abandon A8/A8.F (#103); build ONE unified
pipeline driven by retail's PView portal visibility — seamless by construction. The
2026-05-30 camera-collision + physics viewer-cap work is kept (retail-faithful, but a
detour from the seam fix). New Phase U scoped; #103 superseded; CLAUDE.md / roadmap /
milestones updated; full decision + scope + next-session pickup prompt in
docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-30 11:35:41 +02:00

218 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Unified retail-faithful render pipeline — decision, scope, and handoff (2026-05-30)
## TL;DR (the decision)
We are **abandoning the two-pipe (inside / outside) rendering approach** and
committing to a **single, unified, retail-faithful render pipeline** built around
retail's portal-visibility view (`PView`). Modern code, retail behavior. This is a
new roadmap phase — **Phase U (Unified Render Pipeline)** — and it is milestone-scale
work, not a patch.
**Why:** acdream inherited a *two-pipe* render structure from WorldBuilder (WB) — a
normal outdoor draw plus a separate flat `RenderInsideOut` stencil pass toggled on
`cameraInsideBuilding` (`GameWindow.cs` ~7345). That split is the root cause of every
indoor/outdoor seam bug (the "flap", missing/transparent walls, terrain bleeding into
interiors). **Retail has no such split.** Retail renders through one portal-visibility
traversal: starting from whatever cell the camera is in, it walks recursively through
portals (doors, windows, cell openings), builds a screen-space clip region per opening,
and draws every visible cell — indoor *and* outdoor — in one pass. There is no
"am I inside a building?" branch, so transitions are seamless **by construction**.
The A8.F effort tried to graft retail's recursive clip *on top of* WB's two-pipe
stencil (a CPU-built NDC mask bridging the two pipes). That hybrid is inherently
fragile and failed its visual gate (**issue #103**). You cannot make two pipes hand
off seamlessly at a doorway; retail avoids the entire bug class by never splitting.
This was confirmed in collaboration with the user, who correctly identified that the
whole direction was wrong: *"in retail there is totally seamless transitions between
out and in… it's like we are on the wrong path here."* Correct.
---
## What this session actually shipped (the salvage)
This session was originally a "camera collision" effort (a reframing of the #103
failure onto the camera eye — itself a detour). The camera work is **real,
retail-faithful, and kept**, but it is **not** the fix for seamless transitions:
- **Swept-sphere camera collision** (retail `SmartBox::update_viewer`, `0x00453ce0`):
`CameraDiagnostics.CollideCamera` (default on), `ICameraCollisionProbe` +
`PhysicsCameraCollisionProbe` (reuses `PhysicsEngine.ResolveWithTransition`,
retail `viewer_sphere` 0.3 m, `init_object(player, 0x5c)` =
`IsViewer|PathClipped|FreeRotate|PerfectClip`), wired into `RetailChaseCamera`
(collide into a *separate* published eye — never the damped sought eye — to avoid
the wall-press oscillation), `GameWindow` wiring + a Camera-menu toggle.
- **Physics fix (retail-faithful):** viewer/sight sweeps bypass acdream's 30-step
safety cap in `Transition.FindTransitionalPosition` (retail `find_transitional_position`
has no cap; `calc_num_steps` has a `state & 4` viewer branch). Gate: `&& !ObjectInfo.IsViewer`.
- Commits `69c7f8d``aae5300` (plus the design spec/plan docs).
**Camera-collision residual nits (minor, deferred — NOT blockers):**
- Eye can clip ~0.3 m into a wall and reveal outside (near-clip plane 1 m vs collision
radius 0.3 m — tuning).
- Residual non-retail-smooth feel at walls (the gross oscillation is fixed; tuning remains).
These are polish; revisit after the pipeline lands (or never — they're cosmetic).
---
## Git state at handoff
- Branch `claude/strange-albattani-3fc83c` was **239 commits ahead of `main`**
(both forked at Phase O, `2256006`, 2026-05-21). The branch carries ~9 days of good
work since Phase O: A6 physics, issues #98/#100/#101, A7 lighting, the A8/A8.F
rendering arc, and the camera work — all of it.
- Per user decision (2026-05-30): **the whole branch was merged into `main`** so none
of that work is lost. The dormant, gated-off A8 two-pipe rendering rides along and is
**deleted as Task 1 of Phase U** (see below). The user pushes `main` to remotes.
- The `#98`/`#101` physics WIP from a prior session is preserved in `git stash` on this
branch (a subagent had `git stash apply`-ed it mid-session; it was re-stashed). Do not
lose it.
---
## Phase U — Unified Render Pipeline (scope / design sketch)
> This is a scope sketch to start a proper brainstorm + spec from — **not** a finalized
> design. The next session brainstorms it.
### Goal
One render path. The camera's current cell is the root of a per-frame portal-visibility
traversal that yields *(visible cells, per-cell screen-space clip region)*; the renderer
draws all visible geometry (indoor cells, outdoor cells, entities, terrain) in a single
pass gated by that visibility. **No `cameraInsideBuilding` branch. No `RenderInsideOut`
stencil pass. No outdoor-vs-indoor toggle.** Seamless in/out by construction.
### Retail oracle (the thing to port)
- `PView::ConstructView` (decomp ~`433750`), `PView::ClipPortals` (~`433572`),
`PView::GetClip` (~`432344`) — the recursive per-portal screen-space clip-region BFS.
- `CEnvCell::find_visible_child_cell` (`acclient_2013_pseudo_c.txt:311397`,
`0x0052dc50`) — per-cell portal-visible-child resolution; call site `:280028`.
- `RenderDeviceD3D::DrawBlock` (~`430027`) — the render loop the visibility chain feeds.
- See the project memory note **indoor-portal-visibility-wb-vs-retail** for why WB
cannot express per-portal clipping and the oracle is retail `PView`.
### What to KEEP (do not re-port)
- The WB-derived **mesh/dat pipeline** is fine and stays: `ObjectMeshManager`,
`WbMeshAdapter`, `WbDrawDispatcher`, terrain (`TerrainModernRenderer`,
`LandblockMesh`), `DatCollection`, texture decode. Phase U is about **visibility +
draw orchestration**, not mesh extraction.
- The **camera collision** and **physics** work from this session.
- Cell data: `CellVisibility` (`FindCameraCell`, `PointInCell`, portal data),
`PhysicsDataCache` cell structures.
### What is likely SALVAGEABLE from the failed A8.F (verify, don't assume)
The A8.F CPU clip-builder pieces are **unit-test-correct** (the integration is what
failed). They may feed a unified draw directly:
- `PortalProjection` (GL near-plane clip), `ScreenPolygonClip` (2D convex intersection),
`ViewPolygon`/`CellView` (clip-region model), `PortalVisibilityBuilder` (recursive
portal-clip BFS producing per-cell `OutsideView`).
The failure was the *two-pipe stencil graft* around them (the CPU NDC mask gating a
*separate* outdoor pipe), not the clip math. A unified pipeline can likely reuse the
builder to produce per-cell clip frames and gate **one** pass.
### What to DELETE (Task 1 — clear the deck)
The dead two-pipe rendering, so the unified path is built clean, not bolted on:
- `RenderInsideOutAcdream` and the `cameraInsideBuilding` branch in `GameWindow.cs`
(~`7345`+), the `IndoorCellStencilPipeline` / `MarkAndPunchNdc` stencil graft, the
Job-A/B decouple, the `ACDREAM_A8_INDOOR_BRANCH` kill-switch, the
`EnvCellRenderer` WB `RenderInsideOut` port (`f9a644a` lineage).
- Keep `EnvCellRenderManager`'s *mesh* path if the unified draw needs it; delete only
the inside-out *visibility/stencil* machinery.
- Audit before deleting: some A8 commits also fixed real bugs (e.g. `BuildingId`
stamping, pool aliasing `9559726`) — keep those.
### Approach sketch (for the brainstorm to refine)
1. **Visibility pass** (CPU, GL-free, testable): from the camera cell, recursive portal
BFS → ordered set of visible cells, each with a screen-space clip polygon (intersection
of the portal openings along its chain). This is retail `PView::ConstructView`.
Likely reuses the salvaged A8.F `PortalVisibilityBuilder` family.
2. **Draw pass** (single, unified): for each visible cell (front-to-back), draw its
geometry + entities clipped to its clip region (scissor or stencil-per-cell, retail
uses a clip rect/region). Outdoor cells are just cells in this set — no special path.
Terrain is drawn per visible outdoor cell, gated the same way.
3. **No branch:** the camera being indoors vs outdoors changes only *which cell is the
root*, not the algorithm.
### Key risks / lessons (do not repeat)
- **Do not graft retail recursion onto WB's flat two-pipe stencil** — that's what
#103 was. Build the unified pass; don't bridge two pipes.
- **Unit tests on synthetic visibility data did not catch #103 — only the visual gate
did.** Visual verification at the cottage/cellar/inn/dungeon is the real acceptance.
Build a runtime visibility probe early (`ACDREAM_PROBE_VIS`) and validate against live
frames, not just synthetic fixtures.
- **A CPU-built mask gating ALL outdoor geometry is fragile.** The unified pass should
gate per-cell at draw time (scissor/stencil per visible cell), close to how retail
clips, rather than one global mask.
- **The camera is not the fix.** That reframing cost this session; the fix is the
visibility architecture.
### Success criteria (visual)
- Walk Holtburg cottage → cellar → out the door: no flap, walls solid, no terrain
bleed, seamless threshold crossing from any camera angle/zoom.
- Holtburg Inn: no outdoor stabs/terrain visible through the floor/walls (closes #78).
- Dungeon via Town Network portal: `visibleCells` stays sane (~415), no other-dungeon
geometry (closes/relates #95).
- No regression to outdoor rendering (the default game today).
---
## Next-session pickup prompt
```
We are building Phase U — a single unified retail-faithful render pipeline (retail
PView portal-visibility), abandoning the WB-inherited two-pipe (inside/outside) split
that caused the indoor seam bugs (the flap, missing/transparent walls, terrain bleed).
The decision + full scope is in
docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md — READ IT FIRST,
then the project memory note "indoor-portal-visibility-wb-vs-retail" and the #103
failure handoff (docs/research/2026-05-29-a8f-visual-gate-failure-handoff.md).
State both altitudes:
Currently working toward: M1.5 — Indoor world feels right.
Current phase: U (Unified Render Pipeline). This supersedes the abandoned A8/A8.F
two-pipe approach (#103).
Start with superpowers:brainstorming to design the unified pipeline (do NOT jump to
code). The scope sketch in the handoff doc is the input. Key decisions to settle in the
brainstorm: (a) reuse the salvaged A8.F clip-builder (PortalProjection/ScreenPolygonClip/
ViewPolygon/PortalVisibilityBuilder — unit-test-correct) vs fresh port; (b) per-cell
clip mechanism (scissor rect vs stencil-per-cell) matching retail's GetClip; (c) how
terrain + outdoor entities become "just cells" in the visible set.
Task 1 of implementation is to DELETE the dead two-pipe code (RenderInsideOutAcdream,
the cameraInsideBuilding branch, IndoorCellStencilPipeline/MarkAndPunchNdc, the
ACDREAM_A8_INDOOR_BRANCH kill-switch) to clear the deck — but audit first; some A8
commits fixed real bugs (BuildingId stamping, pool aliasing) that must be kept.
Retail anchors: PView::ConstructView ~433750, ClipPortals ~433572, GetClip ~432344,
CEnvCell::find_visible_child_cell :311397, RenderDeviceD3D::DrawBlock ~430027.
Keep: the WB mesh/dat pipeline (ObjectMeshManager/WbDrawDispatcher/terrain), the camera
collision + physics from the 2026-05-30 session. Visual verification at Holtburg
cottage/cellar/inn + a portal dungeon is the acceptance gate — unit tests did not catch
#103.
Preserve the git stash on the branch (#98/#101 physics WIP).
```
---
## Reference index
- Decision context / why WB can't do it: project memory **indoor-portal-visibility-wb-vs-retail**.
- #103 failure detail: `docs/research/2026-05-29-a8f-visual-gate-failure-handoff.md`.
- Camera-collision work this session: `docs/superpowers/specs/2026-05-29-a8f-camera-collision-design.md`,
`docs/superpowers/plans/2026-05-29-a8f-camera-collision.md`.
- A8.F portal-frame port (the failed two-pipe graft):
`docs/superpowers/specs/2026-05-29-phase-a8f-portal-frame-visibility-design.md`.
- Retail decomp: `docs/research/named-retail/acclient_2013_pseudo_c.txt`.
- WB visibility reference: `references/WorldBuilder/Chorizite.OpenGLSDLBackend/Lib/VisibilityManager.cs`.