From dda1e2a03a82334bc6ef14ae8bf52246cdff7b19 Mon Sep 17 00:00:00 2001 From: Erik Date: Tue, 4 Aug 2026 00:51:32 +0200 Subject: [PATCH] =?UTF-8?q?docs:=20C4=20route=204b=20scoping=20=E2=80=94?= =?UTF-8?q?=20split=20three=20ways,=20and=20two=20corrections?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scoping at 44830a0e puts 4b at 1,300-2,200 production lines (centred ~1,700) plus ~2,500-3,500 lines of test work — 4-6x route 4a and ~2x route 2, the two largest landings in this campaign, which took 4 and 5 review rounds. Split into 4b-1 (infrastructure, no behaviour change), 4b-2 (far branch), 4b-3 (teleport and cell-less, with the ~739-line class deletions). 4b-1 stays separate regardless of appetite for landings. Corrects two errors in documents from yesterday: AP-135 does NOT retire with 4b. Its own condition is retirement with the free-fall sweep gate, which 4b does not touch, and its sites are the airborne no-op branches — 4a-owned dispositions. The trap is that its two writes sit inside OnPosition, which 4b rewrites heavily. Retail has exactly ONE ConstrainTo on the remote arm (@0x00454272); all three nonzero-returning MoveOrTeleport branches funnel through it. My route-4 scoping implied a distinct remote-teleport arming site. There is none, so 4b must not add a second one — the post-operation arm 4a introduced becomes the only arm. Records a new failure mode 4b must not create: a DeferredCell park WITHDRAWS the entity (InWorld false, Active cleared, clock suspended, residency dropped), and Forget-on-every-accepted-Position kills the park without restoring any of it. If the next packet classifies Interpolate, no placement runs and the remote stays withdrawn indefinitely — invisible AND intangible, the #184 class through a third door. Direction: refuse rather than park; the next packet is the retry, because remote Positions are a 5-10 Hz stream. Two transfer errors named explicitly so they are not repeated: do not port route 2's re-issue funnel (re-issuing a superseded pose is wrong for a repeated stream), and do not port its ack machinery (retail's remote arm has no SendPositionEvent). Also records that remotePlacementRequired fires for every non-visible remote on the graphical host — a routine hot path, not a teleport rarity — and that deleting the legacy blocks removes the only handler for null/Rejected*, which during the login window is every remote packet. Co-Authored-By: Claude Opus 5 --- docs/plans/2026-08-02-placement-cutover.md | 19 ++- ...026-08-04-c4-route-4b-scoping-and-split.md | 154 ++++++++++++++++++ 2 files changed, 171 insertions(+), 2 deletions(-) create mode 100644 docs/research/2026-08-04-c4-route-4b-scoping-and-split.md diff --git a/docs/plans/2026-08-02-placement-cutover.md b/docs/plans/2026-08-02-placement-cutover.md index ef589e7a..5be90fd7 100644 --- a/docs/plans/2026-08-02-placement-cutover.md +++ b/docs/plans/2026-08-02-placement-cutover.md @@ -297,8 +297,23 @@ same commit) → docs/handoff commit. No workarounds; no fused slices. own live evidence, not a silent convergence. - Register row **AP-135** (the airborne no-op's retained acdream bookkeeping: the server cell id for the free-fall sweep gate, and the - last-server-position sample) retires with 4b's transition machinery, not - before. + last-server-position sample) — **CORRECTED 2026-08-04: this row does NOT + retire with 4b.** Its own stated condition is retirement together with the + free-fall sweep gate (`RuntimeRemotePhysicsUpdater.cs:342`), which 4b does + not touch, and its sites are the airborne no-op branches — 4a-owned + dispositions, not 4b's far-snap/teleport/cell-less. The trap is that those + two writes sit physically inside `OnPosition`, which 4b rewrites heavily, + so an implementer will assume they go. They stay. See + [`2026-08-04-c4-route-4b-scoping-and-split.md`](../research/2026-08-04-c4-route-4b-scoping-and-split.md). + - **4b is itself split into 4b-1 / 4b-2 / 4b-3** (2026-08-04). Scoping put + 4b at 1,300-2,200 production lines — 4-6x route 4a — plus ~2,500-3,500 + lines of test work. 4b-1 is infrastructure with no remote behaviour change + (the per-entity placement owner, the service-window guard, the + refuse-rather-than-park policy, N3's headless `RetryPending` pump); 4b-2 is + the far branch alone; 4b-3 is teleport/cell-less and the ~739-line class + deletions. 4b-1 stays a separate landing regardless: it is where the + park-withdraws-the-entity failure mode is decided, and it must not be + reviewed alongside a large deletion. Note the route-4 Create half is ALREADY DONE (C3b/C3c); the remaining work is steady-state remote Position plus the deletions. AP-131 is NOT retired by either sub-slice — see the scoping doc for why route 4 alone cannot. diff --git a/docs/research/2026-08-04-c4-route-4b-scoping-and-split.md b/docs/research/2026-08-04-c4-route-4b-scoping-and-split.md new file mode 100644 index 00000000..773fb7b7 --- /dev/null +++ b/docs/research/2026-08-04-c4-route-4b-scoping-and-split.md @@ -0,0 +1,154 @@ +# C4 route 4b — scoping, a three-way split, and two doc corrections (2026-08-04) + +Scoped at HEAD `44830a0e` (route 4a landed). **4b is 4-6x route 4a and must be +split again.** This also corrects two errors in documents I wrote yesterday. + +## Correction 1 — the plan is wrong about AP-135 + +`docs/plans/2026-08-02-placement-cutover.md` says AP-135 *"retires with 4b's +transition machinery, not before."* **That is wrong.** + +AP-135's own retirement condition is *"retire together with the free-fall sweep +gate, when the remote arc is resolved by the same transition machinery the local +player uses"* — that gate is `RuntimeRemotePhysicsUpdater.cs:342`, which 4b does +not touch. Worse, AP-135's sites are the **airborne no-op** branches, which are +**4a-owned dispositions**, not 4b's. 4b owns far-snap, teleport and cell-less. + +**AP-135 stays open after 4b.** The trap is real: its two writes sit physically +inside `OnPosition`, which 4b rewrites heavily, so an implementer will assume +they go. The contract must say they do not. + +## Correction 2 — retail has ONE `ConstrainTo` on the remote arm, not two + +My route-4 scoping described the third divergence as retail *"re-arming after +`teleport_hook`'s `UnConstrain`"*, implying a distinct remote-teleport site. +There is none. Retail has exactly one: `ConstrainTo` @0x00454272, and **all +three** nonzero-returning `MoveOrTeleport` branches — teleport +(returns 1 @0x00516438), near-interpolate (@0x005163BE), far-snap (@0x005163E8) +— funnel through it. + +Consequence for the contract: **4b must not add a second arming site.** The +single post-operation arm 4a introduced becomes the ONLY arm, with +`remotePlacementRequired` folded into it rather than returning ahead of it. + +The behaviour claim still stands: today a remote hard-teleport arms the leash +nowhere, because the `remotePlacementRequired` block returns before every arming +site while `RemoteTeleportHook` has already called `UnConstrain`. Severity is +narrow — the next Position re-arms ~100-200 ms later — except for a remote that +teleports and then stands still, since ACE stops broadcasting for a stationary +entity. + +## The new failure mode 4b must not create + +**A Runtime `DeferredCell` park WITHDRAWS the entity from the world.** +`ParkDeferred` sets `body.InWorld = false`, clears `Active`, suspends the object +clock, calls `WithdrawCanonical`, and publishes a `Withdraw`. + +`Forget`-on-every-accepted-Position then kills the park **without restoring +any of it**. `CancelCoreDeferred` removes the operation and rewrites the +`Withdraw` into a `Discard`; it does not set `InWorld` back, resume the clock, +or re-enter residency. + +So: packet N parks E (now invisible AND intangible). Packet N+1 ~150 ms later +Forgets the park. **If N+1 classifies `Interpolate` — near, in contact, +committed cell — no placement runs and E stays withdrawn indefinitely.** The +sequence "remote appears beyond 96 m, walks toward you, crosses inside 96 m" +produces it. That is the #184 class through a third door, and it is worse than +#184 because the entity is intangible too. + +**Required direction: refuse rather than park.** A remote whose destination is +not placeable keeps its last committed pose and waits for the next packet — the +next packet IS the retry, because remote Positions are a 5-10 Hz stream. That is +retail-shaped (retail's world is fully resident; "arrived but not placeable" is +unrepresentable) and avoids withdrawal-restore surgery inside a 5,652-line class. + +**Do NOT port route 2's re-issue funnel.** It exists because a ForcePosition is +a one-shot correction ACE never repeats. Re-issuing remote packet N after N+1 +has merged would apply a pose the newer packet already superseded — the same +class of route-2-to-4a transfer error that cost a review round, in reverse. + +**Do NOT port the ack machinery.** Retail's remote arm has no +`SendPositionEvent`; the whole `PositionEventOwed` apparatus has no analogue. + +## Two traps that will bite an implementer + +**T1 — `remotePlacementRequired` and the classifier's `cellless` are different +predicates.** `remotePlacementRequired` derives from `wasCellless` measured +BEFORE the merge, plus `projectionRequiresTeleportHook` — which on the graphical +host includes `!IsSpatiallyVisible`, so **it fires for every remote that is not +currently visible**, a routine hot path rather than a teleport rarity. The +classifier's `cellless` reads `FullCellId` AFTER the merge. They disagree in +both directions. Reconciling them is a design decision, not a rename. + +**T2 — deleting the legacy near/far blocks removes the only handler for `null` +and `Rejected*`.** `ClassifyRemoteAcceptedPosition` returns null whenever +`_playerController` is null — **which during the login window is every remote +packet.** Today those fall into the legacy blocks and hard-snap. Delete the +blocks without a replacement and remotes will not move at all until the local +controller exists. Retail has no analogue; this needs a stated acdream policy. + +## The split + +Estimate: **1,300-2,200 non-comment production lines, centred ~1,700**, plus +~2,500-3,500 lines of test work. That is 4-6x route 4a and ~2x route 2 — the two +largest landings in this campaign, which took 4 and 5 review rounds. + +- **4b-1 — infrastructure, no remote behaviour change (~700-1,000 lines).** + The per-entity remote placement owner (route 2's controller minus the ack and + re-issue funnel, plus an N-way pending map and ledger); the service-window + guard (Runtime interface + a graphical implementation over + `GpuWorldState`/`StreamingController`, which does not exist today); the + refuse-rather-than-park policy; N3's headless `RetryPending` pump; and the + `report.json` parked-count wiring #277 asks for. **No production caller, or + called for zero classifications.** Gate: focused Runtime tests, Release suite, + existing connected routes unchanged, and a proof that + `ParkCollisionResidents`'s overlap throw is unreachable. + *This is where the park semantics get decided and reviewed on their own, + without a behaviour change confusing the signal.* +- **4b-2 — the far branch only (~350-500 lines).** `SetPositionSimple`, + `PlayerDistance >= 96 m`. Deletes both duplicated `96f`/`4f` constant pairs and + both fabricated `Vector3.Zero` reads. Trivially observable. +- **4b-3 — teleport and cell-less (~400-700 lines).** Deletes + `RemoteTeleportController` (605), `RemoteTeleportPlacement` (85), + `RemoteShadowPlacementSynchronizer` (49) and 7 wiring sites, plus 1,709 lines + of their tests. Largest blast radius; needs the two-client teleport gate. + +If the campaign will not tolerate three landings, merge 4b-2 into 4b-3 — but +**keep 4b-1 separate.** It is where the invisible-remote failure decides, and it +must not be reviewed at the same time as a 700-line deletion. + +## What 4b does NOT do + +- **AP-131 is not retired.** Its site is one shared merge call serving every + entity kind; threading classified flags there necessarily changes the local + player's ordinary Apply, which no route owns. Stays for C5. +- **AP-135 is not retired.** Correction 1. +- **#276 is not closed.** 4b retires its remote-placement half by construction + (the canonical transaction owns cell and contact together), but + `SeedRemoteSpawnPlacement` is not classification-gated and still fires under + `Interpolate`, and the AD-61 local-player settle is untouched. Narrow its + scope; do not pin it closed. + +## Two collateral hazards + +- **`ParkCollisionResidents` throws on overlap** — for every spatial root in a + retiring landblock prefix holding an active operation. With N remotes holding + operations, an ordinary streaming retirement becomes session-fatal. Unreachable + today only because steady-state remotes hold no operations. Any 4b design must + prove it stays unreachable. +- **Lost-cell deadlines leak their family.** `ArmLostFamilyDeadlines` arms root + + equipped children; the cancel path clears the root only, and no caller passes + `cancelLostFamily: true`. Inert today because the reaper has no production + caller — do not let 4b be the commit that makes it live. + +## Undetermined — flagged, not guessed + +1. Whether `GpuWorldState.IsNearTier` residency is exactly co-extensive with + collision publication (gating sites are consistent; the retirement side is + unverified). +2. Whether `EntityPhysicsHost.NotifyTeleported()` covers retail's + `TargetManager::ClearTarget` @0x00514F1B. +3. Whether the merge can zero a previously-nonzero `FullCellId`, which decides + whether classifier-`cellless` is a strict subset of `remotePlacementRequired`. +4. Allocation cost of the current legacy far/teleport path — no gate covers + `PhysicsEngine.SetPosition`.