Deliverable 1: docs/research/2026-06-11-building-render-acdream-vs-retail- comparison.md - the acdream-vs-retail architecture comparison synthesized from two ultracode mapping fan-outs (11/12 areas, ~90 agents, every retail claim Ghidra/pc-cited, every acdream claim file:line, 40/76 divergences adversarially verified so far; raw per-area evidence committed under docs/research/2026-06-11-holistic-map/). Headline findings: (1) retail flattens GfxObjs/cells at load exactly like us (ConstructMesh + RemoveNonPortalNodes) - the MDI pipeline survives; (2) the phantom/door mechanism is the skipNoTexture draw-time surface gate (dat-confirmed); (3) retail never geometrically clips world geometry - aperture exactness is a DEPTH discipline (punch maxZ1 / seal maxZ2 / gated clear + far-to-near whole-mesh draws) - reframes #114; (4) flood admission is already faithful, the trigger/depth/multi-view/cone-culling layers are missing; (5) #115 root cause verified (boom damping severed from the published collided viewer); collision A6.P4 design verified with corrections (signed other_portal_id >= 0 gate). Deliverable 2: docs/plans/2026-06-11-building-render-port-plan.md - the phased port plan (BR-1 surface gate, BR-2 depth punch/seal, BR-3 delete the shell chop, BR-4 draw-driven floods, BR-5 viewconeCheck, BR-6 one gate, BR-7 collision A6.P4, BR-8 camera/lighting/LOD) with per-phase acceptance criteria, bug closures, keep-list, and a playable-after-every- phase migration order. AWAITING USER APPROVAL - no implementation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
15 KiB
The holistic building-render port plan (Phase B) — one drawing discipline
Status: AWAITING USER APPROVAL — no implementation until approved.
Companion to the Phase A comparison:
docs/research/2026-06-11-building-render-acdream-vs-retail-comparison.md
(evidence appendices in
docs/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):
- Geometry is flattened at load into surface-batched meshes (we already do this). World geometry is never geometrically clipped at draw time.
- Untextured (solid) surface batches never draw on building shells and
cell meshes (
skipNoTexture); they do draw on plain objects. - 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. - Cells draw whole, far→near, once (frame stamp); the z-buffer plus the punches/seals produce pixel-exact apertures.
- Objects and particles are culled per portal view (sphere vs the view's
edge planes —
viewconeCheck), never clipped, never scissored. - 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:
ObjectMeshManagerflatten + global VAO + bindless MDI (WbDrawDispatcher) — retail-faithful architecture, confirmed by theConstructMesh/RemoveNonPortalNodesfinding. - 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 (verbatimupdate_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 draw-time surface gate (kills the phantom class)
What: classify every mesh batch at decode by surface texturedness
(Surface.Type & (Base1Image|Base1ClipMap)); at draw, skip untextured
batches for building-shell entities and cell meshes only (plain objects
keep drawing them — retail's bypass). Align the cell-side build-time
NoPos/NoNeg drop with this rule: run a dat-wide sweep (all CellStructs +
all building models in the populated landblocks) proving
portal-fill ⇔ untextured; keep the cheaper build-time drop only where the
sweep proves equivalence, otherwise move to the draw-time gate. GfxObj-side:
fills stay in the mesh but never draw (matches retail exactly).
- Closes: #113 phantom staircase class (hall ramp, cottage "flying stairs", every building's baked fills) — without touching doors (entities).
- Acceptance:
DumpPortalFillSurfaceTypes-derived conformance sweep green; hall + hill-cottage phantom gone and doors/windows intact at Holtburg (user gate); all suites green. - Risk note: apertures whose flood fails become true holes (retail-identical); per-building floods + DrawPortal look-in already cover the visible cases. If a hole shows at an unflooded aperture, that is BR-2/ BR-4 evidence, not a BR-1 regression.
- Size: ~2 commits (batch metadata plumb + draw gate; sweep test).
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.
- Closes: #108 (outdoor terrain sweeping across the upstairs door — the
missing true-depth seal is the confirmed
missing-portal-depth-fencedivergence); the outdoor-root depth-discipline gap; part of #109. - Acceptance: cellar↔main-floor walk shows no grass sweep (user gate); 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-computationsinconsistency 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)
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 —
CellarUpharness 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
PlayerPhysicsUpdatedCallbackshape) 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/CPartCellport (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. Sequencing summary
BR-1 (surface gate) — first; standalone visual win, lowest risk
BR-2 (depth punch/seal) — second; 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
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.
5. Approval asks
- Approve the plan shape + ordering (BR-1 → BR-8, BR-7 parallel-capable).
- 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.
- 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.