acdream/docs/research/2026-08-04-c4-route-5-contract.md
Erik 36255af0f6 fix(physics): C4 route 5 — projectile authoritative placement (#276 partial)
Ports retail's missile Position handling into the canonical Runtime
placement owner instead of the deleted ApplyAuthoritativePosition
short-circuit. The Create/residence-window halves of the projectile
pipeline (RuntimeProjectile binding, TryBind's adopted-body branch,
the collision/shadow registration) were already canonical from prior
slices; this closes the remaining gap — how an ACCEPTED Position for
an in-flight missile is classified, placed, and presented.

Byte-decode (Step 1 hard gate, before any code was written):
CPhysicsObj::MoveOrTeleport @0x00516330-0x00516438 disassembled from
the PDB-paired binary (Capstone, x86 32-bit thiscall). `ret 0x10`
establishes four stack args; [esp+0x7c] (arg5, the velocity pointer)
is never referenced in any of the three branches (teleport/near/far).
The retail reviewer independently reproduced this by searching the
whole function body for the `24 7c` mod/rm+disp8 encoding a
`[esp+0x7c]` read would require and found zero occurrences. This
retired a fabricated `?? Vector3.Zero` fallback in the deleted method
— retail's PositionPack::UnPack initializes an absent velocity to
zero and MoveOrTeleport never installs it; the projectile's Vector
channel (RuntimeProjectilePhysicsUpdater.ApplyAuthoritativeVector)
remains the sole velocity authority for a missile. D-P5 in the
contract; the Runtime seam commits no velocity from the Position
packet at all.

The unbound-missile fix: RuntimeEntityObjectLifetime's
ClassifyRemoteAcceptedPosition now derives ProjectileAuthoritative
from a CONJUNCTIVE predicate — the Missile bit AND a bound
RuntimeProjectile whose Body is the canonical PhysicsBody — never the
bit alone. Retail places every non-player CPhysicsObj unconditionally
(there is no missile-specific placement gate in MoveOrTeleport or its
callers), so an unbindable or not-yet-bound missile taking the
ordinary remote tail is retail-faithful, not a fallback: the earlier
bit-only discriminator would have silently frozen it instead.

AP-141 records this as a deliberate, recorded divergence, not
fidelity. Retail mechanically WOULD arm a missile's ConstrainTo leash
on any nonzero MoveOrTeleport return: HandleReceivedPosition
@0x00453FD0's only kind test is player-vs-not, ConstrainTo
@0x00454272 has no kind test of its own, and CPhysicsObj::ConstrainTo
@0x00510520 creates a PositionManager on demand via
MakePositionManager @0x00510523 if one doesn't exist. acdream
deliberately does not construct that EntityPhysicsHost/
PositionManager/InterpolationManager chain for a ballistic body — the
route-5b split the C4 route 5 contract rejected — so a live missile
never shows an armed leash and never catches up via the near/
UnroutedCatchUp policy. This divergence is safe specifically because
ACE never sends UpdatePosition for a missile
(references/ACE/Source/ACE.Server/WorldObjects/WorldObject_Tick.cs:
333-334, SendUpdatePosition() commented out inside the
PhysicsState.Missile branch at :265) — every half of this row is
deterministic-test-gated only, never exercised against a real server.

AP-141 also records the surviving ConstrainTo re-anchor divergence
under clause (b): for the adopted-body case (TryBind's shared-body
branch — an ordinary remote whose Missile bit is set by a later
State packet, so it still carries a live RemoteMotion), acdream now
ports retail's teleport-branch and far-branch StopInterpolating
action (Interp.Clear()), but never re-arms or re-anchors the
inherited ConstrainTo leash the way retail's HandleReceivedPosition
@0x00454254/@0x00454272 does on every nonzero return. The risk
column's earlier wording — that a stale leash "would drag the body
toward a stale anchor" — was wrong and is retracted in this same
commit: ConstraintManager.ConstraintPos is write-only in both retail
and the port (never read by AdjustOffset), and
ConstraintManager::adjust_offset @0x00556180 only tapers or zeroes an
already-composed per-tick offset while InContact — a leash brakes
motion the interp/sticky chain already produced, it cannot pull
anything toward the anchor. The real residual is one tick of un-reset
brake accumulator, contact-gated, and it cannot move an airborne
far-snapped missile at all (the clamp branch does not run while
airborne).

NO CONNECTED GATE EXISTS for this route, by design: ACE never sends a
missile UpdatePosition (see above), so retail's own server never
exercises this code path in play. Every proof obligation here is
test-gated only — Runtime and App-level fixtures constructing the
packet directly — never a live client/server capture.

Three review rounds closed 8 MAJOR findings before this landed:
round 1 (A1 App discarded the seam's status; A2/R1 silent swallow on
an unbound missile; A3/R2 the adopted-body teleport_hook never
wired; A4/A5 zero Runtime/App test coverage); round 2 (a
ParentCellId regression introduced by round 1's own R6 finding,
which the retail reviewer retracted the following round as factually
wrong — the fix here is the REVERT to record.FullCellId, not the
relocation round 1 shipped; B2 the far-branch StopInterpolating skip
never extended to the adopted-body case; residual App/Runtime store-
path coverage; a per-packet closure contradicting the file's own
#315 cached-delegate pattern). Round 3 closed on coverage alone (no
defect): the Advance() retry arm's projectile branch — added at
round 2, semantically reordered at round 2's B5 fix (skip prediction
invalidation on a re-parked Contention, since it writes nothing) —
had never been executed by any test; two new tests drive it directly
and are sabotage-verified against both the reordering and the
retry-arm's own SyncProjectilePresentation call site. The one
recorded defect this campaign produced (the ParentCellId regression)
was caused by complying with a review finding that its own author
later retracted — the standing lesson recorded for future rounds is
that review findings are evidence to re-verify against the code, not
commands to obey unconditionally.

Complete Release suite: 11,063 passed / 4 skipped / 0 failed
(baseline 11,036 at 30d3d114, +27 new tests across this campaign).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 21:03:41 +02:00

55 KiB
Raw Blame History

C4 route 5 — projectile authoritative placement: pinned contract (2026-08-04)

Scope: make the post-residence accepted Position for a live projectile canonical — classify it through the shared route classifier, execute the placement dispositions through the one Runtime placement owner (RuntimeRemotePlacementDriveController / RuntimeSetPositionState), and delete the bespoke ApplyAuthoritativePosition authority that today short-circuits a missile packet before the classifier ever sees it.

Pinned at HEAD 30d3d114, clean tree, branch claude/acdream-physics-divergence-5aa784. Line numbers in this contract are as-of 30d3d114 and WILL go stale; every citation also names the symbol — trust the symbol (process rule 6; line ranges went stale twice within single review rounds during 4b-2/4b-3, and this route's own scoping doc is the third demonstration).

Predecessor documents, binding where they still apply:

  • 2026-08-04-c4-route-5-scoping.md — the research base. Its file:line references predate the OnPosition collapse (edc911b0) and the #315 fix (aaf0811f) and are stale; §10 of this contract lists every claim the collapse invalidated or reshaped. Its retail research (§2), prediction analysis (§4), and trap list (§7) survive and are folded in below, re-verified.
  • 2026-08-04-onposition-collapse-contract.md — the unified remote tail this route dispatches AHEAD of (never into). Its §7 non-goal ("Route 5 will add its arm against the collapsed single path — that is the payoff, not the scope") is cashed out here.
  • 2026-08-04-c4-route-4b-3-contract.md — its 13 "must REMAIN true" invariants and the D4 constraint-arm partition still bind; §5 below extends the partition with the projectile column.
  • The four 4b-3 review rounds (retail / round 2 / architecture / round 2) — the recurring defect classes (a mapping written against one caller's reachable set; an invariant satisfied on one arm only; a register row asserting behaviour the code does not have; a mismapped retail action with the faithful port already in-tree; tests that assert only negatives) are each addressed by name below.
  • 2026-08-04-session-handoff-c4-remaining.md — the six process rules apply verbatim.
  • docs/plans/2026-08-02-placement-cutover.md — C4 route list; route 5 is "projectile authoritative".

Sequencing: 4b-2 (7f1c1f5a), 4b-3 (landed, gate passed), the collapse (edc911b0), and #315 (aaf0811f) are all in. The scoping's "route 5 lands after 4b-2, preferably after 4b-3" constraint is satisfied; route 5 is next.


0. Facts settled before this contract — do not re-litigate

  1. Route 5 is half shipped. The Create half (RuntimeInitialCreateResidenceState.Begin decides RuntimePositionEntityKind.Projectile from record.FinalPhysicsState & PhysicsStateFlags.Missile, :591-595) and the residence-window Position half (RuntimeInitialCreateContinuationExecutor.ApplyPositionAction:1874, kind recovered via EntityKindOf:2571-2581, one shared RuntimeAcceptedPositionRouteRequests.Build at :1914-1926) are canonical. What remains is exactly one thing: the accepted Position arriving AFTER the residence window, short-circuited before the classifier.
  2. NO LIVE GATE IS POSSIBLE. ACE never sends UpdatePosition for a missile. Re-verified at HEAD: references/ACE/Source/ACE.Server/WorldObjects/WorldObject_Tick.cs:333-334 is /*if (PhysicsObj.IsGrounded) SendUpdatePosition();*/, inside the branch gated on (PhysicsObj.State & PhysicsState.Missile) != 0 at :265. Projectile classes broadcast GameMessageVectorUpdate and GameMessageSetState on impact — never a Position. This slice is test-gated only (§8). No connected gate is invented; if the implementer finds evidence contradicting this, STOP and report — it changes the slice.
  3. Route 5 had to land after 4b-2 because it widens RuntimeRemotePlacementDriveController.OwnsPlacement. Done; see §3.

1. Site inventory — re-located at 30d3d114

Every site verified by reading at HEAD, not inherited from the scoping.

1.1 The duplicate authority to delete (the same three sites; new lines)

# site (symbol) at HEAD what it is
D1 LiveEntityNetworkUpdateController.OnPosition — the projectile short-circuit (_projectileController?.ApplyAuthoritativePosition(...) == true → return) :2104-2125 (was :1428-1448) Sits after _movementTruthDiagnostics.OnServerEcho (:2103) and BEFORE the remote classification (earlyRemoteRoute, :2150-2158), TryApplyGenericRemoteRenderPose (:2160), RebucketLiveEntity (:2190), and the whole update.Guid != _playerServerGuid RemoteMotion tail (:2234 on). Carries the fabricated acceptedSpawn.Physics?.Velocity ?? System.Numerics.Vector3.Zero at :2119-2120 (was :1442-1443).
D2 ProjectileController.ApplyAuthoritativePosition (two overloads + doc) :492-590 (unchanged) Validation (the return true swallow on invalid payload at :551-554), ExternalOwnerValid closure, render-pose acknowledgement (:583-586), delegation to the Runtime updater.
D3 RuntimeProjectilePhysicsUpdater.ApplyAuthoritativePosition :301-424 (unchanged) The real authority: InvalidatePrediction :342, body.Orientation :345, body.SnapToCell :346, body.State = record.FinalPhysicsState :347, the velocity commit :348-358, CommitProjectileCell :370-381, the presentation acknowledgement :387, the spatial/hidden InWorld/Activate/shadow-sync/suspend tail :390-422.

Raw total 244 lines; roughly 180 non-comment — the scoping's figure holds.

1.2 The Runtime surfaces route 5 builds against

site (symbol) at HEAD relevance
RuntimeAuthoritativePositionRouteClassifierRuntimePositionEntityKind.Projectile :13; ValidEntityKind:535-538; OperationKind switch :564-575 (Projectile → ProjectileAuthoritative at :572-573) The kind's ONLY behavioural effect in the classifier is the OperationKind mapping. ClassifyAcceptedPosition (:308-478): the single kind branch is LocalPlayer (:349); Projectile and Remote are byte-identical in disposition, flags, StopInterpolating, TeleportHookPhase, and ConstrainPhase — teleport/cell-less :404-421, NoPositionOperation :430-448, near-Interpolate/far-SetPositionSimple :459-477. Pinned by RuntimeAuthoritativePositionRouteClassifierTests.ProjectilePosition_UsesRemoteMoveOrTeleportClassification (tests:418-436).
RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition :617-659; hardcodes RuntimePositionEntityKind.Remote at :642 (was :622) The one production remote-Position classification entry; feeds D1's PreMergeCommittedCellId (:634). Trap T4's site.
RuntimeRemotePlacementDriveController.OwnsPlacement :520-524 (was :274-278) OperationKind is RemoteAuthoritative && Disposition is SetPosition or SetPositionSimple && Teleport flagexcludes ProjectileAuthoritative. The widening target.
RuntimeRemotePlacementDriveController.TryExecuteAcceptedRemotePosition :619-667 Kind-agnostic body: stale-pending self-heal, CanAttemptDestination pre-flight → Refused, TryBeginExclusiveAuthoredPlacement(record, version, route.OperationKind)SubmitAndResolve. Gated only by OwnsPlacement.
RuntimeRemotePlacementDriveController.ApplyAcceptedRemoteFarSnap / ApplyAcceptedRemoteTeleport :760-784 / :821-843 Both REQUIRE a RemoteMotion parameter and THROW unless OwnsFarSnap / OwnsTeleportPlacement own the route. Not usable for projectiles — see §2. StoreAcceptedDestinationPose (:845 on) writes the CANONICAL body and is reusable.
RuntimeRemoteTeleportPosition.OwnsTeleportPlacement :38-46 NEW since the scoping (4b-3). Gates on OperationKind: RemoteAuthoritative — a projectile teleport-classified route is NOT owned.
RuntimeRemoteFarSnapPosition.OwnsFarSnap / ResolveArm :85-95 / :137-148 NEW since the scoping (4b-2). Same RemoteAuthoritative gate; a projectile far route would resolve UnroutedCatchUpApplyInterpolate against a RemoteMotion that does not exist.
RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation :240-259 Requires remote.Host (an EntityPhysicsHost). A projectile has none.
RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement / TryPrepareAndSubmitAuthoredPlacement :1817-1861 region operationKind is a pass-through parameter; the placement pipeline is kind-agnostic. Caveat for the implementer: two internal operation constructors stamp Kind = RemoteAuthoritative outright (:3833/:3866 — the window-departure park constructor; :5338/:5350 — the legacy-direct withdrawal shape), and the direct-command gate at :5445-5446 admits local kinds only. Proof obligation P3 (§6) covers these.
RuntimePhysicsState.CommitProjectileCell :1376-1397 NOT a bypass — routes into the shared CommitCanonicalCell with an exact-owner check including PredictionAuthorityVersion. The 2026-08-02 inventory's "ad hoc" label remains wrong (scoping already corrected this; re-verified).
RuntimeProjectile RuntimeProjectile.cs (whole file) { Body, CollisionSphere, PredictionAuthorityVersion, InvalidatePrediction() }. No Host, no PositionManager, no InterpolationManager, no MovementManager. RuntimeEntityRecord.Projectile is the canonical slot (J5.6).
RuntimeProjectilePhysicsUpdater — quantum + Vector/State channels TryBegin/Complete :37-205; ApplyAuthoritativeVector :207-250; ApplyAuthoritativeState :252-299 Out of scope (invariants 4/5). InvalidatePrediction is bumped at exactly :241, :272, :342 — the sole cancellation mechanism for a split quantum (trap T3).
First-entry admission RuntimeRemoteFirstEntryState.cs:311-314 Admits RemoteAuthoritative or ProjectileAuthoritative. Untouched.
Continuation-executor non-SetPosition gap RuntimeInitialCreateContinuationExecutor.cs:2019-2026 Interpolate/NoPositionOperation/AwaitFreshPosition → typed trace + return ("Binding to the live interpolation owner is cutover work"). §3 D-P4 resolves the projectile half of that gap (no-op by policy); the REMOTE half stays open — do not touch.

1.3 App-side context (dispatch site + presentation)

site (symbol) at HEAD relevance
OnPosition shared prologue :1891-2103 Authority gate + merge (TryAcceptPosition, :1902) — so AcceptedPhysicsTimestamps.PreMergeCommittedCellId is already measured for missile packets; hydration recovery; EnsureWorldOrigin; world-pos translation; MarkLiveOwnerPoseDirty (:2068); OnServerEcho (:2103). All runs for missiles today and keeps running.
ProjectileController.CanAcceptPositionPayload :102-126, called unconditionally at :1898-1901 (was :1205-1208) The SHARED admission validator (returns true for non-projectiles); an invalid missile payload already fails the authority gate before the short-circuit. NOT a deletion target.
Unified remote tail (post-collapse) :2234-2810 One guid-blind path; two named guid survivors (TS-44 sticky :2537-2546; the #316-preserved player AirborneSnap interp-clear/shadow-skip :2581-2632, :2795-2809). Route 5 must not touch any of it.
RunRemoteArmTail / cached #315 callbacks :1427-1463, _remoteArmCallbacks backing methods :1474-1492 The allocation pattern to follow if the projectile arm needs any callback: cached delegates + scratch fields, never per-packet closures.
TryApplyGenericRemoteRenderPose :1010-1025, called :2160 Gate is OwnsSteadyState. The projectile arm dispatches BEFORE this call (§3 D-P6) — a missile packet takes no early wire-pose write today and must not start taking one.
ProjectileController.Tick / AdvanceQuantum / TryBeginQuantum / CompleteQuantum / IsCurrentQuantumIdentity :640-766 / :773-782 / :784 / :822-848 / :850-859 The per-quantum owner. Untouched.
LiveEntityAnimationScheduler split quantum src/AcDream.App/Rendering/LiveEntityAnimationScheduler.cs:404-426 TryBeginQuantum :405-409_animationHooks.CaptureCompleteQuantum :419-423. The straddle window trap T3 protects. Untouched.
ProjectileController.TryBind + create branch :133-330; late-classification path ApplyAuthoritativeState → TryBind :473-489; projection-visible retry in OnProjectionVisibilityChanged :860-897 Still the live fallback for initialResidenceActive == false (DatLiveEntityProjectionMaterializer.cs:848-865 — note: src/AcDream.App/Rendering/, not World/) and the mid-life Missile-bit flip. NOT deleted here (trap T8; C5's deletion pass owns it).
ProjectileController.HandlesMovement :599-602 The Missile-bit ownership predicate the kind discriminator must agree with (§3 D-P1).

2. How the OnPosition collapse changed route 5's shape

The scoping assumed route 5 would rewrite call sites inside two parallel player/NPC copies of the remote tail. The collapse (edc911b0) replaced them with one unified, RemoteMotion-shaped path plus two surviving named guid-conditionals. Consequences, stated precisely:

  1. Route 5 touches the unified tail NOT AT ALL. The projectile arm dispatches from OnPosition at (approximately) the current short-circuit's site — after the shared prologue, BEFORE TryApplyGenericRemoteRenderPose, RebucketLiveEntity, and the update.Guid != _playerServerGuid block — and returns. A missile packet must never reach GetOrCreateRemoteMotionRuntime, SeedRemoteSpawnPlacement, TryCommitAuthoritativeVelocity, the sticky gate, RunRemoteArmTail, the arming site, the wire-cell adopt, or the remote entity-sync/shadow tail. The entire tail assumes a RemoteMotion; a projectile has none, and creating one is exactly the "second body or interpolation owner" the current short-circuit's comment exists to prevent.
  2. The "two copies to widen" work item is gone. The scoping's §8 line item "App call-site rewrite in OnPosition … 40-70 lines" shrinks to ONE dispatch site. This is the collapse's payoff for route 5.
  3. Three ownership predicates now fence projectiles out, not one. The scoping knew only OwnsPlacement (§5.2). Since then 4b-2/4b-3 added OwnsFarSnap and OwnsTeleportPlacement, both gated on OperationKind: RemoteAuthoritative, and both remote arm methods (ApplyAcceptedRemoteFarSnap / ApplyAcceptedRemoteTeleport) THROW on a non-owned route and REQUIRE a RemoteMotion. Consequence: only OwnsPlacement is widened; the other two predicates and the two remote arm methods are deliberately NOT widened and NOT reused — the projectile arm is a sibling seam over the shared TryExecuteAcceptedRemotePosition + StoreAcceptedDestinationPose core (§3 D-P2). The bright side: a mis-routed projectile packet now fails loudly (throwing guards) instead of silently taking a RemoteMotion arm.
  4. The two surviving guid-conditionals are off-limits. TS-44's sticky gate and the #316-preserved player AirborneSnap interp-clear/shadow-publish skip are named, contract-protected asymmetries of the collapse. Route 5 has no business near either.
  5. #315's callback pattern binds new code. The collapse's second commit cached the remote-arm callbacks; the projectile arm must not reintroduce per-packet closures on the packet path (the projectile hook reduction in D-P4 needs at most a method-group/cached delegate).

3. Design decisions — pinned, not open for redesign

D-P1 — one per-packet kind discriminator, derived where the classification already runs (resolves trap T4)

RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition:642 stops hardcoding RuntimePositionEntityKind.Remote and derives the kind from the canonical record:

(canonical.FinalPhysicsState & PhysicsStateFlags.Missile) != 0
    ? RuntimePositionEntityKind.Projectile
    : RuntimePositionEntityKind.Remote

This is the SAME data-driven predicate the Create half uses (RuntimeInitialCreateResidenceState.cs:591-595), the same one EntityKindOf recovers from the lease, and the same bit ProjectileController.HandlesMovement keys movement ownership on — data-driven, never display-name-gated (feedback_retail_dispatch_is_data_driven). The App dispatch in OnPosition branches on the SAME underlying fact: pinned, the App reads route.OperationKind is ProjectileAuthoritative off the returned route wherever a route exists, and for the null-classification case reads one FinalPhysicsState & Missile test on the same canonical record in the same packet — so the classifier's kind and the App's dispatch cannot disagree (a mis-tag is otherwise silent, because the dispositions are identical for the two kinds; only the ledger/owner would be wrong).

The mid-life bit flip is thereby handled by construction: ACE clears Missile on impact (a State packet), and ApplyAuthoritativeState → TryBind is where an ordinary object becomes a missile. Whichever FinalPhysicsState the packet's merge left on the canonical record decides the packet's arm; the adopted-body case (a missile that also carries a RemoteMotion) follows the Missile bit exactly as HandlesMovement already does, and the untouched RemoteMotion is simply not written by that packet.

Route-1 semantics are untouched: the residence/continuation callers already pass an explicit kind; only the remote PositionEvent entry changes its kind input. The classifier itself does not change at all.

D-P2 — the projectile execution seam: one controller, shared core, no RemoteMotion

New Runtime seam on the EXISTING RuntimeRemotePlacementDriveController (never a sibling controller, never a second pending map — trap T9), shape at the implementer's discretion but pinned in behaviour:

ApplyAcceptedProjectilePosition(RuntimeEntityRecord record, in RuntimeAuthoritativePositionRoute route) (reads record.Projectile internally), which:

  1. Validates ownership — the route's OperationKind is ProjectileAuthoritative; record.Projectile and record.PhysicsBody exist and agree (ReferenceEquals(record.PhysicsBody, projectile.Body) — the J5.6 canonical-body identity). Anything else: return NotApplicable-equivalent, write nothing.
  2. Dispatches on disposition:
    • SetPosition (teleport / cell-less): run the projectile hook reduction (D-P4) BEFORE the placement, then InvalidatePrediction(), then TryExecuteAcceptedRemotePosition(record, route) (now OwnsPlacement-admitted, D-P3), then StoreAcceptedDestinationPose on the StoresAcceptedDestination partition — the partition itself is extended UNCHANGED (4b-3 invariant 1; do not re-litigate which statuses store).
    • SetPositionSimple (far): InvalidatePrediction()TryExecuteAcceptedRemotePosition → store on the same partition. No interp clear (no queue exists; retail's own far-branch StopInterpolating is guarded on position_manager != 0 @0x005163C9-@0x005163CB and a never-interpolated missile has none, so the skip is retail-faithful by consequence, not a divergence).
    • Interpolate (near, in contact): no-op by pinned policy — see D-P4. No body write, no prediction invalidation (nothing changed).
    • NoPositionOperation (airborne): no-op — retail's return 0 @0x0051636D, faithful. No prediction invalidation.
  3. Invalidates prediction before ANY body write on this route — the placement path and the store fallback both clobber the body retail-side of an in-flight split quantum (trap T3). One InvalidatePrediction() at the seam's placement dispatch (before Begin) covers both, mirroring today's :342-before-SnapToCell ordering. The no-op dispositions do NOT invalidate: the body is untouched, so a straddling quantum completing is correct, and the Vector/State channels' invalidate-on-write semantics stay coherent.
  4. Commits NO velocity (D-P5).
  5. Arms NO constraint leash (D-P4).

The post-commit lifecycle tail — InWorld/Activate/shadow-sync on spatial+visible, suspend on spatial+hidden, deactivate+suspend on non-spatial (today's RuntimeProjectilePhysicsUpdater.ApplyAuthoritativePosition:390-422) — is preserved semantically on the committed/stored outcomes. Whether it lives in the new seam or stays a small retained method on the projectile updater is the implementer's choice; pinned is that it remains Runtime-owned (J5.6 boundary, invariant 6) and keyed on the same spatial/hidden facts.

App keeps exactly what J5.6 assigns it: the presentation acknowledgement. OnPosition's projectile dispatch (D-P6) projects the committed/stored snapshot into the render entity (SetPosition/Rotation/ParentCellId from the RESOLVED body) and _rootPoses.UpdateRoot — the same ack contract ProjectileController.ApplyAuthoritativePosition:583-586 performs today. No RebucketLiveEntity on this arm (the canonical placement/cell commit is the cell authority, exactly as the per-quantum commit is today; adding the remote tail's bucket transaction here would be a new second writer).

D-P3 — the OwnsPlacement widening, and every consequence

RuntimeRemotePlacementDriveController.OwnsPlacement:520-524 first clause widens to route.OperationKind is RuntimeSetPositionOperationKind.RemoteAuthoritative or RuntimeSetPositionOperationKind.ProjectileAuthoritative (+ doc update). Consequences, each pinned:

  • TryExecuteAcceptedRemotePosition admits projectile routes. The Teleport-flag conjunct keeps excluding Creates (the first-entry conductor owns every Create — unchanged; a projectile Create carries InitialCreateFlags, an accepted Position carries 0x1012).
  • Pending/acknowledgement ledgers are shared. Projectile operations land in the same _pending / _awaitingAcknowledgement maps; DetachRoute's cancel-then-clear teardown and CountLivePending's self-heal cover them with zero new code. No parallel map, no sibling controller (T9).
  • Parks/refusals behave exactly as the remote arms' (4b-3 invariant 3 extended): Refused (pre-flight) and Contention/RejectedPreparation store the accepted destination; DeferredCell is cancelled synchronously with restoreCancelledPark: true — no park survives the controller; RejectedByPlacement does not store. The projectile body stays InWorld, clock active, spatially projected on every non-commit outcome.
  • OwnsFarSnap and OwnsTeleportPlacement are NOT widened (§2 item 3). Their throwing guards and the two RemoteMotion-shaped arm methods are untouched. State this in their docs only if a comment would otherwise mislead (process rule 6).
  • The constraint arm: see D-P4. The classifier's projectile routes carry ConstrainPhase.AfterPositionOperation (kind-blind, :417/:473); the projectile arm deliberately does not consume it. No change to the classifier; the deviation is recorded (D-P7).
  • The interp queue: nonexistent for projectiles; StopInterpolating: !nearby on the far route is inert and retail-faithfully so (D-P2).
  • CommittedHostAcknowledgementPending: a committed placement can enter _awaitingAcknowledgement (:1062-1076). Whether the graphical host's projection-acknowledgement chain retires a ProjectileAuthoritative commit identically is proof obligation P2 (§6) — verify, do not assume.

D-P4 — retail's manager actions, reduced to what a missile has (resolves scoping §5.1; answers the constraint-arm question)

Retail's mechanical answer, verified in the decomp (§4): YES, retail would arm the leash for a missile. SmartBox::HandleReceivedPosition @0x00453FD0 has NO kind test other than arg2 != this->player (@0x0045414D); every nonzero MoveOrTeleport return reaches ConstrainTo @0x00454272; and CPhysicsObj::ConstrainTo @0x00510520 calls MakePositionManager (@0x00510523) — creating the manager on demand for any object, missile included — after which UpdateObjectInternal ticks it (PositionManager::UseTime @0x005159A9-@0x005159B3). Likewise retail's near branch (InterpolateTo @0x005163AF, with IsMovingTo @0x0050EB10 returning 0 for a manager-less missile) would build interpolation machinery for it.

acdream pins the divergence instead of the machinery. Building an EntityPhysicsHost/PositionManager/InterpolationManager chain for a ballistic body — for a packet ACE never sends — is the route-5b split the scoping warned against, and this contract REJECTS it. Pinned:

  • The projectile arm never arms ConstrainTo — on any disposition, any outcome.
  • The near-Interpolate disposition is a no-op (no queue to feed, no manager to create).
  • Both are recorded in ONE new register row (D-P7) citing @0x00454272, @0x00510523, @0x005163AF, and the ACE unreachability (WorldObject_Tick.cs:333-334).

The teleport hook, reduced. Retail's teleport/cell-less branch runs teleport_hook @0x00514ED0 before the placement — six actions, each guarded on its manager existing. For a pure missile, five of six are structurally absent (MovementManager/PositionManager×3/TargetManager are null → retail no-ops through the guards). The sixth, report_collision_end(this, 1) @0x00514F31 → @0x00514620 (force-end-all with bidirectional notification), applies to ANY object with a collision table. Pinned: the projectile teleport/cell-less arm runs the faithful force-end port — RuntimeCollisionReportingState.LeaveWorld (the exact seam the 4b-3 R2 fix validated against @0x00514620, round-2 retail review §2) — before the placement. Omitting it would recreate R2's defect class with the faithful port sitting unused in-tree. The other five actions execute iff their owning component exists — for the adopted-body case (a missile that carries a RemoteMotion), the existing hook actions' per-manager guards already express retail's shape; the implementer wires this without per-packet closures (#315 pattern). For the ordinary arrow/bolt they are no-ops by the same guards retail uses.

Null / Rejected* classifications for a missile packet: swallow. Write nothing, create nothing, return — never fall through to the remote tail (preserving trap T5's semantics: today an unhandleable projectile packet is swallowed, not re-routed). The remote UnroutedCatchUp policy is RemoteMotion-shaped and does not apply. This is an acdream-only state (retail rejects nothing here); it rides in the same register row.

D-P5 — no velocity write from a Position packet (retires the zeroing defect)

Retail's MoveOrTeleport declares arg5 = AC1Legacy::Vector3 const* and never references it in the decompiled body (@0x00516330-@0x00516438, re-verified for this contract); HandleReceivedPosition threads its arg6 into it (@0x00454254) and does nothing else with it; UnpackPositionEvent @0x004542C0 performs no set_velocity. Velocity authority for a missile is the Vector channel (0xF74E / ApplyAuthoritativeVector), untouched here.

Pinned: the projectile arm consumes no velocity from the Position packet. This deletes the acceptedSpawn.Physics?.Velocity ?? Vector3.Zero fabrication (:2119-2120) and the updater's conditional commit (:348-358), retiring the (never-filed) defect where a velocity-less Position packet zeroes an in-flight missile's velocity. Because "textually unreferenced" is strong-but-not-byte-confirmed (BN elision risk, scoping §9.2), the implementer byte-verifies 0x00516330-0x00516438 for any arg5 use before landing (tools/pdb-extract PE byte-decode against the paired refs/acclient.exe — the reference_pe_byte_decode.md recipe). If a use is found: STOP and report — the velocity policy changes.

D-P6 — the App dispatch site and ordering

In OnPosition, the D1 short-circuit is replaced in place by:

classify (kind-aware, D-P1)                       // one call, both kinds —
                                                  // replaces the :2150 remote-only call
if the packet is a missile packet (D-P1's discriminator):
    dispatch the projectile arm (D-P2) and RETURN // before TryApplyGenericRemoteRenderPose,
                                                  // before RebucketLiveEntity,
                                                  // before the RemoteMotion tail
else: the unified remote tail runs UNCHANGED      // earlyRemoteRoute now comes from
                                                  // the same classification call

Pinned facts of this shape:

  • The projectile arm takes no early wire-pose write (TryApplyGenericRemoteRenderPose is never reached — matching today, where the short-circuit precedes it) and no RebucketLiveEntity. Presentation advances from the RESOLVED body via the ack (D-P2), exactly as the per-quantum path does.
  • The shared prologue (authority gate/merge, hydration, world origin, MarkLiveOwnerPoseDirty, OnServerEcho) runs for missiles exactly as today — the dispatch sits at/after the current short-circuit's position.
  • The classification for a REMOTE packet is byte-identical to today's (Remote kind in → same route out); only its call site may merge with the projectile classification. Zero remote-classifier test changes is the regression tripwire (§8 stop condition).

D-P7 — register and issue bookkeeping, in the implementation commit

  • ONE new AP row (projectile accepted-Position divergences): (a) near-Interpolate is a no-op where retail would InterpolateTo a manager-on-demand missile (@0x005163AF, @0x00510523); (b) the post-operation ConstrainTo @0x00454272 is never armed for a projectile (no PositionManager machinery exists for a ballistic body); (c) null/Rejected* missile packets are swallowed rather than caught up (no RemoteMotion); with the shared context that ACE never emits the packet (WorldObject_Tick.cs:333-334 commented out inside the Missile branch) so every half is test-gated. Note in the row that the far branch's StopInterpolating skip is NOT part of the divergence — retail's own position_manager != 0 guard @0x005163C9 skips it for a never-interpolated missile.
  • No row deletion: the velocity-zeroing defect being retired was never filed; record its retirement in the commit message (and this contract), not the register.
  • Comment corrections (process rule 6): the D1 site's comment block ("Missiles reconcile the same predicted PhysicsBody in place…", :2104-2108) dies with the short-circuit; RuntimeProjectilePhysicsUpdater's class doc and any "Position correction" references are re-verified against the new seam; RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition's doc gains the kind-derivation sentence. Grep for ApplyAuthoritativePosition across src/ and docs/architecture/ and re-point every survivor.
  • ISSUES.md: none closed by this slice (#315 already closed; #316 stays OPEN and untouched).

4. Retail ground truth — verified in acclient_2013_pseudo_c.txt for this contract; verify again yourself

claim where verified status
Retail has NO projectile-specific branch anywhere in the accepted-Position chain. UnpackPositionEvent @0x004542C0 resolves the object and calls HandleReceivedPosition @0x00454358 with no state/kind test; HandleReceivedPosition @0x00453FD0's only kind branch is arg2 != this->player @0x0045414D; MoveOrTeleport @0x00516330 tests TELEPORT_TS, this_1->cell == 0 (the body's OWN cell, read at entry), arg4, and player_distance — never state & Missile. A missile takes the identical remote arm. pseudo-C lines 92896-93054 (HandleReceivedPosition), 93055-93098 (UnpackPositionEvent), 284304-284366 (MoveOrTeleport); independently confirmed twice by the 4b-3 reviews ✓ re-read
Teleport/cell-less branch: teleport_hook @0x005163EF → SetFlags(0x1012) @0x00516414 → SetPosition @0x00516420 → return 1 @0x00516438; decided before arg4 is read @0x0051638E same listing
Near branch: InterpolateTo(this, arg2, IsMovingTo(this)) @0x005163AF, return 1 @0x005163BE; IsMovingTo @0x0050EB10 returns 0 without a MovementManager (a missile has none) lines 276430-276440
Far branch: StopInterpolating ONLY if position_manager != 0 @0x005163C9-@0x005163CB; SetPositionSimple(this, arg2, 1) @0x005163D9 (builds 0x1012); return 1 @0x005163E8 same listing
Airborne: arg4 == 0return 0 @0x0051636D — nothing written, and ConstrainTo skipped (it sits inside if (MoveOrTeleport(...) != 0) @0x00454254) same
ConstrainTo @0x00510520 = MakePositionManager @0x00510523 then PositionManager::ConstrainTo — retail creates the manager on demand for a missile; the single remote arming site @0x00454272 has no kind test. So retail WOULD arm a missile's leash on any nonzero return. lines 278353-278364 ✓ — the basis of D-P4's recorded divergence
PositionManager::UseTime ticks in UpdateObjectInternal @0x005159A9-@0x005159B3 for any object holding a manager — an armed missile leash would be live, not vestigial lines 283611-283757
player_distance is maintained for missiles: update_object @0x00515D10 computes player_vector via Position::get_offset @0x00515D5B and stores player_distance @0x00515D95 for every active unparented in-cell object. (BN artifact: the decomp shows only .x feeding it @0x00515D7B — the standard x87-elision of a magnitude; do NOT "fix" acdream's Euclidean distance to .x.) lines 283950-284055
MoveOrTeleport's arg5 (velocity) is textually unreferenced in the body; HandleReceivedPosition threads arg6 into it and does nothing else with it; UnpackPositionEvent calls no set_velocity (the only set_velocity in the chain is the LOCAL player's zero @0x004541B4) all three listings above ✓ textual; byte-verification required before landing (D-P5)

What this means for the design — stated plainly: retail's CLIENT mechanically supports an authoritative missile Position (it is just the generic remote path), but the packet does not exist against ACE, and the manager machinery retail would lazily build for it does not exist in acdream for a ballistic body. Route 5 therefore anchors the placement dispositions to retail's generic remote path (faithful), and pins the manager-dependent halves (near-interpolate, leash) as a recorded acdream divergence — a register row, not an invented retail justification (D-P7). There is no evidence of any dedicated retail missile-Position path to port; anyone claiming otherwise must produce an address.

One reporting note, out of scope but found while verifying: the remote prologue's comment at LiveEntityNetworkUpdateController.cs:2296-2300 ("PositionPack::UnPack initializes an absent velocity to zero; MoveOrTeleport installs that exact vector with set_velocity") is contradicted by the decomp read above — MoveOrTeleport installs no velocity. That comment justifies the REMOTE arm's TryCommitAuthoritativeVelocity, which is 4a-owned and untouched by this slice; flagged for a future 4a-family correction, not acted on here.


5. What must REMAIN true (process rule 1 — for every path, including every refusal)

  1. The pose still advances on the storing partition. A projectile placement that never reached the engine (Refused / Contention / RejectedPreparation) still commits the accepted destination via StoreAcceptedDestinationPose; Deferred and RejectedByPlacement do not. The partition is extended unchanged — not re-litigated.
  2. Presentation still advances. On every committed/stored outcome the render entity (Position/Rotation/ParentCellId) and the root pose reflect the RESOLVED canonical body, and the shadow registry is synced (spatial+visible) or suspended (hidden/non-spatial) exactly per the current tail's semantics. A projectile is never left rendered a packet behind its body (#312's layer — tests must assert it).
  3. The entity stays in-world on every non-commit outcome: body.InWorld, object clock active, FullCellId intact, spatial projection intact. No park survives the controller.
  4. Prediction invalidation accompanies every body write on this route (placement AND store fallback), before the write; an in-flight split quantum straddling the packet aborts at Complete. The no-op dispositions invalidate nothing. The Vector/State channels' invalidation sites (:241, :272) are untouched.
  5. Per-quantum integration is untouched (trap T7): TryBeginQuantum / CompleteQuantum / AdvanceQuantum / CommitProjectileCell and the scheduler's split quantum never route through RuntimeSetPositionState, and their allocation profile is unchanged (Slice I's 0 B/resolve discipline; the placement budget is per accepted packet, not per quantum).
  6. J5.6's ownership boundary does not regress: the projectile component, workset, stepper, and cell commit stay in Runtime; App supplies only DAT shape resolution and retained projection/shadow/effect acknowledgements; a rejected presentation acknowledgement invalidates the pending prediction only — it never rolls back or redirects the canonical owner, and never becomes collision authority.
  7. The unified remote tail is untouched — including ApplyRemoteContactRouting, RunRemoteArmTail, ToConstraintArm, TryAdoptWireCellAfterRouting, ApplyWireAirborneLeftoverBookkeeping, the TS-44 sticky gate, and the #316-preserved player AirborneSnap asymmetry. A missile packet never creates or writes a RemoteMotion.
  8. Mutual exclusion is provable (trap T4): one FinalPhysicsState read per packet decides both the classifier's kind and the App's dispatch; Missile-set ⇒ projectile arm, Missile-clear ⇒ remote tail; asserted by the dual-kind theories (§7).
  9. Unhandleable missile packets are swallowed, with their bookkeeping stated positively (trap T5 + round-2 B1): an invalid payload still fails the shared authority gate exactly as today (CanAcceptPositionPayload unchanged); a null/Rejected* classification writes nothing and falls through to NOTHING — and the test asserts what DID happen (the gate consumed the timestamps; the merge advanced the snapshot) as well as what did not.
  10. No velocity write from the Position packet; an in-flight missile's velocity survives a velocity-less packet bit-identically (the retired zeroing defect's converse, asserted positively).
  11. Ledger convergence: teardown, session reset, and generation change converge RemotePlacementDrivePendingCount (both registrations) to zero with projectile operations in flight — the same suite shape 4b-1 built, driven through the projectile arm.
  12. ParkCollisionResidents's overlap throw stays unreachable — the projectile arm adds packets to the same one-operation-per-key machinery and opens no new operation shape; restate the argument (with 4b-1's B2 stall-not-throw caveat) in the implementation commit.
  13. The lost-cell reaper gains no production caller.
  14. The Create half and residence-window half are untouched: the residence kind decision (:591-595), first-entry admission (:311-314), the continuation executor (including its recorded non-SetPosition trace gap at :2019-2026 — the REMOTE half of that gap stays open and untouched), the materializer's residence-gated TryBind skip, and the projection-visible TryBind retry.
  15. TryBind and its create branch are NOT deleted (trap T8) — still the live fallback for initialResidenceActive == false and the mid-life Missile flip; C5's deletion pass owns it. Record, don't touch.
  16. OwnsFarSnap / OwnsTeleportPlacement and the two RemoteMotion arm methods are not widened, not reused, not weakened — their throwing guards are the loud failure mode protecting item 7.

The D4 constraint-arm partition, extended (the projectile column is the new content; the remote column is 4b-3's, unchanged and re-asserted):

disposition (wire contact) retail return remote arm projectile arm
SetPosition (teleport/cell-less, any contact) 1 arms, every placement outcome never arms (register row) — hook reduction runs (force-end), placement + store partition run
SetPositionSimple (far, grounded) 1 arms never arms (row) — placement + store partition run
Interpolate (near, grounded) 1 arms never arms, no-op (row)
NoPositionOperation (airborne) 0 no arm no arm, no-op — faithful, not a divergence
null / Rejected* n/a UnroutedCatchUp arms (grounded) / D2 shape (airborne) swallow — no arm, no write (row)

6. Proof obligations (must prove, not assume; stated in the implementation commit)

  • P1 — invariant 12's unreachability argument, restated with the B2 caveat.
  • P2 — the commit-acknowledgement path. Verify what acknowledges/retires a ProjectileAuthoritative commit that lands in _awaitingAcknowledgement (CommittedHostAcknowledgementPending, RuntimeRemotePlacementDriveController:1062-1076): either the shared projection-acknowledgement chain is kind-agnostic (state where), or the self-heal prune is the retiring mechanism (state that, and why it is acceptable — or route the ack explicitly). Do not let a projectile commit sit in a ledger dimension nothing retires.
  • P3 — RuntimeSetPositionState kind-agnosticism. Walk the operation lifecycle for a ProjectileAuthoritative operation: the two internal constructors that stamp Kind = RemoteAuthoritative (:3833/:3866 window-departure park; :5338/:5350 legacy-direct withdrawal) and any kind-conditional stage logic (:1530, :5445). Establish that none can capture, rewrite, or strand a projectile operation — or fix the stamp to carry the originating kind if one can. (A park constructor relabeling a projectile op as RemoteAuthoritative would silently move it between ledger columns — the T4 mis-tag shape at the operation layer.)
  • P4 — the byte-decode of MoveOrTeleport's arg5 (D-P5).
  • P5 — the force-end wiring (D-P4): one assertion that a teleport/ cell-less projectile packet empties the owner's collision table (the same cheap observable the 4b-3 round-2 review named for R2's regression risk).

7. Test plan

Rules: assert the layer that historically broke (presentation, prediction, ledger — not only InWorld/clock); assert positive facts, not only negatives (round-2 finding B1 — every "writes nothing" test also asserts the two or three things that DID advance); every new test must fail against a broken implementation (no source-text pins).

The dual-KIND discipline — this route's analog of the collapse contract's dual-guid theories, because the discriminator here is the Missile bit, not the guid range: every OnPosition-level scenario is a [Theory] run twice against the same packet shape — once with FinalPhysicsState carrying Missile, once without — asserting the projectile arm claimed one (projectile facts advanced; NO RemoteMotion exists afterward) and the remote tail claimed the other (RemoteMotion facts advanced; no projectile seam effect). This is what makes a future silent mis-tag fail a test.

Focused Runtime tests (tests/AcDream.Runtime.Tests):

  1. Classification kind derivation (D-P1): Missile-bit canonical record classifies with OperationKind == ProjectileAuthoritative through the production ClassifyRemoteAcceptedPosition entry; bit-clear classifies RemoteAuthoritative; same packet, same record otherwise. Plus the flip-mid-life pair (bit set after a State merge → next Position classifies Projectile; bit cleared on impact → next Position classifies Remote).
  2. OwnsPlacement widening: projectile SetPosition/SetPositionSimple routes admitted; projectile Create (InitialCreateFlags) still excluded; remote routes unchanged (the existing OwnsPlacement expectations stay green untouched).
  3. Per-disposition seam behaviour (D-P2), one test per row of the §5 table's projectile column: teleport/cell-less commit (body at resolved destination, cell committed canonically, collision table force-ended, prediction version advanced, tail facts per invariant 2); far commit; teleport/far refused (destination outside the service window → pose STILL advances to the accepted destination, no park retained, InWorld, prediction invalidated); RejectedByPlacement (pose does NOT move, settled pose survives); near no-op (body bit-identical, prediction version UNCHANGED — the positive fact that a straddling quantum may complete); airborne no-op (same); null/Rejected* swallow (nothing written; timestamps/merge advanced — positive).
  4. Prediction invalidation across a split quantum (trap T3): begin a quantum (TryBeginQuantum), apply an accepted far/teleport Position through the new arm, then CompleteQuantum — the completion must abort (return false / not overwrite the committed placement), and the committed pose must survive. This is the test the scoping said is invisible if you only read the classifier; it must exist before the old path is deleted.
  5. No-velocity invariant: an in-flight missile with nonzero velocity receives a velocity-less accepted far Position → body placed AND velocity bit-identical (the zeroing defect's regression test, positive form).
  6. Ledger/teardown: proof obligation-11 suite — reset/teardown with a retained projectile preparation retry and with an awaiting-acknowledgement commit in flight; both registrations converge to zero.
  7. Constraint never armed: after every projectile disposition, no PositionManager/constraint exists anywhere for the entity (and the D4 partition test for remotes is untouched).

App-layer tests (tests/AcDream.App.Tests):

  1. The dual-kind OnPosition matrix (above) for at least: far commit, teleport commit, near packet, airborne packet, null-classification packet. Projectile half asserts: no RemoteMotion created, no generic wire-pose write, no rebucket-driven bucket move beyond the canonical commit, render entity == resolved body (#312's layer), root pose updated, shadow synced/suspended per spatial state.
  2. The invalid-payload swallow (T5): an invalid missile Position is consumed (gate/validation) and the remote tail observably did not run — asserted positively via the gate's timestamp state.
  3. Sabotage check (manual, once, before the commit is finalised) — the collapse contract's experiment, adapted: break the seam (skip the store fallback, or skip InvalidatePrediction) and confirm the matrix fails on the projectile half; break the kind derivation (hardcode Remote) and confirm the dual-kind theories fail. If a sabotage survives, fix the test, not the sabotage.

Existing tests: ProjectilePosition_UsesRemoteMoveOrTeleportClassification survives (it pins the disposition identity, which is unchanged); the ProjectileController position tests covering D2/D3's deleted bodies are re-expressed against the seam (not deleted as collateral — each scenario must map to a successor or be named as obsolete-with-reason in the commit).


8. Gates

  • Focused: the §7 suites, green.
  • Complete Release suite: $env:ACDREAM_PAK_PATH = "$env:USERPROFILE\Documents\Asheron's Call\acdream.pak", dotnet test AcDream.slnx -c Release -m:1. Baseline 11,036 passed / 4 skipped / 0 failed at 30d3d114. The count will rise; measure and record the new figure — do not inherit the baseline. Two known flakes, never chase and never conflate (they have been conflated twice): #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.
  • NO CONNECTED GATE EXISTS, and none is invented. ACE never sends UpdatePosition for a missile (WorldObject_Tick.cs:333-334, commented out inside the PhysicsState.Missile branch at :265; every live SendUpdatePosition caller is a player/creature/pet/gamepiece/admin path). The accepted-Position half of this route is unreachable in play against ACE; deterministic tests are its ONLY gate, and this contract records that explicitly so a green connected session is never presented as evidence for it. Optional, not a gate: an ordinary bow/spell-bolt smoke (arrow spawns at the launch point, flies a clean arc, vanishes on impact, leaves no invisible collider at the impact point) exercises the UNTOUCHED Create/Vector/State halves and is cheap insurance against a wiring mistake in the deletion — record it as a no-regression observation if run, never as coverage for the Position half.

9. Budget and stop conditions

Budget (scoping's figures re-validated against HEAD): ~244 raw lines deleted across the three D-sites (~180 non-comment); added — seam 60-110, kind derivation + builder plumbing 5-15, OwnsPlacement widening + docs ~10, App dispatch 25-50, hook-reduction wiring 10-25: total added 110-210 non-comment production lines; net roughly flat-to-negative. Tests are the larger share, ~400-700 lines. The collapse SHRANK the App portion versus the scoping's 40-70 (one dispatch site, not two).

Stop and report rather than pushing through when:

  1. Added production lines exceed ~300, or the design starts needing a RemoteMotion, EntityPhysicsHost, PositionManager, or InterpolationManager for a projectile — that is the 5b split this contract rejected; it does not get built ad hoc.
  2. The unified remote tail, ApplyRemoteContactRouting, either surviving guid-conditional, OwnsFarSnap/OwnsTeleportPlacement, or either RemoteMotion arm method needs an edit beyond comment repointing.
  3. Any remote-classification test changes expectation — the remote route must be byte-identical.
  4. Proof obligation P3 finds an operation-lifecycle site that can capture or relabel a projectile operation.
  5. P4's byte-decode finds a live arg5 use in MoveOrTeleport.
  6. Evidence appears that ACE (or the connected environment) DOES emit a missile UpdatePosition — the gate section changes and the user decides.
  7. The complete Release suite deviates from baseline beyond the two named flakes.

10. What this slice does NOT do

  • AP-131 (shared merge call) — C5. AP-135 — stays, untouched (its sites are the remote tail's, which this slice does not enter).
  • #276 — untouched.
  • #316 — the player landing block's missing shadow publish: OPEN, unmeasured, and must not be "fixed" here (it lives in the remote tail's preserved asymmetry; measuring it first is its own issue's requirement).
  • ProjectileController.TryBind + create branch — recorded, retained; C5's deletion pass (trap T8).
  • The per-quantum stepper, Vector channel, State channel — untouched (invariants 4/5).
  • No PositionManager/interpolation machinery for projectiles (the rejected 5b) and no headless remote/projectile motion consumer.
  • No changes to routes 2/3/6/7, the local-player paths, or the continuation executor's remote trace gap.
  • The remote prologue's questionable velocity comment (:2296-2300, §4's reporting note) — reported, not edited; it belongs to a 4a-family correction.

11. Contradictions and stale claims versus the scoping — reported, not smoothed

  1. Every scoping file:line in LiveEntityNetworkUpdateController is stale (the collapse rewrote the file): the short-circuit :1428-1448:2104-2125; the fabricated velocity :1442-1443:2119-2120; the CanAcceptPositionPayload call :1205-1208:1898-1901; OwnsPlacement :274-278:520-524; the lifetime's kind hardcode :622:642. §1 is the corrected inventory.
  2. The scoping's central structural assumption — that route 5 rewrites call sites inside two parallel remote copies — is void. There is one unified tail, and route 5 does not enter it at all (§2). The scoping's §8 App-rewrite estimate shrinks accordingly.
  3. The scoping under-counted the ownership fences. It named only OwnsPlacement (§5.2); 4b-2/4b-3 added OwnsFarSnap and OwnsTeleportPlacement (both RemoteAuthoritative-gated) and two throwing, RemoteMotion-required arm methods. "The far and teleport branches have no handler the instant the short-circuit is removed" is still true, but the failure mode is now loud (throws/refusals), and the fix is a sibling seam over the shared core — not merely the ~10-line widening the scoping described.
  4. The scoping's §5.2 sequencing warning ("that file is route 4b-2's active edit surface") is spent — 4b-2/4b-3/collapse/#315 have all landed; route 5 has the file to itself.
  5. The scoping's §1.1 claim that the classifier is "disposition-identical" for Projectile and Remote remains true at HEAD (re-verified against the current classifier body and its pinning test) — the collapse changed the App, not the classifier.
  6. The scoping's T5 swallow concern is narrower than written: the invalid-payload swallow largely happens UPSTREAM in the shared authority gate (CanAcceptPositionPayload feeds TryAcceptPosition, which returns before the short-circuit); D2/D3's internal return true swallows cover only the origin-translated-world-position and secondary-velocity checks. The invariant (no fall-through to the remote path) is pinned regardless (§5 item 9); the mechanism the scoping described was partially stale even at its own HEAD.
  7. New since the scoping and binding here: #316 (filed by the collapse contract; carried untouched), #315's cached-callback pattern (binds new projectile wiring), and the collapse's two protected guid asymmetries.
  8. No contradiction found in the scoping's retail research — every §2 claim re-verified (§4), including the ConstrainTo/MakePositionManager chain, the arg5 non-use (still textual-only; byte check now mandatory), and the ACE never-sends fact. The scoping's recommendation of §5.1 option 2 (record the divergence, build no machinery) is adopted and pinned (D-P4).
  9. One adjacent-comment defect found while verifying (not in the scoping): the remote prologue's :2296-2300 velocity justification contradicts the decomp (§4 note). Out of scope; reported.