using System.Collections.Immutable; using System.Numerics; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Runtime.Physics; namespace AcDream.Runtime.Entities; internal enum RuntimeInitialCreateExecutionStatus : byte { /// Initial tail + entire FIFO revision applied + residence consumed. Completed, /// Initial authored placement not yet acknowledged; retry later. PendingPlacement, /// /// Yielded mid-drain: a Position continuation began an authored placement /// that is not yet acknowledged; retry later. Two distinct flavors share /// this one status (Round 4 R4-14): (1) the ORDINARY flavor, where /// /// returns the exact token to drive prepare/submit/acknowledge on; and /// (2) transient operation-slot CONTENTION (Round 3 B1) - another /// operation already occupies this entity's SetPosition slot at the /// moment the continuation's own merge committed. In flavor (2), /// TryGetPendingContinuationPlacement returns false (no /// token was ever begun) even though the overall status is still /// AwaitingContinuationPlacement; the caller's only correct action is to /// retry Execute again later with no placement work of its own - /// the retry re-attempts ONLY the placement begin against the /// already-committed merge, never re-running the merge or re-publishing. /// AwaitingContinuationPlacement, RejectedToken, /// Residence retired/superseded - abandoned, ledgers converged. RejectedAuthority, } /// /// Executor-time inputs sampled at the retail decision point. These cannot be /// retained at admission time because they describe LIVE state (the local /// player, the current physics simulation) rather than the accepted wire /// packet itself. Round 3 A3: contact is NOT one of these - it comes solely /// from the retained wire packet's own IsGrounded bit (PositionPack /// bit 0x4, server-asserted contact at admission time), never from a live /// body query or a caller-supplied fallback. /// internal readonly record struct RuntimeInitialCreateExecutionInputs( bool UsePositionFromServer, float PlayerDistance); internal enum RuntimeInitialCreateExecutedActionKind : byte { InitialAdoption, TeleportHookRequest, DeferredChildReplay, /// Round 5 R5-1: one queued accepted parent relation replayed in this parent's own initial tail. ParentRelationReplay, PreTailDescriptionAdaptation, ObjDesc, CreateParent, Parent, Pickup, Position, Movement, State, Vector, WeenieDescription, ResidentCellCleanup, } /// /// Round 4 R4-6: outcome of one deferred child's replay registration /// (). /// Replaces the previous bool DeferredChildRegistered field, which /// collapsed a genuine re-defer (the grandparent is ALSO still missing) /// into the same "true" value as an outright successful registration. /// internal enum RuntimeDeferredChildReplayOutcome : byte { /// was non-null. Registered, /// was true - the replayed child itself still has a missing (grand)parent. ReDeferred, /// Neither Canonical nor DeferredForParent - registration was rejected outright, or the registration callback threw (Round 4 R4-1). Rejected, } /// /// Round 4 R4-6: distinct outcome for a Parent/CreateParent relation /// applied at execution time. Replaces the Round 3 B9 dead-letter /// re-Enqueue with retail-faithful discard (Round 4 R4-5). /// internal enum RuntimeParentRelationOutcome : byte { /// The parent was addressable and current (or, at replay, named the exact live incarnation); the attach commit ran. Applied, /// /// Round 5 R5-1: the LIVE parent incarnation is newer than the one this /// relation named - retail-faithful discard, mirroring /// 's own "current parent /// newer than the packet" branch. The already-accepted position- /// timestamp merge already ran (at the relation's original drain, not /// repeated here); no leave-world, no placement forget. /// DiscardedStaleParent, /// /// Round 5 R5-1: the parent is unaddressable, or (standalone Parent /// only) names a parent incarnation that has not yet arrived - queued /// under the parent's guid exactly like retail's QueueBlobForObject /// (pseudo-C 92326), replayed when that guid is created. /// DeferredAwaitingParent, /// /// Round 5 R5-3: the queued relation's child is no longer valid at /// replay time (not current, or a different incarnation than when /// queued) - a contained failure, not an exception; recorded and /// skipped, never resurrected. /// Rejected, } /// /// Retail's exact three-way ResidentCellCleanup disposition (retail-notes.md /// function 1, SmartBox::HandleCreateObject 0x00454c80, lines ~788-801). /// internal enum RuntimeResidentCellCleanupDisposition : byte { /// /// objcell_id != 0 && cell != 0: already resident - /// un-mark (RemoveObjectToBeDestroyed). /// ResidentUnmarked, /// /// objcell_id != 0 && cell == 0 while an existing /// lost-cell/deferred SetPosition operation already owns this exact /// entity: the destruction mark belongs to that existing lifetime, not /// to this tail action. /// DeferredUnderLostCellOwnership, /// /// No cell claimed at all (objcell_id == 0). Retail's own third /// case (HandleCreateObject, retail-notes.md function 1, lines /// ~93942-93943) additionally requires NO weenie description before /// marking for destruction. That second half is structurally /// UNREACHABLE through this exact envelope path: every /// SameIncarnationCreate continuation this codebase constructs /// carries a WeenieDescription action immediately before /// ResidentCellCleanup, never optionally /// (, /// RuntimeInitialCreateResidenceState.cs:277-284, enforces /// Actions[^2].Kind is WeenieDescription for every admitted /// envelope). This value records the conservative claimed-but-celless /// fact for that case without asserting it matches retail's documented /// no-weenie destruction mark, and without building a second destruction /// mechanism ahead of the object-table wiring that would let the /// executor distinguish the two. /// CelllessNoWeenieMarkUnreachable, } /// /// One immutable trace entry. is the owning /// continuation's FIFO sequence (0 for initial-tail-only facts that precede /// the FIFO entirely). is the same-incarnation envelope /// action index, or -1 outside an envelope. /// and are only meaningful for Position/hook-request /// entries; only for /// . /// internal readonly record struct RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind Kind, ulong Sequence, int Stage, RuntimeAuthoritativePositionDisposition? PositionDisposition, RuntimeTeleportHookPhase HookPhase, RuntimeDeferredChildReplayOutcome? DeferredChildOutcome = null, RuntimeResidentCellCleanupDisposition? ResidentCellCleanupDisposition = null, RuntimePositionConstrainPhase ConstrainPhase = RuntimePositionConstrainPhase.None, bool StopInterpolating = false, bool ZeroVelocity = false, bool PreserveHeading = false, bool SendPositionImmediately = false, bool UnparentBeforeRouting = false, RuntimeParentRelationOutcome? ParentRelationOutcome = null); /// /// Host-independent immutable execution result. Hosts/tests consume this; the /// executor never calls presentation. /// internal readonly record struct RuntimeInitialCreateExecutionReceipt( RuntimeEntityKey Entity, uint FullCellId, RuntimeTeleportHookPhase TeleportHookPhase, ImmutableArray Trace, int ReplayedDeferredChildCount); /// /// C3-1: public projection of . A /// separate public enum (rather than widening the internal one's /// accessibility) keeps the classifier/executor's internal vocabulary free to /// evolve without becoming a host-facing contract; values map 1:1 today. /// public enum RuntimeInitialCreateTeleportHookPhase : byte { None, BeforePositionOperation, AfterPositionOperation, AfterEnterWorld, } /// C3-1: public projection of . public enum RuntimeInitialCreatePositionDisposition : byte { RejectedAuthority, RejectedData, AwaitFreshPosition, NoPositionOperation, Interpolate, SetPosition, SetPositionSimple, } /// C3-1: public projection of . public enum RuntimeInitialCreatePositionConstrainPhase : byte { None, BeforePositionOperation, AfterPositionOperation, } /// /// C3-1: one Position continuation's route facts from the executor's trace, /// projected to a public shape so a host can bind constrain/interpolation /// presentation (retail's ConstrainTo placement, stop-interpolate, /// zero-velocity, preserve-heading, send-position-immediately) without /// reaching into internal Runtime route-classifier types. /// Review addendum (2026-08-02): the route's UnparentBeforeRouting /// ("unset_parent") and ApplyPlacementFrameBeforeRouting /// ("SetPlacementFrame") facts are deliberately NOT projected here - the /// executor's own merge (ApplyAcceptedPositionSnapshot's /// clearParent/installPlacementFrame parameters) already /// applies both directly to the canonical snapshot, synchronously, before /// the trace entry carrying this fact is even built. A host reading this /// record must NOT re-apply either one - the facts this type DOES carry /// (, , /// , , /// , ) are /// exactly the bindings still DEFERRED to the host at presentation time; /// everything already-applied is intentionally excluded. /// public readonly record struct RuntimeInitialCreatePositionRouteFact( ulong Sequence, RuntimeInitialCreatePositionDisposition Disposition, RuntimeInitialCreateTeleportHookPhase HookPhase, RuntimeInitialCreatePositionConstrainPhase ConstrainPhase, bool StopInterpolating, bool ZeroVelocity, bool PreserveHeading, bool SendPositionImmediately); /// /// C3-1: the public host consumption shape for one executor drain's /// completion, reached via /// /// using the correlated /// receipt's own Entity/Sequence identity. Built exactly once, at completion /// time, and cached alongside the internal receipt it projects (see /// ) - /// a host retrying TryGetInitialCreateCompletion across multiple polls /// never triggers a second allocation. /// Review addendum (2026-08-02): 's ARRAY /// ORDER is the authoritative ordering, not /// alone - Position facts drained from the SAME same-incarnation envelope /// ( distinguishes /// them internally, a field this projection does not carry) share one /// continuation Sequence, so two entries can legitimately have equal /// Sequence values. /// walks the executor's trace strictly in construction order (itself the /// exact FIFO drain order) and appends without reordering or deduplicating, /// so array index - never a sort or group-by on Sequence - is the /// only reliable way to recover drain order from this array. /// public readonly record struct RuntimeInitialCreatePlacementCompletion( RuntimeEntityKey Entity, uint FullCellId, RuntimeInitialCreateTeleportHookPhase TeleportHookPhase, ImmutableArray PositionRouteFacts, int ReplayedDeferredChildCount); /// /// Applies one entity's completed initial-Create residence: adopts the /// initial placement exactly once, emits the AfterEnterWorld teleport-hook /// request, replays raw missing-parent child Creates in FIFO order, drains /// every retained continuation strictly by sequence (classifying Position /// continuations at execution time against LIVE inputs), and releases the /// residence once the drained prefix matches the lease's current length. /// Execute is synchronous and retry-idempotent: a caller re-invokes it /// after or /// /// once the placement token in the returned trace has been prepared, /// submitted, and acknowledged by whatever drives /// (a test harness today; a host at /// cutover). This type never references App/UI/Silk.NET/OpenGL/OpenAL/ /// Headless and is reached only by tests in this slice - no production /// caller exists yet. /// internal sealed class RuntimeInitialCreateContinuationExecutor { private enum InitialTailPhase : byte { NotStarted, Adopted, HookRecorded, DeferredReplayed, /// Round 5 R5-1: the accepted-relation queue for this guid has been drained. RelationsReplayed, } private readonly record struct PendingPublish( RuntimeEntityChange Change, Func Matches, RuntimePlacementCancellationReceipt Cancellation); private sealed class Progress { internal required ulong LeaseId { get; init; } internal ulong AppliedThroughSequence { get; set; } internal InitialTailPhase TailPhase { get; set; } internal int EnvelopeStageIndex { get; set; } = -1; internal RuntimeEntityPlacementToken PendingContinuationPlacement { get; set; } internal ulong PendingContinuationSequence { get; set; } internal RuntimeAuthoritativePositionRoute PendingContinuationRoute { get; set; } /// /// Round 3 B1: true once a Position action's merge+publish has /// committed but TryBeginExclusiveAuthoredPlacement failed on /// transient operation-slot contention (another operation currently /// owns this entity's SetPosition slot) rather than genuine /// staleness. While true, a re-entry into ApplyPositionAction /// for the SAME continuation/stage skips the merge/publish entirely /// and retries only the placement begin - closing the "duplicate /// publish on every retry" hole a naive full re-apply would open. /// internal bool PositionMergeCommittedForRetry { get; set; } internal ulong PositionMergeCommittedVersion { get; set; } internal int ReplayedDeferredChildCount { get; set; } internal List EnvelopeBuffer { get; } = []; internal ImmutableArray.Builder Trace { get; } = ImmutableArray.CreateBuilder(); } private readonly RuntimeEntityDirectory _entities; private readonly RuntimeInitialCreateResidenceState _residences; private readonly RuntimePhysicsState _physics; private readonly RuntimeEntityObjectEventStream _events; private readonly Func _registerDeferredChild; /// /// Round 3 B12: mirrors /// for the /// residence path's WeenieDescription tail action. The executor holds no /// direct reference to RuntimeEntityObjectLifetime (it is /// constructed BY that owner) or its ClientObjectTable, so the /// lifetime binds this delegate at construction the same way it binds /// . /// private readonly Func _applyAcceptedSpawn; private readonly Dictionary _progress = []; private readonly HashSet _executing = []; /// /// C0-1: correlates a published /// /// receipt back to the full execution receipt/trace, keyed by the SAME /// public Entity/Sequence identity every other Kind uses (the receipt's /// own Token.Entity/Token.Sequence). Overwritten (never /// accumulated) per entity key - an entity cannot have two drains /// completing concurrently ('s own /// reentrancy guard), so only the most recent completion for a key is /// ever meaningful; the exact-sequence check in /// rejects a stale lookup against a /// superseded completion under a reused key. C3-1: the tuple's third /// slot is the SAME receipt already projected once to the public /// shape (see /// ) - stored here, not recomputed per /// read, so a host polling TryGetInitialCreateCompletion across /// retries never allocates a second time for the same completion. /// private readonly Dictionary _completionReceipts = []; private Func? _generation; private Func? _usePositionFromServer; private Func? _localPlayerPosition; private bool _liveInputsBound; internal RuntimeInitialCreateContinuationExecutor( RuntimeEntityDirectory entities, RuntimeInitialCreateResidenceState residences, RuntimePhysicsState physics, RuntimeEntityObjectEventStream events, Func registerDeferredChild, Func applyAcceptedSpawn) { _entities = entities ?? throw new ArgumentNullException(nameof(entities)); _residences = residences ?? throw new ArgumentNullException(nameof(residences)); _physics = physics ?? throw new ArgumentNullException(nameof(physics)); _events = events ?? throw new ArgumentNullException(nameof(events)); _registerDeferredChild = registerDeferredChild ?? throw new ArgumentNullException(nameof(registerDeferredChild)); _applyAcceptedSpawn = applyAcceptedSpawn ?? throw new ArgumentNullException(nameof(applyAcceptedSpawn)); } internal void BindGeneration(Func generation) { ArgumentNullException.ThrowIfNull(generation); if (_generation is not null) { throw new InvalidOperationException( "The initial-create continuation executor's generation source is already bound."); } _generation = generation; } /// /// C0-2: binds Runtime's own live-input sources so no host ever computes /// / /// /// itself. Optional/nullable exactly like is /// NOT (that one throws when unbound) - here an unbound source is a /// legitimate, permanent state for bare-lifetime tests, which keep /// constructing the executor without a and /// keep driving with an explicit caller-supplied /// override (see /// ). binds the real /// owners - RuntimeCharacterState.UsePositionFromServer and the /// live RuntimeLocalPlayerMovementState.Controller position - /// once both exist (they are constructed AFTER /// /this executor, so this bind /// cannot happen at the executor's own constructor time the way /// does; it happens alongside /// BindEventContext in GameRuntime's construction /// sequence). Throws if called twice, matching every other Bind* seam on /// this class/its siblings (, /// RuntimeEntityObjectEventStream.BindContext"/>, /// RuntimePlacementProjectionChannel.BindGeneration). /// F3: itself returns /// Vector3?, not Vector3 - a BOUND source with no live /// controller yet (the login-window drain, before /// RuntimeLocalPlayerMovementState.Controller exists) must yield /// null, not a fabricated Vector3.Zero. /// falls back to the caller-supplied struct's PlayerDistance whenever /// this source is unbound OR returns null - the SAME fallback rule /// either way, never a synthetic origin-point distance that could /// misclassify a remote entity as implausibly far (>96 m) during that /// window. /// internal void BindLiveInputs( Func usePositionFromServer, Func localPlayerPosition) { ArgumentNullException.ThrowIfNull(usePositionFromServer); ArgumentNullException.ThrowIfNull(localPlayerPosition); if (_liveInputsBound) { throw new InvalidOperationException( "The initial-create continuation executor's live-input sources are already bound."); } _usePositionFromServer = usePositionFromServer; _localPlayerPosition = localPlayerPosition; _liveInputsBound = true; } /// /// C0-2: resolves the EFFECTIVE inputs for one /// call. A bound source always wins; the caller-supplied /// struct is the test-override shape (its own /// doc comment still describes production usage now that this method /// exists) and is used verbatim only for whichever field has no bound /// source - a bare-lifetime test that never calls /// gets EXACTLY the caller-supplied values, /// preserving every existing test's behavior unchanged. /// uses /// the SAME world-space basis as today's legacy remote path /// (LiveEntityNetworkUpdateController.cs's /// MaxPhysicsDistance/dist computation, cutover-routes.md /// route 4: Vector3.Distance(worldPos, localPlayerPos) where /// localPlayerPos is the live physics-CONTROLLER position, never a /// record snapshot) - here, Vector3.Distance between THIS /// entity's own currently-accepted position (the exact field /// BeginAcceptedPlacementCore/CanonicalSetupTableId already /// trust: Snapshot.Physics?.Position ?? Snapshot.Position) and the /// bound local-player controller position. Computed ONCE per /// call, matching the one-shot-per-call granularity /// already had before this slice (retail /// recomputes player_distance per wire packet; refining this /// executor to per-continuation freshness is out of C0-2's scope). /// private RuntimeInitialCreateExecutionInputs ResolveInputs( RuntimeEntityRecord canonical, in RuntimeInitialCreateExecutionInputs inputs) { bool usePositionFromServer = _usePositionFromServer is { } source ? source() : inputs.UsePositionFromServer; float playerDistance = inputs.PlayerDistance; // F3: an unbound source AND a bound-but-null live position (no // controller yet) both fall back to the caller-supplied struct // identically - never fabricate Vector3.Zero as a stand-in. if (_localPlayerPosition?.Invoke() is { } localPlayerPosition && (canonical.Snapshot.Physics?.Position ?? canonical.Snapshot.Position) is { } accepted) { var target = new Vector3( accepted.PositionX, accepted.PositionY, accepted.PositionZ); playerDistance = Vector3.Distance(target, localPlayerPosition); } return new RuntimeInitialCreateExecutionInputs( usePositionFromServer, playerDistance); } /// /// C0-1: reaches the full execution receipt/trace correlated with an /// observed /// receipt, purely via that receipt's own public /// Token.Entity/Token.Sequence identity - the same identity /// every other placement Kind is acknowledged by. Returns false for a /// superseded/stale sequence under a reused entity key. /// internal bool TryGetCompletionReceipt( in RuntimePlacementProjectionToken token, out RuntimeInitialCreateExecutionReceipt receipt) { if (_completionReceipts.TryGetValue( token.Entity, out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt, RuntimeInitialCreatePlacementCompletion Public) entry) && entry.Sequence == token.Sequence) { receipt = entry.Receipt; return true; } receipt = default; return false; } /// /// C3-1: the public host consumption surface for /// - same exact-sequence /// correlation rule, but returns the cached public projection instead of /// the internal receipt/trace. Reached via /// . /// internal bool TryGetCompletion( in RuntimePlacementProjectionToken token, out RuntimeInitialCreatePlacementCompletion completion) { if (_completionReceipts.TryGetValue( token.Entity, out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt, RuntimeInitialCreatePlacementCompletion Public) entry) && entry.Sequence == token.Sequence) { completion = entry.Public; return true; } completion = default; return false; } /// /// Review fix (2026-08-02, architecture pass): every arm is now listed /// explicitly and the catch-all throws instead of silently folding an /// unmapped future internal value into None. A value this method /// cannot map must never reach a host disguised as "nothing to bind" - /// that would silently drop presentation behavior (e.g. a real /// teleport-hook phase host code never runs). See /// 's /// reflection-based completeness test, which walks every declared /// value through this exact /// method and fails if a new internal value is ever added without a /// matching arm here. /// private static RuntimeInitialCreateTeleportHookPhase MapHookPhase( RuntimeTeleportHookPhase phase) => phase switch { RuntimeTeleportHookPhase.None => RuntimeInitialCreateTeleportHookPhase.None, RuntimeTeleportHookPhase.BeforePositionOperation => RuntimeInitialCreateTeleportHookPhase.BeforePositionOperation, RuntimeTeleportHookPhase.AfterPositionOperation => RuntimeInitialCreateTeleportHookPhase.AfterPositionOperation, RuntimeTeleportHookPhase.AfterEnterWorld => RuntimeInitialCreateTeleportHookPhase.AfterEnterWorld, _ => throw new ArgumentOutOfRangeException( nameof(phase), phase, $"Unmapped {nameof(RuntimeTeleportHookPhase)} value - add an explicit arm to {nameof(MapHookPhase)} and to the public {nameof(RuntimeInitialCreateTeleportHookPhase)} projection."), }; /// /// Review fix (2026-08-02): see 's remarks - /// same explicit-arms-plus-throwing-catch-all discipline. /// private static RuntimeInitialCreatePositionDisposition MapDisposition( RuntimeAuthoritativePositionDisposition disposition) => disposition switch { RuntimeAuthoritativePositionDisposition.RejectedAuthority => RuntimeInitialCreatePositionDisposition.RejectedAuthority, RuntimeAuthoritativePositionDisposition.RejectedData => RuntimeInitialCreatePositionDisposition.RejectedData, RuntimeAuthoritativePositionDisposition.AwaitFreshPosition => RuntimeInitialCreatePositionDisposition.AwaitFreshPosition, RuntimeAuthoritativePositionDisposition.NoPositionOperation => RuntimeInitialCreatePositionDisposition.NoPositionOperation, RuntimeAuthoritativePositionDisposition.Interpolate => RuntimeInitialCreatePositionDisposition.Interpolate, RuntimeAuthoritativePositionDisposition.SetPosition => RuntimeInitialCreatePositionDisposition.SetPosition, RuntimeAuthoritativePositionDisposition.SetPositionSimple => RuntimeInitialCreatePositionDisposition.SetPositionSimple, _ => throw new ArgumentOutOfRangeException( nameof(disposition), disposition, $"Unmapped {nameof(RuntimeAuthoritativePositionDisposition)} value - add an explicit arm to {nameof(MapDisposition)} and to the public {nameof(RuntimeInitialCreatePositionDisposition)} projection."), }; /// /// Review fix (2026-08-02): see 's remarks - /// same explicit-arms-plus-throwing-catch-all discipline. /// private static RuntimeInitialCreatePositionConstrainPhase MapConstrainPhase( RuntimePositionConstrainPhase phase) => phase switch { RuntimePositionConstrainPhase.None => RuntimeInitialCreatePositionConstrainPhase.None, RuntimePositionConstrainPhase.BeforePositionOperation => RuntimeInitialCreatePositionConstrainPhase.BeforePositionOperation, RuntimePositionConstrainPhase.AfterPositionOperation => RuntimeInitialCreatePositionConstrainPhase.AfterPositionOperation, _ => throw new ArgumentOutOfRangeException( nameof(phase), phase, $"Unmapped {nameof(RuntimePositionConstrainPhase)} value - add an explicit arm to {nameof(MapConstrainPhase)} and to the public {nameof(RuntimeInitialCreatePositionConstrainPhase)} projection."), }; /// /// C3-1: projects an internal /// to the public /// shape exactly once, at completion time (see the /// _completionReceipts assignment in ). /// Only Position-kind trace entries carry route facts meaningful for /// constrain/interpolation binding; every other action kind /// (InitialAdoption, TeleportHookRequest, replay, envelope stages, ...) /// is intentionally excluded from - /// widening this to every trace entry would require making the whole /// internal action-kind vocabulary public, which the pinned contract /// explicitly prefers to avoid. /// private static RuntimeInitialCreatePlacementCompletion ProjectCompletion( in RuntimeInitialCreateExecutionReceipt receipt) { ImmutableArray trace = receipt.Trace; int positionCount = 0; for (int i = 0; i < trace.Length; i++) { if (trace[i].Kind == RuntimeInitialCreateExecutedActionKind.Position) positionCount++; } ImmutableArray positionFacts; if (positionCount == 0) { positionFacts = ImmutableArray.Empty; } else { var builder = ImmutableArray.CreateBuilder( positionCount); for (int i = 0; i < trace.Length; i++) { RuntimeInitialCreateExecutedAction action = trace[i]; if (action.Kind != RuntimeInitialCreateExecutedActionKind.Position) continue; // Review fix (2026-08-02): PositionDisposition is nullable // on RuntimeInitialCreateExecutedAction because it is only // meaningful for Position/hook-request entries in general - // but BuildPositionTrace is the SOLE constructor of // Kind.Position entries (grep-confirmed, 5 call sites, all // through BuildPositionTrace) and it always passes // route.Disposition, a non-nullable enum, into this slot. // Null is therefore NOT a legitimate state for a Position- // kind entry specifically - a silent `?? NoPositionOperation` // fallback here would have hidden a real bug (a future // Position-trace producer that forgot to set it) behind a // plausible-looking default. Fail loudly instead. if (action.PositionDisposition is not { } disposition) { throw new InvalidOperationException( "A Position-kind executor trace entry must always " + "carry a non-null PositionDisposition - " + "BuildPositionTrace (the sole producer of Kind.Position " + "entries) always supplies route.Disposition."); } builder.Add(new RuntimeInitialCreatePositionRouteFact( action.Sequence, MapDisposition(disposition), MapHookPhase(action.HookPhase), MapConstrainPhase(action.ConstrainPhase), action.StopInterpolating, action.ZeroVelocity, action.PreserveHeading, action.SendPositionImmediately)); } positionFacts = builder.MoveToImmutable(); } return new RuntimeInitialCreatePlacementCompletion( receipt.Entity, receipt.FullCellId, MapHookPhase(receipt.TeleportHookPhase), positionFacts, receipt.ReplayedDeferredChildCount); } /// /// F2: reaps exactly one completion-receipt correlation entry, bound as /// 's /// notification callback - fired the moment a host acknowledges the /// Kind ExecutorCompleted receipt this entry correlates, never before. /// The exact-sequence check rejects removing a NEWER completion's entry /// under a reused key (mirrors 's /// own currency check). /// internal void ForgetCompletionReceipt(RuntimeEntityKey key, ulong sequence) { if (_completionReceipts.TryGetValue( key, out (ulong Sequence, RuntimeInitialCreateExecutionReceipt Receipt, RuntimeInitialCreatePlacementCompletion Public) entry) && entry.Sequence == sequence) { _completionReceipts.Remove(key); } } /// /// F2: folded into / /// IsConverged - an unacknowledged completion receipt is /// outstanding host debt, mirroring /// 's /// existing "must be zero to converge" shape for the SAME underlying /// receipt stream. /// internal int PendingCompletionReceiptCount => _completionReceipts.Count; internal int ProgressCount => _progress.Count; /// /// Round 5 R5-3: mirrors the /// DispatchFailureCount/LastDispatchFailure precedent for the deferred- /// replay containment introduced by Round 4 R4-1 and extended by this /// round's deferred-relation replay. A contained catch never silently /// swallows - it increments this counter and records the exception, /// then keeps draining the remaining entries. /// internal long ReplayFailureCount { get; private set; } internal Exception? LastReplayFailure { get; private set; } private void RecordReplayFailure(Exception error) { ReplayFailureCount++; LastReplayFailure = error; } /// /// Exposes the exact placement token a /// /// yield is waiting on, so a caller (a test harness today; a host at /// cutover) can drive 's ordinary /// prepare/submit/acknowledge cycle on it, exactly like it already does /// for the initial lease's own placement token. /// internal bool TryGetPendingContinuationPlacement( RuntimeEntityKey key, out RuntimeEntityPlacementToken placement) { if (_progress.TryGetValue(key, out Progress? progress) && progress.PendingContinuationPlacement.IsValid) { placement = progress.PendingContinuationPlacement; return true; } placement = default; return false; } /// /// Exposes the exact classified route the pending continuation placement /// is running, so a caller can drive /// with the matching /// / /// - the same information /// already exposes for the initial placement. /// internal bool TryGetPendingContinuationRoute( RuntimeEntityKey key, out RuntimeAuthoritativePositionRoute route) { if (_progress.TryGetValue(key, out Progress? progress) && progress.PendingContinuationPlacement.IsValid) { route = progress.PendingContinuationRoute; return true; } route = default; return false; } /// /// Deterministic cleanup hook wired into the SAME choke points that /// forget a residence lease (). /// A retired residence can never leave orphaned executor progress /// behind. Also forgets any in-flight CONTINUATION placement token /// (distinct from the residence's own initial-lease placement, which /// ForgetInitialCreateResidence already forgets separately, and /// distinct from the unconditional Physics.SetPosition.Forget /// every existing ForgetInitialCreateResidence caller already /// runs alongside it - which independently cancels whatever operation /// currently exists for this key, continuation placement included). /// This is defensive-in-depth: DiscardProgress owns cleanup of the /// state IT introduces (PendingContinuationPlacement) rather than /// relying on every current AND future caller pairing it with an /// ordinary Forget of its own. /// internal void DiscardProgress(RuntimeEntityKey key) { // F2: reap this key's completion-receipt correlation entry // unconditionally - DiscardProgress owns cleanup of every piece of // state IT introduces, and this cache is exactly that (see // _completionReceipts's own doc comment). Independent of whether // _progress still tracks this key: a completed drain has ALREADY // removed its own Progress entry before this correlation entry was // ever added (see ExecuteCore's Released case), so this is the // ONLY choke point that reaps it outside of a normal acknowledge. _completionReceipts.Remove(key); if (!_progress.Remove(key, out Progress? progress)) return; if (progress.PendingContinuationPlacement.IsValid) { RuntimePlacementCancellationReceipt cancellation = _physics.SetPosition.ForgetExactPlacement( progress.PendingContinuationPlacement); _physics.SetPosition.PublishCancellation(cancellation); } } /// /// Deterministic bulk cleanup wired into /// 's call site /// (). Also /// forgets every in-flight continuation placement token, defensively - /// Physics.ResetSessionPhysics() runs immediately after this in /// the same session-clear sequence and would otherwise be the only /// thing to reap them. /// internal void DiscardAll() { foreach (Progress progress in _progress.Values) { if (!progress.PendingContinuationPlacement.IsValid) continue; RuntimePlacementCancellationReceipt cancellation = _physics.SetPosition.ForgetExactPlacement( progress.PendingContinuationPlacement); _physics.SetPosition.PublishCancellation(cancellation); } _progress.Clear(); // F2: bulk-reap every completion-receipt correlation entry - a full // session clear must not carry any of this cache across a reset. _completionReceipts.Clear(); } internal RuntimeInitialCreateExecutionStatus Execute( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, in RuntimeInitialCreateExecutionInputs inputs, out RuntimeInitialCreateExecutionReceipt receipt) { ArgumentNullException.ThrowIfNull(canonical); receipt = default; if (!token.IsValid || canonical.Key is not { } key) return RuntimeInitialCreateExecutionStatus.RejectedToken; // A reentrant Execute for the SAME entity while one is already on the // stack (e.g. a synchronous event observer re-entering) fails closed // rather than interleaving two drains of the same FIFO. if (!_executing.Add(key)) return RuntimeInitialCreateExecutionStatus.RejectedAuthority; try { return ExecuteCore(canonical, token, inputs, key, out receipt); } finally { _executing.Remove(key); } } private RuntimeInitialCreateExecutionStatus ExecuteCore( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, in RuntimeInitialCreateExecutionInputs inputs, RuntimeEntityKey key, out RuntimeInitialCreateExecutionReceipt receipt) { receipt = default; // C0-2: resolve ONCE per Execute call - a bound Runtime source always // wins over the caller-supplied test-override struct (see // ResolveInputs's own doc comment for the exact fallback rule). RuntimeInitialCreateExecutionInputs effectiveInputs = ResolveInputs(canonical, inputs); // An existing Progress for a DIFFERENT (older or ABA-reused) lease // id is discarded here, and THIS exact call fails closed - an old // incarnation's progress can never leak into a reused GUID/key. A // retry with no prior progress starts fresh and succeeds normally. if (_progress.TryGetValue(key, out Progress? existing) && existing.LeaseId != token.LeaseId) { DiscardProgress(key); return RuntimeInitialCreateExecutionStatus.RejectedAuthority; } // Round 3 B11: a FRESH Progress for the CURRENT lease id is never // materialized here - only lazily below, once Complete() actually // reports Completed. A PendingPlacement/RejectedToken/RejectedAuthority // outcome on THIS call must leave the ownership ledger (ProgressCount) // untouched when nothing was ever tracked before - it should reflect // drain work actually in flight, not a placeholder for a residence // that has not even resolved yet. Progress? progress = existing; // Resume a placement that a PREVIOUS Execute call began and yielded // on, before doing anything else. This can belong either to a // standalone Position continuation or to a Position stage inside a // SameIncarnationCreate envelope; ApplyContinuation/ApplyEnvelope // both check PendingContinuationPlacement first for exactly this // reason. while (true) { // The ONLY legitimate window where one of the four baseline // fields can move BETWEEN Execute calls without the executor's // own synchronous code running is a pending continuation // placement's host-driven prepare/submit/acknowledge cycle // (RuntimeSetPositionState's own commit machinery advances // FullCellId/PlacementCommitVersion there). Re-sync the // baseline ONLY when that exact window was left open by a // PREVIOUS call - never unconditionally, or every call with // nothing in flight would bless an external race on these // fields before Complete() ever gets a chance to see it. Safe // even when the placement was displaced/cancelled instead of // committed: ResumePendingPlacement below independently // re-derives that outcome from // IsPlacementCurrent/TryPeekAcknowledgedPlacement, not from // these four fields. if (progress is not null && progress.PendingContinuationPlacement.IsValid) { // Round 4 R4-4: only FullCellId/PlacementCommitVersion can // legitimately move in this exact window (RuntimeSetPositionState's // own commit machinery, not the executor) - PositionAuthorityVersion/ // CreateIntegrationVersion moving here would be a genuine // external race Complete() must still catch. _residences.AdvanceExecutorBaseline( canonical, token, RuntimeExecutorBaselineFields.FullCellId | RuntimeExecutorBaselineFields.PlacementCommitVersion); } RuntimeInitialCreateResidenceCompletionStatus completion = _residences.Complete(canonical, token, out RuntimeInitialCreateResidenceReceipt residenceReceipt); switch (completion) { case RuntimeInitialCreateResidenceCompletionStatus.PendingPlacement: // Nothing has been drained yet for this exact lease - // leave _progress exactly as found (untouched if it // never existed). return RuntimeInitialCreateExecutionStatus.PendingPlacement; case RuntimeInitialCreateResidenceCompletionStatus.RejectedToken: DiscardProgress(key); return RuntimeInitialCreateExecutionStatus.RejectedToken; case RuntimeInitialCreateResidenceCompletionStatus.RejectedAuthority: DiscardProgress(key); return RuntimeInitialCreateExecutionStatus.RejectedAuthority; } // Completed: a residence now exists to drain. Materialize // Progress exactly once, lazily, only at this point. if (progress is null) { progress = new Progress { LeaseId = token.LeaseId }; _progress[key] = progress; } if (progress.TailPhase != InitialTailPhase.DeferredReplayed) { RuntimeInitialCreateExecutionStatus tailStatus = RunInitialTail(canonical, token, residenceReceipt, progress); if (tailStatus != RuntimeInitialCreateExecutionStatus.Completed) return Abandon(canonical, key); } while (progress.AppliedThroughSequence < (ulong)residenceReceipt.Continuations.Length) { if (!_entities.IsCurrent(canonical) || canonical.Key != token.Entity) return Abandon(canonical, key); int index = (int)progress.AppliedThroughSequence; RuntimeInitialCreateResidenceContinuation continuation = residenceReceipt.Continuations[index]; if (continuation.InstanceSequence != canonical.Incarnation) return Abandon(canonical, key); RuntimeInitialCreateExecutionStatus applyStatus = ApplyContinuation(canonical, token, key, continuation, effectiveInputs, progress); // Round 3 B2: every apply method below now rebaselines // itself immediately after its own canonical mutation and // BEFORE its own publish (mutate -> rebaseline -> publish), // closing the reentrant-retirement window a synchronous // Publish observer could otherwise see (the baseline would // still show the PRE-mutation values while the observer // reenters residence/executor state). No blanket // re-synchronize belongs here anymore - each apply already // guarantees its own baseline is current before ANY // observer can run. if (applyStatus == RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement) { return applyStatus; } if (applyStatus != RuntimeInitialCreateExecutionStatus.Completed) return applyStatus; progress.AppliedThroughSequence = continuation.Sequence; progress.EnvelopeStageIndex = -1; } RuntimeInitialCreateResidenceExecutorReleaseStatus release = _residences.ConsumeExecuted( canonical, residenceReceipt.Adoption, progress.AppliedThroughSequence); switch (release) { case RuntimeInitialCreateResidenceExecutorReleaseStatus.Released: { var completedReceipt = new RuntimeInitialCreateExecutionReceipt( key, residenceReceipt.FullCellId, residenceReceipt.TeleportHookPhase, progress.Trace.ToImmutable(), progress.ReplayedDeferredChildCount); receipt = completedReceipt; // C3-1: project to the public host-consumption shape // exactly once here, alongside the internal receipt - // never recomputed per host read/retry (see // ProjectCompletion's and _completionReceipts's own doc // comments). RuntimeInitialCreatePlacementCompletion publicCompletion = ProjectCompletion(completedReceipt); _progress.Remove(key); // C0-1: bridge the executor's own completion onto the // SAME ordered placement receipt stream every // Place/Withdraw/Discard uses (canonical is still // current here - nothing between the last continuation // apply and ConsumeExecuted's Released outcome mutates // it). Correlate the full trace via the fresh token's // Entity/Sequence identity - see TryGetCompletionReceipt. // F2: registration happens INSIDE PublishExecutorCompletion's // beforePublish callback (before the synchronous observer // dispatch), not after this call returns - a subscriber // reading the correlation back from inside its own // OnPlacement callback must already find it. receipt is // copied to a local (completedReceipt) because an `out` // parameter cannot be captured by a lambda. _physics.SetPosition.PublishExecutorCompletion( canonical, beforePublish: token => _completionReceipts[key] = (token.Sequence, completedReceipt, publicCompletion)); return RuntimeInitialCreateExecutionStatus.Completed; } case RuntimeInitialCreateResidenceExecutorReleaseStatus.Revised: // A new continuation arrived mid-drain (Enqueue bumps the // completed entry's Adoption.Revision in place). Re-fetch // via Complete and drain only the newly-appended tail - // AppliedThroughSequence already reflects everything this // progress has committed, so the outer while(true) loop's // inner drain loop naturally continues from there. continue; default: return Abandon(canonical, key); } } } /// /// Round 3 B1: the ONE choke point every abandonment path routes /// through. Retiring the RESIDENCE itself (not just this executor's own /// progress) is essential here - a caller that only discarded progress /// and returned RejectedAuthority would leave the residence's own /// completed entry sitting there fully current; the NEXT Execute call /// for the same key would re-fetch it via Complete(), start a FRESH /// Progress at sequence zero, and REPLAY every continuation already /// committed to the canonical snapshot in this attempt. /// 's own /// retirement notification (bound at /// construction) already /// routes back to for a successful Forget; /// the explicit call here is the same idempotent defense-in-depth every /// other DiscardProgress caller uses, covering the case where Forget /// finds no matching residence at all (nothing left to retire, but this /// key's own progress must still go). /// private RuntimeInitialCreateExecutionStatus Abandon( RuntimeEntityRecord canonical, RuntimeEntityKey key) { if (_residences.Forget( canonical, out _, out RuntimePlacementCancellationReceipt cancellation)) { _physics.SetPosition.PublishCancellation(cancellation); } DiscardProgress(key); return RuntimeInitialCreateExecutionStatus.RejectedAuthority; } private RuntimeInitialCreateExecutionStatus RunInitialTail( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, in RuntimeInitialCreateResidenceReceipt residenceReceipt, Progress progress) { if (progress.TailPhase == InitialTailPhase.NotStarted) { // Resolves the runtime-surface.md 3.1 deadlock: BeginAcceptedPlacementCore // (every placement-begin entry point) rejects while HasRetainedCompletion // is true for this key. Consuming the initial placement's // acknowledged completion here - exactly once, guarded by // PlacementAdopted - is what lets a later Position continuation // begin its OWN authored placement for the same key. if (!_residences.AdoptCompletedPlacement(canonical, token)) return RuntimeInitialCreateExecutionStatus.RejectedAuthority; progress.Trace.Add(new RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind.InitialAdoption, 0UL, -1, null, RuntimeTeleportHookPhase.None)); progress.TailPhase = InitialTailPhase.Adopted; } if (progress.TailPhase == InitialTailPhase.Adopted) { // Retail: SmartBox new-object player branch, init_player / // PlayerPositionUpdated (function 1 in retail-notes.md, // SmartBox::HandleCreateObject 0x00454c80). The hook REQUEST is // the Runtime-side fact; a host runs the actual after-enter // teleport suffix at cutover. if (residenceReceipt.TeleportHookPhase == RuntimeTeleportHookPhase.AfterEnterWorld) { progress.Trace.Add(new RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind.TeleportHookRequest, 0UL, -1, null, RuntimeTeleportHookPhase.AfterEnterWorld)); } progress.TailPhase = InitialTailPhase.HookRecorded; } if (progress.TailPhase == InitialTailPhase.HookRecorded) { if (!ReplayDeferredChildren(canonical, progress)) return RuntimeInitialCreateExecutionStatus.RejectedAuthority; progress.TailPhase = InitialTailPhase.DeferredReplayed; } if (progress.TailPhase == InitialTailPhase.DeferredReplayed) { // Round 5 R5-1: drains the accepted-relation queue keyed to // THIS guid AFTER the raw-Create replay above - wire-arrival // order means any relation waiting on this exact guid was // queued no earlier than the raw children were (retail's // ProcessObjectNetBlobs replays both classes of blob from the // SAME per-guid bucket; our queues are split by shape but // drained in the same relative order). if (!ReplayDeferredAcceptedRelations(canonical, progress)) return RuntimeInitialCreateExecutionStatus.RejectedAuthority; progress.TailPhase = InitialTailPhase.RelationsReplayed; } return RuntimeInitialCreateExecutionStatus.Completed; } /// /// Retail: SmartBox::ProcessObjectNetBlobs 0x00454b20, called at the tail /// of HandleCreateObject's new-object path for the object that was just /// created - a parent's own successful Create replays every blob queued /// waiting on ITS guid, synchronously, in the same call stack, in FIFO /// order. Wire-arrival order means this replay runs BEFORE the FIFO /// drain below: children were queued before any continuation targeting /// this entity itself could exist. /// /// Round 3 B7: retail detaches the ENTIRE queued netblob list for one /// parent atomically before dispatching any of it (pseudo-C ~93617) - /// "detach" IS retail's "consume"; there is no separate peek-then-remove /// step. This replaces the previous peek/consume loop, which needed a /// stale-AdmissionId escape hatch for a race that an atomic detach makes /// structurally impossible: a NEW Create arriving for this same parent /// during replay enqueues into a brand-new queue instance, since /// DetachDeferredCreates already removed the old one from the /// dictionary before this loop starts. /// /// Round 4 R4-1: two containment gaps closed. (1) one child's /// registration THROWING no longer escapes Execute or strands /// the remaining siblings - each registration runs inside a try/catch, /// recording a /// outcome and continuing with the next entry on an exception. (2) a /// mid-loop abandonment (this entity no longer current - e.g. a /// reentrant delete/reset fired synchronously from an earlier sibling's /// own registration callback) restores the UNPROCESSED remainder into /// - in original FIFO order, with /// original AdmissionIds - rather than permanently destroying it. /// Retail's own queued blobs live on CObjectMaint (per-GUID), not /// on the object instance being replayed, so they survive the object /// and replay again against a recreated GUID; our GUID-keyed /// persistence already pins this, RestoreDeferredCreates just makes an /// abandoned-mid-replay attempt honor it too. /// private bool ReplayDeferredChildren(RuntimeEntityRecord canonical, Progress progress) { if (!_entities.IsCurrent(canonical)) return false; ImmutableArray detached = _entities.ParentAttachments.DetachDeferredCreates( canonical.ServerGuid, out DeferredReplayWindowToken window); for (int index = 0; index < detached.Length; index++) { if (!_entities.IsCurrent(canonical)) { _entities.ParentAttachments.RestoreDeferredCreates( window, detached.AsSpan()[index..]); return false; } DeferredParentCreate deferred = detached[index]; RuntimeDeferredChildReplayOutcome outcome; try { RuntimeEntityRegistrationResult result = _registerDeferredChild(deferred.Spawn, deferred.IsLocalPlayer); outcome = result.Canonical is not null ? RuntimeDeferredChildReplayOutcome.Registered : result.DeferredForParent ? RuntimeDeferredChildReplayOutcome.ReDeferred : RuntimeDeferredChildReplayOutcome.Rejected; } catch (Exception error) { // Round 4 R4-1: contain the exception here - one bad child // must not strand the remaining siblings or escape Execute // as a typed status. Round 5 R5-3: record it on the // observable failure surface rather than swallowing it. RecordReplayFailure(error); outcome = RuntimeDeferredChildReplayOutcome.Rejected; } progress.Trace.Add(new RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind.DeferredChildReplay, 0UL, -1, null, RuntimeTeleportHookPhase.None, outcome)); progress.ReplayedDeferredChildCount++; } // Nothing left to restore on a full pass - releases the window. _entities.ParentAttachments.RestoreDeferredCreates( window, ReadOnlySpan.Empty); return true; } /// /// Round 5 R5-1: drains the accepted-relation queue keyed to this exact /// guid, replaying every relation a standalone Parent continuation or /// envelope CreateParent stage deferred because this parent was /// unaddressable or named a not-yet-arrived incarnation. Mirrors /// 's detach-first / cancellation- /// aware-window / contained-failure shape exactly - see that method's /// remarks for the retail citations and the R4-1/R5-2 rationale, which /// apply identically here. /// private bool ReplayDeferredAcceptedRelations(RuntimeEntityRecord canonical, Progress progress) { if (!_entities.IsCurrent(canonical)) return false; ImmutableArray detached = _entities.ParentAttachments.DetachDeferredAcceptedRelations( canonical.ServerGuid, out DeferredReplayWindowToken window); for (int index = 0; index < detached.Length; index++) { if (!_entities.IsCurrent(canonical)) { _entities.ParentAttachments.RestoreDeferredAcceptedRelations( window, detached.AsSpan()[index..]); return false; } DeferredAcceptedParentRelation entry = detached[index]; RuntimeParentRelationOutcome outcome; try { outcome = ApplyReplayedParentRelation(canonical, entry); } catch (Exception error) { RecordReplayFailure(error); outcome = RuntimeParentRelationOutcome.Rejected; } progress.Trace.Add(new RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind.ParentRelationReplay, 0UL, -1, null, RuntimeTeleportHookPhase.None, null, null, RuntimePositionConstrainPhase.None, false, false, false, false, false, outcome)); if (outcome == RuntimeParentRelationOutcome.DeferredAwaitingParent) { // Relation still names an incarnation that has not arrived // yet - wait for the NEXT one. Re-enqueues into a BRAND NEW // queue instance (the whole bucket was already detached // above), so this same detach loop never re-observes it. _entities.ParentAttachments.EnqueueDeferredAcceptedRelation(entry); } } _entities.ParentAttachments.RestoreDeferredAcceptedRelations( window, ReadOnlySpan.Empty); return true; } /// /// Round 5 R5-1: incarnation dispatch vs THIS parent (), /// mirroring 's own rules - /// equal incarnation (or the envelope flavor, which has none to compare) /// commits the attach; THIS parent newer than the relation discards it /// (stale); the relation newer than THIS parent re-enqueues (wait for /// the next incarnation - handled by the caller). The merge already /// committed at the relation's ORIGINAL drain (its own position- /// timestamp-only stamp); replay commits ONLY the attach tail, never /// re-runs it. /// private RuntimeParentRelationOutcome ApplyReplayedParentRelation( RuntimeEntityRecord parent, in DeferredAcceptedParentRelation entry) { if (!_entities.TryGetActive(entry.ChildGuid, out RuntimeEntityRecord child) || child.Key != entry.ChildKey) { return RuntimeParentRelationOutcome.Rejected; } if (entry.ParentInstanceSequence is { } relationParentInstance && parent.Incarnation != relationParentInstance) { return PhysicsTimestampGate.IsNewer(relationParentInstance, parent.Incarnation) ? RuntimeParentRelationOutcome.DiscardedStaleParent : RuntimeParentRelationOutcome.DeferredAwaitingParent; } // Round 5 R5-3 note: the child's OWN residence token (if its // initial-tail is somehow still open at this exact moment) is not // held here - only the parent's is in scope. AdvanceExecutorBaseline // is deliberately SKIPPED (rebaseline: false) rather than guessed at; // if the child's own residence is still active, its own next // Complete() call will correctly observe this PositionAuthorityVersion // bump as an external race and fail closed - the safe direction - // rather than this call silently blessing a baseline it does not // own. CommitParentAttachment(child, default, rebaseline: false, buffer: null); return RuntimeParentRelationOutcome.Applied; } private RuntimeInitialCreateExecutionStatus ApplyContinuation( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeEntityKey key, in RuntimeInitialCreateResidenceContinuation continuation, in RuntimeInitialCreateExecutionInputs inputs, Progress progress) { if (continuation.Kind == RuntimeInitialCreateContinuationKind.SameIncarnationCreate) { return ApplyEnvelope(canonical, token, key, continuation, inputs, progress); } if (progress.PendingContinuationPlacement.IsValid) { RuntimeInitialCreateExecutionStatus resumeStatus = ResumePendingPlacement(canonical, key, progress, out RuntimeAuthoritativePositionRoute route); if (resumeStatus != RuntimeInitialCreateExecutionStatus.Completed) return resumeStatus; progress.Trace.Add(BuildPositionTrace(continuation.Sequence, -1, route)); return RuntimeInitialCreateExecutionStatus.Completed; } RuntimeInitialCreateTailAction action = continuation.Actions[0]; switch (continuation.Kind) { case RuntimeInitialCreateContinuationKind.ObjDesc: if (!ApplyObjDescAction(canonical, token, action, null)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.ObjDesc, continuation.Sequence)); return RuntimeInitialCreateExecutionStatus.Completed; case RuntimeInitialCreateContinuationKind.Parent: return ApplyParentContinuation(canonical, token, key, continuation, action, progress); case RuntimeInitialCreateContinuationKind.Pickup: if (!ApplyPickupAction(canonical, token, action, null)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Pickup, continuation.Sequence)); return RuntimeInitialCreateExecutionStatus.Completed; case RuntimeInitialCreateContinuationKind.Movement: if (!ApplyMovementAction(canonical, token, action, null)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Movement, continuation.Sequence)); return RuntimeInitialCreateExecutionStatus.Completed; case RuntimeInitialCreateContinuationKind.State: if (!ApplyStateAction(canonical, token, action, null)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.State, continuation.Sequence)); return RuntimeInitialCreateExecutionStatus.Completed; case RuntimeInitialCreateContinuationKind.Vector: if (!ApplyVectorAction(canonical, token, action, null)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Vector, continuation.Sequence)); return RuntimeInitialCreateExecutionStatus.Completed; case RuntimeInitialCreateContinuationKind.Position: return ApplyPositionAction( canonical, token, key, continuation.Sequence, -1, action, inputs, progress, null); default: throw new InvalidOperationException( $"Unsupported initial-Create continuation kind {continuation.Kind}."); } } /// /// Round 3 B9 revalidated the standalone Parent continuation's parent /// incarnation at EXECUTION time. Round 4 R4-5 replaced admission's /// dead-letter re-Enqueue with a DISCARD; Round 5 R5-1 OVERTURNS that /// discard with hard retail evidence: a missing/stale parent QUEUES the /// raw blob under the PARENT's guid (standalone parent handler /// 0x004535D0 -> QueueBlobForObject, pseudo-C 92326; GUID-keyed /// placeholder bucket in CObjectMaint, 271082-271088) and replays /// it via ProcessObjectNetBlobs when that guid is created - retail /// NEVER discards on this path; its only check is pointer addressability /// (92312). The already-accepted position-timestamp merge still runs /// exactly once here (gate/snapshot lockstep preserved); the dispatch /// that follows mirrors 's /// OWN established staleness rules verbatim: unaddressable parent or a /// relation naming a not-yet-arrived incarnation both ENQUEUE (wait); /// only a relation whose named incarnation the LIVE parent has already /// superseded is discarded. /// drains the queue this enqueues into, in the target parent's own /// initial tail, after its raw-Create replay. /// private RuntimeInitialCreateExecutionStatus ApplyParentContinuation( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeEntityKey key, in RuntimeInitialCreateResidenceContinuation continuation, RuntimeInitialCreateTailAction action, Progress progress) { ParentEvent.Parsed parentUpdate = action.Parent!.Value; if (!ApplyParentPositionTimestampOnly(canonical, parentUpdate)) return Abandon(canonical, key); RuntimeParentRelationOutcome outcome; if (!_entities.TryGetActive(parentUpdate.ParentGuid, out RuntimeEntityRecord parent)) { _entities.ParentAttachments.EnqueueDeferredAcceptedRelation( canonical.ServerGuid, key, parentUpdate, null, action.AcceptedTimestamps); outcome = RuntimeParentRelationOutcome.DeferredAwaitingParent; } else if (parent.Incarnation != parentUpdate.ParentInstanceSequence) { if (PhysicsTimestampGate.IsNewer(parentUpdate.ParentInstanceSequence, parent.Incarnation)) { // Live parent is NEWER than the relation's named incarnation // - stale, discard (Resolve's own discard branch). outcome = RuntimeParentRelationOutcome.DiscardedStaleParent; } else { // Relation names a FUTURE incarnation - wait for it. _entities.ParentAttachments.EnqueueDeferredAcceptedRelation( canonical.ServerGuid, key, parentUpdate, null, action.AcceptedTimestamps); outcome = RuntimeParentRelationOutcome.DeferredAwaitingParent; } } else { CommitParentAttachment(canonical, token, rebaseline: true, null); outcome = RuntimeParentRelationOutcome.Applied; } progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Parent, continuation.Sequence, parentRelationOutcome: outcome)); return RuntimeInitialCreateExecutionStatus.Completed; } /// /// The position-timestamp-only stamp that ALWAYS runs, exactly once, /// when a standalone Parent continuation is first drained - regardless /// of whether the dispatch that follows applies, defers, or discards /// the relation. /// is exactly retail's ApplyPositionTimestampOnly. None of the /// four executor-tracked baseline fields move here (Round 4 R4-4), so /// no AdvanceExecutorBaseline call belongs here either. /// private bool ApplyParentPositionTimestampOnly( RuntimeEntityRecord canonical, ParentEvent.Parsed update) { if (!_entities.ApplyAcceptedParentSnapshot( canonical.ServerGuid, update, out WorldSession.EntitySpawn stamped)) { return false; } _entities.RefreshSnapshot(canonical, stamped); return true; } /// /// The envelope's CreateParent stage revalidates parent ADDRESSABILITY /// only - carries no /// ParentInstanceSequence at all (retail-notes.md's /// TryApplyCreateParent remarks: "unlike standalone ParentEvent /// it carries no parent INSTANCE_TS"), so there is no incarnation to /// compare - only whether the parent is addressable at all. Round 5 /// R5-1: an unaddressable parent now QUEUES (same retail-faithful /// deferral as the standalone Parent continuation), not discards - the /// merge already ran once, unconditionally, before this dispatch. /// private (bool Success, RuntimeParentRelationOutcome Outcome) ApplyCreateParentContinuation( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeEntityKey key, RuntimeInitialCreateTailAction action, List? buffer) { CreateParentUpdate createParentUpdate = action.CreateParent!.Value; if (!ApplyCreateParentPositionTimestampOnly(canonical, createParentUpdate)) return (false, default); if (!_entities.TryGetActive(createParentUpdate.ParentGuid, out _)) { _entities.ParentAttachments.EnqueueDeferredAcceptedRelation( canonical.ServerGuid, key, null, createParentUpdate, action.AcceptedTimestamps); return (true, RuntimeParentRelationOutcome.DeferredAwaitingParent); } CommitParentAttachment(canonical, token, rebaseline: true, buffer); return (true, RuntimeParentRelationOutcome.Applied); } /// Instance-seam-only stamp - see 's remarks. private bool ApplyCreateParentPositionTimestampOnly( RuntimeEntityRecord canonical, CreateParentUpdate update) { if (!_entities.ApplyAcceptedCreateParentSnapshot( canonical.ServerGuid, update, out WorldSession.EntitySpawn stamped)) { return false; } _entities.RefreshSnapshot(canonical, stamped); return true; } private RuntimeInitialCreateExecutionStatus ApplyEnvelope( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeEntityKey key, in RuntimeInitialCreateResidenceContinuation continuation, in RuntimeInitialCreateExecutionInputs inputs, Progress progress) { int startStage = progress.EnvelopeStageIndex < 0 ? 0 : progress.EnvelopeStageIndex; if (progress.PendingContinuationPlacement.IsValid) { RuntimeInitialCreateExecutionStatus resumeStatus = ResumePendingPlacement(canonical, key, progress, out RuntimeAuthoritativePositionRoute route); if (resumeStatus != RuntimeInitialCreateExecutionStatus.Completed) return resumeStatus; progress.Trace.Add(BuildPositionTrace(continuation.Sequence, startStage, route)); startStage++; progress.EnvelopeStageIndex = startStage; } for (int i = startStage; i < continuation.Actions.Length; i++) { if (!_entities.IsCurrent(canonical)) return Abandon(canonical, key); RuntimeInitialCreateTailAction action = continuation.Actions[i]; switch (action.Kind) { case RuntimeInitialCreateTailActionKind.PreTailDescriptionAdaptation: // AP-119 compatibility: retail does NOT re-run // set_description for an equal-generation Create tail. // The retained PhysicsSpawnData is a presentation-side // compat artifact only; no canonical mutation here. progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.PreTailDescriptionAdaptation, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.ObjDesc: if (!ApplyObjDescAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.ObjDesc, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.CreateParent: { (bool success, RuntimeParentRelationOutcome outcome) = ApplyCreateParentContinuation(canonical, token, key, action, progress.EnvelopeBuffer); if (!success) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.CreateParent, continuation.Sequence, i, parentRelationOutcome: outcome)); break; } case RuntimeInitialCreateTailActionKind.Pickup: if (!ApplyPickupAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Pickup, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.Position: { progress.EnvelopeStageIndex = i; RuntimeInitialCreateExecutionStatus status = ApplyPositionAction( canonical, token, key, continuation.Sequence, i, action, inputs, progress, progress.EnvelopeBuffer); if (status == RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement) { // A mid-envelope yield publishes NOTHING - the // buffer accumulated so far stays on Progress and is // flushed only once the whole envelope completes. return status; } if (status != RuntimeInitialCreateExecutionStatus.Completed) return status; break; } case RuntimeInitialCreateTailActionKind.Movement: if (!ApplyMovementAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Movement, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.State: if (!ApplyStateAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.State, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.Vector: if (!ApplyVectorAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.Vector, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.WeenieDescription: if (!ApplyWeenieDescriptionAction(canonical, token, action, progress.EnvelopeBuffer)) return Abandon(canonical, key); progress.Trace.Add(Simple( RuntimeInitialCreateExecutedActionKind.WeenieDescription, continuation.Sequence, i)); break; case RuntimeInitialCreateTailActionKind.ResidentCellCleanup: { RuntimeResidentCellCleanupDisposition? cleanupDisposition = ApplyResidentCellCleanup(canonical); if (cleanupDisposition is null) { // Round 3 B1: the fail-closed invariant violation // (claimed+celless+not-deferred) is a typed // abandonment, never a throw escaping Execute. return Abandon(canonical, key); } progress.Trace.Add(new RuntimeInitialCreateExecutedAction( RuntimeInitialCreateExecutedActionKind.ResidentCellCleanup, continuation.Sequence, i, null, RuntimeTeleportHookPhase.None, null, cleanupDisposition)); break; } default: throw new InvalidOperationException( $"Unsupported same-incarnation tail action {action.Kind}."); } // Round 3 B1: persist EnvelopeStageIndex after EVERY committed // stage, not only Position. Without this, a retry after an // unexpected mid-envelope failure (or any future non-Position // yield point) would resume from a stale index and REPLAY // stages already committed to the canonical snapshot - // Position's own yield/resume already tracks this correctly; // this makes every other stage kind do the same. progress.EnvelopeStageIndex = i + 1; } // Retail's tail is one synchronous critical section; no observer // boundary between stages. Publish every buffered per-stage event // consecutively, in stage order, only now that every stage committed. foreach (PendingPublish pending in progress.EnvelopeBuffer) PublishNow(canonical, pending.Change, pending.Matches, pending.Cancellation); progress.EnvelopeBuffer.Clear(); progress.EnvelopeStageIndex = -1; return RuntimeInitialCreateExecutionStatus.Completed; } /// /// Round 3 B4: matches /// 's FULL /// record/projection agreement rather than a subset of it - a /// continuation's own authored placement deserves the same staleness /// rigor as the initial lease's placement. Beyond the projection's own /// reported facts, this also re-checks the LIVE canonical record's /// PositionAuthorityVersion (has something ELSE moved the record since /// this exact placement began?) and FullCellId/PlacementCommitVersion /// (does the projection's committed cell/version still match reality?). /// private RuntimeInitialCreateExecutionStatus ResumePendingPlacement( RuntimeEntityRecord canonical, RuntimeEntityKey key, Progress progress, out RuntimeAuthoritativePositionRoute route) { route = progress.PendingContinuationRoute; RuntimeEntityPlacementToken placementToken = progress.PendingContinuationPlacement; if (_physics.SetPosition.IsPlacementCurrent(placementToken)) return RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement; if (!_physics.SetPosition.TryPeekAcknowledgedPlacement( placementToken, out RuntimePlacementProjectionToken projection) || projection.Entity != placementToken.Entity || projection.SessionLifetimeVersion != placementToken.SessionLifetimeVersion || projection.PositionAuthorityVersion != placementToken.PositionAuthorityVersion || canonical.PositionAuthorityVersion != placementToken.PositionAuthorityVersion || projection.ExactCellId == 0u || projection.ExactCellId != canonical.FullCellId || projection.PlacementCommitVersion != canonical.PlacementCommitVersion) { // Round 4 R4-2: forget -> clear -> Abandon. Neither still in // flight nor acknowledged with matching facts - cancelled or // superseded by a newer authoritative operation. ForgetExactPlacement // removes the retained _acknowledgedPlacementCompletions entry // (via ForgetPlacementCompletionCore) even in this mismatch // case - without it, HasRetainedCompletion for this key would // stay true forever and block EVERY later placement begin (the // runtime-surface.md 3.1 deadlock this executor exists to // resolve). A newer owner now has the entity; abandon this // execution. RuntimePlacementCancellationReceipt forgotten = _physics.SetPosition.ForgetExactPlacement(placementToken); _physics.SetPosition.PublishCancellation(forgotten); progress.PendingContinuationPlacement = default; return Abandon(canonical, key); } if (!_physics.SetPosition.ConsumeAcknowledgedPlacement(placementToken, projection)) { // Round 4 R4-2: same forget -> clear -> Abandon ordering - a // concurrent consumer raced this exact acknowledgement away // between TryPeek and here; still forget defensively so no // stale watch/ack entry survives under this token. RuntimePlacementCancellationReceipt forgotten = _physics.SetPosition.ForgetExactPlacement(placementToken); _physics.SetPosition.PublishCancellation(forgotten); progress.PendingContinuationPlacement = default; return Abandon(canonical, key); } progress.PendingContinuationPlacement = default; progress.PendingContinuationSequence = 0UL; return RuntimeInitialCreateExecutionStatus.Completed; } private RuntimeInitialCreateExecutionStatus ApplyPositionAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeEntityKey key, ulong sequence, int stage, RuntimeInitialCreateTailAction action, in RuntimeInitialCreateExecutionInputs inputs, Progress progress, List? buffer) { if (!_residences.TryGetTransaction(canonical, out RuntimeInitialCreateResidenceLease lease) || canonical.Key != key) { return Abandon(canonical, key); } WorldSession.EntityPositionUpdate update = action.Position!.Value; RuntimePositionEntityKind entityKind = EntityKindOf(lease.Route.OperationKind); bool isLocalPlayer = entityKind is RuntimePositionEntityKind.LocalPlayer; RuntimeAuthoritativePositionRoute route; if (progress.PositionMergeCommittedForRetry) { // Round 3 B1: a PREVIOUS attempt already merged and published // this exact position continuation; TryBeginExclusiveAuthoredPlacement // failed on transient operation-slot contention rather than // staleness, and this re-entry retries ONLY the placement // begin. Re-running the merge here would double-publish. route = progress.PendingContinuationRoute; } else { // Round 3 A3 / B5 and Round 4 R4-13 (contact from the retained // wire packet's own IsGrounded bit only; the data-driven // HasAnimations proxy with its PhysicsSpawnData fallback) now // live in the ONE shared request builder, which C4 route 4a's // remote classification also uses - see // RuntimeAcceptedPositionRouteRequests for why a second // hand-written copy of this construction is not allowed. RuntimeAcceptedPositionRouteRequest request = RuntimeAcceptedPositionRouteRequests.Build( CurrentGeneration(), canonical, key, update, entityKind, action.PositionSource, action.PositionDisposition, action.PreviousTeleportSequence, action.AcceptedTimestamps.Teleport, inputs.PlayerDistance, inputs.UsePositionFromServer); route = RuntimeAuthoritativePositionRouteClassifier.ClassifyAcceptedPosition(request); if (!route.Accepted) { // Round 3 B10: distinguish two retained-action shapes. // When admission ITSELF already rejected (only // FORCE_POSITION_TS could have moved), the ordinary Rejected // merge is the correct stamp-only path. When admission // ACCEPTED (Apply/ForcePosition - POSITION_TS/TELEPORT_TS/ // FORCE_POSITION_TS genuinely advanced) but EXECUTION-time // classification now rejects, the snapshot must still // reflect every channel the gate actually moved, not just // ForcePosition. bool stampedOk = action.PositionDisposition is PositionTimestampDisposition.Rejected ? _entities.ApplyAcceptedPositionSnapshot( canonical.ServerGuid, update, PositionTimestampDisposition.Rejected, action.AcceptedTimestamps, isLocalPlayer, null, null, installPlacementFrame: false, clearParent: false, out WorldSession.EntitySpawn stampedOnly) : _entities.ApplyAcceptedPositionExecutionRejectedSnapshot( canonical.ServerGuid, update.PositionSequence, action.AcceptedTimestamps, out stampedOnly); if (!stampedOk) return Abandon(canonical, key); _entities.RefreshSnapshot(canonical, stampedOnly); progress.Trace.Add(BuildPositionTrace(sequence, stage, route)); return RuntimeInitialCreateExecutionStatus.Completed; } // CANONICAL CELL SEMANTICS (deliberate difference from the // legacy direct-commit InboundPhysicsStateController.TryApplyPosition // caller): refreshPosition stays false. A wire position never // directly makes the record resident; only a Runtime SetPosition // commit (below) or a simulation full-cell commit may change // FullCellId. This matches retail (HandleReceivedPosition never // sets a resident cell) and the classifier's own documented rule // that a target frame with a nonzero cell does not make a // cellless canonical body resident. The snapshot's Position // field itself IS refreshed; only the derived FullCellId write // is withheld. // // Round 3 B6: installPlacementFrame/clearParent come from the // classified route's OWN ApplyPlacementFrameBeforeRouting/ // UnparentBeforeRouting flags, not the legacy path's // unconditional true/true. PhysicsBody? body = canonical.PhysicsBody; bool mergedOk = _entities.ApplyAcceptedPositionSnapshot( canonical.ServerGuid, update, action.PositionDisposition, action.AcceptedTimestamps, isLocalPlayer, body?.Orientation, body?.Velocity, installPlacementFrame: route.ApplyPlacementFrameBeforeRouting, clearParent: route.UnparentBeforeRouting, out WorldSession.EntitySpawn merged); if (!mergedOk) return Abandon(canonical, key); _entities.RefreshSnapshot(canonical, merged, refreshPosition: false); _entities.AdvancePositionAuthority(canonical); _entities.ParentAttachments.EndChildProjection(canonical.ServerGuid); // Round 3 B2: mutate -> rebaseline -> publish. Rebaselining // BEFORE Publish closes the reentrant-retirement window a // synchronous observer could otherwise see (the baseline would // still show pre-mutation values while the observer reenters // residence/executor state). Round 4 R4-4: only // PositionAuthorityVersion moved (AdvancePositionAuthority also // bumps VelocityAuthorityVersion, which is not one of the four // executor-tracked baseline fields). _residences.AdvanceExecutorBaseline( canonical, token, RuntimeExecutorBaselineFields.PositionAuthorityVersion); ulong positionVersion = canonical.PositionAuthorityVersion; ulong spatialVersion = canonical.SpatialAuthorityVersion; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.PositionAuthorityVersion == positionVersion && canonical.SpatialAuthorityVersion == spatialVersion, default, buffer); if (!route.PerformsSetPosition) { // Interpolate / NoPositionOperation / AwaitFreshPosition: // typed trace result only. Binding to the live // interpolation owner is cutover work. progress.Trace.Add(BuildPositionTrace(sequence, stage, route)); return RuntimeInitialCreateExecutionStatus.Completed; } progress.PositionMergeCommittedForRetry = true; progress.PendingContinuationRoute = route; progress.PositionMergeCommittedVersion = canonical.PositionAuthorityVersion; } RuntimeEntityPlacementToken placement = _physics.SetPosition .TryBeginExclusiveAuthoredPlacement( canonical, canonical.PositionAuthorityVersion, route.OperationKind); if (!placement.IsValid) { // Round 3 B1: distinguish genuine staleness (abandon) from // transient operation-slot contention (retry - the SAME merge // stays committed; only the begin attempt repeats). if (!_entities.IsCurrent(canonical) || canonical.PositionAuthorityVersion != progress.PositionMergeCommittedVersion) { progress.PositionMergeCommittedForRetry = false; return Abandon(canonical, key); } return RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement; } if (!_physics.SetPosition.WatchPlacementCompletion(placement)) { _ = _physics.SetPosition.ForgetExactPlacement(placement); progress.PositionMergeCommittedForRetry = false; return Abandon(canonical, key); } progress.PositionMergeCommittedForRetry = false; progress.PendingContinuationPlacement = placement; progress.PendingContinuationSequence = sequence; progress.PendingContinuationRoute = route; return RuntimeInitialCreateExecutionStatus.AwaitingContinuationPlacement; } private bool ApplyObjDescAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { if (!_entities.ApplyAcceptedObjDescSnapshot( canonical.ServerGuid, action.ObjDesc!.Value, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged); _entities.AdvanceObjDescAuthority(canonical); // Round 4 R4-4: ObjDescAuthorityVersion is not one of the four // executor-tracked baseline fields (PositionAuthorityVersion/ // CreateIntegrationVersion/FullCellId/PlacementCommitVersion) - no // AdvanceExecutorBaseline call belongs here at all; calling it // unconditionally would silently bless an external race on those // four fields that this apply never touched. ulong version = canonical.ObjDescAuthorityVersion; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.ObjDescAuthorityVersion == version, default, buffer); return true; } /// /// Round 5 R5-1: the SHARED attach-commit tail for BOTH the standalone /// Parent continuation and the envelope CreateParent stage - the two /// were byte-identical bodies before this round. The merge step that /// precedes them (ApplyAcceptedParentSnapshot/ /// ApplyAcceptedCreateParentSnapshot, factored out into /// / /// ) runs EXACTLY /// once at the relation's original drain and is deliberately /// position-timestamp-only - it never sets ParentGuid/ParentLocation on /// the snapshot. Per the test /// StandaloneParentContinuationAppliesPositionTimestampOnlyMergeWithNoReDefer's /// established, pre-Round-5 precedent (verified against it directly: /// an earlier revision of this method wrongly called /// here and broke /// that test), the actual attach commit is out of scope for /// this residence-continuation drain - it is the App-layer /// EquippedChildRenderController's job, invoked through /// only after /// it validates the parent's render-side PartArray/holding-location can /// actually host the child (see LiveEntityRuntime.CommitStagedParent's /// remarks). This method therefore commits ONLY the residence tail /// (AdvancePositionAuthority/LeaveWorld/Forget/rebaseline/publish) and is /// payload-agnostic, so it serves the live-continuation apply AND /// 's replayed apply /// identically - "apply through the SAME parent-apply body used by a /// live Parent continuation" (round5-fixes.md R5-1) means exactly this /// tail, not a new attach step neither live nor replay ever performed. /// Unlike the legacy CommitPositionChannelUpdate helper, this does NOT /// call ForgetInitialCreateResidence - the executor IS the residence /// owner mid-drain; forgetting it here would cancel our own in-progress /// lease. Residence teardown is exclusively the adoption/release /// machinery's job (RunInitialTail / ConsumeExecuted). /// is false ONLY at replay time, when the /// child's own residence token is not held here - see /// 's remarks for why that is /// safe (fails closed, never silently blesses a baseline it does not /// own). /// private void CommitParentAttachment( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, bool rebaseline, List? buffer) { _entities.AdvancePositionAuthority(canonical); _physics.CollisionReports.LeaveWorld(canonical); RuntimePlacementCancellationReceipt cancellation = _physics.SetPosition.Forget(canonical); // Round 3 B2: mutate -> rebaseline -> publish. Round 4 R4-4: // AdvancePositionAuthority only moves PositionAuthorityVersion of // the four tracked fields. if (rebaseline) { _residences.AdvanceExecutorBaseline( canonical, token, RuntimeExecutorBaselineFields.PositionAuthorityVersion); } ulong positionVersion = canonical.PositionAuthorityVersion; ulong spatialVersion = canonical.SpatialAuthorityVersion; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.PositionAuthorityVersion == positionVersion && canonical.SpatialAuthorityVersion == spatialVersion, cancellation, buffer); } private bool ApplyPickupAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { if (!_entities.ApplyAcceptedPickupSnapshot( canonical.ServerGuid, action.Pickup!.Value, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged); // Retail: the object entered world (this residence's initial tail), // then was picked up - a FIFO entry always executes AFTER the // initial placement committed. This is a leave-world edge, but it // must not tear down the residence mid-drain: only ordinary // SetPosition.Forget runs here, never ForgetInitialCreateResidence. _entities.AdvancePositionAuthority(canonical); // D7 (route 7, architecture review A5): retail order is // unset_parent @0x0045227F THEN leave_world @0x00452286 // (SmartBox::DoPickupEvent) - the same reorder // RuntimeEntityObjectLifetime.TryApplyPickup applies to the live // pickup path, applied here to the DORMANT replay of the same wire // event so both pickup paths are one shape. _entities.ParentAttachments.EndChildProjection(canonical.ServerGuid); _physics.CollisionReports.LeaveWorld(canonical); RuntimePlacementCancellationReceipt cancellation = _physics.SetPosition.Forget(canonical); _entities.SuspendObjectClock(canonical); _entities.SetFullCell(canonical, 0u, 0u); // Round 3 B2: mutate -> rebaseline -> publish. Round 4 R4-4: // AdvancePositionAuthority + SetFullCell(0,0) move // PositionAuthorityVersion and FullCellId, the only two of the // four tracked fields this apply touches. _residences.AdvanceExecutorBaseline( canonical, token, RuntimeExecutorBaselineFields.PositionAuthorityVersion | RuntimeExecutorBaselineFields.FullCellId); ulong positionVersion = canonical.PositionAuthorityVersion; ulong spatialVersion = canonical.SpatialAuthorityVersion; Publish( canonical, RuntimeEntityChange.Withdrawn, () => canonical.PositionAuthorityVersion == positionVersion && canonical.SpatialAuthorityVersion == spatialVersion, cancellation, buffer); return true; } private bool ApplyMovementAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { WorldSession.EntityMotionUpdate update = action.Movement!.Value; // Safe to pass the retained wire's own MovementSequence directly // here (unlike the legacy caller, which must read the live gate - // see ApplyAcceptedMotion's remarks): a Movement continuation is // only ever retained when AppliesMovementPayload || HasTimestampMutation, // which structurally guarantees MOVEMENT_TS itself already advanced // to this exact value at admission time. if (!_entities.ApplyAcceptedMotionSnapshot( canonical.ServerGuid, update.MovementSequence, action.AcceptedTimestamps.ServerControlledMove, update, retainPayload: false, out WorldSession.EntitySpawn stamped)) { return false; } _entities.RefreshSnapshot(canonical, stamped); if (!action.AppliesMovementPayload) { // Timestamp-only entry: stamp landed above; no publish beyond // that, matching legacy's own timestamp-only branch. Round 4 // R4-4: the stamp only moves MovementSequence/ServerControlSequence // (nested Physics.Timestamps), never any of the four // executor-tracked baseline fields - no AdvanceExecutorBaseline // call here. return true; } if (action.RetainMovementPayload) { if (!_entities.ApplyAcceptedMotionSnapshot( canonical.ServerGuid, update.MovementSequence, action.AcceptedTimestamps.ServerControlledMove, update, retainPayload: true, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged); _entities.AdvanceMovementAuthority(canonical); } _entities.AdvanceMovementCommit(canonical); // Round 4 R4-4: MovementAuthorityVersion/MovementCommitVersion are // not among the four executor-tracked baseline fields - no // AdvanceExecutorBaseline call belongs here. ulong movementCommitVersion = canonical.MovementCommitVersion; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.MovementCommitVersion == movementCommitVersion, default, buffer); return true; } /// /// Round 4 R4-11: the BecameHidden branch's currency-failure path /// returns false (routes the caller to the shared /// Abandon/RejectedAuthority), not true as an earlier /// revision of this method did - the legacy equivalent reports failure /// there too, and the record genuinely mutated under us mid-apply. /// Not independently unit-tested with a live reentrancy seam: this /// harness has no constructible way to make /// RuntimeCollisionReportingState.LeaveWorld invoke an observer /// callback for a residence-fresh entity - EndExpiredObjectCollisions /// returns immediately whenever _owners has no established /// collision record for this key (see /// RuntimeCollisionReportingState.cs's own early-return guard), /// which is always true for an entity that has never yet run a real /// collision batch. Building a synthetic seam to force that callback /// would be exactly the kind of workaround this project's CLAUDE.md /// forbids; the fix is verified by direct code review of the /// now-symmetric bool contract instead (every OTHER Apply*Action /// method already returns false, never true, on its own currency /// failure). /// private bool ApplyStateAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { SetState.Parsed update = action.State!.Value; if (!_entities.ApplyAcceptedStateSnapshot( canonical.ServerGuid, update, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged); RetailPhysicsStateTransition preview = RetailPhysicsStateTransitions.Apply( canonical.FinalPhysicsState, (PhysicsStateFlags)update.PhysicsState); ulong priorPhysicsMutation = canonical.PhysicsStateMutationVersion; if (preview.HiddenTransition is RetailHiddenTransition.BecameHidden) { _physics.CollisionReports.LeaveWorld(canonical); if (!_entities.IsCurrent(canonical) || canonical.PhysicsStateMutationVersion != priorPhysicsMutation) { // Round 4 R4-11: the record mutated out from under us mid-apply // (LeaveWorld's own synchronous collision-report callbacks can // reenter and either invalidate currency or bump // PhysicsStateMutationVersion again) - that IS an external // race, not a successfully-applied continuation. Return false // so the caller routes through the shared Abandon // (RejectedAuthority), matching the legacy equivalent's // failure report instead of silently claiming success. return false; } } RetailPhysicsStateTransition transition = _entities.ApplyRawPhysicsState(canonical, update.PhysicsState); if (canonical.Key is { } key) { _physics.Engine.ShadowObjects.UpdatePhysicsState( key.LocalEntityId, (uint)canonical.FinalPhysicsState); } // Round 4 R4-4: StateAuthorityVersion/PhysicsStateMutationVersion // are not among the four executor-tracked baseline fields - no // AdvanceExecutorBaseline call belongs here. ulong stateVersion = canonical.StateAuthorityVersion; ulong physicsMutationVersion = canonical.PhysicsStateMutationVersion; Publish( canonical, transition.HiddenTransition is RetailHiddenTransition.BecameHidden ? RuntimeEntityChange.Hidden : RuntimeEntityChange.Updated, () => canonical.StateAuthorityVersion == stateVersion && canonical.PhysicsStateMutationVersion == physicsMutationVersion, default, buffer); return true; } private bool ApplyVectorAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { if (!_entities.ApplyAcceptedVectorSnapshot( canonical.ServerGuid, action.Vector!.Value, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged); _entities.AdvanceVectorAuthority(canonical); // Round 4 R4-4: VectorAuthorityVersion is not among the four // executor-tracked baseline fields - no AdvanceExecutorBaseline // call belongs here. ulong version = canonical.VectorAuthorityVersion; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.VectorAuthorityVersion == version, default, buffer); return true; } private bool ApplyWeenieDescriptionAction( RuntimeEntityRecord canonical, in RuntimeInitialCreateResidenceToken token, RuntimeInitialCreateTailAction action, List? buffer) { // Round 3 A2: this must NOT be a wholesale RefreshSnapshot of the // raw retained packet - it merges exactly like every other // same-generation Create (MergeUntimestampedCreate via the instance // seam), keeping the retained Position/appearance/physics-timestamp // fields earlier stages already committed to _snapshots. if (!_entities.ApplyAcceptedWeenieDescriptionSnapshot( canonical.ServerGuid, action.WeenieDescription!.Value, out WorldSession.EntitySpawn merged)) { return false; } _entities.RefreshSnapshot(canonical, merged, refreshPosition: false); // RuntimeEntityObjectLifetime.RegisterEntityCore's ExistingGeneration // branch only calls Entities.AdvanceCreateAuthority when // !beginInitialResidence - a residence-pending entity's admission // deliberately skipped it. This deferred WeenieDescription tail // action is where that authority mutation actually lands. _entities.AdvanceCreateAuthority(canonical); ulong createVersion = canonical.CreateIntegrationVersion; // Round 3 B12: RuntimeLiveEntitySessionController.OnSpawned (the // non-residence direct-host Create path) drives // ApplyAcceptedSpawn(canonical, integrationVersion, canonical.Snapshot, // replaceGeneration: NewGeneration) for EVERY accepted Create - the // prior "zero callers" claim for this object-table wiring was false. // This tail action is the residence path's exact counterpart: it // only ever runs for an ExistingGeneration same-incarnation Create // (a residence is admitted only when preview is ExistingGeneration), // so replaceGeneration is always false here. // // Round 4 R4-3: order is AdvanceCreateAuthority -> AdvanceExecutorBaseline // -> _applyAcceptedSpawn -> (false -> typed Abandon) -> buffered // publish. Rebaselining BEFORE the object-table apply (rather than // after, as an earlier revision did) means a reentrant callback // FROM WITHIN _applyAcceptedSpawn's own synchronous // ObjectAdded/ObjectUpdated dispatch already observes a current // baseline. _applyAcceptedSpawn's own result is now actually // OBSERVED rather than discarded: RuntimeEntityObjectLifetime. // ApplyAcceptedSpawn re-checks currency before, during, AND after // its own object-table apply (its callback is synchronous and may // re-enter entity lifetime) - a nested replacement racing in from // that same callback invalidates the exact canonical incarnation // this drain is still executing against, mirroring // RuntimeLiveEntitySessionController.cs:87's own gate on that same // call's result. The remaining tail cannot run against a record a // nested replacement has already superseded. _residences.AdvanceExecutorBaseline( canonical, token, RuntimeExecutorBaselineFields.PositionAuthorityVersion | RuntimeExecutorBaselineFields.CreateIntegrationVersion); if (!_applyAcceptedSpawn(canonical, createVersion, merged, /* replaceGeneration: */ false)) return false; Publish( canonical, RuntimeEntityChange.Updated, () => canonical.CreateIntegrationVersion == createVersion, default, buffer); return true; } /// /// Retail: SmartBox::HandleCreateObject's same-incarnation tail, final /// step (retail-notes.md function 1, 0x00454c80, lines ~788-801) - /// objcell_id != 0 && cell == 0 marks for destruction, /// objcell_id != 0 && cell != 0 un-marks, and no cell /// claimed with no weenie also marks for destruction. Asserts the /// invariant rather than building a new destruction mechanism: any /// claimed-but-celless outcome must already be under lost-cell/deferred /// SetPosition ownership. /// private RuntimeResidentCellCleanupDisposition? ApplyResidentCellCleanup( RuntimeEntityRecord canonical) { uint claimedCell = canonical.Snapshot.Physics?.Position?.LandblockId ?? canonical.Snapshot.Position?.LandblockId ?? 0u; if (claimedCell == 0u) { // No cell claimed. See RuntimeResidentCellCleanupDisposition. // CelllessNoWeenieMarkUnreachable's remarks: retail's matching // no-weenie destruction-mark condition is structurally // unreachable through this exact envelope path. return RuntimeResidentCellCleanupDisposition .CelllessNoWeenieMarkUnreachable; } if (canonical.FullCellId != 0u) return RuntimeResidentCellCleanupDisposition.ResidentUnmarked; if (!_physics.SetPosition.IsDeferred(canonical)) { // Round 3 B1: the fail-closed invariant violation // (claimed+celless+not-deferred) is a typed abandonment - null // signals the caller to Abandon rather than letting an // exception escape Execute. return null; } // Claimed but celless, and an existing lost-cell/deferred // SetPosition operation already owns this exact entity: the // destruction mark belongs to that existing lifetime (retail: // AddObjectToBeDestroyed was already reached via that path), not to // this tail action - assert the invariant, do not invent a second // destruction mechanism. return RuntimeResidentCellCleanupDisposition.DeferredUnderLostCellOwnership; } private void Publish( RuntimeEntityRecord canonical, RuntimeEntityChange change, Func matches, RuntimePlacementCancellationReceipt cancellation, List? buffer) { if (buffer is not null) { // Buffered (same-incarnation envelope) publishes flush only // after EVERY stage has committed. The per-field "matches" // check the IMMEDIATE (standalone-continuation) path uses // exists to catch a reentrant race between one mutation and // its own publish - but a LATER stage in the SAME envelope // legitimately advances the SAME field again as normal, // expected progression (e.g. WeenieDescription's // AdvanceCreateAuthority bumps Position/State/Vector/ObjDesc // authority all at once), which would make an EARLIER stage's // captured matches() go stale by flush time even though // nothing external raced it. IsCurrent (checked unconditionally // by PublishNow below) is the only currency guard a buffered // entry needs: envelope processing dispatches no event until // the flush, so there is no opportunity for reentrancy mid- // envelope except at a Position-stage yield, and THAT window is // independently guarded by ApplyEnvelope's own IsCurrent check // at the top of the resumed loop and by ResumePendingPlacement. buffer.Add(new PendingPublish(change, static () => true, cancellation)); return; } PublishNow(canonical, change, matches, cancellation); } private void PublishNow( RuntimeEntityRecord canonical, RuntimeEntityChange change, Func matches, RuntimePlacementCancellationReceipt cancellation) { _physics.SetPosition.PublishCancellation(cancellation); if (_entities.IsCurrent(canonical) && matches()) _events.PublishEntity(change, canonical); } private static RuntimeInitialCreateExecutedAction BuildPositionTrace( ulong sequence, int stage, in RuntimeAuthoritativePositionRoute route) => new( RuntimeInitialCreateExecutedActionKind.Position, sequence, stage, route.Disposition, route.TeleportHookPhase, null, null, // Round 3 B6: record the route's own flags in the trace. route.ConstrainPhase, route.StopInterpolating, route.ZeroVelocity, route.PreserveHeading, route.SendPositionImmediately, route.UnparentBeforeRouting); private static RuntimeInitialCreateExecutedAction Simple( RuntimeInitialCreateExecutedActionKind kind, ulong sequence, int stage = -1, RuntimeParentRelationOutcome? parentRelationOutcome = null) => new( kind, sequence, stage, null, RuntimeTeleportHookPhase.None, ParentRelationOutcome: parentRelationOutcome); private static RuntimePositionEntityKind EntityKindOf( RuntimeSetPositionOperationKind operationKind) => operationKind switch { RuntimeSetPositionOperationKind.InitialLogin or RuntimeSetPositionOperationKind.LocalAuthoritative => RuntimePositionEntityKind.LocalPlayer, RuntimeSetPositionOperationKind.ProjectileAuthoritative => RuntimePositionEntityKind.Projectile, _ => RuntimePositionEntityKind.Remote, }; private RuntimeGenerationToken CurrentGeneration() => _generation?.Invoke() ?? default; }