acdream/docs/research/2026-08-04-c4-route-4b-3-contract.md
Erik 21cd6e9b2b docs: record C4 route 4b-3's connected gate as passed, with its cell-less gap
User-run against the exact 6dc7ba51 Release binary with the retail UI; user
verdict "all works". The probe proves the run actually exercised the arm: 16
[remote-teleport] lines over 7 creatures, all hookRan=True placement=Committed,
every guid in the 0x8xxxxxxx creature range rather than the 0x50xxxxxx player
range — so the corrected creature-target recipe reached the NPC-guid branch
where all three NPC-arm MAJORs lived.

Recorded as a partial pass, not a blanket one: all 16 lines are
cause=teleport-ts and cause=cellless was never observed, so the cell-less half
of the same arm remains test-covered only. Folding that into "gate passed"
would repeat 4b-2's #309 shape, where every park probe shared one cause and the
unexercised cause went unrecorded.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 16:09:32 +02:00

46 KiB

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. 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 1round 2 (delta)round 3round 4.

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 UnConstrained, 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 UnConstrains 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 <TeleportArm>. 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).
  • #276SeedRemoteSpawnPlacement 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 consumerRuntimeLiveEntitySessionController 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):

  1. 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.
  2. 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).
  3. AP-135 preservation on the rewritten arms (both airborne no-op paths).
  4. 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). 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 TryApplyPositionRefreshSnapshot(refreshPosition: acceptedPosition)RefreshDerivedStateSetFullCell(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.

To close it later: provoke a Position on an entity whose canonical cell is 0 — the unwield-to-3D path is the cheapest reachable trigger — and confirm one cause=cellless line. Do not treat it as closed by a teleport-ts session.