acdream/docs/research/2026-08-30-cathedral-review-decomp.md
Erik 6d990d38b3 docs: the cathedral seven-report synthesis + adjudicated FW4 plan
Files the independent review corpus (fable/opus/external + audit,
DAT-format, F418 DAT-geometry, retail decomp reconstruction) and the
synthesis that adjudicates their disagreements: retail roots INTERIOR
at the posed terrace defect pose with a ONE-CELL flood and ov=1 (the
free captures rooted outdoor - both were real); the falls are outdoor
landblock emitters 0xCF418000-13; the "thin seam bands" claim is
DAT-refuted (full-size exits; the seam is a coincident double
exit-to-outside plane at y=48); clear+seals live inside retail''s ov>0
block with a one-frame seal latch. Carries the instrument-first plan
(falls-owner trace, depth checkpoints, pose conformance) and the
decomp-pinned repair sequence toward FW4''s single-owner goal.

Also sweeps in the probe scaffolding slice 5''s path-limited add had
missed (RenderingDiagnostics field + the launch-options row wording).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 20:52:14 +02:00

141 lines
7.3 KiB
Markdown

# Cathedral review — retail interior-root frame reconstruction (companion report)
Delivered 2026-08-30 by the decomp-reconstruction agent (BN pseudo-C +
live Ghidra arbitration on 8081 + acclient.h + symbols.json). THE retail
mechanism reference for the synthesis. Condensed; all pc:/address
citations preserved.
## Frame skeleton (interior root)
1. `SceneTool::BeginScene` @0x0043dad0: **full Clear(7) — COLOR+depth+
stencil — every frame** (pc:68673-68686). `Flip` bumps m_nFrameStamp
at frame end (pc:428642).
2. `RenderNormalMode` @0x00453aa0: interior camera → optional
LScape::update_viewpoint (only when viewer_cell->seen_outside,
pc:92667-71) → DrawInside(viewer_cell) → **unconditional final drain
FlushAlphaList(0f)** (pc:92687).
3. TWO PView singletons (pc:427800/427813): `indoor_pview` (draw_landscape
=1) for the root; `outdoor_pview` (0) for building look-ins — look-in
floods can never re-trigger landscape/clear/seals (their
outside_view.view_count is always 0; ClipPortals never raises it when
draw_landscape=0, pc:433666-84).
4. `PView::DrawInside` @0x005a5860: push root view (full viewport) →
`ConstructView(root, 0xffff)` — PURE CPU flood, draws nothing; exit
portals merge clipped apertures into outside_view (pc:433662-76,
cliplandscape=1 @0x00820f4c).
## PView::DrawCells @0x005a4840 — ALL of this inside `ov > 0` (pc:432715):
1. `Render::PortalList = &outside_view`; `LScape::draw` (pc:432718-19).
2. **Full outdoor alpha drain** FlushAlphaList(0f) (pc:432720).
3. `m_nFrameStamp += 1` (pc:432722) — re-arms part dedupe so a part can
draw in BOTH the outdoor and interior passes of one frame (straddlers
draw twice; "emit once" is anti-retail).
4. **Gated z-only clear** (pc:432725-32, Ghidra-verified):
`forceClear(const 0) || portalsDrawnCount != 0` — the counter is
incremented ONLY by own-depth portal stamps (seals, arg2==0,
pc:424569-70) → **a ONE-FRAME LATCH: last frame's seals arm this
frame's clear.** Steady-state vista frames clear every frame; the
FIRST such frame does not.
5. **Exit seals** (pc:432737-807): per flooded cell (far→near), per view,
EVERY portal with other_cell 0xffffffff:
`DrawPortalPolyInternal(poly, 0)` — REAL projected depth,
DEPTHTEST_ALWAYS + z-write ON (pc:424584), invisible (vertex alpha 0,
pc:424686), CULL_NONE, CPU-clipped to the current view (pc:424577).
(Sibling mode arg2==1 = the far-Z PUNCH z≈0.999999, look-ins only;
does not increment the counter.)
Then UNCONDITIONALLY (even when ov==0):
6. Lighting restore (pc:432811-12).
7. **Cell shells** far→near: DrawEnvCell dedupes per frame stamp — the
shell draws ONCE, WHOLE; the per-view loop is vestigial for it; no
view planes applied; correctness = pure z-test (pc:427885-88).
8. **Cell objects** far→near: PortalList = the cell's views →
DrawObjCellForDummies → shadow parts sorted by CYpt (viewer distance
to scaled sort_center, insertion_sort @pc:701156-75) →
CPhysicsPart::Draw — statics, dynamics, AND particle-emitter parts
through ONE machinery.
9. Final drain (pc:92687) vs interior depth + seal-stamped apertures.
## Clip vs cull (the definitive table)
- CPU polygon-clipped: ONLY portal-chain polys during the flood
(GetClip @0x005a4320) and seal/punch fans at draw (their own
polyClipFinish vs the current view, pc:424577, + a ±12 local-XY trivial
reject pc:424506-50).
- Sphere-vs-view CULLED, whole-drawn: all meshes (statics, dynamics,
particles' parts) — DrawMesh per view: viewconeCheck(sphere)
(pc:429295-96); PARTIAL draws WHOLE under that view; a mesh in N views
submits N times. Building PORTAL pass is cone-exempt (punches always
attempted; GetClip <3 points is the real stop, pc:429302-10).
- Cached-flag culled: terrain cells via in_view stamped by
draw_check_blocks @0x00505f80 iterating EVERY view of PortalList
(blocks wholly outside every exit view skip at pc:267936). Terrain
polys then draw whole + immediate (landPolysDraw @0x006b7040 no view
clip, no alpha list).
- No test at all: EnvCell shell geometry; seal z-behavior (ALWAYS).
- `alwaysDrawObjects = 1` (@0x00820ed4): DrawSortCell runs for EVERY cell
of a drawn block the per-part per-view cone check is the real object
gate (no pixel leak, just CPU).
## The alpha lists (retail)
TWO static FIFO lists, 3000 entries each (Clip @0x00871b10, Alpha
@0x008af370). NO depth key stored, NO sort at flush order = submission
order; far-to-near correctness comes from the cell walk + per-cell CYpt
insertion sort. Overflow DROPS the subset (pc:424983). Entry routing:
s_AlphaDelayMask (default 0xE) vs the surface mask; bit3 Clip list.
Flush sites (grep-complete):
| # | Site | Arg | Depth in place |
|---|------|-----|----------------|
| 1 | DrawBuilding @0x0059f30b | 0f FULL | outdoor-so-far (pre-punch) |
| 2 | DrawBlock @0x005a1a07 | 0.75 valve, fires per outdoor cell (BN's `flush>1` guard is a MISREAD Ghidra: fires when flush<=1.0) | mid-landscape |
| 3 | DrawCells @0x005a4872 | 0f FULL | complete outdoor depth (pre-clear) |
| 4 | RenderNormalMode @0x00453b8b | 0f FULL (final) | interior + seals |
| 5 | CreatureMode::Render (chargen only) | 0f | its own viewport |
## Depth at final drain (interior root)
- Aperture/vista pixels: the SEAL's portal-plane depth (outdoor per-pixel
depth NO LONGER EXISTS cleared; outdoor COLOR survives under the
stamp). Interior alpha fragments behind the portal plane FAIL there.
- Outdoor particles (falls): drawn in the landscape pass, drained at
site #3 vs outdoor depth. Cone-PARTIAL spill outside the aperture is
buried because EVERY interior pixel paints after every outdoor drain
point protection is pure ORDERING, not depth.
## ShouldDrawParticles @0x0050fe60 — an UPDATE-time gate
Called per emitter from ParticleEmitter::UpdateParticles (physics-side),
NOT during rendering: `ExaminationObject → 1; CYpt > degrade_distance →
0; cell NULL → 0; cell->IsInView()==0 → 0`. On false: SetNoDraw(1),
degraded_out=1 (particles stop updating AND drawing).
- CLandCell::IsInView @0x00532cb0 = the PREVIOUS frame's stamped in_view
(one-frame-stale gate).
- **CEnvCell does NOT override it** the slot resolves to the ICF-folded
constant @0x005269f0 `return 1` (PARTIALLY_INSIDE, PDB-verified): for
interior emitters the cell test is VACUOUS; distance is the only
update-time gate; their draw is flood-gated instead.
## Can content outside the flood/exit views draw? NO.
Interior: pure flood membership. Outdoor blocks/cells: in_view under 1
exit view. Outdoor parts: per-exit-view sphere-cone in DrawMesh. Building
portal pass cone-exempt but clip-stopped. No distance bypass exists.
## Corrections to campaign docs
1. DrawBlock per-cell flush polarity RESOLVED (fires; 0.75 pressure
valve) BN misread.
2. The m_nFrameStamp bump separates the landscape and interior passes of
ONE interior-root frame; look-ins NEVER bump (outdoor_pview ov==0).
3. NEW: CEnvCell::IsInView = constant PARTIALLY_INSIDE (ICF fold,
symbols.json 19-name cluster) not in any FW doc.
## UNVERIFIED (the agent's own list)
The 0xF418 falls emitters' owner (land cell vs EnvCell static dat
question; note the DAT-geometry companion resolves this: landblock
object-list stabs); z-write during alpha drains; insertion_sort
direction byte-proof; the ±12 reject polarity; GameSky depth behavior.