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

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

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

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

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

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

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

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

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

25 KiB

C4 route 5 — projectile authoritative placement: retail-conformance review (2026-08-04)

Reviewer lens: retail fidelity only ("is this what the retail client does?"). Architecture is a separate reviewer's job.

Subject: the uncommitted working-tree diff at branch claude/acdream-physics-divergence-5aa784, HEAD 30d3d114 (git diff HEAD + the untracked tests/AcDream.Runtime.Tests/Entities/RuntimeProjectilePositionKindTests.cs). docs/research/2026-08-04-c4-route-5-contract.md is the pinned contract, not part of the change under review.


VERDICT: FAIL

Two MAJOR findings, both unrecorded divergences — the project rule the divergence register exists to enforce ("any commit that introduces a deviation adds its register row IN THE SAME COMMIT; a deviation found without a row is a bug twice over"). Neither is a hard-to-fix design problem: R1 is a one-clause amendment to AP-141 (or a three-line fall-through decision), R2 is either a wiring of the already-in-tree hook actions or a second clause on the same row.

What is right, and independently verified (Section A below): the retail chain the contract asserts is correct in every particular I checked, the byte-decode retiring the fabricated velocity is correct and I reproduced it from the paired binary, and AP-141 describes retail's mechanical behaviour honestly rather than dressing the divergence up as fidelity.


A. Retail claims verified independently (all PASS)

Verified against docs/research/named-retail/acclient_2013_pseudo_c.txt and, for the byte-decode, against C:/Users/erikn/Downloads/acclient.exe (PDB-paired, image base 0x00400000).

A1. MoveOrTeleport has no state & Missile test — CONFIRMED

CPhysicsObj::MoveOrTeleport @0x00516330 (pseudo-C 284304-284366) reads exactly four object fields: update_times[4] (mov cx,[esi+0x16c]), cell (mov eax,[esi+0x90]), player_distance (fld dword [esi+0x20]), and position_manager (mov ecx,[esi+0xc8]). There is no physics-state read anywhere in the 272-byte function. A missile takes the identical generic remote path. The contract's §4 row 1 is correct.

SmartBox::HandleReceivedPosition @0x00453FD0's only kind branch is arg2 != this->player @0x0045414D — confirmed at pseudo-C 92896-93054. UnpackPositionEvent @0x004542C0 calls it @0x00454358 with no state/kind test.

A2. Branch structure — CONFIRMED

retail branch address acdream disposition verdict
teleport/cell-less: teleport_hookSetFlags(0x1012)SetPositionreturn 1 @0x005163EF / @0x00516414 / @0x00516420 / @0x00516438 SetPosition faithful
near: InterpolateTo(this, arg2, IsMovingTo(this)), return 1 @0x005163AF, @0x005163BE Interpolateno-op recorded divergence (AP-141a)
far: if (position_manager != 0) StopInterpolating, SetPositionSimple(this, arg2, 1), return 1 @0x005163C9-@0x005163CB, @0x005163D9, @0x005163E8 SetPositionSimple faithful; the StopInterpolating skip is retail's OWN guard for a manager-less object, correctly excluded from the divergence row
airborne (arg4 == 0): return 0 @0x0051636D NoPositionOperation → no-op, no arm faithful, correctly NOT claimed as a divergence

The far branch's third argument to SetPositionSimple is 1 (byte 6a 01 at @0x005163D3) — matches.

A3. The ConstrainTo chain — CONFIRMED; retail WOULD arm a missile's leash

  • HandleReceivedPosition @0x00454254: if (MoveOrTeleport(...) != 0) → @0x00454272 ConstrainTo(arg2, &arg2->m_position, ...). No kind test, no manager-existence test.
  • CPhysicsObj::ConstrainTo @0x00510520 → MakePositionManager(this) @0x00510523 → PositionManager::ConstrainTo @0x00510533. The manager is created on demand.
  • The sibling CPhysicsObj::InterpolateTo @0x005104F0 does the same (MakePositionManager @0x005104F3 then PositionManager::InterpolateTo @0x00510508), so the near branch would likewise build machinery for a manager-less missile.
  • PositionManager::UseTime is ticked from UpdateObjectInternal @0x005159A9 — an armed missile leash would be live, not vestigial.

The central design decision is therefore a deliberate divergence, and the change records it as one. AP-141 states plainly that "retail's single arming site has no kind test, so retail WOULD build a PositionManager on demand and arm a missile's leash on any nonzero MoveOrTeleport return", and cites @0x00454272 / @0x00510523 / @0x005163AF / @0x0050EB10 plus the ACE unreachability. It does not claim fidelity it does not have. PASS.

A4. The byte-decode of arg5 — CONFIRMED, reproduced independently

I did not take the implementer's disassembly on trust; I re-derived the stack offset and scanned the function's bytes.

Prologue at @0x00516330: 83 ec 64 (sub esp,0x64), 56 (push esi), 8b f1, 66 8b 8e 6c 01 00 00, 57 (push edi). Displacement from entry esp is therefore 0x64 + 4 + 4 = 0x6C, so:

arg slot after prologue
arg2 (Position*) [esp+0x70]
arg3 (uint16 timestamp) [esp+0x74]
arg4 (int32) [esp+0x78]
arg5 (Vector3 const*, velocity) [esp+0x7C]

Every stack-argument read in the function body: 8b 7c 24 74 @0x0051633E (arg3 → edi), 8b 44 24 78 @0x00516387 (arg4), 8b 44 24 74 @0x005163A7 (arg2 — esp is 4 lower after the preceding push eax), 8b 4c 24 70 @0x005163CF (arg2), 8b 54 24 70 @0x005163FC (arg2). Zero occurrences of the SIB+disp8 byte pair 24 7c in the whole function, and no disp32 (84 24 7c 00 00 00) form either; the only other 0x7c bytes in the range belong to the edi ModRM byte and to the constant address 0x007c6afc (the 96 m literal). All four exits are c2 10 00 (ret 0x10) — four dwords of stack args, confirming the four-argument frame.

arg5 is never read. P4 holds. The ?? Vector3.Zero fabrication was genuinely fabricated, and deleting it is correct. PASS.

A5. The teleport-hook reduction — retail side CONFIRMED

CPhysicsObj::teleport_hook @0x00514ED0 (pseudo-C 283115-283151) is exactly six actions:

  1. MovementManager::CancelMoveTo — guarded movement_manager != 0 @0x00514EDB
  2. PositionManager::UnStick — guarded @0x00514EEC
  3. PositionManager::StopInterpolating — guarded @0x00514EFB
  4. PositionManager::UnConstrain — guarded @0x00514F0A
  5. TargetManager::ClearTarget + NotifyVoyeurOfEvent(Teleported) — guarded @0x00514F19
  6. CPhysicsObj::report_collision_end(this, 1) @0x00514F31 — unguarded

So for a bare arrow/bolt (no MovementManager, no PositionManager, no TargetManager) five of six are retail no-ops through retail's own guards, and the sixth applies. The projectile arm's single _entityObjects.Physics.CollisionReports.LeaveWorld(record) is the right port: it is the same seam RunRemoteTeleportHook's ReportCollisionEnd action reaches (LiveEntityRuntime.ForceEndCollisionReportingRuntimeCollisionReportingState.LeaveWorld), i.e. the 4b-3 R2-validated port of @0x00514620. Ordering is also right — it runs strictly before TryExecuteAcceptedRemotePosition, mirroring @0x005163EF before @0x00516420 — and it runs unconditionally of the placement's outcome, exactly as retail discards SetPosition's result and returns 1 @0x00516438.

Caveat: "the other five are genuinely per-manager-guarded no-ops for a bare missile" is true, but the contract also pinned the adopted-body case, and that half was not delivered — see R2.

A6. Register / issue discipline — PARTIAL

  • AP-141 added in the same working tree as the behaviour. PASS.
  • AP-131 and AP-135 are untouched (the register diff is exactly the section-3 count line plus the AP-141 row). PASS.
  • docs/ISSUES.md is not modified; #276 is not closed. PASS.
  • AP-141's retail description is accurate (A3). PASS.
  • AP-141 is incomplete — see R1 and R2.

A7. Comment corrections — the touched ones verified

The deliberate separate correction at src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:2309-2323 replaces the false claim "MoveOrTeleport installs that exact vector with set_velocity". Every assertion in the replacement is true: MoveOrTeleport never reads the velocity slot (A4); UnpackPositionEvent @0x004542C0 performs no set_velocity; the only set_velocity in the chain is the local player's zero @0x004541B4. The replacement is honest about the consequence ("this call's actual retail justification is therefore NOT yet established"). PASS on truthfulness — but see R8 on the bookkeeping.

The two other comment rewrites (ClassifyRemoteAcceptedPosition's kind-derivation paragraph, OwnsPlacement's widening paragraph) match the code beside them. RuntimeProjectilePhysicsUpdater's tombstone comment accurately describes what was deleted and what remains.


B. Findings

R1 — MAJOR — a Missile-flagged entity with no bound projectile now has its accepted Position silently dropped; retail places it, and AP-141 does not cover this shape

Where: src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:2222-2247 (the isMissilePacket dispatch) together with src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs:1004-1012 (ApplyAcceptedProjectilePosition's ownership gate).

Retail contradicted: CPhysicsObj::MoveOrTeleport @0x00516330 — reached from HandleReceivedPosition @0x00454254 for every non-player CPhysicsObj, with no test of any kind on what client-side machinery the object happens to have bound. Retail always places the object.

What changed. The deleted short-circuit was if (_projectileController?.ApplyAuthoritativePosition(...) == true) return;. That method returned false — i.e. fell through to the generic remote tail, which applied the position — whenever TryGetCurrent failed, i.e. whenever the record had the Missile bit but no bound RuntimeProjectile. The new dispatch decides isMissilePacket purely from route.OperationKind/the Missile bit, and ApplyAcceptedProjectilePosition returns null (write nothing, return) when record.Projectile is null. The packet is now dropped with no writer of any kind.

Why this is reachable state, not a hypothetical. ProjectileController.TryBind (src/AcDream.App/Physics/ProjectileController.cs:133-330) can fail permanently for a Missile-flagged entity — TryGetCollisionSphere rejects any Setup that is not "the supported retail one-sphere collision shape" and logs Missile 0x… Setup 0x… does not have the supported retail one-sphere collision shape. It also has a legitimate not-yet-bound window (initialResidenceActive == false, and the projection-visible retry in OnProjectionVisibilityChanged). In all of those states the entity used to track its server position through the remote path; now it freezes for the rest of its life.

Bookkeeping. AP-141 clause (c) covers "a null-classified or Rejected* accepted Position"; it does not cover "an accepted Position for a Missile-flagged entity whose projectile component is absent or does not agree with the canonical body". The XML doc on ApplyAcceptedProjectilePosition does mention the ownership-mismatch swallow, so this is a real design decision that was made and then not written into the register.

Correct behaviour: either (a) fall through to the generic remote tail when record.Projectile is absent — which is what retail does and what the code did yesterday — or (b) keep the swallow and extend AP-141 with a fourth clause naming it, its trigger (unbindable Setup / pre-bind window), and the observable (a Missile-flagged object stops tracking permanently). Do not leave it undocumented.

Mitigating (do not use as a reason to skip the row): unreachable against ACE for the same reason the rest of the row is — WorldObject_Tick.cs:333-334.


R2 — MAJOR — the adopted-body teleport-hook case was pinned by the contract, not implemented, and not registered; retail runs five actions acdream skips

Where: src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs:1017 — the SetPosition arm runs CollisionReports.LeaveWorld(record) and nothing else.

Retail contradicted: CPhysicsObj::teleport_hook @0x00514ED0 actions 1-5 (@0x00514EDB, @0x00514EEC, @0x00514EFB, @0x00514F0A, @0x00514F19). Each is guarded on its manager existing — not on the object being a non-missile.

The gap. A missile can carry the full remote manager set. TryBind's own adopted-body branch exists for exactly this (ProjectileController.cs:176-181: "Retail owns one CPhysicsObj. If a non-missile incarnation already created its MovementManager … classification adopts that same body"), and the late-classification entry ApplyAuthoritativeState → TryBind is the production path that turns an ordinary remote — which by then has a RemoteMotion with an Interp queue and, if hosted, a PositionManager — into a missile. For such an object retail's guards are satisfied and retail runs CancelMoveTo, UnStick, StopInterpolating, UnConstrain and ClearTarget/NotifyVoyeurOfEvent. acdream now runs none of them.

The contract pinned this explicitly (D-P4): "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)". The in-tree faithful port is sitting unused — RemoteTeleportHook.Execute + RemoteTeleportHookActions, the ordered six-action seam with a currency check between each step. This is the 4b-3 round-2 R2 defect class verbatim: a retail action skipped with the faithful port already in-tree.

Not a regression — the deleted ApplyAuthoritativePosition ran no hook either — but it is a divergence this commit had the obligation to close or record, and it did neither.

Correct behaviour: run the hook through the existing ordered seam when the record carries the managers (record.RemoteMotion/its host), letting the per-manager guards no-op for a bare arrow exactly as retail's do; or state in AP-141 that only action 6 of teleport_hook @0x00514ED0 is ported for a projectile and that actions 1-5 are skipped even when the managers exist.

Note on the stale-queue half specifically: while the Missile bit is set the remote interpolation is inert (LiveEntityAnimationScheduler.cs:336 gates on projectileHandlesMovement), so the skipped StopInterpolating is latent rather than immediately observable — it becomes live again the moment ACE clears Missile on impact. ClearTarget/NotifyVoyeurOfEvent and CancelMoveTo have no such shield.


R3 — MINOR — invariant 4 ("prediction invalidated before every body write on this route") and invariant 2 (presentation) hold on the direct arm only; the retained-retry arm satisfies neither

Where: src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs:1150-1213 (Advance()), reached from src/AcDream.App/Net/GraphicalSessionEventRoute.cs:121.

A Contention outcome parks the packet in _pending and re-submits it one or more host cadence pumps later. That retry path calls SubmitAndResolve (a canonical body commit) and, on a destination that left the window, StoreAcceptedDestinationPose (a raw body write) — without projectile.InvalidatePrediction() and without SyncProjectilePresentation. The version bumped at packet time does not protect a quantum begun after that bump and straddling the retry.

Presentation is the same shape: the App's SyncPresentationFromResolvedBody is called only from OnPosition, and RuntimePlacementPresentationSink.TryPublishPlace (src/AcDream.App/World/RuntimePlacementPresentationSink.cs:210-241) does not write entity.Position/Rotation — it snapshots whatever the entity already holds. So a retried projectile commit advances the canonical body with no presentation projection at all until the next quantum re-projects.

Both are bounded (the retry pump runs from the network retry lease, not from inside the scheduler's TryBeginQuantum/CompleteQuantum straddle; the presentation lag self-heals on the next tick), which is why this is MINOR rather than MAJOR. But the invariants are stated absolutely, and this is the "an invariant satisfied on one arm only" class both 4b-3 rounds named.


R4 — MINOR — SyncProjectilePresentation silently drops the spatial+hidden clock consumption while its doc claims a faithful reduction

Where: src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs:975-985 (doc) and :1028-1042 (the else if (spatial) branch).

The deleted tail was:

else if (spatial)
{
    body.InWorld = true;
    body.LastUpdateTime = currentTime;      // <-- gone
    _physics.Engine.ShadowObjects.Suspend(localId);
}

The replacement omits body.LastUpdateTime. The doc immediately above claims the method is that tail "reduced … to this controller's own two-argument seam — _clock supplies the same clock source that method took as an explicit currentTime parameter", which is true of the visible branch and false of this one. The intent of the deleted write is documented elsewhere in the same subsystem (ProjectileController.TryBind: "consume the hidden clock so UnHide cannot replay a time backlog").

Consequence is small — the projectile stepper takes its quantum from RetailObjectQuantumBatch, not from body.LastUpdateTime, and ProjectileController.Tick:601-604 re-stamps LastUpdateTime on the !InWorld re-entry edge — but this is an unclaimed behaviour deletion inside a change whose comment asserts equivalence. (Clock domain is fine: UpdateFrameOrchestrator.CurrentScriptTime => _runtime.SimulationTimeSeconds, so _clock.SimulationTimeSeconds is the same base the App used to pass.)


R5 — MINOR — the App ignores the seam's returned status, so the "presentation advances on committed/stored outcomes only" gate exists on the Runtime side only

Where: src/AcDream.App/Physics/LiveEntityNetworkUpdateController.cs:2237-2245.

Runtime deliberately withholds SyncProjectilePresentation for Deferred and RejectedByPlacement (with the comment "Deferred/RejectedByPlacement leave the body at its prior (already-synced) pose"). The App then calls _projectileController.SyncPresentationFromResolvedBody(...) unconditionally, discarding the returned RuntimeRemotePlacementExecutionStatus? entirely — including on Deferred, where ParkDeferred has already snapped the body to the parked result and withdrawn the entity. The App write moves the render entity to a pose Runtime declined to publish; if the park is later cancelled and restored (WithdrawalRestored), the entity re-shows there. That is AP-136's family.

On the no-op and swallow dispositions the write is inert (same value), so this is MINOR, but the two layers disagree about the same invariant.


R6 — MINOR — on a stored (non-committed) outcome the render cell is left behind while the render position moves; retail's store_position writes objcell_id too

Where: src/AcDream.App/Physics/ProjectileController.cs:512-517 (entity.ParentCellId = record.FullCellId) with RuntimeRemotePlacementDriveController.StoreAcceptedDestinationPose:1104-1136.

StoreAcceptedDestinationPose writes body.Position and body.Orientation only — not the cell, and D-P2 correctly forbids a RebucketLiveEntity on this arm. So after Refused/Contention/RejectedPreparation the render entity gets the new world position with the old ParentCellId. The deleted path could not produce this pairing: it always did body.SnapToCell(fullCellId, …)

  • CommitProjectileCell.

Retail's store_position(this, arg2) @0x00515CE2 writes the object's whole Position including objcell_id, and the branch then calls CObjectMaint::GotoLostCell @0x00515CF2 — i.e. retail moves the cell and hides the object pending load. This is largely AP-138's existing residual (shared with the remote far arm), but it is newly reachable for projectiles and worth naming when AP-141 is amended.


R7 — MINOR — the contract's App-layer dual-kind matrix (§7 items 8-10) is absent; the D-P6 dispatch has no test at all

Added coverage is Runtime-only: RuntimeProjectilePositionKindTests (3 facts, kind derivation + mid-life flip) and 11 new RuntimeRemotePlacementDriveControllerTests facts. These are good tests — positive assertions, real behaviour, no source-text pins, and the split-quantum abort (T3) and no-velocity (D-P5) invariants are both genuinely exercised.

But there is no test anywhere under tests/AcDream.App.Tests that drives OnPosition. The contract made the dual-kind [Theory] "what makes a future silent mis-tag fail a test", and item 10 required a sabotage check ("hardcode Remote and confirm the dual-kind theories fail"). With no App-level test, hardcoding isMissilePacket = false at LiveEntityNetworkUpdateController.cs:2229 would leave the suite green — the exact failure mode the discipline was written to catch. The swallow ordering, the presentation-ack ordering, and the "no RemoteMotion is ever created for a missile packet" claim are all untested.


R8 — MINOR — the corrected 4a comment now records an unjustified production behaviour with nothing tracking it

LiveEntityNetworkUpdateController.cs:2309-2323 now states that the retail justification for the remote arm's TryCommitAuthoritativeVelocity "is therefore NOT yet established and needs its own audit". That is the honest call, and the contract (§10) said to report rather than edit. But an acknowledged-unjustified velocity commit on a live production path now exists with no AP row and no docs/ISSUES.md entry — only a code comment. One line in ISSUES.md (or an AP row) would keep it findable.


C. Anything wrong in the CONTRACT itself

Nothing factually wrong. Three notes:

  1. §4's teleport_hook address is cited two ways and both are right — the contract's §4 row 2 cites @0x005163EF (the call site inside MoveOrTeleport) and D-P4 cites @0x00514ED0 (the function). Confirmed consistent; no action.
  2. D-P4's adopted-body sentence is the only pinned obligation the diff did not discharge (R2). The contract's wording ("the implementer wires this without per-packet closures") is an instruction, not a claim, so the contract is not wrong — the implementation is incomplete against it.
  3. D-P2 step 1's "Anything else: return NotApplicable-equivalent, write nothing" is the source of R1. The contract pinned it without noticing that it removes the deleted path's fall-through and therefore introduces a new divergence needing its own register clause. Worth a sentence when AP-141 is amended, so the next reader does not read the contract as having cleared it.

Everything else I checked in the contract holds: the retail chain (§4, all seven rows), the ACE unreachability (WorldObject_Tick.cs:333-334 inside the PhysicsState.Missile branch at :265), the classifier's kind-blindness past LocalPlayer (RuntimeAuthoritativePositionRouteClassifier.cs:349, :535-538, :564-575), and the three ownership fences (OwnsPlacement widened; OwnsFarSnap/OwnsTeleportPlacement untouched, both arms still ArgumentNullException.ThrowIfNull(remote) + throwing route guards).

I also spot-checked proof obligation P3 and found no capture hazard: both RemoteAuthoritative stamps in RuntimeSetPositionState (:3833/:3866 window-departure park, :5338/:5350 legacy-direct withdrawal) construct fresh operations and the park loop skips records already in _operations, so neither can relabel a live ProjectileAuthoritative operation; route.OperationKind is threaded intact through TryBeginExclusiveAuthoredPlacement and TryPrepareAndSubmitAuthoredPlacement, so the command.Kind != operation.Kind consistency check at :2963 is satisfied; and the direct-command gate at :5445 admits local kinds only, which this route never uses. MoverPhysicsState = record.FinalPhysicsState is carried into the placement request (:2007, :3007, :3810, :5496), so the Missile bit still reaches the engine's missile_ignore handling.


D. Summary table

# Sev Where Retail address contradicted
R1 MAJOR LiveEntityNetworkUpdateController.cs:2222-2247 + RuntimeRemotePlacementDriveController.cs:1004-1012 MoveOrTeleport @0x00516330 via @0x00454254 — placement is unconditional on client machinery
R2 MAJOR RuntimeRemotePlacementDriveController.cs:1017 teleport_hook @0x00514ED0 actions 1-5 (@0x00514EDB/@0x00514EEC/@0x00514EFB/@0x00514F0A/@0x00514F19)
R3 MINOR RuntimeRemotePlacementDriveController.cs:1150-1213 — (invariant 4/2, one arm only)
R4 MINOR RuntimeRemotePlacementDriveController.cs:975-985, 1028-1042 — (unclaimed behaviour deletion + stale doc)
R5 MINOR LiveEntityNetworkUpdateController.cs:2237-2245 — (App/Runtime disagree on invariant 2)
R6 MINOR ProjectileController.cs:512-517, RuntimeRemotePlacementDriveController.cs:1104-1136 store_position @0x00515CE2 / GotoLostCell @0x00515CF2 (writes cell too)
R7 MINOR tests/AcDream.App.Tests/** (absent) — (contract §7 items 8-10 undelivered)
R8 MINOR LiveEntityNetworkUpdateController.cs:2309-2323 — (bookkeeping)