using AcDream.App.Streaming; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; using AcDream.Runtime; using AcDream.Runtime.Entities; using AcDream.Runtime.Physics; using System.Numerics; using System.Runtime.ExceptionServices; namespace AcDream.App.World; /// /// Animation state owned by a live object. The concrete App animation runtime /// implements this seam so identity storage does not depend on a render backend. /// public interface ILiveEntityAnimationRuntime { WorldEntity Entity { get; } uint CurrentMotion { get; } } /// Marker for the DAT-driven effect profile added by the effect phase. public interface ILiveEntityEffectProfile { } public enum LiveEntityProjectionKind { World, Attached, } /// /// Selects whether App may publish a newly constructed sidecar immediately or /// must keep it non-spatial until the canonical Runtime SetPosition receipt is /// projected. The latter is the production CreateObject path; the former is a /// temporary compatibility seam for callers not yet cut over in Slice 4B2. /// internal enum LiveEntityMaterializationResidence { LegacyImmediate, AwaitRuntimePlacement, } /// /// Logical-resource seam coordinated by . /// Spatial bucketing is deliberately absent: registering or removing meshes, /// scripts, and effects is a logical-lifetime operation, not a landblock move. /// public interface ILiveEntityResourceLifecycle { void Register(WorldEntity entity); void Unregister(WorldEntity entity); } /// /// Exact-incarnation App component cleanup invoked by the canonical runtime /// after it has retired the active identity. Mesh/script resource lifetime is /// owned separately by . /// internal interface ILiveEntityRuntimeComponentLifecycle { void TearDown(LiveEntityRecord record); } internal sealed class NullLiveEntityRuntimeComponentLifecycle : ILiveEntityRuntimeComponentLifecycle { public static NullLiveEntityRuntimeComponentLifecycle Instance { get; } = new(); private NullLiveEntityRuntimeComponentLifecycle() { } public void TearDown(LiveEntityRecord record) { } } internal sealed class DelegateLiveEntityRuntimeComponentLifecycle( Action tearDown) : ILiveEntityRuntimeComponentLifecycle { private readonly Action _tearDown = tearDown ?? throw new ArgumentNullException(nameof(tearDown)); public void TearDown(LiveEntityRecord record) => _tearDown(record); } /// /// Composition bridge for the runtime-to-hydration construction cycle. It is /// deliberately fail-fast and single-assignment: a live session must not start /// before the exact component owner is bound, and ownership cannot be replaced /// during a session. /// internal sealed class DeferredLiveEntityRuntimeComponentLifecycle : ILiveEntityRuntimeComponentLifecycle { private ILiveEntityRuntimeComponentLifecycle? _inner; public bool IsBound => Volatile.Read(ref _inner) is not null; public void Bind(ILiveEntityRuntimeComponentLifecycle lifecycle) { ArgumentNullException.ThrowIfNull(lifecycle); if (ReferenceEquals(lifecycle, this)) throw new ArgumentException("A lifecycle bridge cannot bind to itself.", nameof(lifecycle)); if (Interlocked.CompareExchange(ref _inner, lifecycle, null) is not null) throw new InvalidOperationException("The live-entity runtime component lifecycle is already bound."); } public IDisposable BindOwned(ILiveEntityRuntimeComponentLifecycle lifecycle) { Bind(lifecycle); return new Binding(this, lifecycle); } private void Unbind(ILiveEntityRuntimeComponentLifecycle expected) { _ = Interlocked.CompareExchange(ref _inner, null, expected); } public void TearDown(LiveEntityRecord record) { ILiveEntityRuntimeComponentLifecycle lifecycle = Volatile.Read(ref _inner) ?? throw new InvalidOperationException( "The live-entity runtime component lifecycle has not been bound."); lifecycle.TearDown(record); } private sealed class Binding : IDisposable { private DeferredLiveEntityRuntimeComponentLifecycle? _owner; private readonly ILiveEntityRuntimeComponentLifecycle _expected; public Binding( DeferredLiveEntityRuntimeComponentLifecycle owner, ILiveEntityRuntimeComponentLifecycle expected) { _owner = owner; _expected = expected; } public void Dispose() => Interlocked.Exchange(ref _owner, null)?.Unbind(_expected); } } /// Delegate adapter used by the App composition root. public sealed class DelegateLiveEntityResourceLifecycle : ILiveEntityResourceLifecycle { private readonly Action _register; private readonly Action _unregister; public DelegateLiveEntityResourceLifecycle( Action register, Action unregister) { _register = register ?? throw new ArgumentNullException(nameof(register)); _unregister = unregister ?? throw new ArgumentNullException(nameof(unregister)); } public void Register(WorldEntity entity) => _register(entity); public void Unregister(WorldEntity entity) => _unregister(entity); } /// /// App projection sidecar for one canonical . /// The canonical record survives loaded/pending landblock movement and /// temporary loss of its render bucket; this sidecar retains only graphical /// components, visibility, hydration, and teardown progress. /// public sealed class LiveEntityRecord { private readonly RuntimeEntityDirectory _directory; internal LiveEntityRecord( RuntimeEntityDirectory directory, RuntimeEntityRecord canonical) { _directory = directory ?? throw new ArgumentNullException(nameof(directory)); Canonical = canonical ?? throw new ArgumentNullException(nameof(canonical)); } internal RuntimeEntityRecord Canonical { get; } internal RuntimeEntityKey? ProjectionKey { get; set; } public uint ServerGuid => Canonical.ServerGuid; public ushort Generation => Canonical.Incarnation; public WorldSession.EntitySpawn Snapshot { get => Canonical.Snapshot; internal set => _directory.RefreshSnapshot(Canonical, value); } public WorldEntity? WorldEntity { get; internal set; } public uint? LocalEntityId => Canonical.LocalEntityId ?? WorldEntity?.Id; public uint FullCellId { get => Canonical.FullCellId; internal set => _directory.SetFullCell( Canonical, value, Canonical.CanonicalLandblockId); } public uint CanonicalLandblockId { get => Canonical.CanonicalLandblockId; internal set => _directory.SetFullCell( Canonical, Canonical.FullCellId, value); } public uint RawPhysicsState => Canonical.RawPhysicsState; public PhysicsStateFlags FinalPhysicsState { get => Canonical.FinalPhysicsState; internal set => _directory.SetFinalPhysicsState(Canonical, value); } /// /// Incarnation-stable retail CPhysicsObj::update_time owner. It /// survives animation and MovementManager attachment/replacement, spatial /// rebucketing, and appearance rehydration, and dies with this record. /// public RetailObjectQuantumClock ObjectClock => Canonical.ObjectClock; /// /// Changes whenever this record leaves or re-enters the ordinary-object /// workset. Unlike , loaded-to-loaded /// rebucketing does not change this token. A scheduler can therefore stop a /// catch-up batch that crossed a leave/enter boundary without rejecting an /// ordinary same-frame cell move. /// internal ulong ObjectClockEpoch => Canonical.ObjectClockEpoch; /// /// True after this incarnation has successfully constructed its retail /// CPartArray. This is an object-lifetime fact, not an animation- /// runtime proxy: static poses and objects without a MotionTable still /// own a PartArray and participate in the 96-unit Active gate. /// public bool HasPartArray { get => Canonical.HasPartArray; internal set => _directory.SetHasPartArray(Canonical, value); } public PhysicsBody? PhysicsBody { get => Canonical.PhysicsBody; } internal bool PhysicsBodyAcquisitionInProgress { get => Canonical.PhysicsBodyAcquisitionInProgress; } public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; } public IRuntimeRemoteMotion? RemoteMotionRuntime { get => Canonical.RemoteMotion; } internal bool RemoteMotionBindingInProgress { get => Canonical.RemoteMotionBindingInProgress; } public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost { get => Canonical.PhysicsHost; } internal bool RequiresRemotePlacementRuntime { get => Canonical.RequiresRemotePlacementRuntime; } public IRuntimeProjectile? ProjectileRuntime => Canonical.Projectile; public ILiveEntityEffectProfile? EffectProfile { get; internal set; } public bool ResourcesRegistered { get; internal set; } internal LiveEntityMaterializationResidence? MaterializationResidence { get; set; } public bool IsSpatiallyProjected { get; internal set; } public bool IsSpatiallyVisible { get; internal set; } internal ulong ProjectionMutationVersion { get; set; } /// /// Monotonic App operation tokens for accepted wire authority. They let a /// handler detect a newer same-incarnation packet delivered re-entrantly /// while an older packet is crossing presentation or spatial callbacks. /// internal ulong PositionAuthorityVersion => Canonical.PositionAuthorityVersion; internal ulong StateAuthorityVersion => Canonical.StateAuthorityVersion; internal ulong VectorAuthorityVersion => Canonical.VectorAuthorityVersion; internal ulong VelocityAuthorityVersion => Canonical.VelocityAuthorityVersion; internal ulong MovementAuthorityVersion => Canonical.MovementAuthorityVersion; internal ulong ObjDescAuthorityVersion => Canonical.ObjDescAuthorityVersion; /// /// Advances for every accepted CreateObject affecting this incarnation, /// including a fresher same-INSTANCE_TS retransmit. App integration uses /// it to distinguish two transactions that intentionally share identity. /// internal ulong CreateIntegrationVersion => Canonical.CreateIntegrationVersion; public bool WorldSpawnPublished { get; internal set; } /// /// True only after the incarnation's projection, collision/animation /// components, effect owner, state presentation, and pending-effect replay /// have crossed the initial ready barrier. A non-null /// alone is not sufficient: failures after /// resource registration retain the same projection for exact retry. /// public bool InitialHydrationCompleted { get; internal set; } /// /// A fresher same-incarnation CreateObject interrupted projection after /// logical ownership had already been established. The retained entity /// must replay the newest canonical appearance/current snapshot/animation /// before ordinary rebucketing can resume. This obligation deliberately /// survives a failed recovery attempt. /// internal bool CreateProjectionSynchronizationPending { get; set; } internal bool ProjectionHydrationInProgress { get; set; } internal ulong ProjectionHydrationCreateVersion { get; set; } internal bool ProjectionHydrationRetryRequested { get; set; } /// /// A renderer mesh transaction can be re-entered by a newer accepted /// ObjDesc. Keep that independent projection obligation on the exact /// incarnation until the newest visual description publishes. /// internal bool AppearanceProjectionSynchronizationPending { get; set; } internal bool AppearanceHydrationInProgress { get; set; } internal ulong AppearanceHydrationVersion { get; set; } internal bool AppearanceHydrationRetryRequested { get; set; } public LiveEntityProjectionKind ProjectionKind { get; internal set; } internal bool RuntimeComponentsTeardownCompleted { get; set; } internal LiveEntityTeardownPlan? RuntimeComponentTeardownPlan { get; set; } internal bool SpatialProjectionTeardownCompleted { get; set; } internal bool TeardownInProgress { get; set; } internal bool DeleteAcceptedForTeardown { get => Canonical.DeleteAcceptedForTeardown; set => _directory.SetDeleteAcceptedForTeardown(Canonical, value); } internal bool TryDequeueStateTransition(out RetailPhysicsStateTransition transition) => _directory.TryDequeueStateTransition(Canonical, out transition); internal void SuspendObjectClock() => _directory.SuspendObjectClock(Canonical); internal void ResetObjectClockForEnterWorld(bool isStatic) => _directory.ResetObjectClockForEnterWorld(Canonical, isStatic); internal RetailPhysicsStateTransition ApplyRawPhysicsState(uint rawState) => _directory.ApplyRawPhysicsState(Canonical, rawState); internal void NoteObjDescProjectionSynchronization() { AppearanceProjectionSynchronizationPending = true; if (AppearanceHydrationInProgress) AppearanceHydrationRetryRequested = true; } /// /// Cell used when a streamed landblock asks live objects to restore their /// spatial projection. Once materialized, the canonical runtime cell wins; /// the retained server snapshot is only a first-materialization source. /// public uint ProjectionCellId => WorldEntity is not null ? FullCellId : Snapshot.Position?.LandblockId ?? FullCellId; internal void RefreshDerivedState(bool refreshPosition = true) => _directory.RefreshSnapshot(Canonical, Snapshot, refreshPosition); } public readonly record struct LiveEntityRegistrationResult( InboundCreateResult Inbound, RuntimeEntityRecord? Canonical, LiveEntityRecord? Projection, bool LogicalRegistrationCreated, bool ReplacedExistingGeneration, Exception? PriorGenerationCleanupFailure = null); /// /// Update-thread App projection/lifecycle host over the canonical /// . Runtime owns server GUIDs, /// incarnations, accepted snapshots, timestamps, tombstones, and local IDs; /// this host owns graphical components and retryable projection teardown. /// is a spatial projection only; moving between /// loaded and pending buckets never replays a create-time renderer, script, /// animation, or effect action. /// /// /// Retail keeps one object per accepted INSTANCE_TS: same-generation /// CreateObject branches mutate it, Pickup leaves world without destruction, /// and Delete/instance replacement end it. See SmartBox CreateObject/event /// pseudocode in 2026-07-13-retail-projectile-vfx-pseudocode.md, /// CPhysicsObj::set_description (0x00514F40), /// CPhysicsObj::change_cell (0x00513390), and /// CPhysicsObj::exit_world (0x00514E60). /// /// public sealed class LiveEntityRuntime : ILiveEntityRadarSource { public const uint FirstLiveEntityId = RuntimeEntityDirectory.FirstLocalEntityId; public const uint LastLiveEntityId = RuntimeEntityDirectory.LastLocalEntityId; private readonly GpuWorldState _spatial; private readonly ILiveEntityResourceLifecycle _resources; private readonly ILiveEntityRuntimeComponentLifecycle _runtimeComponentLifecycle; private readonly RuntimeEntityObjectLifetime _entityObjects; private readonly RuntimeEntityDirectory _directory; private readonly RuntimePhysicsState _physics; private readonly LiveEntityProjectionStore _projections; // Logical components survive retail's 25-second leave-visibility lifetime. // Frame loops must not scan those retained owners. These component-specific // indexes contain only records with a loaded, non-cellless spatial // projection. Hidden and Frozen remain members: their state controls what // each retail update path advances after the O(active) lookup. private readonly Dictionary _spatialAnimations = new(); private readonly List _spatialRootCanonicalScratch = new(); private readonly List _spatialRemoteCanonicalScratch = new(); // Runtime placement receipts are presentation observations of an already // committed canonical SetPosition transaction. GpuWorldState still emits // its ordinary visibility callback while that observation rebuckets or // withdraws a sidecar, so pin the exact incarnation while the callback is // in flight. A depth (rather than a bool) preserves nested/re-entrant // projection mutations without ever turning graphical visibility into a // Runtime physics/workset mutation. private readonly Dictionary _presentationOnlySpatialMutationDepth = new(); private bool _isClearing; private bool _sessionClearPendingFinalization; private bool _isRegisteringResources; private int _logicalTeardownDepth; private uint _rebucketingGuid; public LiveEntityRuntime( GpuWorldState spatial, ILiveEntityResourceLifecycle resources, RuntimeEntityObjectLifetime entityObjects) : this( spatial, resources, NullLiveEntityRuntimeComponentLifecycle.Instance, entityObjects) { } public LiveEntityRuntime( GpuWorldState spatial, ILiveEntityResourceLifecycle resources, Action tearDownRuntimeComponents, RuntimeEntityObjectLifetime entityObjects) : this( spatial, resources, new DelegateLiveEntityRuntimeComponentLifecycle(tearDownRuntimeComponents), entityObjects) { } internal LiveEntityRuntime( GpuWorldState spatial, ILiveEntityResourceLifecycle resources, ILiveEntityRuntimeComponentLifecycle? runtimeComponentLifecycle, RuntimeEntityObjectLifetime entityObjects) { _spatial = spatial ?? throw new ArgumentNullException(nameof(spatial)); _resources = resources ?? throw new ArgumentNullException(nameof(resources)); _runtimeComponentLifecycle = runtimeComponentLifecycle ?? throw new ArgumentNullException(nameof(runtimeComponentLifecycle)); _entityObjects = entityObjects ?? throw new ArgumentNullException(nameof(entityObjects)); _directory = _entityObjects.Entities; _physics = _entityObjects.Physics; _projections = new LiveEntityProjectionStore(_directory); _spatial.LiveProjectionVisibilityChanged += OnSpatialVisibilityChanged; _physics.CellCommitted += OnRuntimePhysicsCellCommitted; } public int Count => _directory.Count; public int PendingTeardownCount => _directory.PendingTeardownCount; public int MaterializedCount => _projections.MaterializedCount; public IReadOnlyCollection Records => _projections.ActiveRecords; public IReadOnlyCollection MaterializedRecords => _projections.MaterializedRecords; public IReadOnlyCollection VisibleRecords => _projections.VisibleRecords; internal IReadOnlyCollection CanonicalRecords => _directory.ActiveRecords; internal ulong SessionLifetimeVersion => _directory.SessionLifetimeVersion; internal RuntimePhysicsState Physics => _physics; public IReadOnlyDictionary Snapshots => _directory.Snapshots; internal int AnimationRuntimeCount { get { int count = 0; IReadOnlyList records = _projections.ActiveRecords; for (int i = 0; i < records.Count; i++) { if (records[i].AnimationRuntime is not null) count++; } return count; } } internal int SpatialAnimationRuntimeCount => _spatialAnimations.Count; internal int SpatialRemoteMotionRuntimeCount => _physics.SpatialRemoteCount; internal int SpatialProjectileRuntimeCount => _physics.SpatialProjectileCount; internal int SpatialRootObjectCount => _physics.SpatialRootCount; public ParentAttachmentState ParentAttachments => _directory.ParentAttachments; internal bool TryGetCanonical( uint serverGuid, out RuntimeEntityRecord canonical) => _directory.TryGetActive(serverGuid, out canonical); internal bool TryGetProjection( RuntimeEntityRecord canonical, out LiveEntityRecord record) => _projections.TryGet(canonical, out record); internal bool IsCurrentCanonical(RuntimeEntityRecord canonical) => _directory.IsCurrent(canonical); internal bool IsCurrentCreateIntegration( RuntimeEntityRecord canonical, ulong expectedCreateIntegrationVersion) => _directory.IsCurrent(canonical) && canonical.CreateIntegrationVersion == expectedCreateIntegrationVersion; internal void SetHasPartArray( RuntimeEntityRecord canonical, bool value) => _directory.SetHasPartArray(canonical, value); /// /// Raised after a materialized projection enters or leaves the currently /// loaded spatial view. Logical resources remain owned by the record. /// public event Action? ProjectionVisibilityChanged; public LiveEntityRegistrationResult RegisterLiveEntity( WorldSession.EntitySpawn incoming, bool isLocalPlayer = false) { if (_isClearing || _sessionClearPendingFinalization || _isRegisteringResources) { throw new InvalidOperationException( _isClearing || _sessionClearPendingFinalization ? "A live entity cannot register while the session lifetime is clearing." : "A live entity cannot register from inside atomic resource registration."); } // C3c route-1 flip: every graphical initial Create enters the // canonical initial-residence lease. The accepted wire frame stays on // the canonical record with FullCell 0 until the authored Runtime // SetPosition operation commits; the host's first-entry drive // controller walks the conductors from the residence-begin // notification. RuntimeEntityRegistrationResult registration = _entityObjects.RegisterEntityWithInitialResidence( incoming, isLocalPlayer, RetirePriorProjection); RuntimeEntityRecord? canonical = registration.Canonical; LiveEntityRecord? projection = canonical is null ? null : _projections.GetCurrentOrDefault(canonical.ServerGuid); if (projection is not null && ReferenceEquals(projection.Canonical, canonical)) { RefreshSpatialRuntimeIndexes(projection); } return new LiveEntityRegistrationResult( registration.Inbound, canonical, projection, registration.LogicalRegistrationCreated, registration.ReplacedExistingGeneration, registration.PriorGenerationCleanupFailure); } /// /// Completes logical registration when a renderable projection can be /// hydrated. The factory is called at most once for the incarnation. /// public WorldEntity? MaterializeLiveEntity( uint serverGuid, uint fullCellId, Func factory, LiveEntityProjectionKind projectionKind = LiveEntityProjectionKind.World) { if (!_directory.TryGetActive( serverGuid, out RuntimeEntityRecord canonical)) { return null; } return MaterializeLiveEntity( canonical, fullCellId, factory, projectionKind, initializeProjection: null, out _); } /// /// Claims the canonical Runtime local ID, creates its exact-key App /// sidecar, and synchronously constructs the projection. No sidecar exists /// before this method crosses the local-ID claim boundary. /// internal WorldEntity? MaterializeLiveEntity( RuntimeEntityRecord expectedCanonical, uint fullCellId, Func factory, LiveEntityProjectionKind projectionKind, Action? initializeProjection, out LiveEntityRecord? record, LiveEntityMaterializationResidence residence = LiveEntityMaterializationResidence.LegacyImmediate) { ArgumentNullException.ThrowIfNull(expectedCanonical); ArgumentNullException.ThrowIfNull(factory); if (residence is LiveEntityMaterializationResidence .AwaitRuntimePlacement && projectionKind is not LiveEntityProjectionKind.World) { throw new ArgumentException( "Only a top-level world projection can await canonical Runtime placement.", nameof(projectionKind)); } record = null; if (_isClearing || _sessionClearPendingFinalization || _logicalTeardownDepth != 0 || _isRegisteringResources) { throw new InvalidOperationException( "A live entity cannot materialize inside an active logical-lifetime transition."); } if (!_directory.IsCurrent(expectedCanonical)) return null; uint serverGuid = expectedCanonical.ServerGuid; bool createdSidecar = false; if (!_projections.TryGet(expectedCanonical, out record)) { _ = expectedCanonical.LocalEntityId ?? throw new InvalidOperationException( "Runtime must issue entity identity before App projection."); try { record = _projections.AddMaterializing(expectedCanonical); createdSidecar = true; record.MaterializationResidence = residence; initializeProjection?.Invoke(record); } catch { if (record is not null) { _projections.RemoveActive(record); record.ProjectionKey = null; record = null; } throw; } } else if (record.MaterializationResidence != residence) { throw new InvalidOperationException( $"Live entity 0x{serverGuid:X8}/{expectedCanonical.Incarnation} " + $"cannot change its materialization residence from " + $"{record.MaterializationResidence} to {residence}."); } if (record.WorldEntity is null) { uint localId = expectedCanonical.LocalEntityId ?? throw new InvalidOperationException( "A materializing App sidecar lost its Runtime local ID."); WorldEntity entity; try { entity = factory(localId) ?? throw new InvalidOperationException( "A live-entity projection factory returned null."); if (entity.Id != localId) { throw new InvalidOperationException( $"Live projection id 0x{entity.Id:X8} did not match reserved id 0x{localId:X8}."); } if (entity.ServerGuid != serverGuid) { throw new InvalidOperationException( $"Live projection guid 0x{entity.ServerGuid:X8} did not match record 0x{serverGuid:X8}."); } } catch { if (createdSidecar) { _projections.RemoveActive(record); record.ProjectionKey = null; record = null; } throw; } record.WorldEntity = entity; // Runtime-placement residence is sticky for this incarnation and // must suppress draw before arbitrary resource callbacks observe // or re-enter the sidecar. RefreshPresentation(record); _isRegisteringResources = true; try { // Registration may span several App owners. Until the whole // edge returns, Unregister remains a required rollback // obligation; a failed rollback retains this identity as a // teardown tombstone instead of making partial owners // unreachable. record.ResourcesRegistered = true; try { _resources.Register(entity); } catch (Exception registrationError) { Exception? rollbackError = null; try { _resources.Unregister(entity); record.ResourcesRegistered = false; } catch (Exception error) { rollbackError = error; } if (rollbackError is null) { _projections.RemoveActive(record); record.ProjectionKey = null; record.WorldEntity = null; record = null; throw; } _entityObjects.RetireAfterProjectionAcquisitionFailure( expectedCanonical); if (_projections.RemoveActive(record)) { RetainTeardownRecord(record); } throw new AggregateException( "Live entity resource registration and rollback both failed; " + "the partial owner was retained for teardown retry.", registrationError, rollbackError); } } finally { _isRegisteringResources = false; } } if (!_directory.IsCurrent(expectedCanonical) || !_projections.TryGet(expectedCanonical, out LiveEntityRecord currentRecord) || !ReferenceEquals(currentRecord, record)) { record = null; return null; } record.ProjectionKind = projectionKind; if (projectionKind is LiveEntityProjectionKind.World) { // Attached projections inherit the complete ancestor visibility // chain. Once the same logical entity becomes a top-level world // projection it has no render ancestor, so retained inherited // suppression must be cleared at this ownership transition. record.WorldEntity!.IsAncestorDrawVisible = true; } RefreshPresentation(record); WorldEntity materialized = record.WorldEntity!; if (residence is LiveEntityMaterializationResidence .AwaitRuntimePlacement) { // Resource construction is a logical App-side ownership edge, not // world residence. Runtime's immutable Place receipt is the only // path which may install the accepted frame and spatial bucket. record.IsSpatiallyProjected = false; record.IsSpatiallyVisible = false; RefreshSpatialPresentationIndexes(record); RefreshPresentation(record); return materialized; } if (!RebucketLiveEntity(serverGuid, fullCellId) || !_projections.TryGet(expectedCanonical, out LiveEntityRecord? current) || !ReferenceEquals(current, record) || !ReferenceEquals(current.WorldEntity, materialized)) { // Visibility observers may replace this GUID while rebucketing. // A failed teardown can retain the displaced entity as a retry // tombstone; it is not the result of this materialization. record = null; return null; } return materialized; } /// Changes spatial buckets only. No logical resource callbacks run. public bool RebucketLiveEntity(uint serverGuid, uint spatialCellOrLandblockId) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is not { } entity) return false; if (record.MaterializationResidence is LiveEntityMaterializationResidence.AwaitRuntimePlacement && HasActiveInitialCreateResidence(record.Canonical)) { // C3c: while the initial-create residence is ACTIVE, Runtime's // SetPosition owner is the sole canonical position/cell/ // object-clock authority and even the graphical bucket stays // suppressed: the conductor's completion receipt (which reaches // presentation through // TryApplyInitialCreateCompletionPresentation, not this API) is // the entity's first world-visible moment. Without this gate a // re-entrant caller (e.g. a resource-registration observer) // could install a bucket for a suppressed record before its // placement ever committed. A STALE residence is lazily retired // by this same query, after which legacy moves flow. // // C3c-R1 review R2: the gate is the EXACT-token residence // activity view, never the sticky MaterializationResidence enum // alone. Post-residence (the lease completed and was consumed) // this method falls through to the FULL legacy branch below: // the unflipped update routes (network position/state, remote // and local teleports, streaming reprojection, hydration // recovery) are the position authority again, and retail's // prepare_to_enter_world (0x00511FA0) clock rebase must run on // every root-workset membership edge — the earlier // presentation-only shortcut skipped CommitRebucket and that // clock edge for the entity's whole post-residence lifetime. return false; } RuntimeEntityKey key = RequireProjectionKey(record); bool wasProjected = record.IsSpatiallyProjected; bool wasVisible = record.IsSpatiallyVisible; bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical); ulong projectionOperation = ++record.ProjectionMutationVersion; // GpuWorldState reports an intermediate false/true pair while moving // between two loaded buckets. Suppress those implementation details // and publish only the final logical visibility edge. record.IsSpatiallyProjected = true; bool hasExactDestinationCell = spatialCellOrLandblockId != 0u && (spatialCellOrLandblockId & 0xFFFFu) != 0xFFFFu; if (hasExactDestinationCell) { // Runtime's physics cell commit and the graphical sidecar are one // SetPosition result. Retail CPhysicsObj::set_cell changes the // CObjCell read by ShouldDrawParticles at the same edge; retaining // the prior sidecar cell makes newly-created spell particles fail // IsInView as soon as the player crosses an outdoor landcell. // #282: a live entity's cell is ParentCellId alone. EffectCellId // exists only for outdoor dat stabs/building shells, which keep a // null render parent (LandblockLoader:80,97) - writing it here // made it win WorldEntity.VisibilityCellId for live entities, // which the 11 non-rebucketing per-tick writers cannot maintain. entity.ParentCellId = spatialCellOrLandblockId; } Exception? spatialNotificationFailure = null; uint priorRebucketingGuid = _rebucketingGuid; _rebucketingGuid = serverGuid; try { try { _spatial.RebucketLiveEntity( key, entity, spatialCellOrLandblockId); } catch (AggregateException error) { // GpuWorldState has already committed the bucket move and // drained every visibility observer before reporting their // failures. Finish this runtime transaction before surfacing // the notification error to the caller. spatialNotificationFailure = error; } } finally { _rebucketingGuid = priorRebucketingGuid; } if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } bool visible = _spatial.IsLiveEntityProjectionResident(key); record.IsSpatiallyVisible = visible; RefreshPresentation(record); uint committedFullCell = (spatialCellOrLandblockId & 0xFFFFu) != 0xFFFFu ? spatialCellOrLandblockId : record.FullCellId; uint committedLandblock = spatialCellOrLandblockId == 0 ? 0u : (spatialCellOrLandblockId & 0xFFFF0000u) | 0xFFFFu; if (!_entityObjects.CommitRebucket( record.Canonical, committedFullCell, committedLandblock)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World && record.IsSpatiallyProjected && record.IsSpatiallyVisible && record.FullCellId != 0; // prepare_to_enter_world (0x00511FA0) writes update_time = cur_time. // Only a root-workset membership edge rebases the clock. Ordinary // loaded-to-loaded movement preserves it; parented/attached objects // take retail update_object's parent early-out and remain suspended. if (!wasProjected && !isOrdinaryRoot) { record.SuspendObjectClock(); SynchronizePhysicsBodyActiveState(record); } else if (wasOrdinaryRoot != isOrdinaryRoot) { if (isOrdinaryRoot) { // InitObjectBegin and every leave-visibility edge are inactive // before retail prepare_to_enter_world. Preserve that // prerequisite for a first materialization as well. if (!wasProjected) record.ObjectClock.Deactivate(); record.ResetObjectClockForEnterWorld( (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0); SynchronizePhysicsBodyActiveState(record); } else { record.SuspendObjectClock(); SynchronizePhysicsBodyActiveState(record); } } RefreshSpatialRuntimeIndexes(record); Exception? runtimeNotificationFailure = null; if (!wasProjected || wasVisible != visible) { try { PublishProjectionVisibilityChanged(record, visible); } catch (Exception error) { runtimeNotificationFailure = error; } } // Final observers are intentionally re-entrant. A callback can delete // and recreate this GUID or start a newer rebucket on the same record; // the outer caller must not continue with captures from that displaced // projection operation. if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return false; } ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return true; } /// /// C3c: the graphical-bucket-only projection of a conductor-owned /// initial placement — called ONLY from /// (the /// completion receipt at the initial-create residence boundary), never /// from the public (C3c-R1 review R2: /// post-residence moves take the full legacy branch there). /// Deliberately never calls CommitRebucket, /// SuspendObjectClock, or ResetObjectClockForEnterWorld — /// Runtime's SetPosition commit already owns all of those for the /// residence-driven placement this receipt projects. May place into a /// pending (not-yet-loaded) bucket exactly like the legacy Create path /// did; the pending drain publishes visibility when the landblock loads. /// private bool RebucketLiveEntityPresentationOnly( uint serverGuid, LiveEntityRecord record, WorldEntity entity, uint spatialCellOrLandblockId) { RuntimeEntityKey key = RequireProjectionKey(record); bool wasProjected = record.IsSpatiallyProjected; bool wasVisible = record.IsSpatiallyVisible; ulong projectionOperation = ++record.ProjectionMutationVersion; record.IsSpatiallyProjected = true; Exception? spatialNotificationFailure = null; uint priorRebucketingGuid = _rebucketingGuid; _rebucketingGuid = serverGuid; BeginPresentationOnlySpatialMutation(key); try { try { _spatial.RebucketLiveEntity( key, entity, spatialCellOrLandblockId); } catch (AggregateException error) { spatialNotificationFailure = error; } } finally { EndPresentationOnlySpatialMutation(key); _rebucketingGuid = priorRebucketingGuid; } if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } bool visible = _spatial.IsLiveEntityProjectionResident(key); record.IsSpatiallyVisible = visible; RefreshSpatialPresentationIndexes(record); RefreshPresentation(record); RefreshSpatialRuntimeIndexes(record); Exception? runtimeNotificationFailure = null; if (!wasProjected || wasVisible != visible) { try { PublishProjectionVisibilityChanged(record, visible); } catch (Exception error) { runtimeNotificationFailure = error; } } if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return false; } ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return true; } /// /// C3c: applies one initial-Create ExecutorCompleted receipt's /// presentation — the graphical binding point for a residence-driven /// initial placement. Runtime committed position, cell, body, clocks, /// and worksets during the conductor's drain; this installs the /// committed frame on the sidecar and moves its graphical bucket /// (pending buckets allowed — the legacy Create path's own semantics). /// Superseded facts (a later legacy-path move already advanced the /// record past the receipt) are treated as already-projected: the /// receipt is stale for presentation and must not snap the entity back. /// internal bool TryApplyInitialCreateCompletionPresentation( in RuntimePlacementProjectionSnapshot projection) { RuntimePlacementProjectionToken token = projection.Token; if (!token.IsValid || token.SessionLifetimeVersion != _directory.SessionLifetimeVersion || !_projections.TryGet(token.Entity, out LiveEntityRecord? record) || !_directory.IsCurrent(record.Canonical) || record.Canonical.Key != token.Entity || record.WorldEntity is not { } entity) { // No sidecar (a deferred-child replay materializes later and // self-projects from canonical state) or a displaced identity — // acknowledge-only. return true; } if (record.FullCellId != token.ExactCellId || record.Canonical.PlacementCommitVersion != token.PlacementCommitVersion) { // A newer move superseded this receipt's facts after the drain. return true; } entity.SetPosition(projection.WorldPosition); entity.Rotation = projection.Orientation; // #282: live entities carry ParentCellId only; EffectCellId is the // outdoor dat stab / building-shell field (LandblockLoader:80,97). entity.ParentCellId = token.ExactCellId; return RebucketLiveEntityPresentationOnly( record.ServerGuid, record, entity, token.ExactCellId); } /// /// C3c: true while the exact incarnation behind /// holds an initial-create residence lease — the discriminator the /// placement sink uses to leave conductor-owned Place/Withdraw receipts /// at the FIFO head for the drive controller to consume. /// internal bool HasActiveInitialCreateResidence(RuntimeEntityKey key) => _directory.TryGetByLocalId( key.LocalEntityId, out RuntimeEntityRecord canonical) && _directory.IsCurrent(canonical) && canonical.Key == key && _entityObjects.TryGetInitialCreateResidence(canonical, out _); /// /// C3c: true when the exact incarnation behind /// holds an initial-create residence lease. Used by materialization to /// decide whether presentation must await the conductor's completion /// receipt or may self-project from already-committed canonical state. /// internal bool HasActiveInitialCreateResidence( RuntimeEntityRecord canonical) => _entityObjects.TryGetInitialCreateResidence(canonical, out _); /// /// C3c-R1 review F1: the ONLY sanctioned mutation of the otherwise /// sticky — a /// world-created (residence-managed) entity converting to an attached /// projection at a same-incarnation kind transition (the equipped-child /// world→attached path). Attached children have no Runtime placement, /// so the sticky-residence rule expects them to carry /// . /// Owned here so the invariant is asserted at the owner: converting /// while the initial-create residence is still ACTIVE would let an /// attached materialization race the conductor's pending placement. /// internal void ConvertMaterializationResidenceToLegacyImmediate( LiveEntityRecord record) { ArgumentNullException.ThrowIfNull(record); if (record.MaterializationResidence is not LiveEntityMaterializationResidence.AwaitRuntimePlacement) { return; } if (HasActiveInitialCreateResidence(record.Canonical)) { throw new InvalidOperationException( $"Live entity 0x{record.ServerGuid:X8}/" + $"{record.Canonical.Incarnation} cannot convert to " + "LegacyImmediate residence while its initial-create " + "residence lease is still active."); } record.MaterializationResidence = LiveEntityMaterializationResidence.LegacyImmediate; } /// /// Applies one canonical Runtime placement receipt to the graphical /// sidecar only. Runtime has already committed identity, position, /// collision residence, object-clock state, and simulation worksets. /// internal bool TryApplyRuntimePlacementProjection( in RuntimePlacementProjectionSnapshot projection) { if (projection.Kind is RuntimePlacementProjectionKind.Discard) { // Discard cancels only an unacknowledged observation. If its Place // was already projected, retail keeps that last committed frame // visible until a later canonical Place or Withdraw supersedes it. return true; } if (projection.Kind is RuntimePlacementProjectionKind.ExecutorCompleted) { // F1: acknowledge-and-ignore, same as Discard. Must NOT fall // through to the record-lookup/spatial-load gates below - those // legitimately reject for reasons unrelated to this receipt (no // sidecar yet, destination backend not loaded), and a false // return here wedges the whole ordered placement stream at the // FIFO head (RuntimePlacementProjectionSubscription's contract). // Provably inert today: PublishExecutorCompletion has zero // production callers. return true; } RuntimePlacementProjectionToken token = projection.Token; if (!TryGetRuntimePlacementProjectionRecord( token, requirePlacementVersions: projection.Kind is RuntimePlacementProjectionKind.Place, out LiveEntityRecord? record) || record.WorldEntity is not { } entity) { return false; } if (projection.Kind is RuntimePlacementProjectionKind.Place && !_spatial.IsLoaded( (token.ExactCellId & 0xFFFF0000u) | 0xFFFFu)) { // A canonical SetPosition receipt is not permission to create a // pending graphical bucket. Keep the FIFO head unacknowledged // until the destination backend exists, otherwise GpuWorldState's // later pending-drain edge escapes this exact receipt transaction. return false; } return projection.Kind switch { RuntimePlacementProjectionKind.Place => TryApplyRuntimePlacementPlace(in projection, record, entity), RuntimePlacementProjectionKind.Withdraw => TryApplyRuntimePlacementWithdrawal(token, record, entity), _ => false, }; } private bool TryApplyRuntimePlacementPlace( in RuntimePlacementProjectionSnapshot projection, LiveEntityRecord record, WorldEntity entity) { RuntimePlacementProjectionToken token = projection.Token; RuntimeEntityKey key = token.Entity; ulong projectionOperation = ++record.ProjectionMutationVersion; entity.SetPosition(projection.WorldPosition); entity.Rotation = projection.Orientation; // #282: live entities carry ParentCellId only; EffectCellId is the // outdoor dat stab / building-shell field (LandblockLoader:80,97). entity.ParentCellId = token.ExactCellId; record.IsSpatiallyProjected = true; Exception? spatialNotificationFailure = null; uint priorRebucketingGuid = _rebucketingGuid; _rebucketingGuid = record.ServerGuid; BeginPresentationOnlySpatialMutation(key); try { try { _spatial.RebucketLiveEntity(key, entity, token.ExactCellId); } catch (AggregateException error) { spatialNotificationFailure = error; } } finally { EndPresentationOnlySpatialMutation(key); _rebucketingGuid = priorRebucketingGuid; } if (!IsCurrentProjectionOperation( record.ServerGuid, record, projectionOperation) || !TryGetRuntimePlacementProjectionRecord( token, requirePlacementVersions: true, out LiveEntityRecord? current) || !ReferenceEquals(current, record) || !ReferenceEquals(current.WorldEntity, entity)) { ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } bool visible = _spatial.IsLiveEntityProjectionResident(key); record.IsSpatiallyVisible = visible; RefreshSpatialPresentationIndexes(record); RefreshPresentation(record); if (!IsCurrentProjectionOperation( record.ServerGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return true; } private bool TryApplyRuntimePlacementWithdrawal( in RuntimePlacementProjectionToken token, LiveEntityRecord record, WorldEntity entity) { RuntimeEntityKey key = token.Entity; ulong projectionOperation = ++record.ProjectionMutationVersion; record.IsSpatiallyProjected = false; Exception? spatialNotificationFailure = null; uint priorRebucketingGuid = _rebucketingGuid; _rebucketingGuid = record.ServerGuid; BeginPresentationOnlySpatialMutation(key); try { try { _spatial.RemoveLiveEntityProjection(entity); } catch (AggregateException error) { spatialNotificationFailure = error; } } finally { EndPresentationOnlySpatialMutation(key); _rebucketingGuid = priorRebucketingGuid; } if (!IsCurrentProjectionOperation( record.ServerGuid, record, projectionOperation) || !TryGetRuntimePlacementProjectionRecord( token, requirePlacementVersions: false, out LiveEntityRecord? current) || !ReferenceEquals(current, record) || !ReferenceEquals(current.WorldEntity, entity)) { ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } record.IsSpatiallyVisible = false; RefreshSpatialPresentationIndexes(record); RefreshPresentation(record); if (!IsCurrentProjectionOperation( record.ServerGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } ThrowAfterCommittedProjectionChange( record.ServerGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return true; } private bool TryGetRuntimePlacementProjectionRecord( in RuntimePlacementProjectionToken token, bool requirePlacementVersions, out LiveEntityRecord record) { if (!token.IsValid || token.SessionLifetimeVersion != _directory.SessionLifetimeVersion || !_projections.TryGet(token.Entity, out record!) || !_directory.IsCurrent(record.Canonical) || record.Canonical.Key != token.Entity || RequireProjectionKey(record) != token.Entity || !IsValidPortalPlacementAuthority(token)) { record = null!; return false; } if (requirePlacementVersions && (record.Canonical.PositionAuthorityVersion != token.PositionAuthorityVersion || record.Canonical.SpatialAuthorityVersion != token.SpatialAuthorityVersion || record.Canonical.PlacementCommitVersion != token.PlacementCommitVersion || record.Canonical.FullCellId != token.ExactCellId)) { record = null!; return false; } return true; } private static bool IsValidPortalPlacementAuthority( in RuntimePlacementProjectionToken token) { RuntimePortalPlacementAuthority portal = token.Portal; if (!portal.Present) return portal.IsEmpty; return portal.IsValid && portal.Projection.DestinationCell == token.ExactCellId; } private void BeginPresentationOnlySpatialMutation(RuntimeEntityKey key) { _presentationOnlySpatialMutationDepth.TryGetValue(key, out int depth); _presentationOnlySpatialMutationDepth[key] = checked(depth + 1); } private void EndPresentationOnlySpatialMutation(RuntimeEntityKey key) { if (!_presentationOnlySpatialMutationDepth.TryGetValue( key, out int depth) || depth <= 0) { throw new InvalidOperationException( "Presentation-only spatial mutation depth was not balanced."); } if (depth == 1) _presentationOnlySpatialMutationDepth.Remove(key); else _presentationOnlySpatialMutationDepth[key] = depth - 1; } /// /// Removes only the render-bucket reference. The logical record and every /// create-time resource remain alive for later projection/rebucketing. /// public bool WithdrawLiveEntityProjection(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is null) return false; RuntimeEntityKey key = RequireProjectionKey(record); bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical); ulong objectClockEpoch = record.ObjectClockEpoch; ulong projectionOperation = ++record.ProjectionMutationVersion; Exception? spatialNotificationFailure = null; try { _spatial.RemoveLiveEntityProjection(record.WorldEntity); } catch (AggregateException error) { // The projection is already absent and GpuWorldState has already // drained its observer queue. Complete canonical withdrawal below // before reporting the notification failure. spatialNotificationFailure = error; } if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure: null); return false; } // Usually GpuWorldState delivers the false edge synchronously above. // During a reentrant visibility callback it queues that edge until the // outer notification completes, so publish the logical withdrawal now; // OnSpatialVisibilityChanged rejects the later duplicate against this // committed record state. bool spatialEdgeWasDeferred = record.IsSpatiallyVisible; _projections.SetVisible(record, false); record.IsSpatiallyProjected = false; record.IsSpatiallyVisible = false; // GpuWorldState normally publishes the leave edge synchronously and // OnSpatialVisibilityChanged suspends this exact clock there. During a // reentrant notification the edge is deferred, so commit the missing // suspension here. One root-workset leave is one epoch edge -- never // advance it once in the observer and again in this owner. if (wasOrdinaryRoot && record.ObjectClockEpoch == objectClockEpoch) record.SuspendObjectClock(); SynchronizePhysicsBodyActiveState(record); RefreshSpatialRuntimeIndexes(record); Exception? runtimeNotificationFailure = null; if (spatialEdgeWasDeferred) { try { PublishProjectionVisibilityChanged(record, false); } catch (Exception error) { runtimeNotificationFailure = error; } } // The manually published deferred edge is an arbitrary callback // boundary just like Rebucket's final visibility edge. It may // reproject this record or replace the GUID; the displaced outer // withdrawal must not report itself as the current operation. if (!IsCurrentProjectionOperation(serverGuid, record, projectionOperation)) { ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return false; } ThrowAfterCommittedProjectionChange( serverGuid, spatialNotificationFailure, runtimeNotificationFailure); return true; } /// /// Exact-incarnation withdrawal used after App leave-world callbacks. A /// callback may accept a newer INSTANCE_TS for the same GUID; that newer /// projection must never be withdrawn by the displaced operation. /// public bool WithdrawLiveEntityProjection(LiveEntityRecord expectedRecord) { ArgumentNullException.ThrowIfNull(expectedRecord); return WithdrawLiveEntityProjection( expectedRecord, expectedRecord.PositionAuthorityVersion, expectedRecord.ProjectionMutationVersion); } /// /// Exact-operation withdrawal used across arbitrary App callbacks. Both /// the accepted Position authority and projection mutation token are /// pinned so a same-incarnation re-entry cannot be removed by an older /// parent/pickup/leave-world operation. /// internal bool WithdrawLiveEntityProjection( LiveEntityRecord expectedRecord, ulong expectedPositionAuthorityVersion, ulong expectedProjectionMutationVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); return IsCurrentRecord(expectedRecord) && expectedRecord.PositionAuthorityVersion == expectedPositionAuthorityVersion && expectedRecord.ProjectionMutationVersion == expectedProjectionMutationVersion && WithdrawLiveEntityProjection(expectedRecord.ServerGuid); } /// /// Withdraws a still-live projection whose authoritative rollback frame /// is outside every world cell. Logical owners remain registered. /// internal bool WithdrawLiveEntityProjectionToCellless(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is null) { return false; } if (!_entityObjects.CommitWithdrawal( record.Canonical, AcknowledgeCelllessCanonicalCommit)) { return false; } record.WorldEntity.ParentCellId = 0u; return WithdrawLiveEntityProjection(serverGuid); } public bool UnregisterLiveEntity( DeleteObject.Parsed delete, bool isLocalPlayer, bool removeRetainedObject = false, Action? beforeTeardown = null) { if (_isRegisteringResources) { throw new InvalidOperationException( "A live entity cannot unregister from inside atomic resource registration."); } var teardownKey = (delete.Guid, delete.InstanceSequence); LiveEntityRecord? record = null; RuntimeEntityRecord? canonicalOnly = null; if (_directory.TryGetTeardown( teardownKey.Guid, teardownKey.InstanceSequence, out RuntimeEntityRecord retainedCanonical)) { _projections.TryGetTeardown(retainedCanonical, out record); } bool retryingAcceptedDelete = record is not null && (record.DeleteAcceptedForTeardown || _isClearing || _sessionClearPendingFinalization); RuntimeEntityDeleteAcceptance? acceptedDelete = null; if (!retryingAcceptedDelete) { if (!_entityObjects.TryAcceptDelete( delete, isLocalPlayer, removeRetainedObject, out acceptedDelete)) return false; // End active gameplay identity before arbitrary callbacks so a // newer generation can be accepted re-entrantly, but retain the // exact incarnation as a teardown tombstone until unregister has // succeeded. A throwing resource callback can then be retried by // the same accepted Delete instead of orphaning its owner. LiveEntityRecord? retainedRegistrationFailure = record; if (acceptedDelete.RetiredCanonical is { } activeCanonical) { if (_projections.TryGet( activeCanonical, out LiveEntityRecord? activeRecord)) { if (!_projections.RemoveActive(activeRecord)) { throw new InvalidOperationException( $"Exact App projection for 0x{delete.Guid:X8}/{delete.InstanceSequence} could not be retired."); } record = activeRecord; RetainTeardownRecord(record); } else { canonicalOnly = activeCanonical; } } else { record = retainedRegistrationFailure; } if (record is not null) record.DeleteAcceptedForTeardown = true; } List? failures = null; _logicalTeardownDepth++; try { if (!retryingAcceptedDelete) { try { _entityObjects.CompleteAcceptedDelete(acceptedDelete!); } catch (Exception error) { (failures ??= new List()).Add(error); } try { beforeTeardown?.Invoke(); } catch (Exception error) { (failures ??= new List()).Add(error); } } if (record is not null) { try { TearDownRecord(record); ReleaseTeardownRecord(record); } catch (Exception error) { (failures ??= new List()).Add(error); } } else if (canonicalOnly is not null) { TearDownCanonicalOnly(canonicalOnly); } } finally { _logicalTeardownDepth--; } try { // Persistence is GUID-scoped. End it only when the accepted // delete left no replacement incarnation or unfinished teardown // behind. if (!_directory.TryGetActive(delete.Guid, out _) && !HasPendingTeardown(delete.Guid)) { _spatial.ForgetLiveEntity(delete.Guid); } } catch (Exception error) { (failures ??= new List()).Add(error); } if (failures is not null) throw new AggregateException( $"Live entity 0x{delete.Guid:X8} deletion cleanup failed.", failures); return true; } public bool TryGetRecord(uint serverGuid, out LiveEntityRecord record) => _projections.TryGetCurrent(serverGuid, out record!); public bool TryGetWorldEntity(uint serverGuid, out WorldEntity entity) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && record.WorldEntity is { } found) { entity = found; return true; } entity = null!; return false; } /// /// Resolves a current top-level world projection even when its spatial /// bucket is pending/nonresident. Unlike interaction eligibility, this /// does not impose render visibility; unlike , /// it rejects a retained leave-world owner whose pose is historical. /// public bool TryGetSpatiallyProjectedRecord( uint serverGuid, out LiveEntityRecord record) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord found) && found.WorldEntity is not null && found.ProjectionKind is LiveEntityProjectionKind.World && found.IsSpatiallyProjected) { record = found; return true; } record = null!; return false; } /// /// Resolves a current equipped-child projection whose composed frame is /// live. Retail installs an equipped item as a first-class /// CPhysicsObj with its own object id and part array /// (CPhysicsObj::add_child @ 0x0050F870 via /// CSetup::GetHoldingLocation @ 0x005213F0), and /// CPhysicsObj::UpdateChild @ 0x00512D50 recomposes /// Frame::combine(parent part frame, holding frame) into that child's /// own m_position every frame. An attached projection therefore has /// real world presence even though it is deliberately absent from the /// interaction/radar/auto-target visible set. /// public bool TryGetAttachedProjectedRecord( uint serverGuid, out LiveEntityRecord record) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord found) && found.WorldEntity is not null && found.ProjectionKind is LiveEntityProjectionKind.Attached && found.IsSpatiallyProjected) { record = found; return true; } record = null!; return false; } /// /// Resolves an object that currently participates in mouse picking. /// Retail's only candidacy rule is "a drawn part whose owning physics /// object has a nonzero id" (CPhysicsPart::Draw @ 0x0050D7A0 guards /// on CPhysicsPart::get_physobj_id @ 0x0050D490, and /// Render::GfxObjUnderSelectionRay @ 0x0054C740 accumulates the /// hit under that id) — there is no parent redirection and no /// wielded-specific gate, so a click on a wielded weapon returns the /// weapon's own GUID. Picking therefore admits attached projections on top /// of the ordinary top-level visible set. This is deliberately NOT the /// interaction-eligible predicate: radar, auto-target, and /// MoveTo/Sticky establishment must stay wielded-item free. /// public bool TryGetPickEligibleRecord( uint serverGuid, out LiveEntityRecord record) => TryGetInteractionEligibleRecord(serverGuid, out record) || TryGetAttachedProjectedRecord(serverGuid, out record); /// /// Pick eligibility bound to one logical incarnation. A stale published /// frame must never retarget a replacement which reused the server GUID. /// public bool TryGetPickEligibleRecord( uint serverGuid, uint localEntityId, out LiveEntityRecord record) { if (serverGuid != 0u && localEntityId != 0u && TryGetPickEligibleRecord(serverGuid, out LiveEntityRecord found) && found.WorldEntity!.Id == localEntityId) { record = found; return true; } record = null!; return false; } /// /// Resolves a top-level object that currently participates in /// targeting, radar, and wire-driven MoveTo/Sticky establishment. /// Pending, attached, and Hidden projections are intentionally excluded. /// public bool TryGetInteractionEligibleEntity( uint serverGuid, out WorldEntity entity) { if (_projections.TryGetVisibleCurrent( serverGuid, out LiveEntityRecord record) && record.WorldEntity is { } visible) { entity = visible; return true; } entity = null!; return false; } bool ILiveEntityRadarSource.TryGetMaterialized( uint serverGuid, out WorldEntity entity) => TryGetWorldEntity(serverGuid, out entity); bool ILiveEntityRadarSource.TryGetVisible( uint serverGuid, out WorldEntity entity) => TryGetInteractionEligibleEntity(serverGuid, out entity); void ILiveEntityRadarSource.CopyVisibleTo( List> destination) { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach (LiveEntityRecord record in _projections.VisibleRecords) { destination.Add(new KeyValuePair( record.ServerGuid, record.WorldEntity!)); } } public bool TryGetInteractionEligibleRecord( uint serverGuid, out LiveEntityRecord record) { if (_projections.TryGetVisibleCurrent( serverGuid, out LiveEntityRecord found) && found.WorldEntity is not null) { record = found; return true; } record = null!; return false; } /// /// Resolves a render-published interaction candidate only while the same /// logical WorldEntity incarnation still owns the server GUID. A stale /// frame must never retarget a replacement which reused that GUID. /// public bool TryGetInteractionEligibleRecord( uint serverGuid, uint localEntityId, out LiveEntityRecord record) { if (serverGuid != 0u && localEntityId != 0u && TryGetInteractionEligibleRecord(serverGuid, out LiveEntityRecord found) && found.WorldEntity!.Id == localEntityId) { record = found; return true; } record = null!; return false; } public bool ContainsWorldEntity(uint serverGuid) => _projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && record.WorldEntity is not null; public bool TryGetServerGuid(uint localEntityId, out uint serverGuid) { if (_directory.TryGetByLocalId( localEntityId, out RuntimeEntityRecord canonical)) { serverGuid = canonical.ServerGuid; return true; } serverGuid = 0u; return false; } internal bool TryGetRecordByLocalEntityId( uint localEntityId, out LiveEntityRecord record) => _projections.TryGetByLocalId(localEntityId, out record); internal bool TryGetRecord( RuntimeEntityKey key, out LiveEntityRecord record) => _projections.TryGet(key, out record); public bool TryGetLocalEntityId(uint serverGuid, out uint localEntityId) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && record.LocalEntityId is { } found) { localEntityId = found; return true; } localEntityId = 0; return false; } public void SetAnimationRuntime(uint serverGuid, ILiveEntityAnimationRuntime runtime) { ArgumentNullException.ThrowIfNull(runtime); if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is not { } entity) throw new InvalidOperationException($"Cannot bind animation before live entity 0x{serverGuid:X8} is materialized."); if (!ReferenceEquals(runtime.Entity, entity)) throw new InvalidOperationException("Animation runtime belongs to a different WorldEntity."); _ = RequireProjectionKey(record); record.AnimationRuntime = runtime; RefreshSpatialRuntimeIndexes(record); } public bool TryGetAnimationRuntime( uint localEntityId, out ILiveEntityAnimationRuntime runtime) { if (_projections.TryGetByLocalId( localEntityId, out LiveEntityRecord record) && record.AnimationRuntime is { } found) { runtime = found; return true; } runtime = null!; return false; } internal bool TryGetProjectionKey( uint serverGuid, out RuntimeEntityKey key) { if (_projections.TryGetCurrent( serverGuid, out LiveEntityRecord? record) && record.ProjectionKey is { } found) { key = found; return true; } key = default; return false; } public bool ClearAnimationRuntime(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.AnimationRuntime is null) return false; record.AnimationRuntime = null; RefreshSpatialRuntimeIndexes(record); return true; } internal bool ClearAnimationRuntime(LiveEntityRecord expectedRecord) { ArgumentNullException.ThrowIfNull(expectedRecord); if (expectedRecord.AnimationRuntime is null || expectedRecord.ProjectionKey is not { } key || !_projections.TryGet(key, out LiveEntityRecord? record) || !ReferenceEquals(record, expectedRecord)) { return false; } record.AnimationRuntime = null; RefreshSpatialRuntimeIndexes(record); return true; } /// /// Acquires the one retail CPhysicsObj body for this exact live /// incarnation. Static animation may need the body before a MovementManager /// or projectile component exists; later components must adopt this same /// instance instead of replaying CreateObject vector initialization. /// internal PhysicsBody GetOrCreatePhysicsBody( uint serverGuid, Func factory) { ArgumentNullException.ThrowIfNull(factory); if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is null) { throw new InvalidOperationException( $"Cannot acquire physics body before live entity 0x{serverGuid:X8} is materialized."); } bool ProjectionIsCurrent() => _projections.TryGetCurrent( serverGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.WorldEntity is not null; return _physics.GetOrCreatePhysicsBody( record.Canonical, _ => factory(record), ProjectionIsCurrent); } internal void SetRemoteMotionRuntime(uint serverGuid, IRuntimeRemoteMotion runtime) { ArgumentNullException.ThrowIfNull(runtime); if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)) throw new InvalidOperationException($"Cannot bind remote motion before live entity 0x{serverGuid:X8} exists."); ulong lifetimeVersion = CurrentLifetimeMutation(serverGuid); _physics.SetRemoteMotion( record.Canonical, runtime, () => CurrentLifetimeMutation(serverGuid) == lifetimeVersion && _projections.TryGetCurrent( serverGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record)); SynchronizePhysicsBodyActiveState(record); RefreshSpatialRuntimeIndexes(record); } public void InstallPhysicsHost( LiveEntityRecord expectedRecord, AcDream.Core.Physics.Motion.IPhysicsObjHost host) { ArgumentNullException.ThrowIfNull(expectedRecord); ArgumentNullException.ThrowIfNull(host); uint serverGuid = expectedRecord.ServerGuid; bool ProjectionIsCurrent() => _projections.TryGetCurrent( serverGuid, out LiveEntityRecord? current) && ReferenceEquals(current, expectedRecord); _physics.InstallPhysicsHost( expectedRecord.Canonical, host, ProjectionIsCurrent); } public bool TryGetPhysicsHost( uint serverGuid, out AcDream.Core.Physics.Motion.IPhysicsObjHost host) { if (_projections.TryGetCurrent(serverGuid, out _) && _physics.TryGetPhysicsHost(serverGuid, out var existing)) { host = existing; return true; } host = null!; return false; } public bool TryGetRemoteMotionRuntime( uint serverGuid, out IRuntimeRemoteMotion runtime) { if (_projections.TryGetCurrent( serverGuid, out LiveEntityRecord? record) && record.RemoteMotionRuntime is { } found) { runtime = found; return true; } runtime = null!; return false; } internal bool ClearRemoteMotionRuntime(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.RemoteMotionRuntime is null) return false; if (!_physics.ClearRemoteMotion(record.Canonical)) return false; RefreshSpatialRuntimeIndexes(record); return true; } internal RemoteMotion GetOrCreateRemoteMotionRuntime(uint serverGuid) { if (!_projections.TryGetCurrent( serverGuid, out LiveEntityRecord? record)) { throw new InvalidOperationException( $"Cannot acquire remote motion before live entity 0x{serverGuid:X8} exists."); } bool ProjectionIsCurrent() => _projections.TryGetCurrent( serverGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record); return _physics.GetOrCreateRemoteMotion( record.Canonical, ProjectionIsCurrent); } internal IRuntimeProjectile BindProjectileRuntime( uint serverGuid, PhysicsBody body, ProjectileCollisionSphere collisionSphere, Func? externalOwnerValid = null) { ArgumentNullException.ThrowIfNull(body); if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is null) { throw new InvalidOperationException( $"Cannot bind projectile physics before live entity 0x{serverGuid:X8} is materialized."); } if (record.PhysicsBodyAcquisitionInProgress && record.PhysicsBody is null) throw new InvalidOperationException( $"Live entity 0x{serverGuid:X8} cannot bind projectile physics during physics-body acquisition."); if (record.PhysicsBody is { } canonicalBody && !ReferenceEquals(canonicalBody, body)) { throw new InvalidOperationException( $"Live entity 0x{serverGuid:X8} cannot replace its canonical physics body within one incarnation."); } IRuntimeProjectile runtime = _physics.BindProjectile( record.Canonical, body, collisionSphere, () => IsCurrentRecord(record) && record.WorldEntity is { } entity && record.LocalEntityId == entity.Id && (externalOwnerValid?.Invoke() ?? true)); RefreshSpatialRuntimeIndexes(record); return runtime; } public bool TryGetProjectileRuntime( uint serverGuid, out IRuntimeProjectile runtime) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && record.ProjectileRuntime is { } found) { runtime = found; return true; } runtime = null!; return false; } public bool ClearProjectileRuntime(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.ProjectileRuntime is null) return false; _physics.ClearProjectile(record.Canonical); RefreshSpatialRuntimeIndexes(record); return true; } public void SetEffectProfile(uint serverGuid, ILiveEntityEffectProfile profile) { ArgumentNullException.ThrowIfNull(profile); if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)) throw new InvalidOperationException( $"Cannot bind an effect profile before live entity 0x{serverGuid:X8} exists."); record.EffectProfile = profile; } public bool TryGetEffectProfile( uint serverGuid, out ILiveEntityEffectProfile profile) { if (_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && record.EffectProfile is { } found) { profile = found; return true; } profile = null!; return false; } public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) => _directory.TryGetSnapshot(guid, out spawn); public bool TryApplyObjDesc(ObjDescEvent.Parsed update, out WorldSession.EntitySpawn accepted) => _entityObjects.TryApplyObjDesc( update, canonical => { if (_projections.TryGet( canonical, out LiveEntityRecord? record)) { record.NoteObjDescProjectionSynchronization(); } }, out accepted); public bool TryApplyPickup(PickupEvent.Parsed update, out WorldSession.EntitySpawn accepted) => _entityObjects.TryApplyPickup( update, AcknowledgeCelllessCanonicalCommit, out accepted); public bool TryApplyCreateParent(CreateParentUpdate update, out WorldSession.EntitySpawn accepted) => _entityObjects.TryApplyCreateParent( update, acknowledgeProjection: null, out accepted); public bool TryApplyParent(ParentEvent.Parsed update, out WorldSession.EntitySpawn accepted) => _entityObjects.TryApplyParent( update, acknowledgeProjection: null, out accepted); internal bool CommitStagedParent( ParentAttachmentRelation relation, out WorldSession.EntitySpawn accepted) => _entityObjects.TryCommitParent( relation, acknowledgeProjection: null, out accepted); /// /// Commits retail CPhysicsObj::set_parent's cell-less edge only /// after the parent accepted the child (PartArray + holding-location /// validation). The POSITION_TS is consumed before this step, matching /// retail; invalid parent relationships retain the old world projection. /// internal bool CommitAcceptedParentCellless( LiveEntityRecord record, ulong positionAuthorityVersion) { ArgumentNullException.ThrowIfNull(record); return _entityObjects.CommitAcceptedParentCellless( record.Canonical, positionAuthorityVersion, AcknowledgeCelllessCanonicalCommit); } internal bool CommitAcceptedParentCellless( RuntimeEntityRecord canonical, ulong positionAuthorityVersion) { return _entityObjects.CommitAcceptedParentCellless( canonical, positionAuthorityVersion, AcknowledgeCelllessCanonicalCommit); } public bool TryApplyMotion( WorldSession.EntityMotionUpdate update, bool retainPayload, out WorldSession.EntitySpawn accepted, out AcceptedPhysicsTimestamps timestamps) => _entityObjects.TryApplyMotion( update, retainPayload, acknowledgeProjection: null, out accepted, out timestamps); public bool TryApplyVector(VectorUpdate.Parsed update, out WorldSession.EntitySpawn accepted) => _entityObjects.TryApplyVector( update, acknowledgeProjection: null, out accepted); public bool TryApplyState(SetState.Parsed update, out WorldSession.EntitySpawn accepted) => TryApplyState(update, out accepted, out _); public bool TryApplyState( SetState.Parsed update, out WorldSession.EntitySpawn accepted, out RetailPhysicsStateTransition transition) => _entityObjects.TryApplyState( update, (canonical, _) => { if (_projections.TryGet( canonical, out LiveEntityRecord? record)) { RefreshPresentation(record); } }, out accepted, out transition); /// /// Retail parent Hidden directly toggles each attached child's NoDraw bit /// without consuming the child's STATE_TS or replacing its raw wire state. /// public bool SetAttachedChildNoDraw(uint childServerGuid, bool noDraw) { if (!_projections.TryGetCurrent(childServerGuid, out LiveEntityRecord? record)) return false; return _entityObjects.CommitChildNoDraw( record.Canonical, noDraw, _ => RefreshPresentation(record)); } public bool TryApplyPosition( WorldSession.EntityPositionUpdate update, bool isLocalPlayer, System.Numerics.Quaternion? forcePositionRotation, System.Numerics.Vector3? currentLocalVelocity, out PositionTimestampDisposition disposition, out WorldSession.EntitySpawn accepted, out AcceptedPhysicsTimestamps timestamps) { bool projectionRequiresTeleportHook = _directory.TryGetActive( update.Guid, out RuntimeEntityRecord canonical) && (canonical.FullCellId == 0u || !_projections.TryGet( canonical, out LiveEntityRecord? projection) || !projection.IsSpatiallyProjected || !projection.IsSpatiallyVisible); return _entityObjects.TryApplyPosition( update, isLocalPlayer, forcePositionRotation, currentLocalVelocity, projectionRequiresTeleportHook, acknowledgeProjection: null, out disposition, out accepted, out timestamps); } /// /// C4 route 4a: borrows the canonical Runtime classification for one /// remote's accepted Position. App supplies only the two things it owns — /// the canonical record and the live local-player distance — and never /// assembles an authority, a generation token, or a route request of its /// own. Returns when Runtime declines to classify; /// see . /// internal RuntimeAuthoritativePositionRoute? ClassifyRemoteAcceptedPosition( RuntimeEntityRecord canonical, in AcDream.Core.Net.WorldSession.EntityPositionUpdate update, AcDream.Core.Physics.PositionTimestampDisposition disposition, in AcceptedPhysicsTimestamps timestamps, float? playerDistance) => _entityObjects.ClassifyRemoteAcceptedPosition( canonical, update, disposition, timestamps, playerDistance); public bool IsFreshTeleportStart(uint localPlayerGuid, ushort teleportSequence) => _directory.IsFreshTeleportStart(localPlayerGuid, teleportSequence); /// /// Retail CPhysicsObj::update_object (0x00515D40) runs root /// object tick only while the object has visible world-cell membership and /// is not Frozen. Hidden is deliberately not a broad tick stop: retail /// skips PartArray animation and physics, but still applies the /// PositionManager offset and advances targeting/movement/script/particle /// owners. A pickup or parent transition keeps /// script/effect owners but clears the cell and withdraws the root /// projection until a fresh Position re-enters it. /// public RetailObjectClockDisposition GetRootObjectClockDisposition( uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.ProjectionKind is not LiveEntityProjectionKind.World || !record.IsSpatiallyProjected || !record.IsSpatiallyVisible || record.FullCellId == 0) { return RetailObjectClockDisposition.Suspend; } return (record.FinalPhysicsState & PhysicsStateFlags.Frozen) != 0 ? RetailObjectClockDisposition.Suspend : RetailObjectClockDisposition.Advance; } public bool ShouldAdvanceRootRuntime(uint serverGuid) => GetRootObjectClockDisposition(serverGuid) is RetailObjectClockDisposition.Advance; internal bool IsCurrentPositionAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.PositionAuthorityVersion == authorityVersion; internal bool IsCurrentPositionAuthority( RuntimeEntityRecord canonical, ulong authorityVersion) => _directory.IsCurrent(canonical) && canonical.PositionAuthorityVersion == authorityVersion; internal bool IsCurrentStateAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.StateAuthorityVersion == authorityVersion; internal bool IsCurrentVectorAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.VectorAuthorityVersion == authorityVersion; internal bool IsCurrentVelocityAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.VelocityAuthorityVersion == authorityVersion; internal bool IsCurrentVelocityAuthority( RuntimeEntityRecord canonical, ulong authorityVersion) => _directory.IsCurrent(canonical) && canonical.VelocityAuthorityVersion == authorityVersion; internal bool IsCurrentMovementAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.MovementAuthorityVersion == authorityVersion; internal bool IsCurrentObjDescAuthority( LiveEntityRecord record, ulong authorityVersion) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && current.ObjDescAuthorityVersion == authorityVersion; /// /// Copies the canonical loaded root-object workset used by retail /// CPhysics::UseTime. The record reference is the incarnation token; /// callers must revalidate it after callbacks that can delete or rebucket. /// internal void CopySpatialRootObjectRecordsTo(List destination) { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); _physics.CopySpatialRootsTo(_spatialRootCanonicalScratch); for (int index = 0; index < _spatialRootCanonicalScratch.Count; index++) { RuntimeEntityRecord canonical = _spatialRootCanonicalScratch[index]; if (_projections.TryGet( canonical, out LiveEntityRecord? current) && HasSpatialRuntimeProjection(current)) { destination.Add(current); } } } internal bool IsCurrentSpatialRootObject(LiveEntityRecord record) => IsCurrentRecord(record) && _physics.IsSpatialRoot(record.Canonical) && HasSpatialRuntimeProjection(record); internal bool IsCurrentSpatialAnimation( LiveEntityRecord record, ILiveEntityAnimationRuntime animation) => IsCurrentSpatialRootObject(record) && ReferenceEquals(record.AnimationRuntime, animation) && record.ProjectionKey is { } key && _spatialAnimations.TryGetValue(key, out var indexed) && ReferenceEquals(indexed, animation); internal bool IsCurrentSpatialAnimation( uint localEntityId, ILiveEntityAnimationRuntime animation) => _projections.TryGetByLocalId(localEntityId, out LiveEntityRecord record) && IsCurrentSpatialAnimation(record, animation); /// /// Returns whether is still the canonical /// animation component of this logical entity incarnation. Motion /// completion belongs to that CPhysicsObj/PartArray lifetime, not to the /// transient render-bucket projection used by per-frame presentation. /// internal bool IsCurrentAnimationOwner( LiveEntityRecord record, ILiveEntityAnimationRuntime animation) => IsCurrentRecord(record) && ReferenceEquals(record.AnimationRuntime, animation); /// /// Copies the currently spatial animation owners through the concrete /// dictionary enumerator. This keeps per-frame traversal allocation-free; /// exposing the workset as /// would box its enumerator at a hot call site. /// internal void CopySpatialAnimationRuntimesTo( List> destination) where TAnimation : class, ILiveEntityAnimationRuntime { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach ((RuntimeEntityKey key, ILiveEntityAnimationRuntime animation) in _spatialAnimations) { if (animation is TAnimation typed) { destination.Add( new KeyValuePair( key.LocalEntityId, typed)); } } } internal void CopySpatialAnimationLocalIdsTo(HashSet destination) { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); foreach (RuntimeEntityKey key in _spatialAnimations.Keys) destination.Add(key.LocalEntityId); } /// /// Commits retail CPhysicsObj::set_velocity (0x005113F0) to the /// canonical body for this exact incarnation. The body and the retained /// object clock are one CPhysicsObj: a non-Static inactive-to-active edge /// rebases both together, while Static preserves its prior Active state. /// internal bool TryCommitAuthoritativeVelocity( LiveEntityRecord record, PhysicsBody body, Vector3 velocity, double currentTime) => TryCommitAuthoritativeVectorCore( record, body, velocity, angularVelocity: null, currentTime); /// /// Commits the paired retail VectorUpdate writes /// (set_velocity, then set_omega) to one canonical body. /// Validation is atomic: malformed wire vectors cannot partially mutate /// the live object. /// internal bool TryCommitAuthoritativeVector( LiveEntityRecord record, PhysicsBody body, Vector3 velocity, Vector3 angularVelocity, double currentTime) => TryCommitAuthoritativeVectorCore( record, body, velocity, angularVelocity, currentTime); private bool TryCommitAuthoritativeVectorCore( LiveEntityRecord record, PhysicsBody body, Vector3 velocity, Vector3? angularVelocity, double currentTime) { ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(body); bool ProjectionIsCurrent() => _projections.TryGetCurrent( record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && ReferenceEquals(current.PhysicsBody, body); return _physics.TryCommitAuthoritativeVector( record.Canonical, body, velocity, angularVelocity, currentTime, ProjectionIsCurrent); } /// /// Copies the currently spatial remote-motion owners into caller-owned /// reusable storage. The record reference is the incarnation token: a /// delete and GUID reuse cannot make an older snapshot address the newer /// object. /// internal void CopySpatialRemoteMotionRecordsTo(List destination) { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); _physics.CopySpatialRemotesTo(_spatialRemoteCanonicalScratch); for (int index = 0; index < _spatialRemoteCanonicalScratch.Count; index++) { RuntimeEntityRecord canonical = _spatialRemoteCanonicalScratch[index]; if (_projections.TryGet( canonical, out LiveEntityRecord? record) && HasSpatialRuntimeProjection(record)) { destination.Add(record); } } } internal bool IsCurrentSpatialRemoteMotion( LiveEntityRecord record, IRuntimeRemoteMotion runtime) => IsCurrentRecord(record) && _physics.IsSpatialRemote(record.Canonical, runtime) && HasSpatialRuntimeProjection(record); /// /// Copies only projectile owners that currently have a loaded world-cell /// projection. Pending projectiles retain their logical runtime but impose /// no per-frame scan cost. /// internal void CopySpatialProjectileRecordsTo(List destination) { ArgumentNullException.ThrowIfNull(destination); destination.Clear(); _physics.CopySpatialProjectilesTo(_spatialRootCanonicalScratch); for (int i = 0; i < _spatialRootCanonicalScratch.Count; i++) { RuntimeEntityRecord canonical = _spatialRootCanonicalScratch[i]; if (_projections.TryGet(canonical, out LiveEntityRecord? record) && HasSpatialRuntimeProjection(record)) { destination.Add(record); } } } internal bool IsCurrentSpatialProjectile( LiveEntityRecord record, IRuntimeProjectile runtime) => IsCurrentRecord(record) && _physics.IsSpatialProjectile(record.Canonical, runtime) && HasSpatialRuntimeProjection(record); public bool IsHidden(uint serverGuid) => _projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) && (record.FinalPhysicsState & PhysicsStateFlags.Hidden) != 0; /// /// Claims the one logical top-level spawn notification for this object /// incarnation. Spatial re-entry must restore presentation without replaying /// plugin/event registration. An attached-only child claims nothing until it /// first becomes a top-level world projection. /// public bool TryMarkWorldSpawnPublished(uint serverGuid) { if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record) || record.WorldEntity is null || record.ProjectionKind is not LiveEntityProjectionKind.World || record.WorldSpawnPublished) return false; record.WorldSpawnPublished = true; return true; } internal bool TryMarkInitialHydrationCompleted( LiveEntityRecord expectedRecord, ulong expectedCreateIntegrationVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!IsCurrentRecord(expectedRecord) || expectedRecord.CreateIntegrationVersion != expectedCreateIntegrationVersion || expectedRecord.WorldEntity is null || !expectedRecord.ResourcesRegistered) { return false; } expectedRecord.InitialHydrationCompleted = true; return true; } internal bool TryMarkCreateProjectionSynchronizationPending( LiveEntityRecord expectedRecord, ulong expectedCreateIntegrationVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!IsCurrentCreateIntegration( expectedRecord, expectedCreateIntegrationVersion)) { return false; } expectedRecord.CreateProjectionSynchronizationPending = true; return true; } internal bool TryCompleteCreateProjectionSynchronization( LiveEntityRecord expectedRecord, ulong expectedCreateIntegrationVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!IsCurrentCreateIntegration( expectedRecord, expectedCreateIntegrationVersion)) { return false; } expectedRecord.CreateProjectionSynchronizationPending = false; return true; } internal bool TryBeginProjectionHydration( LiveEntityRecord expectedRecord, ulong? expectedCreateIntegrationVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!IsCurrentRecord(expectedRecord) || (expectedCreateIntegrationVersion is { } expected && expectedRecord.CreateIntegrationVersion != expected)) { return false; } if (expectedRecord.ProjectionHydrationInProgress) { if (expectedRecord.CreateIntegrationVersion != expectedRecord.ProjectionHydrationCreateVersion) { expectedRecord.ProjectionHydrationRetryRequested = true; } return false; } expectedRecord.ProjectionHydrationInProgress = true; expectedRecord.ProjectionHydrationCreateVersion = expectedRecord.CreateIntegrationVersion; expectedRecord.ProjectionHydrationRetryRequested = false; return true; } internal bool EndProjectionHydration(LiveEntityRecord expectedRecord) { ArgumentNullException.ThrowIfNull(expectedRecord); bool retry = IsCurrentRecord(expectedRecord) && (expectedRecord.ProjectionHydrationRetryRequested || expectedRecord.CreateIntegrationVersion != expectedRecord.ProjectionHydrationCreateVersion); if (retry) { // Persist the exact replay obligation at the point version drift // is observed. The stale projection body may be unwinding through // an exception and therefore never reach the controller's next // retry-loop iteration. expectedRecord.CreateProjectionSynchronizationPending = true; } expectedRecord.ProjectionHydrationInProgress = false; expectedRecord.ProjectionHydrationCreateVersion = 0UL; expectedRecord.ProjectionHydrationRetryRequested = false; return retry; } internal bool TryBeginAppearanceHydration( LiveEntityRecord expectedRecord, ulong expectedObjDescAuthorityVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!expectedRecord.AppearanceProjectionSynchronizationPending || !IsCurrentObjDescAuthority( expectedRecord, expectedObjDescAuthorityVersion)) { return false; } expectedRecord.AppearanceProjectionSynchronizationPending = true; if (expectedRecord.AppearanceHydrationInProgress) { expectedRecord.AppearanceHydrationRetryRequested = true; return false; } expectedRecord.AppearanceHydrationInProgress = true; expectedRecord.AppearanceHydrationVersion = expectedObjDescAuthorityVersion; expectedRecord.AppearanceHydrationRetryRequested = false; return true; } internal bool EndAppearanceHydration(LiveEntityRecord expectedRecord) { ArgumentNullException.ThrowIfNull(expectedRecord); bool retry = IsCurrentRecord(expectedRecord) && (expectedRecord.AppearanceHydrationRetryRequested || expectedRecord.ObjDescAuthorityVersion != expectedRecord.AppearanceHydrationVersion); expectedRecord.AppearanceHydrationInProgress = false; expectedRecord.AppearanceHydrationVersion = 0UL; expectedRecord.AppearanceHydrationRetryRequested = false; return retry; } internal bool TryCompleteAppearanceProjectionSynchronization( LiveEntityRecord expectedRecord, ulong expectedObjDescAuthorityVersion) { ArgumentNullException.ThrowIfNull(expectedRecord); if (!expectedRecord.AppearanceProjectionSynchronizationPending || !IsCurrentObjDescAuthority( expectedRecord, expectedObjDescAuthorityVersion)) { return false; } expectedRecord.AppearanceProjectionSynchronizationPending = false; return true; } internal bool IsCurrentCreateIntegration( LiveEntityRecord expectedRecord, ulong expectedCreateIntegrationVersion) => IsCurrentRecord(expectedRecord) && expectedRecord.CreateIntegrationVersion == expectedCreateIntegrationVersion; /// /// Retires only the graphical sidecar for an exact Runtime-owned /// incarnation. The Runtime generation-reset transaction retains and /// completes the canonical tombstone after this acknowledgement returns. /// internal void RetireGenerationProjection( RuntimeEntityRecord canonical) { ArgumentNullException.ThrowIfNull(canonical); if (_isClearing || _isRegisteringResources) { throw new InvalidOperationException( _isClearing ? "Live entity projection teardown is already in progress." : "Live entity projection teardown cannot begin inside atomic resource registration."); } LiveEntityRecord? record = null; if (_projections.TryGet(canonical, out LiveEntityRecord active)) { if (!_projections.RemoveActive(active)) { throw new InvalidOperationException( $"Exact App projection for 0x{canonical.ServerGuid:X8}/{canonical.Incarnation} could not be retired during generation reset."); } _projections.RetainTeardown(active); record = active; } else if (_projections.TryGetTeardown( canonical, out LiveEntityRecord retained)) { record = retained; } if (record is null) return; _isClearing = true; try { TearDownRecord(record, completeCanonical: false); _projections.ReleaseTeardown(record); } finally { _isClearing = false; } } /// /// Completes the App-only projection boundary after Runtime has retired /// every exact canonical incarnation. No canonical owner is mutated here. /// internal void CompleteGenerationProjectionRetirement() { if (_projections.ActiveCount != 0 || _projections.TeardownCount != 0) { throw new InvalidOperationException( "Live entity projection storage has not converged at the generation-reset boundary."); } _spatialAnimations.Clear(); _physics.ClearSpatialWorksets(); _projections.ClearConverged(); _spatial.ClearLiveEntityLifetimeState(); _sessionClearPendingFinalization = false; } public void Clear() { if (_isClearing || _isRegisteringResources) { throw new InvalidOperationException( _isClearing ? "Live entity session teardown is already in progress." : "Live entity session teardown cannot begin inside atomic resource registration."); } _isClearing = true; _sessionClearPendingFinalization = true; IReadOnlyList retiredCanonicals = _entityObjects.BeginSessionClear(); List? failures = null; try { foreach (RuntimeEntityRecord canonical in retiredCanonicals) { if (_projections.TryGet( canonical, out LiveEntityRecord? record)) { if (!_projections.RemoveActive(record)) { throw new InvalidOperationException( $"Exact App projection for 0x{record.ServerGuid:X8}/{record.Generation} could not be retired during session clear."); } RetainTeardownRecord(record); } else { TearDownCanonicalOnly(canonical); } } foreach (LiveEntityRecord record in _projections.TeardownRecords.ToArray()) { if (!_projections.IsRetainedTeardown(record)) { continue; } // A re-entrant Clear can observe the tombstone currently being // unregistered by its caller. Leave it queued; the outer // teardown's ReleaseTeardownRecord finalizes the session once // it unwinds successfully. if (record.TeardownInProgress) continue; try { TearDownRecord(record); ReleaseTeardownRecord(record); } catch (Exception error) { (failures ??= new List()).Add(error); } } CompleteSessionClearIfConverged(); } finally { _isClearing = false; } if (failures is not null) throw new AggregateException("One or more live entities failed session teardown.", failures); } /// /// Drains teardown tombstones whose first unregister attempt failed. This /// runs on the update thread after the callback stack that deferred the /// removal has unwound; successful steps are not replayed. /// public int RetryPendingTeardowns() { if (_projections.TeardownCount == 0 || _isClearing || _isRegisteringResources || _logicalTeardownDepth != 0) return 0; int completed = 0; List? failures = null; foreach (LiveEntityRecord record in _projections.TeardownRecords.ToArray()) { if (!_projections.IsRetainedTeardown(record)) { continue; } if (record.TeardownInProgress) continue; _logicalTeardownDepth++; try { try { TearDownRecord(record); ReleaseTeardownRecord(record); completed++; if (!_directory.TryGetActive(record.ServerGuid, out _) && !HasPendingTeardown(record.ServerGuid) && !_directory.TryGetSnapshot(record.ServerGuid, out _)) { _spatial.ForgetLiveEntity(record.ServerGuid); } } catch (Exception error) { (failures ??= new List()).Add(error); } } finally { _logicalTeardownDepth--; } } if (failures is not null) throw new AggregateException("One or more pending live-entity teardowns failed again.", failures); return completed; } private void RefreshRecord( uint guid, WorldSession.EntitySpawn accepted, bool refreshPosition = false) { if (!_directory.TryGetActive(guid, out RuntimeEntityRecord canonical)) return; _directory.RefreshSnapshot(canonical, accepted, refreshPosition); } private ulong AdvanceLifetimeMutation(uint serverGuid) => _directory.AdvanceLifetimeMutation(serverGuid); private ulong CurrentLifetimeMutation(uint serverGuid) => _directory.CurrentLifetimeMutation(serverGuid); private static RuntimeEntityKey RequireProjectionKey( LiveEntityRecord record) => record.ProjectionKey ?? record.Canonical.Key ?? throw new InvalidOperationException( $"Live entity 0x{record.ServerGuid:X8}/{record.Generation} has no materialized projection key."); private bool IsCurrentProjectionOperation( uint serverGuid, LiveEntityRecord record, ulong projectionOperation) => _projections.TryGetCurrent(serverGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record) && record.ProjectionMutationVersion == projectionOperation; private static void ThrowAfterCommittedProjectionChange( uint serverGuid, Exception? spatialNotificationFailure, Exception? runtimeNotificationFailure) { if (spatialNotificationFailure is not null && runtimeNotificationFailure is not null) { throw new AggregateException( $"Projection change for live entity 0x{serverGuid:X8} committed, but spatial and runtime observers failed.", spatialNotificationFailure, runtimeNotificationFailure); } Exception? failure = spatialNotificationFailure ?? runtimeNotificationFailure; if (failure is not null) ExceptionDispatchInfo.Capture(failure).Throw(); } private void AcknowledgeCelllessCanonicalCommit( RuntimeEntityRecord canonical) { if (!_directory.IsCurrent(canonical) || !_projections.TryGet( canonical, out LiveEntityRecord? record)) { return; } SynchronizePhysicsBodyActiveState(record); RefreshSpatialRuntimeIndexes(record); } internal bool IsCurrentRecord(LiveEntityRecord record) => _projections.TryGetCurrent(record.ServerGuid, out LiveEntityRecord? current) && ReferenceEquals(current, record); private static bool HasSpatialRuntimeProjection(LiveEntityRecord record) => record.WorldEntity is not null && record.ProjectionKind is LiveEntityProjectionKind.World && record.IsSpatiallyProjected && record.IsSpatiallyVisible && record.FullCellId != 0; private void RefreshSpatialRuntimeIndexes(LiveEntityRecord record) { bool current = IsCurrentRecord(record); bool spatial = current && HasSpatialRuntimeProjection(record); if (record.ProjectionKey is not { } key) { if (record.WorldEntity is not null) { throw new InvalidOperationException( $"Materialized live entity 0x{record.ServerGuid:X8}/{record.Generation} has no exact projection key."); } return; } _physics.AcknowledgeSpatialProjection(record.Canonical, spatial); RefreshSpatialPresentationIndexes(record, current, spatial, key); } private void RefreshSpatialPresentationIndexes(LiveEntityRecord record) { bool current = IsCurrentRecord(record); bool spatial = current && HasSpatialRuntimeProjection(record); if (record.ProjectionKey is not { } key) { if (record.WorldEntity is not null) { throw new InvalidOperationException( $"Materialized live entity 0x{record.ServerGuid:X8}/{record.Generation} has no exact projection key."); } return; } RefreshSpatialPresentationIndexes(record, current, spatial, key); } private void RefreshSpatialPresentationIndexes( LiveEntityRecord record, bool current, bool spatial, RuntimeEntityKey key) { if (record.WorldEntity is not null) { if (spatial && record.AnimationRuntime is { } animation) { _spatialAnimations[key] = animation; } else if (current || (record.AnimationRuntime is { } retainedAnimation && _spatialAnimations.TryGetValue(key, out var indexedAnimation) && ReferenceEquals(indexedAnimation, retainedAnimation))) { _spatialAnimations.Remove(key); } } } private void RemoveSpatialRuntimeIndexes(LiveEntityRecord record) { if (record.ProjectionKey is not { } key) return; _physics.RemoveSpatialProjection(record.Canonical); if (record.WorldEntity is not null && _spatialAnimations.TryGetValue(key, out var indexedAnimation) && ReferenceEquals(indexedAnimation, record.AnimationRuntime)) { _spatialAnimations.Remove(key); } } private void OnSpatialVisibilityChanged(RuntimeEntityKey key, bool visible) { // GpuWorldState serializes re-entrant visibility callbacks. A callback // may delete an incarnation and materialize the same server GUID before // an older queued edge drains. Apply an edge only while it still // matches current spatial truth; otherwise it belongs to the displaced // projection and must not mutate the replacement generation. if (_spatial.IsLiveEntityProjectionResident(key) != visible) return; if (!_projections.TryGet( key, out LiveEntityRecord? record) || record.WorldEntity is not { } entity || entity.Id != key.LocalEntityId) return; uint serverGuid = record.ServerGuid; bool wasVisible = record.IsSpatiallyVisible; if (RequireProjectionKey(record) != key) return; if (_presentationOnlySpatialMutationDepth.ContainsKey(key)) { record.IsSpatiallyVisible = visible; RefreshSpatialPresentationIndexes(record); RefreshPresentation(record); return; } bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical); record.IsSpatiallyVisible = visible; bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World && record.IsSpatiallyProjected && visible && record.FullCellId != 0; if (_rebucketingGuid != serverGuid && wasOrdinaryRoot != isOrdinaryRoot) { if (isOrdinaryRoot) record.ResetObjectClockForEnterWorld( (record.FinalPhysicsState & PhysicsStateFlags.Static) != 0); else record.SuspendObjectClock(); SynchronizePhysicsBodyActiveState(record); } RefreshSpatialRuntimeIndexes(record); RefreshPresentation(record); if (_rebucketingGuid != serverGuid && wasVisible != visible) PublishProjectionVisibilityChanged(record, visible); } private void OnRuntimePhysicsCellCommitted( RuntimePhysicsCellCommit commit) { if (commit.Record.SpatialAuthorityVersion != commit.SpatialAuthorityVersion || !_directory.IsCurrent(commit.Record) || !_projections.TryGet( commit.Record, out LiveEntityRecord? projection) || projection.WorldEntity is null) { return; } RebucketLiveEntity( commit.Record.ServerGuid, commit.FullCellId); } private static void SynchronizePhysicsBodyActiveState( LiveEntityRecord record) { if (record.PhysicsBody is not { } body) return; if (record.ObjectClock.IsActive) body.TransientState |= TransientStateFlags.Active; else body.TransientState &= ~TransientStateFlags.Active; } private void PublishProjectionVisibilityChanged(LiveEntityRecord record, bool visible) { Delegate[] subscribers = ProjectionVisibilityChanged?.GetInvocationList() ?? Array.Empty(); List? failures = null; for (int i = 0; i < subscribers.Length; i++) { try { ((Action)subscribers[i])(record, visible); } catch (Exception error) { (failures ??= new List()).Add(error); } } if (failures is not null) { throw new AggregateException( $"One or more projection observers failed for live entity 0x{record.ServerGuid:X8}.", failures); } } private void RefreshPresentation(LiveEntityRecord record) { if (record.WorldEntity is not { } entity) return; PhysicsStateFlags state = record.FinalPhysicsState; bool residenceVisible = record.MaterializationResidence is not LiveEntityMaterializationResidence.AwaitRuntimePlacement || record.IsSpatiallyProjected; entity.IsDrawVisible = residenceVisible && (state & (PhysicsStateFlags.NoDraw | PhysicsStateFlags.Hidden)) == 0; bool interactionVisible = record.IsSpatiallyVisible && record.ProjectionKind is LiveEntityProjectionKind.World && (state & PhysicsStateFlags.Hidden) == 0; if (interactionVisible) _projections.SetVisible(record, true); else _projections.SetVisible(record, false); } private void RetainTeardownRecord(LiveEntityRecord record) { _directory.RetainTeardown(record.Canonical); _projections.RetainTeardown(record); } private Exception? RetirePriorProjection( RuntimeEntityRecord canonical) { if (!_projections.TryGet( canonical, out LiveEntityRecord? projection)) { return _entityObjects.RetireCanonicalOnly(canonical); } if (!_projections.RemoveActive(projection)) { return new InvalidOperationException( $"Exact App projection for 0x{canonical.ServerGuid:X8}/{canonical.Incarnation} could not be retired."); } RetainTeardownRecord(projection); _logicalTeardownDepth++; try { try { TearDownRecord(projection); ReleaseTeardownRecord(projection); return null; } catch (Exception error) { return error; } } finally { _logicalTeardownDepth--; } } private void TearDownCanonicalOnly(RuntimeEntityRecord canonical) { Exception? failure = _entityObjects.RetireCanonicalOnly(canonical); if (failure is not null) ExceptionDispatchInfo.Capture(failure).Throw(); } private void ReleaseTeardownRecord(LiveEntityRecord record) { _projections.ReleaseTeardown(record); _directory.ReleaseTeardown(record.Canonical); CompleteSessionClearIfConverged(); } private bool HasPendingTeardown(uint serverGuid) => _directory.HasPendingTeardown(serverGuid); private void CompleteSessionClearIfConverged() { if (!_sessionClearPendingFinalization || !_entityObjects.CompleteSessionClearIfConverged()) { return; } _spatialAnimations.Clear(); _physics.ClearSpatialWorksets(); _projections.ClearConverged(); _spatial.ClearLiveEntityLifetimeState(); _sessionClearPendingFinalization = false; } private void TearDownRecord( LiveEntityRecord record, bool completeCanonical = true) { if (record.TeardownInProgress) throw new InvalidOperationException( $"Live entity 0x{record.ServerGuid:X8} teardown is already in progress."); record.TeardownInProgress = true; List? failures = null; bool TryCleanup(Action cleanup) { try { cleanup(); return true; } catch (Exception error) { (failures ??= new List()).Add(error); return false; } } try { // Remove frame-workset membership before arbitrary callbacks. The // logical component dictionaries and WorldEntity remain intact as // a retryable tombstone until every external cleanup acknowledges // success. RemoveSpatialRuntimeIndexes(record); _physics.ClearProjectile(record.Canonical); if (!record.RuntimeComponentsTeardownCompleted) { record.RuntimeComponentsTeardownCompleted = TryCleanup(() => _runtimeComponentLifecycle.TearDown(record)); } if (record.WorldEntity is { } entity) { if (!record.SpatialProjectionTeardownCompleted) { record.SpatialProjectionTeardownCompleted = TryCleanup(() => _spatial.RemoveLiveEntityProjection(entity)); } if (record.ResourcesRegistered && TryCleanup(() => _resources.Unregister(entity))) { record.ResourcesRegistered = false; } } if (failures is not null) { throw new AggregateException( $"Live entity 0x{record.ServerGuid:X8} teardown failed.", failures); } if (record.WorldEntity is not null) _ = RequireProjectionKey(record); if (completeCanonical) { _entityObjects.CompleteProjectionRetirement( record.Canonical); } record.AnimationRuntime = null; record.EffectProfile = null; record.IsSpatiallyProjected = false; record.IsSpatiallyVisible = false; record.WorldSpawnPublished = false; record.InitialHydrationCompleted = false; record.CreateProjectionSynchronizationPending = false; record.ProjectionHydrationInProgress = false; record.ProjectionHydrationCreateVersion = 0UL; record.ProjectionHydrationRetryRequested = false; record.AppearanceProjectionSynchronizationPending = false; record.AppearanceHydrationInProgress = false; record.AppearanceHydrationVersion = 0UL; record.AppearanceHydrationRetryRequested = false; record.WorldEntity = null; } finally { record.TeardownInProgress = false; } } }