docs(render): REOPEN the render half — full retail-faithful redesign dossier (handoff + huge plan + 3 research docs)
The Phase W indoor seal did NOT land. The 2026-06-02 visual gate proved the interior render is fundamentally broken (#78: transparent walls, outdoor terrain + scenery entities bleeding in, grey floors, no outside-looking-in). Stage 4 (sky-through-door clip) was real but a top layer on a base that never sealed. DECISIVE EVIDENCE (committed in the handoff): the PVS computes correctly AND the cell shells render correctly (opaque, textured, complete — the [shell] probe shows zero NOSNAP / zero missing-texture). The failure is the SEAL + three inconsistent gates — concretely the WbDrawDispatcher.cs:1756 ParentCellId==null -> return true bypass draws outdoor scenery indoors, and the indoor path draws the outdoor world then gates it instead of running ONLY DrawInside. Retail, when inside, runs ONE PView flood: visibility IS the cull; the landscape enters only through clipped exit portals + a conditional depth-only clear. Dossier (per the user's mandate: NO shortcuts/bandaids, port from retail, redesign the whole pipeline if needed, brainstorm first): - Master handoff (root cause + retail target + reusable-vs-redesign + apparatus + do-not-repeat + copy-paste pickup prompt). - Huge staged redesign plan R0(brainstorm)->R1(one visibility authority, kill the bleed)->R2(indoor=DrawInside-only)->R3(the seal, DrawCells port)->R4(per-cell object/particle clip)->R5(outside-looking-in)->R6(dungeons)->R7(polish/conformance). Each ends at a user visual gate. - 3 research docs: full retail render pipeline reference (705 lines, decomp-verified), acdream pipeline inventory + failure map, reference cross-check (WB two-pipe is the wrong model). #78 promoted to the redesign. The 5 remaining Core test failures are pre-existing physics/collision bugs, none render-related. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
b595cfbb9f
commit
21bf97ed35
6 changed files with 2394 additions and 2 deletions
|
|
@ -0,0 +1,169 @@
|
|||
# Render Pipeline Redesign — Full Staged Plan (2026-06-02)
|
||||
|
||||
> **Read the master handoff first:** `docs/research/2026-06-02-render-pipeline-redesign-handoff.md`
|
||||
> (§2 evidence, §3 root cause, §5 retail target + porting checklist CL-A..G).
|
||||
> Then the 3 research docs. Then **BRAINSTORM** (Phase R0) before any code.
|
||||
|
||||
## The mandate (user, 2026-06-02 — non-negotiable, repeated so it's never lost)
|
||||
**FULLY WORKING outdoor + indoor + dungeon rendering. No flaps, no missing textures, no
|
||||
transparent walls, no terrain leaking into cellars, no entity/particle bleed. NO shortcuts,
|
||||
NO bandaids, NO quick fixes — if the architecturally-correct way is slower, take it. Refactor
|
||||
or redesign the whole pipeline if that's what it takes. Port from retail. Do more research
|
||||
mid-session rather than guess. Start with a brainstorm.**
|
||||
|
||||
---
|
||||
|
||||
## 1. The target architecture (retail-faithful — the ONE model)
|
||||
|
||||
From the retail decomp (handoff §5; doc A). The single inversion that fixes everything:
|
||||
|
||||
> **When the viewer is in an EnvCell, run ONLY `DrawInside` — the one PView portal flood.
|
||||
> Do NOT draw the outdoor world and then gate it. Visibility IS the cull: only the visible
|
||||
> cells and their per-cell objects render; the landscape (terrain/sky/rain) is pulled in
|
||||
> ONLY through clipped exit portals, followed by a conditional depth-only clear.**
|
||||
|
||||
```
|
||||
RenderWorld(viewer):
|
||||
if viewer.cell is outdoor landcell:
|
||||
DrawOutside() # LScape: terrain + scenery + sky + weather (today's outdoor path)
|
||||
# buildings seen from outside render their interiors via DrawPortal (R5, outside-looking-in)
|
||||
else: # viewer in an EnvCell
|
||||
DrawInside(viewer.cell): # ONE flood — nothing else
|
||||
frame = ConstructView(cell) # PView BFS → cell_draw_list + per-cell clip + outside_view
|
||||
if frame.outside_view nonEmpty:
|
||||
DrawLScapeClippedTo(frame.outside_view) # terrain/sky/rain through the doorway
|
||||
ConditionalDepthOnlyClear(frame.outside_view) # Z only — never color → no blue hole
|
||||
for cell in frame.cell_draw_list (closest-first):
|
||||
DrawCellShell(cell, clip=frame.clip[cell]) # closed geometry: floor+walls+ceiling
|
||||
DrawCellObjects(cell, clip=frame.clip[cell]) # ONLY this cell's objects (statics/entities)
|
||||
DrawCellParticles(cell, clip=frame.clip[cell]) # ONLY this cell's particles
|
||||
```
|
||||
|
||||
**Components to KEEP (research says these are correct):** `PortalVisibilityBuilder` (the BFS),
|
||||
`ClipFrame`/`ClipFrameAssembler`/`ClipPlaneSet`/`PortalView` (the clip machinery), `EnvCellRenderer`
|
||||
mesh/geometry path, `TerrainModernRenderer`, the WB mesh pipeline, the membership fix (`59f3a13`),
|
||||
the Stage-4 sky NDC-clip + doorway Z-clear, the diagnostic probes.
|
||||
|
||||
**The work is RESTRUCTURING THE ORCHESTRATION + the entity/particle draw, not a from-scratch
|
||||
rewrite.** No stencil two-pipe, no `isInside` gate, no AABB grace-frame, no WB `RenderInsideOut`
|
||||
(handoff §6/§9).
|
||||
|
||||
---
|
||||
|
||||
## 2. The phases (each is visually-verifiable; retail-anchored; no bandaids)
|
||||
|
||||
> Sizing note: this is a multi-session arc (M1.5 "indoor world feels right"). Each phase ends
|
||||
> at a **user visual gate** (the only acceptance that counts for a render seal — handoff §9
|
||||
> lesson). Do NOT batch phases past a gate. The phases are ordered so each is independently
|
||||
> testable and the bleed is killed early.
|
||||
|
||||
### Phase R0 — Brainstorm + lock the design (FIRST, with the user)
|
||||
- `superpowers:brainstorming` the §1 architecture with the user. Confirm the single-flood model,
|
||||
the per-cell object draw, the keep/redesign split. Resolve the open questions in §3.
|
||||
- Write the detailed per-phase spec (`docs/superpowers/specs/2026-06-…-render-redesign-design.md`).
|
||||
- **No code.** Gate: design approved.
|
||||
|
||||
### Phase R1 — One visibility authority + kill the outdoor-scenery bleed
|
||||
**Retail anchor:** CL-B (render-root unification), CL-F (visibility is the cull). Fact 8 (§5.1).
|
||||
- Make `PortalVisibilityBuilder.Build`'s `OrderedVisibleCells` / `CellViews` the **single**
|
||||
visibility answer. Route the **entity** dispatch off it (the same `pvFrame` the shells/terrain
|
||||
use) instead of the parallel `CellVisibility.ComputeVisibilityFromRoot` BFS.
|
||||
- **Delete the `ParentCellId==null → return true` bypass** at `WbDrawDispatcher.cs:1756`. Outdoor
|
||||
scenery (houses/trees/stabs) is gated to `OutdoorVisible` (drawn only when an exit portal makes
|
||||
the outdoors visible, and then clipped to it). This is the unification, not a special-case patch.
|
||||
- Decommission the duplicate `CellVisibility` entity path (or make it return the identical set).
|
||||
- **Gate (visual):** standing in the cellar, **no houses/trees/outdoor stabs are visible.** The
|
||||
`[shell]`/`[vis]`/entity probes confirm one visibility set drives all geometry.
|
||||
|
||||
### Phase R2 — The binary render decision: indoor = `DrawInside` only
|
||||
**Retail anchor:** CL-B1 (single decision), `RenderNormalMode @ 0x453aa0`, fact 2 (§5.1).
|
||||
- Restructure `GameWindow.OnRender` so that when `CellGraph.CurrCell` is an EnvCell, the **full
|
||||
outdoor draw is NOT issued** — only the indoor flood runs. The outdoor terrain/scenery/sky are
|
||||
reachable only via the exit-portal path (R3). When outdoors, the existing outdoor path runs.
|
||||
- Remove the "draw outdoor world, then draw cell shells on top, gated" structure. The render root
|
||||
is the physics `CurrCell` (already wired, Stage 3); the *consequence* (only-DrawInside-when-inside)
|
||||
is the new part.
|
||||
- **Gate (visual):** indoors you no longer see the full-screen outdoor "world background"; you see
|
||||
the interior (walls/floor/ceiling) and, through the door, the outside (still rough until R3).
|
||||
|
||||
### Phase R3 — The seal mechanics (`DrawCells` faithful port) — THE seal
|
||||
**Retail anchor:** CL-D, `PView::DrawCells @ 0x5a4840` (the three-loop seal sequence), fact 5–7.
|
||||
- In the indoor flood, when `outside_view` is non-empty: draw `LScape` (terrain/sky/rain) **clipped
|
||||
to the doorway** (reuse the Stage-4 sky NDC-clip + the terrain OutsideView clip) → **conditional
|
||||
depth-only clear** (the Stage-4 Z-clear) scissored to the doorway → then the cells.
|
||||
- Verify cell shells are **closed** (floor + walls + ceiling from `drawing_bsp`); the `[shell]`
|
||||
evidence shows geometry is present — confirm the floor face is in the mesh and faces correctly.
|
||||
- Resolve the terrain *model*: terrain is drawn ONLY through the exit-portal clip, never as a floor
|
||||
under the interior. Remove the `TerrainClipMode.Skip`-as-floor-removal confusion.
|
||||
- Self-contained GL state per draw (memory `render-self-contained-gl-state`).
|
||||
- **Gate (visual):** the cottage interior is **fully sealed** — opaque walls, solid floor, ceiling,
|
||||
**sky + rain visible through the door only** (no blue hole, no full-screen), no terrain under the
|
||||
floor, no grey-floor. The cellar is sealed.
|
||||
|
||||
### Phase R4 — Per-cell object + particle clipping (no bleed)
|
||||
**Retail anchor:** CL-F, fact 8; `find_visible_child_cell @ 0x52dc50`; #104.
|
||||
- Make the object draw **per visible cell** (iterate `cell_draw_list`, draw each cell's objects
|
||||
clipped to that cell's region) instead of one global entity pass. Entities straddling a portal
|
||||
are portal-clipped.
|
||||
- Give particles a cell (`OwnerCellId` from the owning entity's `ParentCellId`) and clip them to
|
||||
the visible set (#104).
|
||||
- **Gate (visual):** no NPC / door / smoke bleed through walls; an NPC just outside the door is
|
||||
visible through the doorway but not through the wall.
|
||||
|
||||
### Phase R5 — Outside-looking-in (U.5)
|
||||
**Retail anchor:** CL-E, `DrawPortal @ 0x5a5ab0`, `ConstructView(CBldPortal) @ 0x5a59a0`, fact 9.
|
||||
- When outdoors and a building's door/window is in view, render the building's **interior through
|
||||
that portal's clip** (the mirror of DrawInside, entered from the outdoor cell). Same machinery.
|
||||
- **Gate (visual):** looking through the cottage door/window from outside shows the **sealed
|
||||
interior**, not transparent walls.
|
||||
|
||||
### Phase R6 — Dungeons
|
||||
**Retail anchor:** fact 10 (emergent), the `update_count` watermark (fact 12 — #95), CL-C5.
|
||||
- Validate the all-EnvCell / `seen_outside==0` / no-exit-portal path on a real dungeon: no terrain,
|
||||
no sky, sealed cells, BFS convergence (the watermark bounds the reprocesses — confirm #95 closed).
|
||||
- **Gate (visual):** a real dungeon is sealed, no terrain/sky, no FPS collapse from the BFS.
|
||||
|
||||
### Phase R7 — Polish + conformance
|
||||
- Resolve the `CullMode.Landblock→None` double-sided stopgap (the actual winding).
|
||||
- Textures: confirm no missing textures anywhere (the `[shell]` zh=0 evidence says good; verify
|
||||
across dungeons).
|
||||
- Conformance tests (headless): the PView frame product, the seal asserts (handoff §8 apparatus).
|
||||
- Update roadmap; flip the M1.5 milestone; memory notes.
|
||||
- **Final gate (visual):** cottage + dungeon + outside-looking-in, all sealed and seamless.
|
||||
|
||||
---
|
||||
|
||||
## 3. Open questions for the R0 brainstorm
|
||||
- **Outdoor scenery while a door is open:** when indoors looking out, the visible outdoor scenery
|
||||
(the cottage across the street) must draw — but clipped to the doorway. Does that come for free
|
||||
from "LScape through the exit portal" (terrain + scenery both), or does scenery need its own
|
||||
exit-portal-clipped pass? (Retail draws LScape — which includes scenery — through the clip.)
|
||||
- **Building shells from outdoors:** when outdoors, the cottage's *exterior* shell must draw (it's a
|
||||
building). Is that an EnvCell shell drawn from the outdoor root, or part of the outdoor scenery?
|
||||
Reconcile with R5 (outside-looking-in) so the exterior + the door-interior compose correctly.
|
||||
- **The `EnvCellRenderer` filter vs per-cell-clip:** today shells use one `Render(filter)` with
|
||||
per-cell clip slots. R4 wants per-cell object draw. Confirm the EnvCellRenderer + WbDrawDispatcher
|
||||
can both be driven per-cell from `cell_draw_list` without a global pass.
|
||||
- **Two cameras (eye vs player cell):** the U.4c flap fix roots visibility at the player cell while
|
||||
projecting from the eye. Confirm the redesign preserves that (the eye can be outside the player
|
||||
cell in 3rd person) — `find_visible_child_cell` for the camera child.
|
||||
- **Scope of the entity-draw restructure:** is per-cell object draw a refactor of `WbDrawDispatcher`
|
||||
or a new dispatch path? (Per CLAUDE.md: don't break the working mesh pipeline; restructure the
|
||||
orchestration around it.)
|
||||
|
||||
## 4. Risks
|
||||
- **Big restructure of the render loop** — do it behind the visual gates, one phase at a time;
|
||||
keep the outdoor path working throughout (it's the 99% case).
|
||||
- **Per-cell object draw vs MDI batching** — the modern dispatcher batches across entities; a naive
|
||||
per-cell loop could regress perf. Design the per-cell clip to preserve batching (the clip-slot SSBO
|
||||
already supports per-instance clip; the cull is the membership, not a per-cell draw call necessarily).
|
||||
- **Don't reintroduce the abandoned approaches** (handoff §9): no stencil two-pipe, no isInside gate,
|
||||
no AABB grace-frame.
|
||||
- **Get the user's eyes early** — every phase ends at a visual gate; never declare a seal off tests.
|
||||
|
||||
## 5. The no-shortcuts rules (enforce on every task)
|
||||
1. If a task tempts a fast-but-wrong path, take the retail-faithful path; note the tradeoff in the commit.
|
||||
2. No suppression flags, grace periods, or "if (problem) return early" guards at a symptom site.
|
||||
3. Every AC-specific behavior cites a retail decomp anchor (address + pseudo-C line).
|
||||
4. Mid-session research over guessing — if the retail behavior is unclear, read the decomp / attach cdb.
|
||||
5. Each phase ends GREEN (build + tests) AND at a user visual gate. The seal is verified on screen.
|
||||
Loading…
Add table
Add a link
Reference in a new issue