refactor(runtime): publish canonical entity object deltas

Issue stable Runtime identities at canonical registration, publish entity and inventory commits through one generation-stamped synchronous stream, and make graphical adapters borrow the same direct views and events as a no-window host. Preserve exact projection teardown and retail mutation order while removing App-side event reconstruction.

Make the hard-recenter ordering fixture independent of the production two-millisecond frame budget so its injected-failure gate is deterministic.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-26 06:42:13 +02:00
parent d3e96ff912
commit ce3ac310d9
23 changed files with 2352 additions and 666 deletions

View file

@ -3,6 +3,7 @@ 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 System.Numerics;
using System.Runtime.ExceptionServices;
@ -377,29 +378,13 @@ public sealed class LiveEntityRecord
internal RetailPhysicsStateTransition ApplyRawPhysicsState(uint rawState) =>
_directory.ApplyRawPhysicsState(Canonical, rawState);
internal void AdvancePositionAuthority() =>
_directory.AdvancePositionAuthority(Canonical);
internal void AdvanceVectorAuthority() =>
_directory.AdvanceVectorAuthority(Canonical);
internal void AdvanceMovementAuthority() =>
_directory.AdvanceMovementAuthority(Canonical);
internal void AdvanceObjDescAuthority()
internal void NoteObjDescProjectionSynchronization()
{
_directory.AdvanceObjDescAuthority(Canonical);
AppearanceProjectionSynchronizationPending = true;
if (AppearanceHydrationInProgress)
AppearanceHydrationRetryRequested = true;
}
internal void AdvanceCreateAuthority() =>
_directory.AdvanceCreateAuthority(Canonical);
internal void SetChildNoDraw(bool noDraw) =>
_directory.SetChildNoDraw(Canonical, noDraw);
/// <summary>
/// Cell used when a streamed landblock asks live objects to restore their
/// spatial projection. Once materialized, the canonical runtime cell wins;
@ -582,150 +567,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
: "A live entity cannot register from inside atomic resource registration.");
}
InboundCreateResult result = _directory.AcceptCreate(incoming);
if (result.Disposition is CreateObjectTimestampDisposition.StaleGeneration)
return new LiveEntityRegistrationResult(
result,
Canonical: null,
Projection: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
ulong sessionVersion = _directory.SessionLifetimeVersion;
ulong operationVersion = AdvanceLifetimeMutation(incoming.Guid);
if (result.Disposition is CreateObjectTimestampDisposition.ExistingGeneration)
RuntimeEntityRegistrationResult registration =
_entityObjects.RegisterEntity(
incoming,
RetirePriorProjection);
RuntimeEntityRecord? canonical = registration.Canonical;
LiveEntityRecord? projection = canonical is null
? null
: _projections.GetCurrentOrDefault(canonical.ServerGuid);
if (projection is not null
&& ReferenceEquals(projection.Canonical, canonical))
{
if (_directory.TryGetActive(
incoming.Guid,
out RuntimeEntityRecord retainedCanonical))
{
_directory.RefreshSnapshot(
retainedCanonical,
result.Snapshot,
refreshPosition: true);
_directory.AdvanceCreateAuthority(retainedCanonical);
_projections.TryGet(
retainedCanonical,
out LiveEntityRecord? retainedProjection);
if (retainedProjection is not null)
RefreshSpatialRuntimeIndexes(retainedProjection);
return new LiveEntityRegistrationResult(
result,
retainedCanonical,
retainedProjection,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
// A failed materialization rollback retains this exact incarnation
// as a teardown tombstone. Do not create a second active record
// with the same (GUID, INSTANCE_TS) while its partial resources are
// still converging; a later retransmit can recover after teardown.
if (_directory.TryGetTeardown(
incoming.Guid,
result.Snapshot.InstanceSequence,
out _))
{
return new LiveEntityRegistrationResult(
result,
Canonical: null,
Projection: null,
false,
false);
}
// Defensive repair for a caller that cleared only the logical map.
// Normal session teardown clears both owners together.
RuntimeEntityRecord recoveredCanonical =
_directory.AddActive(result.Snapshot);
return new LiveEntityRegistrationResult(
result,
recoveredCanonical,
Projection: null,
LogicalRegistrationCreated: true,
ReplacedExistingGeneration: false);
RefreshSpatialRuntimeIndexes(projection);
}
bool replaced = _directory.RemoveActive(
incoming.Guid,
out RuntimeEntityRecord? oldCanonical);
LiveEntityRecord? old = null;
if (oldCanonical is not null
&& _projections.TryGet(oldCanonical, out LiveEntityRecord? projected))
{
old = projected;
if (!_projections.RemoveActive(old))
{
throw new InvalidOperationException(
$"Exact App projection for 0x{incoming.Guid:X8}/{old.Generation} could not be retired.");
}
}
if (result.Disposition is CreateObjectTimestampDisposition.NewGeneration)
ParentAttachments.EndGeneration(incoming.Guid, result.Snapshot.InstanceSequence);
Exception? tearDownFailure = null;
if (old is not null)
{
RetainTeardownRecord(old);
_logicalTeardownDepth++;
try
{
try
{
TearDownRecord(old);
ReleaseTeardownRecord(old);
}
catch (Exception error)
{
tearDownFailure = error;
}
}
finally
{
_logicalTeardownDepth--;
}
}
else if (oldCanonical is not null)
{
TearDownCanonicalOnly(oldCanonical);
}
// Resource callbacks are arbitrary App integration code. Any nested
// create, accepted delete, or session reset advances this epoch. The
// outer packet is then superseded even when the nested action left no
// record/snapshot (delete/reset); reinstalling it would resurrect an
// incarnation after an accepted terminal event.
if (_directory.SessionLifetimeVersion != sessionVersion
|| CurrentLifetimeMutation(incoming.Guid) != operationVersion)
{
if (tearDownFailure is not null)
{
throw new AggregateException(
$"Prior incarnation of live entity 0x{incoming.Guid:X8} failed teardown while its incoming replacement was superseded.",
tearDownFailure);
}
return new LiveEntityRegistrationResult(
SupersededCreateResult(),
_directory.TryGetActive(
incoming.Guid,
out RuntimeEntityRecord currentCanonical)
? currentCanonical
: null,
_projections.GetCurrentOrDefault(incoming.Guid),
false,
replaced,
tearDownFailure);
}
RuntimeEntityRecord canonical = _directory.AddActive(result.Snapshot);
return new LiveEntityRegistrationResult(
result,
registration.Inbound,
canonical,
Projection: null,
true,
replaced,
tearDownFailure);
projection,
registration.LogicalRegistrationCreated,
registration.ReplacedExistingGeneration,
registration.PriorGenerationCleanupFailure);
}
/// <summary>
@ -785,7 +646,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
bool createdSidecar = false;
if (!_projections.TryGet(expectedCanonical, out record))
{
uint localId = _directory.ClaimLocalId(expectedCanonical);
_ = expectedCanonical.LocalEntityId
?? throw new InvalidOperationException(
"Runtime must issue entity identity before App projection.");
try
{
record = _projections.AddMaterializing(expectedCanonical);
@ -800,7 +663,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
record.ProjectionKey = null;
record = null;
}
_directory.ReleaseLocalId(expectedCanonical);
throw;
}
}
@ -833,7 +695,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
_projections.RemoveActive(record);
record.ProjectionKey = null;
_directory.ReleaseLocalId(expectedCanonical);
record = null;
}
throw;
@ -870,14 +731,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
_projections.RemoveActive(record);
record.ProjectionKey = null;
_directory.ReleaseLocalId(expectedCanonical);
record.WorldEntity = null;
record = null;
throw;
}
if (_directory.IsCurrent(expectedCanonical))
_directory.RemoveActive(expectedCanonical);
_entityObjects.RetireAfterProjectionAcquisitionFailure(
expectedCanonical);
if (_projections.RemoveActive(record))
{
RetainTeardownRecord(record);
@ -983,11 +843,24 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
bool visible = _spatial.IsLiveEntityProjectionResident(key);
record.IsSpatiallyVisible = visible;
RefreshPresentation(record);
if ((spatialCellOrLandblockId & 0xFFFFu) != 0xFFFFu)
record.FullCellId = spatialCellOrLandblockId;
record.CanonicalLandblockId = spatialCellOrLandblockId == 0
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
@ -1182,7 +1055,12 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
return false;
}
ClearWorldCell(serverGuid);
if (!_entityObjects.CommitWithdrawal(
record.Canonical,
AcknowledgeCelllessCanonicalCommit))
{
return false;
}
record.WorldEntity.ParentCellId = 0u;
return WithdrawLiveEntityProjection(serverGuid);
}
@ -1218,10 +1096,9 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
if (!_entityObjects.TryAcceptDelete(
delete,
isLocalPlayer,
removeRetainedObject,
out acceptedDelete))
return false;
AdvanceLifetimeMutation(delete.Guid);
ParentAttachments.DeleteGeneration(delete.Guid, delete.InstanceSequence);
// End active gameplay identity before arbitrary callbacks so a
// newer generation can be accepted re-entrantly, but retain the
@ -1229,11 +1106,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
// succeeded. A throwing resource callback can then be retried by
// the same accepted Delete instead of orphaning its owner.
LiveEntityRecord? retainedRegistrationFailure = record;
if (_directory.TryGetActive(
delete.Guid,
out RuntimeEntityRecord activeCanonical)
&& activeCanonical.Incarnation == delete.InstanceSequence
&& _directory.RemoveActive(activeCanonical))
if (acceptedDelete.RetiredCanonical is { } activeCanonical)
{
if (_projections.TryGet(
activeCanonical,
@ -1266,16 +1139,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
if (!retryingAcceptedDelete)
{
if (removeRetainedObject)
try
{
try
{
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
}
catch (Exception error)
{
(failures ??= new List<Exception>()).Add(error);
}
_entityObjects.CompleteAcceptedDelete(acceptedDelete!);
}
catch (Exception error)
{
(failures ??= new List<Exception>()).Add(error);
}
try
@ -1872,95 +1742,44 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_directory.TryGetSnapshot(guid, out spawn);
public bool TryApplyObjDesc(ObjDescEvent.Parsed update, out WorldSession.EntitySpawn accepted)
{
bool applied = _directory.TryApplyObjDesc(update, out accepted);
if (applied
&& _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvanceObjDescAuthority();
else
_directory.AdvanceObjDescAuthority(canonical);
}
return applied;
}
=> _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)
{
bool applied = _directory.TryApplyPickup(update, out accepted);
if (applied
&& _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvancePositionAuthority();
else
_directory.AdvancePositionAuthority(canonical);
ClearWorldCell(canonical);
ParentAttachments.EndChildProjection(update.Guid);
}
return applied;
}
=> _entityObjects.TryApplyPickup(
update,
AcknowledgeCelllessCanonicalCommit,
out accepted);
public bool TryApplyCreateParent(CreateParentUpdate update, out WorldSession.EntitySpawn accepted)
{
bool applied = _directory.TryApplyCreateParent(update, out accepted);
if (applied
&& _directory.TryGetActive(
update.ChildGuid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvancePositionAuthority();
else
_directory.AdvancePositionAuthority(canonical);
}
return applied;
}
=> _entityObjects.TryApplyCreateParent(
update,
acknowledgeProjection: null,
out accepted);
public bool TryApplyParent(ParentEvent.Parsed update, out WorldSession.EntitySpawn accepted)
{
bool applied = _directory.TryApplyParent(update, out accepted);
if (applied
&& _directory.TryGetActive(
update.ChildGuid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvancePositionAuthority();
else
_directory.AdvancePositionAuthority(canonical);
}
return applied;
}
=> _entityObjects.TryApplyParent(
update,
acknowledgeProjection: null,
out accepted);
internal bool CommitStagedParent(
ParentAttachmentRelation relation,
out WorldSession.EntitySpawn accepted)
{
bool committed = _directory.TryCommitParent(
relation.ChildGuid,
relation.ParentGuid,
relation.ParentLocation,
relation.PlacementId,
relation.ChildPositionSequence,
=> _entityObjects.TryCommitParent(
relation,
acknowledgeProjection: null,
out accepted);
if (committed
&& _directory.TryGetActive(
relation.ChildGuid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
}
return committed;
}
/// <summary>
/// Commits retail <c>CPhysicsObj::set_parent</c>'s cell-less edge only
@ -1973,25 +1792,20 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ulong positionAuthorityVersion)
{
ArgumentNullException.ThrowIfNull(record);
if (!IsCurrentPositionAuthority(record, positionAuthorityVersion))
return false;
ClearWorldCell(record.ServerGuid);
return IsCurrentPositionAuthority(record, positionAuthorityVersion);
return _entityObjects.CommitAcceptedParentCellless(
record.Canonical,
positionAuthorityVersion,
AcknowledgeCelllessCanonicalCommit);
}
internal bool CommitAcceptedParentCellless(
RuntimeEntityRecord canonical,
ulong positionAuthorityVersion)
{
ArgumentNullException.ThrowIfNull(canonical);
if (!_directory.IsCurrent(canonical)
|| canonical.PositionAuthorityVersion != positionAuthorityVersion)
{
return false;
}
ClearWorldCell(canonical);
return _directory.IsCurrent(canonical)
&& canonical.PositionAuthorityVersion == positionAuthorityVersion;
return _entityObjects.CommitAcceptedParentCellless(
canonical,
positionAuthorityVersion,
AcknowledgeCelllessCanonicalCommit);
}
public bool TryApplyMotion(
@ -1999,43 +1813,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
bool retainPayload,
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps)
{
bool applied = _directory.TryApplyMotion(update, retainPayload, out accepted, out timestamps);
if (_directory.TryGetSnapshot(update.Guid, out WorldSession.EntitySpawn snapshot)
&& _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, snapshot);
}
if (applied
&& retainPayload
&& _directory.TryGetActive(update.Guid, out canonical))
{
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvanceMovementAuthority();
else
_directory.AdvanceMovementAuthority(canonical);
}
return applied;
}
=> _entityObjects.TryApplyMotion(
update,
retainPayload,
acknowledgeProjection: null,
out accepted,
out timestamps);
public bool TryApplyVector(VectorUpdate.Parsed update, out WorldSession.EntitySpawn accepted)
{
bool applied = _directory.TryApplyVector(update, out accepted);
if (applied
&& _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
record.AdvanceVectorAuthority();
else
_directory.AdvanceVectorAuthority(canonical);
}
return applied;
}
=> _entityObjects.TryApplyVector(
update,
acknowledgeProjection: null,
out accepted);
public bool TryApplyState(SetState.Parsed update, out WorldSession.EntitySpawn accepted)
=> TryApplyState(update, out accepted, out _);
@ -2044,23 +1833,19 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
SetState.Parsed update,
out WorldSession.EntitySpawn accepted,
out RetailPhysicsStateTransition transition)
{
bool applied = _directory.TryApplyState(update, out accepted);
transition = default;
if (applied
&& _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord canonical))
{
_directory.RefreshSnapshot(canonical, accepted);
transition = _directory.ApplyRawPhysicsState(
canonical,
update.PhysicsState);
if (_projections.TryGet(canonical, out LiveEntityRecord? record))
RefreshPresentation(record);
}
return applied;
}
=> _entityObjects.TryApplyState(
update,
(canonical, _) =>
{
if (_projections.TryGet(
canonical,
out LiveEntityRecord? record))
{
RefreshPresentation(record);
}
},
out accepted,
out transition);
/// <summary>
/// Retail parent Hidden directly toggles each attached child's NoDraw bit
@ -2070,9 +1855,10 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
{
if (!_projections.TryGetCurrent(childServerGuid, out LiveEntityRecord? record))
return false;
record.SetChildNoDraw(noDraw);
RefreshPresentation(record);
return true;
return _entityObjects.CommitChildNoDraw(
record.Canonical,
noDraw,
_ => RefreshPresentation(record));
}
public bool TryApplyPosition(
@ -2084,62 +1870,26 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps)
{
bool hadCanonical = _directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord? beforeCanonical);
LiveEntityRecord? beforePosition = null;
if (hadCanonical)
_projections.TryGet(beforeCanonical!, out beforePosition);
bool wasCellLess = hadCanonical
&& (beforeCanonical!.FullCellId == 0
|| beforePosition is null
|| !beforePosition.IsSpatiallyProjected
|| !beforePosition.IsSpatiallyVisible);
bool known = _directory.TryApplyPosition(
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);
if (known && _directory.TryGetSnapshot(update.Guid, out WorldSession.EntitySpawn snapshot))
{
bool acceptedPosition = disposition is not PositionTimestampDisposition.Rejected;
if (disposition is PositionTimestampDisposition.Apply)
{
timestamps = timestamps with
{
TeleportHookRequired = timestamps.TeleportAdvanced || wasCellLess,
};
}
if (_directory.TryGetActive(
update.Guid,
out RuntimeEntityRecord acceptedCanonical))
{
_directory.RefreshSnapshot(
acceptedCanonical,
snapshot,
refreshPosition: acceptedPosition);
if (acceptedPosition
&& ReferenceEquals(acceptedCanonical, beforeCanonical))
{
if (_projections.TryGet(
acceptedCanonical,
out LiveEntityRecord? acceptedRecord)
&& ReferenceEquals(acceptedRecord, beforePosition))
{
acceptedRecord.AdvancePositionAuthority();
}
else
{
_directory.AdvancePositionAuthority(acceptedCanonical);
}
ParentAttachments.EndChildProjection(update.Guid);
}
}
}
return known;
}
public bool IsFreshTeleportStart(uint localPlayerGuid, ushort teleportSequence) =>
@ -2669,15 +2419,13 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_isClearing = true;
_sessionClearPendingFinalization = true;
_directory.BeginSessionClear();
IReadOnlyList<RuntimeEntityRecord> retiredCanonicals =
_entityObjects.BeginSessionClear();
List<Exception>? failures = null;
try
{
foreach (RuntimeEntityRecord canonical in
_directory.ActiveRecords.ToArray())
foreach (RuntimeEntityRecord canonical in retiredCanonicals)
{
if (!_directory.RemoveActive(canonical))
continue;
if (_projections.TryGet(
canonical,
out LiveEntityRecord? record))
@ -2796,12 +2544,6 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_directory.RefreshSnapshot(canonical, accepted, refreshPosition);
}
private static InboundCreateResult SupersededCreateResult() => new(
CreateObjectTimestampDisposition.StaleGeneration,
default,
null,
default);
private ulong AdvanceLifetimeMutation(uint serverGuid)
=> _directory.AdvanceLifetimeMutation(serverGuid);
@ -2841,37 +2583,18 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
ExceptionDispatchInfo.Capture(failure).Throw();
}
private void ClearWorldCell(uint guid)
private void AcknowledgeCelllessCanonicalCommit(
RuntimeEntityRecord canonical)
{
if (!_directory.TryGetActive(guid, out RuntimeEntityRecord canonical))
if (!_directory.IsCurrent(canonical)
|| !_projections.TryGet(
canonical,
out LiveEntityRecord? record))
{
return;
ClearWorldCell(canonical);
}
}
private void ClearWorldCell(RuntimeEntityRecord canonical)
{
if (!_directory.IsCurrent(canonical))
return;
if (!_projections.TryGet(canonical, out LiveEntityRecord? record))
{
_directory.SetFullCell(canonical, 0, 0);
return;
}
bool wasOrdinaryRoot = record.ProjectionKey is { } key
&& _spatialRootObjects.TryGetValue(
key,
out LiveEntityRecord? indexedRoot)
&& ReferenceEquals(indexedRoot, record);
// Pickup/Parent is retail's leave-world edge. Suspend the canonical
// object clock while the record is still indexed as a root; clearing
// FullCellId first removes that evidence and makes the later projection
// withdrawal look like a duplicate non-root edge.
if (wasOrdinaryRoot)
{
record.SuspendObjectClock();
SynchronizePhysicsBodyActiveState(record);
}
_directory.SetFullCell(canonical, 0, 0);
SynchronizePhysicsBodyActiveState(record);
RefreshSpatialRuntimeIndexes(record);
}
@ -3091,27 +2814,50 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
_projections.RetainTeardown(record);
}
private void TearDownCanonicalOnly(RuntimeEntityRecord canonical)
private Exception? RetirePriorProjection(
RuntimeEntityRecord canonical)
{
// A canonical-only incarnation has never crossed the App projection
// acquisition edge, so it owns no renderer/effect/animation suffix.
// Retain it briefly only so Runtime's validated mutators can converge
// any presentation-free physics state before the tombstone vanishes.
_directory.RetainTeardown(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
{
_directory.SetPhysicsHost(canonical, null);
_directory.SetPhysicsBody(canonical, null);
_directory.SetPhysicsBodyAcquisitionInProgress(canonical, false);
_directory.SetHasPartArray(canonical, false);
_directory.ReleaseLocalId(canonical);
try
{
TearDownRecord(projection);
ReleaseTeardownRecord(projection);
return null;
}
catch (Exception error)
{
return error;
}
}
finally
{
_directory.ReleaseTeardown(canonical);
_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);
@ -3125,7 +2871,7 @@ public sealed class LiveEntityRuntime : ILiveEntityRadarSource
private void CompleteSessionClearIfConverged()
{
if (!_sessionClearPendingFinalization
|| !_directory.CompleteSessionClearIfConverged())
|| !_entityObjects.CompleteSessionClearIfConverged())
{
return;
}