Resolves the plan §3 open questions with the user this session: - object/entity/particle draw = LITERAL PER-CELL LOOP (retail DrawCells), not a global MDI batch with per-instance clip. Fidelity > perf > blast-radius. - sequencing = HOLISTIC: build the per-cell DrawInside directly; no intermediate global-pass gate-fix. First visual gate = sealed cottage interior, no bleed. - terrain in the seal = FAITHFUL: drawn only through the exit-portal clip, never as a floor under the interior. Inventory's 'relax Skip' suggestion REJECTED as a non-retail workaround; grey-floor = a sealing bug (verify cell mesh in R1). - WB mesh pipeline KEPT (per-cell draws from the global buffers, batched within a cell); two-camera invariant preserved (eye projects, player cell roots visibility). Phases (holistic): R1 unified per-cell DrawInside (the core) -> R2 outside-looking-in (DrawPortal) -> R3 dungeons -> R4 polish+cleanup. Each ends GREEN + a user visual gate. Retail anchors cited throughout (RenderNormalMode 0x453aa0, DrawCells 0x5a4840, etc). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
12 KiB
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:brainstormingthe §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'sOrderedVisibleCells/CellViewsthe single visibility answer. Route the entity dispatch off it (the samepvFramethe shells/terrain use) instead of the parallelCellVisibility.ComputeVisibilityFromRootBFS. - Delete the
ParentCellId==null → return truebypass atWbDrawDispatcher.cs:1756. Outdoor scenery (houses/trees/stabs) is gated toOutdoorVisible(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
CellVisibilityentity 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.OnRenderso that whenCellGraph.CurrCellis 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_viewis non-empty: drawLScape(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 (
OwnerCellIdfrom the owning entity'sParentCellId) 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→Nonedouble-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
RESOLVED 2026-06-02 in
docs/superpowers/specs/2026-06-02-render-pipeline-redesign-design.md§1. Outcome: object/entity/particle draw = literal per-cell loop (retailDrawCells), not global MDI; sequencing = holistic (build the per-cellDrawInsidedirectly, no intermediate global-pass gate-fix); terrain in the seal = faithful (only through the exit-portal clip; the "relax Skip" suggestion is rejected as a workaround); WB mesh pipeline kept (per-cell draws from the global buffers); two-camera invariant preserved (eye projects, player cell roots visibility). The design spec is the locked authority.
- 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
EnvCellRendererfilter vs per-cell-clip: today shells use oneRender(filter)with per-cell clip slots. R4 wants per-cell object draw. Confirm the EnvCellRenderer + WbDrawDispatcher can both be driven per-cell fromcell_draw_listwithout 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_cellfor the camera child. - Scope of the entity-draw restructure: is per-cell object draw a refactor of
WbDrawDispatcheror 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)
- If a task tempts a fast-but-wrong path, take the retail-faithful path; note the tradeoff in the commit.
- No suppression flags, grace periods, or "if (problem) return early" guards at a symptom site.
- Every AC-specific behavior cites a retail decomp anchor (address + pseudo-C line).
- Mid-session research over guessing — if the retail behavior is unclear, read the decomp / attach cdb.
- Each phase ends GREEN (build + tests) AND at a user visual gate. The seal is verified on screen.