acdream/docs/research/2026-08-04-c4-route-4b-2-round3-correction.md
Erik 7f1c1f5aa6 feat(physics): C4 route 4b-2 — remote far snap through the canonical placement
Flips the SetPositionSimple classification (contact, PlayerDistance >= 96 m) for
remotes onto 4b-1's drive controller and deletes both legacy far blocks, both
duplicated 96f/4f constant pairs, and both `?? Vector3.Zero` fabrications. The
4 m constant now exists exactly once. Teleport and cell-less stay legacy for
4b-3.

Retail: MoveOrTeleport @0x00516330's far branch runs StopInterpolating
@0x005163CB before SetPositionSimple @0x005163D9 and returns 1 @0x005163E8
regardless — the SetPositionError is discarded — so HandleReceivedPosition arms
ConstrainTo @0x00454272 post-move on commit AND on failure. The x87 parity
decode at @0x00516393-@0x0051639E puts exactly 96.0 on the far branch.
SetPositionSimple @0x005162B0 builds flags 0x1012 at @0x005162C4.

Non-commit outcomes still advance the body, because retail's SetPositionInternal
@0x00515BD0 commits the destination via store_position @0x00515CE2 when no cell
resolves. The partition is by STAGE, not heuristic, enforced by an exhaustive
switch: Refused/Contention/NotApplicable/RejectedPreparation store (the placement
never executed); Committed/Deferred/RejectedByPlacement do not (the engine ran
and refused, matching retail's non-storing returns @0x00515CB2 and @0x00515CD5).
Without this a refused far snap froze the remote with an emptied queue.

Also fixes a shipped defect this route made live: ParkDeferred's quiescence parks
withdrew the entity (InWorld=false, clock suspended, residency removed) and were
never restorable, while Forget(restoreCancelledPark: true) runs for every
accepted Position on every entity. The restorable decision now lives inside
ParkDeferred AFTER SnapToCell, reading body.CellPosition.ObjCellId — the value
RestoreParkWithdrawal actually restores at — against every live quiescence
rather than one minimum-OperationId token. The three pre-snap fields are hoisted
into locals because SnapToCell ends with InWorld = true. ParkCollisionResidents
passes restorableOnCancel: false explicitly; the plain unplaceable park is
provably unchanged. RestoreParkWithdrawal re-tests the prefix at restore time so
a retained route-2 park cannot re-admit into a prefix that began quiescing
during the park.

CanAttemptDestination is retained as an OPTIMISATION only, with the two Core
predicates it cannot reproduce written down at the pre-flight, plus the two
properties that depend on it staying there.

Four fix rounds and eight Opus reviews. The slice was fully green at 10,990,
10,997 and 11,004 while containing real defects — a frozen remote pinned as
correct by its own test, a fallback that over-wrote on the exact retail paths
that decline to store, and a park guard incomplete on two independent axes.

Register: AP-137 (leftover classifications take AP-87's catch-up; states the
cell-less enqueue-vs-place delta deferred to 4b-3, that RejectedData is applied
anyway, and the headless divergence), AP-138 (the refusable far placement),
AP-136 narrowed to match the relocation. #309's acceptance steps rewritten —
step 5 previously asserted a recovery the code does not perform — and gated on a
new ACDREAM_PROBE_PARK=1 signal so the check cannot pass while broken.

Suite 11,009 passed / 4 skipped / 0 failed against a measured 10,968 baseline.
The 10,973 figure recorded earlier was wrong and is corrected here.

Connected gate outstanding: the two-client far-snap walk and #309.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 07:55:56 +02:00

12 KiB

C4 route 4b-2 — round 3 correction (2026-08-04)

Both round-2 reviews returned FAIL. The findings are now small and local; this is a closing-out round, not a structural change.

Do NOT revert the shared-core park change. Both reviewers verified it removes a session-permanent strand from routes 1 and 2: RuntimeEntityObjectLifetime.cs:1767-1769 calls Forget(canonical, restoreCancelledPark: true) on every accepted Position for every entity, so before this round a local-player ForcePosition or a first-entry placement that hit either quiescence park was left InWorld = false, clock suspended, FullCellId = 0, with its operation destroyed by the next packet. The change is directionally right and fixes shipped behaviour.

A1 — one relocation closes both shared-core MAJORs

IsRestorableQuiescencePark (RuntimeSetPositionState.cs:3877-3880) has two independent defects, and moving the decision fixes both:

  1. It compares against one quiescence, not the set. It tests quiescence.Token.LandblockPrefix — the token TryGetBlockingQuiescence returned — but both overloads (:3906-3925, :3927-3949) return the minimum-OperationId match, not the only one. _collisionPrefixQuiescence is a per-prefix dictionary (:589, :885) and streaming opens one per landblock mutation (activation RuntimePhysicsState.cs:1820-1824, retirement :2025-2030), so a recenter has several live. Source S quiescing before destination D: the predicate tests D != S, returns true, and the restore re-admits the spatial root into D, which is quiescing — the AP-136 pin. At 5-10 Hz the park→cancel→restore→re-park cycle can starve D's mutation permission for as long as S stays quiescing (:927).
  2. Its input is not the value the restore uses. It is fed deferred.CellId / held.CellId (:2982, :3036), but RestoreParkWithdrawal reads body.CellPosition.ObjCellId (:3481), set by ParkDeferred's body.SnapToCell (:4346-4349) → PhysicsBody.StageDormantCellFrame (PhysicsBody.cs:212-223), which for any outdoor cell runs LandDefs.AdjustToOutside. That function's own doc (LandDefs.cs:115-122) says the resulting cell id may belong to a neighbour landblock. Reachability NOT VERIFIED, and the relocation removes the question.

Fix: move the restorable decision inside ParkDeferred, after SnapToCell, and express it as !IsCollisionPrefixQuiescing(body.CellPosition.ObjCellId). That method (:827-829) already exists, is internal, masks its own argument, and is the pre-flight's own predicate; it subsumes the current test exactly. Make it non-static as needed; the CollisionPrefixQuiescence parameter then goes away.

Blast radius note that must not be lost: the remote far-snap arm is masked from defect 1 by CanAttemptDestination's destination pre-flight (RuntimeRemotePlacementDriveController.cs:599, :1155). The exposed callers are routes 1 and 2, which have no such pre-flight (RuntimeAcceptedPositionDriveController.TryExecuteAcceptedLocalPosition:330-392 goes straight from ClassifyForcePosition to TryBeginExclusiveAuthoredPlacement). All three new quiescence tests drive the remote arm and explicitly assert the safe configuration (:1193, :1268), so none of them can see it.

A2 — two tests the round is missing

  • Concurrent quiescences, source OperationId < destination OperationId, far snap S → D: assert the record is not re-admitted as a spatial root in D, or that D's mutation permission still converges.
  • A route-2 local-player ForcePosition parking at :2948 under a quiescing source: assert body.InWorld && record.ObjectClock.IsActive && record.FullCellId != 0 after the next packet's merge-time Forget.

A3 — say "blocking prefix", or make it true

IsRestorableQuiescencePark's doc (:3866-3868) and AP-136's narrowed row both claim "the rollback re-admits nothing into the retiring prefix." It re-admits nothing into the blocking prefix, which is a different statement. A1's fix makes the stronger claim true; if any wording survives it, scope it.

A4 — #309's acceptance steps are now stale

AP-136 says this needs the two-client connected check in #309, but docs/ISSUES.md is unmodified in this diff. #309's steps (docs/ISSUES.md:82-95) describe only "a landblock the observer has not streamed" — the plain unplaceable park. They do not exercise a quiescing source or a quiescing swept neighbour, which are exactly the shapes this round newly makes restorable, and step 4 asks only that route 2's corrections "still land unchanged" without naming the local-player-parks-into-a-quiescing-landblock case that is now behaviourally different. Deferring the run is fine; leaving its scope stale is not. Update the steps in this commit. Also AP-136's Risk column still says "a remote" — the local player now traverses the same restore.

A5 — record the dependency at CanAttemptDestination

N3's "a retained retry can no longer strand" property and MAJOR 1's masking both depend on the pre-flight staying in place. The class doc calls it "an optimisation" with no mention of either. If it is ever deleted as redundant, both re-open silently. Write that down at the pre-flight.

(The round's stated reason for declining N3's CurrentCellId re-check — that it would "re-derive a private Core predicate outside Core" — is false; CanAttemptDestination already calls Core's own internal IsCollisionPrefixQuiescing. The conclusion still holds, for the reason above. Correct the reason.)

B1 — a 36-line doc block was detached from its method

RuntimeRemotePlacementDriveController.cs:1009-1085. CountLivePending() was inserted between CountLiveAwaitingAcknowledgement's existing doc and its method. Now :1009-1044 (which describes pruning _awaitingAcknowledgement, the second ownership registration, and _awaitingAcknowledgementScratch) documents CountLivePending() at :1066 — which prunes _pending, backs the first registration, and uses _pendingScratch. Every specific is wrong for the member it now sits on. :1045-1065 is a second <summary> on the same member, and CountLiveAwaitingAcknowledgement() at :1085 has no doc.

B2 — the stage rule is true by coincidence and stated as a falsehood

:172-188 and :993-1005 assert that RejectedByPlacement's producers are "returned AFTER _physics.Engine.SetPosition ran". RuntimeSetPositionStatus.Rejected has three producers and only one is post-engine:

site what stage
:2854 entry validation (!ownsToken, stage mismatch, null body, stale IsPreparationAuthorityCurrent, velocity-version mismatch) pre-engine
:2945 !IsStructurallyValid(canonicalRequest) pre-engine
:3044 !result.IsSuccessful after the engine ran at :2997 post-engine

Both pre-engine sites reach SubmitAndResolve's default: and are filed RejectedByPlacementno store — contradicting the round's own rule (RejectedPreparation's doc :128-142) that a producer which never reached the engine stores.

The reviewer traced both unreachable today, but only by duplicated guards: PrepareMover (:1563-1577) re-checks everything :2830-2855 re-checks with nothing reentrant between; IsPreparationAuthorityCurrent (:5290-5306) catches all seven version dimensions one stage earlier as RejectedAuthorityRejectedPreparation; the AwaitingCell divergence needs DormantLocalActivation, which :1380 forbids for a record with a body; and :2945 is shadowed by PrepareMover:1636's identical check.

Either state in the default: comment that :3044 is the only reachable producer and why, or thread the stage out of Core so the split is enforced. As written the comment says something false about Core — the same shape as round 1's MAJOR A, which was also a false clause in that same comment.

B3 — the deleted phrase came back

RuntimeRemotePlacementDriveControllerTests.cs:1191, an added line: // The complete pre-flight passes: it reads only the destination. CanAttemptDestination's doc in the same diff (:1111-1113) says it is "correcting this comment's earlier 'the complete pre-flight' framing", and the whole of MAJOR B is that no pre-flight can be complete. The sentence also contradicts itself in eight words.

Minor

  • m1TryAdoptWireCellAfterRouting's new scope paragraph (LiveEntityNetworkUpdateController.cs:1128-1143) enumerates commit and RejectedByPlacement and "every outcome that resolved NO cell". Deferred is in neither: ParkDeferred:4345-4349 snaps to the parked result cell, WithdrawCanonical zeroes FullCellId, and RestoreParkWithdrawal:3485-3491 re-commits from body.CellPosition.ObjCellId — the swept cell for the post-sweep park, not necessarily the wire cell. Behaviour is unaffected (the suppression keys on the arm); the claim is not.
  • m2Contention's doc (:118-127) names two producers; there are at least four. TryBeginExclusiveAuthoredPlacement:1349-1354 also refuses on HasRetainedCompletion(key) — ordinary right after a Committed far snap whose projection sink declined — and BeginAcceptedPlacementCore:1404-1418 on !_entities.IsCurrent(record). Both pre-engine, so the store side is right.
  • m3 — the MAJOR D test (:1516-1552) reaches the superseded state via lifetime.Entities.RemoveActive(record) and picks Refused, which never calls CancelToken. It pins that the guard exists, not that it sits after the synchronous cancellation receipt — the defect's actual mechanism.
  • m4NotApplicable via record.Key is null (body non-null) stores (:576-581:800); the enum doc :97-101 justifies only the no-body sub-case. Unreachable in production (RuntimeEntityObjectLifetime.cs:600-612).
  • m5ParkCollisionResidents (:3689-3696) relies on restorableOnCancel defaulting false. The old blanket doc was itself the guard against a forgetful caller; now that it is gone, pass false explicitly.
  • n1:644 cites classifier :467 for StopInterpolating: !nearby; it is :468. Pre-existing.
  • n2ParkDeferred carries two adjacent <summary> blocks (:4292-4301, :4302-4325); only the second is emitted.
  • n3 — the Runtime test class doc (:30-33) asserts every test was revert-verified, with no per-test mapping in the tree. Either map them or drop the claim to what the file can show.

Do not churn — verified across both reviews

Retail decode of SetPositionInternal @0x00515BD0 (both storing and non-storing branches) is exact and matches the enum docs line-for-line. The 7-value switch is exhaustive with _ => throw, and no consumer outside the controller reads the value for a decision, so the Rejected split cannot silently miss a case. Deferred-not-storing is right, with a stronger unreachability proof than the comment gives (ParkCollisionResidents:3563-3568 throws rather than parking an entity that already holds an operation). MAJOR D's guard is the correct predicate (RuntimeEntityDirectory.cs:75-77), sufficient, and correctly placed after the receipt at both sites. N5 is genuinely unconstructible:917-923/:935-940 throw on binding a component whose body differs from canonical. Pose parity survives the remote.Bodyrecord.PhysicsBody change byte-for-byte. The neighbour test's construction is arithmetically verified (191.95 mod 24 = 23.95 > 24 - 0.1lx+10xB3), it provably hits the post-sweep park, and its Z arm discriminates (terrain 7, authored 6). N7's reframing is accurate. All six required tests discriminate; the cancelled-after-commit construction is genuinely clever. Route 2's re-issue funnel does not interact with the restore (it decides on PositionAuthorityVersion/_newestForce, none of which the restore moves). RestoreParkWithdrawal writes exactly five things and no sweep footprint, cross-cell registration, or BSP residency among them. No added line in the diff carries a source line-number citation — that failure mode is fixed.

Gate

Complete Release suite. Baseline 10,968 / 4 / 0 at 1b631f12. Round 2 measured 11,004 / 4 / 0 while containing everything above. Known flakes, do not chase or conflate: #302 (PortalProjectionTests…, App.Tests), #308 (NakEmissionTests.LossSoak_…, Core.Net.Tests).