The FW3 visual gate's stairwell/grass transition flash (grass briefly covering floor openings at doorway crossings - the #119 family) was the FW3 dual path leaking: the walk decided WHETHER terrain draws while the old PortalVisibilityBuilder assembly decided WHERE (slice planes, count, scissor), and punch fans indexed the old slice array with walk view indices. The new ACDREAM_PROBE_WALK_ROOT apparatus pinned the boundary frames: fat/degenerate old-apparatus exit views splash terrain over interior pixels, the interior depth-clear preserves color, and cells absent from the walk's flood never repaint. Retail has ONE visibility structure and cannot produce this. ClipFrameAssembler.ReassembleOutsideViewFromWalk now materializes the walk's own outside_view (pixel screen points -> standard NDC -> the existing ClipPlaneSet machinery) into the assembly's outside-view block after Collect, ahead of the single PrepareClipFrame publication (moved below the walk block). The Landscape event carries the walk's active view count on the record's existing OutsideViewCount field (trace mapping compares kind only - zero oracle-fixture churn) and the driver fans exactly that many terrain slices; activeTerrainSliceCount is deleted end to end. Outdoor roots keep the assembler's single full-screen slice, asserted ==1. Hermetic 6,762/0 (4 new materializer tests pin the y-flip and plane-sign conventions), Walk lane 209/1, InstalledDat walk conformance 40/1. Seals/cell slices/look-in seeding stay on the old per-cell views for the rest of FW4 (identical dat polygons; only the visible set can differ). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
583 lines
33 KiB
Markdown
583 lines
33 KiB
Markdown
# Campaign FW — the retail frame walk (implementation plan)
|
||
|
||
**Status:** ACTIVE (opened 2026-08-30; spec approved by owner 2026-08-30).
|
||
**Spec (approved):**
|
||
[`docs/superpowers/specs/2026-08-30-campaign-w-retail-frame-walk-design.md`](../superpowers/specs/2026-08-30-campaign-w-retail-frame-walk-design.md)
|
||
— note: the campaign letter is **FW** (frame walk); the spec predates the
|
||
rename (the roadmap already owns "Phase W" for the historical Unified Cell
|
||
Graph program).
|
||
**Branch:** `claude/campaign-w-retail-frame-walk` off main `e102fb36`.
|
||
**Predecessor:** Campaign PV, halted 2026-08-30
|
||
(`docs/plans/2026-08-30-pview-visibility-campaign.md`); its back half is
|
||
quarantined on `claude/cathedral-rendering-debug-47a417` and nothing merges
|
||
from it.
|
||
|
||
**One-sentence goal:** retail owns every frame decision (rooting, portal
|
||
views, cone gating, landscape walk, buildings, stage order, depth
|
||
semantics, degrade, particles, camera); modern code owns only pixel-neutral
|
||
GPU mechanics; the ~4.7K-line accumulated visibility layer and its patch
|
||
apparatus are deleted at their stages.
|
||
|
||
---
|
||
|
||
## Binding rules (all stages, binding on subagents)
|
||
|
||
1. **The ownership line is the campaign invariant.** If a modern component
|
||
makes a decision retail did not make, that is a campaign bug regardless
|
||
of pixels. Every ported function cites its named retail symbol.
|
||
2. **Grep named first.** `docs/research/named-retail/acclient_2013_pseudo_c.txt`
|
||
by `class::method` before any fresh decompilation; pseudocode notes in
|
||
`docs/research/` before C# (the mandatory workflow).
|
||
3. **Fail loud, never silently drop.** The PV3 post-mortem rule: any
|
||
admission/seeding/ordering filter that rejects work must log the
|
||
rejection under a diagnostic flag or fall open — silent drops produced
|
||
this week's hollow-world regressions.
|
||
4. **The transition checklist is a standing self-run gate.** Every
|
||
candidate build gets, before the owner sees it: portal-in arrival
|
||
frames, a Holtburg house exit, a teleport-to-Holtburg stopwatch, and a
|
||
steady-state fps read (drive.ps1 + screenshots). No terrace-only
|
||
verification, ever again.
|
||
5. **No long-lived dual path.** Campaign V precedent: a stage that cuts
|
||
over deletes the code it replaces in the same stage. Short-lived
|
||
scaffolding inside one stage is fine; a toggle that survives a stage
|
||
boundary is not.
|
||
6. **Register discipline.** Deviations add their row in the same commit;
|
||
ported mechanisms delete rows in the same commit
|
||
(`docs/architecture/retail-divergence-register.md`).
|
||
7. **Suites green at every stage close** (hermetic filter
|
||
`Lane!=InstalledDat&Lane!=Linux&Lane!=Manual&Lane!=Timing&Lane!=Live&Lane!=PreparedPackage`),
|
||
plus the stage's own gates. Sonnet implements; Opus reviews stage
|
||
boundaries (architectural + retail-faithfulness lenses).
|
||
|
||
## Verified seam map (recon 2026-08-30; cite these, do not re-derive)
|
||
|
||
**Frame chain today:** `GameWindow.OnRender` (GameWindow.cs:1785/1809) →
|
||
`GameFrameGraphSlot.Render` → `RenderFrameOrchestrator.Render`
|
||
(RenderFrameOrchestrator.cs:206/226) → `VulkanWorldScenePhase`
|
||
(VulkanCompositionFramePhases.cs:113, owns the backbuffer pass) →
|
||
`WorldSceneRenderer.Render` (WorldSceneRenderer.cs:128) →
|
||
`RetailPViewRenderer.DrawInside` (RetailPViewRenderer.cs:112), which builds
|
||
`PortalVisibilityFrame` internally (line 122) and drives
|
||
`RetailPViewPassExecutor` through the packed route contract
|
||
(`RenderFrameCandidateRoute`: LandscapeOutdoorStatic → LandscapeBuildingShell
|
||
→ LookInObject → LandscapeOutsideDynamic → CellStatic → DynamicLast;
|
||
`WbDrawDispatcher.PackedOracle.cs:108/171` enforces in-order consumption).
|
||
|
||
**What the walk-order submitter REPLACES:** `GroupKey` material bucketing
|
||
(`ClassifyBatches` WbDrawDispatcher.cs:3292, `ClassifyPackedBatches`
|
||
PackedOracle.cs:610, `_groups`/`_packedGroups`), `PartitionInstanceGroups`
|
||
(:2232), and the two distance sorts (`CompareOpaqueSubmissionOrder` :2865,
|
||
`CompareTransparentSubmissionOrder` :2871). Note: the deferred-alpha path
|
||
(`DeferTransparentGroups` :2619 → `RetailAlphaQueue`) already reconstructs
|
||
draw-local order per instance — the transparent side is half-way to
|
||
walk-order today and is the SSBO-layout pattern to follow.
|
||
|
||
**What is REUSED unchanged:** `GlobalMeshBuffer` arena (GlobalMeshBuffer.cs:84),
|
||
`WbMeshAdapter`/`ObjectMeshManager` mesh prep + self-heal
|
||
(WbMeshAdapter.cs:328/435), `TextureCache` GpuTextureSlot resolution
|
||
(WbDrawDispatcher.cs:3421), the ring-section SSBO writers + shared
|
||
`WorldTransformFrameArena` (Rhi.cs:913/945), `MeshPipelineSet` (Rhi.cs:49),
|
||
and the terminal MDI recorder `DrawIndirectRangeRhi` (Rhi.cs:798).
|
||
|
||
**Caveats that shaped the stages:** three parallel classifiers exist
|
||
(classic, packed, directional-shadow — WbDrawDispatcher.DirectionalShadows.cs:1021)
|
||
and must agree until each is cut over; `EntityClassificationCache` and
|
||
`PackedProjectionClassificationCache` assume group-bucketed submission and
|
||
retire with their routes; `StageImmediateGroup` lays per-instance SSBOs by
|
||
group-order cursor, so the walk submitter re-derives layout from stream
|
||
order (start per-instance like deferred alpha; merge adjacent runs after
|
||
correctness).
|
||
|
||
**Visibility consumers to re-point at the walk output** (the complete
|
||
production list — radar, audio, and picking do NOT consume visibility):
|
||
- Particles: `WorldSceneRenderer.cs:263` → `ParticleVisibilityController`
|
||
→ `ParticleSystem.ApplyRetailView` (Core/Vfx/ParticleSystem.cs:313), fed
|
||
outdoors by `TerrainModernRenderer.VisibleCellIds`.
|
||
- Lights: `WorldSceneRenderer.cs:264` → `RuntimeWorldFrameEnvironmentPreparation`
|
||
(WorldRenderFrameBuilder.cs:543) → `LightManager.BuildPointLightSnapshot`
|
||
(Core/Lighting/LightManager.cs:284).
|
||
- EnvCell prepare filter: `RetailPViewPassExecutor.cs:276` →
|
||
`EnvCellRenderer.PrepareRenderBatches` (EnvCellRenderer.cs:456).
|
||
- Entity routes: `RenderScenePViewFrameProduct.cs:1330-1587`.
|
||
- Gated diagnostics only beyond that.
|
||
|
||
**Streaming atomicity (what already holds vs what FW adds):** per-cell
|
||
publication is ALREADY atomic — one dictionary swap of a complete
|
||
immutable `EnvCellLandblock` (EnvCellRenderer.CommitPublication:347);
|
||
outdoor terrain commits at LandblockRenderPublisher.AdvanceBeginOne:267.
|
||
The real partial states are (a) landblock-stage terrain-before-interiors
|
||
across yielded publication stages (LandblockPresentationPipeline.cs:711/757
|
||
— deliberate walking pop-in, hidden by the reveal hold during
|
||
portal/login), and (b) committed cells drawing before every contained
|
||
static mesh is resident (skip-if-missing, EnvCellRenderer.cs:720). FW keeps
|
||
both as EXPLICIT, register-rowed adaptations; the walk consumes only the
|
||
committed registry (`CellVisibility.TryGetCell` — production role is the
|
||
registry; its BFS is already dead code).
|
||
|
||
**The patch apparatus to delete (exact sites):**
|
||
- `ShellDrawLiftZ` +0.02 lift, three consumers: PortalVisibilityBuilder.cs:353
|
||
(const + Build's drawLiftZ), RetailPViewPassExecutor.cs:697 (depth fans),
|
||
LandblockBuildFactory.cs:523 (baked into draw cellTransform).
|
||
- Punch stencils (#117 adaptation): PortalDepthMaskRenderer.Rhi.cs:59-125.
|
||
- Look-in seeding heuristics: PortalVisibilityBuilder.BuildFromExterior:713-923
|
||
+ callers MergeNearbyBuildingFloods (RetailPViewRenderer.cs:411),
|
||
BuildInteriorRootLookIns (:471), DrawBuildingLookIns (:600-860), and the
|
||
barrier math (:515-559).
|
||
- The InViewCells side-channel (RetailPViewRenderer.cs:2187) once consumers
|
||
read the walk output.
|
||
|
||
**Camera facts:** `RetailChaseCamera.cs` — `Distance` default 2.61 m,
|
||
clamps DistanceMin=2/DistanceMax=40 (:110-113); collision probe + slope
|
||
align + chase already default-on retail behaviors
|
||
(Core/Rendering/CameraDiagnostics.cs:28-50). Retail envelope (from the
|
||
d71f5ef1 diff, which lives ONLY on the quarantined branch — this branch has
|
||
no AD-116 row): `CameraSet::Farther` @0x00458890 scales the viewer offset
|
||
multiplicatively and REFUSES the write once |offset.y| ≥ 10 (gates |x|<10,
|
||
|y|<10, z<450, z>-1.8); the standard arc saturates near offset
|
||
(0, −10, +8.33) ≈ a 13.0 m boom. `CameraSet::Raise` @0x00457980 rotates at
|
||
preserved length. TS-56 records the non-retail adjustment scalars
|
||
(CameraAdjustmentSpeed=40/s etc.).
|
||
|
||
**Retail anchors already established:** `SmartBox::RenderNormalMode`
|
||
@0x00453aa0 (frame rooting), `PView::DrawCells` @0x005A4840,
|
||
`LScape::draw` @0x00506330, `LScape::grab_visible_cells` @0x00504EC0,
|
||
`DrawBuilding` pre-punch flush @0x0059F2A0, `ShouldDrawParticles`
|
||
@0x0050FE60, the untextured skip in `D3DPolyRender::DrawMesh` @0x0059d4a0,
|
||
`ACRender::polyClipFinish` (two call sites: `PView::GetClip` + debug),
|
||
`Render::viewconeCheck`.
|
||
|
||
---
|
||
|
||
## Stages
|
||
|
||
### FW0 — the walk oracle (tooling + fixtures)
|
||
|
||
**STATUS: COMPLETE 2026-08-30.** Ten trace fixtures + README
|
||
(`docs/research/2026-08-30-fw-walk-oracle/`, commit `71b11817`), the
|
||
replay helper + 19 fixture goldens (`9f4c0f95`), and the port-ready
|
||
pseudocode model + full decomp appendix
|
||
(`docs/research/2026-08-30-fw-walk-pseudocode.md`, `a6885aa2`). Findings
|
||
that reshaped the campaign: the frame roots at the CAMERA's cell;
|
||
outdoor frames draw the #456 far building every frame and bury it by
|
||
depth (no hiding mechanism exists to port); retail DOES draw depth-only
|
||
invisible portal-polygon panels (`DrawPortalPolyInternal` — punch far-Z
|
||
/ seal own-depth) — the AD-117 stamps re-invented a real mechanism at
|
||
the wrong site. **Known gap:** the first capture round did not record
|
||
the camera pose; exact FW1 replay needs pose-stamped traces. A prepared
|
||
template (`tools/walk-oracle/fw0-capture-pose.cdb.template`) makes the
|
||
re-capture a short owner-assisted session; scheduled for when FW1's walk
|
||
is ready to gate (structural conformance proceeds meanwhile on the
|
||
existing traces).
|
||
|
||
**Goal:** retail's actual per-frame draw walk, captured and replayable,
|
||
BEFORE any port is judged.
|
||
|
||
- Build the cdb capture harness (`tools/walk-oracle/`): breakpoint script
|
||
logging, per frame, the sequence of cell/building draw entries with
|
||
stage boundaries — hook `PView::DrawCells`, `CEnvCell` draw,
|
||
`CLandBlock`/`LScape::draw`, `DrawBuilding`; dump cell ids (`this`
|
||
offsets verified with one-shot `dt` first). cdb safety rules from the PV
|
||
campaign are binding: never inline `-c` attach to a client that matters,
|
||
every `j` branch ends in `gc`, `qd` threshold auto-detach, watch hit-rate
|
||
lag.
|
||
- Capture positions (owner assists — retail sessions): Sanctuary terrace
|
||
center + terrace EDGE (normal camera — the #456 acceptance pose),
|
||
cathedral portal-in arrival, a Holtburg doorway (the flap scene), a
|
||
Holtburg street + house exit, one deep dungeon corridor. Record camera
|
||
pose per capture.
|
||
- Also capture the same positions in acdream (world state: cell registry
|
||
contents) so W1 replays are apples-to-apples.
|
||
- Pin the port function list as a research note with pseudocode stubs:
|
||
`docs/research/2026-08-30-fw-walk-pseudocode.md` (grow it through FW1).
|
||
- **Deliverable/gate:** trace fixtures checked into
|
||
`docs/research/2026-08-30-fw-walk-oracle/`, replayable by a test
|
||
helper; harness scripts committed; no production code touched.
|
||
|
||
### FW1 — `RetailFrameWalk` + `PViewSet` (pure CPU, conformant)
|
||
|
||
**Goal:** the ported walk as a standalone module that reproduces the
|
||
oracle traces exactly.
|
||
|
||
- New `src/AcDream.App/Rendering/Walk/`: `RetailFrameWalk` (rooting per
|
||
`SmartBox::RenderNormalMode`; EnvCell recursion per `PView::DrawCells`;
|
||
landscape walk per `LScape::draw`/`grab_visible_cells`; building
|
||
look-ins per `DrawBuilding` + `ConstructView(CBldPortal)`),
|
||
`PViewSet` (`ConstructView`/`set_view`/`PView::GetClip` — the chain
|
||
polygon is the only CPU-clipped geometry; one view per chain, never
|
||
merged), `IWalkWorldView` (the read seam: committed cells via the
|
||
registry, buildings via `BuildingRegistry`, camera pose).
|
||
- Output: an ordered walk event stream (cell id, view, stage) matching the
|
||
oracle trace format, plus the frame's visible-cell set.
|
||
- Pseudocode first (workflow step 3), C# port line-by-line (step 4), named
|
||
symbols in comments.
|
||
- **Gates:** oracle-trace replay tests pass position-for-position;
|
||
synthetic-world unit tests for rooting/recursion/cone edge cases; the
|
||
existing replay fixture suite (Issue95/113/120/124/127/130/131/176-177/
|
||
181, SanctuaryPortalSeam, HouseExitWalkReplay, TowerAscentReplay,
|
||
CornerFloodReplay) re-expressed against the walk — behavior differences
|
||
vs the old builder are adjudicated against the ORACLE, not against the
|
||
old builder. No production wiring; hermetic suites green.
|
||
|
||
**FW1 STATUS (2026-08-30, @`77f5342b`):** NINE of the ten pose-stamped
|
||
fixtures reproduce retail frame-exactly (foundry-deep all 39 frames,
|
||
doorway-still, street-outdoor, terrace-center, terrace-edge — the #456
|
||
acceptance pose — cathedral-arrival, holtburg-walkout, -transitions,
|
||
-walkabout); foundry-entry is exact through F66 with the F67–F79
|
||
standing segment parked on ONE live number (building 0036's root-plane
|
||
viewpoint — probe `tools/walk-oracle/fw1-f67-viewpoint-probe.cdb`,
|
||
goal-sanctioned retail-session stop filed with the user). Load-bearing
|
||
adjudications, all decomp-cited: the two-arm `get_degrade` threshold
|
||
rule (ideal→max at the live `deg_mul≈+0.99`), deg_mul's DYNAMIC swing
|
||
under capture load (doorway-still pins mul=0 — an environment pin like
|
||
the viewport), znear=0.1 confirmed, and the Ghidra-arbitrated portal
|
||
walker truth table (BN FPU pseudo-C mis-renders branch sense — three
|
||
separate misreads this stage; Ghidra first, always).
|
||
**Gate amendment:** the old-replay-fixture re-expression is retired as
|
||
an FW1 gate — the ten traces are direct retail evidence and strictly
|
||
supersede fixtures that encode the OLD builder's behavior; the old
|
||
suite's scenario classes (doorway flap, dungeon seams, tower ascent,
|
||
corner flood) are covered by the traces and re-verified live at the
|
||
FW3/FW4 connected gates, where the old fixtures retire with the old
|
||
builder. Production classes: `RetailFrameWalk`, `WalkPView` (the
|
||
PViewSet role), `WalkBuildingPortals`, `WalkLandscape`,
|
||
`WalkVisibilityMath`, `WalkScreenClip`, `WalkCopyView` under
|
||
`src/AcDream.App/Rendering/Walk/`.
|
||
|
||
### FW2 — `OrderedDrawStream` + `OrderPreservingSubmitter`
|
||
|
||
**Goal:** walk-order submission through the existing RHI, proven
|
||
pixel-equivalent on static content.
|
||
|
||
- `OrderedDrawStream`: append-only (mesh ref, transform, material state,
|
||
stage, cell) commands in walk order. `OrderPreservingSubmitter`: binds
|
||
`GlobalMeshBuffer` once, reuses ring-section writers + `MeshPipelineSet`
|
||
+ `DrawIndirectRangeRhi`; emits per-instance first (the deferred-alpha
|
||
SSBO pattern), then merges ADJACENT same-state commands into MDI runs —
|
||
a merge across a state or stage boundary is forbidden by construction
|
||
(assert it).
|
||
- Depth semantics: port retail's depth compare/write configuration for the
|
||
world passes verbatim in this stage (current set uses Less — verify
|
||
against retail's raster state and document the finding either way).
|
||
- Diagnostic harness: a dev scene (offscreen target) drawn twice — old
|
||
path vs walk path — framebuffer-compared (the Slice-7 comparison
|
||
pattern, with the pixel-diff repeat floor from
|
||
`feedback_pixel_diff_needs_repeat_floor`).
|
||
- **Gates:** submitter unit tests (order preservation, merge legality);
|
||
framebuffer equivalence on static-content scenes; hermetic suites green.
|
||
|
||
**FW2 STATUS (2026-08-30, @`e65644cb`):** `OrderedDrawStream` +
|
||
`WbDrawDispatcher.OrderedStream` (the submitter partial — per-instance-
|
||
first emission, pure-CPU merge-run builder, fail-loud stage/pipeline/
|
||
cull boundary validation) and `WorldDepthContract` are landed with 27
|
||
RecordingGpuDevice gates; hermetic 6,714/0. Depth verified verbatim
|
||
from the decomp (`zfuncVal` 0x2 = D3DCMP_LESS via `SetDepthBufferMode`
|
||
@0x005a2d10; LESSEQUAL is GameSky-local). **Gate adjudication:** the
|
||
offscreen pixel A/B rides FW3's cutover toggle — a walk-driven scene
|
||
first exists there, `IGpuRenderTarget` has no readback verb today, and
|
||
opaque content under Less is order-independent, so the RecordingGpuDevice
|
||
order/merge referees are FW2's automated gate and the pixel proof joins
|
||
FW3's (offline pixel gate + owner visual gate). Deferred to FW3 and
|
||
recorded in the code: the building-detail overlay replay, portal-punch
|
||
emission, and the `_drawCullModes`/alpha-scope sequencing constraint.
|
||
|
||
### FW3 — static world cutover + THE perf checkpoint
|
||
|
||
**Goal:** terrain, EnvCell shells, and buildings draw via the walk in
|
||
production; the statics gather path is deleted; perf is measured before
|
||
deeper commitment.
|
||
|
||
- Root the production frame at the walk: `WorldSceneRenderer` drives
|
||
`RetailFrameWalk` + submitter for the static routes
|
||
(LandscapeOutdoorStatic, LandscapeBuildingShell, CellStatic); dynamic
|
||
routes (LookInObject, LandscapeOutsideDynamic, DynamicLast) stay on the
|
||
packed path within this stage only (the route-contract split makes the
|
||
seam clean).
|
||
- Retire the three `ShellDrawLiftZ` sites in this stage — walk order +
|
||
retail depth semantics replace the tie-break; prove it at the #130
|
||
doorway-strip scene before deleting, and delete the register row in the
|
||
same commit.
|
||
- Terrain/sky/weather draw at their walk turns (the stage interleave from
|
||
`RetailPViewPassExecutor`'s ported knowledge moves inward).
|
||
- **Perf checkpoint (the spec's stop rule):** dense Arwic uncapped,
|
||
Release, exact binary: CPU/GPU p50/p99 + fps vs the current production
|
||
profile (519.7 fps / 1.869 / 1.096 ms baseline). Within 20% → proceed;
|
||
worse → STOP, owner decision with numbers on the table.
|
||
- **Gates:** transition checklist self-run; hermetic suites; owner visual
|
||
gate (statics look right indoors + outdoors, no seam strip at the
|
||
doorway, no z-fighting at shells); perf numbers recorded in this plan.
|
||
|
||
**FW3 SLICE PLAN (2026-08-30, from the cutover-surface survey):**
|
||
|
||
- **FW3.1 — production walk world data.** Materialize the walk's world
|
||
model at landblock commit time, registry-owned, through the LEGAL
|
||
`IDatReaderWriter` seam (never raw `DatCollection` —
|
||
`RuntimeDatAccessArchitectureTests`): (a) `WalkCell` fields onto/beside
|
||
`LoadedCell` (portal side/exact from the RAW `Flags` already preserved
|
||
on `CellPortalInfo`; plane from the existing `ClipPlanes` parallel
|
||
list; the 0xFFFF→0xFFFFFFFF exit-sentinel widening); (b) a production
|
||
`WalkBuilding` build in `LandblockBuildFactory`/`BuildingLoader` — the
|
||
BIGGEST gap: today's `Building` drops ModelId/Frame/portal
|
||
flags/stab lists at load; (c) the walk landscape assembly (retail
|
||
z-slabs from the height table, the SideCellCount LOD pyramid,
|
||
`CellBuildings[]`, viewer-centred grid) fed from landblock residency;
|
||
(d) the production frame context: camera/rays/CyPlane/ObjectToClip
|
||
derived from the LIVE `WorldCameraFrame` + real viewport (the capture
|
||
client's 1024×720 constants are fixture pins, not production values).
|
||
Conformance: the ten oracle fixtures re-run against the PRODUCTION
|
||
builders (same DATs → identical walk output as the test adapter).
|
||
- **FW3.2 — walk-driven static routes.** `WorldSceneRenderer` drives
|
||
`RetailFrameWalk` → `OrderedDrawStream`
|
||
(Terrain/BuildingShell/CellStatic/LookInStatic/PortalPunch stages) →
|
||
`SubmitOrderedStream` for static content; the old builder STOPS
|
||
EMITTING `LandscapeOutdoorStatic`/`LandscapeBuildingShell`/`CellStatic`
|
||
ranges (the `CompletePackedProductionFrame` count assertion forbids
|
||
emit-but-skip); dynamic routes (`LookInObject`,
|
||
`LandscapeOutsideDynamic`, `DynamicLast`) stay packed — which requires
|
||
`ClipFrameAssembler` + `ViewconeCuller` to SURVIVE this stage (their
|
||
slices key the dynamic routes). Walk-path SELECTION publication
|
||
(static picking today rides `publishSelection: true` in the packed
|
||
classifier — an unlisted consumer; the walk submitter must feed
|
||
`RetailSelectionScene` or statics go un-clickable). Portal-punch
|
||
emission via `PortalDepthMaskRenderer` at walk turns; building-detail
|
||
overlay replay; sky/weather move from per-slice to the walk's
|
||
landscape turn; the `_drawCullModes`/alpha-scope sequencing constraint
|
||
resolved (own cull scratch or strict sequencing). Route-keyed particle
|
||
owners re-sourced from the walk.
|
||
- **FW3.2b-2 — the production rooting (DESIGN, 2026-08-30; execute
|
||
INLINE, not via subagent — the integration-slice rule).** The
|
||
dual-compute split for this stage: the OLD visibility
|
||
(`PortalVisibilityBuilder` + look-in frames + clip assembly +
|
||
viewcone) KEEPS RUNNING to feed everything dynamic; the WALK draws
|
||
everything static. No old static DRAWS remain (no double draw):
|
||
1. `RetailPViewRenderer.DrawInside` keeps steps 1–8 and 10–11
|
||
(visibility, merge/look-in seeding, clip frame, prepare — with
|
||
`prepareCells` UNIONED with the walk's flood/look-in cells —
|
||
viewcone, route build) but `RenderScenePViewFrameProduct` STOPS
|
||
emitting `LandscapeOutdoorStatic`, `LandscapeBuildingShell`, and
|
||
`CellStatic` ranges. Per the plan's route split, LOOK-IN OBJECTS
|
||
(`LookInObject`) are dynamic-side and STAY PACKED this stage —
|
||
the old look-in machinery keeps drawing look-in cell content and
|
||
its punches; the WALK's building turns therefore draw shell
|
||
content + punches for NON-look-in... NO — resolve the overlap the
|
||
other way: the walk's building turns run their punch+look-in
|
||
machinery ONLY for the walk's own conformance events; production
|
||
look-in DRAWS (punch fans + content) stay entirely on the old
|
||
path this stage, and the walk's `BuildingShell` stage content +
|
||
`OnPunchGeometry`/look-in DC draws are SUPPRESSED in the driver's
|
||
production wiring for look-in-owned buildings… REJECTED — that is
|
||
a dual path. FINAL: the walk owns ALL of it — building shells,
|
||
punches, look-in STATIC content (stage LookInStatic) — and the
|
||
`LookInObject` route survives ONLY for the DYNAMICS inside
|
||
look-in cells (`BuildLookInRoutes` filtered to dynamic
|
||
classifications). The old `DrawBuildingLookIns` static half and
|
||
`DrawLookInPortalPunch` retire with the static routes.
|
||
2. `WalkFrameDriver`'s leaf renderer maps to:
|
||
sky → `RetailPViewPassExecutor` sky call; terrain slice →
|
||
`_terrain.Draw(camera, frustum, clipPlanes: slice.Planes,
|
||
ndcClipAabb: slice.NdcAabb)`; cell shell →
|
||
`EnvCellRenderer.Render(pass, singleCellSet)` (+ transparent
|
||
ordered per cell); punch fan → `PortalDepthMaskRenderer.
|
||
DrawDepthFan(worldVerts, viewProjection, slicePlanes,
|
||
forceFarZ: true)`; `ClearInteriorDepth` →
|
||
`IWorldPassScope.ClearInteriorDepth` (keep production's
|
||
slices>0 gate); `DrawExitSeals` → the existing seal-fan loop
|
||
(`DrawPortalDepthWrite` seal flavor over the root flood's exit
|
||
portals); alpha barrier → `RetailAlphaQueue.FlushFartherThan`.
|
||
3. Static records: cell statics via `RenderSceneQuery.
|
||
CopyCellStaticsTo(cellId)`; outdoor statics via a per-landscape-
|
||
cell index built once per frame from
|
||
`CopyIndexTo(OutdoorStatic)`; building shells via the
|
||
`IsBuildingShell` entities keyed by anchor cell.
|
||
4. Consumers re-pointed: particle owners
|
||
(`RenderFrameRouteOwnerSelector.Replace(LandscapeOutdoorStatic…)`
|
||
sites) re-source from the walk's populated cells; diagnostics
|
||
source counts tolerate the route removal; `EmitRenderSignature`'s
|
||
terrain/scenery counts re-source or annotate.
|
||
5. Sky/weather: sky moves into the walk's LS turn (once); weather
|
||
stays in the old late-dynamics slice loop this stage (it is
|
||
dynamic-adjacent and the plan's FW4 moves it fully).
|
||
6. Gates before the owner sees a build: hermetic + InstalledDat
|
||
suites; the connected lifecycle route; the offline pixel gate
|
||
(outdoor statics); dense-Arwic pre/post pair (physical display).
|
||
|
||
**FW3.2b-2 STATUS (2026-08-30, @`4918677b`): THE STATIC CUTOVER IS
|
||
COMMITTED.** The walk drives every production static draw
|
||
(`DrawWalkDrivenStatics` + the dynamics-remainder phases); the three
|
||
static routes are deleted from the product builder; particle owners
|
||
re-source from the walk's per-cell-turn visited sets (retail's own
|
||
gate — `ShouldDrawParticles` @0x0050FE60). Landed evidence: the walk
|
||
shadow probe ran 11k+ live frames faultless with walkCells ⊂ oldCells
|
||
every frame. The old static path survives ONLY behind `!walkActive`
|
||
for the executor-fake test path (15 retail-ordering regression tests;
|
||
unreachable by production) — **its deletion is FW4 scope**.
|
||
Transitional risks recorded in code: two-pass walk cost (FW3.4
|
||
measures), interior slice-count reconciliation between the old clip
|
||
assembly and the walk's exit-view survival, outdoor merged-flood
|
||
punch coverage now riding the walk's building-BSP punches (owner
|
||
visual gate verifies). Suites: hermetic 6,750/0; Walk 201/1;
|
||
InstalledDat conformance 40/1 untouched.
|
||
|
||
- **FW3.3 — ShellDrawLiftZ retirement.** THREE sites (the register row
|
||
AP-32 lists only two — `LandblockBuildFactory.cs` bakes the lift into
|
||
the drawn cell transform and is unlisted): delete all three, flip the
|
||
#130 proof (`UnliftedGate_LeavesTheStripAtTheDrawnTopEdge` is
|
||
explicitly written to fail-by-gap until the lift is gone), delete
|
||
AP-32 in the same commit.
|
||
**FW3.3 STATUS (2026-08-30, @`05240d2c`): RETIRED + connected-PASSED.**
|
||
ShellDrawLiftZ deleted at every site (AP-32 deleted same commit); the
|
||
#130 proof flipped as authored (strip test deleted, the 147-combo
|
||
coverage sweep passes in the one dat space, unchanged tolerances).
|
||
Three lifecycle-gate PASSES today on exact binaries; owner-confirmed
|
||
live: no seams at the doorway class. PERF FLAG for FW3.4: Holtburg
|
||
town 19-22 ms CPU p50 / ~1.8 MB-frame alloc on the walk path (capped
|
||
diagnostic read; interiors 3.3 ms) - candidates: collapse the
|
||
two-pass walk, batch per-cell shells, cache the outdoor-statics
|
||
index, cut per-segment rebinds.
|
||
|
||
**FW3.4 PERF PAIR (2026-08-30, same session/conditions, exact
|
||
binaries):** dense Arwic uncapped, PRE (@87853359, old static path)
|
||
vs POST (@3a05d659, walk-driven): CPU p50 23.6 -> 31.6 ms (+33.5%),
|
||
p95 36.3 -> 44.3 ms (+21.9%), alloc p50 133 KB -> 1,920 KB/frame
|
||
(14x). THE +/-20% STOP RULE TRIGGERS. Sources are the flagged known
|
||
costs (two walk passes/frame, per-cell single-segment submissions
|
||
with full section rebinds, per-cell record-array copies in the world
|
||
data). FW3.4a fixes them (single walk pass; prepare-once/draw-ranges
|
||
- the deferred-alpha architecture; pooled record segments), then the
|
||
pair re-measures against the SAME baseline; the owner decision only
|
||
triggers if it still exceeds 20% after the known fixes.
|
||
|
||
**FW3.4a RESULT (2026-08-30, @`1da178d9`): THE STOP RULE CLEARS.**
|
||
Post-refactor dense Arwic uncapped vs the SAME baseline: CPU p50
|
||
23.6 -> 22.9 ms (-3.2%), p95 36.3 -> 34.7 ms (-4.5%) - the walk path
|
||
is now FASTER than the old static path. The refactor's own crash
|
||
round (VK_ERROR_DEVICE_LOST) taught the binding contract: leaf draws
|
||
and alpha flushes rebind set-0 between ranges, so DrawOrderedRange
|
||
rebinds per call; only the ring WRITES are once-per-frame. FOLLOW-UP
|
||
FILED: alloc p50 still ~1.5 MB/frame on the walk path (baseline
|
||
133 KB) - the per-frame driver/leaf/populator object graph in
|
||
DrawWalkDrivenStatics; hoist to renderer fields with per-frame
|
||
Reset (FW3.4 closeout or FW6 polish; CPU-neutral today, GC-hitch
|
||
hygiene).
|
||
|
||
- **FW3.4 — perf checkpoint + gates.** BASELINE CORRECTION: the
|
||
519.7 fps / 1.869 / 1.096 ms numbers are the Aerlinthe
|
||
ordinary-production profile, NOT dense Arwic. Before the cutover
|
||
lands, capture a fresh dense-Arwic pre-cutover baseline with the same
|
||
harness (`tools/run-connected-r6-soak.ps1 -Uncapped -DenseTown`), and
|
||
apply the ±20% stop rule to THAT pair. Transition checklist: the
|
||
lifecycle gate + dense-town soak exist; a Holtburg house-exit stop and
|
||
a teleport stopwatch metric must be ADDED to the lifecycle route
|
||
(`drive.ps1` from the spec does not exist — the checklist runs on the
|
||
existing gate scripts).
|
||
|
||
### FW4 — entities, particles, dynamics into walk turns; delete the apparatus
|
||
|
||
**Goal:** everything draws at its walk turn; the patch apparatus and the
|
||
old visibility layer are gone.
|
||
|
||
**FW4 SLICE 1 — the interior outside-view source cutover (2026-08-30,
|
||
pulled forward from the FW3 visual gate).** The owner's stairwell/grass
|
||
transition report (grass briefly covering floor openings at doorway
|
||
crossings, intermittent — the #119 family) probe-pinned to the FW3 dual
|
||
path: the walk decided WHETHER terrain draws (`_interiorPView.OutsideView.
|
||
ViewCount > 0`) while the old `PortalVisibilityBuilder` assembly decided
|
||
WHERE (slice clip planes + count + scissor), and the walk's punch fans
|
||
indexed the OLD slice array with WALK view indices. At boundary frames
|
||
(`ACDREAM_PROBE_WALK_ROOT` captures: the slices=2 short-flood family at
|
||
OUT→IN, a 1-frame outdoor blip mid-crossing) the old builder's exit views
|
||
went fat/degenerate, terrain splashed over interior pixels, and the
|
||
depth-clear preserves COLOR — any cell absent from the walk's flood never
|
||
repainted its pixels. Retail cannot produce this: it has ONE visibility
|
||
structure. The cutover: `ClipFrameAssembler.ReassembleOutsideViewFromWalk`
|
||
materializes the walk's own `outside_view` (pixel points → standard NDC →
|
||
the existing `ClipPlaneSet.From` machinery) into the assembly's
|
||
outside-view block right after Collect, before the single
|
||
`PrepareClipFrame` publication (moved below the walk block); the
|
||
`Landscape` event now carries the walk's active-view count (rides the
|
||
record's existing `OutsideViewCount` field — trace mapping compares kind
|
||
only, ZERO oracle-fixture churn) and the driver fans exactly that many
|
||
terrain slices — `activeTerrainSliceCount` is deleted end to end. Outdoor
|
||
roots keep the assembler's single full-screen slice (asserted ==1;
|
||
identical content by construction). Suites: hermetic 6,762/0 (4 new
|
||
materializer tests pinning the y-flip + plane-sign conventions), Walk
|
||
lane 209/1, InstalledDat walk conformance 40/1 — all unchanged or grown.
|
||
Remaining desync surfaces deliberately left for the rest of FW4: exit
|
||
SEALS + cell slices still assemble from the old per-cell views (identical
|
||
dat portal polygons; only the visible SET can differ), and interior-root
|
||
look-in seeding still reads the old outside view.
|
||
|
||
- Entities gate per view via `Render::viewconeCheck` at their cell's walk
|
||
turn; dynamics-last and alpha interleave per the walk's stages
|
||
(`RetailAlphaQueue` becomes the stream's alpha stage or is absorbed —
|
||
decide at implementation with the retail stage list as the arbiter);
|
||
particles draw at their owner cell's turn (`ShouldDrawParticles`),
|
||
unclipped.
|
||
- Re-point the consumers at the walk's visible-cell output: particles
|
||
(`ParticleVisibilityController`), lights (`BuildPointLightSnapshot`
|
||
filter), EnvCell prepare filter. Directional-shadow caster selection
|
||
consumes the same set.
|
||
- DELETE in this stage (registry rows updated in the same commits):
|
||
look-in seeding + callers + barrier math; punch stencils
|
||
(`PortalDepthMaskRenderer` #117); the InViewCells side-channel;
|
||
`PortalVisibilityBuilder`'s production role; `RetailPViewRenderer` /
|
||
`RetailPViewPassExecutor` shells (their stage knowledge now lives in the
|
||
walk); the packed route machinery + `PackedOracle` + both
|
||
classification caches; `CellVisibility`'s dead BFS.
|
||
- **Gates:** the full re-expressed replay suite; transition checklist;
|
||
hermetic suites; the owner visual matrix — cathedral #456 acceptance
|
||
(far building, monsters, stalks hidden at the terrace edge at NORMAL
|
||
camera; no seam strip; no z-fighting; candle/falls/lake/portal-flood
|
||
intact), doorway flap, dungeon wall/floor seams, portal-in, house exit,
|
||
teleport timing.
|
||
|
||
### FW5 — camera completion
|
||
|
||
**Goal:** the camera is retail's, end to end.
|
||
|
||
- Port `CameraSet::Farther`/`CameraSet::Raise` verbatim into
|
||
`RetailChaseCamera`: the multiplicative offset scaling with the
|
||
write-refusal gates (|x|<10, |y|<10, z<450, z>−1.8 — NOT a simple
|
||
distance clamp), the preserved-length pitch. The 40 m dev zoom goes
|
||
away with it (owner-approved in the spec round).
|
||
- Port retail's zoom/pitch adjustment rates if recoverable from decomp
|
||
(retires TS-56); if not recoverable, TS-56 stays with a note.
|
||
- **Gates:** camera unit tests against ported constants; owner
|
||
side-by-side feel gate vs retail (chase, zoom saturation ≈13 m boom,
|
||
collision, slope align).
|
||
|
||
### FW6 — closeout
|
||
|
||
- Divergence-register reconciliation (added: landblock-stage streaming
|
||
exposure rows if not already rowed, order-preserving MDI merging as
|
||
intentional architecture; retired: lift, #117 stencils, look-in-era
|
||
rows, TS-56 if ported).
|
||
- Docs: architecture doc render section rewritten around the walk;
|
||
`worldbuilder-inventory.md` updated (WB = asset layer only);
|
||
`launch-options.md` rows for any probes added/removed (probes die with
|
||
their investigations).
|
||
- Memory: `project_render_pipeline_digest.md` rewritten current-truth-on-top
|
||
around the walk; campaign memory closed out.
|
||
- Roadmap + milestones updated; the quarantined PV branch gets a
|
||
disposition note (kept as archive; nothing merges).
|
||
- Final gates: full hermetic suites, both standard connected routes,
|
||
the complete owner visual matrix, merge to main.
|
||
|
||
## Rollbacks
|
||
|
||
Each stage lands as its own commit series; record `git revert` anchors
|
||
here as stages close (the Modern Runtime plan's convention). FW3 and FW4
|
||
are the cutover stages — their revert anchors are mandatory entries.
|
||
|
||
## Risks (tracked, from the spec)
|
||
|
||
- **Perf** — decided by FW3's checkpoint, numbers over hope.
|
||
- **Streaming scope creep** — only the two register-rowed exposure
|
||
adaptations may be touched; anything more is out of scope.
|
||
- **Oracle fidelity** — traces need owner-assisted retail sessions (FW0);
|
||
captured once, checked in.
|
||
- **Classifier drift during staging** — FW3's split runs walk statics
|
||
beside packed dynamics for one stage; the route contract seam is the
|
||
guard, and FW4 deletes the duality.
|
||
- **Camera ergonomics** — the dev zoom disappears at FW5; a separate dev
|
||
flycam is deliberately deferred (the Fly camera in `CameraController`
|
||
already exists for development framing).
|