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>
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_hook → SetFlags(0x1012) → SetPosition → return 1 |
@0x005163EF / @0x00516414 / @0x00516420 / @0x00516438 | SetPosition |
faithful |
near: InterpolateTo(this, arg2, IsMovingTo(this)), return 1 |
@0x005163AF, @0x005163BE | Interpolate → no-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)→ @0x00454272ConstrainTo(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 thenPositionManager::InterpolateTo@0x00510508), so the near branch would likewise build machinery for a manager-less missile. PositionManager::UseTimeis ticked fromUpdateObjectInternal@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:
MovementManager::CancelMoveTo— guardedmovement_manager != 0@0x00514EDBPositionManager::UnStick— guarded @0x00514EECPositionManager::StopInterpolating— guarded @0x00514EFBPositionManager::UnConstrain— guarded @0x00514F0ATargetManager::ClearTarget+NotifyVoyeurOfEvent(Teleported)— guarded @0x00514F19CPhysicsObj::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.ForceEndCollisionReporting →
RuntimeCollisionReportingState.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.mdis 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:
- §4's
teleport_hookaddress is cited two ways and both are right — the contract's §4 row 2 cites @0x005163EF (the call site insideMoveOrTeleport) and D-P4 cites @0x00514ED0 (the function). Confirmed consistent; no action. - 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.
- 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) |