# C4 route 5 — projectile authoritative placement: scoping (2026-08-04) Research only. Nothing implemented, nothing edited under `src/` or `tests/`. All reads are against the clean tree at `d5bdc355` (`git status --porcelain` empty at the time of reading; route 4b-2 was in flight in another agent's session and had not yet written). **Headline: route 5 is much smaller than route 4, and roughly half of it has already shipped.** The Create half and the residence-window Position half are canonical today (C3b/C3c). What remains is one execution seam, three deletions, and two policy decisions that the campaign has not yet made. It should NOT split. But one of those policy decisions — the near-`Interpolate` branch — has no executable machinery in acdream at all, and if the contract does not settle it up front the implementer will reproduce route 4b-2's "deleted the only handler for a live branch" failure exactly. --- ## 1. What route 5 actually is ### 1.1 The classifier surface `RuntimePositionEntityKind.Projectile` (`src/AcDream.Runtime/Physics/RuntimeAuthoritativePositionRouteClassifier.cs:13`) is route 5's entity kind. It has exactly **one** behavioural effect anywhere in the classifier: - `OperationKind` (`:559-570`) maps it to `RuntimeSetPositionOperationKind.ProjectileAuthoritative` (`:567-568`). That is all. `ClassifyCreate` (`:205-273`) gives a Projectile the identical `SetPosition` + `InitialCreateFlags` route as any non-local entity — the only `EntityKind` test in that method is `is RuntimePositionEntityKind.LocalPlayer` at `:263-266`, for the teleport hook. `ClassifyAcceptedPosition` (`:308-473`) has a single `LocalPlayer` branch at `:349`; **Projectile and Remote fall through the same code from `:393` onward and are byte-for-byte identical in disposition, flags, `StopInterpolating`, and `ConstrainPhase`.** The existing test `RuntimeAuthoritativePositionRouteClassifierTests.cs:418-436` (`ProjectilePosition_UsesRemoteMoveOrTeleportClassification`) pins exactly that. So the dispositions route 5 owns for an accepted Position are the same four route 4 owns, discriminated only by `OperationKind == ProjectileAuthoritative`: | Branch | Condition (classifier line) | Runs SetPosition? | |---|---|---| | `SetPosition` | `TeleportAdvanced \|\| CommittedCellId == 0` (`:399`) | yes | | `NoPositionOperation` | `!effectiveContact` (`:425`) | no | | `Interpolate` | contact, `PlayerDistance < 96 m` (`:454-459`) | no | | `SetPositionSimple` | contact, `PlayerDistance >= 96 m` (`:454-459`) | yes | **Consequence worth stating plainly: route 5 is not a classification change at all.** Whether a projectile's accepted Position is tagged `Projectile` or `Remote`, the classifier returns the same route. Route 5 is an *execution and ownership* consolidation — which owner runs the route, and which ledger the operation lands in. ### 1.2 What is ALREADY canonical **(a) The Create half — DONE, live in production.** `RuntimeInitialCreateResidenceState.Begin` decides the kind at `:591-595`: ``` RuntimePositionEntityKind entityKind = isLocalPlayer ? RuntimePositionEntityKind.LocalPlayer : (record.FinalPhysicsState & PhysicsStateFlags.Missile) != 0 ? RuntimePositionEntityKind.Projectile : RuntimePositionEntityKind.Remote; ``` This is the **only** production producer of `RuntimePositionEntityKind.Projectile` in the tree (verified by grep across `src/` and `tests/`). The first-entry conductor accepts it explicitly: `RuntimeRemoteFirstEntryState.cs:311-313` admits `RemoteAuthoritative or ProjectileAuthoritative`. And App has already been cut over — `DatLiveEntityProjectionMaterializer.cs:848-865` deliberately **skips** `_projectiles.TryBind` while `initialResidenceActive`, with a comment naming the exact bug that forced it ("the conductor then correctly rejected the unexpected owner and the missile remained permanently cell-less"). `TryBind` is retried on the committed projection-visible edge (`ProjectileController.cs:870-897`), by which point the canonical body exists and has been placed by the conductor. **(b) The residence-window Position half — DONE, dormant-but-wired.** `RuntimeInitialCreateContinuationExecutor.ApplyPositionAction:1892` recovers the kind from the lease's `OperationKind` (`EntityKindOf`, `:2571-2579`, which maps `ProjectileAuthoritative` → `Projectile` at `:2577-2578`) and classifies through the one shared request builder `RuntimeAcceptedPositionRouteRequests.Build` (`:1914-1928`). A Position arriving while a missile's initial residence is open is already fully canonical. Note the executor's own recorded gap at `:2019-2026`: for `Interpolate`/`NoPositionOperation`/`AwaitFreshPosition` it emits a typed trace and returns — *"Binding to the live interpolation owner is cutover work."* That gap is shared with route 4 and is one half of the policy question in §5 below. ### 1.3 What is still a duplicate authority **The post-residence (steady-state) accepted Position for a live projectile.** Entry point: - `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:1428-1448` — inside `OnPosition`, positioned AFTER the remote-teleport hook (`:1417-1426`) and BEFORE route 4a's classification (`:1473-1481`). It calls `_projectileController.ApplyAuthoritativePosition(...)` and **returns when it returns `true`**, so a projectile never reaches the classifier at all today. - `src/AcDream.App/Physics/ProjectileController.cs:492-590` — `ApplyAuthoritativePosition` (two overloads + doc comment, 99 lines). Validates, then delegates. - `src/AcDream.Runtime/Physics/RuntimeProjectilePhysicsUpdater.cs:301-424` — the actual authority (124 lines): `body.Orientation = orientation` (`:345`), `body.SnapToCell(fullCellId, worldPosition, cellLocalPosition)` (`:346`), `body.State = record.FinalPhysicsState` (`:347`), a velocity commit (`:348-358`), `CommitProjectileCell` (`:370-375`), the presentation acknowledgement (`:387`), and the `InWorld`/`Activate`/shadow-sync tail (`:390-422`). `CommitProjectileCell` itself is **not** a bypass — `RuntimePhysicsState.cs:1376-1397` routes it into the shared `CommitCanonicalCell` (`:2138-2160`), the same canonical-cell writer the remote path uses. The 2026-08-02 inventory called it "ad hoc"; that is wrong and should be corrected in the contract. The bypass is `body.SnapToCell` + the `InWorld`/shadow tail running outside the `RuntimeSetPositionState` transaction, not the cell commit. ### 1.4 Adjacent, NOT route 5 Per the inventory and confirmed by reading: - **Per-quantum integration stays out of `SetPosition`.** `RuntimeProjectilePhysicsUpdater.TryBegin`/`Complete` (`:37-205`), driven by `ProjectileController.Tick` (`:640-766`) and `LiveEntityAnimationScheduler` (`:403-426`). `Complete`'s `SnapToCell` at `:145-152` plus `CommitProjectileCell` at `:158-163` is the simulation commit, not an authoritative placement. The 1,880 B/op → 944 B/op placement budget (C2, plan `:155-170`) is per *operation*; projectile integration runs per object quantum on every missile in flight. **Do not touch it.** - **`ApplyAuthoritativeVector` (`RuntimeProjectilePhysicsUpdater.cs:207-250`) and `ApplyAuthoritativeState` (`:252-299`)** are not placement authorities. Vector already goes through `_physics.TryCommitAuthoritativeVector` (`:242`). State's one pose write is `body.SnapToCell(record.FullCellId, body.Position, ...)` at `:286-296` — a re-normalize of the frame into the *already canonical* cell on the Missile-bit rising edge, not a move. The inventory's "reduce to acknowledge-only" framing overstates what is there. --- ## 2. Retail truth Verify each yourself; every anchor below was read in `docs/research/named-retail/acclient_2013_pseudo_c.txt` during this pass. ### 2.1 There is no projectile branch. At all. `SmartBox::UnpackPositionEvent` @0x004542C0 resolves the object from `CObjectMaint` (@0x004542F2) and calls `HandleReceivedPosition` (@0x00454358) with no state/kind test. `SmartBox::HandleReceivedPosition` @0x00453FD0: - FORCE_POSITION early return @0x0045400C requires `arg2 == this->player`. - `unset_parent` @0x00454129 unconditional; `SetPlacementFrame` @0x00454142 gated on `!HasAnims`. - `if (arg2 != this->player)` @0x0045414D → `MoveOrTeleport` @0x00454254 → **`ConstrainTo` @0x00454272 inside `if (MoveOrTeleport(...) != 0)`, anchored to `&arg2->m_position` read live (post-move)** → `return`. A missile is not `this->player`, so **a projectile takes the identical remote arm.** No `state & Missile` test exists anywhere in this function. `CPhysicsObj::MoveOrTeleport` @0x00516330: ``` if (!newer_event(POSITION_TS-ish gate)) return 0; // @0x00516364/@0x0051636D if (newer_event(TELEPORT_TS, arg3) || this->cell == 0) { // @0x00516386 teleport_hook @0x005163EF; SetFlags(0x1012) @0x00516414; SetPosition @0x00516420; return 1; // @0x00516438 } if (arg4 != 0) { // @0x0051638E — the contact bit if (player_distance < 96f) // @0x00516390-@0x00516399 { InterpolateTo(this, arg2, IsMovingTo(this)) @0x005163AF; return 1; } if (position_manager) StopInterpolating @0x005163CB; SetPositionSimple(this, arg2, 1) @0x005163D9; return 1; // @0x005163E8 } return 0; // @0x0051636D ``` `SetPositionSimple` @0x005162B0 with `arg3 != 0` builds `0x1012` (`Teleport|Slide|SendPositionEvent`, @0x005162C4) — the classifier's `AuthoritativeTeleportFlags`. **Answers to the specific questions asked:** - *Does a projectile's authoritative Position take the same `MoveOrTeleport` path as a remote?* **Yes, identically.** Same function, same branches, no discriminator. - *What flags?* Teleport/cell-less → `0x1012` via the explicit `SetFlags` @0x00516414. Far snap → `0x1012` via `SetPositionSimple(…, 1)` @0x005162C4. Near and airborne → no `SetPosition` at all. - *Is `ConstrainTo` armed for projectiles?* **Yes** — @0x00454272, on any nonzero `MoveOrTeleport` return, with no kind test. It is NOT armed on the airborne no-op (return 0 @0x0051636D). `CPhysicsObj::ConstrainTo` @0x00510520 calls `MakePositionManager` first, so retail *creates* the manager on demand for a missile. - *Is `StopInterpolating` called?* **Only on the far branch**, and only if a `position_manager` already exists (@0x005163C9-@0x005163CB). A missile that has never been near-interpolated has none, so the call is skipped. ### 2.2 `player_distance` is maintained for missiles `CPhysicsObj::update_object` @0x00515D10 computes `player_vector` from `Position::get_offset` @0x00515D5B and stores `player_distance` @0x00515D95 for every active, unparented, in-cell object — which includes every in-flight missile. So retail's near/far test is meaningful for projectiles. (BN artifact note: the decomp shows `player_distance` taking `player_vector.x` @0x00515D7B-@0x00515D95. That is the standard x87-elision artifact for a vector magnitude; acdream's Euclidean `PlayerDistance` is the right reading. Flagging it so nobody "fixes" it to `.x`.) ### 2.3 `MoveOrTeleport` discards the velocity argument `MoveOrTeleport(this, arg2, arg3, arg4, arg5)` declares `arg5 = AC1Legacy::Vector3 const*` and **never references it** in the decompiled body @0x00516330-@0x00516438. `HandleReceivedPosition` threads `arg6` into it @0x00454254 and does nothing else with it. Retail's velocity authority is `set_velocity` via VectorUpdate, not the Position event. *Confidence:* the parameter is textually unreferenced. I did not byte-verify against `refs/acclient.exe` that BN did not elide a use. Treat as **strong but not byte-confirmed**; it matters only because acdream currently *does* commit a velocity here (§3, item 4). ### 2.4 What retail actually does to an in-flight missile — mostly nothing Chain the three facts: an in-flight missile has a non-null cell, no advancing TELEPORT_TS, and no ground contact. `arg4 == 0` → `return 0` @0x0051636D. **Nothing is written, and `ConstrainTo` is skipped.** The contact bit's provenance is ACE `PositionPack.BuildFlags` (`references/ACE/Source/ACE.Server/Network/Structure/PositionPack.cs:72-73`): `IsGrounded` is set from `TransientState & OnWalkable`. A flying missile is not `OnWalkable`. *Not established:* I did not observe a live missile Position packet, because (see §6) ACE does not send one. The claim "an in-flight missile's Position packet would carry `IsGrounded == false`" is an inference from ACE's flag definition, not a measurement. --- ## 3. The duplicate authority to delete Exact deletion targets, with the fabricated values called out. | # | Site | Lines | What it is | |---|---|---|---| | 1 | `src/AcDream.Runtime/Physics/RuntimeProjectilePhysicsUpdater.cs:301-424` | 124 | The real authority: `SnapToCell` `:346`, velocity commit `:348-358`, `CommitProjectileCell` `:370-375`, `InWorld`/`Activate`/shadow tail `:390-422` | | 2 | `src/AcDream.App/Physics/ProjectileController.cs:492-590` | 99 | Two `ApplyAuthoritativePosition` overloads + doc; validation, currency closures, render-pose projection `:583-586` | | 3 | `src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:1428-1448` | 21 | The early-return call site that keeps projectiles out of the classifier | Raw total **244 lines**; roughly **180 non-comment**. **Fabricated values on those paths:** - `LiveEntityNetworkUpdateController.cs:1442-1443`: `acceptedSpawn.Physics?.Velocity ?? System.Numerics.Vector3.Zero`. This is the exact `?? Vector3.Zero` shape route 4b-2's contract calls out, and it is worse than an inert default: it is passed into `RuntimeProjectilePhysicsUpdater.cs:348-358`, which commits it as the body's authoritative velocity whenever `VelocityAuthorityVersion` matches. A Position packet with no `PhysicsDesc.Velocity` therefore **zeroes an in-flight missile's velocity**. Retail's `MoveOrTeleport` does not consume its velocity argument at all (§2.3). - No duplicated distance/threshold constants exist on the projectile path — `MaxPhysicsDistance = 96f` and `BodySnapThreshold = 4f` live only in the classifier (`:194`) and route 4a's seam (`RuntimeRemoteSteadyStatePosition.cs:43`) plus the surviving 4b legacy far halves. Route 5 introduces no new copy and must not. **Not a deletion target, despite appearances:** - `ProjectileController.CanAcceptPositionPayload` (`:102-126`). It is called unconditionally at `LiveEntityNetworkUpdateController.cs:1205-1208` for **every** entity (it returns `true` for non-projectiles via `!= false`), so it is the shared admission validator, not a projectile authority. It also does **not** subsume item 1's inner validation: it checks `update.Position` and `update.Velocity`, while `ApplyAuthoritativePosition` additionally checks the origin-translated `worldPosition` and a *different* velocity (`acceptedSpawn.Physics?.Velocity`). - `ProjectileController.TryBind`'s create branch (`:222-267`) and its `entity.SetPosition`/`ParentCellId`/`RebucketLiveEntity` tail (`:269-291`). This *is* a placement authority — `GetOrCreatePhysicsBody` with a `SnapToCell` seeded straight from the CreateObject wire frame (`:258-264`) — but it is now the **fallback** for the paths the residence conductor does not own: `initialResidenceActive == false` (`DatLiveEntityProjectionMaterializer.cs:857`) and the late-classification route `ApplyAuthoritativeState` → `TryBind` (`ProjectileController.cs:488`), where an ordinary remote gains the Missile bit mid-life. Deleting it belongs to route 5 only if route 5 first proves those two paths unreachable or supplies a canonical replacement. **My recommendation: leave it, record it, and let C5's deletion pass own it** — see §7 trap T8. --- ## 4. Prediction / correction This is where the subtle regression lives, and it is a real one. The projectile prediction model (J5.6): `RuntimeProjectile.PredictionAuthorityVersion` (`RuntimeProjectile.cs:36-39`) is bumped by `InvalidatePrediction()`. It is bumped in exactly three places, all in `RuntimeProjectilePhysicsUpdater`: `ApplyAuthoritativeVector:241`, `ApplyAuthoritativeState:272`, and **`ApplyAuthoritativePosition:342`**. That version is the **sole** cancellation mechanism for an in-flight split quantum: - `TryBegin` captures it into the commit (`:86-87`). - `Complete` re-checks it via `IsSpatialCurrent`/`IsIdentityCurrent` (`:114-122`, `:131-140`, `:164-174`, `:200-204`, `:440-450`). - App re-checks it too, in `ProjectileController.IsCurrentQuantumIdentity` (`:850-859`). And the quantum is genuinely split across other work: `LiveEntityAnimationScheduler.cs:403-426` calls `TryBeginQuantum` at `:404-408`, runs `_animationHooks.Capture` at `:411`, then `CompleteQuantum` at `:418-422`. `ProjectileController.AdvanceQuantum` (`:773-782`) is the fused variant for un-animated missiles. **The trap:** if route 5 replaces `ApplyAuthoritativePosition` with a `RuntimeSetPositionState` route and does not invalidate the projectile's prediction version at the same point, then (a) an already-begun quantum's `Complete` will no longer abort, and will write the pre-correction integrated pose over the freshly committed placement, and (b) the version stops advancing on this channel, weakening the currency checks that the Vector and State channels still rely on. The fix is trivial once seen — the contract must require the replacement to invalidate prediction at the *same* point in the sequence retail's `SetPosition` would clobber the body — but it is invisible if you only read the classifier. *Not established:* whether the network drain can actually land between `:408` and `:418` in a single frame (both are on the update thread; the wire pump is a separate frame phase, and `_animationHooks.Capture` can dispatch effect callbacks). I could not settle re-entrancy by reading alone. The mechanism — `InvalidatePrediction` being the only cancel — is established regardless, and that is enough to make the requirement binding. **Second prediction interaction:** retail's near branch is `InterpolateTo(this, arg2, IsMovingTo(this))` @0x005163AF, and `CPhysicsObj::IsMovingTo` @0x0050EB10 returns nonzero only when a `MovementManager` exists and is moving-to. A missile has no MovementManager, so retail passes 0. Any acdream near-branch policy that wants to be retail-shaped must pass `isMovingTo: false` for projectiles. --- ## 5. The two policy decisions the contract must make Both are of the shape the 4b-2 contract names: *"deleting the legacy block removes the only handler for a live classification."* Neither can be deferred into a code comment. ### 5.1 `Interpolate` (near, in contact) has NO executable machinery Route 4a's seam is `RuntimeRemoteSteadyStatePosition.ApplyInterpolate` (`:113-149`) and it takes a `RemoteMotion` — it drives `remote.Interp` (the interpolation queue) and `remote.Body`. `TryArmConstraintAfterOperation` (`:165-180`) requires `remote.Host` to reach `host.PositionManager.ConstrainTo`. A projectile has **none of that**. `RuntimeProjectile` (`RuntimeProjectile.cs:17-40`) is `{ Body, CollisionSphere, PredictionAuthorityVersion }`. No `EntityPhysicsHost`, no `PositionManager`, no `InterpolationManager`, no `ConstraintManager`. A pure missile also never acquires a `RemoteMotion` — that is created on the 0xF74C motion path, and `ProjectileController.HandlesMovement` (`:599-602`) plus `LiveEntityAnimationScheduler.cs:256-257,336` keep the two owners disjoint while the Missile bit is set. (Coexistence *is* representable — `ProjectileController.Tick:756` handles `record.RemoteMotionRuntime is RemoteMotion` — but it is the adopted-body case, not the ordinary arrow/bolt.) So the contract must choose, explicitly: 1. **Build retail's `PositionManager` chain for projectiles.** Retail-exact (`ConstrainTo` @0x00510520 does `MakePositionManager` on demand). Also the only way to be exact about `StopInterpolating` @0x005163CB and the leash @0x00454272. Large: a new interpolation/constraint owner for a body type that has none, plus its interaction with the per-quantum stepper. **If this is chosen, it is route 5b and route 5 splits.** 2. **State an acdream divergence: a projectile's near-`Interpolate` and its `ConstrainTo` are no-ops**, with a register row citing @0x005163AF and @0x00454272 and the reason (no PositionManager exists for a ballistic body; the classifier's own `ConstrainPhase` is honoured for the placement branches only). Cheap, honest, and — given §6 — has no observable production effect. **My recommendation is (2)**, with the row filed in the same commit. Choosing (1) for a branch that ACE never triggers would be building machinery for a packet that does not exist. ### 5.2 The placement dispositions have no owner `RuntimeRemotePlacementDriveController.OwnsPlacement` (`src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs:274-278`) requires: ``` route.OperationKind is RuntimeSetPositionOperationKind.RemoteAuthoritative && route.Disposition is SetPosition or SetPositionSimple && (route.SetPositionFlags & PhysicsSetPositionFlags.Teleport) != 0 ``` `ProjectileAuthoritative` is **excluded**. So the moment route 5 stops short-circuiting at `LiveEntityNetworkUpdateController.cs:1432`, the `SetPosition` (teleport / cell-less) and `SetPositionSimple` (far) branches for a projectile have *no handler at all*. The controller is otherwise entirely kind-agnostic: the service window, the per-entity `_pending` map, the `_awaitingAcknowledgement` ledger, the refuse-not-park decision, and `DetachRoute`'s cancellation all read only the record and the token. Widening the first clause to `is RemoteAuthoritative or ProjectileAuthoritative` (plus its class/method doc) is ~10 lines and is the right move. Do **not** clone a sibling controller. **Sequencing constraint:** that file is route 4b-2's active edit surface, and route 4b-3 will touch it again. Route 5 must land *after* 4b-2, and its contract should say so. --- ## 6. The connected gate — and an honest problem with it **ACE never sends an `UpdatePosition` for a missile.** The only physics-tick site that would is commented out: `references/ACE/Source/ACE.Server/WorldObjects/WorldObject_Tick.cs:333-334` (`/*if (PhysicsObj.IsGrounded) SendUpdatePosition();*/`), inside the branch explicitly gated on `(PhysicsObj.State & PhysicsState.Missile) != 0` at `:265`. Every live `SendUpdatePosition` / `GameMessageUpdatePosition` caller in `ACE.Server` is a Player, Creature/Monster, Pet, GamePiece, inventory move, or an admin command (grepped exhaustively). The projectile classes broadcast `GameMessageVectorUpdate` (`SpellProjectile.cs:238`) and `GameMessageSetState` (`:229`) on impact — never a Position. So **route 5's authoritative-Position half is unreachable in ordinary play against ACE**, and there is no cheap live trigger: the one admin path (`AdminCommands.cs:4772`, move-selected-object-to-me) requires selecting a projectile inside its ~5 s `ProjectileTimeout` lifetime. The contract must say this rather than invent a gate. Concretely: **What the user CAN observe (the Create half + no-regression):** 1. In peace mode with a bow: fire an arrow at a target ~20 m away. The arrow must appear at the launch point immediately, fly a clean arc, and vanish on impact. 2. Cast a Force Bolt / Flame Bolt at a target across a landblock boundary (stand near a seam, target something on the other side). The bolt must not freeze, teleport, or vanish at the seam. 3. Fire indoors, in a dungeon: the projectile must respect the EnvCell and impact on the wall rather than passing through. 4. Fire at a target at bow max range (~80 m) and confirm the projectile does not stall or snap. 5. After each impact, walk through the impact point. **An invisible collider there is the loud regression** — it means a projectile's shadow/cell state survived its retirement. **Regression signatures:** projectile spawns at the origin or at the player's feet instead of the launch point; hangs motionless; disappears on frame 1; appears only after it has already travelled; leaves an invisible-but-solid body (the #184 signature); an arrow that visibly *slows or stops* mid-flight (that would be the §3 velocity-zeroing path, which route 5 removes — so if it is present today, it should stop). **What the gate cannot cover:** the four accepted-Position dispositions. Those must be gated by focused Runtime + App tests, with the ACE citation above recorded as the reason. Do not let a green connected gate be presented as evidence for the Position half. --- ## 7. Traps **T1 — the near-`Interpolate` branch loses its only handler.** §5.1. Highest risk. Today `ApplyAuthoritativePosition` handles *every* projectile Position shape by hard-correcting. Route 5 replaces it with four dispositions, two of which (`Interpolate`, `NoPositionOperation`) have no projectile executor. The airborne one is correct as a no-op (retail returns 0). The near one is not, and silently dropping it is the same failure as 4b-2's `null`/`Rejected*` trap. **T2 — `OwnsPlacement` excludes `ProjectileAuthoritative`.** §5.2. The far and teleport/cell-less branches have no owner the instant the short-circuit is removed. **T3 — prediction invalidation.** §4. `InvalidatePrediction()` is the only mechanism that aborts an in-flight split quantum; the SetPosition route does not call it. **T4 — the entity kind is decided ONCE, at Create.** `RuntimeInitialCreateResidenceState.cs:591-595` reads `record.FinalPhysicsState & Missile` at Create time and freezes it into the lease's `OperationKind`. But the Missile bit changes at runtime: ACE clears it on impact (`SpellProjectile.cs:229` broadcasts the new state), and `ApplyAuthoritativeState` → `TryBind` (`ProjectileController.cs:473-488`) is the path where an ordinary object *becomes* a missile. Meanwhile `RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition:622` hardcodes `RuntimePositionEntityKind.Remote`. Route 5 must define one per-packet discriminator from the canonical `FinalPhysicsState`, and the remote arm and the projectile arm must be provably mutually exclusive on the same input — otherwise both or neither will own a packet at the moment the bit flips. Because the classifier is disposition-identical for the two kinds (§1.1), a mis-tag is *silent*: the route is right and only the ledger/owner is wrong. **T5 — `ApplyAuthoritativePosition` returns `true` on an invalid payload.** `ProjectileController.cs:550-554` and `RuntimeProjectilePhysicsUpdater.cs:329-339` both `return true` — "handled, ignore" — so the packet does *not* fall through to the generic remote path. Their validation is **not** subsumed by `CanAcceptPositionPayload` (§3). Any replacement must preserve the swallow, or an invalid projectile packet starts being routed as an ordinary remote. **T6 — carrying AP-87 onto a branch that does not need it.** Same shape as 4b-2's warning. AP-87's `bodyToTarget > 4 m` / `!willBeDrTicked` guards (`RuntimeRemoteSteadyStatePosition.cs:43,130-138`) exist to stop an unplaced *remote* body being enqueued into an interpolation queue. A projectile has no queue. Do not copy them in. Equally: do not delete the near-branch copies — those are 4a's and still load-bearing. **T7 — routing per-quantum integration through `SetPosition`.** The one explicit prohibition carried from the original route-5 requirement, and the allocation budget is the reason. `RuntimeProjectilePhysicsUpdater.Complete` (`:94-205`) runs per quantum per in-flight missile. **T8 — deleting `TryBind`'s create branch as "obviously superseded".** `ProjectileController.cs:222-267` looks like dead legacy now that C3c owns Create, but it is still the live path for `initialResidenceActive == false` (`DatLiveEntityProjectionMaterializer.cs:857`) and for late Missile classification (`ProjectileController.cs:488`). Same class as the 4a review's R1 (a "duplicate" that turned out to be the only handler for a real case). **T9 — a second snapshot store for the same state.** The `_pending` / `_awaitingAcknowledgement` maps in `RuntimeRemotePlacementDriveController` are keyed by `RuntimeEntityKey` and are already per-entity. Adding a projectile-specific pending map alongside them would recreate the two-stores-for-one-state shape the campaign has hit twice. Widen the existing controller; do not add a parallel one. **T10 — the register.** No projectile placement row exists anywhere in `docs/architecture/retail-divergence-register.md` (grepped). Route 5 introduces at least one deviation (§5.1 option 2) and retires at least one (the velocity zeroing, §3). Both rows land in the same commit as the behaviour. --- ## 8. Line budget, and whether route 5 splits Calibration: route 4a = 364 production lines; 4b-1 = 230 + a 57-line park fix; 4b-2 budgeted at 350-500. | Item | Non-comment production lines | |---|---| | Delete the three sites in §3 | ~180 removed | | Runtime projectile steady-state seam (airborne no-op + the §5.1 policy + prediction invalidation) | 60-100 | | Projectile classification entry (extend `ClassifyRemoteAcceptedPosition` with a kind parameter, or a sibling) | 10-35 | | Widen `OwnsPlacement` + doc | ~10 | | App call-site rewrite in `OnPosition` (classify → own → skip generic → project committed placement) | 40-70 | | **Total added** | **120-215** | **Estimate: 250-400 non-comment production lines touched, of which 120-215 are new.** Test work is the larger share as usual — roughly 400-700 lines (Runtime seam tests per disposition, the prediction-invalidation test, a *behavioural* App test proving the generic path no longer runs for a projectile). **Route 5 should NOT split — conditional on §5.1 resolving to option 2.** If the contract chooses to build a `PositionManager`/`InterpolationManager` chain for projectiles, that is a separate 5b landing of its own and the estimate above does not cover it. **Sequencing: route 5 lands after 4b-2 and, preferably, after 4b-3**, because it edits `RuntimeRemotePlacementDriveController` and `LiveEntityNetworkUpdateController.OnPosition` — both of which 4b-2/4b-3 are rewriting heavily. A parallel route-5 landing would be exactly the "coupled plan slices in parallel" failure the project has already recorded. --- ## 9. Explicitly not established 1. **Whether the network pump can land a packet between `TryBeginQuantum` and `CompleteQuantum`** (§4). Would be settled by an `ACDREAM_PROBE_*` counter on a straddled `Complete` returning false, or by reading the frame graph's phase ordering end to end. 2. **Whether retail's `MoveOrTeleport` genuinely ignores its velocity argument**, or BN elided a use (§2.3). Would be settled by `tools/pdb-extract` byte-decode of 0x00516330-0x00516438, or a cdb breakpoint at @0x00454254 dumping `arg6` and comparing the object's velocity before and after. 3. **Whether an in-flight missile's Position packet would carry `IsGrounded == false`** (§2.4) — inferred from ACE's `PositionPack.cs:72-73`, never observed, because ACE does not emit the packet. 4. **Whether `initialResidenceActive == false` is actually reachable for a Missile-state top-level Create in the graphical host** (§3, T8). It is plainly reachable in the content-less headless path (C3c: "content-less headless keeps pre-flip direct registration") and for Parented/PickedUp Creates. For a graphical top-level missile Create it depends on `RuntimeInitialCreateResidenceState.CanAcceptCreate` (`:559-573`) never refusing, which I did not prove. 5. **Whether any retail server ever sent a projectile Position.** Only ACE was checked. Retail's client clearly supports it (§2.1); acdream targets ACE.