acdream/src/AcDream.Runtime/Session/RuntimeRemotePlacementDriveController.cs
Erik cd3129e9d6 fix(physics): C4 route 7 — child cell propagation moves from a render tick into Runtime
Retail re-cells children when their parent crosses a cell, recursively, to
unbounded depth. acdream did it from a RENDER tick, so headless parented
children were cell-less forever and the canonical cell had two writers. This
slice makes Runtime the sole authority and demotes App's tick to
presentation-only. Contract:
docs/research/2026-08-04-c4-route-7-contract.md; the research that unblocked
it is docs/research/2026-08-04-retail-parent-cell-propagation.md (ca96ea5e).

Retail: SetPositionInternal @0x00515330 branches on `this->cell == curr_cell`
@0x0051536d; the changed branch reaches change_cell @0x00513390, whose
delegates leave_cell @0x00510f50 and enter_cell @0x00510ed0 self-recurse over
children and write the FULL identity (add_object @0x00510ee2, objcell_id
@0x00510f1e, part-array cell id @0x00510f2b, cell pointer @0x00510f35).
change_cell itself has no child loop.

THE TRAP, recorded because it nearly shipped: the depth-1 loop
@0x0051539c-0x005153d8 is the SAME-CELL fast path (objcell_id and part-array
id only, deliberately not the cell pointer), NOT the propagation. An
implementer who finds it first concludes "depth-1, id-only" and strands every
equipped item at a landblock boundary — the #184 class. The clincher against
that reading: update_object @0x00515d10 early-returns on `parent != 0`
@0x00515d40, so a child never runs its own physics tick and parent
propagation is the ONLY mechanism maintaining its cell.

Route 7 performs NO placement (DoPickupEvent @0x00452240 = unset_parent +
leave_world; DoParentEvent @0x00452290 = set_parent + SetPlacementFrame), so
it arms ConstrainTo nowhere — the leash rule INVERTS relative to routes
2/4/5, and both reviewers confirmed nothing arms.

Propagation is an ITERATIVE WORKLIST, not recursion. The first implementation
recursed with a depth-64 cap; both reviews independently found the cap left a
truncated tail at a stale NON-ZERO cell — permanently unrecoverable, logged
only under a probe flag, and on the withdraw path exactly the #184 shape
AP-142 clause (a) exists to reject. Shipping a fresh #184 instance inside the
slice that fixes stranded children was not acceptable, so the cap was removed
rather than tuned. The worklist retires the cap, the constant, its register
clause, and the failure mode together. Termination: every record on the stack
is already at the target pair, so nothing can be pushed twice and a hostile
A->B->A cycle collapses without a visited set.

The child write deliberately bypasses the public RuntimeEntityDirectory
.SetFullCell and calls the record method directly. This is LOAD-BEARING:
the public method re-enters PropagateFullCellToChildren, which opens with
_propagationWorklist.Clear() — routing children through it mid-drain would
wipe the shared stack and silently drop every unprocessed sibling. Any future
side effect added to the public SetFullCell must be mirrored by hand at that
call site.

Deliberate divergence, recorded not disguised: retail's removal path leaves
children with a null cell pointer but a STALE nonzero objcell_id @0x005133c1.
acdream does not reproduce it, because FullCellId != 0 is the liveness
predicate at 45+ sites — faithful porting would mark dead children live.
AP-142 records this; clause (d) records that acdream cannot gate propagation
on HasPartArray the way enter_cell gates on part_array @0x00510ed8, because
the flag's only writers are graphical and headless never sets it — the reason
is Slice J LAYERING, not a semantic difference (retail's part_array is itself
a mesh-construction product, single assignment site makeAnimObject
@0x0050e930 -> CPartArray::CreateSetup @0x0050e93e).

D7 adopts retail's unset_parent-before-leave_world order @0x0045227f ->
@0x00452286, applied to BOTH pickup paths including the dormant executor
replay. Its inertness was verified by reverting it and finding all 12
propagation tests still green — reported honestly rather than papered over
with a manufactured test, and independently confirmed by both reviewers.

ClassifyLeaveWorld and its request/cause types are DELETED: retail has no
classification here, and method-per-cause IS the retail dispatch shape.
Wiring it would have forced a vacuous teleport-sequence predicate with the
#307 shape.

Two review rounds plus a coordinator-required third pass; 5 MAJORs. One was a
handoff failure worth recording: enter_cell's part_array guard was correctly
identified as load-bearing by the research, dropped by the contract when it
enumerated the writes, and inherited as an omission by the code — a right
finding that evaporated across two handoffs with nobody re-reading the source.
Another was a test that survived deleting the entire behaviour it claimed to
pin, because its assertion read a field written unconditionally one line
earlier.

NoProjection is structurally unreachable from TickChild (TryResolveExactAttachment
performs a strictly stronger form of the same guard one call earlier). Kept as
a fail-safe, unit-tested directly, and documented in two places rather than
wrapped in a fabricated end-to-end test.

Headless regression test — the direct gate for this defect, which FAILED
before this work because no code path existed:
RuntimeLiveEntitySessionControllerTests
.DirectSink_D5_StandaloneParentEventCommitsChildToParentsExactCell.

Probe: ACDREAM_PROBE_CHILD_CELL=1 emits [child-cell] lines at all four write
sites (attach / headless-attach / propagate / withdraw / delete). TEMPORARY.

Complete Release suite MEASURED at 11,079 passed / 4 skipped / 0 failed
(baseline 11,063 at cff52c44, +16). An allocation flake appeared once under
load and was proven NOT this slice by reachability — RuntimeCollisionReportingState
contains zero SetFullCell and zero ParentAttachments references.

STILL OWED: the two-client connected gate (equip/unequip, carry across
landblock boundaries, pickup, loot, reconnect) with ACDREAM_PROBE_CHILD_CELL=1,
and a session counts only if [child-cell] cause=propagate lines appear.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 23:53:05 +02:00

1686 lines
87 KiB
C#

using System.Numerics;
using AcDream.Content;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime.Session;
/// <summary>
/// C4 route 4b-1: per-entity remote-placement service-window predicate. A
/// host implements this over whatever collision-publication residency it
/// actually tracks — <c>GpuWorldState.IsNearTier</c> for the graphical host,
/// <c>HeadlessCollisionNeighborhood</c>'s explicit implementation (backed by
/// its private <c>IsCollisionCurrentlyPublished</c> — NOT
/// <see cref="AcDream.Headless.Hosting.IHeadlessCollisionNeighborhood.IsWithinServiceWindow"/>,
/// which is a pure geometry test over the requested 3x3 window and answers a
/// different question: "can this landblock EVER collision-publish", not "is
/// it collision-published right now" — B1 review fix) for headless — so
/// <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 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);
}
/// <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. <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 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>
/// <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>
/// 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>
/// 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>
/// C4 route 4b-1: the Runtime-owned, per-entity accepted-Position execution
/// seam for a remote whose classification is <c>SetPosition</c> (teleport /
/// cell-less — 4b-3's eventual disposition) or <c>SetPositionSimple</c> (far
/// snap, &gt;=96 m — 4b-2's). Route 4a's
/// <see cref="RuntimeRemoteSteadyStatePosition"/> already owns the two
/// dispositions that perform no SetPosition at all
/// (<c>NoPositionOperation</c>/<c>Interpolate</c>); this controller is the
/// architectural sibling for the remaining two, built from route 2's
/// controller shape with two deliberate omissions and one deliberate
/// generalization:
///
/// <list type="bullet">
/// <item><description><b>No ack.</b> Retail's remote arm has no
/// <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. <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
/// after N+1 has already merged would apply a pose the newer packet already
/// superseded. When this controller's own tracked operation dies for any
/// reason (superseded, torn down, forgotten by the entity's next accepted
/// Position), it is simply dropped: the next packet supplies the current
/// truth on its own.</description></item>
/// <item><description><b>Per-entity, not per-session.</b> Route 2's
/// <c>_pending</c> is a single slot because the local player is the only
/// entity that route ever touches. Remotes are N entities, so
/// <see cref="_pending"/> is a per-key map and every entry is independent —
/// route 2's single-owner invariant (Begin refuses a second live entry for
/// the SAME key) is unchanged, just re-derived per entity instead of
/// globally.</description></item>
/// </list>
///
/// <para>
/// <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. 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.
/// </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, 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>
/// One instance per host session route, constructed once per host process
/// and reused across reconnects exactly like
/// <see cref="RuntimeFirstEntryDriveController"/> and
/// <see cref="RuntimeAcceptedPositionDriveController"/> —
/// <see cref="AttachRoute"/>/<see cref="DetachRoute"/> assert the same
/// "session reset precedes a new route" ordering and clear any tracked
/// entries left by a torn-down session.
/// </para>
/// </summary>
internal sealed class RuntimeRemotePlacementDriveController
{
private sealed class Pending
{
internal required RuntimeEntityRecord Record { get; init; }
internal required RuntimeEntityPlacementToken Token { get; init; }
internal required RuntimeAuthoritativePositionRoute Route { get; init; }
}
private readonly RuntimeEntityObjectLifetime _entityObjects;
private readonly IGameRuntimeClock _clock;
private readonly IPreparedCollisionSource _collisionSource;
private readonly IRuntimeRemotePlacementServiceWindow _serviceWindow;
/// <summary>
/// Per-entity preparation-retry map (bounded to the two retryable
/// <see cref="RuntimeSetPositionMoverPreparationStatus"/> reasons — Setup
/// or world-frame data not resolved yet, NEITHER of which withdraws the
/// entity). A <c>DeferredCell</c> outcome never enters this map — see the
/// class doc's central decision.
/// </summary>
private readonly Dictionary<RuntimeEntityKey, Pending> _pending = [];
/// <summary>
/// B4 review fix: per-entity tokens whose <see cref="SubmitAndResolve"/>
/// outcome was <c>CommittedHostAcknowledgementPending</c> and were STILL
/// live (not synchronously consumed-and-acknowledged by the production
/// placement-projection subscription inside that same call) the instant
/// <see cref="SubmitAndResolve"/> returned <c>Committed</c>. Without this,
/// the ledger went blind the moment <c>Committed</c> was returned, hiding
/// exactly the declined-sink class the FIFO retry mechanism exists for.
/// Pruned lazily (self-healing) on every read — see
/// <see cref="CountLiveAwaitingAcknowledgement"/> — never gates any
/// placement decision itself; Core's own <c>_operations</c> map remains
/// the sole authority <c>TryBeginExclusiveAuthoredPlacement</c> consults.
/// </summary>
private readonly Dictionary<RuntimeEntityKey, RuntimeEntityPlacementToken>
_awaitingAcknowledgement = [];
private readonly List<RuntimeEntityKey> _driveScratch = [];
/// <summary>
/// C2-1 review fix (delta round): dedicated reusable scratch list for
/// <see cref="CountLiveAwaitingAcknowledgement"/>'s self-heal removal
/// pass — kept separate from <see cref="_driveScratch"/> (owned by
/// <see cref="Advance"/>) so a ledger read reached while <c>Advance</c>
/// 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;
internal RuntimeRemotePlacementDriveController(
RuntimeEntityObjectLifetime entityObjects,
IGameRuntimeClock clock,
IPreparedCollisionSource collisionSource,
IRuntimeRemotePlacementServiceWindow serviceWindow)
{
_entityObjects = entityObjects
?? throw new ArgumentNullException(nameof(entityObjects));
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_collisionSource = collisionSource
?? throw new ArgumentNullException(nameof(collisionSource));
_serviceWindow = serviceWindow
?? throw new ArgumentNullException(nameof(serviceWindow));
_entityObjects.RegisterRemotePlacementDriveOwnership(
CountLivePending);
// B4 review fix: a second, independent registration — multiple
// registrations sum (RegisterRemotePlacementDriveOwnership's own doc
// comment) — so the awaiting-acknowledgement dimension is visible in
// the SAME ledger without changing what _pending itself reports.
_entityObjects.RegisterRemotePlacementDriveOwnership(
CountLiveAwaitingAcknowledgement);
}
/// <summary>
/// Preparation-stage retries only (see <see cref="_pending"/>'s own doc).
/// Deliberately does NOT include <see cref="_awaitingAcknowledgement"/> —
/// 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;
/// <summary>
/// True when route 4b owns this classification for a remote — the two
/// dispositions route 4a's <see cref="RuntimeRemoteSteadyStatePosition"/>
/// does NOT already handle. Everything else (<c>Interpolate</c>,
/// <c>NoPositionOperation</c>, <c>RejectedAuthority</c>,
/// <c>RejectedData</c>, <c>AwaitFreshPosition</c>) is out of scope here.
/// B6 review fix: <c>Disposition</c> alone is not exact — the classifier
/// (<c>RuntimeAuthoritativePositionRouteClassifier.cs</c>) assigns
/// <c>SetPosition</c>/<c>SetPositionSimple</c> at several independent
/// call sites (:256, :332, :355, :403, :459) and derives
/// <c>OperationKind</c> separately (:560-575) — the SAME disposition
/// covers the LOCAL PLAYER's FORCE_POSITION/teleport branches
/// (<c>RuntimeSetPositionOperationKind.LocalAuthoritative</c>).
/// <paramref name="route"/> is a parameter separate from the entity
/// record at every call site, so a mismatched pair is expressible;
/// gating on <c>OperationKind</c> narrows to remotes AND the local
/// player's own initial Create — <c>OperationKind</c>'s own switch
/// (:559-570) maps <c>InitialLogin</c> to the LOCAL PLAYER's Create
/// ONLY; a REMOTE top-level Create maps to <c>RemoteAuthoritative</c>
/// exactly like a remote accepted Position does (correcting this
/// comment's earlier, wrong claim that all initial Creates map to
/// <c>InitialLogin</c>).
/// <para>
/// C2-4 review fix (delta round): so <c>OperationKind</c> alone still
/// does not exclude a remote top-level Create — <c>ClassifyCreate</c>
/// (:254-273) emits <c>SetPosition</c> + <c>RemoteAuthoritative</c> +
/// <c>InitialCreateFlags</c> (<c>Placement|Slide</c>) for one, while
/// <c>ClassifyAcceptedPosition</c>'s remote branches (:401-416,
/// :454-472) always carry <c>AuthoritativeTeleportFlags</c>
/// (<c>Teleport|Slide|SendPositionEvent</c>) for the SAME disposition/
/// OperationKind pair. The <c>Teleport</c> bit is the exact
/// discriminator retail's own flag choice provides — Create asks for
/// placement collision, an accepted Position asks for a teleport
/// resolve — so requiring it here excludes Creates (route 4b-1 is a
/// POSITION-only route; the first-entry conductor owns every Create)
/// without excluding either remote Position shape.
/// </para>
/// <para>
/// C4 route 5 (D-P3): widened to admit
/// <c>ProjectileAuthoritative</c> alongside <c>RemoteAuthoritative</c> —
/// a live missile's teleport/cell-less and far accepted-Position
/// dispositions are disposition-identical to a remote's (the classifier
/// never branches on kind past <c>LocalPlayer</c>), and route 5's
/// <see cref="ApplyAcceptedProjectilePosition"/> is the sibling seam over
/// this SAME shared core (<see cref="TryExecuteAcceptedRemotePosition"/> +
/// <see cref="StoreAcceptedDestinationPose"/>) — never a second pending
/// map, never a sibling controller. <c>OwnsFarSnap</c> and
/// <c>OwnsTeleportPlacement</c> are deliberately NOT widened: both remote
/// arm methods (<see cref="ApplyAcceptedRemoteFarSnap"/>/
/// <see cref="ApplyAcceptedRemoteTeleport"/>) require and throw without a
/// <c>RemoteMotion</c>, which a projectile never has.
/// </para>
/// </summary>
internal static bool OwnsPlacement(RuntimeAuthoritativePositionRoute route) =>
route.OperationKind is RuntimeSetPositionOperationKind.RemoteAuthoritative
or RuntimeSetPositionOperationKind.ProjectileAuthoritative
&& route.Disposition is RuntimeAuthoritativePositionDisposition.SetPosition
or RuntimeAuthoritativePositionDisposition.SetPositionSimple
&& (route.SetPositionFlags & PhysicsSetPositionFlags.Teleport) != 0;
/// <summary>
/// Mirrors <see cref="RuntimeFirstEntryDriveController.AttachRoute"/> and
/// <see cref="RuntimeAcceptedPositionDriveController.AttachRoute"/>: this
/// controller outlives its session routes, so the "session reset
/// precedes a new route" ordering is asserted, not assumed.
/// </summary>
internal void AttachRoute(object route)
{
ArgumentNullException.ThrowIfNull(route);
if (_routeOwner is not null && !ReferenceEquals(_routeOwner, route))
{
throw new InvalidOperationException(
"A remote placement drive controller serves one session "
+ "route at a time; the prior route must be disposed "
+ "(session reset precedes a new route) before a "
+ "replacement attaches.");
}
_routeOwner = route;
}
/// <summary>
/// Route-scoped teardown: abandons every tracked entry, but ONLY when
/// <paramref name="route"/> is the attached owner.
///
/// <para>
/// C2-1 review fix (delta round) — the prior version of this method (and
/// this comment) was wrong: it cleared <see cref="_pending"/> and
/// <see cref="_awaitingAcknowledgement"/> as if they were pure
/// bookkeeping, citing <see cref="RuntimeFirstEntryDriveController.DetachRoute"/>
/// as the mirror. That is the WRONG sibling — first-entry's tracked
/// entries have an INDEPENDENT owner (the residence lease) that survives
/// route teardown on its own, so clearing first-entry's local map merely
/// stops WATCHING an operation something else still owns. These two maps
/// have no such owner: EVERY entry holds a Core operation THIS
/// controller alone began — <see cref="_pending"/> at
/// <c>AwaitingPreparation</c> (already begun via
/// <c>TryBeginExclusiveAuthoredPlacement</c>), <see cref="_awaitingAcknowledgement"/>
/// at <c>AwaitingCommitAcknowledgement</c> with a published <c>Place</c>.
/// Clearing the local dictionary without cancelling the Core operation
/// left it live forever, pinning its landblock prefix
/// (<c>HasOldPrefixPlacementDebt</c>) — docs/ISSUES.md #310's unbounded
/// streaming-stall hazard, now reachable from an ordinary reconnect/
/// session-reset instead of only a stuck asset retry. The correct mirror
/// is route 2's <c>RuntimeAcceptedPositionDriveController.DetachRoute</c>
/// → <c>AbandonPending</c>, which this now matches: cancel every live
/// operation (<see cref="CancelToken"/>, <c>restoreCancelledPark: true</c>
/// — these are cancellations of an abandoned placement INTENT, not
/// withdrawals, so any park rolls back rather than stranding the body)
/// before clearing the local maps.
/// </para>
/// </summary>
internal void DetachRoute(object route)
{
ArgumentNullException.ThrowIfNull(route);
if (!ReferenceEquals(_routeOwner, route))
return;
_routeOwner = null;
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
if (_pending.Count != 0)
{
Pending[] abandoned = [.. _pending.Values];
_pending.Clear();
foreach (Pending entry in abandoned)
{
setPosition.ForgetPlacementCompletion(entry.Token);
CancelToken(setPosition, entry.Token);
}
}
if (_awaitingAcknowledgement.Count != 0)
{
RuntimeEntityPlacementToken[] abandoned =
[.. _awaitingAcknowledgement.Values];
_awaitingAcknowledgement.Clear();
foreach (RuntimeEntityPlacementToken token in abandoned)
{
setPosition.ForgetPlacementCompletion(token);
CancelToken(setPosition, token);
}
}
}
/// <summary>
/// Executes an already-classified remote accepted Position against the
/// canonical Runtime SetPosition owner. <paramref name="record"/>'s
/// Snapshot and PositionAuthorityVersion must already reflect the merge
/// <see cref="RuntimeEntityObjectLifetime.TryApplyPosition"/> performed —
/// this method never re-merges the wire frame, and <paramref name="route"/>
/// must already be the result of
/// <see cref="RuntimeEntityObjectLifetime.ClassifyRemoteAcceptedPosition"/>
/// for the SAME packet (the shared classification builder every remote
/// caller uses — never re-derived here).
/// </summary>
internal RuntimeRemotePlacementExecutionStatus TryExecuteAcceptedRemotePosition(
RuntimeEntityRecord record,
in RuntimeAuthoritativePositionRoute route)
{
ArgumentNullException.ThrowIfNull(record);
if (!OwnsPlacement(route)
|| record.PhysicsBody is null
|| record.Key is not { } key)
{
return RuntimeRemotePlacementExecutionStatus.NotApplicable;
}
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
// A retained preparation retry whose operation died some other way
// (superseded, torn down, generation change, or simply forgotten by
// an unrelated accepted Position for this same entity) must not
// block a fresh Begin for THIS packet — self-heal rather than report
// a Contention nothing is actually contending.
if (_pending.TryGetValue(key, out Pending? stale)
&& !setPosition.IsPlacementCurrent(stale.Token))
{
_pending.Remove(key);
}
CreateObject.ServerPosition? destination =
record.Snapshot.Physics?.Position ?? record.Snapshot.Position;
if (destination is not { } accepted
|| !CanAttemptDestination(setPosition, accepted.LandblockId))
{
// 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;
}
RuntimeEntityPlacementToken token =
setPosition.TryBeginExclusiveAuthoredPlacement(
record,
record.PositionAuthorityVersion,
route.OperationKind);
if (!token.IsValid)
return RuntimeRemotePlacementExecutionStatus.Contention;
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>
/// C4 route 4b-3: retail's teleport/cell-less branch, end to end, for one
/// remote whose accepted Position already classified to
/// <see cref="RuntimeRemoteTeleportPosition.OwnsTeleportPlacement"/>.
///
/// <code>
/// 00516386 if (newer_event(TELEPORT_TS) || this_1-&gt;cell == 0)
/// 005163ef CPhysicsObj::teleport_hook(this_1, edx_2);
/// 00516414 SetPositionStruct::SetFlags(&amp;var_64, 0x1012);
/// 00516420 CPhysicsObj::SetPosition(this_1, &amp;var_64);
/// 00516438 return 1;
/// </code>
///
/// <para>
/// The teleport hook is the CALLER's responsibility (D3) — it must run
/// BEFORE this method, regardless of what the placement then yields,
/// exactly like retail's ordering. This method does not clear the
/// interpolation queue itself: unlike the far arm, the classifier's
/// teleport branch carries <c>StopInterpolating: false</c> on purpose —
/// retail's clear for THIS branch lives inside <c>teleport_hook</c>'s
/// <c>PositionManager::StopInterpolating</c> @0x00514EFD, not in
/// <c>MoveOrTeleport</c> itself.
/// </para>
///
/// <para>
/// The <c>store_position</c> fallback (invariant 1) is identical to the
/// far arm's: every outcome for which the canonical placement never
/// reached the engine
/// (<see cref="RuntimeRemotePlacementExecutionStatusExtensions.StoresAcceptedDestination"/>)
/// still advances the body to the accepted destination pose. Retail
/// discards <c>SetPosition</c>'s error and returns 1 unconditionally
/// @0x00516438 — the placement's outcome never changes whether the
/// packet "succeeded".
/// </para>
/// </summary>
internal RuntimeRemotePlacementExecutionStatus ApplyAcceptedRemoteTeleport(
RuntimeEntityRecord record,
RemoteMotion remote,
in RuntimeAuthoritativePositionRoute route)
{
ArgumentNullException.ThrowIfNull(record);
ArgumentNullException.ThrowIfNull(remote);
if (!RuntimeRemoteTeleportPosition.OwnsTeleportPlacement(route))
{
throw new ArgumentException(
"Only a remote teleport/cell-less classification (SetPosition, "
+ "RemoteAuthoritative, Teleport-flagged) may be applied "
+ "through the teleport arm; the caller must select the arm "
+ "with RuntimeRemoteTeleportPosition.OwnsTeleportPlacement.",
nameof(route));
}
RuntimeRemotePlacementExecutionStatus status =
TryExecuteAcceptedRemotePosition(record, route);
if (status.StoresAcceptedDestination())
StoreAcceptedDestinationPose(record);
return status;
}
/// <summary>
/// C4 route 5 (D-P2): the projectile arm over this SAME shared core. A
/// live missile carries no <see cref="RemoteMotion"/>, so
/// <see cref="ApplyAcceptedRemoteFarSnap"/>/<see cref="ApplyAcceptedRemoteTeleport"/>
/// are not reusable — both require one and throw without it. This is the
/// sibling seam the D-P2 design pins: same
/// <see cref="TryExecuteAcceptedRemotePosition"/> +
/// <see cref="StoreAcceptedDestinationPose"/> core, same
/// <see cref="_pending"/>/<see cref="_awaitingAcknowledgement"/> ledgers,
/// no second pending map, no sibling controller (trap T9).
///
/// <para>
/// Returns <see langword="null"/> for every disposition this route does
/// not own: <c>Interpolate</c> (near, in contact) and
/// <c>NoPositionOperation</c> (airborne) are pinned NO-OPS — retail would
/// lazily build interpolation/leash machinery for a manager-less missile
/// (@0x005163AF / @0x00454272-@0x00510523), which acdream deliberately
/// does not construct for a ballistic body (the register row this route
/// adds); <c>RejectedAuthority</c>/<c>RejectedData</c> and an ownership
/// mismatch are SWALLOWED — write nothing, never fall through to the
/// remote tail (trap T5). A caller must not fall back to any remote arm
/// when this returns <see langword="null"/>.
/// </para>
///
/// <para>
/// <b>No velocity write (D-P5).</b> Retail's <c>MoveOrTeleport</c>
/// @0x00516330 never references its velocity argument in the decompiled
/// body, and a byte-level disassembly of the PDB-paired binary
/// (@0x00516330-@0x00516438) confirms no instruction anywhere in the
/// function reads that argument's stack slot. This seam commits no
/// velocity from the Position packet at all — the Vector channel
/// (<see cref="RuntimeProjectilePhysicsUpdater.ApplyAuthoritativeVector"/>)
/// remains the sole velocity authority for a missile.
/// </para>
///
/// <para>
/// <b>No constraint leash armed (D-P4).</b> Unlike the remote arms, this
/// method never calls <c>TryArmConstraintAfterOperation</c> — the
/// classifier's projectile routes still carry
/// <c>ConstrainPhase.AfterPositionOperation</c> (kind-blind), but this arm
/// deliberately does not consume it, matching the pinned divergence.
/// </para>
///
/// <para>
/// <b>Teleport hook reduction (D-P4).</b> Of retail's six
/// <c>teleport_hook</c> @0x00514ED0 actions, five are structurally absent
/// for a manager-less missile (no <c>MovementManager</c>/
/// <c>PositionManager</c>/<c>TargetManager</c>). The sixth,
/// <c>report_collision_end(this, 1)</c> @0x00514F31-@0x00514620, applies
/// to any object with a collision table and runs BEFORE the placement —
/// ported here as <c>RuntimeCollisionReportingState.LeaveWorld</c> (the
/// exact force-end seam the 4b-3 round-2 review validated against the
/// same retail address).
/// </para>
///
/// <para>
/// Prediction is invalidated once per packet, before any body write on
/// this route (placement or the store fallback) — mirroring
/// <c>RuntimeProjectilePhysicsUpdater</c>'s existing invalidate-before-
/// write ordering — so an in-flight split quantum straddling this packet
/// aborts at <c>Complete</c> rather than clobbering a canonical
/// placement. The no-op dispositions invalidate nothing: the body is
/// untouched, so a straddling quantum completing over them is correct.
/// </para>
/// </summary>
internal RuntimeRemotePlacementExecutionStatus? ApplyAcceptedProjectilePosition(
RuntimeEntityRecord record,
in RuntimeAuthoritativePositionRoute route)
{
ArgumentNullException.ThrowIfNull(record);
if (route.OperationKind
is not RuntimeSetPositionOperationKind.ProjectileAuthoritative
|| record.Projectile is not RuntimeProjectile projectile
|| record.PhysicsBody is not { } body
|| !ReferenceEquals(body, projectile.Body))
{
return null;
}
// A6 fix (review round): captured BEFORE the placement dispatch,
// mirroring the deleted tail's `bool wasInWorld = body.InWorld;`
// ordering — TryExecuteAcceptedRemotePosition's canonical commit
// calls body.SnapToCell, which sets InWorld = true, so reading this
// AFTER the dispatch (as the first cut of this seam did) makes the
// re-activation branch below permanently dead on every committed
// outcome.
bool wasInWorld = body.InWorld;
RuntimeRemotePlacementExecutionStatus status;
switch (route.Disposition)
{
case RuntimeAuthoritativePositionDisposition.SetPosition:
_entityObjects.Physics.CollisionReports.LeaveWorld(record);
projectile.InvalidatePrediction();
status = TryExecuteAcceptedRemotePosition(record, route);
break;
case RuntimeAuthoritativePositionDisposition.SetPositionSimple:
// B1/B2 fix (round-2 review): retail's far branch runs
// `StopInterpolating` @0x005163C9-@0x005163CB whenever
// `position_manager != 0` — the SAME guard the remote far
// arm ports as `ApplyAcceptedRemoteFarSnap`'s
// `if (route.StopInterpolating) remote.Interp.Clear();`. A
// bare missile has no RemoteMotion so this is structurally
// inert, but the ADOPTED-BODY case (TryBind's shared-body
// branch: an ordinary remote whose Missile bit was set by a
// later State packet) carries a live Interp queue the far
// branch must clear too — the teleport hook only covers the
// SetPosition disposition.
if (route.StopInterpolating
&& record.RemoteMotion is RemoteMotion adoptedFar)
{
adoptedFar.Interp.Clear();
}
projectile.InvalidatePrediction();
status = TryExecuteAcceptedRemotePosition(record, route);
break;
default:
// Interpolate / NoPositionOperation: pinned no-op (D-P4).
// RejectedAuthority / RejectedData: swallow (T5) — the
// shared authority gate already rejected an invalid payload
// upstream; there is nothing left to route.
return null;
}
if (status.StoresAcceptedDestination())
StoreAcceptedDestinationPose(record);
if (status is not RuntimeRemotePlacementExecutionStatus.Deferred
and not RuntimeRemotePlacementExecutionStatus.RejectedByPlacement)
{
// Invariant 2: presentation advances on every committed/stored
// outcome only — Deferred/RejectedByPlacement leave the body at
// its prior (already-synced) pose.
SyncProjectilePresentation(record, projectile, body, wasInWorld);
}
return status;
}
/// <summary>
/// C4 route 5 (REVISED after the review round — A6/A7/A8): the
/// J5.6-owned post-commit lifecycle tail (InWorld/Activate/shadow-sync
/// on spatial+visible, suspend on spatial+hidden, deactivate+suspend on
/// non-spatial), reduced from the deleted
/// <c>RuntimeProjectilePhysicsUpdater.ApplyAuthoritativePosition</c>
/// tail (former <c>:390-422</c>) to this controller's own seam —
/// <paramref name="wasInWorld"/> is the caller's pre-dispatch capture
/// (A6: reading <c>body.InWorld</c> here, after the canonical commit's
/// own <c>SnapToCell</c> already forced it true, made the re-activation
/// branch permanently dead); <see cref="_clock"/> supplies the same
/// clock source the deleted method took as an explicit
/// <c>currentTime</c> parameter; the world-frame offset comes from
/// <see cref="RuntimePhysicsState.TryGetWorldFrameOffset"/> (the same
/// source <see cref="StoreAcceptedDestinationPose"/> uses) instead of an
/// App-supplied live-center pair.
/// </summary>
private void SyncProjectilePresentation(
RuntimeEntityRecord record,
RuntimeProjectile projectile,
PhysicsBody body,
bool wasInWorld)
{
if (!_entityObjects.Entities.IsCurrent(record)
|| !ReferenceEquals(record.Projectile, projectile)
|| !ReferenceEquals(record.PhysicsBody, body))
{
return;
}
RuntimePhysicsState physics = _entityObjects.Physics;
bool spatial = physics.IsSpatialProjectile(record, projectile);
bool hidden =
(record.FinalPhysicsState & PhysicsStateFlags.Hidden) != 0;
uint localId = record.LocalEntityId ?? 0u;
if (spatial && !hidden)
{
if (!wasInWorld)
{
body.LastUpdateTime = _clock.SimulationTimeSeconds;
if ((body.State & PhysicsStateFlags.Static) == 0)
body.TransientState |= TransientStateFlags.Active;
}
body.InWorld = true;
// A8 fix: #284's policy ("a frame that can never arrive is
// terminal, never silent") applies here exactly as it does to
// StoreAcceptedDestinationPose. A false result during the
// legitimate pre-local-player-Create window silently skips the
// publish (self-heals once the frame arrives); a false result
// AFTER that window is a genuinely stuck frame, and
// ThrowIfWorldFrameUnreachable escalates it instead of leaving
// the shadow silently stale forever.
if (physics.TryGetWorldFrameOffset(
record.FullCellId,
out float offsetX,
out float offsetY))
{
physics.Engine.ShadowObjects.UpdatePosition(
localId,
body.Position,
body.Orientation,
offsetX,
offsetY,
record.FullCellId,
seedCellId: record.FullCellId);
}
else
{
physics.ThrowIfWorldFrameUnreachable(record.FullCellId);
}
}
else if (spatial)
{
body.InWorld = true;
// A7 fix: retail's hidden-branch clock consumption — restored,
// matching ProjectileController.TryBind's equivalent branch
// ("consume the hidden clock so UnHide cannot replay a time
// backlog").
body.LastUpdateTime = _clock.SimulationTimeSeconds;
physics.Engine.ShadowObjects.Suspend(localId);
}
else
{
body.InWorld = false;
body.TransientState &= ~TransientStateFlags.Active;
physics.Engine.ShadowObjects.Suspend(localId);
}
}
/// <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
/// re-calling the SAME prepare+submit pair, exactly like
/// <see cref="RuntimeFirstEntryDriveController"/>'s own continuation
/// completion. Bounded, non-allocating iteration mirrors
/// <see cref="RuntimeFirstEntryDriveController"/>'s <c>_driveScratch</c>
/// template. Safe to call from any host cadence point; a no-op when
/// nothing is pending.
/// </summary>
internal void Advance()
{
if (_driving || _pending.Count == 0)
return;
_driving = true;
try
{
_driveScratch.Clear();
foreach (RuntimeEntityKey key in _pending.Keys)
_driveScratch.Add(key);
RuntimeSetPositionState setPosition =
_entityObjects.Physics.SetPosition;
foreach (RuntimeEntityKey key in _driveScratch)
{
if (!_pending.TryGetValue(key, out Pending? pending))
continue;
if (!setPosition.IsPlacementCurrent(pending.Token))
{
// Forgotten by some other accepted Position for this
// same entity before this retry resolved. No re-issue
// funnel for remotes: the next packet supplies current
// truth on its own.
_pending.Remove(key);
continue;
}
_pending.Remove(key);
// R3 fix (review round): a retained retry can belong to a
// projectile operation exactly as it can belong to a
// remote's — this is the SAME shared _pending map (trap T9:
// no second map), and pending.Route carries the OperationKind
// that was classified when the retry was first parked.
// Neither invariant this route pins (prediction invalidated
// before every body write; presentation advances on every
// committed/stored outcome) may hold on the direct arm only.
// Real nullable locals (not a stored bool) so the compiler
// can track definite assignment through the branches below.
RuntimeProjectile? pendingProjectile = null;
PhysicsBody? pendingBody = null;
if (pending.Route.OperationKind
is RuntimeSetPositionOperationKind.ProjectileAuthoritative
&& pending.Record.Projectile is RuntimeProjectile candidateProjectile
&& pending.Record.PhysicsBody is { } candidateBody
&& ReferenceEquals(candidateBody, candidateProjectile.Body))
{
pendingProjectile = candidateProjectile;
pendingBody = candidateBody;
}
bool pendingWasInWorld = pendingBody?.InWorld ?? false;
// 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 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;
RuntimeRemotePlacementExecutionStatus retryStatus;
if (destination is not { } accepted
|| !CanAttemptDestination(
setPosition,
accepted.LandblockId))
{
CancelToken(setPosition, pending.Token);
pendingProjectile?.InvalidatePrediction();
StoreAcceptedDestinationPose(pending.Record);
retryStatus = RuntimeRemotePlacementExecutionStatus.Refused;
}
else
{
// B5 fix (round-2 review): invalidating BEFORE this call
// unconditionally was wrong when SubmitAndResolve itself
// re-parks (returns Contention) — that outcome writes
// NOTHING (no store, no commit), so invalidating for it
// violates invariant 4's "the no-op dispositions
// invalidate nothing" on this arm specifically (unlike
// the entry point, where StoresAcceptedDestination()
// treats Contention as a storing outcome via the
// caller's own StoreAcceptedDestinationPose — this retry
// arm does not store on a re-parked Contention, matching
// the pre-existing residual A3/round-1 already named).
// Invalidating AFTER the call instead of before is safe
// here: this method is single-threaded and synchronous,
// so a write performed inside SubmitAndResolve and the
// very next statement's invalidate are never observably
// separated by a quantum's Complete call.
retryStatus = SubmitAndResolve(
pending.Record, pending.Token, pending.Route);
if (retryStatus
is not RuntimeRemotePlacementExecutionStatus.Contention)
{
pendingProjectile?.InvalidatePrediction();
}
}
if (pendingProjectile is { } confirmedProjectile
&& pendingBody is { } confirmedBody
&& retryStatus is not RuntimeRemotePlacementExecutionStatus.Deferred
and not RuntimeRemotePlacementExecutionStatus.RejectedByPlacement)
{
SyncProjectilePresentation(
pending.Record,
confirmedProjectile,
confirmedBody,
pendingWasInWorld);
}
}
}
finally
{
_driving = false;
}
}
private RuntimeRemotePlacementExecutionStatus SubmitAndResolve(
RuntimeEntityRecord record,
in RuntimeEntityPlacementToken token,
in RuntimeAuthoritativePositionRoute route)
{
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
RuntimeSetPositionMoverPreparationStatus status =
setPosition.TryPrepareAndSubmitAuthoredPlacement(
record,
token,
route.OperationKind,
route.SetPositionFlags,
_collisionSource,
_clock.SimulationTimeSeconds,
out RuntimeSetPositionOutcome outcome,
resolveWorldOffsetFromRuntimeFrame: true);
if (status != RuntimeSetPositionMoverPreparationStatus.Prepared)
{
if (status.IsRetryable())
{
_pending[token.Entity] = new Pending
{
Record = record,
Token = token,
Route = route,
};
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.RejectedPreparation;
}
switch (outcome.Status)
{
case RuntimeSetPositionStatus.CommittedHostAcknowledgementPending:
// No ack for remotes. The production placement-projection
// subscription (shared infrastructure, not owned here) has
// already applied-and-acknowledged the Place receipt
// synchronously inside the SetPosition call above, if it was
// going to — exactly like route 2's own commit branch, minus
// the ack call route 2 makes for the local player. B4 review
// fix: when the sink declined instead (host not ready — the
// exact scenario the FIFO retry mechanism exists for), the
// operation is STILL LIVE in Core's _operations map until
// some later AcknowledgeProjection retires it. Track it so
// the ledger can see that class of outstanding operation
// instead of going blind the instant this method returns.
if (setPosition.IsPlacementCurrent(token))
_awaitingAcknowledgement[token.Entity] = token;
return RuntimeRemotePlacementExecutionStatus.Committed;
case RuntimeSetPositionStatus.DeferredCell:
// 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.Deferred;
default:
// 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.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
/// landed through whatever path — the synchronous in-call apply, a later
/// FIFO retry, or Runtime's own session-reset/generation-change teardown
/// clearing the operation outright) before returning the live count.
/// This is what lets the constructor's second
/// <c>RegisterRemotePlacementDriveOwnership</c> registration converge to
/// zero (B4 review fix) without this controller needing a separate
/// periodic pump for this one dictionary — every ownership-snapshot read
/// (including the exact convergence checks teardown/reset/generation
/// change assert) sees the truth as of that read.
///
/// <para>
/// C2-1 review fix (delta round), disposal safety: <c>IsPlacementCurrent</c>'s
/// first statement is <c>EnsureNotDisposed</c>, which THROWS once
/// <see cref="RuntimeSetPositionState"/> is disposed. A post-<c>Dispose()</c>
/// <c>CaptureOwnership()</c> read is the designed contract
/// (<c>GameWindowLifetime.DisposeGameRuntime</c>: <c>runtime.Dispose();
/// runtime.CaptureOwnership();</c>), so this — the first ledger provider
/// to reach into another disposable subsystem — must survive it. When
/// <see cref="RuntimePhysicsState.IsDisposed"/> is already true, Core
/// itself is gone; there is nothing left to ask, so this returns
/// whatever count is STILL in the local map rather than calling into the
/// disposed state. A healthy teardown already cancelled and cleared
/// every entry via <see cref="DetachRoute"/> before disposal, so this
/// branch reports 0 in the healthy path and a genuine nonzero leak
/// otherwise — never an exception either way.
/// </para>
/// <para>
/// C2-1 review fix (delta round), allocation: reuses
/// <see cref="_awaitingAcknowledgementScratch"/> instead of a per-call
/// <c>List&lt;RuntimeEntityKey&gt;</c>, mirroring <see cref="_driveScratch"/>'s
/// own template.
/// </para>
/// </summary>
private int CountLiveAwaitingAcknowledgement()
{
if (_awaitingAcknowledgement.Count == 0)
return 0;
if (_entityObjects.Physics.IsDisposed)
return _awaitingAcknowledgement.Count;
RuntimeSetPositionState setPosition = _entityObjects.Physics.SetPosition;
_awaitingAcknowledgementScratch.Clear();
foreach ((RuntimeEntityKey key, RuntimeEntityPlacementToken token)
in _awaitingAcknowledgement)
{
if (!setPosition.IsPlacementCurrent(token))
_awaitingAcknowledgementScratch.Add(key);
}
foreach (RuntimeEntityKey key in _awaitingAcknowledgementScratch)
_awaitingAcknowledgement.Remove(key);
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 (the projection
/// materializer <c>DatLiveEntityProjectionMaterializer</c> — C4 route
/// 4b-3 deleted the second shipped writer, <c>RemoteTeleportController</c>'s
/// rollback, and C4 route 7 D4 demoted the third,
/// <c>EquippedChildRenderController.TickChild</c>, to a presentation-
/// only bucket move that no longer touches <c>record.FullCellId</c> —
/// Runtime's own D1/D2 propagation is the child's canonical writer now)
/// can move <c>record.FullCellId</c> to a THIRD landblock between the
/// retained submit and the retry. The remaining writer is
/// 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)
{
// Cancellation, not withdrawal: this controller abandons a placement
// intent while the remote stays in the world, so a DeferredCell park
// must roll back rather than strand the entity invisible and
// intangible (RuntimeSetPositionState.Forget).
RuntimePlacementCancellationReceipt cancellation =
setPosition.ForgetExactPlacement(
token,
restoreCancelledPark: true);
if (cancellation.IsValid)
setPosition.PublishCancellation(cancellation);
}
}