# C4 route 4b-1 — remote placement infrastructure: pinned contract (2026-08-04) Scoping and the three-way split: [`2026-08-04-c4-route-4b-scoping-and-split.md`](2026-08-04-c4-route-4b-scoping-and-split.md). **4b-1 builds the machinery and changes NO remote behaviour.** It has no production caller, or is called for zero classifications. 4b-2 (far branch) and 4b-3 (teleport / cell-less) flip it on afterwards. The reason for landing it alone: 4b-1 is where the park-withdraws-the-entity failure mode is decided, and that decision must be reviewed on its own signal, not alongside a ~700-line class deletion. ## Scope — build these five things 1. **A per-entity remote placement owner** in `AcDream.Runtime`. 2. **A Position-time service-window guard**, with a Runtime-facing interface and implementations for both hosts. 3. **The refuse-rather-than-park policy** (see "The central decision"). 4. **N3** — headless never calls `RetryPending` after construction. 5. **Parked-count observability** wired into the ownership ledger and `report.json`. ## The central decision — refuse, do not park A Runtime `DeferredCell` park **withdraws the entity from the world**: `ParkDeferred` sets `body.InWorld = false`, clears `TransientStateFlags.Active`, suspends the object clock, calls `WithdrawCanonical`, and publishes a `Withdraw`. `RuntimeEntityObjectLifetime`'s `Forget`-on-every-accepted-Position then kills the park **without restoring any of that** — `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 entity E (invisible AND intangible); packet N+1 ~150 ms later Forgets the park; and **if N+1 classifies `Interpolate`, no placement runs and E stays withdrawn indefinitely.** The producing sequence is mundane: a remote appears beyond 96 m, walks toward you, crosses inside 96 m. **Therefore: a remote whose destination is not placeable now must NOT open a park.** It keeps its last committed pose and waits. The next packet IS the retry, because remote Positions are a 5-10 Hz stream. This is also retail-shaped — retail's world is fully resident, so "arrived but not placeable" is unrepresentable there. If you conclude the refusal cannot be expressed without touching `RuntimeSetPositionState`'s park machinery, STOP and report rather than adding a withdrawal-restore path inside a 5,652-line class. ## Two transfer errors — named so they are not repeated Route 2's controller is the architectural model. **Two of its parts must NOT be ported**, and both would look correct to an implementer copying it: - **The ack machinery.** `PositionEventOwed`, `positionEventOwed`, the `SendPositionEvent` plumbing — retail's remote arm has NO `SendPositionEvent`. `HandleReceivedPosition` @0x00453FD0 calls it only on the local-player FORCE_POSITION branch. Delete the concept, do not carry it. - **The re-issue funnel.** Route 2 re-issues because a ForcePosition is a one-shot correction ACE never repeats, so a dropped one is lost. A remote Position is a repeated stream; re-issuing packet N after N+1 has merged would apply a pose the newer packet already superseded. Same class of error as the route-2-to-4a rebucket mistake, in reverse. ## Per-entity state Route 2's `_pending` is ONE slot and `RetainPending` throws on a second live pending. That shape does not transfer. Required: - A per-key map, with route 2's single-owner invariant re-derived **per entity**. - The ownership ledger count becomes a dictionary count that must converge to zero at teardown, reset, and generation change. - Bounded, non-allocating iteration for any pump — `RuntimeFirstEntryDriveController._driveScratch` is the in-repo template. - Per-entity currency: GUID reuse, incarnation change, generation change, an entity torn down mid-drain, and two entities interleaved must all be safe. A pure function does not by itself make the CALLER re-validate identity. ## The service-window guard - **Headless has one**: `IHeadlessCollisionNeighborhood.IsWithinServiceWindow` (`HeadlessSessionWorldProjection.cs:27`, impl `:245-257`) — a Chebyshev `dx <= 1 && dy <= 1` test against `_requestedCenterLandblock`. Its only consumer today is Create-time. - **The graphical host has none.** It must be built from `GpuWorldState.IsNearTier` / `IsNearTierOrPending`, `StreamingController.NearRadius` and the observer centre, exposed to Runtime through an interface mirroring the headless one. - **Unproven, and you must establish it**: that near-tier residency is exactly co-extensive with collision publication. The four gating call sites are consistent with it; the retirement side is unverified. If it is not co-extensive, say so and propose the correct predicate rather than shipping the assumption. - `ToCellessCreateRoute` does **not** apply. It produces `AwaitFreshPosition`, its only consumer operates on a residence lease, and it refuses once `FullCellId != 0`. A steady-state Position has no residence by construction. 4b-1 needs its own "decline this placement" outcome. ## N3 — fix regardless `HeadlessSessionEventRoute.Attach` constructs the subscription with `retryPendingOnSubscribe: true` and that is the ONLY `RetryPending` call headless ever makes. `PumpFirstEntry` calls `IsReady`, `DriveAll` and `Advance` — not `RetryPending`. The graphical host binds it correctly per frame. `RuntimePlacementProjectionSubscription.OnPlacement` only projects when the new delta IS the FIFO head, so a declined head is never revisited. Add `RetryPending()` to the headless pump in the same order the graphical route uses (drives first, retry last). This is a latent defect in shipped code; fix it whether or not 4b-1 flips headless remotes. ## Must prove, not assume **`ParkCollisionResidents` throws on overlap** — for every spatial root in a retiring landblock prefix that holds an active operation (`RuntimeSetPositionState.cs:3387-3395`). With N remotes holding operations, an ordinary streaming retirement becomes session-fatal. It is unreachable today only because steady-state remotes hold no operations. **4b-1 must demonstrate it stays unreachable under the new design** — that is the concrete failure #277's bound was protecting, and it is a gate item, not a note. ## Do NOT touch - **AP-135's two writes** (`rmState.CellId`, `LastServerPos`/`LastServerPosTime`) on the airborne branches. They are 4a-owned dispositions and AP-135 does not retire with 4b. They sit inside the method 4b rewrites, which is the trap. - **`RemoteTeleportController`, `RemoteTeleportPlacement`, `remotePlacementRequired`** — 4b-3. - **The legacy far halves** in either arm — 4b-2. - **`ConstrainTo` arming.** Retail has exactly ONE site on the remote arm (@0x00454272); all three nonzero-returning `MoveOrTeleport` branches funnel through it. Do not add a second. 4b-2/4b-3 fold the existing arm outward. - **Route 1's executor.** `RuntimeAcceptedPositionRouteRequests` is shared with it; any change there is a regression in shipped functionality. ## Contract 1. The owner is Runtime-resident and presentation-independent; both hosts can drive it. 2. It has **no production caller**, or is called for zero classifications. 4b-1 changes no remote behaviour, and the existing connected routes must be unchanged. 3. No ack. No re-issue funnel. Per-entity state. 4. A non-placeable destination is refused, not parked. 5. The ownership ledger includes the per-entity pending count and converges to zero at teardown, reset, and generation change. 6. `ParkCollisionResidents`'s overlap throw is demonstrably unreachable. 7. Nothing in the "Do NOT touch" list changes. ## Acceptance - Focused Runtime tests: per-entity independence, currency across GUID reuse / incarnation / generation / teardown, the refusal outcome, ledger convergence, and the service-window predicate on both hosts. - **A test for the §"central decision" sequence specifically**: attempt a placement whose destination is not placeable, deliver a second accepted Position that classifies `Interpolate`, and assert the entity is **still in the world** — `InWorld` true, clock running, residency intact. That is the invisible-and-intangible failure, and it must be pinned before 4b-2 flips anything on. - Complete Release suite green. Baseline **10,938 passed / 4 skipped / 0 failed**. Two known flakes, do NOT chase and do NOT conflate: **#302** (`PortalProjectionTests.ClipToRegion_FrameOwnedStore_ReusesExactResultArray`, a GC-allocation assertion in App.Tests) and **#308** (`NakEmissionTests.LossSoak_...`, a wall-clock deadline in Core.Net.Tests that fails only under full-suite load). If either appears, re-run and say which. - No connected gate: 4b-1 changes no behaviour, so there is nothing for a user to observe. Say so rather than inventing one. ## Budget **~700-1,000 non-comment production lines.** Route 4a came in at 364, which was 91% of its ~400 budget — not "well under". If 4b-1 exceeds 1,000, stop and report before continuing rather than pushing through.