docs(render): S3 chunk 3 contract -- the landscape interleave
Verified against the two cathedral transcripts and the decomp: DrawBlock 0x005a17c0 draws LandCell then SortCell per land cell (alwaysDrawObjects=1, flush=0.75 makes the per-cell alpha flush inert); the ported ring order, closest-cell table and ring-to-LOD mapping reproduce every captured LC/SC sequence at both poses with zero mismatches once LOD cell ids are read as x*8+y+1 in LOD coordinates; one land cell's terrain is arithmetic on the existing cell-major 384-index landblock mesh. The interleave is expected to remove the Holtburg terrain-punch leak by draw order alone (S4 hypothesis); LScape::draw draws the sky twice (before and after the blocks). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
fa217bc6c7
commit
296bfd8d48
1 changed files with 141 additions and 0 deletions
|
|
@ -344,3 +344,144 @@ 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 0–1 → side 8, ring 2 → 4, ring 3–4 → 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.** `LScape::draw` @0x00506330 calls
|
||||
`GameSky::Draw(sky, 0)` @0x0050633c BEFORE `draw_check_blocks`, walks the
|
||||
blocks (`DrawBlock` @0x00506374), and then calls `GameSky::Draw(sky, 1)`
|
||||
@0x00506396 AFTER every block. acdream emits one `Sky()` event at the
|
||||
landscape turn's start. The implementer reads `GameSky::Draw` (grep it)
|
||||
and reports what the second pass (`arg2 == 1`) draws; if it is a real
|
||||
post-terrain layer (horizon band, celestial sprites, the far fog dome),
|
||||
chunk 3 adds the second sky event after the last land cell; if it is
|
||||
empty at retail defaults, the contract records why with the address. Do
|
||||
not guess — read it.
|
||||
|
||||
### 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 ~530–700 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).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue