From 75b1df9cc31827832366a80a8b1ff632ae70d2cc Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 30 May 2026 11:35:41 +0200 Subject: [PATCH] docs: abandon two-pipe render approach; scope Phase U (unified retail-faithful pipeline) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- CLAUDE.md | 16 ++ docs/ISSUES.md | 7 +- docs/plans/2026-04-11-roadmap.md | 41 +++- docs/plans/2026-05-12-milestones.md | 11 + ...ed-render-pipeline-decision-and-handoff.md | 218 ++++++++++++++++++ 5 files changed, 289 insertions(+), 4 deletions(-) create mode 100644 docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md diff --git a/CLAUDE.md b/CLAUDE.md index 018dbce..319dbbd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -725,6 +725,22 @@ Visual side-by-side passed: Holtburg town, inn interior, dungeon all render identically to pre-O. Spec: [`docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md`](docs/superpowers/specs/2026-05-21-phase-o-dat-path-unification-design.md). +**2026-05-30 — RENDER PIPELINE PIVOT (read this first).** The two-pipe +(inside / outside) render approach is **ABANDONED**. acdream inherited a +WorldBuilder-style split — a normal outdoor draw plus a separate flat +`RenderInsideOut` stencil pass toggled on `cameraInsideBuilding` — and that +split is the root cause of every indoor seam bug (the flap, missing/transparent +walls, terrain bleeding into interiors). Retail has no such split; it renders +through one portal-visibility traversal (`PView`) and is seamless by +construction. We are building **Phase U — a single unified retail-faithful +render pipeline**. This supersedes the A8/A8.F two-pipe arc (issue #103). The +camera-collision work (retail `SmartBox::update_viewer` spring arm) + a +physics viewer-cap fix **SHIPPED this session and are kept** (they're real and +retail-faithful, just not the seam fix). Full decision + scope + next-session +pickup prompt: +[`docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md`](docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md). +The M1.5 narrative below is history retained for context. + **Currently working toward: M1.5 — Indoor world feels right** (resumed from 2026-05-20 baseline after Phase O ship). **A6.P1 + A6.P2 + A6.P3 slice 1 SHIPPED 2026-05-21.** **A6.P3 slice 2 v2 SHIPPED 2026-05-22** diff --git a/docs/ISSUES.md b/docs/ISSUES.md index 6ee62e3..50c65f1 100644 --- a/docs/ISSUES.md +++ b/docs/ISSUES.md @@ -46,7 +46,12 @@ Copy this block when adding a new issue: ## #103 — Phase A8.F portal-frame indoor rendering broken at runtime (visual-gate failure) -**Status:** OPEN +**Status:** SUPERSEDED 2026-05-30 by **Phase U (Unified Render Pipeline)**. The +two-pipe (inside/outside) approach this bug lives in is being abandoned wholesale — +the broken `RenderInsideOut` two-pipe path is deleted as Task 1 of Phase U and +replaced by a single unified retail `PView` portal-visibility pipeline. #103 will +not be fixed in place. See +[docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md](research/2026-05-30-unified-render-pipeline-decision-and-handoff.md). **Severity:** MEDIUM (opt-in branch only — default game unaffected) **Filed:** 2026-05-29 **Component:** render (indoor visibility) diff --git a/docs/plans/2026-04-11-roadmap.md b/docs/plans/2026-04-11-roadmap.md index b58dc93..ee0b13a 100644 --- a/docs/plans/2026-04-11-roadmap.md +++ b/docs/plans/2026-04-11-roadmap.md @@ -113,12 +113,47 @@ with no code changes lost — M1.5 doesn't touch WB-extracted territory. ### Milestone M1.5 — "Indoor world feels right" (ACTIVE — Phase O shipped; resuming from 2026-05-20 baseline) -The current top of the work order. Two phases (A6 + A7) inside one -milestone. M2 ("kill a drudge") is deferred until M1.5 lands — -drudges live in dungeons and the M2 demo target requires solid indoor +The current top of the work order. M2 ("kill a drudge") is deferred until M1.5 +lands — drudges live in dungeons and the M2 demo target requires solid indoor navigation. Full milestone block in [`docs/plans/2026-05-12-milestones.md`](2026-05-12-milestones.md). +**2026-05-30 — render-pipeline pivot.** Indoor *rendering* (the seamless in/out +seam: the flap, missing/transparent walls, terrain bleed) is NO LONGER pursued via +the WB-inherited two-pipe (inside/outside) split. That whole approach (Phase A8/A8.F, +issue #103) is **abandoned**. Indoor rendering is now **Phase U** below. Phase A6 +(physics) and A7 (lighting) inside M1.5 are unaffected. + +#### Phase U — Unified retail-faithful render pipeline (NEW — supersedes A8/A8.F) + +**Decision (2026-05-30):** replace the two render paths (outdoor `Draw` + +`RenderInsideOut` stencil, toggled on `cameraInsideBuilding`) with ONE pipeline driven +by retail's portal-visibility view (`PView::ConstructView` / `ClipPortals` / `GetClip`; +`CEnvCell::find_visible_child_cell`). The camera's cell is just the root of a recursive +per-portal clip-region traversal; all visible cells (indoor + outdoor) draw in one pass. +Seamless in/out **by construction** — no inside/outside branch. Modern code, retail +behavior. + +- **Why:** the two-pipe split is a WB-editor inheritance, not a game-client design; you + cannot make two pipes hand off seamlessly at a doorway. Retail never splits. The A8.F + attempt to graft retail recursion onto the WB stencil failed its visual gate (#103). +- **Keep:** WB mesh/dat pipeline (ObjectMeshManager/WbDrawDispatcher/terrain), the + 2026-05-30 camera-collision + physics work. **Salvage (verify):** the A8.F CPU + clip-builder (PortalProjection/ScreenPolygonClip/ViewPolygon/PortalVisibilityBuilder — + unit-test-correct). **Task 1:** delete the dead two-pipe code (RenderInsideOutAcdream, + the cameraInsideBuilding branch, IndoorCellStencilPipeline, the ACDREAM_A8_INDOOR_BRANCH + kill-switch) — audit first; some A8 commits fixed real bugs (BuildingId stamping, pool + aliasing) that stay. +- **Scope + next-session pickup:** + [`docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md`](../research/2026-05-30-unified-render-pipeline-decision-and-handoff.md). + Start with `superpowers:brainstorming`; visual verification at Holtburg + cottage/cellar/inn + a portal dungeon is the acceptance gate (unit tests did not + catch #103). +- **Camera-collision (shipped 2026-05-30, kept):** retail `SmartBox::update_viewer` + swept-sphere spring arm (`CameraDiagnostics.CollideCamera`, `PhysicsCameraCollisionProbe`, + `RetailChaseCamera` integration) + viewer/sight bypass of the 30-step transition cap. + Specs: [`2026-05-29-a8f-camera-collision-design.md`](../superpowers/specs/2026-05-29-a8f-camera-collision-design.md). + **Today's pre-M1.5 baseline** (2026-05-20 — committed in this session): A4 multi-cell BSP iteration (`691493e`), #89 sphere-overlap in CheckBuildingTransit (`7ac8f54`), #90 sphere-overlap stickiness in diff --git a/docs/plans/2026-05-12-milestones.md b/docs/plans/2026-05-12-milestones.md index 569db86..7740195 100644 --- a/docs/plans/2026-05-12-milestones.md +++ b/docs/plans/2026-05-12-milestones.md @@ -187,6 +187,17 @@ close range and the player sees "You pick up the X." in chat. ### M1.5 — "Indoor world feels right" — 🔵 ACTIVE (resumed 2026-05-21 after Phase O ship) +**2026-05-30 — render-pipeline pivot.** The indoor *rendering* seam (seamless +in/out: the flap, missing/transparent walls, terrain bleed) will be solved by a +**single unified retail-faithful render pipeline (Phase U)**, replacing the +abandoned two-pipe inside/outside split (A8/A8.F, issue #103). The two-pipe split +is a WorldBuilder inheritance; retail uses one portal-visibility pass and is +seamless by construction. Decision + scope: +[`docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md`](../research/2026-05-30-unified-render-pipeline-decision-and-handoff.md). +Camera-collision + a physics viewer-cap fix shipped 2026-05-30 and are kept (they +were a detour from the real seam fix, but retail-faithful and worth keeping). A6 +(physics) and A7 (lighting) are unaffected. + **Phase O — DatPath Unification — shipped 2026-05-21.** ONE thing touches the DATs. ~33 WB files (~7.7K LOC) extracted into `src/AcDream.{Core,App}/Rendering/Wb/`; project references to diff --git a/docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md b/docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md new file mode 100644 index 0000000..a7b6896 --- /dev/null +++ b/docs/research/2026-05-30-unified-render-pipeline-decision-and-handoff.md @@ -0,0 +1,218 @@ +# 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`.