acdream/docs/research/2026-06-11-building-render-acdream-vs-retail-comparison.md
Erik 5e2f99d08e docs: Phase A comparison + Phase B port plan (holistic building-render investigation)
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>
2026-06-11 05:54:12 +02:00

414 lines
26 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# acdream vs retail — building/interior render architecture comparison
**Phase A deliverable of the holistic building-render investigation**
(mandate 2026-06-11: *"map acdream's way vs retail, then make a plan how to port
retail's way of doing it once and for all"*; charter:
[2026-06-11-building-render-holistic-port-handoff.md](2026-06-11-building-render-holistic-port-handoff.md)).
**Branch:** `claude/thirsty-goldberg-51bb9b`. **Method:** two ultracode workflow
fan-outs — 12 mapping areas (11 completed; transparency/sorting re-running),
~90 agents, every retail claim cited to Ghidra decompile (port 8081, verified
PDB program) / named pseudo-C (`pc:LINE`) / `acclient.h`, every acdream claim
cited `file:line`, one adversarial verifier per claimed divergence
(Ghidra-first; BN pseudo-C distrusted per project history). Raw per-area
mapping outputs (the full evidence record) live in
[`2026-06-11-holistic-map/`](2026-06-11-holistic-map/).
**Companion deliverable (Phase B):** the phased port plan at
[`docs/plans/2026-06-11-building-render-port-plan.md`](../plans/2026-06-11-building-render-port-plan.md)
— awaiting user approval before any implementation.
---
## 1. Executive summary
The investigation **confirms the mandate's premise and overturns two of our
working theories.** Retail has one drawing discipline; we have fragments of it
plus three mechanisms it doesn't use. The headline findings:
1. **Retail flattens GfxObjs and cells at load, exactly like we do.**
`CGfxObj::InitLoad` builds a flat D3D mesh from the *full* polygon
dictionary (`D3DPolyRender::ConstructMesh`, Ghidra 0x0059dfa0) and then
**deletes every non-portal node from the drawing BSP**
(`BSPTREE::RemoveNonPortalNodes`, Ghidra 0x0053a040). There is no per-frame
BSP traversal of ordinary geometry. Our flatten-at-decode global-VAO
pipeline is retail-faithful as a *base* — the bindless MDI architecture
survives the port intact.
2. **The phantom staircase and the vanished doors are one mechanism, and it is
a draw-time *surface* gate, not a poly filter.** Building/cell meshes skip
surface batches whose `CSurface.type` has neither `BASE1_IMAGE` (0x2) nor
`BASE1_CLIPMAP` (0x4) — the `skipNoTexture` rule (D3DPolyRender inner draw,
Ghidra 0x0059d4a0; default on, data 0x00820e30). Dat-confirmed on our side
(commit `e223325` + `DumpPortalFillSurfaceTypes`): **every** portal-fill
quad on all 13 Holtburg-area building models — door fills, window fills,
*and* the meeting-hall phantom stair-ramp — is `Base1Solid` (untextured).
Retail draws none of them, ever. The doors players see are **door
entities**; we draw the solid fills as colored geometry, which is why
removing them read as "doors disappeared."
3. **Retail never geometrically clips cell or shell geometry. Pixel exactness
at apertures is a DEPTH discipline.** Production cell draws are whole
prebuilt meshes (`use_built_mesh`, `DrawEnvCell` pc:427905); the famous
`planeMask=0xffffffff` per-poly path is a legacy fallback whose mask means
*skip all clip edges*. What makes doorways pixel-perfect is:
**(a)** an invisible *depth punch* — the portal polygon, software-clipped
against the accumulated view, drawn depth-always/z-write/alpha-0 at far-Z
(`maxZ1`, opens a building aperture before its interior draws) or at its
true depth (`maxZ2`, seals indoor exits after the landscape draws);
**(b)** far→near cell order with a draw-once frame stamp; **(c)** the
z-buffer. This **reframes #114**: our `gl_ClipDistance` shell chop was
chasing retail's fallback path. The accumulated portal views exist for
*admission*, *object culling*, and *punch shapes* — never to cut geometry.
4. **Per-frame portal machinery is the heart, and we half-have it.** Building
shells draw in two passes (`DrawBuilding`, Ghidra 0x0059f2a0): a
portal-only BSP walk that dispatches each aperture through
`PView::DrawPortal``ConstructView(CBldPortal)` (eye-side vs
`portal_side` at ε=0.0002 → screen-clip vs the current view → cell-loaded
check) — flood + punch on success, *nothing* on failure — then the whole
shell mesh. The `PortalRef.PortalIndex` in the dat indexes the building's
`CBldPortal` array via `outdoor_portal_list` (pc:433920). Our flood port
(R-A1/A2/A2b, keep-listed) already implements faithful analogues of the
admission gates; what's missing is the *trigger* (we use a 48 m seed
constant instead of the shell's own draw), the *depth machinery* (punch
path exists as an unwired no-op), multi-view union (first-wins drops
second apertures), and per-view object culling (`viewconeCheck`).
5. **The adjunct systems diverge in ways users feel.** Camera: retail's boom
damping interpolates *from the published collided viewer* each frame —
shorten-fast/ease-out is emergent; we damp from our own previous damped
eye, severing that feedback (**#115 root cause, verified**). Lighting:
retail bakes *all* static cell lights per-vertex and adds a viewer light;
we cap at 8 viewer-nearest dynamic lights and sun-light interiors when the
player-cell gate says "outside" (unverified, high-confidence). Collision:
retail registers objects into per-cell shadow lists via a sphere-overlap
portal flood at registration; our landblock-wide registry is the #99/A6.P4
debt (**verified**, design corrections noted in the area file).
**Bug attribution coverage:** #113 phantom class → finding 2; #114 indoor crop
→ finding 3; doors-vanish mystery → finding 2 (solved, `e223325`); #108
grass-sweep → missing aperture depth seal + scissored AABB clear (finding 3);
#109 far-door oscillation → 48 m flood pop + first-wins view loss + missing
punch (findings 34); particles-through-walls → scissor-AABB gating instead of
per-view cone culling (finding 4); #99 doors run-through → finding 5
(collision); #115 camera drag → finding 5 (camera).
---
## 2. The retail architecture (2013 client, decomp-cited)
What follows is the synthesis; each area file in
[`2026-06-11-holistic-map/`](2026-06-11-holistic-map/) carries the full
call-chains and citations.
### 2.1 Load time: flatten + prune
- `CGfxObj::InitLoad` (Ghidra 0x005346b0): `BSPTREE::RemoveNonPortalNodes`
prunes the drawing BSP to a skeleton of portal-bearing nodes, then
`D3DPolyRender::ConstructMesh` flattens **all** polygons (portal fills
included) into a surface-batched mesh. EnvCells get the same at
`CEnvCell::UnPack` (ConstructMesh at pc:311085).
- Portal polys live in the same polygon array; `BSPPORTAL` nodes reference
them as `CPortalPoly { portal_index, CPolygon* }` (acclient.h:39075) — the
dat's `PortalRef { PolyId, PortalIndex }` (our `e223325` finding).
### 2.2 Frame composition (outdoor root)
`SmartBox::RenderNormalMode` (0x453aa0): viewer in an EnvCell →
`DrawInside(viewer_cell)`; viewer outdoors → full-screen view + `LScape::draw`
→ per landblock `DrawBlock` → per land cell in view: `DrawLandCell` (terrain)
then `DrawSortCell` = `DrawBuilding(cell->building)` + `DrawObjCell(cell)`
**a building draws exactly when its host land cell draws** (per-cell
interleave, far→near blocks; `CSortCell.building` is the one-building-per-cell
slot, acclient.h:31880).
### 2.3 The building two-pass + portal machinery
`RenderDeviceD3D::DrawBuilding` (0x0059f2a0):
1. `outdoor_pview->outdoor_portal_list = building->portals` — installs the
CBldPortal lookup the shell's portal polys index.
2. **Pass 1 (portals):** `CPhysicsPart::Draw(part, 1)` → the pruned BSP walk
(`build_draw_portals_only` modes 1 then 2) submits each portal poly →
`PView::DrawPortal` (0x005a5ab0) resolves
`outdoor_portal_list[portal_index]`, pushes fresh `portal_view` slots onto
every stab-list cell, and runs `ConstructView(CBldPortal)` (0x005a59a0):
- eye side vs `portal_side` at ε=0.0002 — in-plane rejects outright;
- `GetClip` — the portal polygon software-clipped against the **current**
view (full screen outdoors; a doorway slot if this building is itself
seen through a portal); empty → fail, *no distance constant exists*;
- `CEnvCell::GetVisible` — target cell loaded;
- success mode 1 → **far-Z depth punch** of the clipped aperture
(`DrawPortalPolyInternal`, alpha-0, depth-always, z-write, `maxZ1`);
- success mode 2 → recurse `ConstructView(CEnvCell)` (the BFS flood) and
`DrawCells` draws the interior into the punched aperture.
The `building_view` latch binds nested floods to the view slot they were
discovered under (saved/-1/restored around `DrawPortal`, pc:427906-427914).
3. **Pass 2 (shell):** `ObjBuildingOrBuildingPart=1`;
`CPhysicsPart::Draw(part, 0)` draws the whole constructed mesh — with
`skipNoTexture` skipping every untextured (solid) surface batch. Interior
pixels survive only inside the punched aperture.
### 2.4 The indoor flood + DrawCells
`PView::DrawInside` (0x005a5860): root view = full-screen quad;
`ConstructView(CEnvCell)` floods via `InitCell` (eye-side per portal,
ε=0.0002; entered-portal back-walk block) + `ClipPortals` (per accumulated
view: project portal, homogeneous Sutherland-Hodgman `polyClipFinish`
near-W clip first, then each view edge; pixel-exact, no plane budget) +
`AddViewToPortals` (first discovery enqueues; growth propagates **in place**
via `AddToCell`/`FixCellList`/`AdjustCellView` with the `update_count`
watermark; termination from `copy_view`'s 1-pixel vertex dedup, not a cap).
A multi-portal cell accumulates a **list** of view polygons — union-as-list,
all views consumed downstream.
`PView::DrawCells` (0x005a4840):
1. If any outside views: `PortalList=&outside_view`**`LScape::draw`** (the
landscape through the accumulated doorway views) → conditional full
**depth clear** (gated on `portalsDrawnCount`) → far→near per cell per
view: **z-seal** every portal leading outside (`other_cell_id==0xFFFF`) at
its true projected depth (`maxZ2`) — terrain seen through the door keeps
its pixels; interior geometry farther than the door plane z-fails inside
the aperture.
2. Cells far→near per view via `DrawEnvCell` — whole prebuilt mesh, drawn
once (`GetDrawnThisFrame` frame-stamp), **never clipped**.
3. Per cell: `PortalList = cell's view stack``DrawObjCell` — every object
sphere-tested per view by `Render::viewconeCheck` (0x0054c250: sphere vs
eye plane + each `view_vertex.plane`); objects are **culled, never
clipped**. Translucent batches defer to the AlphaList and flush sorted.
### 2.5 The adjuncts
- **Camera** (`wf2-camera-viewer.md`): `CameraManager::UpdateCamera`
interpolates the sought pose **from the published collided viewer**
(`PlayerPhysicsUpdatedCallback` passes `&this->viewer`) with
α = stiffness·dt·10 (stiffness 0.45); `SmartBox::update_viewer` re-sweeps
pivot→sought *every frame* (0.3 m sphere) and publishes the raw collided
stop + `viewer_cell = sphere_path.curr_cell`. No explicit boom smoothing
exists — the feel is emergent from the collided-feedback loop. Player mesh
fades over the 0.45→0.20 m approach band
(`SetTranslucencyHierarchical`).
- **Lighting** (`wf2-indoor-lighting.md`, unverified): per-cell static lights
burn into vertices; interiors are never sun-lit; a white viewer light rides
above the player; per-object light selection against object bounds.
- **Sky/weather/scenery** (`wf2-sky-weather-scenery.md`, unverified): weather
gates on `is_player_outside`; rain cylinder at world-absolute z; scenery
draws per land cell, participating in the same per-cell interleave.
- **Collision** (`wf1-interior-collision.md`, verified): registration builds
the cell set by a sphere-overlap **portal flood** (`add_shadows_to_cells` /
`find_cell_list` family); queries iterate per-cell `shadow_object_list`;
buildings dispatch through `CSortCell.building` (per-cell channel);
`check_building_transit` gates `other_portal_id >= 0` (sign-extension
Ghidra-proven — BN renders it unsigned, the invented-sign failure mode).
- **Picking** (`wf2-picking-selection.md`, unverified): object-sphere/poly
arbitration inside the draw traversal's visibility, not a parallel ray.
---
## 3. The acdream architecture today
Mapped in full in the area files; the short form, with keep/replace verdicts:
| Subsystem | Today | Verdict |
|---|---|---|
| Mesh pipeline (flatten → global VAO → bindless MDI) | `ObjectMeshManager``WbDrawDispatcher`, ~12-15 GL calls/frame | **KEEP** — matches retail's flatten-at-load |
| PView flood (admission) | `PortalVisibilityBuilder` — faithful homogeneous clipper, side tests, reciprocal clip, exact-match skip | **KEEP** (R-A1/A2/A2b + dac8f6a, conformance-gated) — adjust constants/heuristics per ledger |
| Flood trigger | 48 m per-building seed over Chebyshev≤1 landblocks, outdoor roots only | **REPLACE** with shell-draw-driven `DrawPortal` (retail has no distance constant) |
| Aperture enforcement | `gl_ClipDistance` shell chop (outdoor-scoped, #114) + scissored AABB depth clear + unwired `DrawExitPortalMasks` | **REPLACE** with retail depth punch/seal/clear discipline; shells draw whole |
| Portal-fill suppression | GfxObjs: none (fills drawn — phantom class); cells: build-time `NoPos`/`NoNeg` stippling drop | **REPLACE/ALIGN** with draw-time `skipNoTexture` surface gate (dat-confirmed equivalent on audited cells) |
| Object/particle visibility | Cell-membership buckets; particles scissor-AABB | **EXTEND** with per-view `viewconeCheck`; route particles through the same gate |
| Building physics/collision | Landblock-wide `ShadowObjectRegistry` + `b3ce505` gate (#99) | **REPLACE** per A6.P4 (verified, with corrections: signed `OtherPortalId` + `>=0` gate, per-cell building channel) |
| Membership / straddle gate / streaming / camera collision / znear / texture flush | P1 9/9 golden; `414c3de`; A.5; verbatim `update_viewer`; 0.1; `c787201` | **KEEP** (charter keep-list, re-confirmed by verifiers) |
| Visibility computations | **TWO live systems**: `CellVisibility.ComputeVisibilityFromRoot` (ACME BFS) *and* `PortalVisibilityBuilder` (verified) + legacy remnants (`InteriorRenderer`, `IndoorDrawPlan`, dual frustum impls) | **CONSOLIDATE** to one gate (PView) + delete dead paths |
| Camera boom | Damps from own previous damped eye; fade computed, never applied | **FIX** feedback anchor (#115, verified) + apply fade |
| Lighting | Single scene UBO, 8 viewer-nearest lights, player-cell sun gate | **EXTEND** per lighting area (burn-in, viewer light, interior sun mask) — pending verification |
---
## 4. The divergence ledger
76 divergences across 11 mapped areas (the 12th, transparency/sorting, is
re-running). Ranked within area; verification = adversarial re-derivation
(Ghidra-first). `UNVERIFIED` rows had their verifier interrupted by the
session token limit — a resume is in flight; treat them as high-confidence
mapped claims, not yet adversarially proven. Full evidence per row in the
area files.
### Where each open bug lands
| Bug | Primary divergences |
|---|---|
| #113 phantom geometry class | `solid-surface-skip-missing` (gfxobj), `portal-polys-baked-unconditional` (shells) |
| Door-vanish mystery | **SOLVED**`e223325` + surface-type dump; same rows as #113 |
| #114 indoor crop | `shell-chop-vs-depth-discipline`, `missing-aperture-depth-punch`, `multiview-loss-first-wins`, `knife-edge-epsilon-and-rescue` (interior-cells) |
| #108 grass-sweep | `missing-portal-depth-fence` (culling, **confirmed**), `depth-clear-shape-and-order`, `eight-plane-budget-passall` |
| #109 far-door oscillation | `building-flood-seeding-48m-cutoff` (adjusted: pop mechanism confirmed, 48 m linkage unmeasured), `multiview-loss-first-wins`, `missing-portal-depth-fence` |
| particles-through-walls | `particles-not-cell-resident` (statics), `object-particle-gating` (interior-cells), `particles-third-gate-tier` (gates) |
| #99 door run-through | `registration-cell-set-not-portal-flood`, `flat-object-query-not-per-cell` (collision, both **confirmed**) |
| #115 camera drag | `boom-no-collided-feedback` (**confirmed**) |
| Indoor "feels right" (M1.5) | the six `indoor-lighting` rows (unverified) |
### Ledger (severity / verdict / one-line)
**Area 1 — GfxObj draw** (`wf1-gfxobj-draw.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | adjusted | `portal-poly-conditional-pass-missing` — no per-frame z-punch/z-seal/ConstructView pass on portal polys |
| HIGH | adjusted | `solid-surface-skip-missing` — untextured (solid) batches drawn on building/cell meshes retail skips |
| MED | confirmed | `degrade-lod-scoped-to-humanoids` — retail degrades every non-player part per frame |
| MED | adjusted | `no-per-view-entity-pass` — no per-portal-view re-cull of objects |
| MED | confirmed | `stippling-semantics-divergence` — WB's NoPos/NoNeg side-drop vs retail batch flag + sides_type |
| LOW | adjusted | `no-frame-dedup` — no GetDrawnThisFrame frame-stamp |
**Area 2 — Building shells** (`wf1-building-shells.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | unverified | `portal-polys-baked-unconditional` — fills baked + drawn; PortalIndex→CBldPortal pairing never consumed |
| CRIT | unverified | `no-per-slot-building-draw` — building never draws per view slot; floods not shell-draw-driven |
| HIGH | adjusted | `flood-gate-shape` — 48 m seed + 0.01 ε + eye-inside rescue vs retail's no-distance chain (analogues otherwise faithful) |
| HIGH | unverified | `aperture-depth-machinery` — far-Z punch missing; particles scissor-only |
| MED | confirmed | `building-not-in-physics-cell-graph` — per-cell building channel missing; `other_portal_id>=0` gate missing (sign-extension proven) |
| LOW | unverified | `leaf-cells-unported` — retail path itself appears dormant; do not port without runtime proof |
**Area 3 — Interior cells** (`wf1-interior-cells.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | unverified | `shell-chop-vs-depth-discipline` — we clip shell geometry; retail clips nothing (depth discipline) |
| CRIT | unverified | `missing-aperture-depth-punch` — DrawExitPortalMasks unwired; AABB far-clear wrong shape/value |
| HIGH | unverified | `multiview-loss-first-wins` — MergeBuildingFrame drops views; CellIdToSlot keeps slices[0] |
| HIGH | unverified | `eight-plane-budget-passall` — >8 edges → slot-0 PASS-ALL; scissor fallback unimplemented |
| HIGH | unverified | `knife-edge-epsilon-and-rescue` — 0.01 vs 0.0002 ε + non-retail 1.75 m full-view rescue |
| MED | unverified | `growth-requeue-vs-in-place` — re-enqueue + cap-16 vs retail in-place propagation + 1-px dedup floor |
| MED | unverified | `object-particle-gating` — membership-only culling; particles scissored |
| MED | unverified | `portal-poly-suppression-criterion` — build-time stippling vs retail draw-time surface gate |
**Area 4 — Statics + dynamics** (`wf1-statics-dynamics.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | unverified | `building-portal-polys-unconditional` — (cross-ref Area 2) |
| CRIT | adjusted | `particles-not-cell-resident` — owner-bucket + 2D scissor vs emitter-cell residency + per-slot cone |
| HIGH | confirmed | `single-cell-buckets-vs-shadow-parts` — one ParentCellId bucket vs register-in-every-overlapped-cell + draw-once |
| HIGH | confirmed | `shells-drawn-whole-in-retail-production` — the #114 reframe anchor |
| MED | confirmed | `no-per-slot-viewcone-for-meshes` |
| MED | **refuted** | `livedynamic-dropped-indoors` — claim did not survive; see area file |
| LOW | adjusted | `outdoor-objects-redrawn-per-slice` |
| LOW | adjusted | `per-cell-depth-sort-missing` |
**Area 5 — Culling/frame composition** (`wf1-culling.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | confirmed | `missing-portal-depth-fence` — the maxZ2 fence after the clear is absent; hook unwired |
| CRIT | adjusted | `approximate-portal-clip-for-landscape` — ≤8 GL planes + AABB scissor vs exact software clip |
| HIGH | adjusted | `depth-clear-shape-and-order` — per-slice scissored clears after all slices vs one gated full clear |
| HIGH | unverified | `portal-poly-conditional-draw` — (cross-ref Areas 1/2) |
| HIGH | adjusted | `building-flood-seeding-48m-cutoff` — pop mechanism confirmed; #109@48 m unmeasured |
| MED | confirmed | `entity-cull-no-portal-viewcone` |
| MED | adjusted | `weather-gate-player-vs-viewer` — rain through doorways while inside |
| MED | adjusted | `unattached-particles-dropped-outdoors` |
| LOW | confirmed | `global-passes-vs-per-cell-interleave` |
**Area 6 — Interior collision** (`wf1-interior-collision.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| CRIT | confirmed | `registration-cell-set-not-portal-flood` — XY grid vs sphere-overlap portal flood |
| CRIT | confirmed | `flat-object-query-not-per-cell` — one radial query vs per-cell shadow-list iteration |
| HIGH | adjusted | `building-shell-as-shadow-object` — landblock-wide entries vs per-LandCell building channel |
| HIGH | confirmed | `check-other-cells-env-only` — retail runs env AND shadow objects per other cell |
| MED | adjusted | `a6p5-topology-widening` — wider than retail's straddle gate (pending A6.P4) |
| MED | confirmed | `single-landblock-grid-clamp` — registration clamps to own landblock |
| LOW | confirmed | `movement-reregistration-source` — fresh grid vs transition's own cell array |
**Area 2.1 — Camera/viewer** (`wf2-camera-viewer.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| HIGH | confirmed | `boom-no-collided-feedback` — sought eye never re-anchors to published collided viewer (#115 root cause) |
| MED | confirmed | `player-fade-computed-not-applied` |
| LOW | confirmed | `sought-position-lacks-cell-identity` |
| LOW | adjusted | `camera-input-scalars-unverified` |
**Area 2.2 — Indoor lighting** (`wf2-indoor-lighting.md`) — all unverified
| Sev | Divergence |
|---|---|
| CRIT | `interior-sun-bleed` — interiors sun-lit when player-cell gate says outside |
| HIGH | `no-static-light-burnin` — interiors capped at 8 viewer-nearest lights vs all static lights baked |
| MED | `no-per-object-light-selection`; `no-viewer-light`; `surface-luminosity-diffuse-ignored` |
| LOW | `dynamic-entity-lights-unregistered` |
**Area 2.3 — Sky/weather/scenery** (`wf2-sky-weather-scenery.md`) — all unverified
| Sev | Divergence |
|---|---|
| CRIT | `outside-portal-zstamp-missing` — (same family as the depth fence) |
| HIGH | `weather-indoor-gate`; `particles-not-portal-clipped` |
| MED | `no-nested-building-flood-through-outside-view` |
| LOW | `outdoor-objects-flat-bucket`; `rain-anchor-z-relative`; `weather-enabled-toggle-absent` |
**Area 2.5 — Visibility-gates audit** (`wf2-visibility-gates-audit.md`)
| Sev | Verdict | Divergence |
|---|---|---|
| HIGH | confirmed | `object-lists-skip-portal-view-gate` |
| HIGH | confirmed | `indoor-shell-clip-disabled` — indoor roots have NO draw-side discipline today |
| HIGH | unverified | `particles-third-gate-tier` |
| MED | confirmed | `dual-live-visibility-computations` — ACME BFS + retail flood both run per frame |
| MED | adjusted | `landscape-redrawn-per-outside-slice` |
| MED | unverified | `flood-convergence-heuristics`; `drawportal-membership-rule-mismatch`; `livedynamic-invisible-under-interior-roots` |
| MED | confirmed | `exit-portal-mask-pass-dormant` |
| MED | adjusted | `legacy-outdoor-branch-remnant` — clipRoot==null second path |
| LOW | unverified | `dual-frustum-implementations` |
**Area 2.6 — Picking** (`wf2-picking-selection.md`) — all unverified, all ≤ medium
`pick-outside-draw-traversal`; `occluder-stricter-and-looser-than-retail`;
`no-poly-stage-no-poly-beats-sphere`; `no-selected-in-view-tracking`.
---
## 5. Mysteries resolved this session
1. **Door-vanish (charter §4.1)** — SOLVED, dat-proven (`e223325` +
`DumpPortalFillSurfaceTypes`): the e46d3d9 filter walked only
`node.Polygons`, never `node.Portals` (`PortalRef`); every dropped poly was
a portal fill; all fills are `Base1Solid`; retail skips them via
`skipNoTexture`; visible doors are entities. **No static filter can be
correct** — but the correct rule is a *surface-type draw gate*, which is
nearly as simple.
2. **#114's real shape (charter §4.2)** — retail does not crop indoor
geometry; it punches/seals depth at apertures and draws far→near. The
"admission-quality vs draw-quality regions" framing dissolves: regions
only ever needed to be admission-quality (+ punch shapes).
3. **#115 (charter §4.5)** — root cause confirmed: missing collided-viewer
feedback into the damping origin; plus the player fade is computed but
never applied.
4. **#108/#109 (charter §4 re-test list)** — concrete mechanisms named (depth
fence, clear shape, flood pop, view loss) — see ledger.
5. **Charter §4.3 (cottage entry transparency)** — not separately
investigated; the straddle gate + flood stability work landed earlier;
re-test after the port phases land.
## 6. Open questions carried into the port plan
The area files carry ~30 open questions; the load-bearing ones:
1. ~~Where does retail draw the textured fill of a building portal poly?~~
**Answered**: nowhere at Holtburg — all fills untextured
(`DumpPortalFillSurfaceTypes`). A dat-wide sweep should pin the invariant
before relying on it globally (plan P1 acceptance).
2. `LScape::draw` internals — does retail clip terrain polys against
outside views or only cull blocks/cells per view? (Affects how faithful
our per-slice terrain clip needs to be once the punch exists.)
3. `PView::DrawPortal` mode 3 (seal-on-failure) — who calls it; matters for
unstreamed interiors.
4. Window-type CBldPortals — do they flood (stab lists + GetVisible-able
targets)? Decides window treatment in P1/P4 gates.
5. `cdstW` near-W constant; `CBldPortal.sidedness` semantics; the
`DrawMesh` skipNoTexture else-branch latch — pin during P1/P2
implementation.
6. Dungeon same-volume overlap sweep — z-only indoor compositing assumes
non-overlapping cell volumes; one-off dat sweep before declaring P3 done.
## 7. Verification status + how to finish it
- 40/76 divergences adversarially verified (1 refuted, 11 adjusted with
corrected claims — the corrections are folded into this doc's ledger).
- Both workflows are resumable; a resume was launched for the remaining
verifiers, the transparency/sorting map, and both completeness critics
(run IDs `wf_475e012b-f74`, `wf_dd8381c7-c0a`). This doc's ledger should be
refreshed from the final JSONs when they land.