Commit graph

4 commits

Author SHA1 Message Date
Erik
735f0a72af 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>
2026-08-05 21:17:44 +02:00
Erik
19d9509497 fix(physics): #307 — PreviousTeleport was always 0 on the live Position path
Shipped defect in route 2 (9966b531), found while reviewing route 4a.

`InboundPhysicsStateController.TryApplyPosition` built its AcceptedPhysicsTimestamps
via `Current(gate, teleportAdvanced: ...)`, omitting `previousTeleport`, which
defaulted to a literal 0. The only site that populated it was the deferred
initial-create path — which is why the continuation executor was correct and
every newer consumer was not.

Consequence in shipped code: route 2 feeds this into
`ValidAcceptedAuthority`, which requires Previous == Accepted for a
ForcePosition. Any local player whose TELEPORT_TS is nonzero — anyone who has
portalled or recalled this session — had the authority rejected and the force
correction SILENTLY DROPPED. The user's @pklite acceptance was genuine but
narrow: that character had not teleported, so the stamp was still 0.

Second latent consequence: with an accepted stamp >= 0x8000, wrap-safe
TeleportRegressed also fires against the 0 and rejects ordinary Apply positions,
not just ForcePosition.

The fix captures `previousTeleport = gate.TeleportTimestamp` BEFORE
`TryAcceptPositionEvent` mutates it, matching the shape the deferred path
already used. Ordering is the whole point: capturing after would make
Previous == Accepted unconditionally, so ValidAcceptedAuthority's check would
pass vacuously — the symptom would disappear while the semantics broke.

Also removes the footgun that allowed it. `Current`'s parameter is now
`ushort? previousTeleport = null` resolving to `gate.TeleportTimestamp`, so the
eleven non-Position channels — none of which can move TELEPORT_TS — get
"previous == current" by omission rather than a literal 0 that is
indistinguishable from a genuine "never teleported".

Consumer audit: only TryApplyPosition was defective. The two route-2 call sites
trace back to it; the RuntimeEntityObjectLifetime sites source from
TryAcceptDeferredPosition and were already correct.

Tests discrimination-verified by reverting the argument to 0: the stamp test
fails Expected 10 / Actual 0, and the classifier test fails Expected
SetPositionSimple / Actual RejectedAuthority — the shipped defect reproduced
exactly.

Gates: complete Release solution 10,935 passed / 4 skipped / 0 failed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 23:48:09 +02:00
Erik
5db3de3c7a feat(runtime): execute initial placement continuations
The admission checkpoint (30012361) sealed accepted updates behind a
pending initial placement; nothing could apply them, so AcknowledgeAdoption
refused any non-empty FIFO and the residence system had no path to
completion. RuntimeInitialCreateContinuationExecutor is that missing
mechanism: a synchronous, retry-idempotent Execute transaction that adopts
the acknowledged initial placement exactly once (consuming the retained
completion so later authored placements for the key can begin), emits the
AfterEnterWorld hook request for the local player, replays deferred
missing-parent raw Creates and queued parent relations by parent GUID
(retail ProcessObjectNetBlobs order: whole-bucket detach, FIFO dispatch,
cancellation-aware restore), and drains the mixed continuation FIFO
strictly by sequence with retail route decisions taken at execution time
via ClassifyAcceptedPosition on live inputs (server-asserted wire contact,
data-driven animation proxy, live distance/options).

Apply bodies are shared with the legacy fused paths through new gate-less
instance seams on InboundPhysicsStateController that keep the one snapshot
store in lockstep; SameIncarnationCreate envelopes apply atomically with
per-stage idempotency and buffered publication after the final stage;
every abandonment path retires the residence through the lifetime choke
point and converges the ownership ledger (executor progress, deferred
buckets, replay windows, placement watches all folded into IsConverged).
Position/placement side effects are exactly-once under retry, external
mutations are detected via a field-masked executor baseline, and
AwaitingContinuationPlacement yields keep the FIFO head retryable.

Production routes are deliberately untouched: graphical and headless
Create still use legacy RegisterEntity, and no host calls Execute. The
cutover is the next checkpoint; AP-1/AD-1 remain open until it lands.
Register rows AD-59/AD-60/AP-130/AP-131/AP-132/TS-62/TS-63 document the
slice's deviations in this commit.

Reviewed: retail-conformance PASS + architecture/adversarial PASS after
five implementation rounds (wire-contact source, snapshot lockstep,
WeenieDescription merge, abandonment convergence, reentrant retirement
windows, acknowledged-completion leak, baseline precision, replay
containment/restore, queue-by-parent-GUID relation deferral all fixed at
root cause). Runtime tests 903/903; complete Release solution 10,696
passed / 4 intentional skips; focused executor gate 161/161.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-02 03:49:56 +02:00
Erik
f7442d13e9 refactor(runtime): move accepted entity wire state
Move the presentation-free inbound physics timestamp/snapshot authority and parent-relation state into AcDream.Runtime.Entities without changing their control flow. Move their dedicated tests with them and keep App consumers as borrowers during the staged J3 cutover.

Validated by 26 focused Runtime tests, 232 focused App tests, the Release solution build, and 8,429 complete Release tests with five existing skips.

Co-authored-by: Codex <codex@openai.com>
2026-07-25 19:52:29 +02:00
Renamed from tests/AcDream.App.Tests/World/InboundPhysicsStateControllerTests.cs (Browse further)