# 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 (~4–15), 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`.