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>
12 KiB
C4 route 5 — architecture / adversarial DELTA review, round 3 (2026-08-04)
Verdict: FAIL — one finding (C1), coverage-only, no identified defect.
Every behavioural finding from rounds 1 and 2 is now closed and independently
verified. The single remaining item is that the Advance() retry arm's
projectile branch — added in round 2 (R3) and semantically modified in round 3
(B5, a prediction-invalidation reordering) — is executed by no test at any
layer. I traced the reordering and believe it is safe; but "safe by reading"
is the standard this project rejects, and it is the standard I applied to the
implementer twice (A4, A5). The templates to close it are already in the same
file. The bar for round 4 is one test.
Gates run for this review: dotnet build AcDream.slnx -c Release --no-incremental
— 0 errors, 21 warnings, all pre-existing and none in any route-5 file (they
are in AcDream.App.Tests/Composition, AcDream.Core.Tests nullability and
xUnit-analyzer nits; my earlier "0 warnings" readings were incremental builds
that skipped those projects — no regression). Focused Runtime suites 59/59;
focused App suites including UpdateFrameOrchestratorTests 94/94.
1. Finding
C1 — the Advance() retry arm's projectile branch has never been executed by a test, and round 3 changed its prediction-invalidation semantics
Severity: MAJOR (coverage). No defect identified — the code reads correct.
RuntimeRemotePlacementDriveController.Advance:1221-1305 contains the R3
projectile branch (kind/identity re-validation, pendingWasInWorld capture,
prediction invalidation, SyncProjectilePresentation) and the B5 reordering.
All six drive.Advance() call sites in
RuntimeRemotePlacementDriveControllerTests.cs (:533, :549, :592,
:1015, :1021, :1755) belong to remote-kind tests. No test parks a
ProjectileAuthoritative retry and pumps Advance().
What is therefore unexercised:
- prediction invalidation on a retried projectile placement (trap T3 — a straddling quantum clobbering a committed placement is exactly what this guards);
- the B5 semantic change: invalidation skipped when
SubmitAndResolvereturnsContention; SyncProjectilePresentationon the retry arm (invariant 2 on a second call site);- the stale-route guard (
pending.Route.OperationKind+ body/component re-validation) that exists precisely because a retry can outlive its binding.
The R3 comment states the intent plainly — "Neither invariant this route pins … may hold on the direct arm only" — and both invariants are, in fact, asserted only on the direct arm.
Reachability: _pending receives a projectile entry whenever
SubmitAndResolve returns a retryable preparation status
(RetrySetupUnavailable/RetryWorldFrameUnavailable) for a
ProjectileAuthoritative route — an ordinary streaming-edge condition — and
Advance() is the host cadence pump. Standing caveat: like the whole route,
ACE-unreachable in play.
Fix direction (cheap — both templates are in the same file): rebuild
FarSnap_RetryablePreparation_StoresThePoseAndStillRetainsTheRetry (:985)
and Advance_DestinationLeavesTheWindow_StoresTheNewestDestinationPose
(:1707) against CreateProjectileRecord + a ProjectileAuthoritative route
— the same borrowing the projectile ledger test already did from
Teleport_LedgerConverges_…. Assert, on the retry: the pose advanced, the
prediction version moved on the storing outcomes and did not move on a
re-parked Contention, and the shadow row followed the body.
2. The B5 reordering — safety verified
Asked to scrutinise this specifically. It is safe, and for a reason stronger than the comment's "single-threaded and synchronous".
The invalidation exists solely so a straddling quantum aborts at Complete.
PredictionAuthorityVersion has exactly four consumers, enumerated across
src/: RuntimeProjectilePhysicsUpdater.TryBegin/Complete/IsIdentityCurrent,
RuntimePhysicsState.CommitProjectileCell's IsExactOwner, and
ProjectileController.IsCurrentQuantumIdentity (:837). The only callers of
CompleteQuantum are ProjectileController.AdvanceQuantum (:760) and
LiveEntityAnimationScheduler (:419) — both frame-loop driven. Nothing in
the placement pipeline's synchronous publish chain (placement projection sink,
RebucketLiveEntity's visibility callbacks, collision-report observers) can
reach either. Notably, the one projection callback that does re-enter the
projectile controller — OnProjectionVisibilityChanged → TryBind — returns
early on a retained runtime and never touches a quantum. So no Complete can
interleave between the write inside SubmitAndResolve and the invalidate on
the next statement.
The skip on Contention is also correct: Contention from SubmitAndResolve
means preparation returned a retryable status, so PhysicsEngine.SetPosition
never ran and nothing wrote the body — verified by walking
TryPrepareAndSubmitAuthoredPlacement's non-Prepared path, which touches
only operation.* fields and reads (body.InContact/OnWalkable), never the
body's pose.
Two ordering details I checked and found sound: the Refused branch still
invalidates before StoreAcceptedDestinationPose (the one body write on
that arm); and CancelToken's synchronous cancellation receipt — which the
class doc warns can delete or replace the incarnation — is harmless here
because pendingProjectile is a captured strong reference (invalidating a
displaced component is inert, and a displaced component's quantum already
fails IsIdentityCurrent's ReferenceEquals(record.Projectile, projectile)),
while both StoreAcceptedDestinationPose and SyncProjectilePresentation
re-validate currency at entry.
Honest note on the invariant's status: contract §5 item 4 says invalidation is "before the write". On this arm it is now after, resting on a reachability argument rather than structural ordering. The argument holds today; it is recorded here so a future change that lets a projection callback drive a quantum knows it broke something. The comment states the reasoning openly, which is the right handling.
3. The indoor staging in the B2 regression test — legitimate, and it corrects my round-2 finding
Judgment: the staging is legitimate, the test genuinely pins production behaviour, and the diagnosis behind it is correct.
I verified the mechanism at source. PhysicsBody.Position's setter
(PhysicsBody.cs:153-162) calls SyncCellPositionDelta(delta) on every write,
including StoreAcceptedDestinationPose's. That method (:287-308) has two
branches:
- outdoor (low word
1..0x40):LandDefs.AdjustToOutsidere-derives the cell index from the shifted local origin and bumps the landblock on a 192 m crossing — so after a store to a cross-landblock destination the body's own cell id self-corrects to the destination cell; - indoor (low word outside that range — an EnvCell id): the local origin shifts but the cell id is kept verbatim, so it stays pinned at the source EnvCell.
So the implementer's finding is right, and it corrects my round-2 B2: the
defect I reported was real but its blast radius was narrower than I stated. My
stated scenario ("a missile crosses a landblock boundary at the streaming
edge") is an outdoor case, which self-heals through AdjustToOutside; the
genuinely divergent case is a body in an EnvCell — a bolt fired inside a
dungeon. That correction is recorded here rather than smoothed over.
The staging is not a fixture artefact:
IndoorSourceCell = SourceLandblock | 0x0100 is a well-formed EnvCell id (the
first EnvCell index), and SnapToCell seeds indoor claims verbatim by design
(PhysicsBody.cs:190-205, "Indoor EnvCell claims (low word >= 0x100) … seeded
verbatim"). The scenario — indoor body, outdoor wire destination, service
window refuses — is production-reachable.
One asymmetry worth naming, which does not affect the pin: the test stages the
indoor cell on the body while leaving record.FullCellId at the outdoor
SourceCell, a pairing production would not produce. It is inert here because
the merge overwrites record.FullCellId with the wire cell before the ack
runs, so the pre-packet value is never read. The assertion depends only on the
two expressions diverging, which they genuinely do.
Discrimination confirmed by construction: the test asserts
body.CellPosition.ObjCellId == IndoorSourceCell (the divergence actually
occurred) and then entity.ParentCellId == DestinationCell. Restoring round
1's body.CellPosition.ObjCellId read fails the second assertion.
4. Round-2 findings — disposition
| # | status | verification |
|---|---|---|
B1 (adopted-body far snap leaves Interp armed; AP-141 asserts the opposite) |
CLOSED, well | The clear landed in the Runtime seam at ApplyAcceptedProjectilePosition's SetPositionSimple case, before the placement — matching retail's @0x005163CB-before-@0x005163D9 ordering — guarded on route.StopInterpolating && record.RemoteMotion is RemoteMotion, the exact shape ApplyAcceptedRemoteFarSnap ports. Correctly scoped to one action, not the full hook. MissileAdoptedBody_FarCommit_ClearsInterpQueueButLeavesConstraintArmed is discriminating in both directions: it pins the queue clear and pins that UnConstrain did not run, proving the fix did not over-apply the teleport hook to the far branch. AP-141 is now narrowed to "faithful ONLY for a BARE missile" and additionally names the surviving ConstrainTo re-anchor divergence for the adopted case — a more accurate row than before the finding. |
B2 (ParentCellId regression) |
CLOSED, well | Reverted to record.FullCellId; the A1 gate kept. The doc now carries three independent justifications, of which two I verified directly (the sibling remote arm pairs wire pose with wire cell; Runtime's own SyncProjectilePresentation publishes the shadow at record.FullCellId in the same call, so the body-cell read would have disagreed with the shadow for one body in one packet) and the third is retail's store_position @0x00515CE2 writing objcell_id. Regression-tested — see §3. |
| B3 (store path unasserted at both layers) | CLOSED at both layers | Runtime: Refused_StillAdvancesPoseNoParkPredictionInvalidated now asserts shadowEntry.Position == body.Position; on the Refused path nothing but SyncProjectilePresentation publishes the shadow, so it discriminates. App: MissileFarRefused_StorePathStillMovesEntityToDestinationParentCellIdAgreesWithWireCell asserts entity position and ParentCellId; deleting the ack leaves the entity at its spawn pose. What remains untested from B3's neighbourhood is only the A1 gate's own two statuses (Deferred/RejectedByPlacement), which round 2 already accepted as an unconstructible carve-out, and the retry arm (C1). |
| B4 (per-packet closure) | CLOSED, correctly | IsCurrentProjectilePositionOwner is now a third cached delegate inside RemoteArmCallbacks, bound to IsCurrentProjectileArmPositionOwner, reading _projectileArmPosition* scratch fields stamped immediately before use — byte-for-byte the same shape as the existing _remoteArmPosition* / IsCurrentRemoteArmPositionOwner pair (:92-93, :1467-1468, :1502-1506). Same reentrancy exposure as the incumbent #315 pattern, therefore parity rather than a new hazard. The UpdateFrameOrchestratorTests zero-Delegate-field guard holds — it ran green inside the 94/94 App suite, and the cache stayed inside the named type rather than becoming bare fields. |
| B5 (retry arm invalidating with no write) | CLOSED — see §2 | Correct, and the safety of the reordering verified independently. Untested (C1). |
| Round-1 MINOR (c) (4a velocity comment with no retail basis) | FILED as #317 | docs/ISSUES.md — accurate description, correct scoping rationale, and an acceptance criterion that names the actual work (audit the whole accepted-Position velocity chain, then cite or remove with a register row). Better handling than a silent comment fix. |
No round-1 or round-2 finding regressed, and nothing in this round's diff introduced a new behavioural defect that I could identify.
5. What would make this PASS
One test: a ProjectileAuthoritative retained retry driven through
drive.Advance(), asserting the pose advanced, the prediction version moved on
a storing outcome and did not move on a re-parked Contention, and the
shadow row followed the body. Two existing remote tests in the same file are
the templates.
Nothing else is outstanding.