using AcDream.Content; using AcDream.Core.Physics; using AcDream.Runtime.Physics; namespace AcDream.Runtime.Entities; /// /// Typed yields for . /// Mirrors the C3a conductor's vocabulary /// (RuntimeLocalPlayerFirstEntryStatus) rather than inventing a /// parallel one; the two publication-only statuses have no remote analog. /// internal enum RuntimeRemoteFirstEntryStatus : byte { /// /// The underlying /// call reported Completed: residence consumed, initial tail and /// FIFO drained, ExecutorCompleted receipt dispatched. Terminal. /// Completed, /// /// The authored-mover Setup read /// () is not /// yet available. Retry with the same arguments once the prepared-asset /// package lands; no Runtime state changed. /// AwaitingCollisionSource, /// /// The submitted placement deferred (DeferredCell — destination /// collision generation not ready, or a collision-prefix quiescence held /// it) and its parked operation has not produced an acknowledgeable /// Place projection yet. The wake is internal to /// (collision-generation commit /// drives RetryDeferred); retry after it. /// AwaitingPlacement, /// /// Our projection exists but could not be acknowledged this call — /// either another entity's receipt sits ahead of ours in the one ordered /// FIFO, or /// still observed PendingPlacement. Retry the same stage. /// AwaitingReceiptAcknowledgement, /// /// Passthrough of the executor's own AwaitingContinuationPlacement /// — a later FIFO continuation needs its own authored placement before /// the drain can finish; entirely the executor's concern from here on. /// AwaitingContinuationPlacement, /// /// A reentrant for the SAME entity arrived while an /// outer call for it was still on the stack, or another owner's /// body/remote-motion binding callback is mid-flight on this record. /// Retry once the outer call has returned. /// Contention, /// /// The residence token matches nothing this conductor can own — including /// a LOCAL-PLAYER lease (), /// which belongs to the C3a conductor, never this one. /// RejectedToken, /// /// An authority-shaped failure (stale epoch/session/identity, deleted or /// replaced record, a foreign physics body bound out-of-band, a rejected /// or cancelled submission). Abandoned; progress removed. The caller must /// begin a fresh sequence (a new residence lease), never retry this call. /// RejectedAuthority, } internal readonly record struct RuntimeRemoteFirstEntryOwnershipSnapshot( int ActiveCount) { internal bool IsConverged => ActiveCount == 0; } /// /// The dormant, resumable Runtime transaction that dissolves C3's Finding C: /// ordinary remote-creature and projectile Creates classify to /// SetPosition, but no production path constructs their canonical /// at Create time (bodies arrive with first motion /// today), so 's /// Record.PhysicsBody requirement rejects the residence route's /// initial placement. This class is the remote analog of the C3a conductor /// (RuntimeLocalPlayerFirstEntryState) WITHOUT the publication chain — /// remotes have no PlayerMovementController — and with retail body /// construction in its place: /// /// mover preparation (retail CPhysicsObj::makeObject shaping the /// Setup, which precedes set_description in /// ACCObjectMaint::CreateObject 0x00558870 step 2-vs-6) -> /// body construction per the exact set_description order /// (0x00514F40; ) bound through the /// canonical writer /// -> ordinary authored submission /// ( — retail /// enter_world, which SmartBox::HandleCreateObject 0x00454C80 /// runs for a top-level object with a nonzero wire cell AFTER CreateObject /// returns) -> Withdraw/Place receipt acknowledgement -> /// (FIFO /// drain). /// /// Unlike the local-player path this class never touches the dormant /// activation family: no operation it drives ever has /// DormantLocalActivation set, so the ordinary submission tail is the /// correct — and only — commit route. /// /// PRODUCTION-DRIVEN since the C3c flip: /// fully constructs and wires this class (construction, retirement fan-out, /// bulk session-clear cleanup, ownership fold) exactly like the C3a /// conductor, and the host first-entry drive /// (RuntimeFirstEntryDriveController) calls /// for every remote/projectile initial-create residence on both the /// graphical and headless hosts. /// internal sealed class RuntimeRemoteFirstEntryState { private enum Stage : byte { /// No progress yet, or the mover has not been prepared. AwaitingMoverPreparation, /// Mover command in hand; the body has not been constructed. MoverPrepared, /// /// The canonical body is constructed and bound; the placement has /// not been submitted. /// BodyConstructed, /// /// Submission deferred (DeferredCell): the parked operation's /// Withdraw/Place receipts are drained from the projection FIFO as /// they surface; the wake itself is internal to /// . /// PlacementSubmitted, /// /// The Place projection token is known but not yet acknowledged. /// PlacementCommitted, /// /// The Place projection has been acknowledged. Only /// /// remains; the acknowledgement step is never re-entered. /// Acknowledged, } private sealed class Progress { internal required ulong LeaseId { get; init; } internal Stage Stage { get; set; } = Stage.AwaitingMoverPreparation; internal RuntimeSetPositionCommand PreparedCommand { get; set; } internal PhysicsBody? ConstructedBody { get; set; } internal RuntimeRemoteBodyConstructionReceipt Construction { get; set; } internal RuntimePlacementProjectionToken Projection { get; set; } } private readonly RuntimeInitialCreateResidenceState _residences; private readonly RuntimeInitialCreateContinuationExecutor _executor; private readonly RuntimePhysicsState _physics; private readonly Dictionary _progress = []; private readonly HashSet _executing = []; internal RuntimeRemoteFirstEntryState( RuntimeInitialCreateResidenceState residences, RuntimeInitialCreateContinuationExecutor executor, RuntimePhysicsState physics) { _residences = residences ?? throw new ArgumentNullException(nameof(residences)); _executor = executor ?? throw new ArgumentNullException(nameof(executor)); _physics = physics ?? throw new ArgumentNullException(nameof(physics)); } /// /// Exposes the body-construction receipt for a still-tracked entry — /// the MID-FLIGHT half of the consumption rule documented on /// (C3b review M1): while the sequence is in /// flight this query serves diagnostics/tests; the terminal /// Completed yield delivers the same receipt through Advance's /// own out-param in the call that reaps this entry. Returns false once /// the sequence completed or was abandoned. /// internal bool TryGetConstruction( RuntimeEntityKey key, out RuntimeRemoteBodyConstructionReceipt construction) { if (_progress.TryGetValue(key, out Progress? progress) && progress.ConstructedBody is not null) { construction = progress.Construction; return true; } construction = default; return false; } /// /// One resumable step. Callers pass the SAME arguments on every retry; /// this method re-reads currency from the owning states on every entry /// rather than trusting anything cached beyond its own stage cursor and /// the exact command/token structs the owning methods themselves require. /// /// Construction-receipt consumption rule (C3b review M1), /// following the C3a/F2 precedent of receipts riding the terminal /// Advance out-params: is populated /// ONLY on the /// yield — the same call that delivers the executor /// — because the terminal Advance is a C3c /// host's one natural consumption point and the progress entry (the /// receipt's only retained storage) is reaped in that same call. /// Mid-flight the receipt stays inspectable via /// ; after Completed nothing is /// retained. A lease whose route performs no SetPosition (Parented/ /// PickedUp) constructs no body, so its terminal receipt is default. /// internal RuntimeRemoteFirstEntryStatus Advance( RuntimeEntityRecord record, in RuntimeInitialCreateResidenceToken residenceToken, IPreparedCollisionSource collisionSource, double gameTime, in RuntimeInitialCreateExecutionInputs inputs, out RuntimeInitialCreateExecutionReceipt receipt, out RuntimeRemoteBodyConstructionReceipt construction) { ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(collisionSource); receipt = default; construction = default; if (!residenceToken.IsValid || record.Key is not { } key) return RuntimeRemoteFirstEntryStatus.RejectedToken; // Mirrors the executor's and the C3a conductor's _executing guard: a // synchronous reentrant call for the SAME entity fails closed rather // than interleaving two drains of one stage machine. if (!_executing.Add(key)) return RuntimeRemoteFirstEntryStatus.Contention; try { return AdvanceCore( record, residenceToken, collisionSource, gameTime, inputs, key, out receipt, out construction); } finally { _executing.Remove(key); } } private RuntimeRemoteFirstEntryStatus AdvanceCore( RuntimeEntityRecord record, in RuntimeInitialCreateResidenceToken residenceToken, IPreparedCollisionSource collisionSource, double gameTime, in RuntimeInitialCreateExecutionInputs inputs, RuntimeEntityKey key, out RuntimeInitialCreateExecutionReceipt receipt, out RuntimeRemoteBodyConstructionReceipt construction) { receipt = default; construction = default; _progress.TryGetValue(key, out Progress? progress); // ABA/GUID-reuse guard, exactly like the C3a conductor and the // executor's own Progress reconciliation. if (progress is not null && progress.LeaseId != residenceToken.LeaseId) { Discard(key); progress = null; } if (progress is null || progress.Stage is Stage.AwaitingMoverPreparation) { if (!_residences.TryGetCurrent( record, out RuntimeInitialCreateResidenceLease lease) || lease.Token != residenceToken) { if (progress is null) return RuntimeRemoteFirstEntryStatus.RejectedToken; Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } // This conductor owns REMOTE and PROJECTILE residence leases // only. A local-player lease (InitialLogin — or the structurally // impossible-at-Create LocalAuthoritative) belongs to the C3a // conductor and its publication chain; refusing it here is // "nothing tracked in this domain", not an abandonment. if (lease.Route.OperationKind is not (RuntimeSetPositionOperationKind.RemoteAuthoritative or RuntimeSetPositionOperationKind.ProjectileAuthoritative)) { return RuntimeRemoteFirstEntryStatus.RejectedToken; } if (!lease.Route.PerformsSetPosition) { // Parented/PickedUp residence: no SetPosition operation // exists, so there is nothing to place and — matching // today's production behavior for those routes — no body is // constructed at Create (retail constructs one, but a // parented child's placement is driven by later parent/ // pickup events; body-at-Create for those routes stays with // the first-motion path until a later slice widens this). // Skip straight to Execute, mirroring the C3a conductor. progress ??= new Progress { LeaseId = residenceToken.LeaseId }; progress.Stage = Stage.Acknowledged; _progress[key] = progress; return RunExecute( record, residenceToken, inputs, key, progress, out receipt, out construction); } RuntimeSetPositionMoverPreparationStatus moverStatus = _physics .SetPosition.TryPrepareAuthoredMover( record, lease.Placement, lease.Route.OperationKind, lease.Route.SetPositionFlags, collisionSource, gameTime, out RuntimeSetPositionCommand command, resolveWorldOffsetFromRuntimeFrame: true); if (moverStatus == RuntimeSetPositionMoverPreparationStatus.RetrySetupUnavailable) { return RuntimeRemoteFirstEntryStatus.AwaitingCollisionSource; } if (moverStatus != RuntimeSetPositionMoverPreparationStatus.Prepared) { if (progress is not null) Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } progress ??= new Progress { LeaseId = residenceToken.LeaseId }; progress.PreparedCommand = command; progress.Stage = Stage.MoverPrepared; _progress[key] = progress; } if (progress.Stage is Stage.MoverPrepared) { if (!_residences.TryGetCurrent( record, out RuntimeInitialCreateResidenceLease lease) || lease.Token != residenceToken) { Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } if (record.PhysicsBody is { } existing) { if (ReferenceEquals(progress.ConstructedBody, existing)) { // Idempotent retry: our own construction already bound. progress.Stage = Stage.BodyConstructed; } else { // A body this conductor did not construct appeared while // the residence lease was still active — an out-of-band // owner raced Create-time construction. Never clobber an // existing canonical body (the writer map's invariant); // fail closed and let the lease's own retirement path // converge. Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } } else if (record.PhysicsBodyAcquisitionInProgress || record.RemoteMotionBindingInProgress) { // Another owner's binding callback is mid-flight on this // exact record (only reachable when this Advance itself runs // inside that callback). Typed contention instead of letting // GetOrCreatePhysicsBody throw its structural guard. return RuntimeRemoteFirstEntryStatus.Contention; } else { // Retail order: CreateObject acquires the physics object // from the Setup (makeObject — our mover preparation, stage // 1) and then applies the PhysicsDesc via set_description // (RuntimeRemoteBodyDescription.Construct). Binding runs // through the canonical GetOrCreatePhysicsBody writer: its // post-factory InitializeNewPhysicsBody re-applies // state/velocity/omega from the live snapshot — identical by // value to the frozen-description writes the factory already // made (nothing between admission and this call mutates the // snapshot's physics payload; continuations are queued, not // applied) — and its SynchronizeBodyActiveState aligns the // Active transient bit with the record's object clock. RuntimeRemoteBodyConstructionReceipt built = default; PhysicsBody constructed = _physics.GetOrCreatePhysicsBody( record, r => RuntimeRemoteBodyDescription.Construct( r, lease.InitialCreate.Physics, progress.PreparedCommand, out built)); progress.ConstructedBody = constructed; progress.Construction = built; progress.Stage = Stage.BodyConstructed; } } if (progress.Stage is Stage.BodyConstructed) { if (!_residences.TryGetCurrent( record, out RuntimeInitialCreateResidenceLease lease) || lease.Token != residenceToken) { Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } RuntimeSetPositionOutcome outcome = _physics.SetPosition .SubmitPreparedPlacement(lease.Placement, progress.PreparedCommand); switch (outcome.Status) { case RuntimeSetPositionStatus.CommittedHostAcknowledgementPending: progress.Projection = outcome.Projection; progress.Stage = Stage.PlacementCommitted; break; case RuntimeSetPositionStatus.DeferredCell: // ParkDeferred published a Withdraw receipt and parked // the operation; the projection FIFO drives everything // from here (drained in the PlacementSubmitted stage // below, this same call). progress.Stage = Stage.PlacementSubmitted; break; default: // Rejected (the SetPosition transaction failed — retail's // enter_world failure leaves the object celless; the // resident-cell-cleanup family owns that destiny, not a // silent retry here) or Cancelled (a reentrant observer // displaced the operation). Fail closed. Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } } if (progress.Stage is Stage.PlacementSubmitted) { if (!_residences.TryGetCurrent( record, out RuntimeInitialCreateResidenceLease lease) || lease.Token != residenceToken) { Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } // Drain OUR OWN receipts from the FIFO head as they surface: // Withdraw (the deferred park) must be acknowledged before the // internal collision-generation wake can resubmit; the wake's // commit then publishes the Place this stage is waiting for. while (true) { if (!_physics.SetPosition.TryPeekProjection( out RuntimePlacementProjectionSnapshot head)) { // Nothing pending anywhere — the operation is parked // awaiting its cell/collision-generation wake. The // residence currency check above already proved the // placement operation itself is still tracked. return RuntimeRemoteFirstEntryStatus.AwaitingPlacement; } if (head.Token.Entity != key) { // Another entity's receipt sits ahead of ours in the one // ordered FIFO. return RuntimeRemoteFirstEntryStatus .AwaitingReceiptAcknowledgement; } if (head.Kind is RuntimePlacementProjectionKind.Withdraw) { if (!_physics.SetPosition.AcknowledgeProjection(head.Token)) { Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } // The withdrawal acknowledgement may have re-armed (or — // when the generation was already ready — synchronously // resubmitted) the parked operation; peek again. continue; } if (head.Kind is RuntimePlacementProjectionKind.Place) { progress.Projection = head.Token; progress.Stage = Stage.PlacementCommitted; break; } // Discard (a delete/cancel rewrote our slot) or any other // kind bearing our key: authority moved. Leave the receipt // for the ordinary host drain — mirroring the C3a // conductor's abandonment, which never consumes a Discard // it did not publish — and fail closed. Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } } if (progress.Stage is Stage.PlacementCommitted) { if (!_physics.SetPosition.AcknowledgeProjection(progress.Projection)) { // Same re-validation the C3a conductor performs on a failed // acknowledge: only a genuinely-not-our-turn FIFO head stays // retryable; a retired lease or a rewritten/superseded slot // means authority moved. if (!IsAcknowledgementStillPending( record, residenceToken, progress.Projection)) { Discard(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } return RuntimeRemoteFirstEntryStatus .AwaitingReceiptAcknowledgement; } progress.Stage = Stage.Acknowledged; } return RunExecute( record, residenceToken, inputs, key, progress, out receipt, out construction); } /// /// Re-validates authority after a failed acknowledge — the exact C3a /// mechanism, shared verbatim with the local-player conductor via /// (C3b /// review M2: one body, so the abandonment fix cannot regress /// independently in either conductor). Full rationale on the shared /// helper. /// private bool IsAcknowledgementStillPending( RuntimeEntityRecord record, in RuntimeInitialCreateResidenceToken residenceToken, in RuntimePlacementProjectionToken expected) => RuntimeFirstEntryAcknowledgement.IsStillPending( _residences, _physics.SetPosition, record, residenceToken, expected); private RuntimeRemoteFirstEntryStatus RunExecute( RuntimeEntityRecord record, in RuntimeInitialCreateResidenceToken residenceToken, in RuntimeInitialCreateExecutionInputs inputs, RuntimeEntityKey key, Progress progress, out RuntimeInitialCreateExecutionReceipt receipt, out RuntimeRemoteBodyConstructionReceipt construction) { construction = default; RuntimeInitialCreateExecutionStatus executeStatus = _executor.Execute( record, residenceToken, inputs, out receipt); switch (executeStatus) { case RuntimeInitialCreateExecutionStatus.Completed: // C3b review M1: the terminal Advance is the one natural // consumption point — deliver the construction receipt in // the same call that reaps its only retained storage (this // progress entry). Default (no body constructed) for a // route that performs no SetPosition. construction = progress.Construction; _progress.Remove(key); return RuntimeRemoteFirstEntryStatus.Completed; case RuntimeInitialCreateExecutionStatus.PendingPlacement: return RuntimeRemoteFirstEntryStatus .AwaitingReceiptAcknowledgement; case RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement: return RuntimeRemoteFirstEntryStatus .AwaitingContinuationPlacement; case RuntimeInitialCreateExecutionStatus.RejectedToken: _progress.Remove(key); return RuntimeRemoteFirstEntryStatus.RejectedToken; default: _progress.Remove(key); return RuntimeRemoteFirstEntryStatus.RejectedAuthority; } } /// /// Drops this class's own progress entry for . /// Unlike the C3a conductor there is no publication candidate/activation /// to discard — the constructed body, once bound through the canonical /// writer, belongs to the record and is torn down by ordinary entity /// teardown (retail has no entry-flow rollback; the C3a carried finding /// applies identically here). The residence and executor own their own /// convergence independently. /// private void Discard(RuntimeEntityKey key) => _progress.Remove(key); /// /// Cleanup for one key. binds /// this into 's multicast /// retirement notification (alongside the executor's /// DiscardProgress and the C3a conductor's Forget), so any /// residence retirement path — delete, reset, generation replacement, a /// host discovering staleness — reaps this class's progress /// automatically, using the exact key the residence tracked internally. /// internal void Forget(RuntimeEntityKey key) => Discard(key); /// /// Bulk cleanup wired into the same session-clear sequence /// () as the /// executor's and the C3a conductor's own DiscardAll calls. /// internal void DiscardAll() => _progress.Clear(); internal RuntimeRemoteFirstEntryOwnershipSnapshot CaptureOwnership() => new(_progress.Count); }