fix(physics): classify before merge on every steady-state Position (C5b, #275, AP-131/AD-60)
The steady-state accepted-Position merge did two things retail never does,
on every single Position packet: it installed the wire placement frame and
unparented unconditionally, and it derived the record's FullCellId from
bare wire acceptance. Both are now correct, and they land together - a
half-flipped intermediate (classified flags with the wire stamp, or vice
versa) is exactly the mixed-residency state this campaign keeps paying for.
WHY the flags need no route. SmartBox::HandleReceivedPosition @0x00453FD0
decides both pre-placement writes BEFORE MoveOrTeleport is consulted: Gate A
@0x0045400C returns @0x0045409D ahead of unset_parent @0x00454129 and ahead
of the HasAnims SetPlacementFrame gate @0x00454137. Neither gate reads the
near/far/teleport classification. So the two flags are a pure function of
(disposition, hasAnimations) and are computable inside the merge, pre-merge,
with no signature change, no route construction and no playerDistance - the
scoping's ~150-400-line route-plumbing estimate over-counted because it did
not see this. That truth table IS
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition's own
ApplyPlacementFrameBeforeRouting/UnparentBeforeRouting rows; the classifier
stays the oracle and the equality is pinned by test, not by a shared path,
so each computation remains separately sabotage-verifiable.
WHY the cell is withheld. HandleReceivedPosition reads the wire objcell_id
into a LOCAL @0x00453FE3 and hands it only to BlipPlayer / TeleportPlayer /
MoveOrTeleport / ConstrainTo; it never assigns the object's cell. The
object's cell moves inside the placement family (SetPositionInternal
@0x00515BD0 to set_cell, enter_world) or per-frame transit, and nowhere
else. The continuation executor has encoded that rule since the executor
slice; this caller now matches it verbatim.
WHAT DELIBERATELY SURVIVES. Two steady-state wire-cell writers stay,
downstream of the merge and outside the classification window: the
OnPosition prologue rebucket (W2, into CommitRebucket), which is also the
local player's own cell-freshness path, and the post-routing wire-cell adopt
for non-placing arms (W3, AP-135). Gating W2 "for symmetry" would freeze the
player's canonical cell between teleports and #319's child-cell equality
would inherit the freeze. AD-60's rewrite names both so the retirement
cannot be misread as "wire acceptance never changes residency anywhere".
REGISTER. AP-131 RETIRED - the unconditional literals no longer exist; the
caller was corrected, not deleted, so the row's own "deleted at the
production cutover" framing is overtaken. AD-60's legacy half RETIRED and
the row REWRITTEN rather than deleted, naming W2/W3 (route 4b-3's D8
precedent: a silent whole-row deletion would hide surviving channels).
AP-130 amended - the merge consumes the same static HasAnimations proxy,
deliberately not escalated to a live animation-queue read. AP-146 and #320
amended - their "accepted inbound Position (RefreshSnapshot into
RuntimeEntityRecord.cs:234)" local-player cell writer is now the generic
tail's CommitRebucket, and a ForcePosition (which returns before that tail)
is placement-receipt-authoritative. #275 closed.
HEADLINE BEHAVIOURAL DELTA, stated once: a refused or contended local
ForcePosition now leaves FullCellId at the last committed cell where the
merge used to stamp the refused packet's wire cell. Retail cannot refuse
(AD-62) and its body keeps its last placed cell, so the new shape is the
retail-reachable one.
THREE CONSUMER SITES THE CONTRACT'S BLAST-RADIUS SURVEY MISSED, all
D2-caused, all found by the suite rather than by reading, all intended
semantics rather than regressions (recorded in the contract's new section
14):
(1) DatLiveEntityProjectionMaterializer's self-projection branch reads
FullCellId inside OnPosition's prologue recovery, ahead of W2. It now
correctly declines to project from an unplaced wire claim; production
installs the bucket at W2 in the same call (verified: no return between
the recovery call and W2 is conditioned on IsSpatiallyProjected or
FullCellId). Two hydration tests asserted the bucket at the recovery
boundary and now drive the production W2 step - the same shape as trap
T2, one layer up.
(2) ProjectileController.SyncPresentationFromResolvedBody writes
ParentCellId = record.FullCellId. On a refused missile placement that is
now the committed source cell. The MAJOR-1 invariant is unchanged and is
now asserted as the identity it always meant rather than as a wire-cell
constant.
(3) The merge's Rebucketed ternary does NOT become always-Updated as the
contract predicted, and is deliberately kept: the
Forget(restoreCancelledPark: true) above it can roll a wakeable
lost-cell park back, and RestoreParkWithdrawal restores canonical
residency. That is a real cell edge produced inside this method by a
placement owner.
TEST-COUNT RECONCILIATION. Baseline measured at this HEAD by stashing the
change: Runtime.Tests 1176, App.Tests 4135 (4132 passed / 3 skipped),
solution 11,106 passed / 4 skipped - matching the recorded figure at
6921a027 exactly. Post-change: Runtime.Tests 1195, App.Tests 4135 unchanged,
solution 11,125 passed / 4 skipped / 0 failed. Net +19, entirely new Runtime
tests: 3 facts plus a 12-row matrix theory in
InboundPhysicsStateControllerTests, 1 fact plus a 2-row theory in the new
RuntimeSteadyStatePositionMergeTests, and 1 fact in
RuntimeAcceptedPositionDriveControllerTests. No test was deleted; five
existing tests were rewritten in place, never delete-only. No new skip; none
of #302/#308/#321 appeared.
SABOTAGE VERIFICATIONS (each new discriminating test, both directions;
production line broken, suite run, line restored):
installPlacementFrame (!force && !hasAnimations) to (!force)
5 fail: ApplyOnAnimatedEntity_NeverInstallsTheWirePlacementFrame plus
the 4 animated non-force matrix rows.
installPlacementFrame to false
6 fail: ApplyOnNonAnimatedEntity_InstallsTheWirePlacementFrame,
PositionPlacementAbsentAndPresentZeroBothApplyRetailZero plus the 4
non-animated non-force matrix rows.
clearParent (!force) to true
3 fail: ForcePositionOnParentedLocalPlayer_RetainsTheParentAttachment
plus the 2 force+parented matrix rows.
clearParent (!force) to false
4 fail: the 4 Apply+parented matrix rows.
refreshPosition false to acceptedPosition
4 fail: AcceptedPosition_WithholdsTheWireCellAtTheMergeBoundary,
ContendedForcePosition_WritesNoResidencyAnywhere,
ReentrantNewerPositionDuringPickupDiscardSuppressesStalePickupDelta,
MissileFarRefused_...ParentCellIdAgreesWithCommittedCell. Confirmed a
second time by the baseline measurement above, where the withhold test
was the sole red.
CommitRebucket publishes Updated instead of Rebucketed
2 fail: both parent classes of
CellChangingAcceptedPosition_ConservesOneRebucketAndOneChildPropagation.
RuntimeEntityDirectory.SetFullCell drops PropagateFullCellToChildren
2 fail: the same two rows.
T4 respected: the ForcePosition placement-frame half is inert
(appliedPlacement keeps old.PlacementId under either flag value), so the
force row's discriminating assertion is parent retention, never the frame.
NOT DONE, deliberately: the executor is still not wired into the
steady-state path (#275's alternative branch); W2/W3 are untouched; no probe
added or stripped; AP-130's proxy not escalated; no while-here unification
of the two merge callsites. No automated OnPosition-level test drives the
full pickup / drop / reproject sequence (no fixture covers pickup at that
layer); the contract's connected gate recipe item 1 is the positive evidence
for it and has NOT been run - this commit is not connected-gated.
Contract: docs/research/2026-08-05-c5b-contract.md (committed here, with its
section 14 implementation outcome appended).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0257844106
commit
735f0a72af
11 changed files with 1715 additions and 63 deletions
|
|
@ -35,9 +35,11 @@ question is real and unresolved
|
|||
**Description.** Retail writes the local player's cell on EVERY physics tick
|
||||
(`CPhysicsObj::SetPositionInternal` @0x00515330, unconditional). acdream's
|
||||
canonical `FullCellId` for the LOCAL player is written only at three edges:
|
||||
login activation (`RuntimeSetPositionState.cs:2741-2745`), an accepted inbound
|
||||
Position/ForcePosition (`RuntimeEntityDirectory.RefreshSnapshot` →
|
||||
`RuntimeEntityRecord.cs:234`), and a teleport/portal placement commit
|
||||
login activation (`RuntimeSetPositionState.cs:2741-2745`), the `OnPosition`
|
||||
generic tail's prologue rebucket after an accepted inbound Position
|
||||
(`LiveEntityNetworkUpdateController` → `LiveEntityRuntime.RebucketLiveEntity`
|
||||
→ `RuntimeEntityObjectLifetime.CommitRebucket`), and a teleport/portal
|
||||
placement commit
|
||||
(`RuntimeSetPositionState.cs:5001-5007`; `LocalPlayerTeleportController.cs:255`).
|
||||
Ordinary WASD movement passes a LANDBLOCK id, not an exact cell
|
||||
(`LocalPlayerProjectionController.Project`, low 16 bits forced to `0xFFFF` in
|
||||
|
|
@ -47,6 +49,18 @@ and mostly-frozen between teleports — see the register row this issue's fix
|
|||
commit files (AP-146) for the full citation and the argument that this is
|
||||
currently safe for every EXISTING consumer.
|
||||
|
||||
**Amended 2026-08-05 by C5b (#275).** The second edge above used to be the
|
||||
accepted-Position MERGE itself (`RuntimeEntityDirectory.RefreshSnapshot` →
|
||||
`RuntimeEntityRecord.cs:234`). C5b made that merge withhold the wire cell
|
||||
(AD-60), so the writer is now the `OnPosition` prologue rebucket's
|
||||
`CommitRebucket` — one step later in the same call, same value. Nothing about
|
||||
this issue's substance changes: the coarse landblock-preserve branch at
|
||||
`LiveEntityRuntime.cs:935-938` is unchanged and is still what makes the
|
||||
player's cell mostly-frozen. One shape DID change and belongs to this issue's
|
||||
survey: a local **ForcePosition** returns before that tail, so its residency
|
||||
is now placement-receipt-authoritative — a refused or contended force writes
|
||||
no cell at all (retail's own shape; AD-62).
|
||||
|
||||
**Why this is not #319's blast radius.** #319's fix makes a player-parented
|
||||
equipped child inherit the parent's (the player's) canonical cell EXACTLY —
|
||||
an equality invariant, not a freshness one. The child is stale-but-equal
|
||||
|
|
@ -1648,7 +1662,8 @@ Copy this block when adding a new issue:
|
|||
|
||||
## #275 — Unify the legacy Position wire path onto the executor's route classifier
|
||||
|
||||
**Status:** OPEN — post-cutover (physics campaign, filed 2026-08-02)
|
||||
**Status:** CLOSED 2026-08-05 (C5b) — behaviour unified; the remaining
|
||||
structural item is tracked below, not by this issue
|
||||
**Severity:** LOW (internal refactor debt; not a retail divergence)
|
||||
**Component:** Runtime / inbound Position
|
||||
|
||||
|
|
@ -1663,6 +1678,26 @@ in, unify the legacy caller onto the same classifier (or delete it with the
|
|||
route) and retire AP-131/AD-60's legacy halves. See
|
||||
`InboundPhysicsStateController.TryApplyPosition` remarks.
|
||||
|
||||
**Resolution (C5b, 2026-08-05, `docs/research/2026-08-05-c5b-contract.md`).**
|
||||
The steady-state merge was CORRECTED rather than deleted — it is still the only
|
||||
production Position wire caller, and the issue's alternative branch ("or delete
|
||||
it with the route") was not taken. Two behaviour changes landed atomically:
|
||||
`TryApplyPosition` now computes `installPlacementFrame`/`clearParent` pre-merge
|
||||
from `(disposition, hasAnimations(old))`, which is exactly the classifier's own
|
||||
two rows because retail decides both writes ahead of `MoveOrTeleport`; and the
|
||||
merge stops deriving `FullCellId` from bare wire acceptance
|
||||
(`refreshPosition: false`). AP-131 retired, AD-60's legacy half retired and
|
||||
its row rewritten to name the surviving wire-cell channels (W2 the prologue
|
||||
rebucket, W3 the post-routing adopt).
|
||||
|
||||
**What deliberately remains, and is NOT this issue.** The two computations are
|
||||
still separate small pure expressions in two places rather than one shared code
|
||||
path — they are pinned equal by
|
||||
`InboundPhysicsStateControllerTests.MergedPrePlacementFieldsMatchTheClassifiedRouteFlags`,
|
||||
which uses the production classifier as the oracle. Wiring the continuation
|
||||
executor into the steady-state path is a separate structural decision that no
|
||||
longer has any behavioural motivation behind it.
|
||||
|
||||
---
|
||||
|
||||
## #274 — Restricted/barred-house entry needs a connected retail comparison
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue