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 by735f0a72and fixed only atff100cf3. 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>
918 lines
54 KiB
Markdown
918 lines
54 KiB
Markdown
# 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
|
||
92896–93051). 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:1948` →
|
||
`LiveEntityInboundAuthorityGate.TryAcceptPosition:161`
|
||
(`src/AcDream.App/Physics/` — the scoping gave no directory) →
|
||
`LiveEntityRuntime.TryApplyPosition:2550` →
|
||
`RuntimeEntityObjectLifetime.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-1926` —
|
||
`Entities.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-237` →
|
||
`SetFullCell(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-2175`
|
||
→ `RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition` →
|
||
`RuntimeAcceptedPositionRouteRequests.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-945` → `CommitRebucket` → `SetFullCell(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.RefreshSnapshot` → `RuntimeEntityRecord.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-141–AP-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:2329`
|
||
→ `CommitRebucket`; 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 (`RefreshSnapshot` → `RuntimeEntityRecord.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` 0x0045402B–0x00454054:
|
||
|
||
```
|
||
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**.
|