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>
This commit is contained in:
Erik 2026-08-04 07:55:56 +02:00
parent 1b631f127d
commit 7f1c1f5aa6
24 changed files with 5234 additions and 375 deletions

View file

@ -84,9 +84,28 @@ route 4b-1 contract's "no connected gate" line does not apply to the park fix.
core behind EVERY production placement, and the merge-time
`restoreCancelledPark: true` is on the accepted-Position path every remote and
the local player traverse. So shipped behaviour changes for any entity whose
placement parks.
placement parks — including the local player, whose route-2 ForcePosition
corrections now roll a cancelled quiescence park back instead of leaving the
character withdrawn for the rest of the session.
Proposed connected check (two clients, local ACE). Scope widened 2026-08-04 at
C4 route 4b-2 round 3: the slice made `SubmitPreparedPlacementCore`'s two
collision-prefix QUIESCENCE parks restorable as well, which the original steps
(the plain unplaceable-destination park only) never exercised, and made the
LOCAL PLAYER traverse the same restore rather than only remotes.
**Run the whole gate with `ACDREAM_PROBE_PARK=1`** (added 2026-08-04, round 4).
Steps 4 and 5 both need a ForcePosition to land INSIDE a transient
collision-prefix quiescence window, which the tester cannot synchronise with a
teleport or portal arrival — so without a signal a clean teleport and a
correctly-parked one look identical and those steps pass while broken. The flag
emits one `[park]` line per park (guid, cause, the caller's pre-snap
`resultCell`, the POST-snap `restoreCell` the rollback would use, `eligible` =
the caller's half, `captured` = the final decision) and one `[park-restore]`
line per rollback (`residency` = whether canonical residency was re-taken).
Nothing is emitted for an ordinary committing placement, so an empty log means
the window was never entered — retry the step; do not record a pass.
Proposed connected check (two clients, local ACE):
1. Walk the observed character to a landblock boundary so a remote sits in a
landblock the observer has not streamed, forcing a `DeferredCell` park.
2. Confirm the remote no longer vanishes permanently — the pre-fix symptom was
@ -94,9 +113,45 @@ Proposed connected check (two clients, local ACE):
3. Confirm it appears at the SERVER-authoritative destination pose, not at a
stale pre-park pose, and that it becomes collidable once the landblock
publishes.
4. Confirm the local player's own ForcePosition corrections (route 2) still
land unchanged — that path shares the same cancel.
Steps 1-3 are the user-visible acceptance for AP-136's residual.
4. **Quiescing swept NEIGHBOUR (new).** Stand/run within about a metre of a
landblock seam while the neighbouring landblock across that seam is being
retired or republished by streaming (recentre by travelling, then provoke a
server ForcePosition — a `/teleport`-class correction or a portal arrival —
at the seam). The sweep footprint reaches the quiescing neighbour, so the
placement parks even though neither the source nor the destination is
quiescing. Confirm the LOCAL PLAYER is not left frozen/invisible after the
next server Position: it must stay in the world, keep simulating, and stay
collidable. **Performed only when the log shows BOTH** `[park] …
cause=quiescence:0x<neighbour-prefix> … eligible=True captured=True` **and a
later** `[park-restore] … residency=True` for the same guid. This is the
shape round 3 measured as reachable; the "quiescing source landblock" shape
is NOT reachable through either accepted-Position caller on a first submit,
because both commit the accepted wire cell to `record.FullCellId` before
submitting.
5. **Quiescing DESTINATION (new).** Provoke a ForcePosition into a landblock
that is mid-retirement. The park is deliberately NOT restored here (AP-136's
reason applies exactly): the character is left withdrawn — out of world,
object clock suspended, not a spatial root.
**Corrected 2026-08-04 (round 4).** The earlier text asked the tester to
confirm the player "recovers on the next server Position rather than staying
withdrawn indefinitely". It does not recover on that packet, and
`QuiescingDestinationPrefix_ForcePositionParkIsNotRestored` pins the
opposite: route 2 dispatches only for `ForcePosition`, so the ordinary
`Apply` that follows merges behind the drive and cancels the park WITHOUT
restoring it. Recovery needs a later packet that actually runs a placement
and commits — another accepted ForcePosition (correction, teleport, or
portal arrival) once that landblock's quiescence has released. Confirm
exactly that, and confirm streaming's retirement of that landblock still
COMPLETES rather than stalling; the retirement is the thing the
non-restorable park exists to protect. **Performed only when the log shows**
`[park] … cause=quiescence:0x<destination-prefix> … eligible=True
captured=False` (eligible-but-declined is the decision under test) **and no**
`[park-restore]` **line for that guid until the recovering ForcePosition.**
6. Confirm the local player's own ordinary ForcePosition corrections (route 2)
still land unchanged — that path shares the same cancel — including the
case where the correction lands in a landblock that is NOT quiescing, which
must be indistinguishable from pre-slice behaviour.
Steps 1-5 are the user-visible acceptance for AP-136's residual.
## #310 — Retained preparation retry stalls landblock retirement with no bound

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,232 @@
# C4 route 4b-2 — delta review FAIL (round 2 correction), 2026-08-04
Both delta Opus reviews returned **FAIL** on the fix round. Original slice is WIP
`dfd27896`; the fix round is uncommitted on top of it.
The fix round did real work and most of it is verified correct — see
"Do not churn" at the end. Two defects block, and one of them is worse than the
defect the round was fixing.
## MAJOR A — the `store_position` fallback fires on the two retail paths that decline to store
The round's load-bearing claim (`RuntimeRemotePlacementDriveController.cs:549-553`
and AP-138 part 1) is that retail's non-storing returns "are reached only after a
cell resolved and the transition ran — states this controller's non-commit
outcomes never represent, because they all mean the placement never executed."
**The second clause is false for `Rejected`.**
Retail, `CPhysicsObj::SetPositionInternal` @0x00515BD0:
- `CheckPositionInternal == 0` @0x00515C85`handle_all_collisions` @0x00515CC2
`return ((eax_14 - eax_14) & 2) + 2` @0x00515CD5**2 or 4**. No store.
- `sphere_path.curr_cell == 0` @0x00515C8F`return 3` @0x00515CB2. No store.
acdream ports that enum literally — `PhysicsSetPosition.cs:12-20`
(`NoValidPosition = 2, NoCell = 3, Collided = 4`, doc-commented against
`acclient.h` enum 491). The producing chain:
`PhysicsEngine.cs:1548-1575` / `:1580-1584``RuntimeSetPositionState.cs:3034-3039`
(`if (!result.IsSuccessful) return Outcome(Rejected, …)`), which is **after**
`_physics.Engine.SetPosition(...)` at `:2983``RuntimeRemotePlacementDriveController.cs:775-780`
`default:``Rejected``:578-579` stores.
So a far snap whose destination the engine's own sweep refuses — a remote
server-snapped into geometry, or into a cell the sphere path cannot resolve —
now teleports the canonical body into that refused destination. Retail leaves the
object where it was. Reachable in the ordinary case: `CanAttemptDestination`
passing means collision *is* published, which is exactly when the engine runs.
**Worse sub-case:** `RuntimeSetPositionState.cs:3057-3081` returns `Cancelled`
**after `CommitCanonical` succeeded** ("retail lets that physical commit land
regardless"). `Cancelled` also falls into `default:``Rejected` → the fallback
overwrites the just-settled body (contact plane, step-down) with the raw
unresolved destination. `RemoteMotion.Body` **is** `record.PhysicsBody` in
production (`RuntimePhysicsState.cs:1039`, sole construction site).
**Zero of the five new non-commit tests cover `Rejected`.** The 10-row
discrimination table has no row for the one outcome where the fallback is wrong.
### The required partition
- **Store** (placement genuinely never executed → retail @0x00515C1D
`store_position`): `Refused`, `Contention`, `NotApplicable`, **and** `Rejected`
arising from a *preparation* failure (`InvalidData`/`RejectedAuthority`,
`:770-773`) which never reached the engine.
- **Do not store** (engine ran and refused → retail @0x00515CB2 / @0x00515CD5):
`Rejected` arising from `outcome.Status`, and `Cancelled`-after-commit, which
must not overwrite a settled pose.
**The status enum is too coarse to express this.** Widen it or thread the
originating outcome; do not approximate it with a heuristic.
## MAJOR B — the quiescence pre-flight is incomplete on BOTH sides
Both reviewers found this independently by different routes. `CanAttemptDestination`
(`RuntimeRemotePlacementDriveController.cs:911-915`) tests one prefix, and
`IsCollisionPrefixQuiescing` (`RuntimeSetPositionState.cs:827-829`) masks to that
one prefix, called with the **destination** only.
Core's predicates are broader in two independent ways:
1. **Source landblock**`PlacementTouchesPrefix` (`:3836-3841`) also matches
`request.CurrentCellId`, populated from `operation.Record.FullCellId` whenever
`body.InWorld` (`:2884-2888`). A far snap *out of* a quiescing landblock passes
the pre-flight and parks at `:2946`/`:2974`. **This is the likelier shape**
streaming retires and republishes continuously, and a remote at >=96 m sits
near the window edge where retirement happens.
2. **Swept neighbour**`ResultTouchesPrefix` (`:3843-3858`) scans **every**
`QueriedCellIds` entry. That footprint provably spans neighbour landblocks:
`PhysicsEngine.cs:1347` binds it to the transition's candidate array,
materialized at `:1384`; `CellArray.Add` mirrors every id
(`CellArray.cs:43-52`); `CellTransit.FindCellSet` (`TransitionTypes.cs:3521-3527`)
reaches `AddAllOutsideCells` (`CellTransit.cs:918`, `:985`); and
`AddOutsideCell` (`CellTransit.cs:375-384`) states explicitly there is **no
same-block filter** — neighbour cells come out with the neighbour's prefix,
added whenever the sphere is within its radius of a boundary
(`AddAllOutsideCells:337-350`).
Either route reaches `ParkDeferred` with `restorableOnCancel` defaulting **false**
(`:4276`), so `CancelToken`'s `restoreCancelledPark: true` finds
`ParkWithdrawal.Captured` false (`:3399-3410`, `:4285`) and `RestoreParkWithdrawal`
never runs. The remote is left `InWorld = false`, `Active` cleared, clock
suspended, `FullCellId = 0`, spatial projection withdrawn (`:4294-4311`), with the
only operation that could wake it destroyed. The fallback pose write does not
help — it writes a pose to a withdrawn body, which merely makes the stranding
invisible to a position assertion.
**A pre-flight cannot close this.** The sweep footprint does not exist until the
sweep has run. This is the same structural conclusion 4b-1's review reached, and
`SubmitAndResolve`'s own comment at `:768-770` cites `TryGetBlockingQuiescence` by
name as the residual — recording the hole while the code claims to close it.
### MAJOR C — even a would-COMMIT placement is converted to a non-restorable park
`:3012`'s check is `result.IsSuccessful && TryGetBlockingQuiescence(...)`, and
`IsSuccessful` is `Error == Ok` (`PhysicsSetPosition.cs:151`) — true for a fully
committed result. It sits **ahead** of the restorable `result.IsDeferred` park at
`:3050-3054`. So a healthy, resident, about-to-commit far snap near a seam is
rewritten to `DeferredCell` at `:3017-3020` and parked non-restorably. The claim
that "the restorable engine-result park stays reachable" holds only when no
quiescing prefix is touched; the quiescence branch pre-empts it.
### The pinned fix — at the source, as the campaign already decided
AP-136's blanket "every quiescence/retirement park is non-restorable" is
**over-broad for the two `SubmitPreparedPlacementCore` parks**. Its stated reason
— re-admitting a spatial root into a retiring prefix would block the retirement —
is about `ParkCollisionResidents` (`:3683`), where the entity's **own** cell is
retiring and `IsAffectedCollisionResident` (`:3722-3738`) /
`HasOldPrefixPlacementDebt` (`:3809-3834`) would pin the prefix forever.
It does **not** hold at `:2974`/`:3025`: `RestoreParkWithdrawal` restores residency
at `body.CellPosition.ObjCellId` (`:3475`) — the destination cell — while the
blocking prefix is the merely-swept neighbour or the departed source. Restoring
there re-admits nothing into the retiring prefix.
**Make `:2974`/`:3025` restorable when the restore cell's prefix differs from the
blocking quiescence prefix.** Keep `CanAttemptDestination` as an optimisation —
it uses Core's own read-only predicate, is exact within its subset, adds no
timer/retry/flag, and strictly shrinks the reachable set. It must simply stop
being the correctness mechanism. Update AP-136 and AP-138(2) to match.
## MAJOR D — `StoreAcceptedDestinationPose` writes through a possibly-superseded incarnation
`CancelToken` publishes its receipt **synchronously**
(`RuntimeRemotePlacementDriveController.cs:925-930`), and this same diff's own doc
(`LiveEntityNetworkUpdateController.cs:1005-1018`) states a caller "MUST
re-validate position ownership … on EVERY placement status, before writing
anything else for the packet". `:576-580` calls `StoreAcceptedDestinationPose`
immediately after that dispatch with **no currency check**. `RestoreParkWithdrawal`
does guard (`RuntimeSetPositionState.cs:3468-3469`). The App-side re-validation
happens only after the seam returns — too late.
This is the exact rule the R5 fix in this same diff introduced. One
`IsCurrent(record)` test closes it.
## MINOR
- **N1 — `Advance()`'s window-drop path is asymmetric.** `:692-696` calls
`CancelToken` without `StoreAcceptedDestinationPose`, so a retained retry whose
destination leaves the window reproduces a smaller version of the freeze this
round exists to fix. Its comment at `:685` still claims the entry point "keeps
its last committed pose" — no longer true.
- **N2 — post-sweep quiescence park overwrites a settled pose.** At `:3017` the
parked `result` is the committed one, whose `Position` is `spherePath.CurPos`
(`PhysicsEngine.cs:1605`) — collision-settled. `ParkDeferred:4290` snaps there;
`StoreAcceptedDestinationPose:627-630` then writes the raw destination over it.
Latent while MAJOR B stands; live the moment the park becomes restorable.
- **N3 — `Advance()` reopens the `CurrentCellId` condition.** `:690-702` re-checks
only `CanAttemptDestination(destination)`. Non-Position rebucket paths
(`RemoteTeleportController.cs:532`, `DatLiveEntityProjectionMaterializer.cs:777`,
`EquippedChildRenderController.cs:408`) can move `record.FullCellId` to a third,
quiescing landblock in between.
- **N4 — five stale comments, inside the round meant to end the disease.**
`:777-778` ("Rejected/Cancelled — … so the body never moved") is the one whose
correctness would have exposed MAJOR A. Also `:685` (N1), `:91-93`
(`Rejected`'s doc omits the engine-refusal producer), `:2144-2151` (doesn't
mention the guard now in front of it), `:2183-2187` (provenance claim fine,
value claim false). And `LiveEntityNetworkUpdateController.cs:1078-1079` cites
`:1886`/`:2130` where the actual early returns are `:1977`/`:2241` — wrong
citations *in the fix for the stale-comment finding*.
- **N5 — `NotApplicable` via `record.PhysicsBody is null`** (`:436`) still runs the
fallback, writing a `RemoteMotion` body that in that state is not the canonical
one (`RemoteMotion.cs:271`).
- **N6 — `teleport_hook` enumeration incomplete.** @0x00514ED0 also calls
`report_collision_end(this, 1)` @0x00514F31, omitted from
`RuntimeRemoteFarSnapPosition.cs:454-457` and AP-137.
- **N7 — a test assertion is order-dependent.**
`RemotePlacementLedger_ConvergesAcrossGuidReuse_WithoutTeardown:708-711`
`Assert.Equal(0, drive.PendingCount)` is zero only because the preceding
`CaptureOwnership()` invoked `CountLivePending`, which **mutates** `_pending`.
Swap the asserts and it fails. Also omits `AssertConverged`.
## Required tests
- `Rejected` from an engine refusal: assert the body does **not** move.
- `Cancelled`-after-commit: assert the settled pose survives.
- `Rejected` from a preparation failure: assert the body **does** advance.
- Quiescing **source** landblock: far-snap out of it, assert
`body.InWorld && record.ObjectClock.IsActive && record.FullCellId != 0`.
- Quiescing **neighbour**: far-snap to a point one sphere radius inside a
landblock seam with the neighbour quiescing, same assertions.
- `Advance()`'s window-drop path: assert the pose advances.
## Do not churn — independently verified correct
- The retail decode of `SetPositionInternal`'s storing branch (@0x00515CDA
@0x00515CE2@0x00515CF2@0x00515CF7`return 0` @0x00515D07). Exact.
- **R2's deferral to 4b-3 is the right call**, and both reviewers agree.
`teleport_hook` @0x00514ED0 runs *before* the placement @0x00516420, so a
pose-only half-port would strand a live moveto, stick, and leash.
`RuntimeTeleportHookPhase.BeforePositionOperation` exists
(`RuntimeAuthoritativePositionRouteClassifier.cs:410`) and is recorded-and-dropped
for the remote arm — a representable delta with the data model already present.
- **The carried `SEND_POSITION_EVENT_SPF = 0x1000` question is SETTLED: the flag
is inert.** @0x00515330 takes `(CPhysicsObj*, CTransition const*)` — no struct;
@0x00515BD0 reads only bit 5; @0x00516040 tests only bits 8/9; a tree-wide
search finds no bit-12 test on a `SetPositionStruct`.
- **Pose parity and the decoy.** `TryGetWorldFrameOffset` is the source of
`ShadowWorldOffsetX/Y`; `StoreAcceptedDestinationPose:627-635` composes
identically to `RuntimeSetPositionMoverPreparer.TryBuild`
(`RuntimeSetPositionMoverPreparation.cs:155-168`). `worldPos` is **not** an
input — the decoy retains its discriminating power.
- The status is genuinely no longer discarded; both arms thread
`RemoteContactRouting(Arm, Placement)`.
- Register bookkeeping: `grep -c "^| AP-"` = 96, header updated, no duplicates,
nothing retired kept a row.
- R5's ordering is identical in both arms, and its "not constructible" claim
holds — though it is the standard signal the guard belongs behind a testable
Runtime seam rather than duplicated at two App call sites. Name that for 4b-3.
- The `StopInterpolating` "unobservable" claim is **true**:
`TryExecuteAcceptedRemotePosition` never receives `remote`, and no placement or
projection path reads or writes the queue. Stating it beats a false pin.
- R7's `Assert.True(remote.Interp.IsActive)` genuinely closes its hole. R8's two
park tests provoke their states honestly.
- AP-136's scoping was read correctly and was not retrofitted (untouched in the
diff).
## Gate
Complete Release suite. **Baseline 10,968 / 4 / 0** at `1b631f12` (App 4088,
Runtime 1073), measured. The fix round measured 10,997 / 4 / 0 while containing
every defect above. Known flakes: **#302** (`PortalProjectionTests…`, App.Tests)
and **#308** (`NakEmissionTests.LossSoak_…`, Core.Net.Tests). Do not conflate.

View file

@ -0,0 +1,206 @@
# C4 route 4b-2 — dual review FAIL + the pinned correction (2026-08-04)
Both mandated Opus reviews returned **FAIL**. Nothing is committed as final; the
work sits at WIP `dfd27896`.
Review A (retail faithfulness + deletions) confirmed **every retail citation**
independently, including the x87 parity decode proving exactly 96.0 takes the
FAR branch. The deletions are complete and exact, the "Do NOT touch" list was
respected, AP-87 was neither carried onto the far branch nor deleted from the
near branch, and the two-site `ConstrainTo` partition is exhaustive and disjoint.
Review B confirmed the `worldPos` decoy genuinely discriminates, both test
removals are real replacements rather than drops, and `OwnsFarSnap` is exact
against every classifier emission site. **Do not churn any of that.**
## The one root defect — everything else is downstream
**A refused / contended / rejected far snap leaves the remote frozen with an
emptied interpolation queue.**
`ApplyRemoteContactRouting` discards the status
(`LiveEntityNetworkUpdateController.cs:1002`, `_ = placementDrive.…`), while
`Interp.Clear()` has already run unconditionally on the route flag
(`RuntimeRemotePlacementDriveController.cs:485-487`). Three of five outcomes
move nothing: `Refused` (`:405`, `:624`), `Contention` (`:414`, `:588`),
`Rejected` (`:592`, `:630`). The body keeps its stale pose, the queue is empty,
and the next 5-10 Hz packet reproduces the state.
**Reachable, not theoretical.** The graphical service window is
`GpuWorldState.IsNearTier` — collision-published *right now*
(`GraphicalRemotePlacementServiceWindow.cs:89-90`) — with default near radius 4
(`GameWindow.cs:145`). A remote that is rendered but still streaming is refused
while genuinely beyond 96 m. `RetrySetupUnavailable` is reachable for any remote
whose prepared Setup collision has not resolved.
**Retail never does this.** The far branch always calls `SetPositionSimple`
@0x005163D9, and even when `SetPositionInternal` finds no cell it still commits
the destination pose — `store_position` @0x00515CE2, then
`GotoLostCell` @0x00515CF2 and `reenter_visibility` on cell arrival. That is the
finding AP-136 is built on. The deleted legacy block also always tracked. **The
shipped state is strictly further from retail than either.**
### Both reviewers' second MAJOR collapses into this one
Review B rated the `TryAdoptWireCellAfterRouting` arm-asymmetry MAJOR, arguing a
refused player far snap leaves `record.FullCellId` a landblock away from the
body and that `rmState.CellId` seeds the per-tick sweep — the #184
invisible-but-solid producer.
Review A found the same asymmetry and rated it MINOR with the mechanism:
`RebucketLiveEntity(update.Guid, p.LandblockId)` at `:1592` has **already**
committed the wire full cell to canonical
(`LiveEntityRuntime.cs:895-909`), and `RemoteMotion.CellId` reads through to
`record.FullCellId` for every bound remote (`RemoteMotion.cs:169-176` +
`RuntimePhysicsState.cs:965-984`). **Review A is right on the mechanism.** The
suppressed write is a no-op on refusal.
**Resolution: the cell/body divergence is real but is caused by the frozen body,
not by the suppression.** Fix the freeze and it disappears. Do not restructure
the suppression to chase it.
## The correction — pinned, not open for redesign
**On every non-commit outcome the far arm must still advance the body to the
accepted destination pose.** That is retail's `store_position`, and it restores
the tracking the deleted legacy block had.
Constraints on the fix:
- The status must stop being discarded at `:1002`.
- The service window stays an **optimisation**, not a correctness mechanism —
4b-1's review already ordered that framing corrected and it shipped
uncorrected. Do not delete it; do not let it be the reason a remote stops
tracking.
- Do **not** add a timer, retry, settle window, or suppression flag. The
fallback is a pose write because retail writes the pose, not because it makes
a symptom go away.
- `LiveEntityNetworkRemoteFarSnapIntegrationTests.cs:120-147` currently
**pins the freeze as correct** (`Assert.Equal(before, body.Position)`). It
must be inverted, not deleted quietly.
## Also required
**R1 — AP-137's justification is factually wrong (review A M2).** The row claims
the deleted `_playerController?.Position ?? Vector3.Zero` distance had "no
relationship to `player_distance`". It was streaming-origin-relative
(`LiveEntityNetworkUpdateController.cs:1360-1366`) and the streaming origin
recentres on the player's landblock — a biased but genuinely correlated proxy,
error bounded by roughly one landblock. The policy may still be right; the
stated reason is not true. Rewrite it to say what the deleted test actually
computed.
**R2 — AP-137 must state the cell-less delta (review A M3).** Retail routes a
cell-less body through @0x00516386`SetPosition` @0x00516420, an
**unconditional placement** sitting *before* the contact test. 4b-2 routes that
classification into `ApplyInterpolate`, which **enqueues** whenever
`!firstUp && willBeDrTicked && bodyToTarget <= 4 m`. For a cell-less remote
already tracking, acdream now queues where retail places — at *any* distance,
not only >=96 m. State it. If it should instead place, say so and change it.
**R3 — AP-137 must state that `RejectedData` is applied anyway.** It is the one
classification meaning "this payload failed validation"
(`classifier.cs:535-547`), and `UnroutedCatchUp` hands the same payload to
`ApplyInterpolate`. Not a regression — the legacy block did the same — but the
slice's stated purpose was an explicit handler, and the row omits it.
**R4 — a register row for the refusable far placement.** `grep` for
"Refused|service window" in the register returns zero. 4b-1 was dormant and owed
nothing; **4b-2 is the commit that makes it live and therefore owes the row**
(CLAUDE.md register rule 1). Whatever residual divergence survives the fix above
gets measured against retail's `store_position`/`GotoLostCell` behaviour.
**R5 — the two arms' guard/arm ordering must be identical (review A M4).**
| arm | routing | re-entrancy guard | post-op arm |
|---|---|---|---|
| player | `:2020` | `:2047-2054` (`return`) | `:2037`**before** the guard |
| NPC | `:2202` | `:2214-2221` (`return`) | `:2235`**after** the guard |
The NPC arm therefore arms `ConstrainTo` **zero** times on a superseded
incarnation, where retail arms unconditionally on the nonzero return
@0x00454254/@0x00454272. The player arm arms the leash on a possibly-superseded
`rmState.Host`. This also contradicts the diff's own new remarks at `:958-966`
("MUST re-validate … before writing anything else for the packet") — the player
arm writes the leash first.
**R6 — six comments the change falsifies** (review A m6; review B found the same
class independently as N6). This is the **fifth** consecutive slice shipping
stale comments asserting behaviour the code no longer has:
`LiveEntityNetworkUpdateController.cs:835`, `:868`, `:1589-1591`, `:2076-2081`;
`RuntimeEntityObjectLifetime.cs:600-605`; and
`RuntimeRemotePlacementDriveController.cs:106-143`, which still headlines "The
central decision — refuse, do not park" and asserts a cancelled park leaves the
entity invisible and intangible for the session — falsified at HEAD by the park
restore **this same class's** `CancelToken` uses (`:698-703`). 4b-1's review
already ordered that one corrected.
**R7 — three tests assert less than their names claim (review B N2/N3).**
- `NoClassificationAtAll_NearAndTicked_EnqueuesInsteadOfSnapping`
(`:191-213`) never inspects `remote.Interp`. A regression to "snap when
`bodyToTarget > 4 m`, else do nothing" passes it *and* its sibling. One
`Assert.True(remote.Interp.IsActive)` closes it.
- The `StopInterpolating` ordering test (`RuntimeRemotePlacementDriveControllerTests.cs:830-871`)
proves the clear is unconditional, not that it precedes the placement. Its doc
comment claims proof it does not have. Either pin the order or state plainly
that it is unobservable in acdream.
**R8 — the `DeferredCell` park branch has zero coverage (review B N5).** The
opt-in chain is correct by reading (`:623``:698-703`
`RuntimeSetPositionState.CancelCore``RestoreParkWithdrawal`), but no test in
4b-1 or 4b-2 exercises it, and the far arm is the first production path that can
provoke a park. Add the test: commit the destination collision generation, open
a `CollisionPrefixQuiescence` on the destination prefix, far-snap, then assert
`body.InWorld && record.ObjectClock.IsActive && record.FullCellId != 0`.
**R9 — currency across GUID reuse / incarnation / generation is undelivered
(review B N4).** Contract item 7 named five dimensions; two shipped
(interleaving, teardown). `_pending` and `_awaitingAcknowledgement` are keyed by
`RuntimeEntityKey` (guid+incarnation), so a reused GUID makes a *new* key and the
old entry self-heals only on a ledger read (`:670-688`) or in `Advance()`
(`:513-525`), which early-returns unless something is pending. Dead-incarnation
entries accumulate until `DetachRoute`. Converges at teardown; does **not**
converge in-session. Add the test: far-snap `G` at incarnation 1, retire it,
re-create `G` at incarnation 2, assert `RemotePlacementDrivePendingCount == 0`
**without** a teardown.
**R10 — `AirborneNoOperation` falls into `default:`** (review A m7). Unreachable
today — both callers early-return (`:1876-1881`, `:2130-2136`) — but the
`default:` comment asserts unreachability where an explicit case enforces it.
## Not blocking, recorded
- **n8**: `OwnsFarSnap` also matches the classifier's FORCE_POSITION shape
(`classifier.cs:329-345`), unreachable for remotes via `ValidAcceptedAuthority`
(`:522-525`) and `PhysicsTimestampGate` (`:190`). Defense-in-depth; note only.
- **NOT VERIFIED, carried**: what `SetPositionInternal` @0x00515330 does with the
0x1000 (`SendPositionEvent`) bit, which reaches
`TryPrepareAndSubmitAuthoredPlacement` for the far snap while the controller
doc argues acdream has "no ack" for remotes. Those are two different things
(the `SetPositionStruct` flag vs `CommandInterpreter::SendPositionEvent`
@0x00454091); 4b-1's review cleared only the latter. Settle with a read of
@0x00515330-@0x00515543.
- **Process**: `ParkCollisionResidents_StaysUnreachable_AfterRefusalAndAfterCommit`
(`RuntimeRemotePlacementDriveControllerTests.cs:782`) still calls the method
directly at `:824` — the shape 4b-1's finding B2 declared void. It shipped at
`2e8e09ac`, so it is outside this diff, but it is in the tree being counted as
evidence.
- **Headless (contract item 6) is satisfied vacuously.** Nothing in headless
constructs the drive controller and `RuntimeLiveEntitySessionController.OnPositionUpdated`
returns early for every non-local GUID (`:212-217`). The contract allowed this
only if stated plainly; AP-137 does not mention headless, and
`IRuntimeRemotePlacementServiceWindow`'s doc still describes the headless
implementation as a live consumer. State it.
## Gate
Complete Release suite, not a subset. **Corrected baseline: 10,968 / 4 / 0** at
`1b631f12`, measured independently in a throwaway worktree (App 4088, Runtime
1073). The 10,973 figure in the 4b-2 contract was wrong and propagated from an
earlier mis-record — an inflated baseline is exactly what would let a future
slice delete tests and still look green. The WIP state measured 10,990 / 4 / 0
while containing the freeze defect above.
Two known flakes, do not chase and do NOT conflate: **#302**
(`PortalProjectionTests…`, GC-allocation assertion, App.Tests) and **#308**
(`NakEmissionTests.LossSoak_…`, wall-clock deadline, Core.Net.Tests, full-suite
load only).

View file

@ -0,0 +1,211 @@
# 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
`RejectedByPlacement`**no 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 `RejectedAuthority`
`RejectedPreparation`; 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
- **m1**`TryAdoptWireCellAfterRouting`'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.
- **m2**`Contention`'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.
- **m4**`NotApplicable` 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`).
- **m5**`ParkCollisionResidents` (`: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.
- **n2**`ParkDeferred` 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.Body``record.PhysicsBody` change
byte-for-byte. The neighbour test's construction is arithmetically verified
(`191.95 mod 24 = 23.95 > 24 - 0.1``lx+1``0xB3`), 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).

View file

@ -0,0 +1,174 @@
# C4 route 4b-2 — round 4 correction (final), 2026-08-04
Both round-3 reviews returned **FAIL**, and both bound the failure to the same
place. **No shared-core edit is warranted.** Review A verified the relocation
line by line and states plainly: the code is correct.
Round 4 is one test, one deleted sentence, two `docs/ISSUES.md` fixes, and a
handful of doc corrections.
## The MAJOR — both reviewers, independently
**Nothing in the tree discriminates round 3's predicate from round 2's**, and a
test doc cites a sibling test that does not exist.
`RuntimeRemotePlacementDriveControllerTests.cs:1343` names
`ConcurrentQuiescences_ParkIsNotRestoredIntoTheQuiescingDestination`. That
identifier occurs exactly twice in the tree, both inside that one comment.
Review A evaluated round 2's form
(`blockingToken.LandblockPrefix != (result.CellId & 0xFFFF0000)`) against all six
quiescence tests. **Every row agrees with round 3.** Revert the relocation to the
single-token comparison and the suite is still green at 11,007. So A1's two
stated defects — comparing one token instead of the live set, and reading the
pre-snap cell instead of the post-snap one — have no automated floor under them
at all, while the tree asserts otherwise.
**Write the test.** Both reviewers identified the same and only discriminating
construction, in `RuntimeAcceptedPositionDriveControllerTests` (route 2 has no
pre-flight, which is what makes it the caller that can reach the shape):
- two live quiescences, source `OperationId` < destination `OperationId`
- park at `:2948` under the source
- assert `!IsSpatialRoot(record)` after the next packet's merge-time `Forget`
It fails under the round-2 form (`Source != Dest` → restorable → re-admitted into
the retiring destination) and passes under round 3. Then delete or correct the
`:1343` cross-reference.
## #309 — the gate currently passes while broken
**S1.** Step 5 (`docs/ISSUES.md:115-119`) says the player "recovers on the next
server Position rather than staying withdrawn indefinitely".
`QuiescingDestinationPrefix_ForcePositionParkIsNotRestored` (`:906-912`) asserts
the opposite for that packet — `InWorld == false`, clock inactive,
`!IsSpatialRoot`. Route 2's drive dispatches **only** for `ForcePosition`; an
ordinary `Apply` merges behind it (`RuntimeAcceptedPositionDriveController.cs:337`).
So the packet the step names is precisely the one that does not recover it. Name
what actually does — a later ForcePosition, or the quiescence releasing plus a
placement.
**S2.** Steps 4 and 5 both need a ForcePosition to land *inside* a transient
collision-prefix quiescence window, which the user cannot synchronise with a
teleport or portal arrival, and neither step names a signal confirming the park
fired. A tester sees a clean teleport and records a pass. **Give each step a
stated confirmation signal** — a probe line, or a documented
`CaptureOwnership().DeferredCellCount` check. If a deterministic way to hold a
prefix quiescing for the duration is cheap, prefer it.
Step 5's retirement-still-completes clause is good; steps 1-3 and 6 are
performable as written.
## Doc corrections
- **D1** — AP-136 says restore covers the plain unplaceable park
"**unconditionally**". It does not: `ParkDeferred` now gates *every* park,
including `:3063`, on `!IsCollisionPrefixQuiescing(body.CellPosition.ObjCellId)`
(`:4358-4360`). The same row states it correctly two sentences later — the row
contradicts itself.
- **D2** — AP-136's trailing parenthetical still summarises #309 as the original
three steps plus "route 2's corrections are unchanged". This commit rewrote
#309. The row's body says the scope widened; its own summary does not.
- **D3**`RuntimeRemotePlacementDriveController.cs:1064-1066` attributes the
null-body re-test to `PrepareMover`'s guard clause
(`RuntimeSetPositionState.cs:1563-1577`), which has **no** body test. The real
dependency is `RuntimeRemotePlacementDriveController.cs:625`
(`record.PhysicsBody is null``NotApplicable`). Conclusion holds; the
attribution points a maintainer at the wrong guard.
- **D4 — B2's chosen value was not delivered.** Choosing "state it" over "enforce
it" is sound and both reviewers concur with the decision — but **none** of the
four dependencies is annotated at its own site: `PrepareMover`'s guard
(`:1563-1577`), `IsPreparationAuthorityCurrent` (`:5304-5320`),
`PrepareDormantLocalActivationOwnership`'s `record.PhysicsBody is not null`
(`:1380`), `TryBeginExclusiveAuthoredPlacement`'s existing-operation refusal
(`:1349-1351`). Deleting any one is invisible from the site, which is the whole
thing "state it" was supposed to buy. Four one-line back-references close it.
- **D5 — scope the `CurrentCellId` claim to the first submit.** `ParkDeferred`'s
doc (`:4300-4307`, `:4320-4325`), AP-136 and AP-138(2) all say the pre-engine
arm "names the destination" in production. True on the first submit, **false
for a retained retry**: `Advance()` re-reads `record.FullCellId` at submit with
no fresh merge (`RuntimeRemotePlacementDriveController.cs:941-954`,
`RuntimeAcceptedPositionDriveController.cs:663-667`), and
`RemoteTeleportController.cs:532` is a shipped writer that moves it to the
pre-teleport landblock. `CanAttemptDestination`'s own doc (`:1234-1241`) already
says this correctly — the three others contradict it. Otherwise the next reader
concludes the `CurrentCellId` arm is dead code.
Same family: `:1252-1253` ("every shape this predicate cannot see is a
merely-swept NEIGHBOUR") is contradicted by the third-landblock shape named ten
lines above it.
## D6 — make the `Captured` claim exact rather than caveated
`Captured` is a **park-time** snapshot. For the far snap the window is
nanoseconds (`SubmitAndResolve`'s `DeferredCell` arm cancels synchronously,
`:1038`). For **route 2** the park is *retained*
(`RuntimeAcceptedPositionDriveController.cs:745-753`, `AwaitingCommitWake: true`)
and the restore lands on the next packet's merge-time `Forget`, ~150 ms later.
Streaming opens a quiescence per landblock mutation, so a prefix clean at park
time can be quiescing at restore time, and `RestoreParkWithdrawal` re-admits into
it unconditionally.
It self-heals and is no worse than the baseline, so it is a residual rather than
a regression — but **add the re-test**: one `IsCollisionPrefixQuiescing(residentCellId)`
condition at `RuntimeSetPositionState.cs:3488`, in the method that already reads
that variable. That makes the docs' claim true at the moment it matters instead
of requiring a "when the park was taken" caveat everywhere it appears. Prefer the
exact claim over the caveat.
## NIT
- **N1**`IsCollisionPrefixQuiescing(0)` re-opens the C3c-F3 sentinel
conflation. `AdjustToOutside` zeroes the cell id on map-edge failure
(`LandDefs.cs:139`), and prefix `0x00000000` is the *legitimate* corner
landblock (0,0) per `BeginCollisionPrefixQuiescence:840-846`, not "no
landblock". Review A could not construct a reachable path, but this file
already carries a shipped-crash comment about exactly this conflation. Add the
`!= 0u` guard.
- **N2**`FarSnap_ConcurrentQuiescences_RefusesBeforeOpeningAPark` is
behaviourally redundant with the destination-prefix test (both fail identically
if the pre-flight's `IsCollisionPrefixQuiescing` half is removed), and its
`sourceQuiescence.OperationId < destinationQuiescence.OperationId` assert never
influences an observed outcome. Keep it, but its doc should not imply stronger
A5 evidence than it carries.
- **N3**`RuntimeAcceptedPositionExecutionStatus.DeferredCell`'s doc ("the
destination landblock's collision generation was not ready") is the status two
new route-2 tests assert for a *quiescence* park. Pre-existing, in a file this
diff does not touch — the next stale comment in line. Fix it or file it.
## Do not churn — verified across both reviews
The A1 relocation is mechanically correct: `IsCollisionPrefixQuiescing` consults
the full map (`:827-829`; one live entry per prefix via Remove-then-Add
`:857`/`:885`); the post-snap cell is exactly what `RestoreParkWithdrawal:3484`
restores at, with no writer in between; `SnapToCell` really does set
`InWorld = true` (`PhysicsBody.cs:201-205`) so the three-local hoist is required,
not stylistic; `RetryDeferred` re-parks without a second `SnapToCell`, keeping
the decision bound to the cell it was taken against; the plain park at `:3063` is
**provably** unchanged (the `:3017` quiescence check already returned false, and
`AdjustToOutside` is idempotent on an adjusted pair); `ParkCollisionResidents`
passes `false` explicitly; no prefix pin or streaming stall is possible
(`HasOldPrefixPlacementDebt:3829` skips `WakeableLostCell`, and the operation is
retired before the restore). All four `ParkDeferred` call sites accounted for.
**The pushback is a legitimate correction**, traced independently through
`TryApplyPosition:1771-1774``RefreshDerivedState:230-237``SetFullCell`, and
the remote leg through `RebucketLiveEntity``CommitRebucket:1816`. Route 1 and
remote creates cannot reach the source shape at all (`CurrentCellId` requires
`body.InWorld`, false for a first-entry body).
Both route-2 tests drive production (`MergeAccepted` → real `TryApplyPosition`,
real `PhysicsTimestampGate`, then `TryExecuteAcceptedLocalPosition`); nothing is
hand-built. Keeping `FarSnap_QuiescingSourceLandblock_` is right — the state is
constructible in the fixture, only production cannot reach it — and its caveat is
accurate. A5 is satisfied at the pre-flight itself. B1, B3, m1-m5, n2, n3 all
fixed and accurate. **n1 in the round-3 correction was MINE and wrong**
classifier `:467` is correct, `:468` is `ConstrainPhase`, and the member-name
replacement is exact. No added line carries a source line-number citation —
verified by scanning every `+` line.
## Gate
Complete Release suite. Baseline **10,968 / 4 / 0** at `1b631f12`; round 3
measured **11,007 / 4 / 0**. Known flakes, do not chase or conflate: **#302**
(`PortalProjectionTests…`, App.Tests), **#308** (`NakEmissionTests.LossSoak_…`,
Core.Net.Tests).

View file

@ -588,6 +588,18 @@ internal sealed class SessionPlayerCompositionPhase
() => d.PlayerController.Controller,
() => d.Character.UsePositionFromServer,
() => liveSessionSource.CurrentSession);
// C4 route 4b-2 (2026-08-04): the graphical remote-placement drive
// controller — route 4b-1's dormant owner, now driven by the remote
// far snap. Its service window is the graphical host's near-tier
// residency (GraphicalRemotePlacementServiceWindow's own remarks
// justify IsNearTier over IsNearTierOrPending). Shares the SAME entity
// lifetime, clock, and prepared-collision source as the two drive
// controllers above.
var remotePlacementDrive = new RuntimeRemotePlacementDriveController(
d.EntityObjects,
d.Runtime.Clock,
firstEntryCollision,
new GraphicalRemotePlacementServiceWindow(live.WorldState));
var hydration = new LiveEntityHydrationController(
live.LiveEntities,
d.EntityObjects,
@ -654,6 +666,7 @@ internal sealed class SessionPlayerCompositionPhase
localPhysicsTimestamps.Publish,
d.MovementDiagnostics,
acceptedPositionDrive,
remotePlacementDrive,
worldDropProjection);
var liveness = new LiveEntityLivenessController(
live.LiveEntities,
@ -977,7 +990,8 @@ internal sealed class SessionPlayerCompositionPhase
live.PlacementProjection,
placementProjectionRetry,
firstEntryDrive,
acceptedPositionDrive),
acceptedPositionDrive,
remotePlacementDrive),
liveSessionCommands,
d.Log);
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(

View file

@ -18,6 +18,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
private readonly RuntimePlacementProjectionRetrySlot _retries;
private readonly RuntimeFirstEntryDriveController? _firstEntry;
private readonly RuntimeAcceptedPositionDriveController? _acceptedPositionDrive;
private readonly RuntimeRemotePlacementDriveController? _remotePlacementDrive;
private readonly Action<RuntimeEntityRecord>? _localPlayerCompleted;
private RuntimePlacementProjectionSubscription? _subscription;
private IDisposable? _retryLease;
@ -32,7 +33,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action<RuntimeEntityRecord>? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
RuntimeRemotePlacementDriveController? remotePlacementDrive = null)
: this(
events,
() => new RuntimePlacementProjectionSubscription(
@ -43,7 +45,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
retries,
firstEntry,
localPlayerCompleted,
acceptedPositionDrive)
acceptedPositionDrive,
remotePlacementDrive)
{
ArgumentNullException.ThrowIfNull(runtime);
ArgumentNullException.ThrowIfNull(placements);
@ -56,7 +59,8 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
RuntimePlacementProjectionRetrySlot retries,
RuntimeFirstEntryDriveController? firstEntry = null,
Action<RuntimeEntityRecord>? localPlayerCompleted = null,
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null)
RuntimeAcceptedPositionDriveController? acceptedPositionDrive = null,
RuntimeRemotePlacementDriveController? remotePlacementDrive = null)
{
_events = events ?? throw new ArgumentNullException(nameof(events));
_createSubscription = createSubscription
@ -67,6 +71,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
_firstEntry = firstEntry;
_localPlayerCompleted = localPlayerCompleted;
_acceptedPositionDrive = acceptedPositionDrive;
_remotePlacementDrive = remotePlacementDrive;
}
public void Attach()
@ -84,6 +89,10 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
// Position drive controller (RuntimeAcceptedPositionDriveController
// .AttachRoute's doc comment).
_acceptedPositionDrive?.AttachRoute(this);
// C4 route 4b-2: route 4b-1's controller gained its first production
// caller, so its one-route-at-a-time latch and its route-scoped
// cancellation on detach are load-bearing rather than dormant.
_remotePlacementDrive?.AttachRoute(this);
_events.Attach();
RuntimePlacementProjectionSubscription? subscription = null;
@ -106,6 +115,10 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
{
_firstEntry?.DriveAll();
_acceptedPositionDrive?.Advance();
// C4 route 4b-2: resolves a retained remote preparation
// retry (Setup or world frame not resolved yet) on the
// same cadence, before the FIFO head is republished.
_remotePlacementDrive?.Advance();
return boundSubscription.RetryPending();
});
_subscription = subscription;
@ -136,6 +149,7 @@ internal sealed class GraphicalSessionEventRoute : ILiveSessionEventRouting
// route's entries.
_firstEntry?.DetachRoute(this);
_acceptedPositionDrive?.DetachRoute(this);
_remotePlacementDrive?.DetachRoute(this);
if (!_eventsDisposed)
{
_events.Dispose();

View file

@ -88,7 +88,8 @@ internal sealed record LiveSessionWorldRuntime(
RuntimePlacementPresentationSink PlacementProjection,
RuntimePlacementProjectionRetrySlot PlacementRetries,
RuntimeFirstEntryDriveController FirstEntryDrive,
RuntimeAcceptedPositionDriveController AcceptedPositionDrive);
RuntimeAcceptedPositionDriveController AcceptedPositionDrive,
RuntimeRemotePlacementDriveController RemotePlacementDrive);
/// <summary>
/// Builds the exact per-generation route/reset graph for the canonical live
@ -266,7 +267,8 @@ internal sealed class LiveSessionRuntimeFactory
_world.PlacementRetries,
_world.FirstEntryDrive,
_ => session.SendGameAction(GameActionLoginComplete.Build()),
_world.AcceptedPositionDrive);
_world.AcceptedPositionDrive,
_world.RemotePlacementDrive);
}
private LiveInventorySessionBindings CreateInventoryBindings() => new(

View file

@ -65,6 +65,13 @@ internal sealed class LiveEntityNetworkUpdateController
private readonly InventoryWorldDropProjectionController?
_worldDropProjection;
private readonly RuntimeAcceptedPositionDriveController _acceptedPositionDrive;
/// <summary>
/// C4 route 4b-2: the Runtime-owned remote placement seam. Route 4b-1
/// landed it with no production caller; the remote far snap
/// (<c>SetPositionSimple</c>, <c>player_distance &gt;= 96 m</c>) is its
/// first, so its ownership ledger stops being tautologically zero here.
/// </summary>
private readonly RuntimeRemotePlacementDriveController _remotePlacementDrive;
private PlayerMovementController? _playerController => _playerControllerSource.Controller;
private EntityPhysicsHost? _playerHost => _playerHostSource.Host;
@ -106,6 +113,7 @@ internal sealed class LiveEntityNetworkUpdateController
Action<uint, AcceptedPhysicsTimestamps> publishTimestamps,
IMovementTruthDiagnosticSink movementTruthDiagnostics,
RuntimeAcceptedPositionDriveController acceptedPositionDrive,
RuntimeRemotePlacementDriveController remotePlacementDrive,
InventoryWorldDropProjectionController? worldDropProjection = null)
{
_liveEntities = liveEntities ?? throw new ArgumentNullException(nameof(liveEntities));
@ -143,6 +151,8 @@ internal sealed class LiveEntityNetworkUpdateController
?? throw new ArgumentNullException(nameof(movementTruthDiagnostics));
_acceptedPositionDrive = acceptedPositionDrive
?? throw new ArgumentNullException(nameof(acceptedPositionDrive));
_remotePlacementDrive = remotePlacementDrive
?? throw new ArgumentNullException(nameof(remotePlacementDrive));
_worldDropProjection = worldDropProjection;
}
@ -822,9 +832,19 @@ internal sealed class LiveEntityNetworkUpdateController
/// App contributes only retail's <c>player_distance</c> — the live
/// physics-controller distance, and <see langword="null"/> (never a
/// fabricated <c>Vector3.Zero</c>) when no controller exists yet, which
/// makes Runtime decline and leaves the legacy path untouched. Callers
/// must only invoke this for a genuinely remote (never local-player)
/// entity whose <c>remotePlacementRequired</c> gate is already false.
/// makes Runtime decline. Callers must only invoke this for a genuinely
/// remote (never local-player) entity whose
/// <c>remotePlacementRequired</c> gate is already false.
///
/// <para>
/// C4 route 4b-2 review fix — this comment used to end "and leaves the
/// legacy path untouched". There is no legacy path left: the duplicated
/// App-side near/far blocks were deleted with this slice, and a declined
/// classification now takes the stated <c>UnroutedCatchUp</c> policy
/// (AP-137) through <c>ApplyRemoteContactRouting</c>'s default arm. The
/// <see langword="null"/> return is still "Runtime has no opinion", never
/// "rejected"; what changed is what the caller does with it.
/// </para>
/// </summary>
private RuntimeAuthoritativePositionRoute? ClassifyRemoteAcceptedPosition(
AcDream.Core.Net.WorldSession.EntityPositionUpdate update,
@ -854,8 +874,20 @@ internal sealed class LiveEntityNetworkUpdateController
/// and the airborne no-op writes nothing at all (retail
/// <c>MoveOrTeleport</c> 0x00516330 returns 0 @0x0051636D). Writing the
/// wire pose here first would be the second writer route 2's original
/// defect consisted of. Every other classification keeps the pre-existing
/// write, unchanged, until route 4b.
/// defect consisted of.
/// </para>
///
/// <para>
/// C4 route 4b-2 review fix — this comment used to end "Every other
/// classification keeps the pre-existing write, unchanged, until route
/// 4b", which is now false in both halves. The gate is still
/// <c>OwnsSteadyState</c>, so the FAR snap (4b-2's own arm) DOES take the
/// wire-pose write here even though it goes on to place canonically. That
/// is deliberate and is not a second writer in the route 2 sense: the far
/// arm's tail re-syncs the render entity from the RESOLVED body
/// afterwards, so this write only covers the window before the placement
/// commits, exactly as it did before the slice. Route 4b-3 revisits the
/// gate when it takes the cell-less half.
/// </para>
///
/// <para>
@ -893,12 +925,12 @@ internal sealed class LiveEntityNetworkUpdateController
return true;
}
/// <summary>Which arm of the non-player-remote contact routing claimed a
/// packet. The value is the seam's observable outcome, asserted by the
/// acceptance tests; production only distinguishes
/// <see cref="RemoteContactArm.Legacy"/>, but the finer result is what
/// makes the PRECEDENCE testable and must not be collapsed to a
/// bool.</summary>
/// <summary>Which arm of the remote contact routing claimed a packet. The
/// value is the seam's observable outcome, asserted by the acceptance
/// tests; production only distinguishes
/// <see cref="RemoteContactArm.FarSnapPlacement"/> (which alone can be
/// re-entrant), but the finer result is what makes the PRECEDENCE and the
/// arm selection testable and must not be collapsed to a bool.</summary>
internal enum RemoteContactArm : byte
{
/// <summary>The body was airborne. Hard-snapped, exactly as before
@ -914,33 +946,87 @@ internal sealed class LiveEntityNetworkUpdateController
/// <summary>Route 4a's near InterpolateTo branch.</summary>
SteadyStateInterpolate,
/// <summary>Neither: the caller runs its own untouched legacy
/// near/far routing.</summary>
Legacy,
/// <summary>C4 route 4b-2: retail's far snap — <c>StopInterpolating</c>
/// @0x005163CB then <c>SetPositionSimple</c> @0x005163D9 — executed
/// through the canonical Runtime placement owner.</summary>
FarSnapPlacement,
/// <summary>The acdream-only leftover set (null, <c>Rejected*</c>, the
/// cell-less <c>SetPosition</c> half 4b-3 will own). See
/// <see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/> for
/// the stated policy.</summary>
UnroutedCatchUp,
}
/// <summary>
/// C4 route 4a: the ORDERING carve-out for the non-player-remote arm. An
/// airborne body's contact packet keeps its pre-existing authoritative
/// hard-snap and is decided BEFORE route 4a's near-Interpolate branch can
/// claim it — the player-remote arm gets the same precedence structurally,
/// because its landing block sits ahead of its own routing and returns.
/// The complete observable outcome of one
/// <see cref="ApplyRemoteContactRouting"/> call: which arm claimed the
/// packet, and — for
/// <see cref="RemoteContactArm.FarSnapPlacement"/> alone — what the
/// canonical Runtime placement actually did.
///
/// <para>
/// This exists as one entry point precisely so the precedence is
/// observable: a landing packet classifies <c>Interpolate</c>, so if the
/// 4a test came first it would ENQUEUE a body that must PLANT, and a
/// creature knocked off a ledge would glide down over a packet interval.
/// Landing is not a behaviour route 4a is scoped to change.
/// C4 route 4b-2 review fix: the placement status used to be discarded at
/// the call site (<c>_ = placementDrive.…</c>), which made the far arm's
/// non-commit outcomes invisible from outside and hid the freeze
/// the review found. Production still takes no DECISION from it —
/// retail's <c>MoveOrTeleport</c> likewise discards
/// <c>SetPositionSimple</c>'s <c>SetPositionError</c> and returns 1
/// @0x005163E8 — but the value is now carried out of the seam so the
/// acceptance tests assert the commit path and the
/// <c>store_position</c> fallback path apart from each other.
/// <see cref="Placement"/> is <see langword="null"/> for every arm that
/// performs no placement.
/// </para>
/// </summary>
internal static RemoteContactArm ApplyRemoteContactRouting(
internal readonly record struct RemoteContactRouting(
RemoteContactArm Arm,
RuntimeRemotePlacementExecutionStatus? Placement);
/// <summary>
/// The complete remote grounded/contact routing for ONE accepted Position,
/// shared by the player-remote and NPC-remote arms — retail's
/// <c>CPhysicsObj::MoveOrTeleport</c> (0x00516330) makes no
/// <c>this == player</c> distinction on any of these branches.
///
/// <para>
/// C4 route 4a contributed the ORDERING carve-out: an airborne body's
/// contact packet keeps its pre-existing authoritative hard-snap and is
/// decided BEFORE the near-Interpolate branch can claim it. A landing
/// packet classifies <c>Interpolate</c>, so if the 4a test came first it
/// would ENQUEUE a body that must PLANT, and a creature knocked off a
/// ledge would glide down over a packet interval. The player-remote caller
/// reaches this method only with <c>Airborne == false</c> (its landing
/// block sits ahead of its routing and returns), so the carve-out is inert
/// there and the two callers stay one decision.
/// </para>
///
/// <para>
/// C4 route 4b-2 added <see cref="RemoteContactArm.FarSnapPlacement"/> and
/// deleted the two duplicated App-side near/far blocks that used to follow
/// this call. <b>The far arm is the only re-entrant one</b> — a canonical
/// placement publishes its <c>Place</c> receipt synchronously, and a
/// non-commit outcome publishes a cancellation receipt just as
/// synchronously, and the production placement-projection sink can delete
/// or replace the incarnation from inside either — so a caller MUST
/// re-validate position ownership after this returns
/// <see cref="RemoteContactArm.FarSnapPlacement"/>, on EVERY placement
/// status, before writing anything else for the packet. That includes the
/// <c>ConstrainTo</c> leash: both arms therefore run the re-validation
/// FIRST and arm second (see AP-138).
/// </para>
/// </summary>
internal static RemoteContactRouting ApplyRemoteContactRouting(
RuntimeRemotePlacementDriveController placementDrive,
RuntimeEntityRecord canonical,
RemoteMotion remote,
RuntimeAuthoritativePositionRoute? route,
System.Numerics.Vector3 worldPos,
System.Numerics.Quaternion rotation,
bool willBeDrTicked)
{
ArgumentNullException.ThrowIfNull(placementDrive);
ArgumentNullException.ThrowIfNull(canonical);
ArgumentNullException.ThrowIfNull(remote);
if (remote.Airborne)
{
@ -959,19 +1045,126 @@ internal sealed class LiveEntityNetworkUpdateController
// the queue is empty here could delete it.
remote.Body.Position = worldPos;
remote.Body.Orientation = rotation;
return RemoteContactArm.AirborneSnap;
return new RemoteContactRouting(
RemoteContactArm.AirborneSnap, Placement: null);
}
if (!RuntimeRemoteSteadyStatePosition.IsNearInterpolate(route))
return RemoteContactArm.Legacy;
switch (RuntimeRemoteFarSnapPosition.ResolveArm(route))
{
case RuntimeRemoteAcceptedPositionArm.FarSnapPlacement:
return new RemoteContactRouting(
RemoteContactArm.FarSnapPlacement,
placementDrive.ApplyAcceptedRemoteFarSnap(
canonical,
remote,
route!.Value));
RuntimeRemoteSteadyStatePosition.ApplyInterpolate(
remote,
worldPos,
rotation,
isMovingTo: remote.Movement.IsMovingTo(),
willBeDrTicked);
return RemoteContactArm.SteadyStateInterpolate;
case RuntimeRemoteAcceptedPositionArm.NearInterpolate:
RuntimeRemoteSteadyStatePosition.ApplyInterpolate(
remote,
worldPos,
rotation,
isMovingTo: remote.Movement.IsMovingTo(),
willBeDrTicked);
return new RemoteContactRouting(
RemoteContactArm.SteadyStateInterpolate, Placement: null);
case RuntimeRemoteAcceptedPositionArm.AirborneNoOperation:
// R10 review fix: explicit rather than folded into `default`,
// where the comment ASSERTED unreachability that no code
// enforced. Retail's arg4 == 0 branch writes NOTHING at all
// (@0x0051636D returns 0), so there is no operation this
// method could perform; both production callers early-return
// on IsAirborneNoOperation before they route (the player arm's
// AIRBORNE NO-OP block, the NPC arm's mirror of it — the two
// `IsAirborneNoOperation` call sites in this file, cited here
// by name because line numbers went stale within one review
// round). Reaching here means a caller
// skipped that gate, and the only faithful answer is to say
// so — ApplyInterpolate's own doc likewise forbids being
// called for this disposition.
throw new InvalidOperationException(
"A NoPositionOperation (airborne no-op) classification "
+ "must be handled by the caller's own early return "
+ "before routing; retail's MoveOrTeleport writes nothing "
+ "at all on that branch (@0x0051636D).");
default:
// UnroutedCatchUp takes the SAME AP-87 catch-up the near
// branch uses — the stated policy (AP-137), and the reason
// the App's two duplicated 96 m / 4 m constant pairs and both
// fabricated Vector3.Zero player positions are gone.
RuntimeRemoteSteadyStatePosition.ApplyInterpolate(
remote,
worldPos,
rotation,
isMovingTo: remote.Movement.IsMovingTo(),
willBeDrTicked);
return new RemoteContactRouting(
RemoteContactArm.UnroutedCatchUp, Placement: null);
}
}
/// <summary>
/// C4 route 4b-2: the NPC-remote arm's post-routing wire-cell adoption,
/// extracted so its ONE suppression rule is exercised by production and by
/// test through the same entry point rather than restated in a test body.
///
/// <para>
/// <see cref="RemoteMotion.CellId"/> writes THROUGH to the canonical
/// <c>FullCellId</c> (<c>RuntimePhysicsState.CommitCanonicalCell</c>).
/// After a far snap the canonical placement is the cell authority — retail
/// <c>CPhysicsObj::SetPositionInternal</c> (0x00515BD0) resolves the
/// destination cell through <c>AdjustPosition</c>/<c>set_cell</c> and
/// nothing writes the wire cell over it afterwards — so this write is
/// suppressed for that arm alone. Unlike the player arm, whose identical
/// write sits BEFORE its routing, the NPC one sits after; leaving it
/// unguarded would discard a resolved cell that differs from the wire
/// cell. Every other arm performs no placement, so the wire cell is still
/// the newest truth there.
/// </para>
///
/// <para>
/// <b>Scope, stated precisely (C4 route 4b-2 review; corrected at the
/// delta review).</b> The suppression bites whenever the canonical
/// placement RESOLVED a cell different from the wire cell. That is the
/// commit, and also the <c>RejectedByPlacement</c> shape where
/// <c>CommitCanonical</c> settled the body (and wrote
/// <c>record.FullCellId</c>) before the projection ownership was
/// displaced — the earlier "only when the placement COMMITTED" wording
/// missed that one. It also bites on <c>Deferred</c>, which round 3
/// (correction m1) adds to this enumeration: <c>ParkDeferred</c> snaps
/// the body to the PARKED result cell and
/// <c>RestoreParkWithdrawal</c> re-commits residency from
/// <c>body.CellPosition.ObjCellId</c>, which for a post-sweep park is the
/// swept/settled cell and need not be the wire cell. The remaining
/// outcomes — <c>Refused</c>, <c>Contention</c>,
/// <c>RejectedPreparation</c>, <c>NotApplicable</c>, and the
/// <c>RejectedByPlacement</c> shape the engine's own sweep refused —
/// resolve no cell, and there the suppression is a no-op: the per-UP
/// <c>RebucketLiveEntity</c> above already committed the wire full cell
/// to canonical, and <c>RemoteMotion.CellId</c> reads through to the same
/// <c>FullCellId</c>, so the suppressed write would have written the
/// value that is already there. Keying on the ARM rather than the
/// placement status is therefore exact as well as simpler — and the
/// body/cell divergence a refusal used to produce was the frozen body,
/// which the <c>store_position</c> fallback fixes at its source.
/// </para>
///
/// <para>
/// Returns true when the wire cell was adopted.
/// </para>
/// </summary>
internal static bool TryAdoptWireCellAfterRouting(
RemoteMotion remote,
RemoteContactArm arm,
uint wireCellId)
{
ArgumentNullException.ThrowIfNull(remote);
if (arm is RemoteContactArm.FarSnapPlacement)
return false;
remote.CellId = wireCellId;
return true;
}
/// <summary>
@ -1465,11 +1658,12 @@ internal sealed class LiveEntityNetworkUpdateController
// near InterpolateTo queue — no direct body write here) never
// receives it. remotePlacementRequired guarantees the classifier's
// teleport (SetPosition) disposition never reaches here — that stays
// route 4b. The local player never reaches this generic-remote code
// path at all. Every classification 4a does NOT own — the >=96 m
// far snap, a cell-less remote, a rejected authority or payload, and
// "no classification at all" — falls through to the pre-existing
// legacy routing completely unchanged; 4b deletes that fallback.
// route 4b-3. The local player never reaches this generic-remote code
// path at all. C4 route 4b-2 additionally routes the >=96 m far snap
// through the canonical Runtime placement owner; a cell-less remote, a
// rejected authority or payload, and "no classification at all" take
// the stated UnroutedCatchUp policy
// (RuntimeRemoteFarSnapPosition.ResolveArm) until 4b-3.
RuntimeAuthoritativePositionRoute? earlyRemoteRoute =
update.Guid != _playerServerGuid && !remotePlacementRequired
? ClassifyRemoteAcceptedPosition(
@ -1491,9 +1685,18 @@ internal sealed class LiveEntityNetworkUpdateController
// ordinary moving remote's draw bucket, commits its canonical
// FullCellId (which feeds back as the classifier's own
// CommittedCellId and as the ConstraintDistance cell key), and
// recovers a pending bucket promotion. Neither 4a branch performs a
// placement, so unlike route 2 there is no committed placement
// receipt that could project this in its stead.
// recovers a pending bucket promotion.
//
// C4 route 4b-2 review fix — this used to end "Neither 4a branch
// performs a placement, so unlike route 2 there is no committed
// placement receipt that could project this in its stead". The far
// arm DOES perform a placement now, and this call still runs ahead of
// it for every classification. That ordering is what makes the NPC
// arm's post-routing wire-cell suppression a no-op on a non-commit
// outcome (TryAdoptWireCellAfterRouting): the wire full cell is
// already canonical by the time routing starts. A COMMITTED placement
// resolves its own destination cell afterwards, which is the case the
// suppression exists for.
if (!_liveEntities!.RebucketLiveEntity(update.Guid, p.LandblockId)
|| !_liveEntities.TryGetRecord(
update.Guid,
@ -1707,10 +1910,14 @@ internal sealed class LiveEntityNetworkUpdateController
// a nonzero MoveOrTeleport return (@0x00454272, inside the
// `if (MoveOrTeleport(...) != 0)` at @0x00454254) — so this
// pre-operation, unconditional arming is now the LEGACY shape and
// runs only for the classifications 4a does not own. The two 4a
// classifications arm it after their own operation instead, or
// (the airborne no-op) not at all. 4b deletes this fallback.
if (!RuntimeRemoteSteadyStatePosition.OwnsSteadyState(earlyRemoteRoute)
// runs only for the classifications the post-operation arm does
// not own. C4 route 4b-2 added the far snap to that set, leaving
// this fallback for the cell-less half, the two rejections, and
// "no classification at all"; 4b-3 deletes it. The gate reads the
// SAME predicate TryArmConstraintAfterOperation does, so exactly
// one of the two sites arms any given classification.
if (!RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
earlyRemoteRoute)
&& rmState.Host is { } remoteConstraintHost)
{
RuntimeRemoteSteadyStatePosition.ArmConstraintAfterOperation(
@ -1911,72 +2118,66 @@ internal sealed class LiveEntityNetworkUpdateController
}
// ── GROUNDED ROUTING (CPhysicsObj::MoveOrTeleport) ────────────
// C4 route 4a: the near (Interpolate) decision — including the
// AP-87 placement-snap backstop — now lives in
// RuntimeRemoteSteadyStatePosition.ApplyInterpolate, shared
// with the NPC branch below. Every OTHER classification (the
// >=96 m far snap, a cell-less remote, a rejection, or none at
// all) keeps the pre-existing legacy near/far routing below,
// unchanged, until route 4b — "not Interpolate" is NOT "far".
// C4 routes 4a + 4b-2: the complete near/far/leftover decision
// is the SAME shared entry point the NPC arm below calls —
// retail's disassembly makes no `this == player` distinction
// on any of these branches. The player arm reaches it only
// with Airborne == false (the landing block above returns), so
// the airborne carve-out inside is inert here.
bool willBeDrTicked = WillAdvanceRemoteMotion(update.Guid, rmState);
if (RuntimeRemoteSteadyStatePosition.IsNearInterpolate(
earlyRemoteRoute))
{
RuntimeRemoteSteadyStatePosition.ApplyInterpolate(
rmState,
worldPos,
rot,
isMovingTo: rmState.Movement.IsMovingTo(),
willBeDrTicked);
RemoteContactRouting playerRouting = ApplyRemoteContactRouting(
_remotePlacementDrive,
acceptedPositionCanonical,
rmState,
earlyRemoteRoute,
worldPos,
rot,
willBeDrTicked);
// D2: ConstrainTo arms strictly AFTER the operation,
// anchored post-move — retail arms it only once
// MoveOrTeleport returns nonzero (@0x00454254/@0x00454272).
RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
earlyRemoteRoute,
rmState);
}
else
// C4 route 4b-2: the far arm is re-entrant — the canonical
// placement publishes its Place receipt (or, on a non-commit
// outcome, its cancellation receipt) synchronously, and the
// production projection sink can delete or replace this
// incarnation from inside either. Re-validate before ANY
// further write for this packet, exactly as the landing block
// does after MovementManager.HitGround.
//
// R5 review fix: this now sits BEFORE the leash arming, which
// is the same order the NPC arm has always had — the two arms
// were mirror images of each other and one of them had to be
// wrong. Arming is a write (it stamps rmState.Host's
// PositionManager), and this class's own rule is that nothing
// may be written through a superseded owner. The residual
// versus retail's unconditional arm on a nonzero
// MoveOrTeleport return is AP-138.
if (playerRouting.Arm is RemoteContactArm.FarSnapPlacement
&& (!IsCurrentPositionOwner(entity)
|| !ReferenceEquals(
positionRecord.RemoteMotionRuntime,
rmState)))
{
// LEGACY near/far routing, unchanged. Its leash was
// already armed by the legacy pre-operation call above.
const float MaxPhysicsDistance = 96f; // retail player_distance far-snap
const float BodySnapThreshold = 4f; // large correction / teleport / unplaced -> snap
var localPlayerPos = _playerController?.Position ?? System.Numerics.Vector3.Zero;
float dist = System.Numerics.Vector3.Distance(worldPos, localPlayerPos);
// #184 Slice 2b: the player UP routing gains the SAME placement-snap
// backstop the NPC routing got in Slice 1 (AP-87). The 4 m
// bodyToTarget guard is the LOAD-BEARING backstop;
// !willBeDrTicked snaps a no-Sequencer player whose queue nothing
// would consume; dist>96 is retail's far-snap.
float bodyToTarget = System.Numerics.Vector3.Distance(
rmState.Body.Position, worldPos);
if (dist > MaxPhysicsDistance || !willBeDrTicked
|| bodyToTarget > BodySnapThreshold)
{
// Beyond view bubble / large correction / unplaced body:
// SetPositionSimple slide-snap. Clear queue.
rmState.Interp.Clear();
rmState.Body.Position = worldPos;
rmState.Body.Orientation = rot;
}
else
{
// Within view bubble, placed + near: enqueue waypoint for
// adjust_offset to walk to.
System.Numerics.Quaternion? immediateOrientation =
rmState.Interp.Enqueue(
worldPos,
rot,
isMovingTo: rmState.Movement.IsMovingTo(),
currentBodyPosition: rmState.Body.Position,
currentBodyOrientation: rmState.Body.Orientation);
if (immediateOrientation is { } closeOrientation)
rmState.Body.Orientation = closeOrientation;
}
return;
}
// D2: ConstrainTo arms strictly AFTER the operation, anchored
// post-move — retail arms it only once MoveOrTeleport returns
// nonzero (@0x00454254/@0x00454272), which the near AND far
// branches both do (@0x005163BE, @0x005163E8). The far branch
// arms on EVERY placement outcome, including a failed one:
// retail discards SetPositionSimple's SetPositionError and
// returns 1 regardless. Every remaining classification already
// armed through the legacy pre-operation call above.
//
// Delta review N4: retail's arm is unconditional, acdream's is
// not — the currency guard immediately above returns without
// arming when the far arm's synchronous receipt replaced or
// deleted this incarnation. That one-packet gap is the third
// part of AP-138, and this comment must not read as though the
// arm below is reached on every far-snap outcome.
RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
earlyRemoteRoute,
rmState);
// Track the UP-derived synth velocity for diagnostics
// ([VEL_DIAG] pace comparison). L.2g S5 (2026-07-02): the
// #39-era cycle-refinement call that used to live here is
@ -1997,12 +2198,30 @@ internal sealed class LiveEntityNetworkUpdateController
}
}
// Sync the visible entity to the body — overrides the unconditional
// entity.SetPosition(worldPos) snap at the top of this function.
// For the far-snap branch this is a no-op (body == worldPos); for
// the near-enqueue branch this prevents a 1-frame teleport-then-
// yank-back rubber-band as TickAnimations chases worldPos via the
// queue.
// Sync the visible entity to the body — overrides the
// entity.SetPosition(worldPos) write at the top of this
// function (TryApplyGenericRemoteRenderPose, suppressed for
// the two 4a classifications). This prevents a 1-frame
// teleport-then-yank-back rubber-band as TickAnimations
// chases worldPos via the queue.
//
// C4 route 4b-2 review fix — this used to claim "For the
// far-snap branch this is a no-op (body == worldPos)". The
// PROVENANCE claim behind that was always wrong and is what
// matters here: the far arm's body pose comes from the
// canonical accepted destination resolved through Runtime's
// world frame (a committed placement, a park's snap, or
// store_position), never from the caller's separately-derived
// worldPos, and this write is what carries that canonical pose
// to the render entity.
//
// Delta review N4 — the VALUE claim, stated correctly: on the
// store_position path the two happen to be equal, because #283
// proved App's streaming origin and Runtime's world frame
// cannot disagree and both compose the same accepted origin.
// It is genuinely NOT a no-op on the committed path, where the
// body carries the collision-settled spherePath.CurPos. Do not
// "simplify" this write away on the strength of the equal case.
//
// #184 Slice 2b: sync the player-remote shadow to the RESOLVED/placed
// body (mirrors the NPC UP-branch tail). Now that grounded players run
@ -2105,21 +2324,18 @@ internal sealed class LiveEntityNetworkUpdateController
Console.WriteLine(FormattableString.Invariant(
$"[sticky-snap-skip] guid=0x{update.Guid:X8} d={snapDist:F3} srv=({worldPos.X:F2},{worldPos.Y:F2}) body=({rmState.Body.Position.X:F2},{rmState.Body.Position.Y:F2})"));
}
var npcRouting = new RemoteContactRouting(
RemoteContactArm.UnroutedCatchUp, Placement: null);
if (!snapSuppressedByStick)
{
// C4 route 4a: the near (Interpolate) decision — including the
// AP-87 placement-snap backstop — is now the SAME Runtime
// owner the player-remote branch above calls; retail's
// MoveOrTeleport (0x00516330) makes no `this == player`
// distinction, so the two per-kind copies became one. TS-44's
// sticky suppression stays an NPC-only CALLER gate (this
// `if`), which is what its register row describes and what
// the player arm has never had.
// C4 routes 4a + 4b-2: the complete near/far/leftover decision
// is the SAME shared entry point the player-remote branch
// above calls; retail's MoveOrTeleport (0x00516330) makes no
// `this == player` distinction, so the two per-kind copies
// became one. TS-44's sticky suppression stays an NPC-only
// CALLER gate (this `if`), which is what its register row
// describes and what the player arm has never had.
//
// Every OTHER classification (the >=96 m far snap, a
// cell-less remote, a rejection, or none at all) keeps the
// pre-existing legacy routing below, unchanged, until route
// 4b — "not Interpolate" is NOT "far".
// #184 (2026-07-07): an AIRBORNE body keeps its authoritative
// hard-snap (the arc integrates locally, K-fix15), and that
// decision is taken FIRST — a landing packet classifies
@ -2127,73 +2343,42 @@ internal sealed class LiveEntityNetworkUpdateController
// airborne test would enqueue a body that must plant, and a
// creature knocked off a ledge would glide down over a packet
// interval. Physics digest 2026-07-07 banner.
if (ApplyRemoteContactRouting(
rmState,
earlyRemoteRoute,
worldPos,
rot,
WillAdvanceRemoteMotion(update.Guid, rmState))
is RemoteContactArm.Legacy)
{
// LEGACY NPC near/far routing, unchanged. Its leash was
// already armed by the legacy pre-operation call above.
// The body is PLACED (hard-snapped) whenever it is not
// already tracking NEAR the server position — the first
// UP, a large correction / teleport, an out-of-view
// creature (>96 m from the local player), or an entity the
// DR loop won't tick — otherwise the server point is a
// GENTLE dead-reckoning TARGET the per-tick interp
// catch-up walks to, and the KEPT sweep de-overlaps that
// movement.
const float MaxPhysicsDistanceNpc = 96f; // retail player_distance far-snap
const float BodySnapThresholdNpc = 4f; // large correction / teleport -> snap
var localPlayerPosNpc = _playerController?.Position
?? System.Numerics.Vector3.Zero;
float distNpc = System.Numerics.Vector3.Distance(worldPos, localPlayerPosNpc);
float bodyToTargetNpc = System.Numerics.Vector3.Distance(
rmState.Body.Position, worldPos);
bool firstUpNpc = rmState.LastServerPosTime <= 0.0;
bool willBeDrTickedNpc = WillAdvanceRemoteMotion(
update.Guid,
rmState);
npcRouting = ApplyRemoteContactRouting(
_remotePlacementDrive,
acceptedPositionCanonical,
rmState,
earlyRemoteRoute,
worldPos,
rot,
WillAdvanceRemoteMotion(update.Guid, rmState));
if (firstUpNpc || !willBeDrTickedNpc
|| distNpc > MaxPhysicsDistanceNpc
|| bodyToTargetNpc > BodySnapThresholdNpc)
{
// Placement / far / large-correction: SNAP + clear queue.
rmState.Interp.Clear();
rmState.Body.Position = worldPos;
rmState.Body.Orientation = rot;
}
else
{
// Near DR correction: enqueue the waypoint for the per-tick
// catch-up (Path B consumes it via ComputeOffset).
System.Numerics.Quaternion? immediateOrientation =
rmState.Interp.Enqueue(
worldPos,
rot,
isMovingTo: rmState.Movement.IsMovingTo(),
currentBodyPosition: rmState.Body.Position,
currentBodyOrientation: rmState.Body.Orientation);
if (immediateOrientation is { } closeOrientation)
rmState.Body.Orientation = closeOrientation;
}
// C4 route 4b-2: the far arm is re-entrant (see
// ApplyRemoteContactRouting's own remarks). Re-validate before
// any further write for this packet — including the leash
// arming below, which is why the player arm now runs this
// check in the SAME position relative to its own arming call
// (R5 review fix).
if (npcRouting.Arm is RemoteContactArm.FarSnapPlacement
&& (!IsCurrentPositionOwner(entity)
|| !ReferenceEquals(
positionRecord.RemoteMotionRuntime,
rmState)))
{
return;
}
}
// D2: ConstrainTo arms strictly AFTER the operation above,
// anchored post-move (@0x00454272, inside the
// `if (MoveOrTeleport(...) != 0)` at @0x00454254), and only for a
// classification route 4a owns — every other one already armed
// the legacy pre-operation call above, exactly as before. Retail's
// ConstraintManager leash is independent of the acdream-only TS-44
// sticky suppression (which only concerns the enqueue/snap above),
// so this deliberately sits OUTSIDE the snapSuppressedByStick
// gate: a stuck NPC's leash still re-arms every accepted Position
// exactly as it did before this route split the single call into a
// per-branch pair.
// classification the post-operation arm owns — every other one
// already armed the legacy pre-operation call above, exactly as
// before. Retail's ConstraintManager leash is independent of the
// acdream-only TS-44 sticky suppression (which only concerns the
// enqueue/snap/placement above), so this deliberately sits OUTSIDE
// the snapSuppressedByStick gate: a stuck NPC's leash still
// re-arms every accepted Position exactly as it did before this
// route split the single call into a per-branch pair.
RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
earlyRemoteRoute,
rmState);
@ -2222,7 +2407,11 @@ internal sealed class LiveEntityNetworkUpdateController
// per-tick ResolveWithTransition sweep then advances CheckCellId
// as the sphere crosses cells and writes the new cell back into
// rmState.CellId so the NEXT frame starts in the correct cell.
rmState.CellId = p.LandblockId;
//
// C4 route 4b-2: NOT after a far snap — see
// TryAdoptWireCellAfterRouting for the rule and why it applies to
// this arm and not the player one.
TryAdoptWireCellAfterRouting(rmState, npcRouting.Arm, p.LandblockId);
// Near UpdatePosition orientation is carried by the same complete
// interpolation Frame as translation. Placement, airborne, and

View file

@ -61,6 +61,29 @@ public static class PhysicsDiagnostics
public static bool ProbeCellEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_CELL") == "1";
/// <summary>
/// Issue #309's connected-gate confirmation signal (2026-08-04, C4 route
/// 4b-2). The gate's quiescence steps need a ForcePosition to land INSIDE
/// a transient collision-prefix quiescence window, which a tester cannot
/// synchronise with a teleport or portal arrival — so without a signal a
/// clean teleport and a correctly-parked one look identical and the gate
/// passes while broken.
///
/// <para>When true, <c>RuntimeSetPositionState</c> emits one
/// <c>[park]</c> line when a placement parks (entity, park cause, the
/// blocking landblock prefix if any, the caller's pre-snap cell, the
/// POST-snap cell the rollback would restore into, and whether the
/// rollback was captured or declined), and one <c>[park-restore]</c> line
/// when a cancelled park's withdrawal is rolled back or its residency arm
/// is declined at restore time. Low volume: parks are rare, and nothing
/// is emitted for an ordinary committing placement. Zero cost when off
/// (one static-bool read per park).</para>
///
/// <para>Initial state from <c>ACDREAM_PROBE_PARK=1</c>.</para>
/// </summary>
public static bool ProbeParkEnabled { get; set; } =
Environment.GetEnvironmentVariable("ACDREAM_PROBE_PARK") == "1";
/// <summary>
/// #283 reachability probe (2026-08-03). Runtime rebases its world frame
/// the instant an accepted Position carries TeleportAdvanced, while App's
@ -721,6 +744,7 @@ public static class PhysicsDiagnostics
{
ProbeResolveEnabled = false;
ProbeCellEnabled = false;
ProbeParkEnabled = false;
ProbeBuildingEnabled = false;
ProbeCellSetEnabled = false;
ProbeStickyEnabled = false;

View file

@ -600,9 +600,18 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
/// Returns <see langword="null"/> when no classification can honestly be
/// made: the lifetime has no bound generation yet, the canonical record
/// has not claimed a local id, or there is no live local-player position
/// to derive retail's <c>player_distance</c> from. In every one of those
/// cases the caller's pre-existing legacy path runs completely unchanged
/// — a null here is "route 4a has no opinion", never "rejected".
/// to derive retail's <c>player_distance</c> from. A null here is
/// "Runtime has no opinion", never "rejected".
/// </para>
///
/// <para>
/// C4 route 4b-2 review fix — this used to add "In every one of those
/// cases the caller's pre-existing legacy path runs completely
/// unchanged". That path no longer exists: 4b-2 deleted the graphical
/// caller's duplicated near/far blocks, and a null now takes the stated
/// <c>UnroutedCatchUp</c> policy (AP-137;
/// <c>RuntimeRemoteFarSnapPosition.ResolveArm</c>), which is AP-87's
/// shared catch-up rather than a re-derived 96 m test.
/// </para>
/// </summary>
internal RuntimeAuthoritativePositionRoute? ClassifyRemoteAcceptedPosition(

View file

@ -0,0 +1,208 @@
using AcDream.Core.Physics;
namespace AcDream.Runtime.Physics;
/// <summary>
/// Which arm of retail <c>CPhysicsObj::MoveOrTeleport</c> (0x00516330) an
/// already-classified REMOTE accepted Position takes. Total over every
/// classification a remote caller can hold, including the two acdream-only
/// ones retail cannot produce (see
/// <see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/>).
/// </summary>
internal enum RuntimeRemoteAcceptedPositionArm : byte
{
/// <summary>Retail's <c>arg4 == 0</c> branch: <c>return 0</c>
/// @0x0051636D, nothing written. Route 4a.</summary>
AirborneNoOperation,
/// <summary>Retail's <c>player_distance &lt; 96f</c>
/// <c>InterpolateTo</c> @0x005163AF. Route 4a.</summary>
NearInterpolate,
/// <summary>Retail's <c>player_distance &gt;= 96f</c>
/// <c>StopInterpolating</c> @0x005163CB + <c>SetPositionSimple</c>
/// @0x005163D9. Route 4b-2 — this slice.</summary>
FarSnapPlacement,
/// <summary>
/// <b>acdream-only.</b> No retail arm corresponds to it, because retail
/// cannot reach the states that produce it: a classification of
/// <c>RejectedAuthority</c>/<c>RejectedData</c> (retail validates no wire
/// frame this way), <see langword="null"/> (retail always has a player, so
/// <c>player_distance</c> always exists — acdream returns null for every
/// remote packet during the login window before the local movement
/// controller exists), or the cell-less <c>SetPosition</c> half that route
/// 4b-3 will own. See <see cref="ResolveArm"/>'s remarks for the stated
/// policy.
/// </summary>
UnroutedCatchUp,
}
/// <summary>
/// C4 route 4b-2 (2026-08-04): the remaining <c>MoveOrTeleport</c> arm
/// selection for a REMOTE accepted Position, beside route 4a's
/// <see cref="RuntimeRemoteSteadyStatePosition"/>. Route 4a owns the two
/// dispositions that perform no SetPosition; this owns the far snap
/// (<c>player_distance &gt;= 96 m</c> with wire contact, classified
/// <see cref="RuntimeAuthoritativePositionDisposition.SetPositionSimple"/>)
/// and states what happens to everything left over.
/// </summary>
internal static class RuntimeRemoteFarSnapPosition
{
/// <summary>
/// True when route 4b-2 owns this classification: retail's far branch,
/// @0x005163C1-@0x005163E8.
///
/// <para>
/// The disposition alone is NOT sufficient — the classifier emits
/// <c>SetPositionSimple</c> for the LOCAL PLAYER's FORCE_POSITION
/// (<c>RuntimeAuthoritativePositionRouteClassifier.cs:332</c>) and
/// teleport-advanced (<c>:355</c>) branches too, both carrying
/// <c>RuntimeSetPositionOperationKind.LocalAuthoritative</c>. The
/// <c>Teleport</c> flag bit is the same discriminator
/// <see cref="AcDream.Runtime.Session.RuntimeRemotePlacementDriveController.OwnsPlacement"/>
/// uses (a remote top-level Create carries <c>Placement|Slide</c>, an
/// accepted remote Position carries retail's
/// <c>Teleport|Slide|SendPositionEvent</c> = <c>0x1012</c>, exactly the
/// flags <c>CPhysicsObj::SetPositionSimple</c> @0x005162B0 builds for
/// <c>arg3 != 0</c>), so this predicate is a strict narrowing of
/// <c>OwnsPlacement</c> to its far half.
/// </para>
/// </summary>
internal static bool OwnsFarSnap(RuntimeAuthoritativePositionRoute? route) =>
route is
{
Disposition:
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
OperationKind:
RuntimeSetPositionOperationKind.RemoteAuthoritative,
}
&& (route.Value.SetPositionFlags & PhysicsSetPositionFlags.Teleport)
!= 0;
/// <summary>
/// The ONE predicate deciding whether retail's single
/// <c>SmartBox::HandleReceivedPosition</c> <c>ConstrainTo</c> site
/// (@0x00454272, inside <c>if (MoveOrTeleport(...) != 0)</c> @0x00454254)
/// is armed AFTER the position operation for this classification, rather
/// than by the legacy pre-operation App call.
///
/// <para>
/// Route 4a moved its two dispositions onto the post-operation arm; this
/// slice adds the far snap. Every remaining classification still arms
/// through the legacy pre-operation call site, unchanged. Both the legacy
/// gate and
/// <see cref="RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation"/>
/// read THIS predicate, so a classification can never be armed twice or
/// not at all.
/// </para>
/// </summary>
internal static bool OwnsAfterOperationConstraint(
RuntimeAuthoritativePositionRoute? route) =>
RuntimeRemoteSteadyStatePosition.OwnsSteadyState(route)
|| OwnsFarSnap(route);
/// <summary>
/// Selects the arm for one already-classified remote accepted Position.
///
/// <para>
/// <b>The <see cref="RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp"/>
/// policy, stated.</b> Before this slice, every classification route 4a
/// did not own fell into a duplicated App-side near/far block that
/// re-derived retail's <c>player_distance &gt;= 96 m</c> test from
/// <c>_playerController?.Position ?? Vector3.Zero</c>. That test is
/// deleted here rather than preserved, because in every residual case it
/// was already meaningless or wrong:
/// </para>
/// <list type="bullet">
/// <item><description><see langword="null"/> — the classifier returned no
/// route precisely BECAUSE there is no local-player position
/// (<c>RuntimeAcceptedPositionRouteRequests.TryBuild</c> refuses to
/// fabricate one; <c>GameRuntime</c> states the rule). The legacy block
/// then measured the distance from a fabricated <c>Vector3.Zero</c>.
/// <b>Review fix — what that actually computed:</b> <c>worldPos</c> is
/// streaming-origin-relative
/// (<c>LiveEntityNetworkUpdateController.cs</c>: local position plus
/// <c>(landblock _origin.Center) * 192 m</c>), and the streaming origin
/// recentres on the local player's landblock, so the fabricated distance
/// was the remote's distance from the ORIGIN LANDBLOCK'S CORNER — a
/// biased but genuinely correlated proxy for <c>player_distance</c>,
/// its error bounded by the player's own offset inside that landblock
/// (0-192 m per axis). The earlier claim that it had "no relationship to
/// <c>player_distance</c>" was false. It is deleted anyway because a
/// silently-biased proxy for retail's exact 96 m threshold is not a
/// threshold: the bias is up to ~2.8x the threshold itself, so the arm it
/// selects is not the arm retail would select, and there is no honest way
/// to correct for it without the player position the classifier already
/// declined to fabricate.</description></item>
/// <item><description>Cell-less <c>SetPosition</c> — the classifier
/// returns at <c>:399-417</c>, BEFORE it ever evaluates the distance.
/// Retail's cell-less body takes the <c>this_1-&gt;cell == 0</c> branch
/// @0x00516386 (<c>SetPosition</c>, flags <c>0x1012</c>), never the far
/// branch, so applying a far test to it was a divergence in its own
/// right. <b>Review fix — the residual delta, stated:</b> retail's
/// cell-less arm is an UNCONDITIONAL placement sitting ahead of the
/// contact test — <c>teleport_hook</c> @0x005163EF then <c>SetPosition</c>
/// @0x00516420, <c>return 1</c> @0x00516438 — while acdream now routes
/// that classification here, where <see cref="ApplyInterpolate"/> ENQUEUES
/// whenever <c>!firstUp &amp;&amp; willBeDrTicked &amp;&amp;
/// bodyToTarget &lt;= 4 m</c>, at any distance. <b>It is deliberately not
/// changed to place here.</b> Retail's arm is not a pose write: it is
/// <c>teleport_hook</c> @0x00514ED0 — the COMPLETE call list, read from
/// the decomp (delta review N6 added the last of these, which the earlier
/// enumeration dropped): <c>MovementManager::CancelMoveTo</c> @0x00514EDF,
/// <c>PositionManager::UnStick</c> @0x00514EEE,
/// <c>PositionManager::StopInterpolating</c> @0x00514EFD,
/// <c>PositionManager::UnConstrain</c> @0x00514F0C,
/// <c>TargetManager::ClearTarget</c> @0x00514F1B +
/// <c>NotifyVoyeurOfEvent(Teleported_TargetStatus)</c> @0x00514F28, and
/// <c>CPhysicsObj::report_collision_end(this, 1)</c> @0x00514F31 — followed
/// by the canonical flags-<c>0x1012</c>
/// <c>SetPosition</c>. Writing only the pose here would leave a live
/// moveto, a live stick, a live constraint leash pointing at the old
/// cell, and every collision partner still believing it is in contact,
/// which is strictly worse than the recorded queue. Porting the
/// whole arm is route 4b-3's entire scope; claiming half of it here is
/// route 4a's "'not Interpolate' is not 'far'" mistake one level up.
/// Recorded as AP-137.</description></item>
/// <item><description><c>RejectedAuthority</c>/<c>RejectedData</c> — the
/// packet's authority or payload was refused; retail, which validates
/// neither, has no state here at all. <b>Review fix — stated plainly:</b>
/// this arm therefore APPLIES a payload the classifier just marked
/// invalid. <c>ClassifyAcceptedPosition</c> emits <c>RejectedData</c> from
/// two sites a remote can reach — an accepted wire position that fails
/// <c>ValidPosition</c> (non-finite origin/orientation or a frame
/// <c>PositionFrameValidation</c> refuses), and a non-finite or negative
/// derived <c>player_distance</c> — and both then take this arm. That is
/// not a regression — the deleted legacy block fed the same payload to the
/// same snap/enqueue pair — but this slice's stated purpose was an
/// EXPLICIT handler, so it is named rather than left implied. Retiring it
/// means giving the two rejections their own no-operation arm, which is a
/// behaviour change route 4b-3 must decide with the cell-less half, not a
/// tidy-up. Recorded as AP-137.</description></item>
/// </list>
/// <para>
/// What remains is exactly AP-87 — the shared
/// <see cref="RuntimeRemoteSteadyStatePosition.ApplyInterpolate"/> catch-up
/// the near branch already uses, whose unconditional snap on
/// <c>firstUp || !willBeDrTicked || bodyToTarget &gt; 4 m</c> still PLACES
/// an unplaced or badly-lagging body. So a remote keeps tracking the
/// server through the login window and through a rejected packet; it is
/// never silently frozen, which is what deleting the legacy block without
/// a replacement would have done. The single 4 m constant now lives only
/// in <c>RuntimeRemoteSteadyStatePosition</c>; the App's two duplicated
/// copies are gone.
/// </para>
/// </summary>
internal static RuntimeRemoteAcceptedPositionArm ResolveArm(
RuntimeAuthoritativePositionRoute? route)
{
if (RuntimeRemoteSteadyStatePosition.IsAirborneNoOperation(route))
return RuntimeRemoteAcceptedPositionArm.AirborneNoOperation;
if (RuntimeRemoteSteadyStatePosition.IsNearInterpolate(route))
return RuntimeRemoteAcceptedPositionArm.NearInterpolate;
return OwnsFarSnap(route)
? RuntimeRemoteAcceptedPositionArm.FarSnapPlacement
: RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp;
}
}

View file

@ -16,16 +16,15 @@ namespace AcDream.Runtime.Physics;
/// incarnations — retail's disassembly makes no <c>this==player</c>
/// distinction on this path (see <c>ConstraintDistance</c>) — so one Runtime
/// owner decides and applies both, replacing the two independent per-kind
/// copies that used to live in <c>LiveEntityNetworkUpdateController</c>. The
/// far (&gt;=96 m) and teleport/cell-less branches remain the legacy App path
/// until C4 route 4b.
/// copies that used to live in <c>LiveEntityNetworkUpdateController</c>.
///
/// <para>
/// <b>Exactly two dispositions are owned here.</b> Everything else —
/// <c>SetPositionSimple</c>, <c>SetPosition</c>, <c>RejectedAuthority</c>,
/// <c>RejectedData</c>, and "not classified at all" (<see langword="null"/>) —
/// falls through to the untouched legacy App routing. "Not Interpolate" must
/// never be read as "far".
/// belongs to <see cref="RuntimeRemoteFarSnapPosition"/>, which owns the far
/// (&gt;=96 m) snap from C4 route 4b-2 and states the policy for the
/// leftovers. "Not Interpolate" must never be read as "far".
/// </para>
/// </summary>
internal static class RuntimeRemoteSteadyStatePosition
@ -157,9 +156,23 @@ internal static class RuntimeRemoteSteadyStatePosition
/// does NOT run on the airborne no-op.
///
/// <para>
/// Route-gated so it can only fire for a classification route 4a owns:
/// every other classification still arms the leash through the legacy
/// pre-operation call site, unchanged, until 4b moves them too.
/// Route-gated by
/// <see cref="RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint"/>
/// — route 4a's two dispositions plus (C4 route 4b-2) the far snap. Every
/// other classification still arms the leash through the legacy
/// pre-operation call site, unchanged, until 4b-3 moves them too. The
/// legacy site reads the SAME predicate, so no classification can be
/// armed twice or left unarmed.
/// </para>
///
/// <para>
/// This deliberately does not consult the outcome of the operation it
/// follows. Retail's far branch returns 1 @0x005163E8 unconditionally —
/// <c>MoveOrTeleport</c> discards <c>SetPositionSimple</c>'s
/// <c>enum SetPositionError</c> return entirely — so
/// <c>HandleReceivedPosition</c> arms the leash even when the placement
/// FAILED. "Arm only on commit" is the natural misreading and is the same
/// shape as the already-recorded unarmed-leash bug.
/// </para>
/// </summary>
internal static bool TryArmConstraintAfterOperation(
@ -168,7 +181,8 @@ internal static class RuntimeRemoteSteadyStatePosition
{
ArgumentNullException.ThrowIfNull(remote);
if (route is not { } selected
|| !OwnsSteadyState(selected)
|| !RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
selected)
|| !selected.ConstrainAfterRouting
|| remote.Host is not { } host)
{

View file

@ -1,4 +1,4 @@
using System.Collections.Immutable;
using System.Collections.Immutable;
using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using AcDream.Content;
@ -1346,6 +1346,14 @@ internal sealed class RuntimeSetPositionState : IDisposable
{
EnsureNotDisposed();
ArgumentNullException.ThrowIfNull(record);
// B2 dependency (C4 route 4b-2): the `_operations.ContainsKey`
// refusal is what makes an INHERITED park impossible for any
// operation begun here, which is in turn what lets
// RuntimeRemotePlacementDriveController document a pre-engine
// Rejected as unreachable on its far-snap path (the
// WakeableLostCell-only `with` rewrite in
// SubmitPreparedPlacementCore cannot apply). Loosening it to admit a
// second concurrent operation invalidates that argument.
if (record.Key is not { } key
|| _operations.ContainsKey(key)
|| HasRetainedCompletion(key))
@ -1377,6 +1385,12 @@ internal sealed class RuntimeSetPositionState : IDisposable
|| operation.Stage is not RuntimeEntityPlacementStage
.AwaitingPreparation
|| !ReferenceEquals(operation.Record, record)
// B2 dependency (C4 route 4b-2): this `is not null` is what makes
// DormantLocalActivation and "the record has a body" mutually
// exclusive, which is the step
// RuntimeRemotePlacementDriveController's pre-engine-Rejected
// argument uses to dismiss the AwaitingCell stage divergence
// between PrepareMover and SubmitPreparedPlacementCore.
|| record.PhysicsBody is not null
|| !IsCurrent(operation)
|| body.InWorld
@ -1560,6 +1574,15 @@ internal sealed class RuntimeSetPositionState : IDisposable
command = default;
// Round 3 audit: safe - fresh lookup + Token check earlier in this
// SAME guard clause precede IsCurrent, nothing reentrant in between.
//
// B2 dependency (C4 route 4b-2): this guard clause SHADOWS
// SubmitPreparedPlacementCore's entry validation one call later, and
// RuntimeRemotePlacementDriveController's far-snap default arm
// documents a pre-engine `Rejected` as unreachable BECAUSE of that
// shadowing - every term dropped from here re-surfaces there as a
// non-storing Rejected instead of a storing RejectedAuthority. The
// one term it deliberately does NOT shadow is the null-body test;
// that caller's own entry check covers it, and says so.
if (!token.IsValid
|| !_operations.TryGetValue(token.Entity, out Operation? operation)
|| operation.Token != token
@ -2977,7 +3000,12 @@ internal sealed class RuntimeSetPositionState : IDisposable
collisionGenerationOverride:
quiescence!.Token.CollisionGeneration,
collisionPrefixOverride:
quiescence.Token.LandblockPrefix);
quiescence.Token.LandblockPrefix,
// A quiescence park is a momentarily-unavailable destination,
// not a world going away, so it is eligible for the rollback.
// ParkDeferred is what decides whether the cell it would
// restore into is itself quiescing.
restorableOnCancel: true);
}
PhysicsSetPositionResult result;
@ -3028,7 +3056,11 @@ internal sealed class RuntimeSetPositionState : IDisposable
collisionGenerationOverride:
queriedQuiescence!.Token.CollisionGeneration,
collisionPrefixOverride:
queriedQuiescence.Token.LandblockPrefix);
queriedQuiescence.Token.LandblockPrefix,
// Same reason as the pre-sweep park above, plus the swept
// footprint: ResultTouchesPrefix scans every QueriedCellIds
// entry, and that set spans NEIGHBOUR landblocks.
restorableOnCancel: true);
}
operation.Result = result;
if (!result.IsSuccessful)
@ -3048,9 +3080,9 @@ internal sealed class RuntimeSetPositionState : IDisposable
_preparedMovers[operation.Key] = canonicalRequest;
if (result.IsDeferred)
// The one restorable park: the destination is not placeable right
// now. Every other ParkDeferred caller is a quiescence/retirement
// withdrawal - see ParkDeferred's restorableOnCancel doc.
// The plain restorable park: the destination is not placeable
// right now. The only NON-restorable caller is the retirement
// park - see ParkDeferred's restorableOnCancel doc.
return ParkDeferred(operation, result, restorableOnCancel: true);
if (!CommitCanonical(operation, result))
@ -3418,6 +3450,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
/// cancelled park cannot leave the entity invisible and intangible with
/// nothing able to wake it.
///
/// <para>Residency specifically is re-tested against the live quiescence
/// map HERE as well as at park time, because this runs on a later packet
/// for a retained park - see the inline comment for the window. The
/// <c>InWorld</c>/transient/clock half is restored unconditionally: it is
/// per-entity simulation state, not a claim on any landblock's collision
/// generation, so it cannot block a retirement.</para>
///
/// <para><b>The committed pose is deliberately left alone.</b>
/// <see cref="ParkDeferred"/> snaps the body to the destination before
/// withdrawing, which is precisely retail's lost-cell behaviour:
@ -3476,13 +3515,39 @@ internal sealed class RuntimeSetPositionState : IDisposable
}
if (withdrawal.ClockActive)
_entities.ResumeObjectClock(record);
if (residentCellId != 0u && record.FullCellId == 0u)
// Round 4 (D6): the park-time decision in ParkDeferred is a SNAPSHOT,
// and for a RETAINED park it can be stale by the time this runs. The
// far snap's park is cancelled synchronously (SubmitAndResolve's
// DeferredCell arm), so its window is nanoseconds - but route 2's
// park is retained (AwaitingCommitWake) and this restore lands on the
// NEXT packet's merge-time Forget, ~150 ms later at ACE's 5-10 Hz.
// Streaming opens a quiescence per landblock mutation, so a prefix
// that was clean when the park was taken can be quiescing now.
// Re-testing here is what makes AP-136's claim - "the rollback never
// re-admits a spatial root into a retiring prefix" - exact at the
// moment residency is actually written, instead of true only as of
// when the park was taken. The `!= 0u` test ahead of it is also the
// C3c-F3 sentinel guard ParkDeferred applies: AdjustToOutside zeroes
// the cell id on map-edge failure, and prefix 0x00000000 is the
// legitimate corner landblock.
bool residencyRestored = false;
if (residentCellId != 0u
&& !IsCollisionPrefixQuiescing(residentCellId)
&& record.FullCellId == 0u)
{
_entities.SetFullCell(
record,
residentCellId,
(residentCellId & 0xFFFF0000u) | 0xFFFFu);
_physics.AcknowledgeSpatialProjection(record, spatial: true);
residencyRestored = true;
}
// Issue #309's connected-gate confirmation signal; see the [park]
// line's own comment in ParkDeferred.
if (PhysicsDiagnostics.ProbeParkEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[park-restore] guid=0x{record.ServerGuid:X8} restoreCell=0x{residentCellId:X8} inWorld={withdrawal.InWorld} residency={residencyRestored}"));
}
}
@ -3687,7 +3752,13 @@ internal sealed class RuntimeSetPositionState : IDisposable
collisionGenerationOverride:
quiescence?.Token.CollisionGeneration,
collisionPrefixOverride:
quiescence?.Token.LandblockPrefix);
quiescence?.Token.LandblockPrefix,
// Stated, not inherited from the parameter default: a
// RETIREMENT park withdraws the entity because its own world
// is going away, which is the one shape AP-136's reason
// covers exactly. Round 3 (m5) - the blanket doc that used to
// guard a forgetful caller here is gone.
restorableOnCancel: false);
if (_pendingProjection.TryGetValue(
parked.Projection.Sequence,
out RuntimePlacementProjectionSnapshot staged))
@ -4252,20 +4323,97 @@ internal sealed class RuntimeSetPositionState : IDisposable
/// 0x00000000: a corner-landblock quiescence override read as "absent",
/// so <see cref="Operation.CollisionQuiescenceHeld"/> derived false and
/// the parked operation skipped the QuiescenceHeld stage entirely.
/// </summary>
/// <summary>
/// <paramref name="restorableOnCancel"/> opts this park into
/// <see cref="Forget"/>'s rollback. It is true for exactly one caller:
/// the plain "destination is not placeable right now" park, which is the
/// one that strands an entity when a later packet cancels it.
///
/// <para>It is deliberately FALSE for every quiescence/retirement park.
/// Those withdraw the entity precisely because its own landblock prefix
/// is being retired or held, so restoring canonical residency there would
/// re-admit a spatial root into the prefix that is trying to quiesce and
/// block the retirement outright - a streaming stall traded for a
/// stranded entity. Those entities are withdrawn because their world is
/// going away, not because a destination was momentarily unavailable.</para>
/// <para>
/// <paramref name="restorableOnCancel"/> is the caller's HALF of the
/// opt-in into <see cref="Forget"/>'s rollback: "this park is a
/// momentarily-unavailable destination, not a world going away". It is
/// true for the plain "destination is not placeable right now" park and
/// for <see cref="SubmitPreparedPlacementCore"/>'s two collision-prefix
/// QUIESCENCE parks; it is false for a retirement park
/// (<see cref="ParkCollisionResidents"/>), which withdraws the entity
/// precisely because its own world is being retired.
/// </para>
///
/// <para>
/// The OTHER half is decided here, after <see cref="PhysicsBody.SnapToCell"/>
/// has committed the parked pose, and it is the only test AP-136's stated
/// reason actually needs: the rollback restores canonical residency at
/// <c>body.CellPosition.ObjCellId</c>
/// (<see cref="RestoreParkWithdrawal"/>), so it is safe exactly when THAT
/// cell's prefix is not itself quiescing
/// (<see cref="IsCollisionPrefixQuiescing"/> — the pre-flight's own
/// predicate). Deciding it here rather than at the call sites is what
/// makes the claim true rather than approximately true, for two
/// independent reasons (C4 route 4b-2, round-3 correction A1):
/// </para>
/// <list type="number">
/// <item><description>A blocking-quiescence token is the MINIMUM-
/// <c>OperationId</c> match, not the only live one —
/// <c>_collisionPrefixQuiescence</c> is a per-prefix dictionary and a
/// streaming recentre opens one per landblock mutation. Comparing the
/// restore cell against that single token proves nothing about the
/// others, so it could answer "safe" for a destination that is itself
/// retiring. (Round 4 bounded the reach rather than asserting it. On a
/// FIRST submit it is unreachable, and provably so: the pre-engine check
/// can match at most the request's own prefix and its
/// <c>CurrentCellId</c>, which are the same prefix there, so its token IS
/// the restore cell's; and the post-sweep check runs only once the
/// pre-engine check has proven the destination prefix is NOT quiescing,
/// so its token can never be. What is NOT ruled out is a RETAINED
/// operation whose <c>CurrentCellId</c> has since been moved to another
/// landblock by a non-Position rebucket — see the arm's own scoping
/// below — with concurrent quiescences on both prefixes. That shape is
/// unmeasured, and it is exactly the shape the far arm's pre-flight
/// refuses on the only route that has one. Testing the live map here
/// makes the decision free of the question rather than dependent on
/// it.)</description></item>
/// <item><description>The restore cell is not the caller's
/// <c>result.CellId</c>. <see cref="PhysicsBody.StageDormantCellFrame"/>
/// runs <see cref="AcDream.Core.Physics.LandDefs.AdjustToOutside"/> for
/// any outdoor cell, whose own contract says the re-derived id may belong
/// to a NEIGHBOUR landblock. Only the post-snap value is the value the
/// rollback will use. This is the reachable half, and it is pinned by
/// <c>QuiescingOwnPrefix_SeamCrossingParkIsRestoredAtTheReDerivedNeighbourCell</c>:
/// a wire (cell, position) pair whose position lies past its own named
/// block's seam is exactly the pair #107's re-derivation exists to
/// distrust, and the entity is stranded without this.</description></item>
/// </list>
///
/// <para>
/// AP-136's blanket "no quiescence park is restorable" was over-broad
/// because <see cref="TryGetBlockingQuiescence"/> also fires on prefixes a
/// placement merely TOUCHES — chiefly any <c>QueriedCellIds</c> entry
/// (a NEIGHBOUR the sweep reached across a seam;
/// <c>CellTransit.AddOutsideCell</c> has no same-block filter), and the
/// request's <c>CurrentCellId</c>.
/// </para>
///
/// <para>
/// <b>What <c>CurrentCellId</c> names, scoped to the submit (round 4,
/// D5).</b> On the FIRST submit of an accepted Position it names the
/// DESTINATION, not a departed source: both accepted-Position callers
/// commit the accepted wire cell to <c>record.FullCellId</c> ahead of the
/// drive (route 2 through the merge, the graphical remote path through
/// <c>LiveEntityRuntime.RebucketLiveEntity</c>), and this request field is
/// read from that same record field. It is NOT invariant for a RETAINED
/// operation: both drives re-submit from their own cadence pump with no
/// fresh merge in between (<c>SubmitAndResolve</c> re-reads the record as
/// it then stands), and <c>RemoteTeleportController</c>'s rollback is a
/// shipped writer that rebuckets <c>FullCellId</c> back to the
/// PRE-teleport landblock. <c>RuntimeRemotePlacementDriveController</c>'s
/// <c>CanAttemptDestination</c> doc states this correctly; treat the arm
/// as live, not as dead code.
/// </para>
///
/// <para>
/// In those touched-prefix
/// shapes the retiring prefix is not the one residency is restored into,
/// so the rollback pins nothing, while leaving the park non-restorable
/// stranded the entity (<c>InWorld = false</c>, clock suspended,
/// <c>FullCellId = 0</c>) with the only operation able to wake it
/// destroyed by its own next accepted Position.
/// </para>
/// </summary>
private RuntimeSetPositionOutcome ParkDeferred(
Operation operation,
@ -4276,21 +4424,55 @@ internal sealed class RuntimeSetPositionState : IDisposable
bool restorableOnCancel = false)
{
PhysicsBody body = operation.Body!;
// Captured BEFORE the withdrawal below clears it. Only the
// WITHDRAWAL is captured, never the pose: the SnapToCell on the next
// line deliberately commits the destination pose and that commit
// stands, exactly as retail's store_position @0x00515CE2 commits it
// on the lost-cell branch and never un-commits it.
operation.ParkWithdrawal = new ParkWithdrawal(
Captured: restorableOnCancel,
InWorld: body.InWorld,
TransientState: body.TransientState,
ClockActive: operation.Record.ObjectClock.IsActive);
// Read BEFORE the withdrawal below clears them - and before the
// SnapToCell, which sets InWorld = true itself. Only the WITHDRAWAL
// is captured, never the pose: the SnapToCell deliberately commits
// the destination pose and that commit stands, exactly as retail's
// store_position @0x00515CE2 commits it on the lost-cell branch and
// never un-commits it.
bool priorInWorld = body.InWorld;
TransientStateFlags priorTransientState = body.TransientState;
bool priorClockActive = operation.Record.ObjectClock.IsActive;
body.Orientation = result.Orientation;
body.SnapToCell(
result.CellId,
result.Position,
result.CellLocalPosition);
// The restorable decision reads the POST-snap cell, because that is
// the cell RestoreParkWithdrawal will restore residency into - see
// this method's own doc for why the caller's result.CellId and a
// single blocking-quiescence token are both the wrong inputs.
//
// C3c-F3 sentinel guard (round 4, N1): cell id 0 is
// AdjustToOutside's map-edge FAILURE sentinel
// (LandDefs.AdjustToOutside's `cellId = 0u; return false` tail), and
// prefix 0x00000000 is simultaneously the LEGITIMATE corner landblock
// (0,0) - which is exactly why BeginCollisionPrefixQuiescence rejects
// landblock id 0 rather than treating it as "absent". So the
// quiescence map must never be consulted with the sentinel: a corner
// landblock quiescing would otherwise make every map-edge failure
// read as "restoring into a retiring prefix". A sentinel cell has no
// residency to re-admit anywhere - RestoreParkWithdrawal applies the
// same `!= 0u` test before it restores residency at all - so the
// decision falls back to the caller's half alone.
uint parkedCellId = body.CellPosition.ObjCellId;
operation.ParkWithdrawal = new ParkWithdrawal(
Captured: restorableOnCancel
&& (parkedCellId == 0u
|| !IsCollisionPrefixQuiescing(parkedCellId)),
InWorld: priorInWorld,
TransientState: priorTransientState,
ClockActive: priorClockActive);
// Issue #309's connected-gate confirmation signal. Off by default;
// one line per park, nothing at all for a committing placement.
if (PhysicsDiagnostics.ProbeParkEnabled)
{
string parkCause = collisionPrefixOverride is uint blockedPrefix
? FormattableString.Invariant($"quiescence:0x{blockedPrefix:X8}")
: "unplaceable";
Console.WriteLine(FormattableString.Invariant(
$"[park] guid=0x{operation.Record.ServerGuid:X8} cause={parkCause} resultCell=0x{result.CellId:X8} restoreCell=0x{parkedCellId:X8} eligible={restorableOnCancel} captured={operation.ParkWithdrawal.Captured}"));
}
body.InWorld = false;
body.TransientState &= ~TransientStateFlags.Active;
if (operation.Record.RemoteMotion is IRuntimeRemotePlacement remote)
@ -5231,6 +5413,17 @@ internal sealed class RuntimeSetPositionState : IDisposable
orientation.Y,
orientation.Z);
/// <summary>
/// B2 dependency (C4 route 4b-2): all SEVEN version dimensions below are
/// load-bearing for <c>RuntimeRemotePlacementDriveController</c>'s
/// documented invariant that a PRE-engine <c>Rejected</c> can never reach
/// its far-snap default arm — it relies on <see cref="PrepareMover"/>
/// catching every authority divergence one call earlier, as
/// <c>RejectedAuthority</c> (which STORES), rather than
/// <see cref="SubmitPreparedPlacementCore"/> catching it as
/// <c>Rejected</c> (which does not). Dropping a dimension here silently
/// moves that shape to the non-storing arm.
/// </summary>
private static bool IsPreparationAuthorityCurrent(
Operation operation,
in MoverPreparationAuthority authority) =>

View file

@ -64,10 +64,20 @@ internal enum RuntimeAcceptedPositionExecutionStatus : byte
Contention,
/// <summary>
/// The destination landblock's collision generation was not ready; the
/// operation parked. The ack fires later, once
/// The operation parked instead of committing. The ack fires later, once
/// <see cref="RuntimeAcceptedPositionDriveController.Advance"/> observes
/// the deferred commit.
///
/// Round 4 (2026-08-04, N3): the previous text named only one cause —
/// "the destination landblock's collision generation was not ready" —
/// which is <c>SubmitPreparedPlacementCore</c>'s plain
/// <c>result.IsDeferred</c> park. Two other causes reach this same
/// status and are asserted by this route's own tests: the PRE-engine
/// collision-prefix quiescence park (the destination prefix is
/// mid-retirement) and the POST-sweep one (a landblock the sweep merely
/// TOUCHED is mid-retirement, which can rewrite an otherwise
/// about-to-commit placement). All three are "parked, not committed";
/// none of them means the placement failed.
/// </summary>
DeferredCell,

View file

@ -1,3 +1,4 @@
using System.Numerics;
using AcDream.Content;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -19,16 +20,41 @@ namespace AcDream.Runtime.Session;
/// <see cref="RuntimeRemotePlacementDriveController"/> can
/// ask, BEFORE attempting any canonical SetPosition, whether the accepted
/// destination is one this host can actually place a remote into right now.
///
/// <para>
/// <b>Headless is not a live consumer, stated plainly (C4 route 4b-2, review
/// fix).</b> The headless implementation above exists and is exact, but
/// NOTHING in <c>AcDream.Headless</c> constructs a
/// <see cref="RuntimeRemotePlacementDriveController"/>: the graphical
/// composition root (<c>SessionPlayerComposition</c>) is the only
/// construction site, and
/// <c>RuntimeLiveEntitySessionController.OnPositionUpdated</c> returns early
/// for every non-local GUID before any remote routing could occur. Route
/// 4b-2's contract item 6 ("both hosts drive the identical Runtime entry
/// point, or the divergence is stated plainly rather than satisfied
/// vacuously") is therefore satisfied by THIS statement, not by a shared
/// caller. The far snap is a graphical-host-only path until a headless
/// remote-motion consumer exists.
/// </para>
/// </summary>
public interface IRuntimeRemotePlacementServiceWindow
{
/// <summary>
/// True when <paramref name="landblockId"/>'s collision is currently
/// published by this host, so a canonical SetPosition into it can be
/// attempted without risking an un-wakeable <c>DeferredCell</c> park (see
/// published by this host, so a canonical SetPosition into it is worth
/// attempting rather than certain to park (see
/// docs/research/2026-08-04-c4-route-4b-1-contract.md's "central
/// decision"). <paramref name="landblockId"/> may be a full cell id — the
/// implementation canonicalizes to the containing landblock.
///
/// <para>
/// C4 route 4b-2 delta review: this answer is an OPTIMISATION only. It
/// cannot be a correctness gate — see
/// <see cref="RuntimeRemotePlacementDriveController"/>'s
/// <c>CanAttemptDestination</c> for the two Core predicates that are
/// strictly broader than any pre-flight, one of which reads a sweep
/// footprint that does not exist until the sweep has run.
/// </para>
/// </summary>
bool IsWithinServiceWindow(uint landblockId);
}
@ -36,36 +62,208 @@ public interface IRuntimeRemotePlacementServiceWindow
/// <summary>
/// Typed yields for
/// <see cref="RuntimeRemotePlacementDriveController.TryExecuteAcceptedRemotePosition"/>.
///
/// <para>
/// <b>The partition this enum exists to express (C4 route 4b-2, delta review
/// MAJOR A).</b> Retail's <c>CPhysicsObj::SetPositionInternal</c> @0x00515BD0
/// has exactly two shapes, and the earlier three-value enum could not tell
/// them apart:
/// </para>
/// <list type="bullet">
/// <item><description><b>Stores.</b> <c>AdjustPosition</c> yielded no
/// transition @0x00515C1D, so the resolve never ran:
/// <c>prepare_to_leave_visibility</c> @0x00515CDA,
/// <c>store_position(this, arg2)</c> @0x00515CE2,
/// <c>CObjectMaint::GotoLostCell</c> @0x00515CF2, transient-state clear
/// @0x00515CF7, <c>return 0</c> @0x00515D07. The object MOVES to the accepted
/// destination.</description></item>
/// <item><description><b>Does not store.</b> The resolve DID run and refused:
/// <c>CheckPositionInternal == 0</c> @0x00515C85 →
/// <c>handle_all_collisions</c> @0x00515CC2 →
/// <c>return ((eax_14 - eax_14) &amp; 2) + 2</c> @0x00515CD5 (2 or 4), and
/// <c>sphere_path.curr_cell == 0</c> @0x00515C8F → <c>return 3</c>
/// @0x00515CB2. Both leave the object where it was.</description></item>
/// </list>
/// <para>
/// <see cref="RuntimeRemotePlacementExecutionStatusExtensions.StoresAcceptedDestination"/>
/// is the single predicate that reads this partition; every value below
/// states which side it falls on and WHY, so a new value cannot be added
/// without deciding.
/// </para>
/// </summary>
internal enum RuntimeRemotePlacementExecutionStatus : byte
{
/// <summary>Out of this route's scope: not a disposition this
/// controller owns (<see cref="RuntimeRemotePlacementDriveController.OwnsPlacement"/>),
/// no canonical body, or no incarnation key.</summary>
/// <summary>
/// Out of this route's scope: not a disposition this controller owns
/// (<see cref="RuntimeRemotePlacementDriveController.OwnsPlacement"/>),
/// no canonical body, or no incarnation key. <b>Stores</b> — nothing ran.
///
/// <para>
/// The three sub-cases are not symmetric, and round 3 (correction m4)
/// states each. <i>Not owned</i> stores the accepted destination like any
/// other never-ran outcome. <i>No canonical body</i> stores nothing:
/// there is nowhere to write, which
/// <see cref="RuntimeRemotePlacementDriveController.StoreAcceptedDestinationPose"/>
/// tests directly rather than inferring from this value. <i>No
/// incarnation key, with a body</i> DOES write, and that is correct on
/// retail's partition — nothing ran, so the pose must advance — but it is
/// unreachable in production regardless: the far arm requires an
/// already-classified route, and
/// <c>RuntimeAcceptedPositionRouteRequests.TryBuild</c> refuses to build
/// one for a record whose <c>Key</c> is null, so no caller can hold a
/// route for a keyless record.
/// </para>
/// </summary>
NotApplicable,
/// <summary>
/// The central decision (see the class doc on
/// <see cref="RuntimeRemotePlacementDriveController"/>): the accepted
/// destination is not one this host can currently collision-publish, or
/// the canonical SetPosition attempted anyway and Core still deferred it.
/// No operation is retained either way — the entity keeps its last
/// committed pose and waits for the next accepted Position, which for a
/// remote is a 5-10 Hz stream away.
/// <see cref="RuntimeRemotePlacementDriveController"/>): the pre-flight
/// declined to attempt this destination at all, so no operation was ever
/// begun and the canonical resolve never ran.
///
/// <para>
/// <b>Stores.</b> This is exactly retail's no-transition branch — "no cell
/// for it here", never "do not move". See
/// <see cref="RuntimeRemotePlacementDriveController.ApplyAcceptedRemoteFarSnap"/>.
/// </para>
/// </summary>
Refused,
/// <summary>The entity already holds an active operation — a concurrent
/// placement authority (portal/teleport/another host route) or this
/// controller's own still-outstanding preparation retry.</summary>
/// <summary>
/// <see cref="RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement"/>
/// refused, or this controller retained a preparation retry.
///
/// <para>
/// Round 3 (correction m2) — the producers this route can actually reach,
/// not the two the earlier text named. The Begin refuses when the entity
/// already holds an active operation (a concurrent placement authority —
/// portal, teleport, another host route), when it holds a RETAINED
/// COMPLETION that has not been acknowledged yet
/// (<c>HasRetainedCompletion</c> — ordinary right after a
/// <see cref="Committed"/> far snap whose projection sink declined), or
/// when the record is no longer the current incarnation
/// (<c>!IsCurrent</c>). Its remaining refusals cannot fire from here:
/// the expected position-authority version is read off the same record
/// one statement earlier, and the accepted destination was already
/// required non-null by the pre-flight. This controller adds the fourth
/// producer: a retryable preparation status
/// (<c>RetrySetupUnavailable</c>/<c>RetryWorldFrameUnavailable</c>).
/// </para>
///
/// <para>
/// <b>Stores</b> in every one of those shapes: all four are decided
/// before <c>_physics.Engine.SetPosition</c> is called for THIS packet,
/// and retail never waits for an asset before committing the pose.
/// (<see cref="RuntimeRemotePlacementDriveController.StoreAcceptedDestinationPose"/>
/// re-validates currency itself, so the <c>!IsCurrent</c> producer cannot
/// write through a superseded record.)
/// </para>
/// </summary>
Contention,
/// <summary>The canonical SetPosition committed synchronously.</summary>
/// <summary>
/// Preparation refused TERMINALLY, before anything was submitted:
/// <c>RuntimeSetPositionMoverPreparationStatus.RejectedAuthority</c> (the
/// operation/authority pair went stale between Begin and prepare) or
/// <c>InvalidData</c> (the accepted frame fails
/// <c>PositionFrameValidation</c>, or the prepared mover could not be
/// built).
///
/// <para>
/// <b>Stores.</b> The engine was never called — this is the same
/// "placement never executed" state as <see cref="Refused"/>, reached one
/// stage later. Kept distinct from <see cref="RejectedByPlacement"/>
/// because that one is the opposite side of the partition.
/// </para>
/// </summary>
RejectedPreparation,
/// <summary>The canonical SetPosition committed synchronously.
/// <b>Does not store</b>: the committed pose IS the settled pose, and it
/// is already on the canonical body.</summary>
Committed,
/// <summary>Rejected/cancelled by Core (invalid prepared data, authority
/// displaced mid-submit).</summary>
Rejected,
/// <summary>
/// Core parked the placement (<c>RuntimeSetPositionStatus.DeferredCell</c>)
/// and this controller cancelled-and-restored it immediately rather than
/// retaining a watch — no park survives this controller.
///
/// <para>
/// <b>Does not store.</b> <c>ParkDeferred</c> has ALREADY performed
/// retail's <c>store_position</c>: it snaps the body to the parked result
/// before withdrawing (and <c>RestoreParkWithdrawal</c> deliberately
/// leaves that pose alone). For the pre-sweep quiescence park that result
/// is the accepted destination; for the post-sweep one it is the
/// COLLISION-SETTLED <c>spherePath.CurPos</c>, and writing the raw
/// destination over it would undo the settle (delta review N2). The two
/// <c>DeferredCell</c> returns that park nothing
/// (<c>InheritedLostDeadline</c>, <c>WakeableLostCell</c>) are
/// unreachable here: <c>TryBeginExclusiveAuthoredPlacement</c> refuses
/// whenever an operation already exists for the key, so neither flag can
/// be inherited, and this controller's own retained entries are
/// preparation retries that have not parked.
/// </para>
/// </summary>
Deferred,
/// <summary>
/// The canonical placement EXECUTED and did not commit:
/// <c>RuntimeSetPositionStatus.Rejected</c> (the engine's own sweep
/// refused the destination — <c>PhysicsEngine.SetPosition</c> returned a
/// non-Ok error, acdream's port of @0x00515CD5/@0x00515CB2) or
/// <c>Cancelled</c> (authority was displaced after the engine ran, which
/// includes the case where <c>CommitCanonical</c> had already SETTLED the
/// body).
///
/// <para>
/// <b>Does not store.</b> Retail's matching returns (2/3/4) leave the
/// object where it was, and the <c>Cancelled</c>-after-commit shape has a
/// freshly settled pose (contact plane, step-down) that a raw destination
/// write would destroy.
/// </para>
///
/// <para>
/// Round 3 (correction B2): "EXECUTED" is a reachability property of the
/// producers this route can reach, not a structural guarantee Core
/// enforces. <c>RuntimeSetPositionStatus.Rejected</c> has two other,
/// PRE-engine producers in <c>SubmitPreparedPlacementCore</c>, both
/// unreachable through this controller; the <c>default:</c> arm of
/// <see cref="RuntimeRemotePlacementDriveController.SubmitAndResolve"/>
/// enumerates exactly what each depends on, and why the failure direction
/// if one is ever broken is the safe one.
/// </para>
/// </summary>
RejectedByPlacement,
}
internal static class RuntimeRemotePlacementExecutionStatusExtensions
{
/// <summary>
/// True when retail's <c>store_position</c> @0x00515CE2 is what this
/// outcome corresponds to: the canonical placement never reached the
/// engine, so nothing has committed a pose to the body and the remote
/// would otherwise be frozen at a superseded one. See the enum's own doc
/// for the retail partition and for why each value falls where it does.
///
/// <para>
/// Written as an exhaustive switch rather than a set test so adding a
/// value is a compile-time decision, not a silent default.
/// </para>
/// </summary>
internal static bool StoresAcceptedDestination(
this RuntimeRemotePlacementExecutionStatus status) =>
status switch
{
RuntimeRemotePlacementExecutionStatus.NotApplicable => true,
RuntimeRemotePlacementExecutionStatus.Refused => true,
RuntimeRemotePlacementExecutionStatus.Contention => true,
RuntimeRemotePlacementExecutionStatus.RejectedPreparation => true,
RuntimeRemotePlacementExecutionStatus.Committed => false,
RuntimeRemotePlacementExecutionStatus.Deferred => false,
RuntimeRemotePlacementExecutionStatus.RejectedByPlacement => false,
_ => throw new ArgumentOutOfRangeException(nameof(status)),
};
}
/// <summary>
@ -85,7 +283,21 @@ internal enum RuntimeRemotePlacementExecutionStatus : byte
/// <c>SendPositionEvent</c> — <c>HandleReceivedPosition</c> @0x00453FD0 calls
/// it only on the local-player FORCE_POSITION branch. There is nothing here
/// resembling route 2's <c>PositionEventOwed</c>/<c>SendPositionEvent</c>
/// pair.</description></item>
/// pair. <b>The separate question 4b-1's review left open is now settled</b>
/// (C4 route 4b-2 review fix): the <c>SetPositionStruct</c> flag
/// <c>SEND_POSITION_EVENT_SPF = 0x1000</c> — which
/// <c>CPhysicsObj::SetPositionSimple</c> @0x005162B0 sets as part of
/// <c>0x1012</c> @0x005162C4 and which therefore reaches
/// <c>TryPrepareAndSubmitAuthoredPlacement</c> here — is <b>never read</b>.
/// The commit path <c>CPhysicsObj::SetPositionInternal</c>
/// @0x00515330-@0x00515593 takes only <c>this</c> and the <c>CTransition</c>
/// and never sees the struct; the flag-reading overload @0x00515BD0 tests
/// only bit 5 (<c>DONOTCREATECELLS_SPF = 0x20</c>) @0x00515BF8/@0x00515C6C;
/// and no site anywhere in the decomp tests bit 12 of a
/// <c>SetPositionStruct</c>. It is a carried, inert flag, entirely distinct
/// from <c>CommandInterpreter::SendPositionEvent</c> @0x006B4770 (dispatched
/// from @0x00454091, the local-player branch 4b-1 already
/// cleared).</description></item>
/// <item><description><b>No re-issue funnel.</b> Route 2 re-issues a dead
/// operation because a ForcePosition is a one-shot correction ACE never
/// repeats. A remote Position is a REPEATED stream — re-issuing packet N
@ -104,42 +316,66 @@ internal enum RuntimeRemotePlacementExecutionStatus : byte
/// </list>
///
/// <para>
/// <b>The central decision — refuse, do not park.</b>
/// <b>The central decision — avoid the PARK where it is cheap to, never the
/// tracking.</b>
/// <see cref="RuntimeSetPositionState"/>'s <c>DeferredCell</c> park
/// withdraws the entity from the world (<c>ParkDeferred</c> sets
/// <c>body.InWorld = false</c>, suspends the object clock, and publishes a
/// <c>Withdraw</c>) — and <c>RuntimeEntityObjectLifetime.TryApplyPosition</c>
/// calls <c>RuntimeSetPositionState.Forget</c> on EVERY subsequent accepted
/// Position for that same entity, regardless of disposition. <c>Forget</c>'s
/// <c>CancelCoreDeferred</c> removes the operation and rewrites the
/// <c>Withdraw</c> into a <c>Discard</c> WITHOUT restoring <c>InWorld</c>,
/// resuming the clock, or re-entering residency. Because ACE broadcasts a
/// remote's Position every 100-150 ms — almost always faster than the
/// collision-generation wake this park would need to resolve on its own —
/// any DeferredCell park opened here would be cancelled by the entity's own
/// next packet long before it could wake, leaving the entity invisible AND
/// intangible for the rest of the session. <see cref="_serviceWindow"/>
/// exists to prevent this controller from ever attempting a SetPosition
/// whose destination cannot be placed right now: <see cref="TryExecuteAcceptedRemotePosition"/>
/// Position for that same entity, regardless of disposition. Because ACE
/// broadcasts a remote's Position every 100-150 ms — almost always faster
/// than the collision-generation wake this park would need to resolve on its
/// own — any DeferredCell park opened here would be cancelled by the
/// entity's own next packet long before it could wake.
/// <b>Correcting this paragraph's earlier claim</b> (4b-1's review ordered it
/// and it shipped uncorrected): that cancellation no longer strands the
/// entity "invisible AND intangible for the rest of the session".
/// <see cref="CancelToken"/> passes <c>restoreCancelledPark: true</c>, and
/// <c>RuntimeSetPositionState.RestoreParkWithdrawal</c> rolls
/// <c>InWorld</c>, the object clock, and canonical residency back — the
/// AP-136 behaviour <c>634bc551</c> landed. The park is still worth avoiding
/// (a restored park re-shows the entity where retail keeps it hidden until
/// cell load, which is precisely what AP-136 records), so
/// <see cref="_serviceWindow"/> stays — but it is an OPTIMISATION, not a
/// correctness mechanism, and it is never the reason a remote stops
/// tracking the server (see
/// <see cref="ApplyAcceptedRemoteFarSnap"/>'s <c>store_position</c>
/// fallback). <see cref="TryExecuteAcceptedRemotePosition"/>
/// checks it BEFORE calling
/// <see cref="RuntimeSetPositionState.TryBeginExclusiveAuthoredPlacement"/>,
/// and if Core still returns <c>DeferredCell</c> despite the guard passing,
/// the operation is cancelled immediately rather than retained, so the
/// ledger still converges even though the guard's invariant did not hold for
/// that one packet. <b>Why the guard can still miss (the co-extensivity
/// finding and its residual):</b> a tier/residency-backed service window
/// (the graphical host's <c>GpuWorldState.IsNearTier</c>, headless's
/// that one packet.
/// </para>
///
/// <para>
/// <b>Why a pre-flight structurally cannot be the correctness mechanism (C4
/// route 4b-2, delta review MAJOR B/C).</b> A tier/residency-backed service
/// window (the graphical host's <c>GpuWorldState.IsNearTier</c>, headless's
/// collision-published check) is co-extensive with collision PUBLICATION in
/// both directions — verified by reading both tier-writer call sites
/// (promotion cannot read Near before collision commits) and the retirement
/// call site (the tier flips to Far as the FIRST, synchronous step of
/// retirement, strictly before collision-side withdrawal). It is NOT
/// co-extensive with a live in-place collision-prefix MUTATION that leaves
/// the tier/residency reading unchanged while the prefix quiesces —
/// <see cref="RuntimeSetPositionState"/>'s private <c>TryGetBlockingQuiescence</c>
/// is Core's own check for exactly that case, and it is what a placement can
/// still hit even after this guard passes. That one narrow window is the
/// residual this guard cannot close from outside Core.
/// co-extensive with a live in-place collision-prefix MUTATION, which is why
/// <see cref="CanAttemptDestination"/> also reads Core's own
/// <see cref="RuntimeSetPositionState.IsCollisionPrefixQuiescing"/>. But even
/// that pair only tests ONE prefix — the DESTINATION's — while Core's park
/// predicates are broader: <c>ResultTouchesPrefix</c> scans every
/// <c>QueriedCellIds</c> entry, a sweep footprint that provably spans
/// NEIGHBOUR landblocks (<c>CellTransit.AddOutsideCell</c> re-derives the
/// block id from the global lcoord and has no same-block filter). The sweep
/// footprint does not exist until the sweep has run, so no pre-flight can
/// close that half. <b>The correctness mechanism is therefore at the source:</b>
/// <c>SubmitPreparedPlacementCore</c>'s two quiescence parks are RESTORABLE,
/// and <c>ParkDeferred</c> decides whether the rollback is safe by testing
/// the cell it will actually restore into — after the snap, against EVERY
/// live quiescence — so the rollback re-admits nothing into ANY quiescing
/// prefix, which is the only thing AP-136's blanket non-restorable scoping
/// was protecting. This guard merely shrinks the reachable set. Recorded as
/// AP-138(2).
/// </para>
///
/// <para>
@ -198,6 +434,13 @@ internal sealed class RuntimeRemotePlacementDriveController
/// is mid-iteration can never corrupt its scratch buffer.
/// </summary>
private readonly List<RuntimeEntityKey> _awaitingAcknowledgementScratch = [];
/// <summary>
/// R9 review fix: <see cref="CountLivePending"/>'s own removal buffer,
/// kept separate from <see cref="_driveScratch"/> (owned by
/// <see cref="Advance"/>) for the same reason
/// <see cref="_awaitingAcknowledgementScratch"/> is.
/// </summary>
private readonly List<RuntimeEntityKey> _pendingScratch = [];
private bool _driving;
private object? _routeOwner;
@ -215,7 +458,7 @@ internal sealed class RuntimeRemotePlacementDriveController
_serviceWindow = serviceWindow
?? throw new ArgumentNullException(nameof(serviceWindow));
_entityObjects.RegisterRemotePlacementDriveOwnership(
() => _pending.Count);
CountLivePending);
// B4 review fix: a second, independent registration — multiple
// registrations sum (RegisterRemotePlacementDriveOwnership's own doc
// comment) — so the awaiting-acknowledgement dimension is visible in
@ -230,6 +473,9 @@ internal sealed class RuntimeRemotePlacementDriveController
/// that dimension is reported to the lifetime's ownership ledger via the
/// constructor's second <c>RegisterRemotePlacementDriveOwnership</c> call
/// (B4 review fix) and has no separate test-visible counter of its own.
/// Raw, unhealed: this is the map's literal size. The ownership ledger
/// reads <see cref="CountLivePending"/> instead, which prunes entries Core
/// has already retired (R9 review fix).
/// </summary>
internal int PendingCount => _pending.Count;
@ -398,10 +644,14 @@ internal sealed class RuntimeRemotePlacementDriveController
CreateObject.ServerPosition? destination =
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
if (destination is not { } accepted
|| !_serviceWindow.IsWithinServiceWindow(accepted.LandblockId))
|| !CanAttemptDestination(setPosition, accepted.LandblockId))
{
// The central decision: refuse rather than open a park this
// host's own service window could never wake.
// The central decision: skip a placement this host already knows
// will only park and be cancelled again. An OPTIMISATION — see
// CanAttemptDestination for the two Core predicates it cannot
// reproduce and where correctness actually lives — and the body
// still advances, because Refused is on the storing side of
// retail's partition.
return RuntimeRemotePlacementExecutionStatus.Refused;
}
@ -416,6 +666,218 @@ internal sealed class RuntimeRemotePlacementDriveController
return SubmitAndResolve(record, token, route);
}
/// <summary>
/// C4 route 4b-2: retail's far branch, end to end, for one remote whose
/// accepted Position already classified to
/// <see cref="RuntimeRemoteFarSnapPosition.OwnsFarSnap"/>. This is the
/// route's FIRST production caller of
/// <see cref="TryExecuteAcceptedRemotePosition"/>.
///
/// <code>
/// 005163c1 position_manager = this_1-&gt;position_manager;
/// 005163c9 if (position_manager != 0)
/// 005163cb PositionManager::StopInterpolating(position_manager);
/// 005163d9 CPhysicsObj::SetPositionSimple(this_1, arg2, 1);
/// 005163e8 return 1;
/// </code>
///
/// <para>
/// The ORDER is load-bearing and is why this lives here rather than being
/// assembled by a caller: <c>StopInterpolating</c> runs strictly BEFORE
/// the placement (@0x005163CB before @0x005163D9). acdream's
/// <c>InterpolationManager</c> queue is the analogue — a stale near
/// waypoint surviving a far snap would immediately drag the freshly
/// placed body back toward it on the next per-tick catch-up. The
/// classifier's own <c>StopInterpolating</c> flag
/// (<c>RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition</c>'s
/// remote tail, <c>StopInterpolating: !nearby</c>) is what gates it, so
/// the retail condition is read from the route rather than restated.
/// </para>
///
/// <para>
/// The destination is NOT a parameter: it is read from the canonical
/// merged snapshot by <see cref="TryExecuteAcceptedRemotePosition"/> and
/// resolved through Runtime's own accepted world frame
/// (<c>resolveWorldOffsetFromRuntimeFrame: true</c>). A caller's
/// separately-derived world position is not a second input — #283 proved
/// by measurement that App's streaming origin and Runtime's world frame
/// cannot disagree, and <c>LiveWorldOriginState.EnsureAgreesWithRuntimeFrame</c>
/// is terminal if they ever do.
/// </para>
///
/// <para>
/// <c>ConstrainTo</c> is deliberately NOT armed here.
/// <c>SmartBox::HandleReceivedPosition</c> owns retail's single arming
/// site (@0x00454272) for all three nonzero-returning branches; acdream's
/// analogue is
/// <see cref="RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation"/>,
/// called once by the caller after the routing block. Arming inside this
/// method would be the second site the route 4b scoping forbids.
/// </para>
///
/// <para>
/// <b>The <c>store_position</c> fallback (C4 route 4b-2 review fix — the
/// one root defect).</b> Every outcome for which the canonical placement
/// never reached the engine
/// (<see cref="RuntimeRemotePlacementExecutionStatusExtensions.StoresAcceptedDestination"/>)
/// still advances the body to the accepted destination pose, through
/// <see cref="StoreAcceptedDestinationPose"/>. Without it a refused,
/// contended, or preparation-rejected far snap left the body at its stale
/// pose with the interpolation queue ALREADY cleared by the
/// <c>StopInterpolating</c> above — a frozen remote that the next 5-10 Hz
/// packet simply reproduces, because nothing about the refusal reason
/// changes at packet cadence.
/// </para>
/// <para>
/// This is retail, not a symptom patch.
/// <c>CPhysicsObj::SetPositionInternal</c> @0x00515BD0 takes the
/// no-transition branch @0x00515C1D whenever <c>AdjustPosition</c>
/// resolves no cell, and that branch commits the destination pose —
/// <c>prepare_to_leave_visibility</c> @0x00515CDA,
/// <c>store_position(this, arg2)</c> @0x00515CE2,
/// <c>CObjectMaint::GotoLostCell</c> @0x00515CF2 — and returns 0
/// (success) @0x00515D07. "This host cannot resolve a cell for the
/// destination right now" is exactly that state; retail moves the object
/// and hides it pending cell load, and never leaves it standing at a
/// superseded pose. The deleted legacy far block also always tracked.
/// </para>
/// <para>
/// <b>The other half of retail's partition is honoured too (delta review
/// MAJOR A — correcting this comment's earlier claim that no non-commit
/// outcome could represent it).</b> Retail's failure returns that do NOT
/// store — <c>curr_cell == 0</c> @0x00515C8F/@0x00515CB2 and the blocked
/// <c>CheckPositionInternal</c> @0x00515C85/@0x00515CD5 — are reached
/// AFTER the resolve ran, and acdream CAN represent that:
/// <see cref="RuntimeRemotePlacementExecutionStatus.RejectedByPlacement"/>
/// is returned when <c>PhysicsEngine.SetPosition</c> itself refused, or
/// when authority was displaced after it ran (including after
/// <c>CommitCanonical</c> had already settled the body). Those do not
/// store, so a destination the engine's own sweep refuses no longer
/// teleports the canonical body into it, and a just-settled pose is never
/// overwritten. The residual is recorded as AP-138.
/// </para>
/// </summary>
internal RuntimeRemotePlacementExecutionStatus ApplyAcceptedRemoteFarSnap(
RuntimeEntityRecord record,
RemoteMotion remote,
in RuntimeAuthoritativePositionRoute route)
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(remote);
if (!RuntimeRemoteFarSnapPosition.OwnsFarSnap(route))
{
throw new ArgumentException(
"Only a remote far-snap classification (SetPositionSimple, "
+ "RemoteAuthoritative, Teleport-flagged) may be applied "
+ "through the far-snap arm; the caller must select the arm "
+ "with RuntimeRemoteFarSnapPosition.ResolveArm.",
nameof(route));
}
if (route.StopInterpolating)
remote.Interp.Clear();
RuntimeRemotePlacementExecutionStatus status =
TryExecuteAcceptedRemotePosition(record, route);
if (status.StoresAcceptedDestination())
StoreAcceptedDestinationPose(record);
return status;
}
/// <summary>
/// Retail <c>CPhysicsObj::store_position</c> @0x00515CE2, reached from
/// <c>SetPositionInternal</c>'s no-resolvable-cell branch @0x00515C1D.
/// Commits the SAME accepted destination
/// <see cref="TryExecuteAcceptedRemotePosition"/> reads — the canonical
/// merged snapshot, resolved through Runtime's own accepted world frame,
/// which is the exact pair
/// <c>RuntimeSetPositionMoverPreparer.TryBuild</c> composes for a
/// committed placement (<c>cellLocal + ShadowWorldOffset</c> in X/Y, the
/// authored Z, the authored quaternion). A caller's separately-derived
/// wire pose is deliberately not an input here either, for the same
/// reason the committed path does not take one.
///
/// <para>
/// <b>Currency is re-validated first (delta review MAJOR D).</b>
/// <see cref="CancelToken"/> publishes its cancellation receipt
/// SYNCHRONOUSLY, and the production placement-projection sink can delete
/// or replace this incarnation from inside that publish — so by the time
/// control returns to <see cref="ApplyAcceptedRemoteFarSnap"/> the record
/// may no longer be the canonical one for its GUID. Writing a pose
/// through a superseded incarnation is the exact class the R5 fix
/// introduced the rule for, and
/// <c>RuntimeSetPositionState.RestoreParkWithdrawal</c> already guards the
/// same way. The App-side re-validation happens only after this seam
/// returns, which is too late.
/// </para>
///
/// <para>
/// <b>The write target is the CANONICAL body (delta review N5).</b>
/// Retail's <c>store_position(this, arg2)</c> writes the CPhysicsObj's own
/// position, and acdream's canonical CPhysicsObj is
/// <see cref="RuntimeEntityRecord.PhysicsBody"/>.
/// <c>RemoteMotion.Body</c> IS that instance in production, by two
/// independent routes: <c>RuntimePhysicsState.GetOrCreateRemoteMotion</c>
/// passes <c>record.PhysicsBody</c> into the constructor, and when the
/// record has no body yet <c>SetRemoteMotion</c> ADOPTS the component's
/// private one as canonical (<c>InitializeNewPhysicsBody</c> +
/// <c>SetPhysicsBody</c>). So the divergent pair is not constructible
/// through the production seam and this change moves no production
/// behaviour — it removes the ability to write a pose into a body the
/// canonical record does not own, which is what
/// <see cref="RuntimeRemotePlacementExecutionStatus.NotApplicable"/>'s
/// no-body producer would otherwise permit for a caller-supplied
/// component. Reading the record directly makes "there is no canonical
/// body" and "there is nothing to store into" the same test.
/// </para>
///
/// <para>
/// Returns false only when there is nothing to write, or nowhere to write
/// it: no canonical body, no accepted destination on the snapshot, a
/// superseded incarnation, or no Runtime world frame yet. The last case
/// reuses Core's own #284 policy —
/// <see cref="RuntimePhysicsState.ThrowIfWorldFrameUnreachable"/> makes a
/// frame that can never arrive terminal rather than silent — so the only
/// surviving false there is the genuine pre-local-player-Create window,
/// which the far arm cannot reach anyway: the classifier needs a live
/// <c>player_distance</c> to emit <c>SetPositionSimple</c> at all, and
/// that requires the local movement controller to exist.
/// </para>
/// </summary>
private bool StoreAcceptedDestinationPose(RuntimeEntityRecord record)
{
if (!_entityObjects.Entities.IsCurrent(record)
|| record.PhysicsBody is not { } body)
{
return false;
}
CreateObject.ServerPosition? destination =
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
if (destination is not { } accepted)
return false;
if (!_entityObjects.Physics.TryGetWorldFrameOffset(
accepted.LandblockId,
out float worldOffsetX,
out float worldOffsetY))
{
_entityObjects.Physics.ThrowIfWorldFrameUnreachable(
accepted.LandblockId);
return false;
}
body.Position = new Vector3(
accepted.PositionX + worldOffsetX,
accepted.PositionY + worldOffsetY,
accepted.PositionZ);
body.Orientation = new Quaternion(
accepted.RotationX,
accepted.RotationY,
accepted.RotationZ,
accepted.RotationW);
return true;
}
/// <summary>
/// Host cadence pump: retries a preparation-only retry status
/// (<c>RetrySetupUnavailable</c>/<c>RetryWorldFrameUnavailable</c>) by
@ -457,24 +919,35 @@ internal sealed class RuntimeRemotePlacementDriveController
// B3 review fix: a retry can sit retained across many host
// cadence pumps (bounded only by how long the asset stayed
// unavailable) while its destination's collision publication
// retires out from under it. Re-check the SAME service-window
// guard the entry point uses BEFORE resubmitting — dropping
// (and cancelling the already-begun token) rather than
// resubmitting matches the entry point's own Refused
// semantics: no operation survives, the entity keeps its last
// committed pose, and the next packet supplies current truth
// on its own. Without this re-check a destination that fell
// out of the window would just keep coming back Contention
// forever (the asset source has not changed), never
// retires out from under it. Re-check the SAME pre-flight the
// entry point uses BEFORE resubmitting — dropping (and
// cancelling the already-begun token) rather than resubmitting
// matches the entry point's own Refused semantics: no
// operation survives and the next packet supplies current
// truth on its own. Without this re-check a destination that
// fell out of the window would just keep coming back
// Contention forever (the asset source has not changed), never
// converging even though the window already knows better.
//
// Delta review N1: this path is the entry point's Refused
// semantics, so it must ALSO be the entry point's
// store_position — correcting this comment's earlier claim
// that "the entity keeps its last committed pose". A retained
// retry whose destination leaves the window is exactly the
// freeze the fallback exists to prevent, one cadence pump
// later; the pre-flight is an optimisation here for the same
// reason it is one at the entry point, and it may never be the
// reason a remote stops tracking.
CreateObject.ServerPosition? destination =
pending.Record.Snapshot.Physics?.Position
?? pending.Record.Snapshot.Position;
if (destination is not { } accepted
|| !_serviceWindow.IsWithinServiceWindow(
|| !CanAttemptDestination(
setPosition,
accepted.LandblockId))
{
CancelToken(setPosition, pending.Token);
StoreAcceptedDestinationPose(pending.Record);
continue;
}
@ -517,8 +990,12 @@ internal sealed class RuntimeRemotePlacementDriveController
return RuntimeRemotePlacementExecutionStatus.Contention;
}
// Preparation refused TERMINALLY (RejectedAuthority/InvalidData).
// Nothing was submitted, so the engine never ran — this is the
// STORING side of retail's partition, one stage later than the
// pre-flight Refused. Delta review MAJOR A.
CancelToken(setPosition, token);
return RuntimeRemotePlacementExecutionStatus.Rejected;
return RuntimeRemotePlacementExecutionStatus.RejectedPreparation;
}
switch (outcome.Status)
@ -541,25 +1018,141 @@ internal sealed class RuntimeRemotePlacementDriveController
return RuntimeRemotePlacementExecutionStatus.Committed;
case RuntimeSetPositionStatus.DeferredCell:
// Central decision: this branch means the service-window
// guard passed but Core still deferred the destination — the
// narrow residual the class doc's central-decision paragraph
// explains (a live in-place collision-prefix quiescence the
// tier/residency guard cannot see from outside Core, per
// RuntimeSetPositionState's private TryGetBlockingQuiescence).
// Cancel immediately rather than retain a watch: no
// re-issue, no park survives this controller.
// Central decision: the pre-flight passed but Core still
// parked the destination — the residual the class doc's
// central-decision paragraph explains, now that the pre-flight
// is explicitly only an optimisation (it tests the DESTINATION
// prefix; Core's own post-sweep predicate also matches the
// whole swept footprint, which spans neighbouring landblocks
// and does not exist until the sweep has run). Cancel
// immediately rather than
// retain a watch: no re-issue, no park survives this
// controller, and CancelToken's restoreCancelledPark rolls the
// withdrawal back at the destination cell.
//
// ParkDeferred has already performed retail's store_position —
// it snaps the body to the parked result before withdrawing,
// and RestoreParkWithdrawal deliberately leaves that pose
// alone — so the fallback must NOT fire here. For the
// post-sweep park that result is the collision-SETTLED
// position and a raw destination write would undo the settle
// (delta review N2).
CancelToken(setPosition, token);
return RuntimeRemotePlacementExecutionStatus.Refused;
return RuntimeRemotePlacementExecutionStatus.Deferred;
default:
// Rejected/Cancelled — authority moved out from under this
// operation, so the body never moved.
// Rejected/Cancelled. Correcting this comment's earlier claim
// that "the body never moved" (delta review MAJOR A — it was
// the claim that made the fallback fire here): the statuses
// that reach this arm are returned AFTER
// _physics.Engine.SetPosition ran. Rejected is the engine's
// own sweep refusing the destination (retail @0x00515CD5 /
// @0x00515CB2, neither of which stores), and Cancelled
// includes the shape where CommitCanonical already SETTLED
// the body and only the projection ownership was displaced.
// Neither may be overwritten with the raw accepted
// destination.
//
// Round 3 (correction B2) — that is a REACHABILITY claim, not
// a structural one, so state what it rests on. Core's
// SubmitPreparedPlacementCore has three Rejected producers
// and only the post-engine one (`!result.IsSuccessful` after
// the SetPosition call) can reach here. Its two PRE-engine
// producers are unreachable through this controller because
// each is shadowed by a check PrepareMover already made, with
// nothing reentrant in between:
// * the entry validation's identity/authority terms (token
// ownership, stage, preparation authority) re-test what
// PrepareMover's own guard clause tested one call
// earlier, and IsPreparationAuthorityCurrent catches
// all seven of its version dimensions there first — as
// RejectedAuthority, i.e. RejectedPreparation, which
// DOES store. The one stage divergence PrepareMover
// admits and SubmitPreparedPlacementCore refuses is
// AwaitingCell, which additionally requires
// DormantLocalActivation — forbidden for a record that
// has a body, and this route requires one. Its remaining
// terms (finite GameTime, matching Kind/Portal, the exact
// PreparedCommand, the velocity-version echo) are
// satisfied by construction: TryPrepareAndSubmitAuthored-
// Placement submits the very command PrepareMover just
// stored, built from the same operation, with nothing in
// between.
// * the NULL-BODY term is the one entry-validation test
// PrepareMover does NOT shadow (round 4, D3 — its guard
// clause has no body test at all). This route's own
// entry check does: TryExecuteAcceptedRemotePosition's
// `record.PhysicsBody is null → NotApplicable` above.
// The only two writers that CLEAR it are entity teardown
// (RuntimeEntityObjectLifetime's canonical leave-world
// tail) and local-player activation retirement, and each
// retires the operation as part of the same transaction —
// so a body lost in between surfaces as Cancelled, not
// as a pre-engine Rejected.
// * !IsStructurallyValid(canonicalRequest) re-tests the
// command PrepareMover just validated. The `with` between
// them only replaces fields IsStructurallyValid reads
// when WakeableLostCell is set, which requires an
// inherited park — and TryBeginExclusiveAuthoredPlacement
// refuses whenever an operation already exists for the
// key, so nothing can be inherited here.
// If a future change breaks one of those, the failure lands
// on the SAFE side: a pre-engine Rejected filed here does not
// store, so the body holds a stale pose for ONE packet (no
// operation is retained — CancelToken runs below — so the
// next 5-10 Hz packet starts clean). The opposite mistake,
// storing after the engine refused, teleports the canonical
// body into a destination the sweep just declined. That
// asymmetry is why this stays a documented invariant rather
// than a stage flag threaded out of Core: the flag would
// change the shared placement contract three shipped routes
// depend on, to make the already-safe direction slightly
// safer.
CancelToken(setPosition, token);
return RuntimeRemotePlacementExecutionStatus.Rejected;
return RuntimeRemotePlacementExecutionStatus.RejectedByPlacement;
}
}
/// <summary>
/// R9 review fix — the same self-healing read
/// <see cref="CountLiveAwaitingAcknowledgement"/> already performed, for
/// <see cref="_pending"/>. Both maps are keyed by
/// <see cref="RuntimeEntityKey"/> (guid + incarnation), so a reused GUID
/// produces a NEW key and cannot displace a dead incarnation's entry by
/// itself. <see cref="Advance"/> drops such entries, but it early-returns
/// whenever nothing is pending and is a host cadence call the ledger has
/// no right to assume ran; without this the count converged only at
/// <see cref="DetachRoute"/>, i.e. never in-session — contract item 7's
/// GUID-reuse/incarnation dimension.
///
/// <para>
/// Removal is gated on <c>!IsPlacementCurrent</c>, so this can only ever
/// drop entries Core has ALREADY retired. It never cancels anything and
/// therefore cannot hide the live-operation leak
/// <see cref="DetachRoute"/>'s own review fix exists to prevent. The
/// disposal guard matches its sibling's, for the same
/// post-<c>Dispose()</c> <c>CaptureOwnership()</c> contract.
/// </para>
/// </summary>
private int CountLivePending()
{
if (_pending.Count == 0)
return 0;
if (_entityObjects.Physics.IsDisposed)
return _pending.Count;
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
_pendingScratch.Clear();
foreach ((RuntimeEntityKey key, Pending entry) in _pending)
{
if (!setPosition.IsPlacementCurrent(entry.Token))
_pendingScratch.Add(key);
}
foreach (RuntimeEntityKey key in _pendingScratch)
_pending.Remove(key);
return _pending.Count;
}
/// <summary>
/// Self-healing read: prunes every <see cref="_awaitingAcknowledgement"/>
/// entry whose token Core no longer considers current (the ack already
@ -616,6 +1209,96 @@ internal sealed class RuntimeRemotePlacementDriveController
return _awaitingAcknowledgement.Count;
}
/// <summary>
/// An OPTIMISATION, never the correctness mechanism: can this host place a
/// remote into <paramref name="landblockId"/> RIGHT NOW without paying for
/// a park that will only be cancelled again?
///
/// <para>
/// <b>Deliberately NOT complete, and it cannot be (C4 route 4b-2, delta
/// review MAJOR B/C — correcting this comment's earlier "the complete
/// pre-flight" framing).</b> This tests ONE prefix, the destination's.
/// Core's own park predicates are broader in two independent ways that no
/// pre-flight can reproduce:
/// </para>
/// <list type="number">
/// <item><description><c>PlacementTouchesPrefix</c> also matches the
/// request's <c>CurrentCellId</c>. <b>Round 3 — the earlier text called
/// that "the SOURCE landblock the remote is snapping OUT of" and "the
/// likelier shape"; that is not what production supplies.</b>
/// <c>CurrentCellId</c> is read from <c>record.FullCellId</c>, and both
/// accepted-Position callers commit the accepted WIRE cell to that field
/// before this seam is reached — the graphical remote path through
/// <c>LiveEntityRuntime.RebucketLiveEntity</c> →
/// <c>RuntimeEntityObjectLifetime.CommitRebucket</c> in its shared
/// prologue, route 2 through the merge itself. So on the FIRST submit
/// this arm names the DESTINATION, and it is the destination prefix — the
/// one this pre-flight DOES see — that it matches. Measured, not argued.
/// Scoped deliberately (round 4, D5): that is a property of the first
/// submit only. A RETAINED operation re-submits from a cadence pump with
/// no fresh merge, so see the <see cref="Advance"/> paragraph below for
/// what the arm can name by then — the arm is live, not dead
/// code.</description></item>
/// <item><description><c>ResultTouchesPrefix</c> scans every
/// <c>QueriedCellIds</c> entry, and that sweep footprint provably spans
/// NEIGHBOUR landblocks (<c>CellTransit.AddOutsideCell</c> re-derives the
/// block id from the global lcoord and states outright that there is no
/// same-block filter). The footprint does not EXIST until the sweep has
/// run.</description></item>
/// </list>
/// <para>
/// <see cref="Advance"/> re-reads this predicate and is subject to the
/// same two gaps, plus a third: a non-Position rebucket
/// (<c>RemoteTeleportController</c>, the projection materializer, the
/// equipped-child renderer) can move <c>record.FullCellId</c> to a THIRD
/// landblock between the retained submit and the retry. All three are
/// harmless for the same reason (delta review N3). That reason is the
/// paragraph below — NOT, as the round-2 text claimed, that re-reading
/// <c>record.CurrentCellId</c> here would "re-derive a private Core
/// predicate outside Core": this method already calls Core's own
/// <c>internal</c> <see cref="RuntimeSetPositionState.IsCollisionPrefixQuiescing"/>,
/// so that objection was simply false (round-3 correction A5).
/// </para>
/// <para>
/// <b>Correctness lives at the source instead.</b>
/// <c>SubmitPreparedPlacementCore</c>'s two quiescence parks are opted
/// into <c>ParkDeferred</c>'s rollback, and <c>ParkDeferred</c> then
/// restores only when the cell it will actually restore into —
/// <c>body.CellPosition.ObjCellId</c>, read AFTER the snap and tested
/// against EVERY live quiescence — is not itself quiescing.
/// <b>Corrected round 4 (D5):</b> the earlier text said "every shape this
/// predicate cannot see is a merely-swept NEIGHBOUR", which contradicts
/// the third-landblock rebucket shape named ten lines above. The right
/// statement is weaker and does not need the enumeration: whatever the
/// blocking prefix turns out to be, the rollback is decided against the
/// RESTORE cell rather than against that prefix, so it re-admits nothing
/// into any quiescing prefix and AP-136's reason for the blanket
/// non-restorable scoping (a spatial root re-entering the prefix that is
/// trying to quiesce) cannot apply. This predicate therefore only
/// shrinks the reachable set, and the <c>DeferredCell</c> case in
/// <see cref="SubmitAndResolve"/> handles whatever still reaches Core.
/// Recorded as AP-138(2).
/// </para>
/// <para>
/// <b>Two other properties DEPEND on this pre-flight staying here
/// (round-3 correction A5). Do not delete it as redundant.</b> First,
/// N3's "a retained retry can no longer strand" argument: a retry whose
/// destination has begun quiescing since the submit is refused HERE and
/// takes the <see cref="Advance"/> window-drop <c>store_position</c>,
/// rather than reaching Core and opening a park into a prefix whose own
/// restore is (correctly) declined. Second, the destination-quiescing
/// shape is the one case where <c>ParkDeferred</c> would refuse to
/// restore, so refusing ahead of it is what keeps that shape off the
/// remote path entirely; without this call it becomes reachable and the
/// remote is left withdrawn until a later packet commits.
/// </para>
/// </summary>
private bool CanAttemptDestination(
RuntimeSetPositionState setPosition,
uint landblockId) =>
_serviceWindow.IsWithinServiceWindow(landblockId)
&& !setPosition.IsCollisionPrefixQuiescing(landblockId);
private static void CancelToken(
RuntimeSetPositionState setPosition,
in RuntimeEntityPlacementToken token)

View file

@ -0,0 +1,582 @@
using System.Numerics;
using AcDream.App.Physics;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
namespace AcDream.App.Tests.Physics;
/// <summary>
/// C4 route 4b-2: behavioural acceptance for the remote far snap. Every test
/// calls the PRODUCTION routing entry point
/// (<see cref="LiveEntityNetworkUpdateController.ApplyRemoteContactRouting"/>)
/// against a REAL <see cref="RuntimeRemotePlacementDriveController"/>, a real
/// canonical <see cref="RuntimeEntityRecord"/>/<see cref="PhysicsBody"/> pair,
/// and REAL
/// <see cref="RuntimeAuthoritativePositionRouteClassifier"/> output. Nothing
/// is re-derived in a test body — that is the #292 gap route 2 left and route
/// 4a's first attempt repeated.
///
/// <para>
/// The load-bearing discriminator is the <c>worldPos</c> argument: it is
/// deliberately DIFFERENT from the accepted destination merged onto the
/// canonical snapshot. The deleted legacy far block wrote
/// <c>Body.Position = worldPos</c>; the canonical placement writes the
/// snapshot's destination resolved through Runtime's world frame. Any
/// regression back to the legacy write therefore fails
/// <see cref="FarSnap_PlacesTheBodyFromTheCanonicalDestination_NotTheCallersWirePose"/>
/// on the exact position value.
/// </para>
/// </summary>
public sealed class LiveEntityNetworkRemoteFarSnapIntegrationTests
{
private static readonly Vector3 Destination = new(12f, 14f, 7f);
/// <summary>A wire pose the caller passes but the far arm must ignore —
/// distinct from <see cref="Destination"/> in every component.</summary>
private static readonly Vector3 DecoyWirePose = new(-70f, -80f, -90f);
[Fact]
public void FarSnap_PlacesTheBodyFromTheCanonicalDestination_NotTheCallersWirePose()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004001u, Destination);
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 200f),
DecoyWirePose,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.FarSnapPlacement,
routing.Arm);
Assert.Equal(
RuntimeRemotePlacementExecutionStatus.Committed,
routing.Placement);
Assert.Equal(
Destination + RemotePlacementDriveFixture.DestinationWorldOffset,
body.Position);
Assert.NotEqual(DecoyWirePose, body.Position);
// The canonical placement — not the App — committed residency.
Assert.Equal(RemotePlacementDriveFixture.DestinationCell, record.FullCellId);
fixture.DrainPlacementFifo();
Assert.Equal(0, fixture.LiveOperationCount);
Assert.Equal(0, fixture.RemotePlacementLedger);
}
/// <summary>
/// Retail's far branch stops the interpolation queue BEFORE placing
/// (@0x005163CB precedes @0x005163D9). A stale near waypoint surviving the
/// snap would drag the freshly placed body back on the next per-tick
/// catch-up.
/// </summary>
[Fact]
public void FarSnap_ClearsTheInterpolationQueue()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004002u, Destination);
remote.Interp.Enqueue(
new Vector3(40f, 40f, 7f),
Quaternion.Identity,
isMovingTo: false,
currentBodyPosition: body.Position,
currentBodyOrientation: body.Orientation);
Assert.True(remote.Interp.IsActive);
_ = LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 200f),
DecoyWirePose,
Quaternion.Identity,
willBeDrTicked: true);
Assert.False(remote.Interp.IsActive);
fixture.DrainPlacementFifo();
}
/// <summary>
/// <b>The inverted test (C4 route 4b-2 review).</b> This used to assert
/// <c>Assert.Equal(before, body.Position)</c> and so PINNED the defect
/// both reviews found: a refused far snap left the body at its stale pose
/// with the interpolation queue already cleared, i.e. a frozen remote
/// that the next 5-10 Hz packet reproduces identically.
///
/// <para>
/// Retail never leaves the object behind. When
/// <c>CPhysicsObj::SetPositionInternal</c> @0x00515BD0 resolves no cell,
/// it takes @0x00515C1D and still commits the destination —
/// <c>store_position</c> @0x00515CE2 — before <c>GotoLostCell</c>
/// @0x00515CF2. So a refusal advances the body to the accepted
/// destination, resolved through Runtime's world frame (NOT the caller's
/// wire pose), while retaining no operation and committing no cell.
/// </para>
/// </summary>
[Fact]
public void FarSnap_RefusedDestination_StillAdvancesTheBodyToTheAcceptedDestination()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
// Deliberately NOT AllowDestination().
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004003u, Destination);
Vector3 before = body.Position;
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 200f),
DecoyWirePose,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.FarSnapPlacement,
routing.Arm);
Assert.Equal(
RuntimeRemotePlacementExecutionStatus.Refused,
routing.Placement);
// store_position: the body TRACKED, and from the canonical
// destination — not the caller's wire pose, and not its stale pose.
Assert.Equal(
Destination + RemotePlacementDriveFixture.DestinationWorldOffset,
body.Position);
Assert.NotEqual(before, body.Position);
Assert.NotEqual(DecoyWirePose, body.Position);
// …and it is a store_position, NOT a placement: no cell was
// committed, nothing was retained.
Assert.Equal(RemotePlacementDriveFixture.SourceCell, record.FullCellId);
Assert.True(body.InWorld);
Assert.True(record.ObjectClock.IsActive);
Assert.Equal(0, fixture.LiveOperationCount);
Assert.Equal(0, fixture.RemotePlacementLedger);
}
/// <summary>
/// The same refusal repeated: a remote genuinely beyond the host's
/// service window keeps TRACKING every packet rather than freezing until
/// the window opens. This is the reachable production shape the review
/// named — a remote that is rendered but still streaming, refused while
/// genuinely beyond 96 m — and it is why the fallback cannot be a
/// one-shot.
/// </summary>
[Fact]
public void FarSnap_RepeatedRefusals_KeepTrackingEveryPacket()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x7000400Cu, Destination);
foreach (Vector3 step in new[]
{
new Vector3(20f, 14f, 7f),
new Vector3(28f, 14f, 7f),
new Vector3(36f, 14f, 7f),
})
{
record.Snapshot = record.Snapshot with
{
Position = new CreateObject.ServerPosition(
RemotePlacementDriveFixture.DestinationCell,
step.X,
step.Y,
step.Z,
1f,
0f,
0f,
0f),
};
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 200f),
DecoyWirePose,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
RuntimeRemotePlacementExecutionStatus.Refused,
routing.Placement);
Assert.Equal(
step + RemotePlacementDriveFixture.DestinationWorldOffset,
body.Position);
}
Assert.Equal(0, fixture.LiveOperationCount);
Assert.Equal(0, fixture.RemotePlacementLedger);
}
// ── The null / Rejected* / cell-less policy ────────────────────────────
/// <summary>
/// The login-window trap. <c>ClassifyRemoteAcceptedPosition</c> returns
/// null for EVERY remote packet until the local movement controller
/// exists, because there is no <c>player_distance</c> to derive. Deleting
/// the legacy block without a replacement would freeze every remote for
/// that whole window. The stated policy routes it through AP-87's
/// catch-up, whose 4 m guard PLACES a body that has drifted.
/// </summary>
[Fact]
public void NoClassificationAtAll_StillTracksTheServer()
{
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004004u, Destination);
var target = new Vector3(60f, 10f, 7f); // 50 m from the body.
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
route: null,
target,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.UnroutedCatchUp,
routing.Arm);
Assert.Equal(target, body.Position);
// No canonical placement was attempted for an unclassified packet.
Assert.Equal(0, fixture.LiveOperationCount);
}
/// <summary>
/// …and the same policy ENQUEUES when the body is already tracking, which
/// is what proves it is AP-87's catch-up rather than an unconditional
/// snap bolted on to make the previous test pass.
///
/// <para>
/// R7 review fix: the earlier version asserted only that the body did not
/// move, which "snap when <c>bodyToTarget &gt; 4 m</c>, else do nothing"
/// satisfies just as well as an enqueue — it and its sibling both passed
/// against that regression. The queue assertion below is what actually
/// distinguishes "enqueued" from "did nothing".
/// </para>
/// </summary>
[Fact]
public void NoClassificationAtAll_NearAndTicked_EnqueuesInsteadOfSnapping()
{
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004005u, Destination);
Vector3 before = body.Position;
var target = before + new Vector3(0.5f, 0f, 0f);
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
route: null,
target,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.UnroutedCatchUp,
routing.Arm);
Assert.Equal(before, body.Position);
Assert.True(remote.Interp.IsActive);
}
[Fact]
public void RejectedData_TakesTheUnroutedCatchUp_NotTheFarSnap()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004006u, Destination);
var target = new Vector3(60f, 10f, 7f);
RuntimeAuthoritativePositionRoute rejected =
Classify(hasContact: true, playerDistance: float.NaN);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.RejectedData,
rejected.Disposition);
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
rejected,
target,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.UnroutedCatchUp,
routing.Arm);
Assert.Equal(target, body.Position);
Assert.Equal(0, fixture.LiveOperationCount);
}
/// <summary>
/// Retail's cell-less body takes <c>this_1-&gt;cell == 0</c> @0x00516386
/// (<c>SetPosition</c>), never the far branch — and route 4b-3, not this
/// slice, owns it. Claiming it here would be exactly route 4a's
/// "'not Interpolate' is not 'far'" finding one level up.
/// </summary>
[Fact]
public void CellLessRemote_TakesTheUnroutedCatchUp_AndNeverThePlacementOwner()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004007u, Destination);
var target = new Vector3(60f, 10f, 7f);
RuntimeAuthoritativePositionRoute cellLess = Classify(
hasContact: true,
playerDistance: 200f,
committedCellId: 0u);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPosition,
cellLess.Disposition);
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
cellLess,
target,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.UnroutedCatchUp,
routing.Arm);
Assert.Equal(target, body.Position);
Assert.Equal(0, fixture.LiveOperationCount);
Assert.Equal(0, fixture.RemotePlacementLedger);
}
/// <summary>
/// The airborne precedence carve-out still outranks the far snap, not
/// only the near branch: a remote mid-arc whose packet happens to
/// classify far must keep the pre-existing authoritative hard-snap rather
/// than open a canonical placement.
/// </summary>
[Fact]
public void AirborneBody_OutranksTheFarSnap()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x70004008u, Destination);
remote.Airborne = true;
var target = new Vector3(60f, 10f, 7f);
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 200f),
target,
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.AirborneSnap,
routing.Arm);
Assert.Equal(target, body.Position);
Assert.Equal(0, fixture.LiveOperationCount);
}
/// <summary>
/// Per-entity independence across the two arms in the same tick: one
/// remote's committed placement must not touch another's body or leak
/// into its ledger.
/// </summary>
[Fact]
public void TwoRemotesInTheSameTick_FarAndNear_DoNotContaminateEachOther()
{
using var fixture = new RemotePlacementDriveFixture();
fixture.PublishDestinationCollision();
fixture.AllowDestination();
(RuntimeEntityRecord farRecord, RemoteMotion farRemote,
PhysicsBody farBody) = fixture.AddRemote(0x70004009u, Destination);
(RuntimeEntityRecord nearRecord, RemoteMotion nearRemote,
PhysicsBody nearBody) = fixture.AddRemote(0x7000400Au, Destination);
Vector3 nearBefore = nearBody.Position;
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.FarSnapPlacement,
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
farRecord,
farRemote,
Classify(hasContact: true, playerDistance: 200f),
DecoyWirePose,
Quaternion.Identity,
willBeDrTicked: true).Arm);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm
.SteadyStateInterpolate,
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
nearRecord,
nearRemote,
Classify(hasContact: true, playerDistance: 10f),
nearBefore + new Vector3(0.5f, 0f, 0f),
Quaternion.Identity,
willBeDrTicked: true).Arm);
Assert.Equal(
Destination + RemotePlacementDriveFixture.DestinationWorldOffset,
farBody.Position);
Assert.Equal(nearBefore, nearBody.Position);
fixture.DrainPlacementFifo();
Assert.Equal(0, fixture.LiveOperationCount);
Assert.Equal(0, fixture.RemotePlacementLedger);
}
/// <summary>
/// R10 review fix: <c>AirborneNoOperation</c> used to fall into
/// <c>default:</c>, whose comment ASSERTED unreachability that nothing
/// enforced — so a caller that skipped its own airborne-no-op early
/// return would have silently enqueued a waypoint on a branch where
/// retail writes nothing at all (@0x0051636D). The explicit case is what
/// makes the assertion true.
/// </summary>
[Fact]
public void AirborneNoOperationClassification_IsRejectedByTheRoutingSeam()
{
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, PhysicsBody body) =
fixture.AddRemote(0x7000400Du, Destination);
Vector3 before = body.Position;
RuntimeAuthoritativePositionRoute airborne =
Classify(hasContact: false, playerDistance: 200f);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.NoPositionOperation,
airborne.Disposition);
Assert.Throws<InvalidOperationException>(() =>
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
airborne,
new Vector3(60f, 10f, 7f),
Quaternion.Identity,
willBeDrTicked: true));
Assert.Equal(before, body.Position);
Assert.False(remote.Interp.IsActive);
Assert.Equal(0, fixture.LiveOperationCount);
}
// ── The NPC arm's post-routing wire-cell adoption ──────────────────────
/// <summary>
/// After a far snap the canonical placement owns the cell. The NPC arm's
/// wire-cell write sits AFTER its routing (the player arm's sits before),
/// and <c>RemoteMotion.CellId</c> writes through to the canonical
/// <c>FullCellId</c> — so an unguarded write would discard the resolved
/// cell the placement just committed.
/// </summary>
[Fact]
public void WireCellAdoption_IsSuppressedAfterAFarSnapAndRunsForEveryOtherArm()
{
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, _) =
fixture.AddRemote(0x7000400Bu, Destination);
uint resolved = remote.CellId;
Assert.Equal(RemotePlacementDriveFixture.SourceCell, resolved);
Assert.False(
LiveEntityNetworkUpdateController.TryAdoptWireCellAfterRouting(
remote,
LiveEntityNetworkUpdateController.RemoteContactArm
.FarSnapPlacement,
RemotePlacementDriveFixture.DestinationCell));
Assert.Equal(resolved, remote.CellId);
Assert.Equal(resolved, record.FullCellId);
foreach (LiveEntityNetworkUpdateController.RemoteContactArm arm in
new[]
{
LiveEntityNetworkUpdateController.RemoteContactArm.AirborneSnap,
LiveEntityNetworkUpdateController.RemoteContactArm
.SteadyStateInterpolate,
LiveEntityNetworkUpdateController.RemoteContactArm
.UnroutedCatchUp,
})
{
remote.CellId = RemotePlacementDriveFixture.SourceCell;
Assert.True(
LiveEntityNetworkUpdateController.TryAdoptWireCellAfterRouting(
remote,
arm,
RemotePlacementDriveFixture.DestinationCell));
Assert.Equal(
RemotePlacementDriveFixture.DestinationCell, remote.CellId);
}
}
private static RuntimeAuthoritativePositionRoute Classify(
bool hasContact,
float playerDistance,
uint committedCellId = RemotePlacementDriveFixture.SourceCell) =>
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(
new RuntimeAcceptedPositionRouteRequest(
new RuntimeAuthoritativePositionAuthority(
new RuntimeGenerationToken(7),
new RuntimeEntityKey(0x70000001u, 3),
PositionAuthorityVersion: 11UL,
AcceptedPositionSequence: 20,
PreviousTeleportSequence: 10,
AcceptedTeleportSequence: 10,
PositionTimestampDisposition.Apply),
RuntimePositionEntityKind.Remote,
RuntimeAcceptedPositionSource.PositionEvent,
new CreateObject.ServerPosition(
RemotePlacementDriveFixture.DestinationCell,
Destination.X,
Destination.Y,
Destination.Z,
1f,
0f,
0f,
0f),
PlacementFrame: 0u,
PositionPackVelocity: Vector3.Zero,
committedCellId,
hasContact,
playerDistance,
UsePositionFromServer: false,
HasAnimations: false,
default));
}

View file

@ -157,11 +157,17 @@ public sealed class LiveEntityNetworkRemoteSteadyStateIntegrationTests
RuntimeAuthoritativePositionDisposition.Interpolate,
route.Disposition);
RemoteMotion remote = MakeLandingRemote();
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, _) =
fixture.AddRemote(0x70005001u, new Vector3(12f, 14f, 7f));
remote.Body.Position = new Vector3(10f, 10f, 5f);
remote.Airborne = true;
var landing = new Vector3(10.5f, 10f, 5f); // 0.5 m — well within 4 m.
LiveEntityNetworkUpdateController.RemoteContactArm arm =
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
route,
landing,
@ -170,7 +176,7 @@ public sealed class LiveEntityNetworkRemoteSteadyStateIntegrationTests
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.AirborneSnap,
arm);
routing.Arm);
Assert.Equal(landing, remote.Body.Position);
}
@ -181,12 +187,17 @@ public sealed class LiveEntityNetworkRemoteSteadyStateIntegrationTests
// route 4a's own branch: queued for the per-tick catch-up, body
// untouched. This is what proves the airborne test above is a
// PRECEDENCE carve-out and not a blanket disable of route 4a.
RemoteMotion remote = MakeLandingRemote();
using var fixture = new RemotePlacementDriveFixture();
(RuntimeEntityRecord record, RemoteMotion remote, _) =
fixture.AddRemote(0x70005002u, new Vector3(12f, 14f, 7f));
remote.Body.Position = new Vector3(10f, 10f, 5f);
remote.Airborne = false;
var target = new Vector3(10.5f, 10f, 5f);
LiveEntityNetworkUpdateController.RemoteContactArm arm =
LiveEntityNetworkUpdateController.RemoteContactRouting routing =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
fixture.Drive,
record,
remote,
Classify(hasContact: true, playerDistance: 10f),
target,
@ -196,43 +207,10 @@ public sealed class LiveEntityNetworkRemoteSteadyStateIntegrationTests
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm
.SteadyStateInterpolate,
arm);
routing.Arm);
Assert.Equal(new Vector3(10f, 10f, 5f), remote.Body.Position);
}
[Fact]
public void GroundedBodyOnAClassificationRoute4aDoesNotOwn_FallsThroughToLegacy()
{
RemoteMotion remote = MakeLandingRemote();
remote.Airborne = false;
Vector3 before = remote.Body.Position;
LiveEntityNetworkUpdateController.RemoteContactArm arm =
LiveEntityNetworkUpdateController.ApplyRemoteContactRouting(
remote,
Classify(hasContact: true, playerDistance: 200f),
new Vector3(10.5f, 10f, 5f),
Quaternion.Identity,
willBeDrTicked: true);
Assert.Equal(
LiveEntityNetworkUpdateController.RemoteContactArm.Legacy,
arm);
// Nothing was written — the caller's own legacy routing owns it.
Assert.Equal(before, remote.Body.Position);
}
private static RemoteMotion MakeLandingRemote()
{
var remote = new RemoteMotion();
remote.Body.Position = new Vector3(10f, 10f, 5f);
remote.Body.Orientation = Quaternion.Identity;
// Past the AP-87 firstUp hint, so the near case really is near.
remote.LastServerPosTime = 1_700_000_000d;
remote.Airborne = true;
return remote;
}
private static void AssertRenderPoseSuppressed(
RuntimeAuthoritativePositionRoute? route)
{

View file

@ -0,0 +1,218 @@
using System.Numerics;
using AcDream.Content;
using AcDream.Content.Pak;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
namespace AcDream.App.Tests.Physics;
/// <summary>
/// C4 route 4b-2: a REAL
/// <see cref="RuntimeRemotePlacementDriveController"/> over a bare
/// <see cref="RuntimeEntityObjectLifetime"/>, so App-layer acceptance tests
/// drive the production far-snap path end to end instead of simulating it.
/// Mirrors <c>RuntimeRemotePlacementDriveControllerTests</c>' own fixture.
/// </summary>
internal sealed class RemotePlacementDriveFixture : IDisposable
{
internal const uint SourceLandblock = 0xB1000000u;
internal const uint SourceCell = SourceLandblock | 0x0001u;
internal const uint DestinationLandblock = 0xB2000000u;
internal const uint DestinationCell = DestinationLandblock | 0x0001u;
/// <summary>The +X world offset <see cref="PublishDestinationCollision"/>
/// gives the destination landblock, so a committed placement's world
/// position is the authored local position plus this.</summary>
internal static readonly Vector3 DestinationWorldOffset = new(192f, 0f, 0f);
internal const float SpawnHeight = 7f;
private readonly ServiceWindow _window = new();
internal RemotePlacementDriveFixture()
{
Lifetime = new RuntimeEntityObjectLifetime(FlatEngine());
Drive = new RuntimeRemotePlacementDriveController(
Lifetime,
new GameRuntimeClock(),
new UnusedCollisionSource(),
_window);
}
internal RuntimeEntityObjectLifetime Lifetime { get; }
internal RuntimeRemotePlacementDriveController Drive { get; }
internal void AllowDestination() => _window.Allow(DestinationLandblock);
/// <summary>
/// Commits the destination landblock's collision generation and observes
/// the source world frame once, so a placement into
/// <see cref="DestinationCell"/> can actually resolve.
/// </summary>
internal void PublishDestinationCollision()
{
var heights = new byte[81];
Array.Fill(heights, (byte)SpawnHeight);
var heightTable = new float[256];
for (int index = 0; index < heightTable.Length; index++)
heightTable[index] = index;
Lifetime.Physics.ObserveLocalWorldFrame(
SourceCell, teleportAdvanced: false);
Lifetime.Physics.SetPosition.BeginCollisionGeneration(
DestinationLandblock, 1UL);
Lifetime.Physics.Engine.AddLandblock(
DestinationLandblock,
new TerrainSurface(heights, heightTable),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: DestinationWorldOffset.X,
worldOffsetY: DestinationWorldOffset.Y);
Lifetime.Physics.SetPosition.CommitCollisionGeneration(
DestinationLandblock, 1UL, ready: true);
}
/// <summary>
/// Registers one remote incarnation with a canonical body, its shared
/// <see cref="RemoteMotion"/>, and the accepted destination already merged
/// onto its snapshot — exactly the state
/// <c>RuntimeEntityObjectLifetime.TryApplyPosition</c> leaves behind
/// before <c>OnPosition</c> routes the packet.
/// </summary>
internal (RuntimeEntityRecord Record, RemoteMotion Remote, PhysicsBody Body)
AddRemote(uint guid, Vector3 destination)
{
RuntimeEntityRecord record = Lifetime.RegisterEntity(
Spawn(guid)).Canonical!;
Lifetime.Entities.SetFinalPhysicsState(record, PhysicsStateFlags.Gravity);
Lifetime.Entities.SetFullCell(
record, SourceCell, (SourceCell & 0xFFFF0000u) | 0xFFFFu);
var body = new PhysicsBody
{
Position = new Vector3(10f, 10f, SpawnHeight),
Orientation = Quaternion.Identity,
LastUpdateTime = 1d,
State = PhysicsStateFlags.Gravity,
TransientState = TransientStateFlags.Active,
};
body.SnapToCell(SourceCell, body.Position, body.Position);
Lifetime.Entities.SetPhysicsBody(record, body);
record.ObjectClock.Activate();
Lifetime.Physics.AcknowledgeSpatialProjection(record, spatial: true);
record.Snapshot = record.Snapshot with
{
Position = new CreateObject.ServerPosition(
DestinationCell,
destination.X,
destination.Y,
destination.Z,
1f,
0f,
0f,
0f),
};
RemoteMotion remote = Lifetime.Physics.GetOrCreateRemoteMotion(record);
// Past AP-87's firstUp hint, so the 4 m body-to-target guard is the
// condition under test rather than the first-sample one.
remote.LastServerPosTime = 1_700_000_000d;
return (record, remote, body);
}
/// <summary>
/// Stands in for the production placement-projection subscription this
/// bare fixture never wires.
/// </summary>
internal void DrainPlacementFifo()
{
while (Lifetime.Physics.SetPosition.TryPeekProjection(
out RuntimePlacementProjectionSnapshot head))
{
if (!Lifetime.Physics.SetPosition.AcknowledgeProjection(head.Token))
break;
}
}
internal int LiveOperationCount =>
Lifetime.Physics.CaptureOwnership().SetPositionOperationCount;
internal int RemotePlacementLedger =>
Lifetime.CaptureOwnership().RemotePlacementDrivePendingCount;
public void Dispose() => Lifetime.Dispose();
private static WorldSession.EntitySpawn Spawn(uint guid) => new(
guid,
new CreateObject.ServerPosition(
SourceCell, 10f, 10f, SpawnHeight, 1f, 0f, 0f, 0f),
SetupTableId: null,
AnimPartChanges: Array.Empty<CreateObject.AnimPartChange>(),
TextureChanges: Array.Empty<CreateObject.TextureChange>(),
SubPalettes: Array.Empty<CreateObject.SubPaletteSwap>(),
BasePaletteId: null,
ObjScale: null,
Name: "remote",
ItemType: null,
MotionState: null,
MotionTableId: 0x09000001u);
private static PhysicsEngine FlatEngine()
{
var engine = new PhysicsEngine { DataCache = new PhysicsDataCache() };
engine.AddLandblock(
SourceLandblock,
new TerrainSurface(new byte[81], new float[256]),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetY: 0f);
return engine;
}
private sealed class ServiceWindow : IRuntimeRemotePlacementServiceWindow
{
private readonly HashSet<uint> _within = [];
internal void Allow(uint landblockId) =>
_within.Add((landblockId & 0xFFFF0000u) | 0xFFFFu);
public bool IsWithinServiceWindow(uint landblockId) =>
_within.Contains((landblockId & 0xFFFF0000u) | 0xFFFFu);
}
private sealed class UnusedCollisionSource : IPreparedCollisionSource
{
public PreparedAssetPresence ProbeCollision(
PakAssetType type,
uint sourceFileId) => PreparedAssetPresence.Available;
public PreparedCollisionReadResult<FlatSetupCollision> ReadSetupCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
PreparedCollisionReadResult<FlatSetupCollision>.Missing;
public PreparedCollisionReadResult<FlatGfxObjCollisionAsset> ReadGfxObjCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionReadResult<FlatCellStructureCollisionAsset>
ReadCellStructureCollision(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionReadResult<FlatEnvCellTopology> ReadEnvCellTopology(
uint sourceFileId,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public PreparedCollisionSourceStats CollisionStats => default;
public void Dispose()
{
}
}
}

View file

@ -0,0 +1,312 @@
using System.Numerics;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
using AcDream.Runtime.Session;
namespace AcDream.Runtime.Tests.Physics;
/// <summary>
/// C4 route 4b-2: the arm-selection half of the remote far snap. Every route
/// here is produced by the REAL classifier from a real request, including the
/// negative cases the classifier genuinely emits from other entry points (a
/// local-player <c>SetPositionSimple</c>, a remote top-level Create) and which
/// the far arm must decline. Exactly ONE route is hand-shaped — the
/// flags/disposition mismatch in
/// <see cref="OwnsFarSnap_IsAStrictSubsetOfThePlacementOwnersPredicate"/>,
/// which no classifier path produces but a caller could express.
///
/// <para>
/// Each test was verified to discriminate by temporarily reverting the
/// corresponding guard in <see cref="RuntimeRemoteFarSnapPosition"/> and
/// confirming the matching test failed, then restoring it.
/// </para>
/// </summary>
public sealed class RuntimeRemoteFarSnapPositionTests
{
private const uint Cell = 0x0101FFFFu;
[Fact]
public void OwnsFarSnap_TrueForTheRemoteFarBranch()
{
RuntimeAuthoritativePositionRoute far =
Classify(hasContact: true, playerDistance: 200f);
// Retail: player_distance >= 96f -> StopInterpolating @0x005163CB +
// SetPositionSimple @0x005163D9, whose arg3 != 0 builds flags 0x1012
// (Teleport|Slide|SendPositionEvent) @0x005162C4.
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
far.Disposition);
Assert.True(far.StopInterpolating);
Assert.True(RuntimeRemoteFarSnapPosition.OwnsFarSnap(far));
}
[Fact]
public void OwnsFarSnap_TrueExactlyAtTheRetailBoundary()
{
// Retail compares player_distance against 96f and takes the
// InterpolateTo branch only when strictly LESS (@0x00516393-@0x0051639E,
// the `x87_r7 < temp1` test). 96.0 itself is the far branch.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: true, playerDistance: 95.99f)));
Assert.True(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: true, playerDistance: 96f)));
}
[Fact]
public void OwnsFarSnap_FalseForEveryOtherRemoteClassification()
{
// Near InterpolateTo @0x005163AF.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: true, playerDistance: 10f)));
// Airborne no-op @0x0051636D.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: false, playerDistance: 10f)));
// Cell-less: retail's `this_1->cell == 0` branch @0x00516386 —
// SetPosition, NOT the far snap. 4b-3 owns it.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: true, playerDistance: 200f, committedCellId: 0u)));
// Fresh TELEPORT_TS: the same @0x00516386 branch. 4b-3 owns it.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(
hasContact: true,
playerDistance: 200f,
previousTeleport: 10,
acceptedTeleport: 11)));
// Rejections and "no classification at all".
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(
hasContact: true,
playerDistance: 200f,
disposition: PositionTimestampDisposition.Rejected)));
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(
Classify(hasContact: true, playerDistance: float.NaN)));
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(null));
}
/// <summary>
/// The disposition alone is not the discriminator: the classifier emits
/// <c>SetPositionSimple</c> for the LOCAL PLAYER's FORCE_POSITION branch
/// too (<c>RuntimeAuthoritativePositionRouteClassifier.cs:332</c>). Route
/// 4b-2 is a remote route; claiming that one would run a remote arm over
/// route 2's local-player transaction.
/// </summary>
[Fact]
public void OwnsFarSnap_FalseForTheLocalPlayerForcePositionRoute()
{
RuntimeAuthoritativePositionRoute force = ClassifyKind(
RuntimePositionEntityKind.LocalPlayer,
hasContact: true,
playerDistance: 200f,
disposition: PositionTimestampDisposition.ForcePosition);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
force.Disposition);
Assert.Equal(
RuntimeSetPositionOperationKind.LocalAuthoritative,
force.OperationKind);
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(force));
}
/// <summary>
/// A REMOTE top-level initial Create is also <c>RemoteAuthoritative</c>;
/// it is excluded here by its disposition (<c>SetPosition</c>), which
/// route 4b-3 owns.
/// </summary>
[Fact]
public void OwnsFarSnap_FalseForARemoteTopLevelCreateRoute()
{
RuntimeAuthoritativePositionRoute create =
RuntimeAuthoritativePositionRouteClassifier.ClassifyCreate(
new RuntimeCreatePositionRouteRequest(
Authority(PositionTimestampDisposition.Apply, 10, 10),
RuntimePositionEntityKind.Remote,
RuntimeCreateResidenceKind.TopLevel,
new CreateObject.ServerPosition(
Cell, 10f, 20f, 30f, 1f, 0f, 0f, 0f),
default));
Assert.Equal(
RuntimeSetPositionOperationKind.RemoteAuthoritative,
create.OperationKind);
Assert.Equal(
PhysicsSetPositionFlags.Placement | PhysicsSetPositionFlags.Slide,
create.SetPositionFlags);
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(create));
}
/// <summary>
/// The invariant the <c>Teleport</c>-flag term actually carries:
/// <see cref="RuntimeRemoteFarSnapPosition.OwnsFarSnap"/> must be a strict
/// SUBSET of
/// <see cref="RuntimeRemotePlacementDriveController.OwnsPlacement"/>. The
/// far arm hands its route straight to that controller, so any route the
/// arm claims but the controller declines would execute
/// <c>StopInterpolating</c> and then silently do nothing
/// (<c>NotApplicable</c>) — a remote that stops tracking the server with
/// no diagnostic. The flags term is what keeps the two predicates
/// aligned; without it, the hand-shaped route below is claimed here and
/// declined there.
/// </summary>
[Fact]
public void OwnsFarSnap_IsAStrictSubsetOfThePlacementOwnersPredicate()
{
// Every route the real classifier produces.
foreach (RuntimeAuthoritativePositionRoute route in new[]
{
Classify(hasContact: true, playerDistance: 200f),
Classify(hasContact: true, playerDistance: 10f),
Classify(hasContact: false, playerDistance: 10f),
Classify(hasContact: true, playerDistance: 200f, committedCellId: 0u),
Classify(hasContact: true, playerDistance: float.NaN),
ClassifyKind(
RuntimePositionEntityKind.LocalPlayer,
hasContact: true,
playerDistance: 200f,
disposition: PositionTimestampDisposition.ForcePosition),
})
{
if (RuntimeRemoteFarSnapPosition.OwnsFarSnap(route))
{
Assert.True(
RuntimeRemotePlacementDriveController.OwnsPlacement(route));
}
}
// …and the shape a caller could construct that the classifier does
// not: the far disposition and operation kind with a Create's flags.
RuntimeAuthoritativePositionRoute mismatched =
Classify(hasContact: true, playerDistance: 200f) with
{
SetPositionFlags = PhysicsSetPositionFlags.Placement
| PhysicsSetPositionFlags.Slide,
};
Assert.False(
RuntimeRemotePlacementDriveController.OwnsPlacement(mismatched));
Assert.False(RuntimeRemoteFarSnapPosition.OwnsFarSnap(mismatched));
}
// ── Arm selection is total, and the leftovers are named ─────────────────
[Fact]
public void ResolveArm_MapsEveryRemoteClassificationToExactlyOneArm()
{
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.AirborneNoOperation,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(hasContact: false, playerDistance: 10f)));
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.NearInterpolate,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(hasContact: true, playerDistance: 10f)));
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.FarSnapPlacement,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(hasContact: true, playerDistance: 200f)));
// The acdream-only leftovers, all four shapes. This is the stated
// policy: they are NOT far. Reading "not Interpolate" as "far" is
// route 4a's own review finding, one level up.
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(hasContact: true, playerDistance: 10f, committedCellId: 0u)));
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(
hasContact: true,
playerDistance: 10f,
disposition: PositionTimestampDisposition.Rejected)));
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
RuntimeRemoteFarSnapPosition.ResolveArm(
Classify(hasContact: true, playerDistance: float.NaN)));
Assert.Equal(
RuntimeRemoteAcceptedPositionArm.UnroutedCatchUp,
RuntimeRemoteFarSnapPosition.ResolveArm(null));
}
// ── The single ConstrainTo site's gate ──────────────────────────────────
[Fact]
public void OwnsAfterOperationConstraint_CoversTheThreeArmsThatRunAnOperation()
{
// Retail arms @0x00454272 for every nonzero MoveOrTeleport return.
// The airborne no-op returns 0 @0x0051636D, so it is excluded — and
// that exclusion is route 4a's, carried forward unchanged.
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
Classify(hasContact: true, playerDistance: 10f)));
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
Classify(hasContact: true, playerDistance: 200f)));
Assert.True(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
Classify(hasContact: false, playerDistance: 10f)));
// The leftovers still arm through the App's legacy pre-operation
// site; claiming them here would double-arm them.
Assert.False(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
Classify(hasContact: true, playerDistance: 10f, committedCellId: 0u)));
Assert.False(RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(
Classify(hasContact: true, playerDistance: float.NaN)));
Assert.False(
RuntimeRemoteFarSnapPosition.OwnsAfterOperationConstraint(null));
}
private static RuntimeAuthoritativePositionAuthority Authority(
PositionTimestampDisposition disposition,
ushort previousTeleport,
ushort acceptedTeleport) =>
new(
new RuntimeGenerationToken(7),
new RuntimeEntityKey(0x70000001u, 3),
PositionAuthorityVersion: 11UL,
AcceptedPositionSequence: 20,
previousTeleport,
acceptedTeleport,
disposition);
private static RuntimeAuthoritativePositionRoute Classify(
bool hasContact,
float playerDistance,
uint committedCellId = Cell,
ushort previousTeleport = 10,
ushort acceptedTeleport = 10,
PositionTimestampDisposition disposition =
PositionTimestampDisposition.Apply) =>
ClassifyKind(
RuntimePositionEntityKind.Remote,
hasContact,
playerDistance,
committedCellId,
previousTeleport,
acceptedTeleport,
disposition);
private static RuntimeAuthoritativePositionRoute ClassifyKind(
RuntimePositionEntityKind kind,
bool hasContact,
float playerDistance,
uint committedCellId = Cell,
ushort previousTeleport = 10,
ushort acceptedTeleport = 10,
PositionTimestampDisposition disposition =
PositionTimestampDisposition.Apply) =>
RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(
new RuntimeAcceptedPositionRouteRequest(
Authority(disposition, previousTeleport, acceptedTeleport),
kind,
RuntimeAcceptedPositionSource.PositionEvent,
new CreateObject.ServerPosition(
Cell, 10f, 20f, 30f, 1f, 0f, 0f, 0f),
PlacementFrame: 0u,
PositionPackVelocity: Vector3.Zero,
committedCellId,
hasContact,
playerDistance,
UsePositionFromServer: false,
HasAnimations: false,
default));
}

View file

@ -317,17 +317,52 @@ public sealed class RuntimeRemoteSteadyStatePositionTests
Assert.Null(host.PositionManager.Constraint);
}
/// <summary>
/// C4 route 4b-2: the far branch moved onto the post-operation arm with
/// route 4a's two. Retail's `MoveOrTeleport` returns 1 @0x005163E8 there,
/// so `HandleReceivedPosition`'s single `ConstrainTo` @0x00454272 runs —
/// and the App's legacy PRE-operation call site now reads the same
/// predicate and skips it, so it is armed exactly once.
/// </summary>
[Fact]
public void TryArmConstraintAfterOperation_SkipsClassificationsRoute4aDoesNotOwn()
public void TryArmConstraintAfterOperation_ArmsForTheFarSnapBranch()
{
// The far branch still arms its leash — through the untouched legacy
(RemoteMotion remote, EntityPhysicsHost host) = MakeRemoteWithHost(
new Vector3(1f, 2f, 3f));
RuntimeAuthoritativePositionRoute far =
Classify(hasContact: true, playerDistance: 200f);
Assert.Equal(
RuntimeAuthoritativePositionDisposition.SetPositionSimple,
far.Disposition);
Assert.True(RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
far,
remote));
Assert.True(host.PositionManager.Constraint?.IsConstrained);
}
[Fact]
public void TryArmConstraintAfterOperation_SkipsClassificationsNoArmOwns()
{
// The cell-less half, the two rejections, and "no classification at
// all" still arm their leash through the untouched legacy
// PRE-operation call site, not through here. Arming here too would
// double-arm it.
// double-arm them.
(RemoteMotion remote, EntityPhysicsHost host) = MakeRemoteWithHost(
new Vector3(1f, 2f, 3f));
Assert.False(RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
Classify(hasContact: true, playerDistance: 200f),
Classify(hasContact: true, playerDistance: 10f, committedCellId: 0u),
remote));
Assert.False(RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
Classify(
hasContact: true,
playerDistance: 10f,
disposition: PositionTimestampDisposition.Rejected),
remote));
Assert.False(RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
Classify(hasContact: true, playerDistance: float.NaN),
remote));
Assert.False(RuntimeRemoteSteadyStatePosition.TryArmConstraintAfterOperation(
null,

View file

@ -1,4 +1,4 @@
using System.Net;
using System.Net;
using System.Numerics;
using AcDream.Core.Combat;
using AcDream.Core.Items;
@ -28,6 +28,30 @@ public sealed class RuntimeAcceptedPositionDriveControllerTests
private const uint SpawnLandblock = 0x01010000u;
private const float SpawnHeight = 5f;
/// <summary>
/// A landblock whose collision generation is deliberately NEVER committed
/// in this fixture — the same id the deferred-park tests below use. The
/// pre-engine quiescence check runs ahead of the engine and therefore
/// ahead of any collision-readiness question, so its readiness is
/// irrelevant to the round-3 destination-quiescence test.
/// </summary>
private const uint DestinationLandblock = 0x02020000u;
/// <summary>
/// <see cref="SpawnLandblock"/>'s +X neighbour: landblock ids pack the
/// block X index in bits 24-31, so 0x0101 → 0x0201 is one block east, and
/// <c>LandDefs.LcoordToGid</c> re-derives exactly this prefix for a global
/// lcoord one cell past the 192 m seam.
/// </summary>
private const uint NeighbourLandblock = 0x02010000u;
/// <summary>
/// Cell (7, 0) of <see cref="SpawnLandblock"/> — block-local X in
/// [168, 192), Y in [0, 24). <c>LandDefs.GidToLcoord</c>'s inverse:
/// <c>low = (ly &amp; 7) + ((lx &amp; 7) &lt;&lt; 3) + 1 = 0 + 56 + 1 = 57</c>.
/// </summary>
private const uint SpawnSeamCell = SpawnLandblock | 57u;
[Fact]
public void NotApplicable_WhenDispositionIsNotForcePosition()
{
@ -830,6 +854,359 @@ public sealed class RuntimeAcceptedPositionDriveControllerTests
AssertConverged(runtime);
}
/// <summary>
/// C4 route 4b-2, round-3 correction A2 (test 1 of 2) — the LOCAL PLAYER
/// traverses the shared-core quiescence-park change, on the shape that is
/// actually reachable through this route.
///
/// <para>
/// <b>Reachability, measured rather than assumed.</b> The correction asked
/// for a park under a quiescing SOURCE, reached through
/// <c>PlacementTouchesPrefix</c>'s <c>CurrentCellId</c> arm. That shape is
/// not constructible: the merge this route requires
/// (<c>RuntimeEntityObjectLifetime.TryApplyPosition</c>) commits the
/// accepted wire cell to <c>record.FullCellId</c> BEFORE the drive is
/// called, and <c>CurrentCellId</c> is read from that field — so by submit
/// time it names the DESTINATION, not the landblock being left. The
/// reachable pre-sweep shape is therefore a quiescing destination; the
/// reachable post-sweep shape is a quiescing swept neighbour, which is the
/// sibling test below.
/// </para>
///
/// <para>
/// A quiescing destination is the one shape where the rollback genuinely
/// would re-admit a spatial root into the prefix that is trying to
/// quiesce, so <c>ParkDeferred</c> declines it — AP-136's stated reason,
/// applied to the cell the restore would actually use. Route 2 has no
/// destination pre-flight of its own (unlike the remote far arm's
/// <c>CanAttemptDestination</c>), so it reaches this unmasked.
/// </para>
/// </summary>
[Fact]
public void QuiescingDestinationPrefix_ForcePositionParkIsNotRestored()
{
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
(RuntimeEntityRecord record, PlayerMovementController controller) =
EnterLocalPlayer(runtime);
PhysicsBody body = Assert.IsType<PhysicsBody>(record.PhysicsBody);
RuntimeAcceptedPositionDriveController drive =
CreateAcceptedPositionDrive(runtime, out _);
runtime.EntityObjects.Physics.SetPosition.BeginCollisionPrefixQuiescence(
DestinationLandblock,
collisionGeneration: 2UL,
includeOutdoorCells: true);
WorldSession.EntityPositionUpdate correction = ForceUpdate(
new Vector3(10f, 10f, SpawnHeight),
landblockId: DestinationLandblock | 0x0001u);
(PositionTimestampDisposition disposition, AcceptedPhysicsTimestamps timestamps) =
MergeAccepted(runtime, controller, correction);
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
Assert.Equal(
RuntimeAcceptedPositionExecutionStatus.DeferredCell,
drive.TryExecuteAcceptedLocalPosition(
record,
correction,
disposition,
timestamps,
timestamps.PreviousTeleport));
Assert.False(body.InWorld);
// ACE's next ordinary broadcast, ~100-200 ms later. Its merge-time
// Forget cancels the park — and the rollback must decline, because the
// only cell it could restore into is the retiring one.
MergeAccepted(
runtime,
controller,
OrdinaryUpdate(new Vector3(31f, 33f, SpawnHeight), positionSequence: 3));
Assert.False(runtime.EntityObjects.Physics.IsSpatialRoot(record));
Assert.False(body.InWorld);
Assert.False(record.ObjectClock.IsActive);
drive.Advance();
Assert.Equal(0, drive.PendingCount);
AssertConverged(runtime);
}
/// <summary>
/// C4 route 4b-2, round-3 correction A2 (test 2 of 2) — the reachable
/// RESTORABLE quiescence park on the local-player route.
///
/// <para>
/// The correction is placed one sphere radius inside the seam between
/// <see cref="SpawnLandblock"/> and its +X neighbour, so
/// <c>CellTransit.AddAllOutsideCells</c> adds the neighbour's cells to the
/// sweep footprint (<c>AddOutsideCell</c> re-derives the block id from the
/// global lcoord and has no same-block filter). Core's
/// <c>ResultTouchesPrefix</c> scans every <c>QueriedCellIds</c> entry, so
/// a healthy, resident, about-to-COMMIT ForcePosition is rewritten to
/// <c>DeferredCell</c> by a quiescence in a landblock the player is
/// neither in nor going to.
/// </para>
///
/// <para>
/// Before the shared-core change every quiescence park was non-restorable,
/// so the next packet's merge-time <c>Forget</c> destroyed the only
/// operation able to wake the player and left it <c>InWorld = false</c>,
/// clock suspended, not a spatial root — session-permanent, because
/// nothing else restores that state. The three asserts at the end are that
/// defect. Restoring is safe here for the reason
/// <c>ParkDeferred</c> tests directly: the cell it would restore into is
/// the destination, and the destination is not quiescing.
/// </para>
/// </summary>
[Fact]
public void QuiescingSweptNeighbour_ForcePositionParkIsRestoredByTheNextPacket()
{
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
(RuntimeEntityRecord record, PlayerMovementController controller) =
EnterLocalPlayer(runtime);
CommitLandblockCollision(
runtime, NeighbourLandblock, worldOffsetX: 192f);
PhysicsBody body = Assert.IsType<PhysicsBody>(record.PhysicsBody);
RuntimeAcceptedPositionDriveController drive =
CreateAcceptedPositionDrive(runtime, out _);
runtime.EntityObjects.Physics.SetPosition.BeginCollisionPrefixQuiescence(
NeighbourLandblock,
collisionGeneration: 2UL,
includeOutdoorCells: true);
// Neither the source nor the destination is quiescing — the whole
// point of the shape.
Assert.False(
runtime.EntityObjects.Physics.SetPosition.IsCollisionPrefixQuiescing(
SpawnLandblock));
// Block-local X = 191.95 m: inside cell (7, 0), 0.05 m from the 192 m
// seam, well inside the local player's own sphere radius, which is the
// `pointX > CellLength - radius` test AddAllOutsideCells applies
// before adding lx + 1.
WorldSession.EntityPositionUpdate correction = ForceUpdate(
new Vector3(191.95f, 10f, SpawnHeight),
landblockId: SpawnSeamCell);
(PositionTimestampDisposition disposition, AcceptedPhysicsTimestamps timestamps) =
MergeAccepted(runtime, controller, correction);
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
Assert.Equal(
RuntimeAcceptedPositionExecutionStatus.DeferredCell,
drive.TryExecuteAcceptedLocalPosition(
record,
correction,
disposition,
timestamps,
timestamps.PreviousTeleport));
Assert.False(body.InWorld);
MergeAccepted(
runtime,
controller,
OrdinaryUpdate(new Vector3(31f, 33f, SpawnHeight), positionSequence: 3));
Assert.True(body.InWorld);
Assert.True(record.ObjectClock.IsActive);
Assert.True(runtime.EntityObjects.Physics.IsSpatialRoot(record));
drive.Advance();
Assert.Equal(0, drive.PendingCount);
AssertConverged(runtime);
}
/// <summary>
/// C4 route 4b-2, round-4 correction (the MAJOR) — the DISCRIMINATING
/// test. Round 3 relocated the restorable-park decision out of the two
/// <c>SubmitPreparedPlacementCore</c> call sites and into
/// <c>ParkDeferred</c>, but nothing in the tree distinguished the new
/// predicate from the one it replaced. This does.
///
/// <para>
/// <b>Round 2's form</b> compared the blocking quiescence's own token
/// against the caller's pre-snap cell:
/// <c>blockingToken.LandblockPrefix != (result.CellId &amp; 0xFFFF0000)</c>.
/// <b>Round 3's form</b> tests the POST-snap cell against every live
/// quiescence: <c>!IsCollisionPrefixQuiescing(body.CellPosition.ObjCellId)</c>.
/// Under round 2 this test's player is left withdrawn — <c>InWorld</c>
/// false, clock suspended, not a spatial root — for the rest of the
/// session. That is the exact stranding AP-136's rollback exists to
/// prevent, and it is reached without a second quiescence, purely because
/// the two forms read different cells.
/// </para>
///
/// <para>
/// <b>The shape.</b> The server names a cell in the player's OWN
/// landblock but supplies a block-local X 5 cm PAST that block's 192 m
/// seam. A wire (cell, position) pair that disagrees is exactly the pair
/// <c>PhysicsBody.StageDormantCellFrame</c>'s
/// <c>LandDefs.AdjustToOutside</c> exists to distrust (#107: "never trust
/// a server (cell, pos) pair without re-deriving the cell"), and
/// <c>AdjustToOutside</c>'s own contract says the re-derived id may
/// belong to a NEIGHBOUR landblock — which is what happens here. So the
/// prefix the placement PARKED against (the player's own, mid-retirement)
/// and the prefix residency would be RESTORED into (the healthy
/// neighbour) are different landblocks, and only the second one is the
/// question <c>RestoreParkWithdrawal</c> actually asks.
/// </para>
///
/// <para>
/// Route 2 is the home for it because it has no destination pre-flight:
/// the remote far arm's <c>CanAttemptDestination</c> refuses a quiescing
/// destination before any park is opened, so this park is unreachable
/// there.
/// </para>
/// </summary>
[Fact]
public void QuiescingOwnPrefix_SeamCrossingParkIsRestoredAtTheReDerivedNeighbourCell()
{
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
(RuntimeEntityRecord record, PlayerMovementController controller) =
EnterLocalPlayer(runtime);
PhysicsBody body = Assert.IsType<PhysicsBody>(record.PhysicsBody);
RuntimeAcceptedPositionDriveController drive =
CreateAcceptedPositionDrive(runtime, out _);
// Streaming begins retiring/republishing the player's OWN landblock,
// which is also the landblock the wire cell names.
runtime.EntityObjects.Physics.SetPosition.BeginCollisionPrefixQuiescence(
SpawnLandblock,
collisionGeneration: 2UL,
includeOutdoorCells: true);
// Cell (7, 0) of SpawnLandblock covers block-local X in [168, 192).
// 192.05 m is 5 cm past its block's own seam, so AdjustToOutside
// re-derives lx = 8 + floor(192.05 / 24) = 16, i.e. block X index 2,
// and LcoordToGid rebuilds the id as NeighbourLandblock | 1.
WorldSession.EntityPositionUpdate correction = ForceUpdate(
new Vector3(192.05f, 10f, SpawnHeight),
landblockId: SpawnSeamCell);
(PositionTimestampDisposition disposition, AcceptedPhysicsTimestamps timestamps) =
MergeAccepted(runtime, controller, correction);
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
Assert.Equal(
RuntimeAcceptedPositionExecutionStatus.DeferredCell,
drive.TryExecuteAcceptedLocalPosition(
record,
correction,
disposition,
timestamps,
timestamps.PreviousTeleport));
Assert.False(body.InWorld);
// The two cells the two predicate forms read are DIFFERENT
// landblocks, and only the quiescing one is the caller's.
Assert.Equal(SpawnLandblock, correction.Position.LandblockId & 0xFFFF0000u);
Assert.Equal(
NeighbourLandblock,
body.CellPosition.ObjCellId & 0xFFFF0000u);
Assert.False(
runtime.EntityObjects.Physics.SetPosition.IsCollisionPrefixQuiescing(
NeighbourLandblock));
// ACE's next ordinary broadcast cancels the park at merge time.
MergeAccepted(
runtime,
controller,
OrdinaryUpdate(new Vector3(31f, 33f, SpawnHeight), positionSequence: 3));
// Restored, because the cell residency is restored INTO is healthy.
// All three of these fail under round 2's single-token form.
Assert.True(body.InWorld);
Assert.True(record.ObjectClock.IsActive);
Assert.True(runtime.EntityObjects.Physics.IsSpatialRoot(record));
drive.Advance();
Assert.Equal(0, drive.PendingCount);
AssertConverged(runtime);
}
/// <summary>
/// C4 route 4b-2, round-4 correction D6 — the restorable decision is
/// re-tested at RESTORE time, not only at park time.
///
/// <para>
/// <c>ParkDeferred</c>'s decision is a snapshot. The far snap cancels its
/// park synchronously, so that snapshot cannot go stale; route 2's park is
/// RETAINED (<c>AwaitingCommitWake</c>) and the restore lands on the next
/// packet's merge-time <c>Forget</c>, ~150 ms later at ACE's 5-10 Hz.
/// Streaming opens a quiescence per landblock mutation, so the cell the
/// rollback would restore into can start quiescing inside that window.
/// </para>
///
/// <para>
/// This drives exactly that: a swept-NEIGHBOUR quiescence parks a
/// placement whose destination is healthy (so the park IS restorable when
/// taken), then the destination's own prefix begins quiescing before the
/// next packet. The restore must decline — otherwise it re-admits a
/// spatial root into a prefix that is trying to retire, which is AP-136's
/// stated reason verbatim. The entity self-heals on a later packet, so
/// declining here is strictly the conservative half.
/// </para>
/// </summary>
[Fact]
public void QuiescenceOpenedAfterTheParkDeclinesTheRestoreAtMergeTime()
{
using StartedRuntime started = StartRuntime();
GameRuntime runtime = started.Runtime;
(RuntimeEntityRecord record, PlayerMovementController controller) =
EnterLocalPlayer(runtime);
CommitLandblockCollision(
runtime, NeighbourLandblock, worldOffsetX: 192f);
PhysicsBody body = Assert.IsType<PhysicsBody>(record.PhysicsBody);
RuntimeAcceptedPositionDriveController drive =
CreateAcceptedPositionDrive(runtime, out _);
runtime.EntityObjects.Physics.SetPosition.BeginCollisionPrefixQuiescence(
NeighbourLandblock,
collisionGeneration: 2UL,
includeOutdoorCells: true);
WorldSession.EntityPositionUpdate correction = ForceUpdate(
new Vector3(191.95f, 10f, SpawnHeight),
landblockId: SpawnSeamCell);
(PositionTimestampDisposition disposition, AcceptedPhysicsTimestamps timestamps) =
MergeAccepted(runtime, controller, correction);
Assert.Equal(PositionTimestampDisposition.ForcePosition, disposition);
Assert.Equal(
RuntimeAcceptedPositionExecutionStatus.DeferredCell,
drive.TryExecuteAcceptedLocalPosition(
record,
correction,
disposition,
timestamps,
timestamps.PreviousTeleport));
Assert.False(body.InWorld);
// The park WAS restorable when it was taken: the destination prefix
// was clean. (Its sibling test above asserts the restore that follows
// from exactly this state.)
Assert.Equal(
SpawnLandblock,
body.CellPosition.ObjCellId & 0xFFFF0000u);
// …and now streaming retires that very landblock, mid-park.
runtime.EntityObjects.Physics.SetPosition.BeginCollisionPrefixQuiescence(
SpawnLandblock,
collisionGeneration: 3UL,
includeOutdoorCells: true);
MergeAccepted(
runtime,
controller,
OrdinaryUpdate(new Vector3(31f, 33f, SpawnHeight), positionSequence: 3));
Assert.False(runtime.EntityObjects.Physics.IsSpatialRoot(record));
drive.Advance();
Assert.Equal(0, drive.PendingCount);
AssertConverged(runtime);
}
private static void AssertConverged(GameRuntime runtime)
{
RuntimeEntityObjectOwnershipSnapshot ownership =
@ -1011,7 +1388,8 @@ public sealed class RuntimeAcceptedPositionDriveControllerTests
/// </summary>
private static void CommitLandblockCollision(
GameRuntime runtime,
uint landblockId)
uint landblockId,
float worldOffsetX = 0f)
{
// Mirrors HeadlessSessionHostTests.AddFlatLandblock's exact
// proven-working shape (every heightmap byte and every table entry
@ -1029,7 +1407,7 @@ public sealed class RuntimeAcceptedPositionDriveControllerTests
new TerrainSurface(heights, heightTable),
Array.Empty<CellSurface>(),
Array.Empty<PortalPlane>(),
worldOffsetX: 0f,
worldOffsetX,
worldOffsetY: 0f);
runtime.EntityObjects.Physics.SetPosition.CommitCollisionGeneration(
landblockId, 1UL, ready: true);