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>
804 lines
55 KiB
Markdown
804 lines
55 KiB
Markdown
# 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`](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`](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`](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](2026-08-04-c4-route-4b-3-retail-review.md) /
|
||
[round 2](2026-08-04-c4-route-4b-3-retail-review-round2.md) /
|
||
[architecture](2026-08-04-c4-route-4b-3-architecture-review.md) /
|
||
[round 2](2026-08-04-c4-route-4b-3-architecture-review-round2.md)) — 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`](2026-08-04-session-handoff-c4-remaining.md)
|
||
— the six process rules apply verbatim.
|
||
- [`docs/plans/2026-08-02-placement-cutover.md`](../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 |
|
||
|---|---|---|
|
||
| `RuntimeAuthoritativePositionRouteClassifier` — `RuntimePositionEntityKind.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 flag` — **excludes `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 `UnroutedCatchUp` → `ApplyInterpolate` 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.Capture` … `CompleteQuantum` `: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 == 0` → `return 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`):
|
||
|
||
8. **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.
|
||
9. **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.
|
||
10. **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.
|