acdream/docs/research/2026-08-04-c4-route-5-retail-review-round2.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

418 lines
23 KiB
Markdown

# C4 route 5 — projectile authoritative placement: retail-conformance review, ROUND 2 (delta)
**Reviewer lens:** retail fidelity only. Delta against
[`2026-08-04-c4-route-5-retail-review.md`](2026-08-04-c4-route-5-retail-review.md)
(round 1, FAIL) and cross-read against
[`2026-08-04-c4-route-5-architecture-review.md`](2026-08-04-c4-route-5-architecture-review.md)
(A1-A11).
**Subject:** the uncommitted working tree, HEAD `30d3d114`, branch
`claude/acdream-physics-divergence-5aa784` — 1,626 insertions / 398 deletions
across 10 files plus two untracked test files.
---
## VERDICT: **FAIL**
A narrow FAIL. Eleven of the twelve round-1/architecture findings are properly
closed, several of them better than the fix direction I suggested. The FAIL
rests on two MAJORs:
- **B1** — the fix to my own round-1 R6 went the wrong way and introduced a
position/cell mismatch on the stored partition. **R6 as I wrote it was
factually wrong, and I own this**: I claimed the render cell was "left
behind" while the position moved; it was not — `record.FullCellId` is
already the wire cell at ack time, so the original code was self-consistent.
Switching to the body's own cell made it inconsistent. This is a one-token
revert.
- **B2** — the R2/A3 adopted-body fix closed the **teleport** branch and left
the **far** branch. Retail's far branch @0x005163C1-@0x005163CB runs
`StopInterpolating` whenever `position_manager != 0`, and re-anchors the
leash @0x00454272 on any nonzero return. For an adopted missile both
managers exist, both actions are live in retail, and acdream now performs
neither. I confirmed the harm is real, not theoretical.
Both are the same shape as the findings they descend from: an invariant
satisfied on one arm only.
---
## A. Round-1 / architecture findings — delta status
| ID | round-1 / arch | status | notes |
|---|---|---|---|
| R1 / A2 | unbound missile dropped | **CLOSED — better than my fix direction** | see A1 below |
| R2 / A3 | adopted-body hook unwired | **PARTIAL** | teleport branch closed; far branch open → **B2** |
| R3 | retry arm skips invalidate/sync | **CLOSED** | see A3 |
| R4 / A7 | hidden-branch `LastUpdateTime` dropped | **CLOSED** | restored at `SyncProjectilePresentation`'s `else if (spatial)` with the correct rationale quoted from `TryBind` |
| R5 / A1 | App ignores seam status | **CLOSED** | see A4 |
| R6 | stored-outcome cell | **REGRESSED — my finding was wrong** | → **B1** |
| R7 / A5 | no App-level coverage | **CLOSED** | 7 new `OnPosition` tests incl. the unbound fall-through and the adopted-body hook |
| R8 | unjustified-velocity comment untracked | **OPEN, deliberately** | judgment in D below — partially acceptable |
| A4 | `SyncProjectilePresentation` untested | **CLOSED** | real `ShadowObjects` entry / `TotalRegistered` / `Active`-flag assertions across four new tests |
| A6 | `wasInWorld` read after placement | **CLOSED** | captured pre-dispatch and threaded as a parameter; `…ReenteringWorldReactivatesBody` pins it |
| A8 | silent shadow skip | **CLOSED** | escalates via `ThrowIfWorldFrameUnreachable`, matching `StoreAcceptedDestinationPose`'s #284 policy |
| A9 | null-route fallback unfenced from the player | **CLOSED** | `update.Guid != _playerServerGuid &&` added |
| A10 | `OwnsFarSnap` doc false in the kind dimension | **CLOSED** | corrected paragraph added |
| A11 | cutover ledger stale | not checked here (outside D-P7's letter; architecture reviewer's call) | |
---
## B. Verification of the four items you asked me to judge
### A1 — the conjunctive kind predicate: is it retail-CORRECT, not merely regression-free?
**Yes. PASS, and the reasoning is stronger than "it restores the old
fall-through".**
`RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition` now derives:
```
Missile bit && canonical.Projectile is bound && ReferenceEquals(canonical.PhysicsBody, projectile.Body)
? Projectile : Remote
```
**Why this is retail-correct rather than an implementation detail smuggled
into a classification:**
1. **`RuntimePositionEntityKind` is not a retail concept and has no retail
effect.** I re-verified `RuntimeAuthoritativePositionRouteClassifier`:
the only kind branch in `ClassifyAcceptedPosition` is
`LocalPlayer` (`:349`); `ValidEntityKind` (`:535-538`) admits all three;
the sole downstream difference is `OperationKind` (`:564-575`). Projectile
and Remote produce byte-identical disposition, `SetPositionFlags`,
`StopInterpolating`, `TeleportHookPhase` and `ConstrainPhase`. The kind is
therefore an **acdream ownership label selecting which arm executes**, not
a reproduction of any retail decision. Retail's `MoveOrTeleport`
@0x00516330 has no state test at all (round-1 §A1, byte-confirmed), so
there is no retail predicate for this code to be unfaithful to.
2. **The check lives in the right place.** It is in the *caller* that maps
acdream state → kind, not inside `ClassifyAcceptedPosition`, which remains
a pure port of retail's decision tree. Nothing acdream-specific entered the
retail-faithful classifier.
3. **Mis-routing a genuinely-bound missile is harmless in the retail
direction.** The only way a live missile classifies `Remote` is if
`record.PhysicsBody` was replaced out from under a surviving
`RuntimeProjectile` — already a broken state, and one that
`ApplyAcceptedProjectilePosition`'s identical guard would refuse anyway
(round 1: it dropped the packet). Taking the Remote arm instead **places
the canonical body**, which is what retail does; it also arms the leash and
runs the full hook, i.e. *more* of retail's behaviour, not less. There is no
direction in which the conjunctive predicate produces less retail-faithful
output than the arm it diverts from.
4. **The reverse mis-route cannot happen.** A non-missile can never satisfy
clause 1, so no ordinary remote is diverted into the projectile arm.
The App's null-classification fallback
(`LiveEntityNetworkUpdateController.cs:2132-2140`) now applies the **same three
conjuncts plus the A9 player fence**, so the two discriminators still cannot
disagree. Verified by reading both.
The doc comment's claim — *"Retail's `MoveOrTeleport` places EVERY non-player
object unconditionally — it has no concept of 'client-side machinery not yet
bound'"* — is true and matches my byte-level read of the function.
### A2 — is "has `RemoteMotion`" the right proxy for "has managers"?
**For actions 1-4, yes, provably. For action 5, the proxy is imperfect but
skipping is still the retail-correct outcome.** PASS with a note.
Mapping retail's six `teleport_hook` @0x00514ED0 actions onto acdream's
owners:
| retail action | guard | acdream owner | reachable without `RemoteMotion`? |
|---|---|---|---|
| `MovementManager::CancelMoveTo` @0x00514EDD | `movement_manager != 0` | `RemoteMotion.Movement` (a field on `RemoteMotion`, `RemoteMotion.cs:36`) | **no** |
| `PositionManager::UnStick` @0x00514EEE | `position_manager != 0` | `RemoteMotion.Host.PositionManager` (`Host` is gated on `_fullPhysicsHostBound`, `:87`) | **no** |
| `PositionManager::StopInterpolating` @0x00514EFD | same | `RemoteMotion.Interp` (`:233`) | **no** |
| `PositionManager::UnConstrain` @0x00514F0C | same | `RemoteMotion.Host.PositionManager` | **no** |
| `TargetManager::ClearTarget`/`NotifyVoyeurOfEvent` @0x00514F1B | `target_manager != 0` | `EntityPhysicsHost.TargetManager` | **yes** — see below |
| `report_collision_end` @0x00514F31 | *unguarded* | `RuntimeCollisionReportingState.LeaveWorld` | run by the Runtime seam regardless ✓ |
The one leak: `LiveEntityMotionRuntimeController.ResolvePhysicsHost:246-250`
installs a **minimal** `EntityPhysicsHost` for any record the target/moveto
resolver touches, and that host carries a `TargetManager`. So a bare missile
*can* hold a `TargetManager` while `record.RemoteMotion` is null, and the
reduction skips its `ClearTarget`/`NotifyVoyeurOfEvent`.
**This does not make skipping wrong.** Retail's missile has **no**
`TargetManager``MakeTargetManager` is lazy and nothing in a ballistic
object's life creates one — so retail's guard @0x00514F19 no-ops. acdream's
eager minimal-host creation is a pre-existing structural difference from
retail, not something route 5 introduced, and running the action would be the
divergence, not skipping it. Recorded so the next round does not re-open it.
### A3 — the retry arm (my R3)
**CLOSED and correct.** `Advance()` now derives the projectile/body pair from
`pending.Route.OperationKind` under the same three conjuncts, captures
`pendingWasInWorld` **before** the resubmit (matching A6's ordering fix),
calls `InvalidatePrediction()` before both the store fallback and the
resubmit, and gates `SyncProjectilePresentation` on the same
non-`Deferred`/non-`RejectedByPlacement` partition. The invariant now holds on
both arms.
One residual, **pre-existing and shared with the remote arm, not a finding**:
the retry path never calls `StoreAcceptedDestinationPose` after a
`SubmitAndResolve` that returns `Contention`/`RejectedPreparation`, so
invariant 1's pose advance is skipped on a re-parked retry. That was true
before this slice (`_ = SubmitAndResolve(...)`) and is the remote arm's
behaviour too. Recorded, not filed.
### A4 — the App presentation gate, and whether "stored outcomes write the cell too" is now true
**The gate is CLOSED and correct. The cell question is NOT — and it is worse
than before. See B1.**
The gate itself: App now calls `SyncPresentationFromResolvedBody` only when
the status is non-null and neither `Deferred` nor `RejectedByPlacement`,
mirroring Runtime's own partition exactly. I walked every outcome:
| outcome | body state | ack? | retail analogue |
|---|---|---|---|
| `Committed` | at resolved destination, cell committed | yes | `SetPosition` success |
| `Refused`/`Contention`/`RejectedPreparation`/`NotApplicable` | position stored at destination, cell NOT written | yes | `store_position` @0x00515CE2 |
| `Deferred` | snapped to parked result, withdrawn | no | park (acdream-only, AP-136/138) |
| `RejectedByPlacement` | untouched | no | @0x00515CB2 / @0x00515CD5 — retail's non-storing failures also leave the object where it was ✓ |
| `null` (no-op / swallow) | untouched | no | `return 0` @0x0051636D, or acdream-only |
Correct on every row.
---
## C. New MAJOR findings
### B1 — MAJOR — the R6 "fix" pairs the destination position with the pre-packet cell on every stored outcome; the correct source was the one it replaced
**Where:** `src/AcDream.App/Physics/ProjectileController.cs`,
`SyncPresentationFromResolvedBody``entity.ParentCellId =
runtime.Body.CellPosition.ObjCellId` (was `record.FullCellId`).
**Retail contradicted:** `CPhysicsObj::store_position` @0x00515CE2, reached
from `SetPositionInternal`'s no-resolvable-cell branch @0x00515C1D. It writes
the object's **whole** `Position``objcell_id` together with the frame — so
after a retail store the object's cell and position are the *same* cell,
the destination. It is never left describing a position in cell B while
claiming membership of cell A.
**My round-1 R6 was wrong and I induced this.** I wrote that on a stored
outcome "the render cell is left behind while the render position moves". It
is not: `RuntimeEntityRecord.RefreshDerivedState``SetFullCell`
(`RuntimeEntityRecord.cs:232-237`) stamps `FullCellId = position.LandblockId`
at merge time, *before* classification, and
`StoreAcceptedDestinationPose` composes `body.Position` from
`accepted.PositionX + worldOffsetX` where the offset comes from
`accepted.LandblockId`**the same cell**. So the original
`entity.ParentCellId = record.FullCellId` paired a wire-frame position with
the wire cell: self-consistent, and retail's store semantics. The architecture
reviewer had this right in its "Verified — no finding" section; I did not.
**What the change now produces on a stored outcome:**
- `entity.Position` = destination, expressed in cell **B**'s world frame
- `entity.ParentCellId` = `body.CellPosition.ObjCellId` = cell **A**
(`StoreAcceptedDestinationPose` never writes the cell — the AP-138 residual)
That is precisely the defect shape the architecture review's A1 named — a
render entity parented into a cell it is not geometrically inside, culled or
drawn through walls when A is an indoor EnvCell — relocated from the no-op
partition (now fixed) to the stored partition (now broken).
**Three independent cross-checks all say `record.FullCellId`:**
1. **The sibling remote arm.** `TryApplyGenericRemoteRenderPose`
(`LiveEntityNetworkUpdateController.cs:1010-1025`) writes
`entity.ParentCellId = landblockId` — the **wire** cell — paired with the
wire world position. Parity demands the projectile arm do the same.
2. **Runtime's own shadow publish, forty lines away.**
`SyncProjectilePresentation` publishes
`ShadowObjects.UpdatePosition(..., record.FullCellId, seedCellId: record.FullCellId)`
with `body.Position`. After this change the shadow says cell B and the
render entity says cell A **for the same body in the same call**.
3. **Retail**, as above.
**The doc comment defending the change is self-refuting.** It states *"Retail's
own `store_position` @0x00515CE2 writes the object's whole `Position`
including `objcell_id`; reading the body's own cell here is the client-side
analogue"* — the premise is right and the conclusion inverts it. Retail's
`objcell_id` after a store is the **destination** (`record.FullCellId`), not
the stale one.
**Untested.** Both new App commit tests
(`MissileTeleportCommit_…ParentCellIdAgreesWithBody`,
`MissileFarCommit_…`) assert on **committed** outcomes, where
`body.CellPosition.ObjCellId == record.FullCellId == DestinationCell` and the
two sources coincide. No test drives a stored outcome through the App ack, so
the suite is green either way — a green suite is not evidence.
**Correct behaviour:** revert to `entity.ParentCellId = record.FullCellId`,
and add an App-layer stored-outcome test (`Refused`) asserting the render
position and `ParentCellId` are both the **destination** cell's.
---
### B2 — MAJOR — the adopted-body fix closed the teleport branch only; retail's FAR branch also runs `StopInterpolating`, and re-anchors the leash, whenever the manager exists
**Where:** `LiveEntityNetworkUpdateController.cs` — the hook is gated on
`route.Disposition is …SetPosition && acceptedPositionCanonical.RemoteMotion is RemoteMotion`;
and `RuntimeRemotePlacementDriveController.ApplyAcceptedProjectilePosition`'s
`case …SetPositionSimple:` does nothing but invalidate and place.
**Retail contradicted, two sites:**
```
005163c1 position_manager = this_1->position_manager;
005163c9 if (position_manager != 0)
005163cb PositionManager::StopInterpolating(position_manager);
005163d9 CPhysicsObj::SetPositionSimple(this_1, arg2, 1);
```
and, for **every** nonzero return including this one,
`ConstrainTo(arg2, &arg2->m_position, …)` @0x00454272 — which re-anchors the
leash at the object's **just-updated** position.
**Why the contract's justification does not cover the adopted case.** D-P2
pins the far branch's interp skip as *"retail-faithful by consequence"*
because *"a never-interpolated missile has none"*. That premise is exactly
what the adopted-body case violates: `TryBind`'s shared-body branch
(`ProjectileController.cs:176-181`) exists for an object that was a live
remote first, so it carries a populated `RemoteMotion.Interp` and, if hosted,
a `PositionManager` whose leash route 4a
(`RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation`) already
armed. Retail's `position_manager != 0` guard is **satisfied** there.
**The harm is real, not structural.** I traced it:
- `RuntimePhysicsState.cs:697-708` puts the same key in **both**
`_spatialRemotes` and `_spatialProjectiles` when the record has both
components.
- `RuntimeRemotePhysicsUpdater` consumes `rm.Interp` for every
`_spatialRemotes` entry (`:288`, `:331`, `:941`).
- `ProjectileController.Tick`'s per-quantum tail runs
`RetailObjectManagerTail.Run(remote.Host?.TargetManager, remote.Movement,
null, remote.Host?.PositionManager)` for exactly this record shape.
So after a far snap, a stale waypoint and a leash anchored at the *pre-snap*
position both remain live and drag the freshly-placed missile back — the
identical scenario the architecture review's A3 used to justify wiring the
teleport branch. The teleport branch got the fix; the far branch, which is the
more common disposition for a moving object at >=96 m, did not.
**AP-141 is now factually wrong in its risk column.** It states *"a live
missile never shows a constraint leash"*. After the round-2 fix that is false
for the adopted-body case: such a missile can carry a leash inherited from its
pre-Missile remote life, the teleport branch now clears it, and the far branch
neither clears nor re-anchors it. This is the "a register row asserting
behaviour the code does not have" defect class the 4b-3 reviews named.
**Correct behaviour:** either run the same hook seam on the far branch's
manager-bearing case (retail runs only `StopInterpolating` there, not the full
hook — so the minimal faithful action is `remote.Interp.Clear()` gated on the
host existing, mirroring @0x005163C9), and decide the leash re-anchor
explicitly; or amend AP-141 to state that for an adopted body the far branch
skips retail's guarded `StopInterpolating` @0x005163CB and leaves an inherited
leash un-re-anchored. Do not leave the row asserting the opposite.
---
## D. MINOR findings
### B3 — MINOR — `report_collision_end` now runs twice for the adopted teleport case, and the hook is split across two owners
App's `RunRemoteTeleportHook` executes all six actions including
`ReportCollisionEnd → LiveEntityRuntime.ForceEndCollisionReporting →
RuntimeCollisionReportingState.LeaveWorld`; the Runtime seam then runs
`CollisionReports.LeaveWorld(record)` again before the placement. Retail calls
@0x00514F31 once. The second call is benign (the table is already empty, so
`ForceEnd` finds nothing; it only bumps `_mutationRevision`), and the ordering
relative to `SetPosition` is preserved, so this is cosmetic — but one retail
function now has two owners across a layer boundary, which is how the *order*
of a six-step sequence gets broken later. Cleanest: have the Runtime seam skip
its own `LeaveWorld` when the caller supplied the full hook, or move the whole
hook behind the Runtime seam.
### B4 — MINOR — the projectile hook allocates a per-packet closure while its comment claims the "#315 pattern"
`LiveEntityNetworkUpdateController.cs`, projectile arm:
```csharp
RunRemoteTeleportHook(
acceptedPositionCanonical,
adoptedRemote,
() => _liveEntities.IsCurrentPositionAuthority( // fresh closure, every packet
acceptedPositionRecord,
acceptedPositionAuthorityVersion));
```
The comment says this uses *"the SAME ordered hook seam and per-packet currency
check the remote teleport arm already uses (`RunRemoteTeleportHook`, #315
pattern)"*. The remote arm does not do this: it passes
`_remoteArmCallbacks.RunTeleportHook`, a **cached** method group over scratch
fields (`RunCachedRemoteTeleportHook`), which is precisely what #315 introduced
to remove per-packet closures from the packet path. The contract restates that
constraint (§2 item 5). The seam is shared; the allocation discipline is not,
and the comment asserts otherwise.
### B5 — MINOR — R8 remains open
Judgment you asked for: **deferring the audit is acceptable; deferring the
tracking is not.** The comment now asserts, in production source, that a live
call's retail justification is unestablished. With no `docs/ISSUES.md` row and
no AP row, that assertion is discoverable only by reading
`LiveEntityNetworkUpdateController.cs`. One line in ISSUES.md ("4a remote arm's
`TryCommitAuthoritativeVelocity` has no established retail basis — see the
comment at the call site; `MoveOrTeleport` @0x00516330 byte-confirmed not to
install a velocity") costs nothing and should land with this commit. The
substantive audit belongs to the 4a family, as the contract says.
### B6 — MINOR — the stored-outcome shadow publish uses an unadvanced body on a re-parked retry
`Advance()`'s retry arm calls `SyncProjectilePresentation` after a
`SubmitAndResolve` that returned `Contention`, but never
`StoreAcceptedDestinationPose` on that path (B-section A3's residual). The
shadow is therefore published at `record.FullCellId` (the wire cell) with a
body still at its pre-packet pose — the mirror image of B1, inside Runtime.
Parity with the remote arm, pre-existing, and it disappears if B1's residual
(store never writes the cell, AP-138) is ever closed. Recorded, not filed.
---
## E. Retail claims re-verified this round (no change)
Re-checked because the fix touched the surrounding code, not re-derived from
scratch (round 1 §A has the full derivations):
- `MoveOrTeleport` @0x00516330 — no state/kind test; `arg5` at `[esp+0x7C]`
never read (byte-confirmed round 1); `ret 0x10` at all four exits.
- `teleport_hook` @0x00514ED0 — six actions, five per-manager guarded, the
sixth unguarded. The App fix drives the in-tree ordered port
(`RemoteTeleportHook.Execute`) with per-action `host?.` guards, so retail's
guards decide rather than being re-derived. Correct for the teleport branch.
- `ConstrainTo` @0x00510520`MakePositionManager` @0x00510523; the single
arming site @0x00454272 has no kind test. AP-141 clauses (a) and (b) remain
accurate descriptions of retail; only the row's **risk** column is now wrong
(B2).
- `store_position` @0x00515CE2 writes `objcell_id` with the frame — the basis
of B1.
- ACE never sends a missile `UpdatePosition`
(`WorldObject_Tick.cs:333-334` inside the `PhysicsState.Missile` branch at
`:265`), so B1 and B2 are both test-reachable only. Stated for calibration,
not as a reason to ship them.
- AP-131 and AP-135 untouched; `docs/ISSUES.md` unmodified; #276 not closed.
---
## F. Summary
| # | Sev | Where | Retail address contradicted |
|---|---|---|---|
| B1 | MAJOR | `ProjectileController.SyncPresentationFromResolvedBody` (`entity.ParentCellId`) | `store_position` @0x00515CE2 — writes `objcell_id` with the frame; also breaks parity with `TryApplyGenericRemoteRenderPose` and with the shadow publish in the same call |
| B2 | MAJOR | `LiveEntityNetworkUpdateController` hook gate (`SetPosition` only) + `ApplyAcceptedProjectilePosition`'s `SetPositionSimple` arm | `StopInterpolating` @0x005163C9-@0x005163CB (guard satisfied for an adopted body); `ConstrainTo` @0x00454272 re-anchor |
| B3 | MINOR | App hook action 6 + Runtime seam `LeaveWorld` | @0x00514F31 called once in retail |
| B4 | MINOR | projectile arm's `Func<bool>` closure | — (contract §2 item 5; comment false) |
| B5 | MINOR | no ISSUES/AP row for the acknowledged-unjustified velocity commit | — |
| B6 | MINOR | `Advance()` retry shadow publish | — (mirror of B1 inside Runtime; parity, pre-existing) |
**Both MAJORs are small edits.** B1 is one token. B2 is the far-branch half of
a fix already written for the teleport branch, plus an AP-141 risk-column
correction.