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>
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 1 →
round 2 (delta) →
round 3 →
round 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:
- The teleport branch is decided BEFORE the contact test (
arg4is 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). this_1->cell == 0is 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.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.SetFlags(0x1012)(Teleport|Slide|SendPositionEvent) @0x00516414 — acdream's analog is the classifier'sAuthoritativeTeleportFlags, carried onroute.SetPositionFlagsintoRuntimeSetPositionState.TryPrepareAndSubmitAuthoredPlacement'sflagsparameter. (There is NO separate "teleport hook phase support" insideRuntimeSetPositionState— 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.)- The branch returns 1 and discards
SetPosition's error, soSmartBox::HandleReceivedPosition@0x00453FD0 armsConstrainTo@0x00454272 — the remote arm's ONLY arming site, shared by all three nonzero-returning branches, anchored&arg2->m_positionread live (post-move). Retail arms even when the placement failed. So: hookUnConstrainfirst, 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:
- 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 sameStoresAcceptedDestinationpartition and the sameStoreAcceptedDestinationPosethe far arm uses, which is retail'sstore_position@0x00515CE2 on the no-transition branch.DeferredandRejectedByPlacementdo NOT store, for the reasons already pinned in the enum's own doc (ParkDeferredalready snapped; the engine ran and refused / a settled pose must survive). Do not re-litigate the partition — extend it to the teleport arm unchanged. - 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). - The object clock keeps running and the entity stays in the world on
every non-commit outcome:
body.InWorld,TransientStateFlags.Active,record.ObjectClockactive,FullCellId != 0, spatial projection intact,IsSpatiallyVisibleunchanged. No park survives the controller (CancelTokenwithrestoreCancelledPark: true); the pre-flight (CanAttemptDestination) stays an OPTIMISATION, never the reason a remote stops tracking or becomes invisible. - 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 carriesStopInterpolating: falsebecause retail's clear lives inside the hook. The far arm's route-flag-driven clear is untouched. - 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:
remotePlacementRequiredreturns ahead of every arming site whileRemoteTeleportHookhas alreadyUnConstrained, so a remote that teleports and stands still is leash-less until the next packet), and never twice. See D4 for the complete partition. - The per-packet prologue keeps running for every classification:
TryApplyGenericRemoteRenderPose(see D6 for the gate), theRebucketLiveEntity(update.Guid, p.LandblockId)spatial-bucket transaction, the velocity install (TryCommitAuthoritativeVelocity— retail's PositionPackset_velocity, upstream ofMoveOrTeleport), and the incarnation re-validation chain. Retail's remote teleport never writes velocity itself (ZeroVelocityis the LOCAL player'sSmartBox::TeleportPlayer@0x004541B4 only); the teleport arm must not add a velocity write. AcceptedPositionSource/authority validation is unchanged. The teleport arm executes only an already-classified route fromClassifyRemoteAcceptedPosition— the shared builder (RuntimeAcceptedPositionRouteRequests), never a re-derivation.- AP-135's two writes stay, on both arms' airborne no-op
neighbourhoods: the server-cell adopt (
rmState.CellId = p.LandblockId) and theLastServerPos/LastServerPosTimesample. They are 4a-owned acdream-only bookkeeping for the free-fall sweep gate (RuntimeRemotePhysicsUpdater'srm.CellId != 0gate) 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. ParkCollisionResidents's overlap throw stays unreachable — see "Proof obligations".ArmLostFamilyDeadlines' reaper gains no production caller.TickLostCellDeadlinesandTryDequeueExpiredLostCell(RuntimeSetPositionState) have zero production callers today; this slice must not add one.ParkDeferredarming the family (itsArmLostFamilyDeadlinescall) is pre-existing and stays inert.- 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"). - The 4a dispositions are untouched:
NoPositionOperationwrites nothing (plus item 8's bookkeeping),Interpolateenqueues, 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. - 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
StoresAcceptedDestinationpartition, theIsRestorableQuiescenceParkrelocation, 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, andApplyRemoteContactRoutingdispatches it BEFORE the!remote.Body.InContactfree-flight carve-out — an airborne-body teleport packet places, it does notAirborneSnap.- The player arm's landing block (
!rmState.Body.InContacthard-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
IsAirborneNoOperationearly returns are classification-gated already and cannot claim aSetPositionroute — unchanged. - The NPC arm's TS-44 sticky suppression (
snapSuppressedByStick) does not suppress the teleport arm. Retail's sticky cannot survive a teleport —UnStickis 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 staysOwnsSteadyState— 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 (currentlyarm 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 throughAdjustPosition/set_celland 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/RejectedAuthorityapplied throughUnroutedCatchUp), 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 andPhysicsEngine.ResolvePlacementcitations remain. - AP-136 / AP-138 (D5 scoping text): both name "
RemoteTeleportController's rollback" as the shipped writer that can rebucketrecord.FullCellIdto 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 insideRuntimeSetPositionState'sCurrentCellIddoc andRuntimeRemotePlacementDriveController.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)
ParkCollisionResidents's overlap throw stays unreachable. The argument is 4b-1/4b-2's, extended: every operation this route begins goes throughTryBeginExclusiveAuthoredPlacement(one live operation per key — the Begin refuses a second),DeferredCelloutcomes are cancelled synchronously (no park survives the controller), and retained entries are preparation retries bounded by the pre-flight re-check and theAdvance()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 isTryAcquireCollisionPrefixMutationPermission'sHasOldPrefixPlacementDebtrefusal (a stall, not a throw), and the ledger convergence tests are the floor under it.- 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. - 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 —
SeedRemoteSpawnPlacementis 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) —
OwnsPlacementkeeps excludingProjectileAuthoritative; no widening here. - No headless remote consumer —
RuntimeLiveEntitySessionControllerstill 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 (
LocalPlayerTeleportControllerkeeps its ownNotifyTeleportedcall), or route 1's executor. - The recorded-not-consumed route facts stay recorded-not-consumed:
UnparentBeforeRouting/ApplyPlacementFrameBeforeRoutinghave no reader today (the unparent edge is owned by the merge'sEndChildProjectionand 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):
- 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. - 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). - 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.) - Cell-less classification now fires: a record whose PRE-merge committed
cell is 0 (unwield-to-3D shape) classifies
SetPositionand places unconditionally — the AP-137-retiring behaviour. Companion: the same packet with a nonzero pre-merge cell and no TELEPORT_TS advance does NOT classifySetPosition(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). - 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. - 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. - 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).
- Ledger/teardown: proof obligation 2.
App-layer tests (tests/AcDream.App.Tests):
- The presentation assertion (#312's layer): after a remote teleport
commit, the render
WorldEntitypose equals the resolved body pose,ParentCellIdequals 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. - 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).
- AP-135 preservation on the rewritten arms (both airborne no-op paths).
- 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_PATHset,dotnet test AcDream.slnx -c Release -m:1. Baseline 11,027 passed / 4 skipped / 0 failed at2eb39a02. 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.Applyitself early-returns for any0x50xxxxxxguid, 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 throughTeleport()/SendUpdatePosition(true)and advance ObjectTeleport (TELEPORT_TS), which is exactly this arm's trigger on the observer (@pkliteis route 2's ForcePosition lever, NOT this gate — see2026-08-03-c4-route-2-visual-gate.md). Run withACDREAM_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
- D1's evidence chain (the merge stamps
FullCellIdwith the wire cell before classification, making the shipped cell-less predicate dead) rests on the readingTryApplyPosition→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. 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.- The
LiveEntityPresentationController._activePlacementOwnersdeletion (deletion-inventory last row): confirm by readingIsPlacementActive's two consumers that removing the write-never set cannot change a Hidden/UnHide or visibility-edge restore for non-teleport entities. 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.csandLiveSessionResetManifest.csare real wiring sites it missed;RuntimeRemotePhysicsUpdater.cs,RuntimeSetPositionState.cs, andEntityCollisionFlags.csare 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 theflagsparameter ofTryPrepareAndSubmitAuthoredPlacementfed fromroute.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.