# Recent-regression cleanup — plan (2026-08-03) Three defects introduced by the 2026-08-02/03 stabilization batch, found while reconciling the #281 test failures. All three are **ours, days old, and inside the least-verified code in the tree**. They are cleared before C4 resumes so six more placement routes are not stacked on top of them. Issues: #282 (two cell fields), #283 (two world origins), #284 (silent park). ## Status — CLOSED 2026-08-03 All three landed and are user-accepted. | Slice | Issue | Commit | Outcome | |---|---|---|---| | S1 | #284 | `97d11e6c` | Park reasons named; terminal on the contradictory state | | S2 | #282 | `3c36b4cc` | One cell owner (`VisibilityCellId`); register row AP-133 | | S3 | #283 | `898ff18b` → `89cf1e66` | Measured UNREACHABLE; permanent invariant instead of a restructure | Connected Release gate (retail UI) on S1+S2: user verdict "works fine"; log showed 9 completed reveals, 58 reveal events all `failures=0`, zero unhandled exceptions, zero parked placements, graceful exit (`0c14c402`). S3's probe run recorded zero disagreements across 11 reveals and six landblocks spanning ~45 km, so ownership was deliberately left alone — the evidence disproved the hypothesis, and the guard exists to keep it disproven. Final complete Release solution: **10,844 passed / 4 skipped / 0 failed.** Next: the original campaign order below, starting at C4 route 2. ## Why these first Every one is an instance of the exact weakness the placement campaign exists to remove: **two owners of one fact, with no single writer keeping them agreed.** #282 duplicates "which cell is this in". #283 duplicates "where is zero". #284 is why both stayed invisible. Fixing them inside C4 would mean diagnosing them through C4's much larger diff. ## Standing discipline for this plan - Retail is the oracle. Grep `docs/research/named-retail/acclient_2013_pseudo_c.txt` by `class::method` before writing. - Root causes only. No timeouts, grace periods, suppression flags, or catch-and-ignore. #284 in particular is observability + fail-fast, never a retry cap. - **The complete Release solution suite must be green before every commit.** Focused-run-only gating is exactly what let #281–#284 ship. The full suite takes about 30 seconds; there is no excuse. - Each fix is its own bisectable commit with root-cause evidence, and updates the issue + divergence ledgers in that same commit. --- ## S1 — #284: make a parked placement visible (do this first) Smallest, and it turns the other two from archaeology into observation. 1. Classify the park reason at the single site that produces it (`RuntimeSetPositionState.PrepareMover`): awaiting collision generation, awaiting Setup, awaiting world frame. 2. Fold per-reason parked counts into the existing physics ownership snapshot (`RuntimePhysicsState.CaptureOwnership`) so they appear wherever ledgers are already asserted, and in the connected gates' `report.json`. 3. Fail fast on unresolvable parks. A park awaiting the world frame *while a local player is already registered* is not a wait — it is a contradiction. Surface it as a committed invariant exception, the pattern `01f4791e` established for receipt-ledger violations. 4. Convergence contract: parked entries must be zero at every stable checkpoint. Wire that into the lifecycle/nine-stop gate assertions. **Tests:** each park reason is reported exactly once and clears on resolution; the contradictory park throws rather than retrying; ledgers converge to zero. **Gate:** focused Runtime + complete solution suite. --- ## S2 — #282: one owner for an entity's visibility cell 1. **Establish the retail model.** `CPhysicsObj::set_cell_id` @0x0050f4f0, `change_cell` @0x00513390, `set_cell_id_recursive` @0x00510da0, `ShouldDrawParticles` @0x0050fe60. Retail carries ONE cell per physics object, and particle gating reads that same cell. Write the pseudocode note before touching C#. 2. **Audit the writers.** 12+ sites write `ParentCellId` (`LiveEntityNetworkUpdateController` ×4, `RemotePhysicsUpdater` ×2, `ProjectileController` ×3, `LiveEntityOrdinaryPhysicsUpdater`, `LocalPlayerProjectionController`, `RemoteTeleportController`, …); 3 write `EffectCellId`, all in `LiveEntityRuntime`. For each `ParentCellId` writer record whether it also rebuckets — a rebucket with an exact cell currently repairs the pair by accident. Produce the table before choosing the fix. 3. **Decide the shape.** `EffectCellId`'s documented purpose is narrow: outdoor dat stabs that keep a null render parent while retail still gives them an outdoor landcell. Live/interior entities were explicitly meant to use `ParentCellId`. Preferred fix, in retail's direction: live entities stop populating `EffectCellId`, the stab case keeps it as the documented exception, and one owner writes the visibility cell that the effects path reads. If the audit shows live entities genuinely need it, the alternative is a single writer that maintains both — but never 12 independent writers against a field that wins. 4. **Divergence register.** The two-field split is an adaptation from retail's single cell. Add the row if none exists; delete it if step 3 collapses the split. **Tests:** an entity crossing a cell boundary keeps its particles and lights attached; an equipped/attached child keeps its parent-relative behaviour; the outdoor dat stab case is unchanged. **Gate:** focused App + complete suite, then a **user visual check** — a monster with an active spell effect crossing a cell boundary, and a lit static object, indoors and outdoors. --- ## S3 — #283: one owner for the world origin Sequenced last of the three and immediately before C4 route 3, which touches the same portal code. 1. **Prove or disprove reachability first.** With S1 landed, assert at the placement site that Runtime's frame center and App's `LiveWorldOriginState` center agree; run the portal/recall routes. If they never diverge in practice, the fix is a permanent invariant rather than a behaviour change — record that and stop. Do not restructure on a hypothesis. 2. **Retail evidence.** How retail rebases its landblock offsets across a teleport, and the ordering around `TAS_TUNNEL_CONTINUE` — the same sequence #280 already needs read. Read once, use twice. 3. **Fix shape.** Runtime owns the world frame; App projects it. Today `LiveWorldOriginState` is an independent owner with its own rebase edge. Make it a projection of Runtime's frame, so there is exactly one origin and the retirement-driven edge becomes a *publication* of that origin rather than a second decision. This is the same ownership move the campaign has already applied to entities, physics, and placement. 4. **Ordering invariant.** No placement may commit against an origin the render side has not adopted. Whether that is expressed as a gate or made structurally impossible falls out of step 3. **Tests:** a teleport whose old-window retirement lags by many frames cannot commit a placement against a mismatched origin; frame and origin rebase together; ordinary movement rebases neither (already pinned by `RuntimeWorldFrameTests`). **Gate:** focused + complete suite, lifecycle/reconnect, and a **user visual check** on repeated portal/recall arrivals with objects present. --- ## After S1–S3 Resume the original campaign order, unchanged: 1. **C4 routes 2–7** — ForcePosition, portal (with S3 landed), remote Create/Position, projectile correction, drops, pickup/parent/delete. Fold in #276 and #277 where their route becomes authoritative. 2. **#280** — retail destination prefetch, landed adjacent to route 3. 3. **C5** — delete superseded writers, complete suite, lifecycle/reconnect, nine-stop soak **on the final binary**, two-client observation, the #269/#278 slope-glide check. Only then retire AP-1, AD-1, AP-131, and AD-60's legacy half. 4. **AP-22** — `ShadowShapeBuilder` as sole authority for authored Setup collision shapes. 5. **AD-10** — remote contact-plane projection through the real transition sweep. 6. Final movement/collision matrix; ledger, architecture, roadmap, milestones, memory, `CLAUDE.md`, `AGENTS.md`. Vendor Slice 5 resumes only after that.