docs: file #282-#284 and plan the recent-regression cleanup

Three defects introduced by the 2026-08-02/03 stabilization batch, all found
while reconciling #281's 43 test failures. Each is an instance of the weakness
the placement campaign exists to remove - two owners of one fact with no single
writer keeping them agreed - so they are cleared before C4 stacks six more
placement routes on top of them.

#282: WorldEntity.EffectCellId documents itself as existing only for outdoor
dat stabs, whose null render parent still needs retail's outdoor landcell for
CObjCell::IsInView gating; live/interior entities were explicitly meant to use
ParentCellId. f24532ad began populating it for live entities, and because
EntityEffectPoseRegistry.UpdateRoot resolves EffectCellId ?? ParentCellId it
now wins - while 12+ sites still write ParentCellId alone. Retail carries one
cell per object (CPhysicsObj::set_cell_id @0x0050f4f0, change_cell @0x00513390,
ShouldDrawParticles @0x0050fe60).

#283: 670f307c gave Runtime a world frame that rebases on the accepted teleport
Position, while App's LiveWorldOriginState rebases only after old-window
retirement completes. Between those edges the two disagree by the landblock
delta. Not yet proven reachable; the plan proves or disproves it before
restructuring anything.

#284: a placement that cannot resolve returns RetrySetupUnavailable forever
with nothing counting it or naming its reason. The fix is observability plus
fail-fast on contradictory states, never a retry cap or timeout.

Plan sequences S1 (#284) first so the other two are observable rather than
archaeological, then S2 (#282), then S3 (#283) immediately before C4 route 3,
which shares its portal code. Also records the gating change that would have
caught all of this: the complete Release suite must be green before every
commit, not a focused subset.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-03 13:44:04 +02:00
parent 98e9f9e8c6
commit 95ebc03af4
2 changed files with 195 additions and 0 deletions

View file

@ -24,6 +24,60 @@ What does NOT go here:
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
## Recent-regression cleanup — 2026-08-03
Plan: [`2026-08-03-recent-regression-cleanup.md`](plans/2026-08-03-recent-regression-cleanup.md).
All three were introduced by the 2026-08-02/03 stabilization batch, found while
reconciling #281's 43 test failures.
- **#282 — OPEN — live entities now write `EffectCellId`, contradicting its
documented contract, and 12 `ParentCellId` writers cannot keep it in sync.**
`WorldEntity.EffectCellId` (src/AcDream.Core/World/WorldEntity.cs:95-102)
documents itself as existing ONLY for outdoor dat stabs, which keep a null
render parent while retail still gives their physics object an outdoor
landcell for `CObjCell::IsInView` particle gating — "live/interior entities
normally use `ParentCellId` instead." `f24532ad` began setting it on live
entities at rebucket and placement projection
(`LiveEntityRuntime.cs:855,1101,1238`). Because
`EntityEffectPoseRegistry.UpdateRoot:163` resolves
`EffectCellId ?? ParentCellId`, the field now WINS for live entities, while
12+ production sites still write `ParentCellId` alone. Any of those that
changes cell without a matching rebucket strands particles and lights on the
entity's previous cell: effects vanish behind a wall the object no longer
occupies, or draw through one. Retail has exactly ONE cell per object
(`CPhysicsObj::set_cell_id` @0x0050f4f0, `change_cell` @0x00513390, read by
`ShouldDrawParticles` @0x0050fe60); the two-field split is our adaptation for
the null-render-parent stab case. Fix shape: one owner writes the entity's
visibility cell and the effects path reads that owner. Caught in miniature by
`LiveEntityLightControllerTests.Refresh_FollowsCurrentTopLevelRootAndCell`.
- **#283 — OPEN — Runtime's world frame and App's render origin rebase at
different moments during a teleport.** `670f307c` gave Runtime its own world
frame (`RuntimePhysicsState.ObserveLocalWorldFrame`), which rebases the
instant an accepted Position carries `TeleportAdvanced`. App's
`LiveWorldOriginState` rebases only when
`StreamingOriginRecenterCoordinator.Advance` observes
`IsOriginRecenterRetirementComplete` — many frames later, after the old
window has fully retired. Between those two edges the two owners disagree by
the source-to-destination landblock delta, so a remote Create converted by
Runtime lands a multiple of 192 m from the geometry App is building. Same
failure family as the zero-offset bug `670f307c` fixed, with a wrong origin
instead of a missing one. NOT yet proven reachable live — the portal reveal
gate may or may not exclude Create during that window, and #280 says other
work does continue arriving through it. Two owners of one fact; the campaign
answer is that Runtime owns the frame and App projects it. Route-3 adjacent.
- **#284 — OPEN — a placement that cannot resolve parks forever with no
diagnostic.** A first-entry placement whose world frame is absent returns
`RetrySetupUnavailable` (`RuntimeSetPositionState.PrepareMover:1535-1543`)
and is re-Advanced every pump indefinitely. Nothing counts it, names its
reason, or distinguishes "waiting for something that will arrive" from
"waiting for something that never can". This is why #281's 43 failures
presented as four unrelated symptoms across App and Runtime instead of one
cause. NOT a timeout or grace period — the fix is observability plus
fail-fast on genuinely unresolvable states, matching the committed-invariant
exception pattern established in `01f4791e`. Doing this FIRST makes #282,
#283, and every C4 route cheaper to diagnose and lets the connected gates
fail on nonzero parked entries.
## C3c placement cutover — 2026-08-02
- **#276 — OPEN — SpawnPlacementSettler discards the settle's resolved