# The holistic building-render port plan (Phase B) — one drawing discipline **EXECUTION STATUS (2026-06-11, post-BR-7): BR-2…BR-7 are ALL CODE-COMPLETE on the branch — the render arc as the fused tasks T1–T4 (T1 `579c8b0` frame order; T2 `cf8a2c3`/`529dfcf`/`88f3ce1` flood fidelity, two retail constants refuted by the conformance gate and kept at documented tolerances; T3 `a6aec8c` viewconeCheck; T4 `4a307d3` one-gate deletions), and BR-7 (T6, collision A6.P4) as `6ec4cde` (signed OtherPortalId gate) + `abf36e2` (BuildShadowCellSet flood) + `dbfbf85` (per-cell architecture: flood registration, building channel, per-cell query, b3ce505 DELETED — closes #99) + `ca4b482` (straddle-only outside-add, A6.P5 widening + #90 stickiness removed). Of the 4 #99-era Core reds, 3 flipped green as designed (door apparatus + tick-13558 + tick-22760's blocking invariant); the 4th (BSPStepUp D4) + 22760's lateral-slide delta proved to be a SEPARATE pre-existing slide-response family — filed #116, D4 skipped with the reference (probes show the cell-set layer innocent). Suites: Core 1416/0/2skip, App 225, UI 420, Net 294. **T5 EXECUTED 2026-06-11 (the single comprehensive user gate) — PARTIAL PASS.** ✅ Confirmed by the user: doors block both ways incl. off-center (#99 visual), cellar descent/ascent clean + #108 grass-sweep GONE, inn 2nd floor clean (#97 closed), interiors stable through doorways incl. edge-on, #109 far-door oscillation GONE, formerly-popping stairs now STABLE at all ranges (the distance-pop class is dead). ❌ Remaining — four filed render artifacts: **#117** aperture-shaped see-through (doors/interiors through terrain hills + through nearer buildings — the punch erases occluder depth), **#118** character clipped+vanishes for a moment on house exit, **#119** old-tower stairs partially invisible + extraneous barrel (pre-existing; `[up-null]` permanently-invisible mesh lead in the T5 log), **#120** `[pv-ERROR]` in-place-propagation convergence tripwire at depth 128 on the cottage cells (self-detected T2 invariant break — investigate first). Rain-indoors not verifiable (clear weather). NEXT: fix #120 → #117 → #118 → #119 at the mechanism level, then a focused re-gate on just those spots.** **Status: APPROVED + AMENDED (2026-06-11). EXECUTION DIRECTIVE CHANGED BY THE USER: "I don't care if it is non-playable… I want everything ported, then we test."** The per-phase playability constraint and per-phase user visual gates are DROPPED. BR-2 through BR-6 execute as ONE continuous port (the fused render discipline), with build + unit/conformance tests green at every commit (engineering hygiene, not gates), and **ONE comprehensive visual test pass at the end**. Rationale: the first BR-2 attempt failed precisely because the phase slicing cut retail's frame order in half (the punch shipped without entities-drawn-last and erased characters in apertures — reverted `88be519`); the installment-must-be-a-complete-retail-behavior rule replaces the playability rule. BR-7 (collision) runs as an independent track; BR-8b (lighting) still wants the verification resume first. Companion to the Phase A comparison: [`docs/research/2026-06-11-building-render-acdream-vs-retail-comparison.md`](../research/2026-06-11-building-render-acdream-vs-retail-comparison.md) (evidence appendices in [`docs/research/2026-06-11-holistic-map/`](../research/2026-06-11-holistic-map/)). Mandate: *"one solution that works every time I walk to a new landblock and walk into a dungeon"* (2026-06-11). --- ## 0. The invariant (what "one drawing discipline" means, retail-cited) Every phase below moves us toward — and no phase may move us away from — this frame shape, which is retail's (Ghidra-cited in the comparison doc §2): 1. **Geometry is flattened at load** into surface-batched meshes (we already do this). World geometry is **never geometrically clipped at draw time**. 2. **Untextured (solid) surface batches never draw** on building shells and cell meshes (`skipNoTexture`); they do draw on plain objects. 3. **Portal polygons are not wall geometry.** They exist per frame only as (a) flood admission tests (`ConstructView`: eye-side ε=0.0002 → clip vs current view → cell loaded) and (b) **invisible depth writes** — far-Z *punch* before an interior draws through an aperture; true-depth *seal* on portals to the outside after the landscape draws. 4. **Cells draw whole, far→near, once** (frame stamp); the z-buffer plus the punches/seals produce pixel-exact apertures. 5. **Objects and particles are culled per portal view** (sphere vs the view's edge planes — `viewconeCheck`), never clipped, never scissored. 6. **One visibility computation feeds everything** — the PView flood. No second BFS, no parallel gate, no distance constants in admission. ## 1. Keep-list (the code worth saving — explicitly not touched/rewritten) - **Mesh pipeline**: `ObjectMeshManager` flatten + global VAO + bindless MDI (`WbDrawDispatcher`) — retail-faithful architecture, confirmed by the `ConstructMesh`/`RemoveNonPortalNodes` finding. - **The flood port**: `PortalVisibilityBuilder` (homogeneous clipper, side tests, reciprocal clip, exact-match skip) + conformance gates (`CornerFloodReplayTests`, `Issue113MeetingHallFloodTests`) — BR-4 adjusts constants/heuristics, it does not rewrite the clipper. - **Membership** (P1 9/9 golden) + **straddle gate** (`414c3de`) + **camera collision sweep** (verbatim `update_viewer`) + **znear=0.1** + **#105 texture flush** + **two-tier streaming** + spawn/snap validation (#107/#111/#112). - Diagnostics/probes and the dat dump harness. The M0 freeze list is superseded *for rendering only* by the 2026-06-11 mandate; nothing outside building/interior render + interior collision is in scope. ## 2. Phases Ordering rule: each phase lands green (build + full suites + named visual gate) and the client stays playable after every phase. Conformance pins come from the dat harness + the flood replay harnesses; retail constants are cited inline when ported. ### BR-1 — The surface gate — ✅ RESOLVED AS ALREADY-EQUIVALENT (2026-06-11, execution day 1) **Premise falsified before implementation (the BR-1 pre-check, `ReplicateProductionEmission_OnPortalFills`):** acdream **already suppresses every portal fill** — all four extraction paths skip `Stippling.NoPos` positive sides (`ObjectMeshManager.PrepareGfxObjMeshData:1046`, `PrepareCellStructMeshData:1394`, `CellMesh.Build:44`, `GfxObjMesh.Build:71`), and the Holtburg fills have no negative surface. The planned "draw-time surface gate" has nothing to gate. **What shipped instead — the equivalence pin** (`StipplingSurfaceEquivalenceTests`): 2,607 polys across 13 building models + 13 environments, **zero violations both directions** — `NoPos ⇔ untextured surface`. Our build-time skip is therefore *proven equivalent* to retail's draw-time `skipNoTexture` rule on this content; the `portal-poly-suppression-criterion` divergence closes as equivalent-with-proof. The pin fails loudly if future content breaks the invariant (the cue to implement the draw-time gate then). **Consequences (the honest part):** - The **#113 phantom residual is NOT GfxObj fills** — it cannot be, they never reach a vertex buffer. The "root cause #2" attribution from the e46d3d9 session is corrected; the e46d3d9 user-gate observations (filter removed phantom/doors) were confounded — the filter was a provable mesh no-op on both shells and door parts. - The phantom's plausible true sites are cell-side: flood-admitted stair CELLS drawn with a pass-all slice when the assembler hands them no slot (`RetailPViewRenderer.cs:71` draws ALL visible cells; `NoClipSlice` default), and/or stair-cell STATICS drawn unclipped + un-viewcone'd by design (`object-lists-skip-portal-view-gate`, confirmed). **BR-2's first task is a 10-minute probe at the hall bisect spot pinning which** — the closure moves to BR-2/BR-3 (shells) and BR-5 (statics). - **Closes:** the `portal-poly-suppression-criterion` divergence (as proven-equivalent); #113's closure moves to BR-2/BR-3/BR-5. - **Shipped:** the pre-check + equivalence pin tests; no production code (none needed). ### BR-2 — Aperture depth machinery (punch / seal / clear) **What:** port the invisible depth writes: (a) wire `DrawExitPortalMasks` (today an unwired no-op) as a depth-only draw of each outside-leading portal polygon, software-clipped to its view slice (the `ClipToRegion` math already exists), at the portal's **true projected depth** (retail `maxZ2`) — after the landscape slices, indoor roots; (b) add the **far-Z punch** (retail `maxZ1`) on building-aperture flood success on the outdoor + look-in paths, before the interior cells draw; (c) replace the per-slice scissored `ClearDepthSlice` AABB clear with retail's discipline: one full depth clear between the outside stage and the interior stage, gated on whether any seal was drawn (`portalsDrawnCount`); (d) on the look-in path, draw interior-through-aperture **before** the shell mesh (retail `DrawBuilding` order) so the shell's depth closes everything outside the punch. - **First task (from BR-1's falsification):** the 10-minute probe at the hall bisect spot — when the phantom is visible, log per stair cell (0x100..0x106) whether it drew with a real clip slot or the pass-all `NoClipSlice`, and whether its statics drew — pinning the phantom's true draw site (shells → fixed here/BR-3; statics → BR-5). - **Closes:** #108 (outdoor terrain sweeping across the upstairs door — the missing true-depth seal is the confirmed `missing-portal-depth-fence` divergence); the outdoor-root depth-discipline gap; part of #109; the #113 phantom residual if the probe pins it on pass-all shell slices. - **Acceptance:** cellar↔main-floor walk shows no grass sweep (user gate); phantom-spot check at the hall (user gate, replaces the old BR-1 acceptance); new harness fact: seal depth = portal plane depth inside the clipped aperture polygon (GL readback test or probe assertion); suites green. - **Size:** ~3 commits (~80 lines of GL + clipper reuse per the area estimate, plus the clear re-shape and order swap). ### BR-3 — Retire the geometric shell chop; whole-shell far→near draws **What:** remove `gl_ClipDistance` as the *enforcement* mechanism for cell shells (both the outdoor-scoped enable from `927fd8f`/`9ce335e` and the never-enabled indoor half — i.e. #114 closes by *deleting* the chop, not perfecting it). Shells draw whole, far→near per `OrderedVisibleCells` (already the order), drawn-once. Clip regions remain for admission, punch shapes, and (BR-5) object culling. The landscape-through-aperture pass keeps its per-slice plane clip for now (open Q: `LScape::draw` internals) — revisit after BR-2 proves the seal protects terrain. - **Closes:** #114 (chopped stairs / vanished candle area / barrel-through- wall were artifacts of clipping geometry retail never clips) — jointly with BR-2. Removes the 8-plane budget + slot-0 PASS-ALL as load-bearing for shells. - **Acceptance:** meeting-hall interior + multi-room cottages render unchopped from indoor and outdoor eyes (user gate vs the #114 screenshot set); phantom stays gone (BR-1 unaffected); flood replay gates green. - **Order constraint:** must not land before BR-2 (the depth fence replaces the chop's job at apertures). - **Size:** ~2 commits (mostly deletions + the draw-order assertion). ### BR-4 — Shell-draw-driven floods + flood fidelity **What:** make the building's own draw the flood trigger, retail-shaped: pair the shell GfxObj's `PortalRef.PortalIndex` with its `BuildInfo.Portals` entry (the `outdoor_portal_list` correspondence) and, when a shell survives the cull for a view slice, run each aperture through the ported `ConstructView(CBldPortal)` chain under that slice. Then remove the non-retail machinery the trigger replaces: the 48 m seed constant, the Chebyshev≤1 candidate gather, the `EyeInsidePortalOpening` full-view rescue; adopt retail constants (ε=0.0002; in-plane rejects for building portals); add the 1-px screen-space vertex dedup to `ClipToRegion` output (retail's fixpoint floor) and switch late view growth to in-place propagation (`AddToCell`/`FixCellList`/`AdjustCellView` shape), removing the `MaxReprocessPerCell=16` cap; make `MergeBuildingFrame` union views instead of first-wins and retire single-slot consumers (`CellIdToSlot[0]`); bind nested floods to their originating slot (the `building_view` latch). - **Closes:** #109 (binary 48 m pop + first-wins view loss + missing punch are its named mechanisms); the flood-stability family (edge-on doorway residuals); enables interior-visible-through-window parity. - **Acceptance:** flood replay harnesses extended: (a) building flood triggers with no distance constant — admission matches the clip-survival rule across an eye sweep; (b) two-aperture cell holds two views; (c) growth propagates without the cap on a portal-dense fixture; #109 spot user gate; suites green. - **Size:** ~4–5 commits (trigger + pairing; constants/dedup; growth in-place; merge union; deletions). ### BR-5 — Per-view object + particle culling (viewconeCheck) **What:** port `Render::viewconeCheck`: per view slice, lift the per-edge eye planes (each NDC edge + the eye defines a plane — the `view_vertex.plane` analog) and sphere-test every entity and emitter against the slice before draw; route particles through the same gate and the same clip/punch discipline (delete the `BeginDoorwayScissor` AABB path); fix the outdoor-root unattached-emitter drop; gate the weather pass on `is_player_outside` (player cell, not viewer root). - **Closes:** particles-through-walls (candle flames in other buildings); rain-indoors-through-doorways; the neighbour-room object over-inclusion half of the old #114 report. - **Acceptance:** flame-through-wall spot at Holtburg (user gate); a conformance fact pinning sphere-vs-slice culling on a fixture; no regression in entity draw counts outdoors (perf probe within noise). - **Size:** ~3 commits. ### BR-6 — One gate: consolidate visibility + delete legacy paths **What:** make the PView flood the only visibility computation: remove the per-frame ACME BFS (`CellVisibility.ComputeVisibilityFromRoot`) by folding its remaining consumers (lighting indoor flag etc.) onto PView/ membership outputs; delete or quarantine the confirmed legacy remnants (`InteriorRenderer`, `IndoorDrawPlan` consumers of the old path, the `clipRoot==null` second render branch, the dormant exit-mask wiring once BR-2 rewires it, duplicate frustum implementation); one frustum, one center/radius window. - **Closes:** the `dual-live-visibility-computations` inconsistency class (the one-gate rule, `feedback_render_one_gate`); removes the surface area where two gates disagree (future flap-class bugs). - **Acceptance:** gate-audit re-run shows ONE visibility computation per frame; every deletion verified by a launch + the visual gate set; suites green. - **Size:** ~3 commits, mostly deletions (each independently revertable). ### BR-7 — Interior collision: per-cell shadow lists (A6.P4, verified) — ✅ CODE-COMPLETE 2026-06-11 (`6ec4cde`+`abf36e2`+`dbfbf85`+`ca4b482`; visual confirmation rides T5) **What:** ship the A6.P4 architecture with the investigation's corrections: registration builds the cell set by sphere-overlap portal flood (not an XY grid; crosses landblocks), per-cell `shadow_object_list` iteration on the query side (`CheckOtherCells` runs env AND shadow objects per other cell), buildings dispatch through a per-LandCell building channel (`CSortCell.building` shape), `OtherPortalId` widened to signed with the `>= 0` gate (sign-extension Ghidra-proven). Then remove the `b3ce505` stopgap, the A6.P5 `hasExitPortal` widening, and the #90 stickiness workaround. - **Closes:** #99 (doors block from both sides), very likely #97; retires three flagged workarounds. - **Acceptance:** A6.P4 spec acceptance (doors block both ways at Holtburg inn + cottages; #98 cellar ascent stays fixed — `CellarUp` harness green); capture/replay comparison on the door apparatus; suites green. - **Size:** the A6.P4 spec's estimate stands (~5 commits); independent of BR-2..BR-5 — may run in parallel with them. ### BR-8 — Feel tier: camera, lighting, LOD (post-discipline polish) - **BR-8a Camera (#115, verified root cause; can land any time):** damp the sought eye FROM the published collided viewer each frame (retail `PlayerPhysicsUpdatedCallback` shape) and apply the computed player fade over the 0.45→0.20 m band. Acceptance: cramped-interior turn feel (user gate). ~1–2 commits. - **BR-8b Lighting (pending verifier confirmation):** interior sun mask (never sun-light interiors), static cell-light burn-in (all lights, not 8-nearest), viewer light, per-object light selection, surface luminosity/diffuse. Acceptance: side-by-side interior look vs retail screenshots. Phase-sized; spec before code. - **BR-8c LOD + dedup (low):** per-part degrade selection beyond humanoids; frame-stamp draw dedup. Optional per-cell interleave for draw-order parity is explicitly NOT planned (z-buffer makes it unnecessary; revisit only on evidence). - **Picking refinements** (all-low area): defer; file as issues when the port changes what is clickable. ## 3. What this plan deliberately does NOT do - No per-frame BSP traversal of ordinary geometry (retail doesn't either). - No rewrite of the mesh/MDI pipeline, the flood clipper, membership, or streaming (keep-list). - No `leaf_cells`/`CPartCell` port (path dormant in the 2013 binary — needs runtime proof first). - No transparency-sorting work yet — that area's map is still re-running; fold its findings in as a BR-9 candidate after review (the AlphaList deferral machinery is already decompiled in the Area 1 file). ## 4. Explicitly out of scope — tracked follow-ups (NOT covered by BR-1…BR-8) Completing BR-1 through BR-8 lands the building/interior **drawing discipline** and the collision rearchitecture. It does **not** cover the items below. They are named here so the boundary of what the campaign delivers is written down, not assumed — each becomes its own roadmap item or issue, none blocks BR-1…BR-8. - **FU-1 — Transparency / draw-sorting (→ BR-9 candidate).** Retail's `DrawSortCell` + AlphaList deferral (decompiled in `2026-06-11-holistic-map/wf1-gfxobj-draw.md`) governs water surfaces, translucent windows, and alpha-blend ordering. The area's *map never completed* (agent hit the token limit), so there are no divergences yet — scope it before promoting to BR-9. **Severity: medium; user-visible as wrong window/water compositing.** - **FU-2 — Dungeon visibility scaling (#95).** The 8 phases are Holtburg-building-shaped. Dungeons share the EnvCell/portal discipline so they benefit *automatically*, and BR-4's tighter flood admission (no-distance-constant + screen-clip rejection + cell-loaded gate) **plausibly** shrinks #95's 135-cells/frame blowup — but #95 is a disconnected-landblock *seeding* problem that BR-4 is not guaranteed to fix. **Re-measure #95 after BR-4/BR-6 land; if still blown, it needs its own phase.** Do not assume the building port closes it. - **FU-3 — Distance LOD / degrades (= BR-8c, optional).** Per-part degrade selection beyond humanoids; far models stay base-detail until picked up. - **FU-4 — Picking refinements** (4 low-severity divergences, `wf2-picking-selection.md`). Defer; file as issues if/when the port changes what is clickable (e.g. building shells, baked fills). - **FU-5 — The ~30 open questions** live in the comparison doc §6 (`2026-06-11-building-render-acdream-vs-retail-comparison.md`). The load-bearing ones are referenced inline in the phases that consume them (e.g. `LScape::draw` clip behavior for BR-2/BR-3, the near-W constant, `DrawPortal` mode-3 seal-on-failure for unstreamed interiors); the rest are pinned during implementation, not before. - **FU-6 — Verification top-up.** ~36/76 divergences remain UNVERIFIED (the overnight resume was stopped to preserve budget; both runs are resumable by ID — see comparison §7). Run a cheap resume before **BR-8b lighting** scoping (the one phase that leans on unverified rows) and before promoting FU-1 to BR-9. ## 5. Sequencing summary ``` BR-1 (surface gate) — ✅ RESOLVED as already-equivalent (pin shipped, no production code; #113 closure moved to BR-2/3/5 — see BR-1 section) BR-2 (depth punch/seal) — FIRST implementation phase; opens with the phantom-site probe; enables BR-3 BR-3 (delete shell chop) — closes #114 with BR-2 BR-4 (draw-driven floods) — closes #109; flood fidelity BR-5 (viewconeCheck) — particles/objects through the same gate; closes the phantom if it is statics-side BR-6 (one gate + deletions) — consolidation after the discipline is in BR-7 (collision A6.P4) — independent track; may interleave with BR-2..5 BR-8 (camera/lighting/LOD) — feel tier; BR-8a may land early ``` Every phase: `dotnet build` + full suites green, conformance pins added with retail citations, named user visual gate, roadmap/ISSUES updated in the same session, and the render digest updated when a phase closes one of the named bugs. ## 6. Approval asks 1. Approve the plan shape + ordering (BR-1 → BR-8, BR-7 parallel-capable). 2. Approve the deletions implied by BR-3/BR-6 (shell-chop enforcement, ACME BFS visibility, legacy render branches) — all on the strength of the cited evidence that retail has no counterpart. 3. Note the verification caveat: ~36/76 divergences still carry UNVERIFIED (resume in flight); BR-1..BR-3's load-bearing claims are either verified or dat-confirmed locally, so approval need not wait on the rest.