# C4 route 5 — projectile authoritative placement: retail-conformance review, ROUND 3 (narrow delta) **Reviewer lens:** retail fidelity only. Delta against [round 2](2026-08-04-c4-route-5-retail-review-round2.md) (FAIL: B1, B2), which the architecture reviewer corroborated independently as its B2 and B1. **Subject:** the uncommitted working tree, HEAD `30d3d114`. --- ## VERDICT: **PASS**, with one MUST-FIX-IN-COMMIT documentation correction (C1) Both round-2 MAJORs are closed, and closed well. B1's revert is right *and* its regression test is self-verifying rather than merely sabotage-checked. B2's fix is placed in the correct layer, in the correct order, with the correct guard. The one required edit is a single sentence in AP-141's risk column that asserts a consequence neither retail nor acdream has — **and I wrote the mistake it was copied from.** Round 2's B2 claimed a stale leash would "drag the freshly-placed missile back". That is wrong: acdream's `ConstraintManager` port brakes, it never pulls. I retract it in §C1 and state what the residual actually is. Because register rows land in the same commit as the behaviour they describe, this is a must-fix now, not a follow-up. --- ## A. The four items you asked me to verify ### A1 — B1's revert: is the *reasoning* now right, not just the value? **Yes. PASS on both.** `ProjectileController.SyncPresentationFromResolvedBody` now writes `entity.ParentCellId = record.FullCellId`. I re-derived each of the four claims in the rewritten doc block rather than accepting them: | claim in the comment | verified | |---|---| | on a committed outcome the two sources agree (`CommitCanonical` writes both) | ✓ — the choice is a genuine no-op there | | `StoreAcceptedDestinationPose` composes `body.Position` from `accepted.PositionX + worldOffset(accepted.LandblockId)` — the **wire** cell's frame | ✓ read the method; it writes only `Position`/`Orientation`, offset from `accepted.LandblockId` | | `record.FullCellId` is that same wire cell, stamped by the merge before classification | ✓ `RuntimeEntityRecord.RefreshDerivedState` → `SetFullCell(position.LandblockId, …)` at `:232-237` | | the body's own `CellPosition.ObjCellId` is the **source** cell it left | ✓ untouched by the store fallback | | cross-check (1): the sibling remote arm pairs wire position with wire cell | ✓ `TryApplyGenericRemoteRenderPose` → `entity.ParentCellId = landblockId` | | cross-check (2): Runtime's `SyncProjectilePresentation` publishes the shadow at `record.FullCellId` in the same packet | ✓ — reading the body's cell here would disagree with the shadow for the same body in the same call | | cross-check (3): retail's `store_position` @0x00515CE2 writes the whole `Position` **including `objcell_id`**, so after a retail store the object's cell IS the destination | ✓ | The comment also correctly names the body's stale post-store cell as the acdream-side residual (AP-138, shared with the remote arms) rather than truth to project. The reasoning is sound and matches the code beside it. ### A2 — the indoor-staging test: does the discrimination argument hold, and is it legitimate? **Yes to both. This is the strongest test in the change.** I verified the mechanism at `PhysicsBody.SyncCellPositionDelta` (`PhysicsBody.cs:286-306`), which runs whenever `Position` is written: ```csharp if ((cell & 0xFFFFu) is not (>= 1u and <= 0x40u)) { CellPosition = new Position(cell, new CellFrame(local, …)); // indoor: id PINNED return; } uint adjusted = cell; if (LandDefs.AdjustToOutside(ref adjusted, ref local)) // outdoor: id RE-DERIVED CellPosition = new Position(adjusted, …); ``` So the implementer's account is exactly right: an **outdoor** source cell (index 1..0x40) has its cell id re-derived — including the 192 m landblock wrap — by `AdjustToOutside` as a side effect of the store's position write, so `body.CellPosition.ObjCellId` converges toward the destination landblock on its own and the two expressions stop discriminating. An **indoor** source cell takes the early-return branch: the delta is carried into the local frame and the id stays pinned. That is the only staging in which `body.CellPosition.ObjCellId` and `record.FullCellId` provably differ across the store path. **Legitimate, not contrived.** `IndoorSourceCell = SourceLandblock | 0x0100u` is the canonical first EnvCell in AC's cell-id encoding — outdoor landcells occupy `0x0001`–`0x0040`, EnvCells start at `0x0100` — so this is an ordinary dungeon cell, not a magic number chosen to break an assertion. A missile in a dungeon is an ordinary scenario, and the branch it exercises is the one production takes indoors. **Better than the sabotage run the implementer also did:** the test asserts the divergence *occurred* before asserting the outcome — ```csharp Assert.Equal(IndoorSourceCell, body.CellPosition.ObjCellId); // the divergence is real Assert.Equal(DestinationCell, fixture.Entity.ParentCellId); // and ParentCellId ignored it ``` — so if staging ever stops discriminating (a future change makes the store write the cell, say), the test fails loudly instead of silently going vacuous. That is a self-verifying discriminator, which is the right answer to "a green suite is not evidence". ### A3 — B2's split: is "queue cleared, leash still armed" retail's far-branch behaviour? **Two of the three halves are retail. The third is not, and it is now recorded — so the outcome is acceptable, but the plain answer to your question is: no, retail does not leave the leash as-is.** Retail's far path, in order: ``` 005163c1 position_manager = this_1->position_manager; 005163c9 if (position_manager != 0) 005163cb PositionManager::StopInterpolating(position_manager); 005163d9 CPhysicsObj::SetPositionSimple(this_1, arg2, 1); 005163e8 return 1; ↓ back in SmartBox::HandleReceivedPosition 00454254 if (MoveOrTeleport(...) != 0) { 00454258 GetMaxConstraintDistance / GetStartConstraintDistance 00454272 ConstrainTo(arg2, &arg2->m_position, start, max); ← re-anchor } ``` | half | retail | acdream far arm | verdict | |---|---|---|---| | clear the interpolation queue | yes, whenever `position_manager != 0` @0x005163CB | `route.StopInterpolating && record.RemoteMotion is RemoteMotion → adopted.Interp.Clear()` | ✓ **correct** | | `UnConstrain` | **no** — the far branch never calls `teleport_hook` | not run | ✓ **correct**; the test's "proving the far branch really does run only `StopInterpolating`, not the full hook" is right | | re-anchor the leash | **yes** — `&arg2->m_position` is the object's *just-updated* position, so retail re-anchors on every nonzero return | not run | ✗ **divergent** | The fix's placement is right in every other respect: it lives in the Runtime seam (not App), runs strictly **before** `TryExecuteAcceptedRemotePosition` (mirroring @0x005163CB before @0x005163D9), uses the same `route.StopInterpolating` gate and the same `Interp.Clear()` mapping the sibling `ApplyAcceptedRemoteFarSnap` uses, and the storing partition still runs afterwards so the 4b-2 "cleared-then-frozen" hazard cannot reappear. `record.RemoteMotion is RemoteMotion` is the right analogue of retail's `position_manager != 0` here, because `Interp` is a non-null field of `RemoteMotion` — the same predicate the remote far arm relies on. **What the missing re-anchor actually costs — correcting round 2.** See §C1. It is one tick of brake-taper state, contact-gated, and cannot move the body. ### A4 — AP-141: does the row describe the shipped code exactly? **Almost. The divergence description is now accurate; one sentence of the risk column is not (C1).** Verified accurate: - the bare-missile far skip as *faithful by consequence* — retail's own `position_manager != 0` guard @0x005163C9 skips it for a never-interpolated object ✓; - the adopted-body far clear as **ported**, matching the shipped `route.StopInterpolating && record.RemoteMotion is RemoteMotion` arm ✓; - clause (b) extended to say acdream never arms **or re-anchors** on any disposition, with @0x00454272 cited ✓ — this is the honest recording of the A3 residual; - the "NARROWED … the far-branch clause was factually wrong for the adopted-body case" preamble, which is the right way to retire a superseded claim rather than quietly rewriting it ✓; - clauses (a) and (c) unchanged and still accurate ✓. ### A5 — #317 **Discharges R8. PASS.** Filed OPEN with the byte-decode citation (`MoveOrTeleport` @0x00516330-@0x00516438, every branch, velocity argument never read), the correct scope note (the 4a call left in production deliberately), a real root cause, and an acceptance criterion that names the right next step — auditing the *whole* accepted-Position velocity chain rather than just the one function. My round-2 judgment stands: deferring the audit was always fine; what was missing was the tracking, and it now exists. Nit only: the body cites the call site as "~line 2444" in one paragraph and "~line 2420" in another. It names the symbol in both, which is what process rule 6 says to trust, so this is cosmetic. --- ## B. Round-2 findings — delta status | ID | status | |---|---| | B1 (`ParentCellId`) | **CLOSED** — reverted, reasoning verified, self-verifying indoor regression test | | B2 (far-branch adopted body) | **CLOSED for the `StopInterpolating` half; the `ConstrainTo` re-anchor half is now a recorded divergence** in AP-141 clause (b) rather than an unrecorded one. Acceptable. | | B3 (double `report_collision_end`) | still present (App hook action 6 + Runtime seam's `LeaveWorld`). Benign — the second call finds an empty table. Not re-raised. | | B4 (per-packet closure) | **CLOSED** — `_remoteArmCallbacks.IsCurrentProjectilePositionOwner` + `_projectileArmPosition*` scratch fields, the same cached shape #315 introduced for the remote arm. Comment now describes what the code does. | | B5 (R8 tracking) | **CLOSED** — #317 | | B6 (retry-arm store gap) | unchanged; parity with the remote arm, pre-existing. Not re-raised. | --- ## C. Findings this round ### C1 — MINOR, **must fix in this commit** — AP-141's risk column asserts a consequence neither retail nor acdream has, and I am the source of the error **Where:** `docs/architecture/retail-divergence-register.md`, AP-141, risk column, final clause: > "…but is never re-anchored at the new position by either — **if it survives > un-cleared some other way, it would drag the body toward a stale anchor.**" **This is wrong, and it is my round-2 wording.** I wrote that a leash anchored at the pre-snap position would "drag the freshly-placed missile back". I inferred it from retail's re-anchor existing, without reading acdream's port of what a leash *does*. The in-tree port says otherwise, explicitly: - `ConstraintManager.ConstraintPos` — *"+0x0c retail `constraint_pos` — the leash anchor. Stored by `ConstrainTo`, **never read by `AdjustOffset`** (retail + ACE — write-only in this class)."* - `ConstraintManager.AdjustOffset` (retail `ConstraintManager::adjust_offset` @0x00556180) only **brakes**: while `_host.InContact` it tapers the already-composed per-tick offset between `ConstraintDistanceStart` and `ConstraintDistanceMax`, or zeroes it past max — then unconditionally overwrites `ConstraintPosOffset` with *that tick's step length*. A leash therefore damps motion the interp/sticky chain already produced; it has no mechanism to move anything toward the anchor. The dragging half of round-2 B2 was the **interpolation queue**, which does move the body toward waypoints — and that half is now fixed. **What the missing re-anchor actually costs.** Retail's `ConstrainTo(arg2, &arg2->m_position, …)` sets `ConstraintPos` to the object's just-written position and re-initialises `ConstraintPosOffset = Distance(anchor, host.Position)` = **0**, i.e. it resets the brake accumulator at every accepted Position. acdream leaves `ConstraintPosOffset` at the previous tick's step length. The observable difference is confined to the single tick after the packet, only when the object is `InContact`, and only if that step length already exceeded `ConstraintDistanceStart` — and a far-snapped missile is airborne, where the clamp branch does not run at all. **Correct replacement for the sentence** (substance, not wording): *an adopted-body missile's inherited leash is never re-anchored, so its brake accumulator (`ConstraintPosOffset`) is not reset to zero at each accepted Position as retail's @0x00454272 re-anchor does; the anchor itself is write-only in both retail and the port, so a stale leash brakes rather than pulls and cannot move the body.* Keep the rest of the clause — "never re-anchored at the new position by either" is the accurate divergence and should stay. ### C2 — MINOR (nit) — two comments the fix touched are slightly off 1. `ProjectileController.SyncPresentationFromResolvedBody`'s doc block uses `` twice; the parameter is named `expectedRecord` (`record` is a local from `TryGetCurrent`). The paramref will not resolve. 2. `MissileAdoptedBody_FarCommit_ClearsInterpQueueButLeavesConstraintArmed`'s doc says the leash "must stay armed (proving the far branch really does run only `StopInterpolating`, not the full hook)" — true and well-argued about the *hook*, but silent on @0x00454272, which is the half retail does run. One clause ("…armed but, unlike retail, not re-anchored — AP-141") keeps a future reader from reading the far branch's leash handling as fully faithful. --- ## D. Re-verified retail, no change Spot-checked because the fix touched the surrounding code: - `MoveOrTeleport` @0x00516330 far branch: `position_manager != 0` guard @0x005163C9, `StopInterpolating` @0x005163CB, `SetPositionSimple(…, 1)` @0x005163D9, `return 1` @0x005163E8 — the ported order is correct. - `HandleReceivedPosition` @0x00454254/@0x00454272 — the single post-operation arming site, no kind test, anchor is the object's own just-updated position. - `teleport_hook` @0x00514ED0 — six actions, five per-manager guarded; the far branch does not call it. - `store_position` @0x00515CE2 — writes `objcell_id` with the frame (basis of B1's revert). - ACE never sends a missile `UpdatePosition` (`WorldObject_Tick.cs:333-334` inside the `:265` Missile branch), so every residual here remains test-reachable only. Stated for calibration, not as a reason to ship anything. - AP-131 / AP-135 untouched; #276 not closed; `docs/ISSUES.md` gains only #317. --- ## E. Summary | # | Sev | Where | Action | |---|---|---|---| | C1 | MINOR, must-fix-in-commit | AP-141 risk column, final clause | replace the "drag the body toward a stale anchor" claim — the anchor is write-only; a leash brakes, never pulls. Retracts my own round-2 wording. | | C2 | nit | `SyncPresentationFromResolvedBody` paramref; far-adopted test doc | one-line each | No code changes required. With C1 corrected, route 5 is retail-conformant on every path I have examined across three rounds.