acdream/docs/plans/2026-08-30-campaign-fw-frame-walk.md
Erik 4918677b45 feat(render) Campaign FW3.2b-2: THE STATIC CUTOVER - the walk drives production statics
The retail frame walk now drives every production static draw. In
RetailPViewRenderer.DrawInside, when the concrete executor + the
packed product + the FW3.1 walk registries are all wired (all
production compositions - anything less throws):

- A pre-walk events-only collection pass (the shadow sink generalized
  to WalkVisitedSetCollector) gathers the frame's visited cells,
  buildings, and landscape-cell turns; the visited cells union into
  prepareCells so EnvCellRenderer prepares every shell the driver
  draws.
- DrawWalkDrivenStatics runs the WalkFrameDriver over the production
  world data (WalkProductionWorldData over RenderSceneQuery + the
  building registry): sky, terrain slices, outdoor statics at their
  landscape-cell turns, buildings (alpha barrier -> punch/look-in
  passes -> shell) in retail order, interior clear+seals as leaf
  closures (the old tail block's drain reasoning moves with them),
  flood cells shell-then-contents. Landscape/cell-stage particle
  owners re-source from the walk's visited sets - retail gates
  particles per cell turn (ShouldDrawParticles @0x0050FE60), which
  this is; the old sphere filter was the approximation.
- DrawLandscapeDynamicsPhase + DrawBuildingLookInDynamics carry the
  dynamics-only remainder (LookInObject now dynamic-classified,
  late outside-dynamics + weather, particle unions); DrawDynamicsLast
  and the outdoor flush are unchanged.
- The product builder stops emitting LandscapeOutdoorStatic /
  LandscapeBuildingShell / CellStatic (methods deleted, dead index
  tracking removed); LookInObject loads cells with
  includeStatics: false.

The old static path survives ONLY behind !walkActive for the
standalone/diagnostic executor-fake path that keeps 15 retail-ordering
regression tests exercising the barrier/punch/seal machinery; no
production composition can reach it. Its deletion is FW4 scope (the
plan's "deleting the patch apparatus") - recorded in the plan.

Transitional risks recorded in code/report: the two-pass walk cost
(FW3.4 measures), the interior slice-count reconciliation between the
old clip assembly and the walk's own exit-view survival, and the
outdoor merged-flood punch coverage now riding the walk's own
building-BSP punches (retail-faithful per FW1; the owner visual gate
verifies).

Suites (lead-verified): full Release build 0 warnings; hermetic
6,750/0 (baseline minus the three deleted route tests); Walk lane
201/1; InstalledDat Walk conformance 40/1 untouched. The two
IL-branch tests the implementation round reported failing pass in
every lead run - the recurring parallel-load flake pair.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-30 15:43:22 +02:00

499 lines
28 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.

# 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 F67F79
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 18 and 1011
(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.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.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.
- 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).