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>
141 lines
7.3 KiB
Markdown
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.
|