acdream/docs/research/2026-09-01-overhaul/s3-walk-ownership-map.md
Erik 4bc9c796b4 docs(render): S3 chunk 4 contract draft — the deletions
Retail CPU-clips only portal polygons, punch fans and exit seals
(polyClipFinish); ordinary meshes, shells, terrain, particles and the
weather are never view-clipped. Inventory at edb32783: keep the seal and
punch clips (with equivalence pins), delete the per-slice weather loop,
the sky-post-scene particle clip slot, the dead cell-shell clip routing,
the slot-list shape of the part classifier, and the seven
ProbeCathedral* discriminators with their launch-option rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:31:25 +02:00

564 lines
38 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.

# S3 — Walk: exact PView views, leaf admission, and interleave — ownership map and chunk plan
**Status:** DRAFT packet written 2026-09-03 at the S2 closeout, BEFORE the
owner's §7 capture session. Every retail claim below is decomp-verified
(named pseudo-C + Ghidra bridge, cited by address); the capture session adds
the per-frame TRANSCRIPT the automated gate needs and may amend §3 (marked
"capture-owned"). Nothing in this packet is implemented yet.
**Plan:** `docs/plans/2026-09-01-campaign-overhaul-world-solidity.md` §9 S3.
**Binding inputs:** `oh1-retail-world-contract.md` §4§5 (view/frame order),
`oh1-built-mesh-view-contract.md` (caller order, stamps, direct answers,
binding constraints), `2026-08-30-fw-flood-pseudocode-appendix.md`, the FW
plan's verified seam map (`docs/plans/2026-08-30-campaign-fw-frame-walk.md`
§"Verified seam map"), and S2's ownership truth
(`s2-membership-ownership-map.md` §9: the registry is the only membership
source; the walk reads `GetRetailPartEntriesInCell`).
## 1. What S3 changes (behavior)
`WalkPView` owns retail's exact per-frame state — the portal-view lists, the
view/update counts, the todo order, the draw list, and the stamps — and each
content category follows its retail leaf rule. Terrain is emitted as ordered
landcell events interleaved with buildings and object lists in retail
far-to-near order. `DrawBuilding` is one semantic sequence. The "all terrain
first" simplification and every generic GPU clip rule that retail does not
have are deleted. S3 ships no evidence infrastructure beyond the one
print-only transcript emitter its own gate consumes (rule §5).
## 2. Retail truth (decomp-verified; addresses are the Sept 2013 build)
| Retail function | Address | What it decides | Verified in |
|---|---|---|---|
| `PView::InitCell` / `InsCellTodoList` | (walk root; see flood appendix) | seeds the flood from the viewer cell, orders the todo list | flood appendix |
| `PView::ClipPortals` / `AddViewToPortals` / `ConstructView` | `PView::GetClip → polyClipFinish @0x006B6D00` | builds CHILD VIEWS by polygon-clipping PORTAL polygons — the only polygon clip in the walk | built-mesh contract §"What polygon-clips?" |
| `Render::copy_view` | `0x0054dfc0` | copies the view polygon, derives edge planes (0x48 semantic fields, prune, cap 31) | built-mesh contract ledger |
| `Render::set_view` | `0x0054d0e0` | installs the view in render globals; NO scissor, NO general mesh clip | idem |
| `Render::obj_view_set` | `0x0054b9b0` | transforms the active planes/sphere through `FrameCurrent`, publishes `local_object_center/radius` | idem (side effects unrepresented — Medium row) |
| `Render::viewconeCheck` | `0x0054c250` | the ordinary sphere admission: strict-outside / inclusive-partial | `WalkVisibilityMath` pins |
| `CPhysicsPart::Draw` | `0x0050d7a0` | outer part-stamp skip (`+0xdc` vs device `+0xb0`); force bypasses; local player bypasses the inner stamp | idem |
| `RenderDeviceD3D::DrawMeshInternal` | `0x0059f360` | first admitted view stamps the non-player part and submits the WHOLE eligible mesh once; later views return | idem |
| `RenderDeviceD3D::DrawEnvCell` | `0x0059f170` | no sphere admission; cell stamp (`+0x130`) gate; whole shell once per stamp | idem |
| `RenderDeviceD3D::DrawBuilding` | `0x0059f2a0` | detail state → `FlushAlphaList(0)``CPhysicsPart::Draw(part,1)` portal-only BSP pass → flag → `Draw(part,0)` ordinary shell | idem |
| `PView::DrawCells` | `0x005a4840` | outside-view branch (sunlight, `LScape::draw`, alpha flush, device-stamp advance @`0x005a4886`, counter consume/clear, exit seals) then reverse shells (`CEnvCell::setup_view @0x0052c430` per live view + `DrawEnvCell`) then reverse object lists (`DrawObjCellForDummies`) | retail-world contract §5.2 |
| `LScape::draw_check_blocks` / `landcell_check` / `grab_visible_cells` / `draw` / `calc_draw_order` | (landscape walk) | admitted land cells in retail FAR-TO-NEAR order; per admitted cell `DrawLandCell` THEN `DrawSortCell` (building pass / look-in / shell / object turn) | retail-world contract §5.2; **capture-owned:** the exact `calc_draw_order` sequence at the four poses |
| `DrawObjCellForDummies` | (object-list path) | installs the cell's top `portal_view_type` as `PortalList`, walks the cell's shadow part list (`CShadowPart::insertion_sort` by `CYpt`) and draws each part through the view loop | built-mesh contract §"object-list path" |
Facts already PROVED and binding (do not re-derive, do not weaken):
- the built cell shell is stamped and submitted whole once after Boolean admission;
- portal polygons, not built meshes, are polygon-clipped;
- `DrawCells` draws all shells then all object lists, both in reverse
draw-list order, views ascending;
- device, cell-shell and part stamps persist with the local-player bypass;
- the "first view clips the shell" hypothesis is RETIRED.
## 3. Current acdream owners (functions, not line numbers — re-locate at chunk time)
| Retail mechanism | Current owner | Assessment (from the built-mesh comparison at `eaea8776`, re-checked against `c94a1a40` file list) | S3 action |
|---|---|---|---|
| view polygon / planes | `WalkViews` | strong match | keep; pin |
| viewcone sense | `WalkVisibilityMath` | exact | keep |
| flood + todo order | `WalkFrameDriver` (flood turns, `VisitedCells`, `OnLandscape`) | order proven for shells/objects; `_skyDrawnThisFrame` is a PROXY for retail's `outside_view.view_count != 0` | chunk 2 replaces the proxy with the real outside-view count |
| whole-mesh admission at slot 0 | `WbDrawDispatcher.WalkClassify` (`ResolveWalkLightSet`, part stamps, `RetailWholeMeshSlot`) | correct | keep; chunk 4 deletes the still-translated clip slots per category |
| part stamp identity | `(RenderProjectionId, PartIndex)` | retail identity is the `CPhysicsPart*`; S2 made per-cell membership registry-borrowed, so one logical part now reaches the classifier from EVERY cell it is in with the SAME projection id — the identity audit the contract asked for is now structurally answered; pin it | chunk 2 pin |
| cell shell once per stamp + mid-frame re-arm | `WalkFrameDriver` shell loop, `RetailPViewPassExecutor.WalkLeaf` | correct | keep; re-arm gate moves to the real outside-view count |
| building sequence | `WalkFrameDriver` building turn, `RetailFrameWalk` | matches alpha barrier → portal pass → shell | keep; pin as ONE sequence |
| terrain emission | `RetailPViewPassExecutor` landscape stage (whole-stage terrain event before building/object turns) | DIVERGES: retail is `DrawLandCell` then `DrawSortCell` PER admitted land cell, far-to-near | chunk 3 |
| landscape leaf's use of the installed outside-view list | `TerrainModernRenderer` visible cells + `WalkFrameDriver.VisitedLandscapeCellIds` | top-level ownership proven; per-leaf pixel use is an S3 audit item | chunk 3 audit (bounded: one pose) |
| generic GPU clip rules for categories retail never clips | Vulkan clip slots on look-in / straddling / building categories (`RetailPViewPassExecutor`, `UseIndoorMembershipOnlyRouting`) | retail clips only portal polygons | chunk 4 delete, with a CPU/GPU equivalence pin for any category that must keep a translated clip |
| `obj_view_set` side effects | not represented (world-space sphere vs captured planes) | mathematically aligned for affine transforms | out of S3 unless the capture shows a consumer |
| missing `SelectionSphere` fallback | "any view → whole mesh" | not a retail branch | keep as a corruption fallback, outside parity claims (register row if it survives S5) |
## 4. Chunks (bounded; one Sonnet agent each; stop on a third round)
**Chunk 1 — transcript emitter + parser + fixture import (needs §7 captures).**
One print-only flag (documented in `docs/launch-options.md` in the same
commit) emits the OH line kinds the capture templates produce
(`tools/walk-oracle/oh/*.template`: walk / parts / alpha-depth) from the
production walk; the existing FW0-style test-side parser is extended for the
new kinds; the four captured pose logs are imported as fixtures. Gate: the
emitter's lines parse with the same parser as the retail logs; NO validator,
NO canonical JSONL (rule §5, [[feedback-evidence-infrastructure-sink]]).
BLOCKED until the owner runs the capture session.
**Chunk 2 — exact PView state.** `WalkPView` (new, App/Rendering/Walk) owns:
the live view list per cell (retail `portal_view_type` lists), the
outside-view count, the view/update counts, the todo order, the draw list and
the two stamps. `_skyDrawnThisFrame` and every other proxy for
`outside_view.view_count != 0` is replaced by the real count. Per-category
leaf contract pinned: whole-once (built shell), Boolean sphere (ordinary
part), actual polygon clip (portal polygons only), repeated submission (local
player). Part identity pin: the same logical part reached from two cells
classifies once per stamp. Can start before the capture (decomp oracle);
the capture confirms.
**Chunk 3 — interleave.** Terrain becomes ordered landcell events: for each
admitted land cell in retail far-to-near order, `DrawLandCell` (that cell's
terrain range) then `DrawSortCell` (building pass 1 / look-in work / building
shell / land-cell object turn). Per-landcell terrain ranges come from the
existing terrain mesh layout (index ranges per landcell — verify
`TerrainModernRenderer` exposes them; if not, the chunk adds the range table
at bake/upload time, not a per-frame rebuild). The whole-stage terrain event
is deleted in chunk 4, not here (two-step so the pixel gate can bisect).
Gate: terrain pixels/mesh counts unchanged when no interleave is present;
dense-outdoor p50/p99 recorded before/after.
**Chunk 4 — deletions.** The whole-stage terrain event; the generic clip
rules on categories retail never clips (Vulkan clip slots removed from those
categories); any residual "first view clips the shell" code path. Gate: no
duplicate draw of a stamped part across a pan/zoom replay; membership/draw
count changes only at a retail view boundary.
## 5. Review (one Opus retail lens + one shader/perf lens at the S3 boundary)
- retail lens on every stamp/view loop branch and on the call order against
the capture transcript and the decomp;
- shader review of clip-space sign, y inversion, W plane, plane count and
scissor for the categories that KEEP a translated clip;
- performance review: the command-count increase from interleaving is
measured (dense outdoor p50/p99), not hidden by reordering.
Lead verifies every finding against the source before an edit (S2 precedent:
19 findings, 4 blocking, all real; see `s2-membership-ownership-map.md` §9).
## 6. Automated gate (S3 has no standalone owner gate; folds into G3)
- transcript equality with the §7 captures at the captured depth for all four
poses plus the FW0 still fixtures;
- tests distinguish whole-shell-once, Boolean part admission and actual
polygon clipping;
- pan/zoom replay changes membership/draw count only at a retail view boundary;
- no duplicate draw of a stamped part;
- terrain pixels/mesh counts unchanged when no interleave is present;
- self-gate route (`tools/overhaul-selfgate/`) at the three owner poses +
the four capture poses, pixel-diffed against the G2 candidate run
`logs/selfgate-20260903-010022-s2-review-fixes-3` — the per-region diff,
not the eye, is the verdict ([[feedback-deleted-fallback-carried-population]]).
## 6b. Capture session progress (2026-09-03)
- Recon PASSED against the live 2013 client (`logs/oh-capture/oh-recon.log`):
every template offset and breakpoint address matches; globals sane.
- **Pose `cathedral-leak` (extra, owner-chosen):** standing on the cathedral
stairs where the outside structure leaks into the interior — RETAIL SHOWS
THE ARTIFACT (2013 and EoR clients both). Transcript:
`logs/oh-capture/cathedral-leak.walk.log` (5 frames: DI `f4180108`, DC with
`ov=4`, EC/OC stamps, 531 `LC`/`SC` land-cell draws per frame in far-to-near
order over landblocks `f3``fe` × `00``18`), `cathedral-leak.parts.log`
(3 frames, 5,933 PD / 965 DM), `cathedral-leak.alphadepth.log` (5 frames,
1,407 AM / 2,856 FL / 28 PM / 16 PC). Owner's retail screenshot (2026-09-03, 2013 client, SAVED at
`logs/oh-capture/cathedral-leak.retail.png`, lead-verified): standing on the floating
stairs in `f4180108`, a large slab of the EXTERIOR wall (arched exterior
stone texture) cuts diagonally through the stair chamber from the upper
left, drawn over the interior landings and ramps that remain visible around
it — the same leak acdream shows at the same spot. This pose is S3/S4's
primary oracle: the target is to reproduce retail's frame at it, leak
included.
- **Pose `cathedral-arrival`** (owner "ready" 2026-09-03 06:58): transcript
`logs/oh-capture/cathedral-arrival.walk.log` (5 frames, 5,604 lines: main
PView `009d4f48` DI `f4180108`, DC `ov=3` n=2 `f4180108 f4180107`; a SECOND
PView `009d4530` with `ov=0` draws cells `f4180113/f4180112/f4180114` each
frame with no DI of its own — RESOLVED (§8 R1): it is `RenderDeviceD3D::outdoor_pview`'s
look-in `DrawCells`, driven by `PView::DrawPortal` from the building pass; 454
`LC` + 619 `SC` per frame, 40 EC / 40 OC total), `cathedral-arrival.parts.log`
(3 frames, 7,822 PD / 1,240 DM), `cathedral-arrival.alphadepth.log` (5 frames,
1,735 AM / 3,570 FL / 35 PM / 19 PC). NOTE: the kit README expected
`DI f4180106` (FW0's arrival still); the live client reported `f4180108`,
the stair-chamber cell — the owner's actual standing cell, recorded as-is.
- **Chunk-3 oracle read off the two cathedral transcripts (frame 2 of each,
viewer in `f4180108`):** after the root `DI`/`DC` and ONE `LS`, retail emits
`LC <cell>` then `SC <cell>` for every admitted land cell in STRICT alternation
(`LSLSLS…`, never two `LC` in a row) — `DrawLandCell` then `DrawSortCell` per
cell, exactly the per-cell interleave chunk 3 ports. Block order is far-to-near
from the far corner: rows `y=00…0d` each walked `x=fe→f6`, then from the
Chebyshev-10 ring inward the column and row interleave diagonally
(`fe0e fd0e fe0f fc0e fe10 fb0e fe11 …`), ending at `… f417 f318 f418`; the
exact rule is `LScape::calc_draw_order`'s and the transcript pins it. Far
blocks draw ONE cell (`xx000001`, side count 1); near the viewer `SC` fires for
all 64 cells of the block while `LC` fires only for the in-view ones (`f418`:
16 LC / 64 SC; `f417`: 56 / 64; `f318`: 3 / 64) — `DrawSortCell` runs under
`alwaysDrawObjects` for every cell of an in-view block, `DrawLandCell` only for
in-view cells (`RenderDeviceD3D::DrawBlock` @0x005a19d9 — chunk 3 re-reads the
exact gate). Per frame: leak pose 531 LC / 757 SC / 16 BLD; arrival pose
533 LC / 698 SC / 14 BLD.
- Remaining poses per the kit README: `holtburg-doorway-still`,
`terrace-edge`, `foundry-deep`.
## 7. Open questions for the capture session (ask for exactly these two)
1. `LScape::calc_draw_order` sequence at each of the four poses (far-to-near
land cell order) — chunk 3's oracle. Captured by the walk template's `LC`/`SC`
lines (added 2026-09-03: `RenderDeviceD3D::DrawLandCell` 0x0059f120 /
`DrawSortCell` 0x0059f140, one line per call with the cell's `m_DID`).
2. Per-frame `outside_view.view_count` and the device-stamp advance count at
each pose — chunk 2's oracle for the re-arm gate.
## 8. Chunk 2 contract — exact PView state (lead, 2026-09-03, written during the capture session)
Every claim below was re-read this session in the named pseudo-C and, where
BN's FPU rendering was ambiguous, in Ghidra (port 8081, `patchmem.gpr`, the
paired 2013 build). Addresses are the Sept 2013 build.
### 8.1 Retail facts (verified)
- **R1 — two PViews.** `RenderDeviceD3D::Init` @0x0059efb0 constructs
`indoor_pview = PView(…, 1)` and `outdoor_pview = PView(…, 0)`; the ctor
argument is `draw_landscape` (@0x005a52c1). `RenderDeviceD3D::DrawInside`
@0x0059f0d0 tail-calls `PView::DrawInside(indoor_pview, cell)`;
`RenderDeviceD3D::DrawPortal` @0x0059f0e0 pins `building_view = -1` around
`PView::DrawPortal(outdoor_pview, …)`. The captures' second `pv` with
`ov=0` (`009d50e0` at cathedral-leak, `009d4530` at cathedral-arrival,
cells `f4180112/13/14`, four `DrawCells` per frame, no `DI`) is
outdoor_pview's look-in flood through the cathedral's exterior portals.
acdream's `RetailFrameWalk._outdoorPView { DrawLandscape = false }` and
`WalkBuildingPortals.DrawPortal` already model this. PIN ONLY.
- **R2 — flood reset.** `PView::ConstructView(CEnvCell)` @0x005a57b0 sets
`outside_view.view_count = 0`, `master_timestamp++`, `cell_todo_num = 0`,
`cell_draw_num = 0` before `InitCell`. `WalkPView.ConstructView` matches.
PIN ONLY.
- **R3 — the `outside_view.view_count > 0` branch encloses the whole
landscape turn.** `PView::DrawCells` @0x005a4840: from 0x005a4852 to
0x005a49eb, ALL of `useSunlightSet(1)`, `PortalList = this`,
`LScape::draw`, `FlushAlphaList(0f)` @0x005a4872, `m_nFrameStamp += 1`
@0x005a4886, the gated Z clear @0x005a48930x005a48a9, and the exit-seal
loop @0x005a48c00x005a49eb (per draw-list cell far-to-near, per live view
`CEnvCell::setup_view` @0x005a4983, then `DrawPortalPolyInternal(portal_poly,
0)` @0x005a49b7 for every portal whose `other_cell_id == 0xffffffff`) sit
INSIDE `if (outside_view.view_count > 0)`. Only `useSunlightSet(0)`,
`restore_all_lighting`, the reverse shell loop @0x005a4a00 and the reverse
object-list loop @0x005a4ade are unconditional.
**DIVERGENCE TODAY:** `WalkFrameDriver.OnInteriorFloodDrawTurn` emits
`ClearInteriorDepth` and `ExitSeals` unconditionally and gates the stamp
advance on the `_skyDrawnThisFrame` proxy; its comment and the driver's
type doc claim "both unconditional for an interior root's own flood" — that
claim is wrong against the decomp, and the `WalkFrameDriverTests` pin that
reads "ov==0 … CLEAR/SEALS still fire unconditionally" pins the wrong
behavior. (The plan's S5 cleanup inventory already lists "unconditional
clear code"; chunk 2 retires it now.)
- **R4 — the Z clear is gated by a persistent seal counter.** The clear is
`if (forceClear != 0 || portalsDrawnCount != 0) Clear(Z)`, with
`D3DPolyRender::portalsDrawnCount` (uint16 @0x008719b4) read-then-zeroed
at that point (@0x005a489c0x005a489e). `forceClear` (@0x008ed824) is a
`.data` int with no write anywhere in the pseudo-C (its only references
are the two reads and its definition) → the clear fires iff
`portalsDrawnCount != 0`. The counter increments ONLY in
`DrawPortalPolyInternal` @0x0059bc90 at 0x0059bd74, and only when its
second argument is FALSE, after a degenerate-polygon guard (Ghidra: skip
when every vertex has `x == +12`, or every `x == 12`, or every `y ==
+12`, or every `y == 12` — an equality sentinel, NOT a ±12 clip box as
BN's FPU rendering suggests) and BEFORE `polyClipFinish` — it counts
calls, not surviving fans. False-argument callers: the exit seals
(@0x005a49b7, literal 0); pass-3 `ConstructView(CBldPortal)` punches
(@0x005a5a7b, `arg5 == 1` → false for pass 3); `PView::DrawPortal`'s
`arg4 == 3` failure branch (@0x005a5b7c). But `DrawMeshInternal` runs
buildings through passes 1 and 2 only (@0x0059f3cc / @0x0059f3d9), and
pass 1's punch passes TRUE (`maxZ1`, no increment) — so in the building
path the counter is fed by EXIT SEALS ALONE. Consequence, a retail quirk
to port as-is: the first `ov>0` interior frame after a period without
seals draws NO Z clear; every later `ov>0` frame clears because the
previous frame's seals armed the counter. Outdoor frames and look-in
`DrawCells` (`ov=0`) neither arm nor consume it.
- **R5 — punch/seal depth state.** `SetDepthBufferMode(DEPTHTEST_ALWAYS,
(maxZ >> 2) & 1)` with `maxZ1 = 7` (argument true: building pass 1) and
`maxZ2 = 6` (argument false: seals, pass 3); both write depth. S4's
terrain-punch question is untouched by chunk 2.
- **R6 — part identity.** Retail stamps the `CPhysicsPart` (+0xdc against the
device's +0xb0). After S2, one logical part reaches the classifier from
every cell it is in with the SAME projection id, so `(RenderProjectionId,
PartIndex)` is the identity. PIN ONLY.
### 8.2 Production behavior change
- **B1 — the real count gates the whole turn.** `RetailFrameWalk.DrawInside`
passes `_interiorPView.OutsideView.ViewCount` to
`IWalkEventSink.OnInteriorFloodDrawTurn(cells, outsideViewCount)`. The
driver then executes retail's branch exactly: `outsideViewCount > 0` →
the landscape flush leaf (retail `FlushAlphaList(0f)` @0x005a4872 plus the
pre-clear dynamics hook that `RetailPViewRenderer.ClearWalkInteriorDepth`
bundles today), the stamp advance (`AdvanceWalkPartPassStamp` + the shell
and particle-turn re-arms), then `ClearInteriorDepth` ONLY IF
`PortalsDrawnCount != 0` (read-then-zero, R4), then `ExitSeals`;
`outsideViewCount == 0` → none of them. `_skyDrawnThisFrame` is deleted;
its "second landscape turn" fail-loud guard is kept on a frame-scoped
count field, its "landscape ran" use is replaced by the count the sink
receives.
- **B2 — the counter.** The driver owns `PortalsDrawnCount` (retail
`D3DPolyRender::portalsDrawnCount`), retained ACROSS frames (the driver is
retained by `RetailPViewRenderer`; never cleared by `BeginFrame`/
`AbortFrame`/`EndFrame`), incremented at the exit-seal turn by the number of
seal polygons actually submitted: for each flood cell (far-to-near) × each
live view slice × each portal with `OtherCellId == 0xFFFF` and ≥3
vertices — the same enumeration `RetailPViewPassExecutor.DrawPortalDepthWrite`
performs. Implement it as a return value from the seal leaf
(`DrawExitSeals` returns the submitted count) so the count and the draws
come from ONE enumeration; the driver adds it. Never incremented by punch
fans. Not touched by look-in `DrawCells`.
- **B3 — leaf split.** `RetailPViewRenderer.ClearWalkInteriorDepth` splits into
`FlushWalkLandscape` (pre-clear dynamics + `FlushLandscapeAlpha`) and
`ClearWalkInteriorDepth` (the Z clear only); `IWalkFrameLeafRenderer` gains
the flush leaf and `DrawExitSeals` returns `int`; `WalkFrameEventKind`
gains `LandscapeFlush`. The fake leaf in `WalkFrameDriverTests` logs it as
`"LFLUSH"`.
- **B4 — no new owner class.** The packet's "`WalkPView` (new)" is the
EXISTING `WalkPView`; chunk 2 adds no class. The per-category leaf
contract (whole-once built shell; Boolean sphere for ordinary parts;
polygon clip for portal polygons only; repeated submission for the local
player) is pinned by tests — the implementer first LISTS which of these
four pins already exist (`WalkFrameDriverTests`, `WbDrawDispatcher`
WalkClassify tests, `WalkVisibilityMath` pins) and adds ONLY the missing
ones, naming the retail address in each test's comment.
### 8.3 Tests (flip, add, keep)
- T1 (flip): the `ov==0` pin expects NO `LFLUSH`/`CLEAR`/`SEALS` and no stamp
advance before the flood — the opposite of today's expectation.
- T2 (add): `ov>0` first frame → `LFLUSH`, NO `CLEAR`, `SEALS`, flood; the next
frame of the same driver → `LFLUSH`, `CLEAR`, `SEALS`, flood (armed by frame
1's seals); a driver whose flood cells have no exit portal never clears.
- T3 (add): a look-in `DrawCells` (ov=0, `LookInStatic` stage) neither arms
nor consumes the counter.
- T4 (add if absent): R1 (two pviews, the outdoor one never raises an
outside view) and R2 (the reset) pins.
- T5 (update): the existing direct `OnInteriorFloodDrawTurn` callers
(`WalkFrameDriverTests` ≈ lines 349, 602610, 642, 693703) pass the count
they model; the two "look-in shell repaints after the clear" tests model
`ov>0`: one asserts the first-frame no-clear quirk explicitly, the other
pre-arms the counter (a prior seal turn) and asserts the clear — both
behaviors stay pinned.
- Keep: every other driver/walk pin unchanged; `LaunchOptionsDocumentationTests`
is unaffected (no flag added or removed).
### 8.4 Out of scope for chunk 2
Terrain interleave (chunk 3); clip-slot deletions and the
`ProbeCathedral*` discriminators (chunk 4 / the S5 inventory); S4's punch
z-func; the pre-clear dynamics hook's retail home (`LScape::draw`'s per-cell
object turn — chunk 3 places it).
### 8.5 Lead verification and gate
Every R-fact re-read at its address before the commit; the App hermetic
lane plus the InstalledDat lane green; the three-pose self-gate plus the two
captured cathedral poses pixel-diffed against
`logs/selfgate-20260903-062615-s2-chunk6` — the expected delta is zero at
steady state (the only retail-different frame is the first `ov>0` frame after
a no-seal period, which no still captures); the walk transcript at
cathedral-arrival must still show the root `DC ov=3` and four look-in
`DC ov=0` turns per frame.
## 9. Chunk 3 contract — the landscape interleave (lead, 2026-09-03)
Written during the capture session from the two cathedral transcripts and
the decomp; every fact below is verified at the cited address or against
the captured sequence itself. Chunk 3 implements AFTER chunk 2 lands (it
edits the same driver).
### 9.1 Retail facts (verified)
- **R1 — the per-cell interleave.** `RenderDeviceD3D::DrawBlock` @0x005a17c0
runs two loops over the block's `draw_array` (far-to-near): loop 1
(@0x005a1876) `IsInView && num_shadow_objects` → `UpdateObjCell` +
`CShadowPart::insertion_sort`; loop 2 (@0x005a197d) `if (IsInView)
DrawLandCell(cell)` @0x005a19c0, then `if (alwaysDrawObjects != 0 ||
IsInView) DrawSortCell(cell)` @0x005a19e6 followed by
`FlushAlphaList(flush)` only when `flush > 1`. Retail defaults:
`alwaysDrawObjects = 1` (@0x00820ed4), `flush = 0.75f` (@0x00820ed0) —
so `DrawSortCell` runs for EVERY cell of an in-view block, `DrawLandCell`
only for in-view cells, and the per-cell alpha flush is inert. Transcript
(frame 2 of both captures): one `LS`, then `LC`/`SC` in strict
alternation, never two `LC` in a row; 531 LC / 757 SC (leak), 533 LC / 698
SC (arrival); `SC` = 64 per near block, `LC` = only the in-view cells.
- **R2 — the order port is oracle-exact.** `LandWalkOrder.GetBlockOrder`
(`LScape::get_block_order` @0x00504c50, walked backwards) and
`FillCellOrderFarToNear` + `ClosestCell` (`CLandBlock::calc_sq_draw_order`
@0x0052f4a0; `CLandBlock::calc_draw_order` @0x00530300 is a tail call to
it) reproduce the captured `LC` AND `SC` sequences for all 260 (arrival) /
256 (leak) blocks with ZERO mismatches, given two facts the check needed:
(i) the captured blocks span Chebyshev ring 24, so the grid radius is ≥ 24
— `WalkLandscapeAssembler.MidRadius = 25` is consistent; (ii) LOD cell ids
are numbered `x*8 + y + 1` in LOD coordinates (a side-2 block's cells are
`1, 2, 9, 10`), i.e. retail's `lcell[x*side+y].m_DID`. LOD by ring from the
captures: ring 01 → side 8, ring 2 → 4, ring 34 → 2, ring ≥ 5 → 1 —
exactly `WalkLandscapeAssembler.SideCellCountForRing`. The FW-era
`IWalkEventSink.OnLandscapeCellTurn(landblockId, side, cellIndex)` already
expands a coarse cell into its covered 8×8 owner buckets.
- **R3 — one land cell's terrain is arithmetic on the existing mesh.**
`DrawLandCell` @0x0059f120 draws one land cell at the block's LOD.
acdream's terrain mesh is 8×8 cells per landblock, cell-major
(`LandblockMesh.Build`: `cy` outer, `cx` inner, `indices[i] = i`, 6
indices per cell), uploaded per slot with `FirstIndex = slot * 384`
(`TerrainModernRenderer.IndicesPerLandblock == 384`). acdream cell
`(cx, cy)` is the contiguous run `[(cy*8 + cx)*6, +6)` inside the slot; a
retail LOD cell (side `n`, LOD coords `(X, Y)`) covers `cx ∈ [X*8/n,
(X+1)*8/n)`, `cy ∈ [Y*8/n, (Y+1)*8/n)` → `8/n` runs of `(8/n)*6`
contiguous indices (side 8: one run of 6; side 4: two runs of 12; side 2:
four runs of 24; side 1: one run of 384). No range table is baked; no
per-frame rebuild.
- **R4 — why the interleave is a behavior change (the S4 hypothesis).** The
far-Z punch (`DrawPortalPolyInternal`, `DEPTHTEST_ALWAYS` + write) runs at
the BUILDING's cell turn (`DrawSortCell` → `DrawBuilding`). In retail
every NEARER land cell's terrain is drawn AFTER that punch and overwrites
the punched depth, so a doorway behind a hill cannot show its interior
through the hill. acdream draws ALL terrain first (`TerrainSlice(0)` at the
landscape turn) and every building afterwards, so a doorway behind a hill
punches through it — the doorway-sized house fragments through the
Holtburg hill in the owner's G2 screenshot. Chunk 3 is expected to remove
that artifact by ORDER alone; the Holtburg self-gate pose is the verdict.
If it does, S4's "exact z-func" question closes with no z-func change
(ledger + S4 row note). This supersedes the packet's §4 "two-step
bisect" wording: the whole-stage terrain event is replaced in THIS chunk
(drawing terrain twice would already hide the punch and make the gate
meaningless), and chunk 4 loses that deletion.
- **R5 — the sky draws TWICE, and the second pass is the WEATHER.**
`LScape::draw` @0x00506330 calls `GameSky::Draw(sky, 0)` @0x0050633c BEFORE
`draw_check_blocks`, walks the blocks (`DrawBlock` @0x00506374), then calls
`GameSky::Draw(sky, 1)` @0x00506396 AFTER every block. `GameSky::Draw`
@0x00506ff0's `arg2 == 1` is the weather pass, gated on
`SmartBox::is_player_outside` @0x00451e80 (already Ghidra-arbitrated and
modeled by `RetailPViewPassExecutor.DrawLandscapeSliceLate`'s `RenderWeather`
call — see that method's comment). Chunk 3 keeps the sky at the landscape
turn's start and places the weather pass AFTER the last land cell of the
turn (retail order); the implementer confirms where the late landscape
slice runs relative to the new per-cell events and moves the weather call
only if it currently runs before them.
### 9.2 Production behavior change
- **B1 — per-cell land-cell events.** `RetailFrameWalk.DrawLandscape` emits,
per `DrawArray` cell in retail order: `sink.OnLandCellTurn(landblockId,
side, cellIndex)` when `CellInView[cellIndex] != Outside` (R1's
`DrawLandCell` gate), THEN the existing `DrawBuilding` + `OnLandscapeCellTurn`
(the `DrawSortCell` half, gated by `AlwaysDrawObjects || in view` as today).
The driver records a `LandCell` frame event (slot landblock id, LOD side,
LOD cell index, plus the retail LOD cell id `x*8+y+1` for the chunk-1
transcript) at that point in the stream, and DELETES the whole-stage
`TerrainSlice(0)` emission and the `DrawTerrainSlice` leaf. The leaf
`DrawLandCell` submits the R3 index runs through a new
`TerrainModernRenderer.DrawCellRuns(landblockId, side, cellIndex)` (or a
batched form, B2) with the same pipeline/state the whole-block draw uses,
UNCLIPPED for interior roots exactly as the FW4-slice-6 comment demands
(retail never view-clips terrain; the exit seals and the interior repaint
own aperture exactness).
- **B2 — order-preserving batching.** Consecutive `LandCell` events of the
SAME slot with no intervening non-terrain event merge into ONE indirect
draw whose command list is the concatenated runs in event order; any
intervening event (building alpha barrier, punch, shell, statics mark)
splits the batch. Pixel-identical by construction; this is the only
concession to the ~530700 land cells per frame.
- **B3 — `TerrainModernRenderer.Draw(...)`** keeps serving non-walk callers
(directional-shadow receivers etc.); the walk path stops calling it. The
`_walkTerrainInViewLandcells` plumbing that only fed the walk draw is
deleted if nothing else reads it (grep; if a shadow pass reads it, leave it
and say so).
- **B4 — no new class beyond the leaf/renderer entry points, no flag, no
probe.** The existing `OnLandscapeCellTurn` LOD expansion is unchanged.
### 9.3 Tests
- T1 (order pin, R2): `LandWalkOrder` + `WalkLandscape.CalcDrawOrder` +
`SideCellCountForRing` reproduce frame 2's `LC` and `SC` sequences of BOTH
captures. Fixture = the parsed cell-id sequences (reuse the existing
test-side reader `tests/AcDream.App.Tests/Rendering/Walk/WalkOracleTrace.cs`
for the `LC`/`SC` line kinds; no new evidence infrastructure). Viewer:
block `f4/18`, sq cell `(1, 1)` (from the `P` line: x 36.6, y 24.0).
- T2 (interleave, R1/B1): a two-block synthetic landscape with one building
and one out-of-view cell: events are `SKY, LC(far)…, [LC, BLD…, statics]…`
with each cell's `LC` before its own building/statics, cells far-to-near;
the out-of-view cell gets NO `LC` but keeps its sort turn under
`AlwaysDrawObjects`; no `TerrainSlice` event exists any more.
- T3 (runs, R3): for side ∈ {1, 2, 4, 8} and every LOD cell, the runs cover
exactly the covered 8×8 cells' index ranges, disjoint, total `(8/n)²·6`.
- T4 (batching, B2): two consecutive same-slot `LC` merge into one draw;
an intervening building barrier splits; order inside the batch is the
event order.
- T5: existing `WalkFrameDriverTests` / renderer pins on `TERRAIN:0` updated
to the per-cell events; every other pin unchanged.
### 9.4 Out of scope
Clip-slot deletions (chunk 4); any depth-compare change to the punch (S4 —
but the Holtburg verdict is recorded and S4 re-scopes if the punch leak is
gone); the pre-clear dynamics hook's home (chunk 4).
### 9.5 Lead verification and gate
R-facts re-read at their addresses; the App hermetic + InstalledDat lanes
green; the three-pose self-gate plus the Holtburg terrain-leak pose (the
owner's G2 screenshot spot) plus the two cathedral poses, pixel-diffed
against the chunk-2 run: the ONLY expected delta is the punch region at
Holtburg (fragments gone); dense-outdoor p50/p99 recorded before/after with
the existing frame-time diagnostics (no new counters).
## 10. Chunk 4 contract — deletions (lead draft, 2026-09-03; re-locate every owner at chunk time, after chunks 2 and 3 land)
### 10.1 What retail clips, and what it does not (verified)
- Retail polygon-clips exactly three things on the CPU with
`ACRender::polyClipFinish`: portal polygons when building child views
(`PView::GetClip` @0x005a4320 → `polyClipFinish`), the punch fans
(`DrawPortalPolyInternal` @0x0059bc90 clips the fan against the installed
view — `building_view` @0x0059f3bf during the building pass), and the
exit seals (the same function under `CEnvCell::setup_view` per live view,
@0x005a49830x005a49b7). Ordinary meshes, cell shells, terrain, particles
and the weather are NEVER view-clipped: `DrawMeshInternal` @0x0059f360
submits the whole mesh after the sphere test, `DrawEnvCell` @0x0059f170
draws the whole shell, `LScape::draw` draws whole blocks, and
`GameSky::Draw(sky, 1)` @0x00506396 runs ONCE after the blocks with only
the `is_player_outside` gate (§9 R5).
### 10.2 acdream inventory (at `edb32783`; names, not lines)
KEEP (translated clips with a retail CPU counterpart — each gets a
CPU/GPU equivalence pin if it lacks one):
- exit seals: `RetailPViewRenderer.DrawWalkExitPortalMasks` →
`DrawExitPortalMask(cell, InteriorFloodViewClipPlanesAt(...))` per (flood
cell, live view) — retail's `setup_view` + `polyClipFinish` per view;
- punch fans: `DrawPunchFan(worldPolygon, activeViewIndex)` clipped by the
pinned view's slice planes — retail's `building_view` clip;
- the walk's `CaptureViews`/`AppendClipSlot` slot table ONLY as far as the
two consumers above need it.
DELETE (no retail counterpart):
- the whole-stage terrain event and `DrawTerrainSlice` leaf — already
replaced by chunk 3 (§9 R4); chunk 4 verifies nothing re-grew;
- the per-outside-view-slice weather/late landscape loop
(`RetailPViewRenderer` "GameSky's weather pass still runs through each
active landscape view": `SetTerrainClip` + `ClearClipRouting` +
`DrawLandscapeSliceLate` per slice with `BeginDoorwayScissor` +
`BindTerrainClip`) — retail draws the weather ONCE, unclipped; one
weather draw after the last land cell (chunk 3 places it; chunk 4 deletes
the loop, the scissor, and the terrain-clip binding if no consumer
remains);
- the sky-post-scene particle submission's per-slice `clipSlot:
slice.Slot` — retail inserts emitters into one unclipped alpha list;
- `RetailPViewPassExecutor.SetCellShellClipRouting` (no caller in src or
tests) and every `SetClipRouting`/`ClearClipRouting`/
`UseIndoorMembershipOnlyRouting` call that becomes a no-op once no shell
or entity clip routing exists — the "first view clips the shell" residue;
- `WbDrawDispatcher.WalkClassify.ResolvePartClipSlots`'s slot LIST return
shape: it already answers "whole mesh" (`RetailWholeMeshSlot`) whenever
any view admits the sphere; reduce it to the Boolean retail asks
(`SphereVisibleInLookInTurn`) and delete `VisibleClipSlotsInLookInTurn`
if the seals/punches do not use it (they read planes, not slots — verify);
- the `ProbeCathedralSkip*` discriminators (`ProbeCathedralSkipFloatingStairSeals`,
`…StairBuildingPunch`, `…FloatingStairCellShells`, `…SouthLookInCellShells`,
`…LookInShellCellId`, `…SouthBuildingShell`, `ProbeCathedralShellOrderEnabled`)
with their `docs/launch-options.md` rows and `RenderingDiagnostics`
members — the investigation they served is closed by the retail axiom
(the cathedral leak is retail behavior); the S5 inventory already lists
them, chunk 4 takes them because it touches every one of their call
sites anyway.
### 10.3 Gate
- no duplicate draw of a stamped part across a pan/zoom replay (the
existing replay pins); membership/draw counts change only at a retail view
boundary;
- the three-pose self-gate + Holtburg + the two cathedral poses pixel-diffed
against the chunk-3 run: expected delta ZERO except where a deleted
per-slice scissor previously under-painted the weather at an exit-view
edge (record the region if it appears; it is the deletion's intended
effect, not a regression);
- `LaunchOptionsDocumentationTests` green with the seven probe rows and
their read sites removed together (the test enforces both directions);
- the register: no row is added (every deletion removes an acdream-only
rule); any KEEP item still lacking a CPU/GPU equivalence pin gets one.