refactor(runtime): own per-session physics simulation

Move the sole PhysicsEngine, production cache, collision admissions, canonical bodies and hosts, remote components, ordinary/remote worksets, simulation, cell commits, and shadow synchronization under RuntimeEntityObjectLifetime. Keep App as the prepared-asset, animation-input, and render-projection adapter while preserving the named-retail update and collision order.

Add exact-incarnation, object-clock, callback-reentrancy, GUID-reuse, two-runtime isolation, source ownership, collision publication, and graphical projection coverage. Release build and the complete 8,588-test solution pass.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-26 13:39:57 +02:00
parent 0dc3bfdeff
commit 7e6033d0ad
39 changed files with 3685 additions and 1722 deletions

View file

@ -20,65 +20,6 @@ public interface ILiveEntityAnimationRuntime
uint CurrentMotion { get; }
}
/// <summary>Remote motion state owned by a live object.</summary>
public interface ILiveEntityRemoteMotionRuntime
{
PhysicsBody Body { get; }
}
/// <summary>
/// Optional seam for a remote-motion component that reads the exact
/// incarnation's canonical movement-manager host from
/// <see cref="LiveEntityRecord"/>.
/// </summary>
public interface ILiveEntityPhysicsHostConsumer
{
void BindPhysicsHost(Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> read);
}
/// <summary>
/// Atomic composition seam for a component that consumes both canonical host
/// and cell identity. Implementations validate every delegate before
/// publishing any of them, so a failed bind leaves the component reusable.
/// </summary>
public interface ILiveEntityCanonicalRuntimeConsumer
{
void BindCanonicalRuntime(
Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> readPhysicsHost,
Func<uint> readCell,
Action<uint> writeCell);
}
/// <summary>
/// Optional seam for a remote-motion component that consumes the canonical
/// full-cell identity owned by <see cref="LiveEntityRecord"/>. The runtime
/// binds this for every production remote, regardless of whether projectile
/// classification arrives before or after the MovementManager.
/// </summary>
public interface ILiveEntityCanonicalCellConsumer
{
void BindCanonicalCell(Func<uint> read, Action<uint> write);
}
/// <summary>
/// Remote CPhysicsObj state required to finish a retail MoveOrTeleport
/// placement. A same-incarnation runtime replacement may wrap the same body,
/// but it must preserve this complete placement contract.
/// </summary>
public interface ILiveEntityRemotePlacementRuntime :
ILiveEntityRemoteMotionRuntime,
ILiveEntityCanonicalCellConsumer
{
uint CellId { get; set; }
bool Airborne { get; set; }
Vector3 LastServerPosition { get; set; }
double LastServerPositionTime { get; set; }
Vector3 LastShadowSyncPosition { get; set; }
Quaternion LastShadowSyncOrientation { get; set; }
void HitGround();
void LeaveGround();
}
/// <summary>Projectile physics state owned by a live object.</summary>
public interface ILiveEntityProjectileRuntime
{
@ -296,14 +237,26 @@ public sealed class LiveEntityRecord
set => _directory.SetPhysicsBodyAcquisitionInProgress(Canonical, value);
}
public ILiveEntityAnimationRuntime? AnimationRuntime { get; internal set; }
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime { get; internal set; }
internal bool RemoteMotionBindingInProgress { get; set; }
public ILiveEntityRemoteMotionRuntime? RemoteMotionRuntime
{
get => Canonical.RemoteMotion;
internal set => _directory.SetRemoteMotion(Canonical, value);
}
internal bool RemoteMotionBindingInProgress
{
get => Canonical.RemoteMotionBindingInProgress;
set => _directory.SetRemoteMotionBindingInProgress(Canonical, value);
}
public AcDream.Core.Physics.Motion.IPhysicsObjHost? PhysicsHost
{
get => Canonical.PhysicsHost;
internal set => _directory.SetPhysicsHost(Canonical, value);
}
internal bool RequiresRemotePlacementRuntime { get; set; }
internal bool RequiresRemotePlacementRuntime
{
get => Canonical.RequiresRemotePlacementRuntime;
set => _directory.SetRequiresRemotePlacementRuntime(Canonical, value);
}
public ILiveEntityProjectileRuntime? ProjectileRuntime { get; internal set; }
public ILiveEntityEffectProfile? EffectProfile { get; internal set; }
public bool ResourcesRegistered { get; internal set; }
@ -435,6 +388,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
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
@ -442,12 +396,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
// projection. Hidden and Frozen remain members: their state controls what
// each retail update path advances after the O(active) lookup.
private readonly Dictionary<RuntimeEntityKey, ILiveEntityAnimationRuntime> _spatialAnimations = new();
private readonly Dictionary<RuntimeEntityKey, ILiveEntityRemoteMotionRuntime> _spatialRemoteMotion = new();
private readonly Dictionary<RuntimeEntityKey, ILiveEntityProjectileRuntime> _spatialProjectiles = new();
// Retail CPhysics::UseTime walks the ordinary object table, not a render-
// animation component table. This index is the loaded/cell-backed root
// workset; component-specific indexes remain lookup accelerators only.
private readonly Dictionary<RuntimeEntityKey, LiveEntityRecord> _spatialRootObjects = new();
private readonly List<RuntimeEntityRecord> _spatialRootCanonicalScratch = new();
private readonly List<RuntimeEntityRecord> _spatialRemoteCanonicalScratch = new();
private bool _isClearing;
private bool _sessionClearPendingFinalization;
private bool _isRegisteringResources;
@ -492,8 +443,10 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_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;
@ -506,6 +459,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_projections.VisibleRecords;
internal IReadOnlyCollection<RuntimeEntityRecord> CanonicalRecords =>
_directory.ActiveRecords;
internal RuntimePhysicsState Physics => _physics;
public IReadOnlyDictionary<uint, WorldSession.EntitySpawn> Snapshots => _directory.Snapshots;
internal int AnimationRuntimeCount
{
@ -522,9 +476,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
}
}
internal int SpatialAnimationRuntimeCount => _spatialAnimations.Count;
internal int SpatialRemoteMotionRuntimeCount => _spatialRemoteMotion.Count;
internal int SpatialRemoteMotionRuntimeCount => _physics.SpatialRemoteCount;
internal int SpatialProjectileRuntimeCount => _spatialProjectiles.Count;
internal int SpatialRootObjectCount => _spatialRootObjects.Count;
internal int SpatialRootObjectCount => _physics.SpatialRootCount;
public ParentAttachmentState ParentAttachments => _directory.ParentAttachments;
internal bool TryGetCanonical(
@ -798,10 +752,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
RuntimeEntityKey key = RequireProjectionKey(record);
bool wasProjected = record.IsSpatiallyProjected;
bool wasVisible = record.IsSpatiallyVisible;
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
key,
out LiveEntityRecord? indexedRoot)
&& ReferenceEquals(indexedRoot, record);
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
@ -936,10 +887,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return false;
RuntimeEntityKey key = RequireProjectionKey(record);
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
key,
out LiveEntityRecord? rootRecord)
&& ReferenceEquals(rootRecord, record);
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
ulong objectClockEpoch = record.ObjectClockEpoch;
ulong projectionOperation = ++record.ProjectionMutationVersion;
Exception? spatialNotificationFailure = null;
@ -1436,43 +1384,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
throw new InvalidOperationException(
$"Cannot acquire physics body before live entity 0x{serverGuid:X8} is materialized.");
}
if (record.PhysicsBody is { } retained)
return retained;
if (record.PhysicsBodyAcquisitionInProgress)
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} physics-body acquisition is already in progress.");
}
record.PhysicsBodyAcquisitionInProgress = true;
try
{
PhysicsBody candidate = factory(record)
?? throw new InvalidOperationException("Physics-body factory returned null.");
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? current)
|| !ReferenceEquals(current, record)
|| current.WorldEntity is null)
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} changed incarnation during physics-body acquisition.");
}
if (record.PhysicsBody is { } concurrentlyBound)
{
if (ReferenceEquals(concurrentlyBound, candidate))
return concurrentlyBound;
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} acquired two physics bodies within one incarnation.");
}
record.PhysicsBody = candidate;
candidate.State = record.FinalPhysicsState;
SynchronizePhysicsBodyActiveState(record);
return candidate;
}
finally
{
record.PhysicsBodyAcquisitionInProgress = false;
}
bool ProjectionIsCurrent() =>
_projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? current)
&& ReferenceEquals(current, record)
&& current.WorldEntity is not null;
return _physics.GetOrCreatePhysicsBody(
record.Canonical,
_ => factory(record),
ProjectionIsCurrent);
}
public void SetRemoteMotionRuntime(uint serverGuid, ILiveEntityRemoteMotionRuntime runtime)
@ -1480,122 +1401,17 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ArgumentNullException.ThrowIfNull(runtime);
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record))
throw new InvalidOperationException($"Cannot bind remote motion before live entity 0x{serverGuid:X8} exists.");
if (record.RemoteMotionBindingInProgress)
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} remote-motion binding is already in progress.");
PhysicsBody candidateBody = runtime.Body
?? throw new InvalidOperationException("A remote-motion runtime returned no physics body.");
if (ReferenceEquals(record.RemoteMotionRuntime, runtime))
{
if (!ReferenceEquals(record.PhysicsBody, candidateBody))
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} remote motion changed its canonical physics body.");
}
// Idempotent publication is important when motion and vector
// packets converge on the same runtime in one update turn. The
// component seams are incarnation-bound and must never be rebound.
candidateBody.State = record.FinalPhysicsState;
SynchronizePhysicsBodyActiveState(record);
RefreshSpatialRuntimeIndexes(record);
return;
}
if (record.PhysicsBodyAcquisitionInProgress && record.PhysicsBody is null)
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} cannot bind remote motion during physics-body acquisition.");
if (record.PhysicsBody is { } canonicalBody
&& !ReferenceEquals(canonicalBody, candidateBody))
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} cannot replace its canonical physics body within one incarnation.");
}
if (record.RequiresRemotePlacementRuntime
&& runtime is not ILiveEntityRemotePlacementRuntime)
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} cannot discard its remote placement contract within one incarnation.");
}
// Bind every possibly-throwing exact-incarnation seam before
// publishing any canonical record/index state. An already-bound
// component from an older GUID generation must fail without poisoning
// the replacement record. The callback is arbitrary App code, so the
// exact record, authority epoch, and expected owners are revalidated
// before commit just like resource/physics-body acquisition.
LiveEntityRecord incarnation = record;
ulong sessionVersion = _directory.SessionLifetimeVersion;
ulong lifetimeVersion = CurrentLifetimeMutation(serverGuid);
PhysicsBody? expectedBody = record.PhysicsBody;
ILiveEntityRemoteMotionRuntime? expectedRuntime = record.RemoteMotionRuntime;
bool expectedPlacementContract = record.RequiresRemotePlacementRuntime;
Func<AcDream.Core.Physics.Motion.IPhysicsObjHost?> readPhysicsHost = () =>
_projections.TryGetCurrent(serverGuid, out var current)
&& ReferenceEquals(current, incarnation)
&& ReferenceEquals(current.RemoteMotionRuntime, runtime)
? current.PhysicsHost
: null;
// Reads remain bound to the exact CPhysicsObj incarnation through its
// exit_world notifications. Only writes require active ownership.
Func<uint> readCell = () => incarnation.FullCellId;
Action<uint> writeCell = cellId =>
{
if (cellId != 0
&& _projections.TryGetCurrent(serverGuid, out var current)
&& ReferenceEquals(current, incarnation)
&& ReferenceEquals(current.RemoteMotionRuntime, runtime))
{
RebucketLiveEntity(serverGuid, cellId);
}
};
record.RemoteMotionBindingInProgress = true;
try
{
if (runtime is ILiveEntityCanonicalRuntimeConsumer canonicalConsumer)
{
canonicalConsumer.BindCanonicalRuntime(
readPhysicsHost,
readCell,
writeCell);
}
else
{
bool consumesHost = runtime is ILiveEntityPhysicsHostConsumer;
bool consumesCell = runtime is ILiveEntityCanonicalCellConsumer;
if (consumesHost && consumesCell)
{
throw new InvalidOperationException(
"A remote runtime that consumes both canonical host and cell identity must bind them atomically.");
}
if (runtime is ILiveEntityPhysicsHostConsumer hostConsumer)
hostConsumer.BindPhysicsHost(readPhysicsHost);
if (runtime is ILiveEntityCanonicalCellConsumer cellConsumer)
cellConsumer.BindCanonicalCell(readCell, writeCell);
}
if (_directory.SessionLifetimeVersion != sessionVersion
|| CurrentLifetimeMutation(serverGuid) != lifetimeVersion
|| !_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? current)
|| !ReferenceEquals(current, incarnation)
|| !ReferenceEquals(current.PhysicsBody, expectedBody)
|| !ReferenceEquals(current.RemoteMotionRuntime, expectedRuntime)
|| current.RequiresRemotePlacementRuntime != expectedPlacementContract)
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} changed incarnation or component ownership during remote-motion binding.");
}
record.RequiresRemotePlacementRuntime |=
runtime is ILiveEntityRemotePlacementRuntime;
record.RemoteMotionRuntime = runtime;
record.PhysicsBody = candidateBody;
candidateBody.State = record.FinalPhysicsState;
SynchronizePhysicsBodyActiveState(record);
RefreshSpatialRuntimeIndexes(record);
}
finally
{
record.RemoteMotionBindingInProgress = false;
}
_physics.SetRemoteMotion(
record.Canonical,
runtime,
() => CurrentLifetimeMutation(serverGuid) == lifetimeVersion
&& _projections.TryGetCurrent(
serverGuid,
out LiveEntityRecord? current)
&& ReferenceEquals(current, record));
SynchronizePhysicsBodyActiveState(record);
RefreshSpatialRuntimeIndexes(record);
}
public void InstallPhysicsHost(
@ -1605,26 +1421,23 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ArgumentNullException.ThrowIfNull(expectedRecord);
ArgumentNullException.ThrowIfNull(host);
uint serverGuid = expectedRecord.ServerGuid;
if (host.Id != serverGuid)
throw new ArgumentException("A physics host must match its live entity GUID.", nameof(host));
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| !ReferenceEquals(record, expectedRecord))
{
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} changed incarnation during physics-host installation.");
}
if (record.PhysicsHost is not null)
throw new InvalidOperationException(
$"Live entity 0x{serverGuid:X8} already owns its incarnation-stable physics host.");
record.PhysicsHost = host;
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 LiveEntityRecord? record)
&& record.PhysicsHost is { } existing)
if (_projections.TryGetCurrent(serverGuid, out _)
&& _physics.TryGetPhysicsHost(serverGuid, out var existing))
{
host = existing;
return true;
@ -1656,7 +1469,8 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
if (!_projections.TryGetCurrent(serverGuid, out LiveEntityRecord? record)
|| record.RemoteMotionRuntime is null)
return false;
record.RemoteMotionRuntime = null;
if (!_physics.ClearRemoteMotion(record.Canonical))
return false;
RefreshSpatialRuntimeIndexes(record);
return true;
}
@ -1977,10 +1791,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
ArgumentNullException.ThrowIfNull(destination);
destination.Clear();
foreach ((RuntimeEntityKey key, LiveEntityRecord indexed) in _spatialRootObjects)
_physics.CopySpatialRootsTo(_spatialRootCanonicalScratch);
for (int index = 0;
index < _spatialRootCanonicalScratch.Count;
index++)
{
if (_projections.TryGet(key, out LiveEntityRecord? current)
&& ReferenceEquals(current, indexed)
RuntimeEntityRecord canonical =
_spatialRootCanonicalScratch[index];
if (_projections.TryGet(
canonical,
out LiveEntityRecord? current)
&& HasSpatialRuntimeProjection(current))
{
destination.Add(current);
@ -1990,9 +1810,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
internal bool IsCurrentSpatialRootObject(LiveEntityRecord record) =>
IsCurrentRecord(record)
&& record.ProjectionKey is { } key
&& _spatialRootObjects.TryGetValue(key, out var indexed)
&& ReferenceEquals(indexed, record)
&& _physics.IsSpatialRoot(record.Canonical)
&& HasSpatialRuntimeProjection(record);
internal bool IsCurrentSpatialAnimation(
@ -2169,12 +1987,16 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
ArgumentNullException.ThrowIfNull(destination);
destination.Clear();
foreach ((RuntimeEntityKey key, ILiveEntityRemoteMotionRuntime runtime)
in _spatialRemoteMotion)
_physics.CopySpatialRemotesTo(_spatialRemoteCanonicalScratch);
for (int index = 0;
index < _spatialRemoteCanonicalScratch.Count;
index++)
{
if (_projections.TryGet(key, out LiveEntityRecord? record)
&& ReferenceEquals(record.RemoteMotionRuntime, runtime)
RuntimeEntityRecord canonical =
_spatialRemoteCanonicalScratch[index];
if (_projections.TryGet(
canonical,
out LiveEntityRecord? record)
&& HasSpatialRuntimeProjection(record))
{
destination.Add(record);
@ -2186,10 +2008,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
LiveEntityRecord record,
ILiveEntityRemoteMotionRuntime runtime) =>
IsCurrentRecord(record)
&& ReferenceEquals(record.RemoteMotionRuntime, runtime)
&& record.ProjectionKey is { } key
&& _spatialRemoteMotion.TryGetValue(key, out var indexed)
&& ReferenceEquals(indexed, runtime)
&& _physics.IsSpatialRemote(record.Canonical, runtime)
&& HasSpatialRuntimeProjection(record);
/// <summary>
@ -2623,16 +2442,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return;
}
if (spatial)
{
_spatialRootObjects[key] = record;
}
else if (current
|| (_spatialRootObjects.TryGetValue(key, out var indexedRoot)
&& ReferenceEquals(indexedRoot, record)))
{
_spatialRootObjects.Remove(key);
}
_physics.AcknowledgeSpatialProjection(record.Canonical, spatial);
if (record.WorldEntity is not null)
{
@ -2649,18 +2459,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
}
}
if (spatial && record.RemoteMotionRuntime is { } remote)
{
_spatialRemoteMotion[key] = remote;
}
else if (current
|| (record.RemoteMotionRuntime is { } retainedRemote
&& _spatialRemoteMotion.TryGetValue(key, out var indexedRemote)
&& ReferenceEquals(indexedRemote, retainedRemote)))
{
_spatialRemoteMotion.Remove(key);
}
if (spatial && record.ProjectileRuntime is { } projectile)
{
_spatialProjectiles[key] = projectile;
@ -2679,11 +2477,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
if (record.ProjectionKey is not { } key)
return;
if (_spatialRootObjects.TryGetValue(key, out var indexedRoot)
&& ReferenceEquals(indexedRoot, record))
{
_spatialRootObjects.Remove(key);
}
_physics.RemoveSpatialProjection(record.Canonical);
if (record.WorldEntity is not null
&& _spatialAnimations.TryGetValue(key, out var indexedAnimation)
@ -2692,12 +2486,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_spatialAnimations.Remove(key);
}
if (_spatialRemoteMotion.TryGetValue(key, out var indexedRemote)
&& ReferenceEquals(indexedRemote, record.RemoteMotionRuntime))
{
_spatialRemoteMotion.Remove(key);
}
if (_spatialProjectiles.TryGetValue(key, out var indexedProjectile)
&& ReferenceEquals(indexedProjectile, record.ProjectileRuntime))
{
@ -2725,10 +2513,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
bool wasVisible = record.IsSpatiallyVisible;
if (RequireProjectionKey(record) != key)
return;
bool wasOrdinaryRoot = _spatialRootObjects.TryGetValue(
key,
out LiveEntityRecord? indexedRoot)
&& ReferenceEquals(indexedRoot, record);
bool wasOrdinaryRoot = _physics.IsSpatialRoot(record.Canonical);
record.IsSpatiallyVisible = visible;
bool isOrdinaryRoot = record.ProjectionKind is LiveEntityProjectionKind.World
&& record.IsSpatiallyProjected
@ -2749,6 +2534,25 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
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)
{
@ -2877,9 +2681,8 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
}
_spatialAnimations.Clear();
_spatialRemoteMotion.Clear();
_spatialProjectiles.Clear();
_spatialRootObjects.Clear();
_physics.ClearSpatialWorksets();
_projections.ClearConverged();
_spatial.ClearLiveEntityLifetimeState();
_sessionClearPendingFinalization = false;