# C4 route 4a — remote steady-state Position: pinned contract (2026-08-03) Route 4 split into 4a/4b by user direction after scoping put the whole route at 1,500-2,500 lines against a ~400 budget. Scoping: [`2026-08-03-c4-route-4-scoping.md`](2026-08-03-c4-route-4-scoping.md). **4a is the steady state: the two classifier branches that perform NO `SetPosition`.** Everything that parks, teleports, or snaps is 4b. ## Scope — exactly two classifier branches `RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition` (`:393-472`), remote/projectile arms: | Branch | Condition | Runs SetPosition? | In 4a? | |---|---|---|---| | `NoPositionOperation` | `!effectiveContact` (wire `IsGrounded == false`) | no | **YES** | | `Interpolate` | contact, `PlayerDistance < 96 m` | no | **YES** | | `SetPositionSimple` | contact, `PlayerDistance >= 96 m` | yes | no — 4b | | `SetPosition` | `TeleportAdvanced` or `CommittedCellId == 0` | yes | no — 4b | Because neither 4a branch performs a placement, 4a has **no deferred-cell park, no service-window guard, no allocation exposure, and no interaction with `Forget`-on-every-accepted-Position.** That is the entire reason for the split; do not drag any of it in. ## Staged cutover — the one sanctioned dual path 4a routes the two no-placement branches through the new Runtime seam and leaves the other two on the legacy App path until 4b. **This is a staged cutover, not a duplicate authority**, and it is only sanctioned under these conditions: 1. The discriminator is the CLASSIFIER ITSELF, not a heuristic, a flag, or a guess. One classification, one owner, mutually exclusive by construction. 2. For a classification 4a owns, the legacy path must not run **at all** — not partially, not "just the render write". Route 2's original defect was exactly a second writer running after the canonical one. 3. The fallback is temporary and 4b deletes it. Record that in the code comment at the branch point, with a pointer to this contract. If you find yourself needing a condition beyond "what did the classifier say", STOP and report — that means the seam is wrong. ## Retail truth (verify each yourself; do not trust this table) `CPhysicsObj::MoveOrTeleport` @0x00516330 (pseudo-C 284304): - **Airborne no-op** @0x0051638E / @0x0051636D: `arg4 == 0` (the wire `has_contact` bit) -> **return 0. Nothing is written at all.** - **Near interpolate** @0x005163AF: `player_distance < 96f` -> `InterpolateTo(arg2, IsMovingTo())`. No body write. - `player_distance` is retail's own field — distance to the local player. `SmartBox::HandleReceivedPosition` @0x00453FD0, remote branch: - `unset_parent` @0x00454129 unconditional; `SetPlacementFrame` @0x00454142 gated on `!HasAnims`. - **`ConstrainTo` @0x00454272 runs AFTER `MoveOrTeleport` returns nonzero**, anchored to `&arg2->m_position` — the object's own position read live, i.e. POST-move. It does not run when `MoveOrTeleport` returned 0 (the airborne no-op). ## The two divergences 4a must fix **D1 — the NPC airborne branch ignores the wire bit.** `LiveEntityNetworkUpdateController.cs:1819-1823` hard-snaps `Body.Position`/`Orientation` for NPCs and branches on the client-tracked `rmState.Airborne` flag, never consulting `update.IsGrounded`. Retail writes nothing. Player remotes already behave correctly (`:1590-1599`); NPCs do not. No register row exists. **D2 — `ConstrainTo` is armed before the operation, unconditionally.** `:1522-1529` arms it ahead of the branch, so it fires on the airborne no-op retail skips and anchors to the PRE-move position. Retail arms it after, only on a nonzero return, anchored post-move. No register row exists. (The third divergence — `ConstrainTo` never armed on the remote teleport branch — is on a 4b path. Leave it; 4b owns it.) ## Duplicate authorities 4a deletes Only the parts reachable from the two 4a branches: - The generic tail's remote RENDER-POSE writes (`entity.SetPosition` / `ParentCellId` / `Rotation`) for a remote whose classification is `Interpolate` or `NoPositionOperation`. > **CORRECTED 2026-08-03 (review finding R1, > [`2026-08-03-c4-route-4a-review-findings.md`](2026-08-03-c4-route-4a-review-findings.md)).** > This bullet originally also listed `RebucketLiveEntity`, and said the > result "must reach the render entity through the existing > placement-projection sink instead — the same substitution route 2 made." > That instruction was copied from route 2 and does not transfer: route 2 > performs a placement and therefore has a committed receipt to project, > while NEITHER 4a branch performs a placement, so nothing substitutes for > the bucket transaction. **The rebucket keeps running for both 4a > classifications.** It is the only site that moves an ordinary moving > remote's draw bucket, commits its canonical `FullCellId`, and recovers a > pending bucket promotion (`GpuWorldState`, the 2026-07-03 invisible-player > fix); deleting it produced the #184-class invisible-but-solid creature > through a different door. - The player-remote near/far routing at `:1653-1702` and the NPC copy at `:1826-1871` — **the near half only.** Each has its own duplicated copies of `MaxPhysicsDistance = 96f` and `BodySnapThreshold = 4f`; the far half stays until 4b. - The airborne no-op blocks at `:1590-1599` (player) and `:1819-1823` (NPC). - The unconditional `ConstrainTo` at `:1522-1529`. Do NOT touch `RemoteTeleportController`, `RemoteTeleportPlacement`, or the `remotePlacementRequired` path — all 4b. ## Load-bearing acdream additions that must survive **AP-87** (`retail-divergence-register.md:242`) — the `bodyToTarget > 4 m` and `!willBeDrTicked` snap conditions on the near branch are NOT in retail and NOT in the classifier. They are load-bearing: they prevent the #184 invisible-but-solid monster. Either carry them explicitly as an acdream policy layer over the retail classification, or retire them with live evidence and the register row deleted in the same commit. **Silently dropping them by delegating to the classifier is the failure mode.** Say which you chose. **TS-44** (`:1792-1801`) — sticky-melee suppression of the NPC snap. Same rule. ## Contract 1. One Runtime owner executes the accepted remote Position for the two 4a classifications; App projects the result. 2. Both hosts drive the identical Runtime entry point. 3. The interpolation queue (`RemoteMotion.Interp`) stays the owner of near motion — 4a routes to it, it does not replace it. All of it is already in Runtime (J5.5); no assembly boundary is crossed. 4. Stale sequences, GUID reuse, incarnation change, and generation change cannot commit old state. N entities, so per-entity currency — route 2's `_pending` was one slot and `RetainPending` threw on a second; that shape does not transfer. 5. The airborne branch writes NOTHING (retail returns 0). Not the body, not the render entity, not the cell. > **AMENDED 2026-08-03/04 (review finding R4).** As shipped, the branch > retains exactly two acdream bookkeeping writes — `rmState.CellId` and > `LastServerPos`/`LastServerPosTime` — on both arms. These are not part of > retail's model (retail's `MoveOrTeleport` has no catch-up sweep and no > staleness timer to keep alive), and dropping them would break the > per-tick free-fall sweep and the staleness timer respectively. Verified > NOT a canonical cell commit for ordinary remotes: `RemoteMotion.CellId` > only delegates to `RuntimePhysicsState` when `_canonicalCellWriter` is > bound, which is projectiles-only, and projectiles return earlier. > Recorded as register row **AP-135**. Everything else the branch used to > do — body pose, queue, leash, render entity, shadow publish, and the > velocity-derived animation cycle — is genuinely skipped. 6. `ConstrainTo` moves to after the operation, anchored post-move, and does not run on the airborne branch. 7. No behaviour change to the far, teleport, or cell-less branches. ## Acceptance - Focused Runtime tests for both branches, per-entity currency, and the AP-87 / TS-44 decision. - App tests proving the generic tail no longer double-writes a remote on a 4a classification — **behavioural, not a source-text pin.** Route 2's equivalent was source-pinned and that gap is filed as #292; do not repeat it. - Complete Release suite green. Baseline at the time of writing: **10,904 passed / 4 skipped / 0 failed**. Known flake #302 (`PortalProjectionTests.ClipToRegion_FrameOwnedStore_ReusesExactResultArray`, ~1 in 6) — re-run, do not chase. - **Connected (user-gated), and unlike route 2 this is trivial:** stand still, have a second character walk and run in a circle 5-15 m away, turning in place. Motion must be continuous and smooth with no per-packet stepping. Then have them jump and jump off a ledge — a clean parabola, clean landing, no mid-air correction, and critically no invisible-but-solid body (the #184 / AP-87 signature). Then pull a drudge, let it chase, melee it, let it die. - Divergence ledger: D1 and D2 retired by fix in the same commit. AP-131 stays. AP-87 / TS-44 either stay with justification or are retired with evidence. - **Added 2026-08-03 (review finding R15).** D1 makes ACE's wire `IsGrounded` newly load-bearing for NPC remotes: ACE emits it from `TransientState & OnWalkable` (`PositionPack.cs:73`), while acdream's NPC free-fall was gated on the client-tracked `rmState.Airborne` (set only by `0xF74E` VectorUpdate or `!Body.OnWalkable`, never from the wire bit). A creature ACE reports as not-in-contact while the client believes it grounded now receives NO correction where the legacy routing pulled it. **Push a monster off a ledge / pull one down a cliff**, and confirm it falls and lands without hovering, without a mid-air correction, and without an invisible-but-solid body. ## Budget Stated up front so it can fail: **4a should be well under 400 production lines**, because it adds no placement machinery — it routes two no-op classifications and deletes their duplicates. If it exceeds that, stop and report before continuing; that would mean the split did not actually isolate the cheap half and 4b needs re-planning too.