docs(render): S3 chunk 2 contract -- exact PView state
Decomp-verified during the capture session: PView::DrawCells 0x005a4840 keeps the landscape flush, stamp advance, gated Z clear and exit seals inside outside_view.view_count > 0; the clear is gated by the persistent D3DPolyRender::portalsDrawnCount (0x008719b4), which only exit seals feed in the building path (pass 1 punches pass TRUE; forceClear is never written). acdream's unconditional clear/seals and the _skyDrawnThisFrame proxy are the divergence chunk 2 retires. The captures' second PView is outdoor_pview's look-in flood (RenderDeviceD3D::Init 0x0059efb0). Also: the cathedral floating-stairs scene row now expects retail's exterior-slab leak (retail axiom), the retail screenshot is saved at logs/oh-capture/cathedral-leak.retail.png, and the S3 ledger row moves to chunk 2 in progress. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
c1b24ec0c0
commit
5c64679b11
2 changed files with 159 additions and 5 deletions
File diff suppressed because one or more lines are too long
|
|
@ -145,8 +145,8 @@ Lead verifies every finding against the source before an edit (S2 precedent:
|
|||
`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, to be saved as
|
||||
`logs/oh-capture/cathedral-leak.retail.png`): standing on the floating
|
||||
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
|
||||
|
|
@ -157,7 +157,8 @@ Lead verifies every finding against the source before an edit (S2 precedent:
|
|||
`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 — its identity is a chunk-2 audit item; 454
|
||||
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
|
||||
|
|
@ -174,3 +175,156 @@ Lead verifies every finding against the source before an edit (S2 precedent:
|
|||
`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 @0x005a4893–0x005a48a9, and the exit-seal
|
||||
loop @0x005a48c0–0x005a49eb (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 (@0x005a489c–0x005a489e). `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, 602–610, 642, 693–703) 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.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue