FW0 ledger entry records the fixtures, replay helper, pseudocode model, and the reshaping findings. The pose gap (first captures lack camera pose; exact replay needs it) is documented with a ready template for a short owner session when FW1 gates. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
19 KiB
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)
- 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.
- Grep named first.
docs/research/named-retail/acclient_2013_pseudo_c.txtbyclass::methodbefore any fresh decompilation; pseudocode notes indocs/research/before C# (the mandatory workflow). - 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.
- 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.
- 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.
- Register discipline. Deviations add their row in the same commit;
ported mechanisms delete rows in the same commit
(
docs/architecture/retail-divergence-register.md). - 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 byTerrainModernRenderer.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 — hookPView::DrawCells,CEnvCelldraw,CLandBlock/LScape::draw,DrawBuilding; dump cell ids (thisoffsets verified with one-shotdtfirst). cdb safety rules from the PV campaign are binding: never inline-cattach to a client that matters, everyjbranch ends ingc,qdthreshold 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 perSmartBox::RenderNormalMode; EnvCell recursion perPView::DrawCells; landscape walk perLScape::draw/grab_visible_cells; building look-ins perDrawBuilding+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 viaBuildingRegistry, 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.
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: bindsGlobalMeshBufferonce, reuses ring-section writers +MeshPipelineSetDrawIndirectRangeRhi; 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.
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:
WorldSceneRendererdrivesRetailFrameWalk+ 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
ShellDrawLiftZsites 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.
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::viewconeCheckat their cell's walk turn; dynamics-last and alpha interleave per the walk's stages (RetailAlphaQueuebecomes 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 (BuildPointLightSnapshotfilter), 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/RetailPViewPassExecutorshells (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::Raiseverbatim intoRetailChaseCamera: 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.mdupdated (WB = asset layer only);launch-options.mdrows for any probes added/removed (probes die with their investigations). - Memory:
project_render_pipeline_digest.mdrewritten 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
CameraControlleralready exists for development framing).