using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
using AcDream.Runtime.Physics;
namespace AcDream.Runtime.Entities;
///
/// The single update-thread authority for live server GUIDs, incarnations,
/// accepted wire snapshots, teardown tombstones, and runtime-local identity.
/// It owns no graphical state and can run without loading App or a backend.
///
public sealed class RuntimeEntityDirectory
{
public const uint FirstLocalEntityId = 1_000_000u;
public const uint LastLocalEntityId = 0x3FFF_FFFFu;
private readonly InboundPhysicsStateController _inbound = new();
private readonly Dictionary _activeByGuid = new();
private readonly Dictionary<(uint Guid, ushort Incarnation), RuntimeEntityRecord>
_teardownByIncarnation = new();
private readonly Dictionary _byLocalId = new();
private readonly Dictionary _lifetimeMutationByGuid = new();
private uint _nextLocalEntityId;
public RuntimeEntityDirectory(uint firstLocalEntityId = FirstLocalEntityId)
{
if (firstLocalEntityId is < FirstLocalEntityId or > LastLocalEntityId)
{
throw new ArgumentOutOfRangeException(
nameof(firstLocalEntityId),
$"Live entity ids must stay in 0x{FirstLocalEntityId:X8}..0x{LastLocalEntityId:X8}.");
}
_nextLocalEntityId = firstLocalEntityId;
}
public int Count => _activeByGuid.Count;
public int PendingTeardownCount => _teardownByIncarnation.Count;
public int ClaimedLocalIdCount => _byLocalId.Count;
public ulong SessionLifetimeVersion { get; private set; }
public IReadOnlyCollection ActiveRecords => _activeByGuid.Values;
public IReadOnlyCollection TeardownRecords =>
_teardownByIncarnation.Values;
public IReadOnlyDictionary Snapshots => _inbound.Snapshots;
public ParentAttachmentState ParentAttachments { get; } = new();
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
_inbound.AcceptCreate(incoming);
internal InboundCreateResult AcceptCreateDeferredSameGeneration(
WorldSession.EntitySpawn incoming) =>
_inbound.AcceptCreateDeferredSameGeneration(incoming);
public CreateObjectTimestampDisposition PreviewCreateDisposition(
WorldSession.EntitySpawn incoming) =>
_inbound.PreviewCreateDisposition(incoming);
public bool TryDelete(
AcDream.Core.Net.Messages.DeleteObject.Parsed delete,
bool isLocalPlayer) =>
_inbound.TryDelete(delete, isLocalPlayer);
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
_inbound.TryGetSnapshot(guid, out spawn);
internal bool TryGetAcceptedTimestamps(
uint guid,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryGetAcceptedTimestamps(guid, out timestamps);
public bool TryGetActive(uint guid, out RuntimeEntityRecord record) =>
_activeByGuid.TryGetValue(guid, out record!);
public bool IsCurrent(RuntimeEntityRecord record) =>
_activeByGuid.TryGetValue(record.ServerGuid, out RuntimeEntityRecord? current)
&& ReferenceEquals(current, record);
public bool TryGetByLocalId(uint localEntityId, out RuntimeEntityRecord record) =>
_byLocalId.TryGetValue(localEntityId, out record!);
public bool TryGetTeardown(
uint guid,
ushort incarnation,
out RuntimeEntityRecord record) =>
_teardownByIncarnation.TryGetValue((guid, incarnation), out record!);
public RuntimeEntityRecord AddActive(WorldSession.EntitySpawn snapshot)
{
if (_activeByGuid.ContainsKey(snapshot.Guid))
{
throw new InvalidOperationException(
$"Live entity 0x{snapshot.Guid:X8} already has an active incarnation.");
}
var record = new RuntimeEntityRecord(snapshot);
_activeByGuid.Add(snapshot.Guid, record);
try
{
ClaimLocalId(record);
return record;
}
catch
{
_activeByGuid.Remove(snapshot.Guid);
throw;
}
}
public bool RemoveActive(uint guid, out RuntimeEntityRecord? record) =>
_activeByGuid.Remove(guid, out record);
public bool RemoveActive(RuntimeEntityRecord expected)
{
if (!IsCurrent(expected))
return false;
return _activeByGuid.Remove(expected.ServerGuid);
}
public void RetainTeardown(RuntimeEntityRecord record)
{
var key = (record.ServerGuid, record.Incarnation);
if (_teardownByIncarnation.TryGetValue(
key,
out RuntimeEntityRecord? retained)
&& !ReferenceEquals(retained, record))
{
throw new InvalidOperationException(
$"Live entity teardown tombstone collision for 0x{record.ServerGuid:X8} generation {record.Incarnation}.");
}
_teardownByIncarnation[key] = record;
}
public void ReleaseTeardown(RuntimeEntityRecord record)
{
var key = (record.ServerGuid, record.Incarnation);
if (_teardownByIncarnation.TryGetValue(
key,
out RuntimeEntityRecord? retained)
&& ReferenceEquals(retained, record))
{
_teardownByIncarnation.Remove(key);
}
}
public bool HasPendingTeardown(uint serverGuid)
{
foreach ((uint Guid, ushort Incarnation) key in _teardownByIncarnation.Keys)
{
if (key.Guid == serverGuid)
return true;
}
return false;
}
public uint ClaimLocalId(RuntimeEntityRecord record)
{
if (!IsKnown(record))
{
throw new InvalidOperationException(
"A local id can only be claimed for an active or retained incarnation.");
}
if (record.LocalEntityId is { } existing)
return existing;
uint start = _nextLocalEntityId;
do
{
uint candidate = _nextLocalEntityId;
_nextLocalEntityId = candidate == LastLocalEntityId
? FirstLocalEntityId
: candidate + 1u;
if (_byLocalId.ContainsKey(candidate))
continue;
_byLocalId.Add(candidate, record);
record.LocalEntityId = candidate;
return candidate;
}
while (_nextLocalEntityId != start);
throw new InvalidOperationException("The live entity id namespace is exhausted.");
}
public bool ReleaseLocalId(RuntimeEntityRecord record)
{
if (record.LocalEntityId is not { } localId)
return false;
if (_byLocalId.TryGetValue(localId, out RuntimeEntityRecord? retained)
&& ReferenceEquals(retained, record))
{
_byLocalId.Remove(localId);
}
record.LocalEntityId = null;
return true;
}
public ulong AdvanceLifetimeMutation(uint serverGuid)
{
ulong next = _lifetimeMutationByGuid.GetValueOrDefault(serverGuid) + 1UL;
_lifetimeMutationByGuid[serverGuid] = next;
return next;
}
public ulong CurrentLifetimeMutation(uint serverGuid) =>
_lifetimeMutationByGuid.GetValueOrDefault(serverGuid);
public void BeginSessionClear()
{
SessionLifetimeVersion++;
_lifetimeMutationByGuid.Clear();
ParentAttachments.Clear();
_inbound.Clear();
}
public bool CompleteSessionClearIfConverged()
{
if (_activeByGuid.Count != 0 || _teardownByIncarnation.Count != 0)
return false;
_byLocalId.Clear();
ParentAttachments.Clear();
_inbound.Clear();
return true;
}
public void RefreshSnapshot(
RuntimeEntityRecord record,
WorldSession.EntitySpawn accepted,
bool refreshPosition = false)
{
EnsureKnown(record);
uint previousCell = record.FullCellId;
record.Snapshot = accepted;
record.RefreshDerivedState(refreshPosition);
if (record.FullCellId != previousCell)
{
PropagateFullCellToChildren(
record,
record.FullCellId,
record.CanonicalLandblockId);
}
}
public void AdvanceCreateAuthority(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceCreateAuthority();
}
public void AdvancePositionAuthority(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvancePositionAuthority();
}
public void AdvanceVectorAuthority(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceVectorAuthority();
}
public void AdvanceMovementAuthority(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceMovementAuthority();
}
public void AdvanceMovementCommit(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceMovementCommit();
}
public void AdvancePlacementCommit(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvancePlacementCommit();
}
public void AdvanceParentCommit(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceParentCommit();
}
public void AdvanceObjDescAuthority(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.AdvanceObjDescAuthority();
}
public RetailPhysicsStateTransition ApplyRawPhysicsState(
RuntimeEntityRecord record,
uint rawState)
{
EnsureKnown(record);
return record.ApplyRawPhysicsState(rawState);
}
public bool TryDequeueStateTransition(
RuntimeEntityRecord record,
out RetailPhysicsStateTransition transition)
{
EnsureKnown(record);
return record.TryDequeueStateTransition(out transition);
}
public void SetChildNoDraw(RuntimeEntityRecord record, bool noDraw)
{
EnsureKnown(record);
record.SetChildNoDraw(noDraw);
}
public void SuspendObjectClock(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.SuspendObjectClock();
}
///
/// Re-activates a clock suspended by , for
/// rolling back a withdrawal that is being cancelled rather than
/// completed. NOT an exact inverse — the retained sub-quantum time is
/// discarded, which is retail's set_active(1) rebase semantic; see
/// . Distinct from
/// , which is the
/// entering-the-world edge and additionally rebases the static/dynamic
/// quantum shape; a rollback re-activates a clock that never conceptually
/// left.
///
public void ResumeObjectClock(RuntimeEntityRecord record)
{
EnsureKnown(record);
record.ResumeObjectClock();
}
public void ResetObjectClockForEnterWorld(RuntimeEntityRecord record, bool isStatic)
{
EnsureKnown(record);
record.ResetObjectClockForEnterWorld(isStatic);
}
public void SetFullCell(
RuntimeEntityRecord record,
uint fullCellId,
uint canonicalLandblockId)
{
EnsureKnown(record);
record.SetFullCell(fullCellId, canonicalLandblockId);
PropagateFullCellToChildren(record, fullCellId, canonicalLandblockId);
}
///
/// Reused across every call.
/// Always empty on entry and on exit (the loop drains it unconditionally
/// before returning) — see that method's remarks for why sharing it is
/// safe rather than a repeat of the round-3 A6/B4 scratch-buffer
/// reentrancy lesson: this stack never escapes the method, and nothing
/// on the write path can trigger a nested call while it holds state.
///
private readonly Stack _propagationWorklist = new();
///
/// C4 route 7 D2 — the sole propagation hook. Retail's parent-cell-
/// crossing propagation (CPhysicsObj::SetPositionInternal
/// @0x00515330's changed-cell branch @0x00515372 -> change_cell
/// @0x00513390 -> the self-recursive enter_cell/leave_cell
/// @0x00510ed0/@0x00510f50) has exactly one acdream analogue: every
/// canonical cell write already funnels through this method or
/// 's derived-state write (see
/// docs/research/2026-08-04-retail-parent-cell-propagation.md and the
/// route-7 contract's D2).
///
///
/// ITERATIVE, not recursive (round-3 remediation — both the retail and
/// architecture reviews independently converged on the same finding,
/// N4/B3: a depth-capped RECURSIVE version left a subtree beyond the cap
/// at a STALE NONZERO cell permanently — on the withdraw path that is
/// the #184 shape verbatim, the exact defect AP-142 clause (a) exists to
/// reject, shipped fresh inside the slice whose headline is fixing it.
/// A reused stack removes the depth
/// concept entirely rather than mitigating it: there is no C# call-stack
/// growth to bound, so the only limit is the number of committed
/// relations actually in the system (which cannot exceed the live
/// entity count) — matching retail's own genuinely unbounded recursion
/// exactly, with no acdream-only cap and no register row for one. This
/// also retires B5 in the sense that there is no longer a RECURSIVE call
/// to reason about — but the bypass itself is NOT retired: the loop
/// below still calls a child's own SetFullCell directly rather
/// than the public , and that bypass is
/// DELIBERATE and LOAD-BEARING, not incidental. The public
/// calls this method, and this method opens
/// with _propagationWorklist.Clear() — routing a child through
/// the public method would re-enter this method mid-drain, clear the
/// shared worklist out from under the OUTER loop, and silently drop
/// every sibling still waiting on the stack, with no error and no
/// exception. Any side effect added to the public
/// in the future MUST be mirrored by hand at
/// this call site, because this call site cannot route through it.
///
///
///
/// Skipping a child whose
/// (and , A7)
/// already equals the target is BOTH the cycle guard (an A→B→A wire-
/// induced relation cycle writes A, pushes B, writes B, pops B, finds A
/// already at the target value, and does not re-push it — no visited
/// set needed) and the retail-behavioral subsumption of retail's
/// separate same-cell depth-1 id refresh (@0x0051539c-@0x005153d8)
/// under this project's single-field cell model (AP-142). Zero
/// allocation after warmup:
/// returns
/// the stored list or , and the worklist
/// stack is a reused field, never a fresh collection per call. Field
/// writes only — no clock, workset, shadow, or placement work — so this
/// is safe to run re-entrantly inside whatever transaction is mid-commit
/// on the parent's own cell; the worklist field itself is safe to share
/// across calls because the loop below unconditionally drains it to
/// empty before this method returns, and nothing on the write path
/// (a field assignment and an optional diagnostic log line) can call
/// back into a nested . NOT gated on retail's
/// part_array != 0 guard (@0x00510ed8) — see AP-142 clause (d)
/// for why that guard has no reproducible analogue at acdream's
/// canonical layer.
///
///
/// P8 (contract proof obligation, corrected at the architecture review
/// round — A3): this step deliberately publishes NO per-child
/// RuntimeEntityChange.Rebucketed delta, matching
/// RuntimePhysicsState.CommitCanonicalCell's precedent. The
/// correct basis for that decision is BOTH observer interfaces, not
/// just IRuntimeEntityObjectObserver's direct implementations:
/// GameRuntimeEventHub itself implements
/// IRuntimeEntityObjectObserver and fans every entity delta out
/// to IRuntimeEventObserver, and RuntimeTraceRecorder.OnEntity
/// (GameRuntimeEvents.cs) is a non-stub shipped consumer that
/// records (delta.Change, delta.Entity.CellId) for every entity
/// delta — it is diagnostic tracing, not gameplay logic, and the
/// entries it would lose are graphical-only equipped-child
/// Rebucketed deltas that TickChild's demoted rebucket used to
/// produce, so silence here is acceptable, but "no consumer at all" is
/// false and must not be restated that way.
///
///
///
/// P4 (contract proof obligation — the child broadphase story, stated):
/// this step writes canonical cell FIELDS only. At attach, the
/// preceding cell-less edge already force-ends collision reporting
/// (Physics.CollisionReports.LeaveWorld) and no child broadphase
/// registration exists to rebuild — retail's own
/// recalc_cross_cells @0x00515A30 runs at attach only, never
/// per-crossing (the §0 trap this hook deliberately does not port). At
/// every crossing thereafter, a committed child never becomes a spatial
/// root (LiveEntityRuntime.HasSpatialRuntimeProjection keys off
/// ProjectionKind is World, and an attached child is always
/// ProjectionKind.Attached — AcknowledgeSpatialProjection
/// is never called on this path, headless or graphical), so it never
/// joins any physics workset or shadow/cross-cell list this step would
/// need to maintain. Confirmed, not assumed: zero shadow work is
/// performed anywhere in this method.
///
///
private void PropagateFullCellToChildren(
RuntimeEntityRecord root,
uint fullCellId,
uint canonicalLandblockId)
{
_propagationWorklist.Clear();
_propagationWorklist.Push(root);
while (_propagationWorklist.Count > 0)
{
RuntimeEntityRecord current = _propagationWorklist.Pop();
IReadOnlyList children = ParentAttachments.ChildrenAttachedToParent(
current.ServerGuid,
current.Incarnation);
for (int i = 0; i < children.Count; i++)
{
// A7 (architecture review, LOW): key the idempotence/cycle
// skip on the PAIR, not FullCellId alone. Every production
// writer derives canonicalLandblockId from fullCellId, so
// the two are coupled today, but the coupling is not
// enforced anywhere (LiveEntityRuntime.CanonicalLandblockId's
// setter can write a same-cell, different-landblock pair) —
// testing the pair is the version that is correct
// independent of that coupling.
if (!TryGetActive(children[i], out RuntimeEntityRecord child)
|| (child.FullCellId == fullCellId
&& child.CanonicalLandblockId == canonicalLandblockId))
{
continue;
}
if (PhysicsDiagnostics.ProbeChildCellEnabled)
{
Console.WriteLine(FormattableString.Invariant(
$"[child-cell] parent=0x{current.ServerGuid:X8} child=0x{child.ServerGuid:X8} old=0x{child.FullCellId:X8} new=0x{fullCellId:X8} cause={(fullCellId == 0u ? "withdraw" : "propagate")}"));
}
child.SetFullCell(fullCellId, canonicalLandblockId);
_propagationWorklist.Push(child);
}
}
}
public void SetFinalPhysicsState(
RuntimeEntityRecord record,
PhysicsStateFlags state)
{
EnsureKnown(record);
record.SetFinalPhysicsState(state);
}
internal bool StopMissileAfterCollision(
RuntimeEntityRecord record,
bool requireCurrentMissile)
{
EnsureKnown(record);
return record.StopMissileAfterCollision(requireCurrentMissile);
}
public void SetHasPartArray(RuntimeEntityRecord record, bool value)
{
EnsureKnown(record);
record.HasPartArray = value;
}
public void SetPhysicsBody(
RuntimeEntityRecord record,
AcDream.Core.Physics.PhysicsBody? body)
{
EnsureKnown(record);
record.SetPhysicsBody(body);
}
public void SetPhysicsBodyAcquisitionInProgress(
RuntimeEntityRecord record,
bool value)
{
EnsureKnown(record);
record.PhysicsBodyAcquisitionInProgress = value;
}
public void SetRemoteMotion(
RuntimeEntityRecord record,
IRuntimeRemoteMotion? remote)
{
EnsureKnown(record);
record.RemoteMotion = remote;
}
public void SetRemoteMotionBindingInProgress(
RuntimeEntityRecord record,
bool value)
{
EnsureKnown(record);
record.RemoteMotionBindingInProgress = value;
}
public void SetProjectile(
RuntimeEntityRecord record,
IRuntimeProjectile? projectile)
{
EnsureKnown(record);
record.Projectile = projectile;
}
public void SetProjectileBindingInProgress(
RuntimeEntityRecord record,
bool value)
{
EnsureKnown(record);
record.ProjectileBindingInProgress = value;
}
public void SetRequiresRemotePlacementRuntime(
RuntimeEntityRecord record,
bool value)
{
EnsureKnown(record);
record.RequiresRemotePlacementRuntime = value;
}
public void SetPhysicsHost(
RuntimeEntityRecord record,
AcDream.Core.Physics.Motion.IPhysicsObjHost? host)
{
EnsureKnown(record);
record.PhysicsHost = host;
}
public void SetDeleteAcceptedForTeardown(
RuntimeEntityRecord record,
bool value)
{
EnsureKnown(record);
record.DeleteAcceptedForTeardown = value;
}
public bool TryApplyObjDesc(
AcDream.Core.Net.Messages.ObjDescEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyObjDesc(update, out accepted);
public bool TryApplyPickup(
AcDream.Core.Net.Messages.PickupEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyPickup(update, out accepted);
public bool TryApplyCreateParent(
CreateParentUpdate update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyCreateParent(update, out accepted);
public bool TryApplyParent(
AcDream.Core.Net.Messages.ParentEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyParent(update, out accepted);
public bool TryCommitParent(
uint childGuid,
uint parentGuid,
uint parentLocation,
uint placementId,
ushort positionSequence,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryCommitParent(
childGuid,
parentGuid,
parentLocation,
placementId,
positionSequence,
out accepted);
public bool TryApplyMotion(
WorldSession.EntityMotionUpdate update,
bool retainPayload,
out WorldSession.EntitySpawn accepted,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryApplyMotion(update, retainPayload, out accepted, out timestamps);
public bool TryApplyVector(
AcDream.Core.Net.Messages.VectorUpdate.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyVector(update, out accepted);
public bool TryApplyState(
AcDream.Core.Net.Messages.SetState.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.TryApplyState(update, out accepted);
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) =>
_inbound.TryApplyPosition(
update,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
out disposition,
out accepted,
out timestamps);
internal bool TryAcceptDeferredPosition(
WorldSession.EntityPositionUpdate update,
bool isLocalPlayer,
out PositionTimestampDisposition disposition,
out AcceptedPhysicsTimestamps timestamps,
out bool hasTimestampMutation) =>
_inbound.TryAcceptDeferredPosition(
update,
isLocalPlayer,
out disposition,
out timestamps,
out hasTimestampMutation);
internal bool TryAcceptDeferredObjDesc(
ObjDescEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredObjDesc(update, out timestamps);
internal bool TryAcceptDeferredPickup(
PickupEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredPickup(update, out timestamps);
internal bool TryAcceptDeferredCreateParent(
CreateParentUpdate update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredCreateParent(update, out timestamps);
internal bool TryAcceptDeferredParent(
ParentEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredParent(update, out timestamps);
internal bool TryAcceptDeferredMotion(
WorldSession.EntityMotionUpdate update,
out AcceptedPhysicsTimestamps timestamps,
out bool hasTimestampMutation) =>
_inbound.TryAcceptDeferredMotion(
update,
out timestamps,
out hasTimestampMutation);
internal bool TryAcceptDeferredState(
SetState.Parsed update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredState(update, out timestamps);
internal bool TryAcceptDeferredVector(
VectorUpdate.Parsed update,
out AcceptedPhysicsTimestamps timestamps) =>
_inbound.TryAcceptDeferredVector(update, out timestamps);
public bool IsFreshTeleportStart(uint guid, ushort teleportSequence) =>
_inbound.IsFreshTeleportStart(guid, teleportSequence);
// Round 3 A1: gate-less instance seams for the initial-Create
// continuation executor. Each merges against _inbound's OWN
// _snapshots[guid] (never a caller-supplied base) and writes the result
// back, keeping this store and RuntimeEntityRecord.Snapshot in lockstep.
internal bool ApplyAcceptedObjDescSnapshot(
uint guid,
ObjDescEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedObjDescSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedPickupSnapshot(
uint guid,
PickupEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedPickupSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedCreateParentSnapshot(
uint guid,
CreateParentUpdate update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedCreateParentSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedParentSnapshot(
uint guid,
ParentEvent.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedParentSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedMotionSnapshot(
uint guid,
ushort movementSequence,
ushort acceptedServerControlledMove,
WorldSession.EntityMotionUpdate update,
bool retainPayload,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedMotionSnapshot(
guid,
movementSequence,
acceptedServerControlledMove,
update,
retainPayload,
out accepted);
internal bool ApplyAcceptedStateSnapshot(
uint guid,
SetState.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedStateSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedVectorSnapshot(
uint guid,
VectorUpdate.Parsed update,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedVectorSnapshot(guid, update, out accepted);
internal bool ApplyAcceptedPositionSnapshot(
uint guid,
WorldSession.EntityPositionUpdate update,
PositionTimestampDisposition disposition,
AcceptedPhysicsTimestamps timestamps,
bool isLocalPlayer,
System.Numerics.Quaternion? forcePositionRotation,
System.Numerics.Vector3? currentLocalVelocity,
bool installPlacementFrame,
bool clearParent,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedPositionSnapshot(
guid,
update,
disposition,
timestamps,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
installPlacementFrame,
clearParent,
out accepted);
internal bool ApplyAcceptedPositionExecutionRejectedSnapshot(
uint guid,
ushort acceptedPositionSequence,
AcceptedPhysicsTimestamps timestamps,
out WorldSession.EntitySpawn accepted) =>
_inbound.ApplyAcceptedPositionExecutionRejectedSnapshot(
guid,
acceptedPositionSequence,
timestamps,
out accepted);
internal bool ApplyAcceptedWeenieDescriptionSnapshot(
uint guid,
WorldSession.EntitySpawn incoming,
out WorldSession.EntitySpawn merged) =>
_inbound.ApplyAcceptedWeenieDescriptionSnapshot(guid, incoming, out merged);
///
/// #297 review round 2: see
/// .
///
internal bool TryRefreshObjectDescriptionFlags(
uint guid,
uint bitfield,
out WorldSession.EntitySpawn merged) =>
_inbound.TryRefreshObjectDescriptionFlags(guid, bitfield, out merged);
private bool IsKnown(RuntimeEntityRecord record)
{
if (IsCurrent(record))
return true;
return _teardownByIncarnation.TryGetValue(
(record.ServerGuid, record.Incarnation),
out RuntimeEntityRecord? retained)
&& ReferenceEquals(retained, record);
}
private void EnsureKnown(RuntimeEntityRecord record)
{
ArgumentNullException.ThrowIfNull(record);
if (!IsKnown(record))
{
throw new InvalidOperationException(
$"Runtime entity 0x{record.ServerGuid:X8}/{record.Incarnation} is no longer owned by the directory.");
}
}
}