# C4 route 4b-3 — remote teleport + cell-less: pinned contract (2026-08-04) Split rationale: [`2026-08-04-c4-route-4b-scoping-and-split.md`](2026-08-04-c4-route-4b-scoping-and-split.md). 4b-1 (infrastructure) landed at `2e8e09ac`; 4b-2 (far snap) landed at `7f1c1f5a` after four fix rounds; the shared-core park restore it forced is at `634bc551` and inside 4b-2's rounds 3/4. Read the whole findings chain before implementing — every defect class it names reappears here at larger scale: [round 1](2026-08-04-c4-route-4b-2-review-findings.md) → [round 2 (delta)](2026-08-04-c4-route-4b-2-delta-review-findings.md) → [round 3](2026-08-04-c4-route-4b-2-round3-correction.md) → [round 4](2026-08-04-c4-route-4b-2-round4-correction.md). **4b-3 flips the LAST remote classification on: `SetPosition` — teleport (TELEPORT_TS advanced) and cell-less (the body has no committed cell) — through 4b-1's `RuntimeRemotePlacementDriveController`, runs retail's `teleport_hook` before the placement, and deletes the legacy remote-teleport machinery: `RemoteTeleportController` (605 lines), `RemoteTeleportPlacement` (85), `RemoteShadowPlacementSynchronizer` (49, two classes), the `remotePlacementRequired` predicate, the `TeleportHookRequired` timestamp plumbing, the legacy pre-operation `ConstrainTo` fallback, and the player arm's legacy `!update.IsGrounded` fallback — plus 1,709 lines of their tests.** This retires AP-137's cell-less enqueue-vs-place delta. AP-135 does NOT retire (its two writes sit physically inside the method this slice rewrites — see "Must remain true" item 8). AP-131 does not retire. #276 does not close. ## Retail ground truth — verified in `acclient_2013_pseudo_c.txt`, verify again yourself `CPhysicsObj::MoveOrTeleport` @0x00516330, teleport/cell-less branch: ``` 00516375 eax_8 = CPhysicsObj::newer_event(this_1, TELEPORT_TS, arg3); 00516386 if ((eax_8 != 0 || this_1->cell == 0)) { 005163ef CPhysicsObj::teleport_hook(this_1, edx_2); 005163f8 SetPositionStruct::SetPositionStruct(&var_64); 00516406 SetPositionStruct::SetPosition(&var_64, arg2); 00516414 SetPositionStruct::SetFlags(&var_64, 0x1012); 00516420 CPhysicsObj::SetPosition(this_1, &var_64); 00516438 return 1; 00516386 } 0051638e if (arg4 != 0) { ... near @0x005163AF / far @0x005163C1-E8 ... } 0051636d return 0; ``` Five facts in that listing decide this slice: 1. **The teleport branch is decided BEFORE the contact test** (`arg4` is only read @0x0051638E, after the branch). A teleport/cell-less packet places unconditionally — airborne wire bit, airborne body, any distance. acdream's routing must therefore decide the teleport arm AHEAD of every airborne/landing carve-out (see design decision D5). 2. **`this_1->cell == 0` is the BODY's current cell** — "this object has no resolved cell right now" — not the wire destination's cell. See D1: the classifier's current input implements a different (and dead) predicate. 3. **`teleport_hook` @0x00514ED0 runs BEFORE the placement** and is, complete (each guarded on the manager existing): `MovementManager::CancelMoveTo(0x3C)` @0x00514EDF, `PositionManager::UnStick` @0x00514EEE, `PositionManager::StopInterpolating` @0x00514EFD, `PositionManager::UnConstrain` @0x00514F0C, `TargetManager::ClearTarget` @0x00514F1B + `TargetManager::NotifyVoyeurOfEvent(Teleported_TargetStatus)` @0x00514F28, `CPhysicsObj::report_collision_end(this, 1)` @0x00514F31. 4. **`SetFlags(0x1012)`** (`Teleport|Slide|SendPositionEvent`) @0x00516414 — acdream's analog is the classifier's `AuthoritativeTeleportFlags`, carried on `route.SetPositionFlags` into `RuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement`'s `flags` parameter. (There is NO separate "teleport hook phase support" inside `RuntimeSetPositionState` — the phase lives on the route, `RuntimeTeleportHookPhase.BeforePositionOperation`, emitted at the classifier's remote teleport/cell-less branch. The handoff's phrasing implied otherwise; this is the correction.) 5. **The branch returns 1 and discards `SetPosition`'s error**, so `SmartBox::HandleReceivedPosition` @0x00453FD0 arms `ConstrainTo` @0x00454272 — the remote arm's ONLY arming site, shared by all three nonzero-returning branches, anchored `&arg2->m_position` read live (post-move). Retail arms even when the placement failed. So: hook `UnConstrain` first, place, then ONE post-operation re-arm — never a second arming site, and arm on non-commit outcomes too (the 4b-2 B8/R5 lesson). ## What must REMAIN true (process rule 1 — the contract causes the defect) For every path this slice adds or rewrites, including every refusal, contention, rejection, and short-circuit: 1. **The pose still advances.** A teleport-classified packet whose canonical placement never reached the engine (`Refused` / `Contention` / `RejectedPreparation` / `NotApplicable`) still commits the accepted destination pose to the canonical body — the same `StoresAcceptedDestination` partition and the same `StoreAcceptedDestinationPose` the far arm uses, which is retail's `store_position` @0x00515CE2 on the no-transition branch. `Deferred` and `RejectedByPlacement` do NOT store, for the reasons already pinned in the enum's own doc (`ParkDeferred` already snapped; the engine ran and refused / a settled pose must survive). Do not re-litigate the partition — extend it to the teleport arm unchanged. 2. **The render entity still advances.** The teleport arm's tail syncs `WorldEntity` (position, `ParentCellId`, rotation) from the RESOLVED body and publishes the collision shadow, exactly as both existing grounded arm tails do (`LiveEntityShadowPublisher.TryPublishRemote`). A teleport must never leave the rendered pose a packet behind the body. #312's lesson: presentation state is where this family breaks; tests must assert it (see Test plan). 3. **The object clock keeps running and the entity stays in the world** on every non-commit outcome: `body.InWorld`, `TransientStateFlags.Active`, `record.ObjectClock` active, `FullCellId != 0`, spatial projection intact, `IsSpatiallyVisible` unchanged. No park survives the controller (`CancelToken` with `restoreCancelledPark: true`); the pre-flight (`CanAttemptDestination`) stays an OPTIMISATION, never the reason a remote stops tracking or becomes invisible. 4. **The interpolation queue is empty after the teleport arm runs** — cleared by the hook's `StopInterpolating` (@0x00514EFD), NOT by the route flag: the classifier's teleport branch deliberately carries `StopInterpolating: false` because retail's clear lives inside the hook. The far arm's route-flag-driven clear is untouched. 5. **The leash is armed exactly once per accepted packet**, post-operation, anchored post-move, by the classification partition — never zero times (the current shipped hole: `remotePlacementRequired` returns ahead of every arming site while `RemoteTeleportHook` has already `UnConstrain`ed, so a remote that teleports and stands still is leash-less until the next packet), and never twice. See D4 for the complete partition. 6. **The per-packet prologue keeps running for every classification**: `TryApplyGenericRemoteRenderPose` (see D6 for the gate), the `RebucketLiveEntity(update.Guid, p.LandblockId)` spatial-bucket transaction, the velocity install (`TryCommitAuthoritativeVelocity` — retail's PositionPack `set_velocity`, upstream of `MoveOrTeleport`), and the incarnation re-validation chain. Retail's remote teleport never writes velocity itself (`ZeroVelocity` is the LOCAL player's `SmartBox::TeleportPlayer` @0x004541B4 only); the teleport arm must not add a velocity write. 7. **`AcceptedPositionSource`/authority validation is unchanged.** The teleport arm executes only an already-classified route from `ClassifyRemoteAcceptedPosition` — the shared builder (`RuntimeAcceptedPositionRouteRequests`), never a re-derivation. 8. **AP-135's two writes stay**, on both arms' airborne no-op neighbourhoods: the server-cell adopt (`rmState.CellId = p.LandblockId`) and the `LastServerPos`/`LastServerPosTime` sample. They are 4a-owned acdream-only bookkeeping for the free-fall sweep gate (`RuntimeRemotePhysicsUpdater`'s `rm.CellId != 0` gate) and the first-grounded-packet velocity synthesis. This slice rewrites the method they sit in; they do not go. The register row does not retire. 9. **`ParkCollisionResidents`'s overlap throw stays unreachable** — see "Proof obligations". 10. **`ArmLostFamilyDeadlines`' reaper gains no production caller.** `TickLostCellDeadlines` and `TryDequeueExpiredLostCell` (`RuntimeSetPositionState`) have zero production callers today; this slice must not add one. `ParkDeferred` arming the family (its `ArmLostFamilyDeadlines` call) is pre-existing and stays inert. 11. **Route 1's classification inputs are unchanged.** `RuntimeInitialCreateContinuationExecutor`'s calls into the shared builder keep their current semantics (D1 adds an overload for the remote PositionEvent path; the builder's own doc mandates "an overload here, never a third copy"). 12. **The 4a dispositions are untouched**: `NoPositionOperation` writes nothing (plus item 8's bookkeeping), `Interpolate` enqueues, the landing block still hard-snaps a wire-grounded packet for a not-in-contact body on 4a-owned classifications, AP-87's snap conditions and AP-139's landing clear are unchanged. 13. **The far arm (4b-2) is untouched** except where a shared gate widens to include the teleport arm (D6, D7) — widening must not change the far arm's own behaviour. Everything round 2's/round 4's "do not churn" lists verified stays: the `StoresAcceptedDestination` partition, the `IsRestorableQuiescencePark` relocation, the pose-parity composition, the two-arm guard/arm ordering. ## Design decisions — pinned, not open for redesign ### D1 — the classifier's cell-less input becomes the PRE-merge committed cell (resolves trap T1 and undetermined item 3) **Finding (undetermined item 3, now determined by reading the code):** the merge cannot zero a previously-nonzero `FullCellId` — it does the opposite. `RuntimeEntityObjectLifetime.TryApplyPosition` calls `Entities.RefreshSnapshot(canonical, snapshot, refreshPosition: acceptedPosition)` for every accepted Position, and `RuntimeEntityRecord.RefreshDerivedState` then executes `SetFullCell(position.LandblockId, …)` from the just-merged snapshot. So at classification time (OnPosition classifies after the authority gate's merge, before the prologue rebucket), `canonical.FullCellId` IS the accepted wire cell. A wire cell of 0 fails `LandDefs.InboundValidCellId` inside `PositionFrameValidation.IsValid`, which the classifier's `ValidPosition` check turns into `RejectedData` BEFORE the cell-less test. **Consequence: the classifier's remote `cellless` predicate, as fed today (`CommittedCellId: canonical.FullCellId` in `RuntimeAcceptedPositionRouteRequests.Build`), is unreachable for a remote PositionEvent — the remote `SetPosition` classification currently fires on `TeleportAdvanced` alone.** It is not a subset relationship with `remotePlacementRequired`; it is a dead predicate. (The classifier's own comment above the test — "only a later Runtime SetPosition or simulation commit may change FullCellId" — is falsified by `RefreshDerivedState` and must be corrected in this slice; process rule 6.) Retail's predicate is the BODY's current cell (`this_1->cell == 0` read at `MoveOrTeleport` entry, before any placement) — "this object is not resident anywhere right now", e.g. the first Position after an unwield-to-3D (`SetFullCell(0,0)` at the pickup/parent leave-world sites) or after any canonical withdrawal. The acdream value with exactly that meaning is the committed cell BEFORE this packet's merge — the `wasCellless`/`beforeCell` pair `TryApplyPosition` already measures. **Pinned:** thread the pre-merge committed cell out of the merge and into the remote classification's `CommittedCellId` input, via an overload of the shared builder (never a third copy; never a change to the existing overloads' route-1 semantics). Plumbing shape is the implementer's choice — the natural carrier is `AcceptedPhysicsTimestamps` (whose `TeleportHookRequired` field this slice deletes; replacing a policy tri-OR with a data field is strictly better) or `AcceptedPositionNetworkUpdate`. Constraints: the value is the pre-merge `FullCellId` measured by the SAME `TryApplyPosition` call that merged the packet (never re-read after the merge); no fabrication (a known record always has an honest value — 0 means "was celless", not "unknown"). **What is deliberately NOT adopted from `remotePlacementRequired`:** the graphical `projectionRequiresTeleportHook` arm (`LiveEntityRuntime.TryApplyPosition`: pre-merge `FullCellId == 0` OR `!IsSpatiallyProjected` OR `!IsSpatiallyVisible`). Its visibility half is a presentation predicate with NO retail analogue — it made "not currently rendered" fire the whole teleport machinery on a routine hot path (trap T1's warning). After this slice the teleport classification is retail's exact pair (`TeleportAdvanced || wasCellless`); a not-visible remote's Position classifies by distance like any other, the canonical placement decides placeability, and visibility remains presentation-only. This behavioural change is recorded in the AP-137 rewrite (D8). The whole `projectionRequiresTeleportHook` computation, the lifetime parameter, the headless `false` at `RuntimeLiveEntitySessionController`, and the `TeleportHookRequired` field + its `timestamps with {…}` write are deleted. ### D2 — classifier-null and `Rejected*` keep 4b-2's stated policy (resolves trap T2); the legacy airborne fallback is replaced by the retail return-0 shape The scoping doc's T2 described the pre-4b-2 world; 4b-2 already deleted the legacy near/far blocks and routed `null` / `RejectedAuthority` / `RejectedData` to `UnroutedCatchUp` (`RuntimeRemoteFarSnapPosition.ResolveArm`, AP-137). **4b-3 keeps that policy unchanged** — during the login window (null `_playerController` → classification refuses; every remote packet) remotes keep tracking through AP-87's catch-up, exactly as today. One consequence to state in the register rewrite: a TELEPORT_TS-advancing packet that arrives while classification is null consumes its teleport sequence in the timestamp gate but runs no hook — benign in the only producing window (fresh session: no moveto, stick, leash, or target exists yet to tear down), stated rather than discovered later. What T2 actually leaves 4b-3 is the player arm's legacy `!update.IsGrounded` fallback (the block whose comment says "4b deletes this fallback" — a comment this slice at last makes true), which handled wire-airborne packets for classifications 4a does not own. After D1 the unowned set shrinks to null and `Rejected*`. **Pinned replacement:** a wire-airborne packet with a null/`Rejected*` classification takes the retail return-0 shape applied to the acdream-only states — AP-135's two bookkeeping writes, no body write, no queue write, no render write, no leash arm, return. This deletes the legacy block's entity-revert quirk (`entity.SetPosition(rmState.Body.Position)`) and unifies the player and NPC arms on one leftover-airborne behaviour. Recorded in the AP-137 rewrite. ### D3 — one teleport-hook implementation, triggered by the route, run inside the teleport arm before the placement The hook trigger moves from `timestamps.TeleportHookRequired` (deleted) to the classification: `route.TeleportHookPhase == RuntimeTeleportHookPhase.BeforePositionOperation`, which the classifier already emits on exactly the remote teleport/cell-less branch. The hook runs inside the new teleport arm, immediately before `TryExecuteAcceptedRemotePosition` — retail's order (hook @0x005163EF before `SetPosition` @0x00516420), and it runs REGARDLESS of what the placement then yields (retail runs it before knowing the outcome). There must be exactly ONE hook implementation. The existing `RemoteTeleportHook.Execute` sequence — the six actions in retail order with a currency re-check between every step — is the port and must be preserved verbatim; whether the file moves into `AcDream.Runtime` (every action is expressible there: `remote.Movement.CancelMoveTo`, `rmState.Host.PositionManager.UnStick/UnConstrain`, `remote.Interp.Clear()`, `host.NotifyTeleported()`, `Physics.Engine.ShadowObjects.Suspend(localId)`) or stays an App bundle invoked from the arm is the implementer's choice. Runtime residence is preferred (it is where the sibling arm logic lives and what a future headless remote-motion consumer needs), but not at the cost of inventing a second hook path. `RemoteTeleportHookTests` (38 lines) moves or adapts with it — not silently dropped. **Undetermined item 2 is RESOLVED — yes, `EntityPhysicsHost.NotifyTeleported()` covers retail's TargetManager pair.** Verified on both sides: `NotifyTeleported` executes `_targetManager.ClearTarget()` then `_targetManager.NotifyVoyeurOfEvent(TargetStatus.Teleported)` (`src/AcDream.Runtime/Physics/EntityPhysicsHost.cs`), and retail's `teleport_hook` executes `TargetManager::ClearTarget` @0x00514F1B then `NotifyVoyeurOfEvent(Teleported_TargetStatus)` @0x00514F28 under one `target_manager != 0` guard. One-to-one; no open question remains here. ### D4 — the single `ConstrainTo` arm, and its complete partition The legacy pre-operation arming call (the `OwnsAfterOperationConstraint`-gated fallback in the player/NPC shared section, whose comment already says "4b-3 deletes it") is DELETED. The post-operation site — `RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation`, called once per arm after routing — becomes the only arm, matching retail's single site @0x00454272. Its predicate widens from `OwnsSteadyState || OwnsFarSnap` to the full retail partition: | classification (routed arm) | wire contact | retail return | arm? | |---|---|---|---| | teleport / cell-less (`SetPosition`, new arm) | any | 1 | **yes** — after the operation, on every placement outcome (retail discards the error), post-move anchor | | `Interpolate` (near) | grounded | 1 | yes (unchanged, 4a) | | `SetPositionSimple` (far) | grounded | 1 | yes (unchanged, 4b-2) | | `NoPositionOperation` (airborne no-op) | airborne | 0 | **no** (unchanged, 4a) | | null / `Rejected*` → `UnroutedCatchUp` | grounded | (no retail state; analog: nonzero) | yes — via the same post-operation site, which must therefore accept a null route for this case | | null / `Rejected*`, wire-airborne (D2 shape) | airborne | (analog: 0) | **no** — the current legacy pre-op arm DOES arm these; that was a divergence and it retires with the site | The one-packet unarmed residual on a superseded incarnation (the currency guard returning before arming) is AP-138(3) and extends to the teleport arm unchanged. This partition closes the shipped hole named in scoping correction 2: a remote hard teleport currently arms the leash NOWHERE (`remotePlacementRequired` returns ahead of every arming site after the hook's `UnConstrain`); after this slice the hook `UnConstrain`s and the single post-operation site re-arms — retail's exact sequence. ### D5 — routing order: the teleport arm precedes every contact carve-out; sticky does not suppress it Retail decides the teleport branch before reading `arg4`. Therefore: - `RuntimeRemoteFarSnapPosition.ResolveArm` (or its successor) returns the new teleport arm ahead of everything else, and `ApplyRemoteContactRouting` dispatches it BEFORE the `!remote.Body.InContact` free-flight carve-out — an airborne-body teleport packet places, it does not `AirborneSnap`. - The player arm's landing block (`!rmState.Body.InContact` hard-snap + return) must not claim a teleport-classified packet: the teleport classification is routed before it (or the block is gated to classifications it owns — implementer's choice, pinned outcome: a teleport-classified packet always reaches the teleport arm regardless of wire or body contact). - The 4a `IsAirborneNoOperation` early returns are classification-gated already and cannot claim a `SetPosition` route — unchanged. - **The NPC arm's TS-44 sticky suppression (`snapSuppressedByStick`) does not suppress the teleport arm.** Retail's sticky cannot survive a teleport — `UnStick` is the hook's second action. The suppression remains exactly as it is for the near/far/leftover arms (its register row describes an NPC-only steady-state gate, which stays true). ### D6 — the two per-packet gates widen to the teleport arm, same rule as the far arm - `TryApplyGenericRemoteRenderPose`: the gate stays `OwnsSteadyState` — the teleport arm (like the far arm) takes the early wire-pose write, and its tail re-syncs the render entity from the resolved body (invariant 2). This resolves the standing "route 4b-3 revisits the gate" comment: the answer is "unchanged, now stated"; delete the forward reference. - `TryAdoptWireCellAfterRouting`: the suppression (currently `arm is FarSnapPlacement`) widens to the teleport arm, for the same reason — after a canonical placement the placement is the cell authority; retail resolves the destination cell through `AdjustPosition`/`set_cell` and nothing writes the wire cell over it. ### D7 — the post-placement currency guard covers the teleport arm; consolidation is sanctioned Both arms' re-validation ("the far arm is re-entrant — re-validate position ownership on EVERY placement status before writing anything else, arming included") widens to `Arm is FarSnapPlacement or `. Round 2's "do not churn" explicitly named for 4b-3 that this guard "belongs behind a testable Runtime seam rather than duplicated at two App call sites" — moving the duplicated guard into the Runtime seam is sanctioned in this slice, but only if the two arms' observable ordering (guard before arm, both arms identical — the R5 invariant) is preserved and pinned by test. ### D8 — register bookkeeping, in the implementation commit - **AP-137 is REWRITTEN, not deleted.** Its cell-less enqueue-vs-place delta (part R2) retires — that is this slice's headline. But the row also records the two surviving acdream-only states (null classification during the login window; `RejectedData`/`RejectedAuthority` applied through `UnroutedCatchUp`), which have no retail mechanism and therefore keep a row. Rewrite the row to exactly the survivors plus D1's visibility-arm deletion and D2's wire-airborne leftover shape. (The handoff says "row deletion"; a deletion that silently dropped the surviving divergences would violate register rule 1 — this contract overrides that wording, and the summary reports the contradiction.) - **AD-42 must be updated**: it cites `src/AcDream.App/Physics/RemoteTeleportController.cs (ResolvePlacement)` as a surviving two-call enter-world split path. That citation dies with the class; the headless portal-arrival resync and `PhysicsEngine.ResolvePlacement` citations remain. - **AP-136 / AP-138 (D5 scoping text)**: both name "`RemoteTeleportController`'s rollback" as the shipped writer that can rebucket `record.FullCellId` to a third landblock under a retained retry. That writer is deleted; the surviving non-Position rebucket writers are the projection materializer (`DatLiveEntityProjectionMaterializer`) and the equipped-child renderer (`EquippedChildRenderController.TickChild`). Update both rows and the same claim inside `RuntimeSetPositionState`'s `CurrentCellId` doc and `RuntimeRemotePlacementDriveController.CanAttemptDestination`'s doc (process rule 6). - **AP-138's Risk column gains the teleport arm as a second producer** of the visible-without-collision residual: a remote that teleports into a non-published landblock and stands still is exactly the AP-136/AP-138 shape, now reachable through this arm. No new machinery — the row's retirement path is already #309. - **AP-135 is untouched.** ## Deletion inventory — every file, wiring site, and test Files deleted (739 production lines): | file | lines | |---|---| | `src/AcDream.App/Physics/RemoteTeleportController.cs` | 605 | | `src/AcDream.App/Physics/RemoteTeleportPlacement.cs` | 85 | | `src/AcDream.App/Physics/RemoteShadowPlacementSynchronizer.cs` (contains BOTH `RemoteShadowPlacementSynchronizer` and `RemoteTeleportPlacementPresentation`) | 49 | `src/AcDream.App/Physics/RemoteTeleportHook.cs` (57) is NOT deleted — it is the retail `teleport_hook` port and moves/re-wires per D3. Tests deleted (1,709 lines): | file | lines | |---|---| | `tests/AcDream.App.Tests/Physics/RemoteTeleportControllerTests.cs` | 1,515 | | `tests/AcDream.App.Tests/Physics/RemoteTeleportPlacementTests.cs` | 194 | `tests/AcDream.App.Tests/Physics/RemoteTeleportHookTests.cs` (38) moves with the hook. Wiring sites (the handoff's list of eight was a raw grep; the true set is below — two sites the handoff missed, and three of its entries are comment-only): | site | what happens | |---|---| | `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs` | The heart of the slice. Delete: ctor param + `_remoteTeleportController` field; `remoteHardTeleport` / `remotePlacementRequired`; `RunRemoteTeleportHook` (moves per D3); the `BeginPlacement` call; the whole `remotePlacementRequired` placement block (the `TryApply` call and its `Applied`/`Superseded` tails); the classification gate's `&& !remotePlacementRequired`; the legacy `!update.IsGrounded` fallback (D2); the legacy pre-operation `ConstrainTo` call (D4). Add: the teleport arm dispatch (D5), widened guards (D6/D7). | | `src/AcDream.App/Composition/LivePresentationComposition.cs` | Delete the `RemoteShadowPlacementSynchronizer` + `RemoteTeleportPlacementPresentation` constructions, the `remoteTeleportLease` acquisition, the `RemoteTeleport` record member, and the lease parameter threading. | | `src/AcDream.App/Composition/SessionPlayerComposition.cs` | Three `live.RemoteTeleport` pass-throughs (network-update controller ctor, teardown controller ctor, and the third composition site) — replaced by nothing; the drive controller is already threaded. **Missing from the handoff's list.** | | `src/AcDream.App/Net/LiveSessionRuntimeFactory.cs` | Delete the `RemoteTeleport` record member and the reset-plan binding `RemoteTeleport = _world.RemoteTeleport.Clear`. | | `src/AcDream.App/Net/LiveSessionResetManifest.cs` | Delete the `required Action RemoteTeleport` member and its `new("remote teleport", …)` stage. **Missing from the handoff's list.** Reset coverage is not lost: the drive controller's teardown is `DetachRoute` (cancels every live operation) and its ledger convergence is already asserted. | | `src/AcDream.App/Rendering/GameWindow.cs` | Delete the `_remoteTeleportController` field and its assignment from the composition result. | | `src/AcDream.App/Rendering/GameWindowLifetime.cs` | Delete the `RemoteTeleportController? RemoteTeleport` shutdown-root member and the `Hard("remote teleport", …)` stage. | | `src/AcDream.App/World/LiveEntityRuntimeTeardownController.cs` | Delete the ctor param, field, and the `_remoteTeleport.Forget(record)` cleanup entry. Per-entity teardown coverage is not lost: the drive controller self-heals on `IsPlacementCurrent` and `Forget`-on-accepted-Position retires operations. | | `src/AcDream.Runtime/Physics/RuntimeRemotePhysicsUpdater.cs` | **Comment-only** (two doc comments citing `RemoteTeleportPlacement.Apply` — the isCurrent-delegate note and the five-writer `Airborne` list, which becomes a four-writer list; the teleport path's `Airborne` derivation is now the canonical placement commit's, already on the list). Correct both (process rule 6). | | `src/AcDream.Runtime/Physics/RuntimeSetPositionState.cs` | **Comment-only** (the `CurrentCellId` retained-retry doc naming `RemoteTeleportController`'s rollback — D8). No code change; there is no teleport-hook machinery in this file to touch. | | `src/AcDream.Core/Physics/EntityCollisionFlags.cs` | **Comment-only** (TS-23 history note naming the pre-P3 inlined call sites). Historical statement — rewrite to past tense or leave verifiably historical; do not let it read as a live citation. | | `src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs` | Widen `OwnsPlacement`'s scope comment (it ALREADY matches `SetPosition` + `Teleport` flag — no predicate change needed for the teleport disposition); add the teleport-arm entry point (D3); correct the `Advance()`/`CanAttemptDestination` docs' writer list (D8). | | `src/AcDream.Runtime/Entities/RuntimeEntityObjectLifetime.cs` + `src/AcDream.App/World/LiveEntityRuntime.cs` + `src/AcDream.Runtime/Session/RuntimeLiveEntitySessionController.cs` + `src/AcDream.Runtime/Entities/InboundPhysicsStateController.cs` | Delete the `TeleportHookRequired` field, its `timestamps with {…}` computation, the `projectionRequiresTeleportHook` parameter/computation/`false` argument, and thread the pre-merge cell instead (D1). One doc comment in `InboundPhysicsStateController` cites `TeleportHookRequired`-adjacent bookkeeping — reword. | | `src/AcDream.App/World/LiveEntityPresentationController.cs` | `BeginAuthoritativePlacement` and `CompleteAuthoritativePlacement(deferShadowRestore: false)` lose their only production callers (`DeferShadowRestore`, `HasActivePlacement`, `HasDeferredShadowRestore` already have none), leaving `_activePlacementOwners` write-never while `IsPlacementActive` still reads it in the visibility suspend/restore gates. Delete the dead half in this commit — do not leave a zombie set that silently gates nothing — but trace the `IsPlacementActive` consumers first and state in the commit what each gate degenerates to. If tracing shows a live non-teleport dependency, STOP and report rather than deleting blind. | Test files updated (not deleted) — each references the deleted machinery incidentally: `UpdateFrameOrchestratorTests` (a `typeof(RemoteTeleportPlacementPresentation)` row), `RuntimeEntityOwnershipTests` (two `typeof(RemoteTeleportController)` exact-key assertions), `GameWindowLiveEntityCompositionTests` (`[InlineData("RunRemoteTeleportHook")]`), `LiveEntityLifecycleStressTests` (constructs the controller and calls `TryApply` — its scenario must be re-expressed against the canonical teleport arm, not dropped), `LiveSessionResetPlanTests` (the "remote teleport" stage), `CurrentGameRuntimeAdapterTests` (a noop binding), `RuntimeInitialCreateResidenceStateTests` (`RemoteTeleportSuffixIsQueuedBehindInitialAdmission` — verify what it pins; it is about suffix ordering under initial admission and likely survives with a rename), plus the eight files matching `TeleportHookRequired`. Stale comments this slice must make true (process rule 6 — verify each against the code beside it, and prefer symbol references): the `// 4b deletes this fallback` line (D2 deletes the fallback); the `// 4b-3 deletes it` on the legacy arm site (D4); the `remotePlacementRequired guarantees the classifier's teleport disposition never reaches here` route comment; `ClassifyRemoteAcceptedPosition`'s caller-doc sentence "whose remotePlacementRequired gate is already false"; `SeedRemoteSpawnPlacement`'s "Mirrors `RemoteTeleportPlacement`'s commit"; the AP-140 comment's citation of `RemoteTeleportPlacementTests.Apply_PendingGroundToSteepContact_…` as an `Airborne`-definition dependent (the test dies; the dependency list shrinks); the classifier's "only a later Runtime SetPosition or simulation commit may change FullCellId" (D1 shows it false); `TryApplyGenericRemoteRenderPose`'s "Route 4b-3 revisits the gate" (D6 resolves it). ## Proof obligations (must prove, not assume) 1. **`ParkCollisionResidents`'s overlap throw stays unreachable.** The argument is 4b-1/4b-2's, extended: every operation this route begins goes through `TryBeginExclusiveAuthoredPlacement` (one live operation per key — the Begin refuses a second), `DeferredCell` outcomes are cancelled synchronously (no park survives the controller), and retained entries are preparation retries bounded by the pre-flight re-check and the `Advance()` window-drop. The teleport arm adds packets to the same machinery, not a new operation shape. State this in the contract-conformance section of the implementation commit and keep the honest caveat 4b-1's B2 established: the guarded property is `TryAcquireCollisionPrefixMutationPermission`'s `HasOldPrefixPlacementDebt` refusal (a stall, not a throw), and the ledger convergence tests are the floor under it. 2. **Ledger convergence with teleports in flight**: teardown, session reset, and generation change converge `RemotePlacementDrivePendingCount` (both registrations) to zero with retained teleport retries present — the same suite shape 4b-1 built, driven through the new arm. 3. **The one-arm partition (D4)**: a test that counts arming calls per packet across the partition table's rows — exactly one for every "yes" row, exactly zero for every "no" row. The 4b-2 lesson (R7): assert the observable (the arm count / the anchor), not the code shape. ## What this slice does NOT do - **AP-131** (shared merge call) — C5. - **AP-135** — stays, writes preserved (invariant 8). - **#276** — `SeedRemoteSpawnPlacement` is still not classification-gated; the AD-61 settle is untouched. - **#309 / AP-136 / AP-138 residuals** — the lost-cell/hidden-until-cell-load behaviour is not built here; the teleport arm inherits the far arm's store-and-stay-visible residual and the register rows say so (D8). - **Route 5 (projectile)** — `OwnsPlacement` keeps excluding `ProjectileAuthoritative`; no widening here. - **No headless remote consumer** — `RuntimeLiveEntitySessionController` still returns early for non-local GUIDs; the vacuous-satisfaction statement stays in the interface doc and the AP-137 rewrite. - **No changes to route 2's local-player ForcePosition/teleport paths**, the local-player teleport transit (`LocalPlayerTeleportController` keeps its own `NotifyTeleported` call), or route 1's executor. - **The recorded-not-consumed route facts stay recorded-not-consumed**: `UnparentBeforeRouting` / `ApplyPlacementFrameBeforeRouting` have no reader today (the unparent edge is owned by the merge's `EndChildProjection` and the hydration recovery); this slice adds no reader and does not delete the facts. ## Test plan Tests must assert the layer that broke historically (process rule 4 — presentation/visibility, not only `InWorld`/clock/residency), and every new test must fail against a broken implementation (no source-text pins, no tautologies). Focused Runtime tests (`tests/AcDream.Runtime.Tests`): 1. Teleport-classified commit: body at resolved destination, `FullCellId` = resolved cell, hook ran (moveto cancelled, stick released, interp queue empty, leash re-armed post-operation at the post-move anchor), clock active, `InWorld`. 2. Teleport refused (destination outside the service window): pose STILL advances to the accepted destination (invariant 1), no park opened, no operation retained, entity `InWorld` + clock active + spatial root intact, AND the hook still ran (retail runs it before the placement decision). 3. Teleport `RejectedByPlacement` (engine refused): pose does NOT move; `Cancelled`-after-commit: settled pose survives. (The far arm's tests exist; these drive the teleport arm through the same partition.) 4. Cell-less classification now fires: a record whose PRE-merge committed cell is 0 (unwield-to-3D shape) classifies `SetPosition` and places unconditionally — the AP-137-retiring behaviour. Companion: the same packet with a nonzero pre-merge cell and no TELEPORT_TS advance does NOT classify `SetPosition` (proves D1's input is the pre-merge value, not the post-merge wire cell — this is the test that discriminates the fix from the shipped dead predicate). 5. D4 partition: arm-count table test (proof obligation 3), including the wire-airborne null/`Rejected*` no-arm rows and the teleport-arm-on-every-outcome row. 6. D5 ordering: an airborne-body teleport packet places (does not `AirborneSnap`, does not take the landing block); a stuck NPC's teleport packet runs the hook (`UnStick`) and places despite TS-44's suppression. 7. Currency: teleport arm's synchronous receipt deletes/replaces the incarnation → nothing further written for the packet (guard before arm, both arms — the R5 shape, now for the teleport arm). 8. Ledger/teardown: proof obligation 2. App-layer tests (`tests/AcDream.App.Tests`): 9. **The presentation assertion (#312's layer):** after a remote teleport commit, the render `WorldEntity` pose equals the resolved body pose, `ParentCellId` equals the resolved cell, the entity is spatially visible, and the collision shadow was published. After a refused teleport, the render pose tracks the stored destination and the entity REMAINS visible. 10. D2's leftover-airborne shape: wire-airborne null-classified packet writes exactly AP-135's two fields and nothing else (body, entity, queue, leash all untouched). 11. AP-135 preservation on the rewritten arms (both airborne no-op paths). 12. The generic render-pose + wire-cell-adoption gates: teleport arm takes the early write and suppresses the post-routing wire-cell adopt (D6) — asserted through the existing extracted entry points, not restated logic. Live-execution proof (process rule 5): a `[remote-teleport]` probe line — `PhysicsDiagnostics`-owned, `ACDREAM_PROBE_REMOTE_TELEPORT=1`, one line per routed teleport arm with guid, cause (`teleport-ts` vs `cellless`), hook-ran, and placement status, marked TEMPORARY with the existing probe family. The connected gate below is recorded as a pass ONLY if the probe line shows the new arm executed (a clean-looking session with zero probe lines is a not-run, exactly like #309's park probe). ## Gates - Focused Runtime + App tests above. - Complete Release suite: `$env:ACDREAM_PAK_PATH` set, `dotnet test AcDream.slnx -c Release -m:1`. **Baseline 11,027 passed / 4 skipped / 0 failed** at `2eb39a02`. The net count will move (1,709 test lines deleted, new tests added) — measure and record the new figure; do not inherit 11,027 as the expectation. Two known flakes, do not chase and do NOT conflate: **#302** (`PortalProjectionTests.ClipToRegion_FrameOwnedStore_…`, GC-allocation assertion, App.Tests) and **#308** (`NakEmissionTests.LossSoak_…`, wall-clock deadline, Core.Net.Tests, full-suite load only). If either appears, re-run and say which. - **Two-client connected teleport gate (user-run).** **CORRECTION (2026-08-04 fix round, both independent Opus reviews):** the recipe below originally specified "a second character" as the teleport target. That is WRONG — a player-character target cannot exercise this arm's NPC/creature code path at all, and two of the three MAJOR defects the fix round found (A1's zero-arm leash regression, A2/R3's synthesized-velocity/run-cycle defect) are BOTH on the NPC-guid branch only; `RemoteServerControlledVelocityCycle.Apply` itself early-returns for any `0x50xxxxxx` guid, so a player-target run would report a clean pass while both defects shipped underneath it. **The target MUST be an NPC/creature** (an ACE admin teleport — `@teleto` / `@teleloc` — applied to a drudge/mosswart/etc., NOT a second player character) for the gate to see what it is supposed to see. Recipe: acdream stands as observer; the creature target is teleported with an ACE admin teleport (`@teleto` / `@teleloc`) — those route through `Teleport()` / `SendUpdatePosition(true)` and advance **ObjectTeleport** (TELEPORT_TS), which is exactly this arm's trigger on the observer (`@pklite` is route 2's ForcePosition lever, NOT this gate — see [`2026-08-03-c4-route-2-visual-gate.md`](2026-08-03-c4-route-2-visual-gate.md)). Run with `ACDREAM_PROBE_REMOTE_TELEPORT=1`. Correct: the observed creature vanishes from the old spot and appears at the destination in one step (no glide, no interpolated streak), **stands STILL with correct idle animation — NOT sprinting/running in place** (A2/R3's specific symptom: a synthesized teleport-distance velocity planning a RunForward cycle at the destination), and moves normally afterward (leash re-armed: no rubber-band, no tether — A1's specific symptom is the ABSENCE of a rubber-band/re-anchor where one should exist, since a creature that was knocked airborne on its first accepted Position after this teleport would otherwise never arm at all). Also teleport the remote OUT of view and back: it must re-appear correctly. Regressions to watch: a remote gliding across the map at teleport (queue not cleared), freezing at the old spot (the 4b-2 round-1 freeze class), invisible-but-audible at the destination (#312 class), invisible-but-solid (#184 class), a rubber-band after arrival, or the creature sprinting/running in place at the destination (A2/R3). Confirm at least one `[remote-teleport]` line per teleport, with the expected cause. Graceful close (ACE session-clear) per the standing rule. ## Budget **~400-700 non-comment production lines** (net LOC strongly negative — the deletions are 739 production + 1,709 test lines). For calibration: 4a was 364, 4b-1 was 230+57, 4b-2 landed within its 350-500. Exceed 700 new lines and STOP and report rather than pushing through. ## Open questions routed to the retail-conformance reviewer 1. **D1's evidence chain** (the merge stamps `FullCellId` with the wire cell before classification, making the shipped cell-less predicate dead) rests on the reading `TryApplyPosition` → `RefreshSnapshot(refreshPosition: acceptedPosition)` → `RefreshDerivedState` → `SetFullCell(wire cell)`, with classification after the gate and before the prologue rebucket. Verify independently — it is the load-bearing claim of this contract, and if it is wrong the D1 plumbing is unnecessary churn. 2. **`report_collision_end(this, 1)` ↔ `ShadowObjects.Suspend(localEntityId)`**: the existing hook maps retail's collision-end report to a broadphase shadow suspension. This mapping predates 4b-3 and is carried, not re-derived. Confirm against @0x00514F31's callee that the "1" argument (report-to-partners) has no unported half, or file the delta on the AP-137 successor row. 3. **The `LiveEntityPresentationController._activePlacementOwners` deletion** (deletion-inventory last row): confirm by reading `IsPlacementActive`'s two consumers that removing the write-never set cannot change a Hidden/UnHide or visibility-edge restore for non-teleport entities. 4. **`RemoteTeleportSuffixIsQueuedBehindInitialAdmission`** (`RuntimeInitialCreateResidenceStateTests`): confirm what it pins and that it survives (renamed) rather than being deleted as collateral. ## Contradictions with the handoff/scoping docs — reported, not smoothed over - **The handoff's "AP-137 row deletion belongs in the implementation commit"** conflicts with the row's own content: null and `Rejected*` are acdream-only divergences that survive this slice and must keep a row. Pinned as rewrite-in-place (D8). If the reviewer prefers delete-and-refile-narrow, either satisfies register rule 1; silent whole-row deletion does not. - **The handoff's wiring-site list (8 sites) is a raw grep, not a wiring list**: `SessionPlayerComposition.cs` and `LiveSessionResetManifest.cs` are real wiring sites it missed; `RuntimeRemotePhysicsUpdater.cs`, `RuntimeSetPositionState.cs`, and `EntityCollisionFlags.cs` are comment-only. - **The handoff's "`RuntimeSetPositionState.cs` — the teleport-hook phase support and SetFlags analog"**: there is no teleport-hook phase support in that file. The phase lives on the classifier route (`RuntimeTeleportHookPhase`), and the SetFlags analog is the `flags` parameter of `TryPrepareAndSubmitAuthoredPlacement` fed from `route.SetPositionFlags`. - **The scoping doc's T1 ("they disagree in both directions")** understates the finding: as fed today the classifier's cell-less predicate is not merely different — it is unreachable for remote PositionEvents (D1). The design decision is therefore not "reconcile two predicates" but "feed the classifier retail's predicate at all". - **The scoping doc's T2** describes the pre-4b-2 tree; 4b-2 already established the null/`Rejected*` policy. What 4b-3 actually decides is the wire-airborne leftover shape (D2), which neither doc names. --- ## Connected gate RESULT — PASSED 2026-08-04 (user-run, user-accepted) Run against the exact `6dc7ba51` Release binary with the retail UI (`ACDREAM_RETAIL_UI=1`) and `ACDREAM_PROBE_REMOTE_TELEPORT=1`, live ACE at `127.0.0.1:9000`. Log: `4b3-gate-retailui.log` (440 lines, graceful exit 0). **User verdict: "all works"** — no glide/streak on arrival, correct standing animation, normal movement afterward, no rubber-band or tether. **Probe evidence (this is what makes it a pass rather than a clean-looking session):** 16 `[remote-teleport]` lines across 7 distinct creatures, every one `hookRan=True placement=Committed`. Every guid is in the `0x8xxxxxxx` creature range — NOT `0x50xxxxxx` — so the run exercised the NPC-guid branch, which is where all three of the fix round's NPC-arm MAJORs lived (A1's zero-arm leash regression, R1's missing D2 shape, R3/A2's synthesized run-cycle velocity). The corrected creature-target recipe is therefore validated in practice, not just in argument. **HONEST GAP — the cell-less half is NOT live-verified.** All 16 lines are `cause=teleport-ts`; `cause=cellless` was never observed. So the arm's TELEPORT_TS trigger is proven live and its cell-less trigger (retail's `this_1->cell == 0` @0x00516386 — a body with no committed cell at all, e.g. after an unwield-to-3D `SetFullCell(0,0)` or any canonical withdrawal) is covered by tests only. This is deliberately recorded rather than folded into a blanket "gate passed", because it is exactly 4b-2's #309 shape: that session's 11 park probes were all one cause, the other cause went unexercised, and without the probe the session would have been recorded as full coverage. **Superseded 2026-08-04 by C4 route 7 (see `docs/research/2026-08-04-c4-route-7-contract.md` §11).** The recipe below — "the unwield-to-3D path is the cheapest reachable trigger" — no longer fires. Route 7 ported retail's parent-cell propagation (`docs/research/2026-08-04-retail-parent-cell-propagation.md`): retail's `unset_parent` does no cell work, so a wielded child's unwield Position reaches `MoveOrTeleport` with `this->cell` = the parent's cell — NON-zero — and retail's cell-less branch never fires for unwield either. A committed child's canonical `FullCellId` is now deterministically the parent's (nonzero whenever the parent is celled), so an unwield Position classifies by TELEPORT_TS/distance like any other packet, exactly matching retail's predicate population. This is a correctness fix, not a regression: the OLD recipe only worked because a parented child's canonical cell was whatever the render-tick writer last produced — zero headless and zero in any pre-first-tick window — which was itself the #184-class defect route 7 closed. **To close the honest gap now:** provoke a Position on a body that is genuinely withdrawn/never-celled at merge time — a body between `CommitWithdrawal`/`CommitAcceptedParentCellless`'s cell-less edge and its next accepted Position, or a body whose initial Create never resolved a cell. Whether ACE ever emits an UpdatePosition in that exact window is UNESTABLISHED; this needs its own investigation before a live recipe can be written down. Test 4 in this contract's plan (`4b-3`, "unwield-to-3D shape classifies `SetPosition`") remains a valid Runtime-level fixture — it directly constructs a record with `PreMergeCommittedCellId == 0` — but it is a SYNTHETIC pre-merge-cell-0 fixture, not a live unwield behavior, and must not be re-labeled as one.