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 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>
This commit is contained in:
parent
ff100cf33f
commit
9ee9c1a1a6
7 changed files with 455 additions and 21 deletions
|
|
@ -24,6 +24,84 @@ What does NOT go here:
|
|||
- Every session: scan OPEN issues at start; promote/close anything we touched during the session before ending.
|
||||
- Promoting to a Phase: mark as `DONE (promoted to Phase X)` + commit SHA where the Phase entry landed.
|
||||
|
||||
## #325 — Gate A's teleport test is narrower than retail's: a ForcePosition carrying a NEWER teleport stamp is misrouted into a full Apply
|
||||
|
||||
**Status:** OPEN
|
||||
**Severity:** MEDIUM (no observed symptom; reachability against ACE is
|
||||
unmeasured — see below. The behaviour when reached is four simultaneous
|
||||
divergences, not one.)
|
||||
**Filed:** 2026-08-05 at the C5b closeout, with register row **AP-148**
|
||||
**Component:** physics / inbound timestamp gate / local-player force position
|
||||
|
||||
**Description.** Retail's `SmartBox::HandleReceivedPosition` Gate A — the
|
||||
local-player FORCE_POSITION self-echo shortcut — takes its shortcut iff the
|
||||
wire TELEPORT_TS is **not older** than the stored one, i.e. equal *or newer*.
|
||||
acdream requires exact equality
|
||||
(`PhysicsTimestampGate.TryAcceptPositionEvent:199`,
|
||||
`teleport == _timestamps[Teleport]`), so acdream's `ForcePosition`
|
||||
disposition is a strict subset of retail's Gate A set.
|
||||
|
||||
The disassembly, the byte-level reasoning, and why two review rounds of
|
||||
reading Binary Ninja pseudo-C recorded the term backwards are in AP-148 and
|
||||
in `docs/research/2026-08-05-c5b-contract.md` §15.1. Short version:
|
||||
0x0045402B–0x00454054 materialises the carry of a wrap-safe 16-bit compare
|
||||
with `sbb eax,eax / neg eax` and skips Gate A on CF, where CF means "wire
|
||||
strictly older"; Binary Ninja drops the flag test and renders the whole
|
||||
thing as `if (-((eax_7 - eax_7)) == 0)`, which is always true.
|
||||
|
||||
**What the misroute does.** The excluded packet falls through to `Apply`
|
||||
with `advancesTeleport` true, which is four behaviour changes at once:
|
||||
|
||||
1. the wire heading is applied instead of the body's being preserved
|
||||
(`InboundPhysicsStateController.ApplyAcceptedPosition:846-856` is
|
||||
`ForcePosition`-gated);
|
||||
2. the entity is unparented, and may have a placement frame installed
|
||||
(`clearParent: !force`, `installPlacementFrame: !force && !hasAnimations`);
|
||||
3. local velocity is zeroed (`:882-885`, the `TeleportAdvanced` arm);
|
||||
4. TELEPORT_TS advances and `OfferTeleportDestination` is called, starting
|
||||
teleport/portal presentation for a packet retail never starts it for.
|
||||
|
||||
Retail's Gate A deliberately lets a force ride *past* a pending teleport
|
||||
advance without consuming it — it returns @0x0045409D, before
|
||||
`newer_event(arg2, TELEPORT_TS, arg8)` @0x00454158 — leaving the ordinary
|
||||
Position channel to process that teleport.
|
||||
|
||||
**This is NOT a one-line comparison swap, and the fix must not be attempted
|
||||
as one.** Three things have to be decided together:
|
||||
|
||||
1. **The predicate exists twice.** Besides `PhysicsTimestampGate.cs:199`,
|
||||
`RuntimeAuthoritativePositionRouteClassifier.ValidAcceptedAuthority`
|
||||
independently requires
|
||||
`authority.PreviousTeleportSequence == authority.AcceptedTeleportSequence`
|
||||
for a `ForcePosition` — the same narrowing, encoded downstream. Widening
|
||||
one without the other turns the newly-admitted packets into
|
||||
`RejectedAuthority` routes, which is a third behaviour, worse than either.
|
||||
2. **TELEPORT_TS's disposition on the Gate A path.** acdream's Gate A branch
|
||||
already returns without advancing TELEPORT_TS, which matches retail —
|
||||
but it has never had to do so while the wire stamp was *newer*. After
|
||||
widening, `AcceptedPhysicsTimestamps.PreviousTeleport` and `.Teleport`
|
||||
would be equal and STALE while the wire carried a newer value: a shape no
|
||||
consumer has seen. `IsFreshTeleportStart`, the drive controller's
|
||||
`previousTeleport` argument, and J6.3's F751/Position teleport
|
||||
correlation all read that pair.
|
||||
3. **Reachability has to be established before the fix, not assumed.** ACE's
|
||||
two `ObjectForcePosition` bumps (`Player.cs:1148` PKLite re-placement,
|
||||
`Player_Tick.cs:488` z-hack correction) do not themselves bump the
|
||||
teleport sequence — but `PositionPack` serialises the *current* teleport
|
||||
sequence, so any client whose TELEPORT_TS lags ACE's is in the divergent
|
||||
window on its next force. Whether that lag is reachable in practice is
|
||||
unmeasured. A cdb trace or a wire capture answers it; guessing does not.
|
||||
|
||||
The correct predicate already exists verbatim one file away:
|
||||
`PhysicsTimestampGate.IsFreshTeleportStart:163` is
|
||||
`!IsNewer(teleport, _timestamps[Teleport])`, which is exactly retail's Gate A
|
||||
term.
|
||||
|
||||
**Acceptance:** both encodings widened together; the newly-admitted shape
|
||||
covered by a discriminating test at the disposition boundary AND at the
|
||||
classifier's authority validation; the three consumers in item 2 checked
|
||||
against a stale-but-equal teleport pair; AP-148 retired in the same commit.
|
||||
|
||||
## #324 — The graphical and no-window hosts run parallel, non-shared inbound entity routes
|
||||
|
||||
**Status:** OPEN
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue