acdream/docs/research/2026-08-05-c5b-contract.md
Erik 9ee9c1a1a6 fix(runtime): close the C5b re-review findings — Gate A narrowing filed, no-window payload gate, bisect hazard recorded
Both C5b re-reviews returned PASS on 02578441..ff100cf3. This lands the
bookkeeping corrections they left, the one gate asymmetry both found
independently, and one wrong retail fact neither of them caught.

1. AP-148 / #325 — Gate A's teleport test, wrong on primary source twice.

The C5b contract stated retail's Gate A teleport term as "TELEPORT_TS
equal" (and, in the trace block, as "must NOT be newer") and blessed
acdream's `teleport == _timestamps[Teleport]` as retail-exact. Disassembly
of the PDB-paired binary at SmartBox::HandleReceivedPosition
0x0045402B-0x00454054 says otherwise: the shortcut is taken iff the wire
stamp is equal OR newer (wrap-safe) — `sbb eax,eax / neg eax` materialises
the carry of the compare and the branch skips Gate A on CF, i.e. only when
the wire stamp is strictly OLDER. It is CPhysicsObj::newer_event
@0x00451B10's identical idiom with the operands swapped. Binary Ninja drops
the flag test and renders it `if (-((eax_7 - eax_7)) == 0)`, always true —
which is why two rounds of reading pseudo-C recorded it backwards.

So acdream's ForcePosition disposition is a strict SUBSET of retail's Gate
A set, and a local ForcePosition carrying a newer teleport stamp is
misrouted into a full Apply: wire heading instead of preserved heading,
unparent, possible placement frame, zeroed velocity, TELEPORT_TS advanced,
and OfferTeleportDestination called for a packet retail never starts
presentation for.

PhysicsTimestampGate.cs is NOT changed. The predicate exists twice (also
ValidAcceptedAuthority's PreviousTeleport == AcceptedTeleport), and the fix
has to decide TELEPORT_TS's disposition on a Gate A path that has never
seen a stale-but-equal pair. #325 records all of it and says explicitly
that it is not a one-line comparison swap. C5b made this marginally
better, not worse: clearParent was unconditionally true before C5b and is
unchanged; installPlacementFrame moved toward retail's HasAnims gate.

2. Retail F2 / architecture L-A — the no-window route had no pre-merge
payload validation. Root fix, not a documented asymmetry.

The graphical route validates before the merge (OnPosition's payloadIsValid
-> LiveEntityInboundAuthorityGate's !payloadIsValid return); despite its
name CanAcceptPositionPayload is not projectile-scoped. The no-window route
had no equivalent, and since D1 fed an unvalidated LandblockId into
CommitWireCellRebucket — where 0 is the withdrawal shape, silently
de-residencing the entity in the field every bot reads as CellId.

RuntimeLiveEntitySessionController.OnPositionUpdated now applies the same
rule at the same point, reusing
RuntimeAuthoritativePositionRouteClassifier.IsValidCreateWirePosition plus
the finite-velocity term — the exact pair TryApplyPosition already applies
on its initial-residence branch. Chosen over documenting it because the fix
is five lines and leaving it would have left two written claims falsified
by the code. It is a behaviour change: headless now drops packets it
merged. Against ACE the set is empty, and the graphical host has carried
this gate since it was written; the argument is recorded in the contract's
§15.2 rather than gated.

Two test fixtures carried cell ids retail's own inbound_valid_cellid
rejects (low words 0x41 and 0x51, above the 0x40 landcell ceiling). Their
constants were corrected; their assertions were not.

New test sabotage-verified in both directions: gate removed -> red at the
withdrawal-shape assertion; gate moved to guard only the cell commit ->
red at the pose assertion, which is what makes it a before-the-MERGE test
rather than a before-the-commit test.

3. Register and doc corrections.

- AD-64: "deliberately absent" was presented as the complete difference
  list and was not. Adds (a) the residence gate is weaker than the merge's
  own — both hosts' commits use TryGetCurrent while TryApplyPosition's FIFO
  branch uses TryGetTransaction, so the wire cell can commit ahead of the
  continuation that will replay it; (b) the two missile gates are two
  different expressions that agree today; (c) the payload gate, now
  present. Risk column records that (a) and (b) have no discriminating test
  on either side.
- AP-147: amended for D1 — pre-D1 the no-window host published [Updated]
  alone and lost the Rebucketed, so a headless event log is now a real
  instance of the "consumer that snapshots a delta" the row warns about.
- AD-60: "Matches retail exactly" scoped to the withhold, since the row's
  body documents two channels that do not.
- CommitWireCellRebucket: notes the unreachable ThrowIfNull /
  EnsureNotDisposed precedence inversion.
- TryCommitAcceptedWireCell: the discarded commit bool is explained rather
  than left bare — false means IsCurrent went stale, unreachable three
  statements after a synchronous TryGetActive.

4. Bisect hazard recorded in the C4 closeout handoff (the doc CLAUDE.md
sends readers to before any C5 work) and in the contract's §15.3: commits
735f0a72..23aa62f2 contain a live headless defect — every remote's
FullCellId frozen for the session — introduced by 735f0a72 and fixed only
at ff100cf3. Nothing throws and no test in the range fails.

Gates: Release build 0 errors/0 warnings. Complete suite 11,142 passed /
4 skipped / 0 failed against the 11,141 / 4 / 0 baseline — net +1, exactly
the one new test. No flake appeared (#302, #308, #321 all green).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-05 23:10:24 +02:00

54 KiB
Raw Permalink Blame History

C5b — classify-before-merge on every steady-state Position (#275): pinned contract (2026-08-05)

Written at HEAD 02578441 (branch claude/acdream-physics-divergence-5aa784; C5a landed at 6921a027, #319 at 392c1e22, AP-145/#318 at f8e55ba5). Every symbol below was re-verified by symbol at this HEAD, not inherited; §12 lists what the C5 scoping (2026-08-05-c5-scoping.md, written at 52175aa1) got wrong. Inputs: the scoping's C5b section, register rows AP-131 / AD-60 / AP-130 / AP-135 / AP-146, the 4b-3 contract (2026-08-04-c4-route-4b-3-contract.md — D1 is C5b's direct ancestor), the C4 closeout handoff's seven process findings, the C5a contract (2026-08-05-c5a-contract.md), and issue #275.

One-paragraph verdict: C5b is smaller than the scoping feared, because of a fact the scoping did not state: the merge's two flags (installPlacementFrame / clearParent) are a pure function of the timestamp disposition and the hasAnimations proxy — both available inside the merge itself, pre-merge, with no routing outcome and no playerDistance. Retail runs unset_parent and the SetPlacementFrame gate BEFORE MoveOrTeleport is consulted, so the flags are upstream of classification proper. C5b therefore needs no route plumbing, no signature changes, no App→Runtime threading — it replaces two true literals with a four-row truth table the classifier already encodes, and flips one refreshPosition: argument to false. The blast radius is correspondingly narrower than "45+ sites mis-reading a withheld wire cell", for a second reason the scoping missed entirely: the merge stamp is one of THREE steady-state wire-cell writers, and the other two stay (§5, W2/W3). What genuinely changes is the classification window, the refused-ForcePosition shape, and the (test-only) missile arm. The contract pins all of this.


1. Retail ground truth — Gate A and the pre-placement sequence, verified in acclient_2013_pseudo_c.txt

SmartBox::HandleReceivedPosition @0x00453FD0 (pseudo-C lines 9289693051). The exact order, with addresses:

00453fe3   objcell_id = arg3->objcell_id          // wire cell read into a LOCAL
00453ff4   Frame::operator=(&var_40, &arg3->frame) // wire frame copied into local Position var_48
0045400c   GATE A: if (arg2 == player && newer_event(player, FORCE_POSITION_TS, arg9)):
0045402b-54    wrapped-compare update_times[4] (TELEPORT_TS) vs arg8 — the wire
               teleport stamp must NOT be OLDER (equal or newer both pass).
               CORRECTED 2026-08-05, see §15 — this line read "must NOT be
               newer" and it was backwards.
00454056-68    get_heading / Frame::set_heading(&var_40)   // preserve body heading
00454074       SmartBox::BlipPlayer(this, &var_48)
00454079       player->update_times[0] = arg7              // stamp POSITION_TS
00454091       cmdinterp->SendPositionEvent()
0045409d       return                                      // BEFORE unset_parent / SetPlacementFrame
004540b7   if (!newer_event(arg2, POSITION_TS, arg7)) → return (the 004540e6 teleport-regression
           quirk stamps update_times[0] and returns @004540f6)
004540f9-11e if (parented && parent->id != player_id): weenie->SetParentedState(0) notification
00454129   CPhysicsObj::unset_parent(arg2)                 // UNCONDITIONAL on this path
00454137   if (CPhysicsObj::HasAnims(arg2) == 0):
00454142       CPhysicsObj::SetPlacementFrame(arg2, arg4, 1)
0045414d   if (arg2 != player):                            // REMOTE branch
00454254       if (MoveOrTeleport(arg2, &var_48, arg8, arg5, arg6) != 0):
00454272           ConstrainTo(arg2, &arg2->m_position, …)  // post-placement anchor
0045415f   else if TELEPORT_TS newer: TeleportPlayer @00454168, ConstrainTo @0045418a
           anchored at the WIRE &var_48, set_velocity(0) @004541b4
           else: ConstrainTo @004541ec; if UsePositionFromServer && arg5: InterpolateTo @0045422c

Five facts decide C5b:

  1. The wire cell is never written to the object. arg3->objcell_id is read into a local @0x00453FE3 and flows only into the local var_48 Position handed to BlipPlayer / TeleportPlayer / MoveOrTeleport / ConstrainTo. Nowhere in this function is the object's cell assigned. The object's cell changes only inside the placement family (SetPositionInternal @0x00515BD0 → set_cell; enter_world @0x00516310/0x00516170) or per-frame movement transit. This is the retail rule AD-60's executor half already encodes ("a wire position never directly makes the record resident").
  2. Gate A (@0x0045400C) is decided on data that exists before any merge: the entity is the player, FORCE_POSITION_TS advanced, and the wire TELEPORT_TS is not older than the stored one. It returns @0x0045409D before unset_parent @0x00454129 and before the SetPlacementFrame gate @0x00454137 — a ForcePosition never unparents and never installs a placement frame. acdream's analog exists upstream: PhysicsTimestampGate.TryAcceptPositionEvent:190-203 produces the ForcePosition disposition when isLocalPlayer && IsNewer(FORCE_POSITION_TS) && teleport == _timestamps[Teleport]. CORRECTED 2026-08-05 (see §15): that third term is retail's Gate A pair NARROWED, not matched. Retail's test is teleport equal-or-newer; acdream's is equal only. The narrowing is filed at AP-148 / issue #325. Everything C5b decides from Gate A is unaffected — the disposition C5b reads is the same one it always was, and the narrowing makes the ForcePosition set strictly SMALLER, so no packet C5b's truth table classifies as force would have been classified otherwise. A remote entity can never receive the ForcePosition disposition (isLocalPlayer guard at :190), so the flag truth table below needs no entity-kind term.
  3. The two pre-placement writes are gated on exactly two facts. unset_parent @0x00454129 runs for every accepted non-Gate-A Position (including packets MoveOrTeleport will return 0 for — retail unparents BEFORE learning the routing outcome). SetPlacementFrame @0x00454142 is gated on HasAnims(arg2) == 0 alone. Neither gate reads the near/far/teleport classification — which is why acdream's merge flags are a function of (disposition, hasAnimations) and nothing else.
  4. MoveOrTeleport @0x00516330 reads the BODY's own state at entry: TELEPORT_TS @0x00516375 and this_1->cell == 0 @0x00516386 — the committed cell, read before any placement — then contact arg4 @0x0051638E. Route 4b-3's D1 fed the classifier the PRE-merge committed cell for exactly this reason; C5b's withhold (D2) makes that pre-merge value structurally equal to the record's FullCellId in the classification window (§4, "strengthened invariant").
  5. store_position @0x00515CE2 is SetPositionInternal's no-transition branch (placement ran, no walkable transition → GotoLostCell @0x00515CF2), not a MoveOrTeleport path. A retail placement that fails leaves the wire pose in m_position and the object HIDDEN in the lost cell — acdream's surviving wire-cell-on-non-commit behaviour (W2, §5) is the visible variant of this, already filed across AP-136/AP-138/#309. C5b does not re-litigate it.

2. The two scoping-identified sites, re-verified at HEAD 02578441

S1 — the steady-state merge's unconditional flags

InboundPhysicsStateController.TryApplyPosition (src/AcDream.Runtime/Entities/InboundPhysicsStateController.cs:610), hardcoding installPlacementFrame: true, clearParent: true at :662-663 (scoping exact). Production chain at HEAD, every hop verified: LiveEntityNetworkUpdateController.OnPosition:1948LiveEntityInboundAuthorityGate.TryAcceptPosition:161 (src/AcDream.App/Physics/ — the scoping gave no directory) → LiveEntityRuntime.TryApplyPosition:2550RuntimeEntityObjectLifetime.TryApplyPosition:1789 (scoping said :1781) → RuntimeEntityDirectory.TryApplyPosition:654 → the S1 overload.

The route-flag-threaded sibling (ApplyAcceptedPositionSnapshot:673, flags at :681-682) already exists and is what the continuation executor uses (RuntimeInitialCreateContinuationExecutor.cs:1983-1993, classified flags at :1991-1992, withhold at :1996) — scoping §1b verified exact.

What changes semantically (not "what gets deleted"): the merged snapshot's Physics.AnimationFrame / PlacementId and its ParentGuid / ParentLocation / Physics.Parent stop being unconditionally wire-installed / wire-cleared and start obeying retail's two gates:

disposition installPlacementFrame clearParent classifier row (cite, don't restate)
Rejected unread (timestamp-only path :793-794) unread
ForcePosition (local player only, teleport-equal) false false RuntimeAuthoritativePositionRouteClassifier.cs:311-333 (Gate A)
Apply !hasAnimations true (unchanged) classifier :336-465 — every accepted non-force route carries UnparentBeforeRouting: true, ApplyPlacementFrameBeforeRouting: !request.HasAnimations

hasAnimations is the AP-130 static proxy, computed from the PRE-merge snapshot old with the identical expression RuntimeAcceptedPositionRouteRequests.cs:99-101 uses (old.MotionTableId ?? old.Physics?.MotionTableId) is { } id && id != 0u). WorldSession.EntitySpawn.MotionTableId exists (WorldSession.cs:103); the inbound controller's _snapshots[guid] and canonical.Snapshot are the same data pre-merge, so the two computations cannot diverge.

Two observability notes that decide the test plan (§8):

  • The ForcePosition placement-frame half is inert: for disposition is not Apply, appliedPlacement keeps old.PlacementId under either flag value (:815-819). Gate A's force half discriminates ONLY through parent retention (:855-857).
  • The Apply placement-frame half is live exactly when hasAnimations == true: today the wire frame (update.PlacementId ?? 0u) is stamped onto an animated entity's snapshot where retail's HasAnims gate skips SetPlacementFrame. That is AP-131's animation snap/reset half.

S2 — the wire-acceptance-derived FullCellId stamp

RuntimeEntityObjectLifetime.cs:1923-1926Entities.RefreshSnapshot(canonical, snapshot, refreshPosition: acceptedPosition) (argument at :1926, matching the C5a contract's corrected citation; the register's :1338 and the scoping's :1918 are both stale — cite by symbol). refreshPosition: true routes through RuntimeEntityRecord.RefreshDerivedState:230-237SetFullCell(position.LandblockId, …) — the wire cell, stamped on bare acceptance, plus PropagateFullCellToChildren (RuntimeEntityDirectory.cs:240-246) when it changes.

What changes semantically: refreshPosition: false. The merge stops being a residency writer. In the window between the merge and the OnPosition prologue rebucket, canonical.FullCellId IS the pre-merge committed cell — which is exactly what 4b-3's D1 threads out-of-band today (timestamps.PreMergeCommittedCellId, :1904-1907). Post-D2, 4b-3's convention becomes a structural property of the record.

The executor's analogue is unchanged and untouched (refreshPosition: false at executor :1996 — its CANONICAL CELL SEMANTICS comment at :1966-1976 is the model for S2's new comment).

3. Design decisions — pinned, not open for redesign

D1 — the merge flags become the classifier's truth table, computed pre-merge inside the merge

In S1's TryApplyPosition, replace the two literals at :662-663 with the table in §2-S1, computed from disposition and hasAnimations(old). No signature changes; no new parameters; no route construction in the merge; playerDistance is never needed (the full route's near/far decision is downstream of retail's unset_parent/SetPlacementFrame order and stays post-merge in ClassifyRemoteAcceptedPosition).

The post-merge classification (LiveEntityNetworkUpdateController.cs:2167-2175RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPositionRuntimeAcceptedPositionRouteRequests.Build) is untouched: it computes the full route from the same retained inputs, so its ApplyPlacementFrameBeforeRouting / UnparentBeforeRouting fields equal the merge's flags by construction. The consistency is pinned by test (§8 test 4), not by a shared code path — the two computations are small, pure, and separately sabotage-verifiable.

Doc comments that must be made true in the same commit (process rule (c)): the :595-608 "Round 4 R4-15 … no HasContact or route-classification concept" remarks; the :657-661 "Legacy immediate-apply reproduces EXACT prior behavior" comment; ApplyAcceptedPosition's :772-781 two-caller doc; the :809-814 PositionPack comment's "retail skips it entirely while HasAnimations is true" (already true — now load-bearing for BOTH callers).

D2 — the steady-state merge withholds the wire cell (refreshPosition: false)

One argument flip at S2 plus the comment rewrite. The 4b-3 D1 comment at :1894-1907 is extended, not deleted: it currently explains why the classifier needs the threaded pre-merge value; it must add that after this change the post-merge record CARRIES that same value through the classification window, making the threading a belt-and-suspenders duplicate that stays (the threaded value remains the classifier's input of record — process finding (b), read the observable once).

Two publish-side effects, both pinned (trap T3):

  1. The merge's publish-kind ternary (:1948, beforeCell != canonical.FullCellId ? Rebucketed : Updated) becomes always-Updated — the merge can no longer change the cell. The Rebucketed edge for a wire-cell-changing packet moves to the prologue rebucket's CommitRebucket (RuntimeEntityObjectLifetime.cs:1956-1987), which TODAY early-outs publish-less (previous == fullCellId at :1973 — because the merge already stamped it) and after D2 publishes. Net observable: exactly one Rebucketed delta per cell-changing accepted Position — same kind, same value, same publisher, later point in the same call. A test pins this at the observable (one delta, not zero, not two); it does not pin the source site.
  2. PropagateFullCellToChildren for a parent whose accepted Position changes its cell moves from the merge's directory wrapper (RuntimeEntityDirectory.cs:240-246) to CommitRebucket's public SetFullCell (:355). Same packet, same value, same single propagation. This is the #319 child-equality channel — a directory-level test pins exactly one propagation per parent cell change on the steady-state path, for BOTH parent classes (player 0x5… and creature 0x8…; process rule from #319 — a test population that only ever sees sequence-0 parents is blind to the player class).

D3 — the other two wire-cell writers STAY, and the register says so

The scoping's premise — withholding S2 "moves residency changes onto placement/simulation commits only" — is false at HEAD (§12 item 1). Two more steady-state wire-cell writers exist downstream of the merge in OnPosition, and C5b keeps both:

  • W2 — the prologue rebucket (LiveEntityNetworkUpdateController.cs:2329 RebucketLiveEntity(update.Guid, p.LandblockId)LiveEntityRuntime.cs:935-945CommitRebucketSetFullCell(wire cell)). Runs for EVERY classification reaching the generic tail — near, far, teleport, cell-less, unrouted, and every ordinary local-player Apply echo. Its other jobs (draw-bucket move, bucket-promotion recovery, prepare_to_enter_world clock edges) are not cell writes and are not in scope. Do NOT gate W2 by classification (trap T5): for the local player W2's echo-driven commit is the mechanism that advances the canonical cell across landblocks during ordinary movement (AP-146/#320 machinery — the per-tick projection deliberately passes a landblock id and preserves the cell, LiveEntityRuntime.cs:935-938); gating it silently freezes the player's cell between teleports and #319's child equality inherits the freeze.
  • W3 — the post-routing wire-cell adopt (TryAdoptWireCellAfterRouting, LiveEntityNetworkUpdateController.cs:1613-1624): RemoteMotion.CellId = wire for every non-placing arm, writing through to FullCellId via CommitCanonicalCell. Already filed as retained bookkeeping under AP-135; the placing arms (far/teleport) are suppressed there because the placement receipt is the cell authority.

W2/W3's population note: packets that RETURN before :2329 never see W2 — the force-local path (Committed/Deferred :2055, Rejected/Contention :2066) and the missile arm (:2256). For those, post-D2 residency is placement-receipt-authoritative (or, on a refused/contended force, unchanged at the last commit — retail's shape, since retail's BlipPlayer-era body keeps its last placed cell; AD-62 already files the non-commit force outcomes). This is D2's genuinely new steady-state surface and it is retail-correcting, asserted by §8 test 6.

D4 — register bookkeeping, in the implementation commit

  • AP-131 RETIRES (§7 evidence). Its "legacy caller is deleted at the production cutover" framing is overtaken: the caller is not deleted, it is corrected — the unconditional literals are replaced by the classified computation. The retirement commit rewrites the row's text to past tense with the evidence, per register rule 1 (a deviation found without a row is a bug twice over; a retired divergence needs its retirement recorded, which the C5a/AP-1 and 4b-3/AP-137 rewrites established as the house style).
  • AD-60 — legacy half RETIRES, row REWRITTEN, not deleted (4b-3's D8 precedent: silent whole-row deletion would hide surviving wire-cell channels). The rewrite keeps the executor half as the standing rule, records the legacy half's retirement with §7's evidence, and names the surviving wire-cell channels: W2 (prologue rebucket commit — cross-cited into AP-146 for the local player) and W3 (already AP-135). The row's surviving claim is therefore precise: "a wire Position never makes a record resident inside the merge or ahead of classification; the post-routing controller still adopts the wire cell for non-placement outcomes as the bounded-residency bridge, filed at AP-135/W2."
  • AP-130 amendment (bookkeeping, not retirement): its Site column names only the executor's hasAnimations local; the merge now consumes the same static proxy. One sentence added. The proxy itself MUST NOT be "improved" to a live animation-queue read in this slice (trap T7).
  • AP-146 / #320 amendment (bookkeeping): #320's edge list cites "an accepted inbound Position/ForcePosition (RuntimeEntityDirectory.RefreshSnapshotRuntimeEntityRecord.cs:234)" as a local-player cell writer. After D2 that writer is the generic-tail rebucket's CommitRebucket (and, for ForcePosition, the placement receipt). Amend both in the same commit or the next reader files a phantom regression.
  • AP-135 stays, its writes stay (must-remain-true item 3).
  • AP-1, AD-1, AP-141AP-145, AD-61, AD-62: pinned untouchable.

4. What must REMAIN true (process rule — the contract causes the defect)

For every path this slice touches, including every refusal and rejection:

  1. Classification reads pre-merge inputs only. 4b-3's D1 binds in full: PreMergeCommittedCellId is threaded exactly as today (RuntimeEntityObjectLifetime.cs:1904-1907), measured by the SAME TryApplyPosition call, never re-read after the merge. D2 adds the structural property (the record equals it in the window) but the threaded value stays the classifier's input of record.
  2. The D4 constraint-arm partition (4b-3) does not move. One post-operation ConstrainTo site, its partition table unchanged. C5b touches no arming code.
  3. AP-135's bookkeeping writes stay — the server-cell adopt (RemoteMotion.CellId) and LastServerPos/LastServerPosTime samples in LiveEntityNetworkUpdateController, on both airborne no-op neighbourhoods.
  4. Dual-parent-class test discipline. Every new test touching parent/ child cell propagation runs a player (0x5…) parent AND a creature (0x8…) parent (#319's lesson; the handoff's corrected gate-2 criterion).
  5. The executor path is untouched — its classified flags, its refreshPosition: false, its replay semantics (ApplyAcceptedPositionExecutionRejectedSnapshot). Do not unify the two merge callsites into one helper "while here" (trap T8).
  6. The timestamp gate is untouched; Gate A's teleport-concurrent shape stays upstream (PhysicsTimestampGate.cs:190-217; a force with a teleport advance falls through to Apply, and ValidAcceptedAuthority's PreviousTeleport == AcceptedTeleport rule handles the classified side). CORRECTED 2026-08-05 (§15): that fallthrough is not retail's shape. Retail's Gate A accepts a force carrying a NEWER teleport stamp and returns before ever advancing TELEPORT_TS. Both acdream sites named in this item encode the narrower equality; both are filed at AP-148 / #325, and both stay untouched by C5b — closing them is its own slice with its own gate.
  7. The OnPosition routing/arms are untouched — generic render-pose gate, W2, the arm dispatch, W3, the unified tail, AP-87's catch-up, AP-139's landing clear, AP-140's InContact gates.
  8. The local player's cell-freshness path (echo → generic tail → W2 → CommitRebucket) is unchanged. AP-146 stays accurate after its §D4 amendment.
  9. The initial-residence branch of TryApplyPosition (:1800-1860) is untouched — it is the executor's enqueue path, already classified.
  10. Physics.ObserveLocalWorldFrame keeps reading the wire landblock directly (:1887-1889); it is not a FullCellId consumer.
  11. No new skip (process rule (d)) and no test weakened to make the withhold pass (trap T2 — the withhold assertion lives at the merge boundary, never at OnPosition level, where W2 legitimately re-stamps).

5. THE BLAST RADIUS — itemised by consumer class

Premise: S2's withhold is the only residency write C5b removes; W2/W3 stay (D3). So the consumers that ever read a value that is intentionally NOT the wire cell are (a) everything in the classification window (merge → :2329), and (b) steady state after packets that return before W2 (force path, missile arm). Everyone else reads the same value as today. The #319 lesson is applied, not assumed: a stale cell is worse than a zero cell for derived/presentation copies — but FullCellId's whole semantic post-D2 is "the last committed cell", which is retail's this->cell; that is not staleness, it is the definition. Zero would be the lie here.

Class A — placement owners (read at/after placement). Verdict: CORRECT, no gate.

RuntimeSetPositionState (pre-flight reads :1172-1255, commit compares :2305/:2760/:5020, parks :3004/:3031/:3677-3697, :3795/:3954, :5225/:5242), RuntimeRemotePlacementDriveController (:1053-1068 — verified: reads record.FullCellId AFTER the placement commit for the shadow publish; the commit just wrote it), RuntimeAcceptedPositionDriveController (receipt resolvedCell: reads :914-1535). Retail's placement reads this->cell — the committed cell, never the wire cell. These get a MORE honest input post-D2.

Class B — per-tick simulation. Verdict: CORRECT, no gate.

RuntimeOrdinaryPhysicsUpdater:127 (transits FROM record.FullCellId — retail's per-frame cell transit reads this->cell), RuntimePhysicsState (:958, commit :2145-2159), RuntimeProjectilePhysicsUpdater (:53-293), the free-fall sweep's rm.CellId != 0 gate (AP-135). The simulation is the retail-sanctioned residency writer between packets.

Class C — classification inputs. Verdict: CORRECT — strengthened.

RuntimeAcceptedPositionRouteRequests (:57 route-1 overload — executor scope, unchanged; :641-family drive-controller builds for the local player — reads the local player's committed cell, whose freshness path is W2, unchanged). The remote PositionEvent build is the threaded PreMergeCommittedCellId — post-D2 the record in the window equals it structurally (§3 D2). 4b-3's D1 moves from convention-defended to structure-defended.

Class D — presentation/projection, event-time readers. Verdict: HARMLESS, with three named window sites.

Event-time readers (landblock-loaded, visibility-changed, per-tick sync) run outside the per-packet window, and W2 keeps steady-state values identical to today for every generic-tail packet: RuntimePlacementPresentationSink:286 (post-commit receipts), LiveRenderProjectionJournal:270-271 (falls back when 0), EntityEffectController:481, LiveStaticAnimationResidency:25, StaticLiveRootCommitter:76, ArchRenderScene (Residency.FullCellId — statics), HeadlessLocalPlayerFrameHost:87, HeadlessRuntimePlacementProjectionSink:103 (token compare), RemotePhysicsUpdater:239/:294 / LiveEntityOrdinaryPhysicsUpdater:107 (simulation-snapshot → ParentCellId sync).

Named window sites (inside merge→W2, or pre-W2 returns) — verdict per site:

site reads verdict
LiveEntityHydrationController:592 (?? candidate.FullCellId fallback) landblock-loaded event HARMLESS — event-time, post-W2 population; the fallback is third in a ?? chain behind projection/snapshot position
LiveEntityHydrationController:1077 (FullCellId != 0 gate) projection recovery inside the window VERIFY at implementation: after D2 it reads the committed cell (correct — recovery should re-place from the last commit, not from an unplaced wire claim). Pre-D2 it could read the just-stamped wire cell; that was the AP-1-shaped read this slice exists to remove
LiveEntityRuntime:1219 / :1564 (token.ExactCellId compares) hydration/migration tokens VERIFY: token cells are create/placement-derived, not wire-derived; the comparison against the committed cell is the intended predicate
LiveEntityPresentationController:220 (RestoreShadow's FullCellId == 0 bail) visibility-edge restore HARMLESS/CORRECT — a cell-less body correctly restores no shadow row; the force-path population keeps its committed cell

Class E — residency/liveness predicates (the "45+ sites"). Verdict: HARMLESS, two CORRECT-AND-LOAD-BEARING.

  • GetRootObjectClockDisposition (LiveEntityRuntime:2602-2617), HasSpatialRuntimeProjection (:3340-3345), the ordinary-root gates (:953-956, :3452-3455): tick/edge-time, read the committed cell — retail's this->cell != 0 predicate shape. CORRECT; unchanged in value for generic-tail packets (W2), changed only for refused-force (retail's shape).
  • IsAffectedCollisionResident (RuntimeSetPositionState:3949) and its !HasCommittedParent companion gates, the collision-retirement sweeps (:3771/:3796/:3990/:4052): event-time (landblock retirement), read the committed cell. CORRECT — a retirement sweep must act on where the body IS, not where an unplaced wire packet claimed.
  • The initial-create residence FullCellId != 0 refusal (RuntimeInitialCreateResidenceState:583) and executor baselines (:877-1182, executor :1836/:2484): pending-residence records never take the steady-state merge branch. UNAFFECTED.
  • 4b-3's cell-less classification input: threaded pre-merge value (Class C).
  • RuntimeEntityDirectory:492 child propagation and RuntimeEntityObjectLifetime:1546-1555 parent-attach propagation: write-side (D2's propagation-source move, pinned in §3 D2 item 2).
  • ProjectileController:279/:568/:930 and the missile arm: the ONLY population whose residency becomes exclusively placement-receipt-driven post-D2 (the arm returns before W2, :2256). Test-only today (AP-141: ACE never sends a missile UpdatePosition). CORRECT — retail's projectile cell comes from SetPosition, full stop.

The headline behavioural delta, stated once

Post-C5b, a refused or contended local ForcePosition (AD-62's shapes (iv)-(vi)) leaves FullCellId at the last committed cell where today the merge stamps the refused packet's wire cell. Retail cannot refuse (AD-62) and its body keeps the last placed cell — the new shape is the retail-reachable one. Every other steady-state observable is either unchanged (W2/W3) or retail-correcting (the classification window, the placement-authoritative force/portal/missile arms).

6. Proof obligations (must prove, not assume)

  1. The truth table is the classifier's. §8 test 4 drives both the merge and RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition (via RuntimeAcceptedPositionRouteRequests.Build) over the packet-shape matrix and asserts the merge-installed AnimationFrame/PlacementId/ParentGuid/ParentLocation/Physics.Parent equal what the route's flags would install. Not a restated table in the test body — the production classifier is the oracle (process rule (e): no test that re-encodes the constant under test).
  2. The withhold is at the merge boundary. §8 test 5 asserts at RuntimeEntityObjectLifetime.TryApplyPosition level: an accepted Position whose wire cell differs from the committed cell leaves canonical.FullCellId == beforeCell immediately after the call. It MUST NOT be asserted at OnPosition level — W2 legitimately commits the wire cell there, and an OnPosition-level assertion would fail and tempt a weakening (trap T2).
  3. Publish-delta conservation. One Rebucketed delta per cell-changing accepted Position on the steady-state path (§3 D2 item 1), and one child propagation per parent cell change (item 2), both asserted at the observable, both parent classes (must-remain-true item 4).
  4. The refused-force shape. A ForcePosition whose drive execution refuses/contends writes NO residency change anywhere (§8 test 6) — merge, W2 (unreached), receipt (none). This is the assertion that makes AD-60's retirement mean something.
  5. Sabotage-verify every new discriminating test in BOTH directions (process rule (e)) — the canonical half and the presentation/snapshot half separately, per route 7's finding.

7. Retirement evidence — AP-131 and AD-60's legacy half (same commit as the change)

AP-131 retires because:

  1. The literals at InboundPhysicsStateController.cs:662-663 no longer exist; the merged flags are computed by the §2-S1 truth table. A grep in the commit message shows no production caller passes unconditional true/true (the executor's :1991-1992 threading is the classified path, already retail-exact).
  2. §8 tests 1-4 pin the new behaviour against the classifier-as-oracle, sabotage-verified: (a) Apply + animated entity → wire frame NOT installed (sabotage installPlacementFrame: true fails it); (b) Apply + non-animated → wire frame installed (sabotage false fails it); (c) ForcePosition + parented entity → parent fields retained (sabotage clearParent: true fails it); (d) the matrix consistency test.
  3. The row's risk column ("animated entity installs a placement frame retail would skip; ForcePosition unparents where Gate A never reaches unset_parent") describes code that no longer exists — the two production behaviours are now the classifier's own rows (RuntimeAuthoritativePositionRouteClassifier.cs:311-333 and :336-465).

AD-60's legacy half retires because:

  1. RuntimeEntityObjectLifetime.cs:1926 reads refreshPosition: false; RefreshDerivedState's SetFullCell(position.LandblockId, …) is unreachable from the steady-state merge.
  2. §8 tests 5-6 pin the withhold at the merge boundary and the refused-force shape.
  3. The row rewrite (§3 D4) names W2/W3 as the surviving, separately-filed wire-cell channels, so the retirement cannot be misread as "wire acceptance never changes residency anywhere" — it means "never inside the merge, never ahead of classification, never for a packet whose placement was declined", with the post-routing adoption for non-placement outcomes filed where it belongs (AP-135 / the W2 citation).

8. Test plan

Focused Runtime tests (tests/AcDream.Runtime.Tests), each asserting the layer that broke historically (merged snapshot fields, canonical cell, publish deltas — never source-text pins):

  1. Apply, animated remote (MotionTableId != 0): merged snapshot keeps old.Physics.AnimationFrame / old.PlacementId; pose and timestamps still merge. Sabotage: force installPlacementFrame: true.
  2. Apply, non-animated remote: merged snapshot installs update.PlacementId ?? 0u. Sabotage: force installPlacementFrame: false.
  3. ForcePosition, parented local player: merged snapshot retains ParentGuid / ParentLocation / Physics.Parent; heading preservation (:797-807) unchanged. Sabotage: force clearParent: true.
  4. Matrix consistency (proof obligation 1): {Apply, ForcePosition} × {animated, not} × {parented, not} × {player 0x5…, creature 0x8…} — merge outcome == classifier route flags' outcome.
  5. Withhold at the merge boundary (proof obligation 2): wire cell ≠ committed cell → canonical.FullCellId unchanged after TryApplyPosition; Snapshot.Position DID refresh (the pose half of the merge is not the withheld half); a subsequent canonical placement commit still changes FullCellId.
  6. Refused-force shape (proof obligation 4): drive a ForcePosition whose execution refuses (destination outside the service window) through the drive controller; assert no residency write from merge, receipt, or rebound — FullCellId is the pre-packet value throughout.
  7. Publish-delta conservation (proof obligation 3): one Rebucketed per cell-changing accepted Position; one child propagation per parent cell change; player parent AND creature parent.
  8. The existing InboundPhysicsStateController unit tests that assert the UNCONDITIONAL flags are rewritten, never delete-only (the scoping's §3 row, carried): each becomes its classified-flag counterpart.

9. Gates

  • Focused tests above.
  • Complete Release suite: $env:ACDREAM_PAK_PATH set, dotnet test AcDream.slnx -c Release -m:1. Re-measure the baseline; do not inherit. Recorded figures: 11,112 passed / 4 skipped / 0 failed at 392c1e22 (C5a contract) and 11,106 at 6921a027 post-C5a (the tasking figure). C5b's net count moves only by test rewrites/additions; the commit message reconciles the net explicitly. Three known flakes, filed separately, never conflated, never chased: #302 (PortalProjectionTests.ClipToRegion_FrameOwnedStore_…, App.Tests), #308 (NakEmissionTests.LossSoak_…, Core.Net.Tests, full-suite load), #321 (DatSoundCacheTests concurrent-decode-dedup, Core.Tests, full-suite load — filed 2026-08-05, deliberately separate from the other two; do NOT fold it in). If any appears, re-run and say which.
  • Connected gate: YES — argued, not assumed. The change is on the hottest wire path (every steady-state Position) and the campaign's standing discipline is a connected gate per live-path behaviour change. But the gate's pass evidence must be positive, because C5b's improvement is absence-of-signal (a placement frame NOT installed; an animation NOT popping — the #319/rule-(g) unfalsifiable-criterion trap). Recipe (two-client, Release, ACDREAM_RETAIL_UI=1, ACDREAM_PROBE_REMOTE_TELEPORT=1, pre-C5c so the probe still exists):
    1. Pickup-then-drop a ground item, several times — the positive probe assertion: [remote-teleport] lines appear with hookRan=True placement=Committed, proving the pre-merge-fed cell-less/teleport classification still fires and commits after the merge stopped being a residency writer. (Gate 4's closure at af828a8a established this recipe reaches the arm; the probe label may read teleport-ts for the short-circuit reason the handoff records — that is expected, not a gap.)
    2. A walking/running creature across ≥1 landblock boundary, observed: visible, smooth, correct cell, no freeze, no origin-snap — the W2 regression watch (the one writer C5b leans on hardest).
    3. @teleto/@teleloc a creature (never a player character — rule (g)): teleport arm unchanged, leash re-armed, no rubber-band, no run-in-place.
    4. Local @teleto + one portal recall: the force/portal paths are now placement-authoritative for residency — arrival pose correct, leash armed, movement immediate.
    5. An animated creature idling/fighting at UpdatePosition cadence: watch for NEW animation popping (regression) — recorded as an observation only, explicitly NOT a pass criterion (the absence of the OLD pop is the improvement and is test-gated, §8 test 1). Graceful close per the standing rule. No new probe flag for C5b: the discriminating signals are either absence-of-signal (ungateable) or already carried by [remote-teleport]; adding a seventh temporary probe for one session and stripping it in C5c is churn without evidence value.

10. Size and split call

Calibrated against the campaign (route 3 ~418, #319 76, 4b-3 ~250-700):

piece production lines test lines
D1 truth table + comment rewrites ~25-40 ~200-350 (matrix + rewrites)
D2 withhold + comment + publish-edge pins ~15-25 ~100-250
Register rewrites + #320/AP-130 amendments 0 (docs) 0

Total ~40-65 production, ~300-600 test — well under the ~500-line split threshold, and dramatically under the scoping's ~150-400 estimate, because the scoping assumed route plumbing (it did not see that the flags are disposition-determined; §12 item 2). One landing. If the test rewrite balloons past ~600 lines, the split line falls between D1's flag tests and D2's withhold tests — but the PRODUCTION change lands atomically: D1+D2 in one commit is one coherent behaviour (classify-then-merge); a half-flipped intermediate (classified flags with the wire stamp, or vice versa) is exactly the mixed-residency state this campaign keeps paying for.

Single implementer, dual reviews (retail-conformance + architecture) per the campaign's discipline, both re-run on the final diff.

11. Traps (routes 6/7 style)

  • T1 — reading hasAnimations post-merge. Compute from old (the pre-merge snapshot) inside TryApplyPosition. For Position packets the merge cannot change MotionTableId, so the failure mode is latent, not live — pin the discipline anyway, because the NEXT field added to this computation may not share the property.
  • T2 — the withhold assertion at the wrong boundary. W2 re-stamps the wire cell at :2329 for every generic-tail packet, BY DESIGN. A test asserting "FullCellId unchanged after OnPosition" fails and invites a weakening or a W2 gate. The assertion lives at RuntimeEntityObjectLifetime.TryApplyPosition (§8 test 5). This is the slice's "weaken a test to make the withhold pass" trap.
  • T3 — the moved publish edges. Rebucketed moves from the merge's ternary to CommitRebucket; child propagation moves with it. Pin the observable (counts), never the source site; do not delete the coverage when the old assertion site goes quiet.
  • T4 — the ForcePosition placement-frame half is inert (appliedPlacement keeps old.PlacementId under either flag). The force-half discriminating assertion is PARENT RETENTION. Sabotaging the placement frame on a ForcePosition proves nothing.
  • T5 — do not gate W2 "for symmetry". The local player's canonical cell freshness runs THROUGH W2 (echo → generic tail → CommitRebucket; AP-146/#320). Gating it freezes the player's cell between teleports and #319's child equality inherits the freeze. W2 is also the far arm's destination-streaming nudge.
  • T6 — the missile arm returns before W2 (:2256). Post-D2 its residency is placement-receipt-only; fixtures driving refused/deferred missile placements must not assert wire-cell residency.
  • T7 — do not "improve" hasAnimations to a live animation-queue read. AP-130 files the static proxy deliberately; the merge consumes the same proxy and the row is amended, not escalated.
  • T8 — no while-here unification of the two merge callsites, the executor's replay merge, or the timestamp gate.
  • T9 — flake discipline. #302/#308/#321 are three separate filed flakes; a full-suite red in any of them is re-run and named, never folded into "the flake class" and never masked (#321's filing note: no retry, no Skip, no delay).

12. Scoping claims found wrong or stale at HEAD 02578441

  1. The scoping's core premise for the withhold is false. §2: withholding S2 "moves residency changes onto placement/simulation commits only — the retail rule". At HEAD the steady-state path has TWO MORE wire-cell writers downstream of the merge (W2 LiveEntityNetworkUpdateController.cs:2329CommitRebucket; W3 TryAdoptWireCellAfterRouting:1613), and C5b keeps both (§3 D3). The retirement must be scoped to the merge boundary and the survivors named in the register rewrite, or AD-60's retirement is a shell game. This is the contract's load-bearing correction.
  2. The size estimate over-counts. Scoping §7: "~150-400 changed on the hottest inbound path". The flags are disposition+hasAnimations-determined (§2-S1 truth table; retail runs both gates BEFORE the MoveOrTeleport branch decision) — no route construction, no playerDistance threading, no signature changes. Re-estimate: ~40-65 production lines (§10).
  3. Stale line citations (all re-verified): TryApplyPosition at RuntimeEntityObjectLifetime.cs:1789 (scoping :1781); the threaded overload's signature at InboundPhysicsStateController.cs:673 (scoping :681 — that is its flags parameter line); S2's call at RuntimeEntityObjectLifetime.cs:1923-1926 (C5a's :1926 correct; the register's :1338 still stale); LiveEntityInboundAuthorityGate lives under src/AcDream.App/Physics/ (scoping gave no directory; :161 exact). Everything else in scoping §1b/§2 holds.
  4. #320's edge list goes stale the day D2 lands — its "accepted inbound Position (RefreshSnapshotRuntimeEntityRecord.cs:234)" writer becomes the generic-tail rebucket's CommitRebucket. Amendment mandated in the implementation commit (§3 D4), or the next reader files a phantom regression.
  5. The scoping's §5 cell-less falsification session is overtaken: gate 4 CLOSED at af828a8a (the cause=cellless label was a probe-artifact — the pickup-then-drop recipe exercised the arm all along). C5b does not inherit that investigation; its connected gate reuses the same recipe as positive evidence (§9). Route-7 thickening and #316's measurement are C5c-ledger items, not C5b design inputs — the implementation session checks the ledger for their status before starting, and does not block on them.

13. What C5b does NOT do

  • Does NOT wire the continuation executor into the steady-state path (#275's alternative branch — the issue's "or delete it with the route"; the steady-state merge stays a live, now-classified, production caller).
  • Does NOT gate, move, or delete W2/W3 (§3 D3).
  • Does NOT touch the OnPosition routing, arms, constraint partition, generic render-pose gate, or interpolation machinery.
  • Does NOT touch the timestamp gate, the executor, the initial-residence branch, or the projectile arm's routing.
  • Does NOT strip any probe (C5c), fix #316/#317/#320, or re-open AP-145's seam.
  • Does NOT add a live animation-queue read (AP-130 stands).

14. Implementation outcome (appended at landing, 2026-08-05)

Both design decisions landed exactly as pinned: installPlacementFrame: !force && !hasAnimations, clearParent: !force at the S1 call site, and refreshPosition: false at S2. No signature changed, no route was plumbed, playerDistance was never needed, W2/W3 were not touched. The production diff is 129 insertions / 38 deletions across two files, of which the BEHAVIOUR is seven lines — four computing force/hasAnimations, two flag arguments, one refreshPosition argument. Everything else in that delta is the §3-D1 mandated comment rewrites. Well inside §10's ~40-65 estimate; the estimate itself was counting comments.

§5's blast-radius survey missed three consumer sites. All three are D2-caused, all three were found by the test suite rather than by reading, and all three turned out to be the intended semantics rather than regressions — but the survey did not enumerate them, so they are recorded here:

  1. DatLiveEntityProjectionMaterializer's self-projection branch (src/AcDream.App/Rendering/DatLiveEntityProjectionMaterializer.cs, the residence is AwaitRuntimePlacement && expectedCanonical.FullCellId != 0u && !HasActiveInitialCreateResidence gate). This is a Class-D window site: it runs inside OnPosition's prologue recovery (:1973 / :2089/:2102), ahead of W2. Pre-D2 it read the just-merged wire cell and installed the spatial bucket there; post-D2 it reads 0 for a withdrawn/inventory-only record and correctly declines to project from an unplaced wire claim. Production still installs the bucket in the SAME OnPosition call, at W2 (:2329) — verified by reading every return between the recovery call and W2: none is conditioned on IsSpatiallyProjected or FullCellId. Two LiveEntityHydrationControllerTests cases asserted the bucket at the recovery boundary and were extended to drive the production W2 step; this is the same shape as trap T2, one layer up.
  2. ProjectileController.SyncPresentationFromResolvedBody (entity.ParentCellId = record.FullCellId). Trap T6 named the missile arm's residency but not this presentation writer. On a REFUSED missile placement the entity's pose still moves to the destination (acdream's StoreAcceptedDestinationPose fallback) while ParentCellId now stays at the committed source cell. The MAJOR-1 invariant the covering test exists to protect (ParentCellId == record.FullCellId != body.CellPosition .ObjCellId) is unchanged and is now asserted as that identity rather than as a wire-cell constant.
  3. The Rebucketed publish edge does NOT always move to CommitRebucket. §3 D2 item 1 says the merge's ternary "becomes always-Updated". It does not, and the ternary is deliberately kept: the Physics.SetPosition.Forget (canonical, restoreCancelledPark: true) call a few statements earlier can roll a wakeable lost-cell park back, and RestoreParkWithdrawal restores canonical residency at the body's committed cell. That is a real cell edge produced inside TryApplyPosition by a placement owner and must still publish as Rebucketed. Collapsing the ternary would have silently downgraded it.

One §5 verdict was checked and stands: LiveEntityHydrationController :1077's FullCellId != 0 gate is unreachable from this change — it sits inside the canonicalSpawn.Position is null branch, and a merged steady-state Position always carries one.

Not done, deliberately: no automated OnPosition-level test drives the full pickup→drop→reproject sequence (no such fixture exists; the collapse matrix fixture does not cover pickup). The claim that production reprojects a dropped item at W2 rests on the code reading above plus the two hydration tests now driving recovery-then-W2 in production order. §9's connected gate recipe item 1 (pickup-then-drop, several times) is the positive evidence for it and has not been run.


15. Corrections and hazards found at the C5b closeout (2026-08-05)

Both C5b re-reviews (retail-conformance and architecture) returned PASS on 02578441..ff100cf3. This section carries what they left behind, plus one correction neither of them made and both of them missed twice.

15.1 §1's Gate A teleport test was WRONG on primary source

This document's §1 stated retail's Gate A teleport term two ways, and both were wrong: the trace line read "teleport must NOT be newer", and fact 2 read "TELEPORT_TS equal". It then blessed acdream's teleport == _timestamps[Teleport] (PhysicsTimestampGate.cs:199) as "retail's Gate A pair, verified at HEAD".

Verified against the PDB-paired binary (acclient.exe v11.4186, CodeView GUID 9e847e2f-777c-4bd9-886c-22256bb87f32, check_exe_pdb.py reports MATCH), disassembled with capstone at SmartBox::HandleReceivedPosition 0x0045402B0x00454054:

00453fff  mov   esi, [esp+0x68]        ; arg2 (the object)
00454003  cmp   esi, ecx               ; == this->player ?
00454005  mov   ebx, [esp+0x80]        ; ebx = arg8 = WIRE teleport stamp
0045401c  call  0x451b10               ; newer_event(player, 6=FORCE_POSITION_TS, arg9)
0045402b  mov   bp,  word [ecx+0x16c]  ; bp = player->update_times[4] == TELEPORT_TS
                                       ;      (base 0x164, 2 bytes/entry -> index 4)
00454032  movzx edx, bx                ; wire
00454035  movzx eax, bp                ; stored
00454038  sub   eax, edx / cdq / xor eax,edx / sub eax,edx   ; abs(stored - wire)
0045403f  cmp   eax, 0x7fff
00454044  jg    0x45404b               ; far apart -> wrapped compare
00454046  cmp   bx, bp                 ; unwrapped: CF <=> wire  < stored
00454049  jmp   0x45404e
0045404b  cmp   bp, bx                 ; wrapped:   CF <=> stored < wire
0045404e  sbb   eax, eax / neg eax     ; eax = CF
00454052  test  eax, eax
00454054  jne   0x4540a0               ; CF set (wire strictly OLDER) -> SKIP Gate A

The shortcut is taken iff the wire teleport stamp is equal or newer (wrap-safe) — "not older". It is CPhysicsObj::newer_event @0x00451B10's identical idiom with the two compare operands swapped: newer_event's unwrapped compare is cmp si(stored), di(incoming) and returns 1 on CF, i.e. "incoming is newer"; Gate A's is cmp bx(wire), bp(stored) and skips on CF, i.e. "wire is older". acclient.h:6090 confirms update_times[4] == TELEPORT_TS, and 0x00454084's mov word [edx+0x164], ax confirms the array base via the POSITION_TS stamp.

Why two review rounds missed it. Binary Ninja drops the flag test and renders the whole sequence as if (-((eax_7 - eax_7)) == 0) — vacuously true. Reading the pseudo-C, at any level of care, cannot recover this. Reading the bytes takes five minutes. This is the same class as the PE byte-decode finding in claude-memory/reference_pe_byte_decode.md: when a decomp renders a comparison as a tautology, that is a decompiler artifact signature, not a retail fact.

Consequence. acdream's ForcePosition disposition is a strict SUBSET of retail's Gate A set. Filed as AP-148 with issue #325. Not fixed here: see the AP row and the issue for why it is not a one-line comparison swap.

C5b's own effect on this row is marginally POSITIVE, not negative. clearParent was unconditionally true before C5b and is !force after — unchanged for the misrouted Apply. installPlacementFrame went unconditional true!force && !hasAnimations, i.e. toward retail's "Gate A never reaches SetPlacementFrame" for the animated half. C5b neither introduced nor widened the narrowing; it narrowed the damage.

15.2 The no-window route had no pre-merge payload validation

Retail finding F2 / architecture finding L-A, found independently by both re-reviewers. Fixed in the closeout commit rather than documented: the no-window route (RuntimeLiveEntitySessionController.OnPositionUpdated) now applies the same predicate at the same point as the graphical route's payloadIsValid gate. See AD-64 and the method's own comment. The choice was the root fix rather than a documented asymmetry because the fix is five lines, reuses an existing Runtime predicate verbatim (RuntimeAuthoritativePositionRouteClassifier.IsValidCreateWirePosition plus the finite-velocity term — the exact pair RuntimeEntityObjectLifetime.TryApplyPosition already applies on its initial-residence branch), and leaving it would have left two written claims falsified by the code (the TryCommitAcceptedWireCell doc's "under the same reachability rules the graphical OnPosition route applies" and AD-64's "whose gates were derived from those returns one by one").

It is a behaviour change, and its blast radius is bounded by argument, not by a gate. Headless now drops packets it previously merged. Against ACE the set is empty: ACE resolves cell 0 at Position construction and never serializes a NaN/Inf frame, and PositionPack writes a real ObjCellId. The shape is unreachable in the same way it has always been unreachable for the graphical host, which has carried this gate since it was written. Two test fixtures did carry illegal cell ids (low word 0x41 and 0x51, both above LandDefs.CellLowInRange's 0x40 landcell ceiling); their constants were corrected, their assertions were not.

15.3 BISECT HAZARD — 735f0a72..23aa62f2 contain a live headless defect

A git bisect that lands anywhere in this range will hit a real, unrelated defect. 735f0a72 (the C5b implementation) made the steady-state Position merge withhold the wire cell, and the replacement writer it relied on — the OnPosition prologue rebucket — lives in AcDream.App. The no-window host has no analogue, so across that range every remote entity's FullCellId is frozen at its placement value for the whole session in AcDream.Headless, and the local player loses one of AP-146's three cell-refresh edges. Nothing throws and no test in that range fails; a bot's RuntimeEntitySnapshot.CellId simply stops advancing, and RuntimeSetPositionState.IsAffectedCollisionResident parks bodies against a landblock they have left.

Fixed at ff100cf3 (the D1 fix). The range is three commits: 735f0a72, ed806997, 23aa62f2.

If you are bisecting a headless cell/residency symptom, treat any bad verdict inside that range as suspect and re-test with ff100cf3's RuntimeLiveEntitySessionController.TryCommitAcceptedWireCell cherry-picked on top. Structural cause: issue #324 (two parallel, non-shared inbound routes); residual duplication: AD-64.