acdream/docs/plans/2026-08-03-recent-regression-cleanup.md
Erik 2ef02f8cbb docs: close the recent-regression cleanup plan
S1 (#284), S2 (#282), and S3 (#283) are all landed and user-accepted. S3 is
recorded as measured-unreachable rather than restructured, so the plan's
"prove or disprove before moving ownership" step is what actually decided the
outcome.

Final complete Release solution: 10,844 passed / 4 skipped / 0 failed.
Next: the original campaign order, starting at C4 route 2.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:34:07 +02:00

8.2 KiB
Raw Permalink Blame History

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 898ff18b89cf1e66 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 S1S3

Resume the original campaign order, unchanged:

  1. C4 routes 27 — 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-22ShadowShapeBuilder 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.