acdream/docs/plans/2026-08-30-campaign-fw-frame-walk.md
Erik 37febd1fe6 fix(render): FW4 slice 1 - interior outside-view slices come from the walk
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>
2026-08-30 18:21:08 +02:00

33 KiB
Raw Blame History

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 — 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.RenderRenderFrameOrchestrator.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:263ParticleVisibilityControllerParticleSystem.ApplyRetailView (Core/Vfx/ParticleSystem.cs:313), fed outdoors by TerrainModernRenderer.VisibleCellIds.
  • Lights: WorldSceneRenderer.cs:264RuntimeWorldFrameEnvironmentPreparation (WorldRenderFrameBuilder.cs:543) → LightManager.BuildPointLightSnapshot (Core/Lighting/LightManager.cs:284).
  • EnvCell prepare filter: RetailPViewPassExecutor.cs:276EnvCellRenderer.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.csDistance 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 DatCollectionRuntimeDatAccessArchitectureTests): (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 RetailFrameWalkOrderedDrawStream (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); ClearInteriorDepthIWorldPassScope.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).