feat(runtime): freeze initial placement inbound admission

This commit is contained in:
Erik 2026-08-01 21:00:03 +02:00
parent 9d601817b8
commit 30012361e1
9 changed files with 2506 additions and 323 deletions

View file

@ -20,13 +20,35 @@ public sealed class InboundPhysicsStateController
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) => public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
_snapshots.TryGetValue(guid, out spawn); _snapshots.TryGetValue(guid, out spawn);
internal bool TryGetAcceptedTimestamps(
uint guid,
out AcceptedPhysicsTimestamps timestamps)
{
if (_gates.TryGetValue(guid, out PhysicsTimestampGate? gate))
{
timestamps = Current(gate);
return true;
}
timestamps = default;
return false;
}
public CreateObjectTimestampDisposition PreviewCreateDisposition( public CreateObjectTimestampDisposition PreviewCreateDisposition(
WorldSession.EntitySpawn incoming) => WorldSession.EntitySpawn incoming) =>
_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate) _gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate)
? gate.PreviewCreateObject(incoming.InstanceSequence) ? gate.PreviewCreateObject(incoming.InstanceSequence)
: CreateObjectTimestampDisposition.InitialGeneration; : CreateObjectTimestampDisposition.InitialGeneration;
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
AcceptCreateCore(incoming, deferSameGenerationWeenieDescription: false);
internal InboundCreateResult AcceptCreateDeferredSameGeneration(
WorldSession.EntitySpawn incoming) =>
AcceptCreateCore(incoming, deferSameGenerationWeenieDescription: true);
private InboundCreateResult AcceptCreateCore(
WorldSession.EntitySpawn incoming,
bool deferSameGenerationWeenieDescription)
{ {
if (!_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate)) if (!_gates.TryGetValue(incoming.Guid, out PhysicsTimestampGate? gate))
{ {
@ -56,8 +78,11 @@ public sealed class InboundPhysicsStateController
return new InboundCreateResult(disposition, incoming, null, Current(gate)); return new InboundCreateResult(disposition, incoming, null, Current(gate));
} }
WorldSession.EntitySpawn merged = MergeUntimestampedCreate(retained, incoming); WorldSession.EntitySpawn merged = deferSameGenerationWeenieDescription
_snapshots[incoming.Guid] = merged; ? retained
: MergeUntimestampedCreate(retained, incoming);
if (!deferSameGenerationWeenieDescription)
_snapshots[incoming.Guid] = merged;
return new InboundCreateResult( return new InboundCreateResult(
disposition, disposition,
merged, merged,
@ -422,6 +447,181 @@ public sealed class InboundPhysicsStateController
return true; return true;
} }
/// <summary>
/// Consumes only the retail Position timestamp gates for a deferred
/// initial-Create continuation. The raw PositionPack is not transformed
/// and no pose, parent, placement, or velocity field is installed. The
/// returned authority is sufficient for one later execution without
/// running the gate a second time.
/// </summary>
internal bool TryAcceptDeferredPosition(
WorldSession.EntityPositionUpdate update,
bool isLocalPlayer,
out PositionTimestampDisposition disposition,
out AcceptedPhysicsTimestamps timestamps,
out bool hasTimestampMutation)
{
if (!TryGet(
update.Guid,
out PhysicsTimestampGate? gate,
out WorldSession.EntitySpawn old))
{
disposition = PositionTimestampDisposition.Rejected;
timestamps = default;
hasTimestampMutation = false;
return false;
}
ushort previousPosition = gate.PositionTimestamp;
ushort previousTeleport = gate.TeleportTimestamp;
ushort previousForcePosition = gate.ForcePositionTimestamp;
bool advancesTeleport = PhysicsTimestampGate.IsNewer(
previousTeleport,
update.TeleportSequence);
disposition = gate.TryAcceptPositionEvent(
update.InstanceSequence,
update.PositionSequence,
update.TeleportSequence,
update.ForcePositionSequence,
isLocalPlayer);
timestamps = Current(
gate,
teleportAdvanced: disposition is PositionTimestampDisposition.Apply
&& advancesTeleport,
previousTeleport: previousTeleport);
hasTimestampMutation = previousPosition != gate.PositionTimestamp
|| previousTeleport != gate.TeleportTimestamp
|| previousForcePosition != gate.ForcePositionTimestamp;
return true;
}
internal bool TryAcceptDeferredObjDesc(
ObjDescEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!TryGet(update.Guid, out PhysicsTimestampGate? gate, out _)
|| !gate.TryAcceptObjDescEvent(
update.InstanceSequence,
update.ObjDescSequence))
{
timestamps = default;
return false;
}
timestamps = Current(gate);
return true;
}
internal bool TryAcceptDeferredPickup(
PickupEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!TryGet(update.Guid, out PhysicsTimestampGate? gate, out _)
|| !gate.TryAcceptPositionChannelEvent(
update.InstanceSequence,
update.PositionSequence))
{
timestamps = default;
return false;
}
timestamps = Current(gate);
return true;
}
internal bool TryAcceptDeferredCreateParent(
CreateParentUpdate update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!TryGet(update.ChildGuid, out PhysicsTimestampGate? gate, out _)
|| !gate.TryAcceptPositionChannelEvent(
update.ChildInstanceSequence,
update.ChildPositionSequence))
{
timestamps = default;
return false;
}
timestamps = Current(gate);
return true;
}
internal bool TryAcceptDeferredParent(
ParentEvent.Parsed update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!_gates.TryGetValue(
update.ParentGuid,
out PhysicsTimestampGate? parentGate)
|| !parentGate.IsCurrentInstance(update.ParentInstanceSequence)
|| !TryGet(
update.ChildGuid,
out PhysicsTimestampGate? childGate,
out _)
|| !childGate.TryAcceptPositionChannelEvent(
childGate.InstanceTimestamp,
update.ChildPositionSequence))
{
timestamps = default;
return false;
}
timestamps = Current(childGate);
return true;
}
internal bool TryAcceptDeferredMotion(
WorldSession.EntityMotionUpdate update,
out AcceptedPhysicsTimestamps timestamps,
out bool hasTimestampMutation)
{
if (!TryGet(update.Guid, out PhysicsTimestampGate? gate, out _))
{
timestamps = default;
hasTimestampMutation = false;
return false;
}
ushort previousMovement = gate.MovementTimestamp;
ushort previousServerControl = gate.ServerControlledMoveTimestamp;
bool accepted = gate.TryAcceptMovementEvent(
update.InstanceSequence,
update.MovementSequence,
update.ServerControlSequence);
timestamps = Current(gate);
hasTimestampMutation = previousMovement != gate.MovementTimestamp
|| previousServerControl != gate.ServerControlledMoveTimestamp;
return accepted;
}
internal bool TryAcceptDeferredState(
SetState.Parsed update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!TryGet(update.Guid, out PhysicsTimestampGate? gate, out _)
|| !gate.TryAcceptStateEvent(
update.InstanceSequence,
update.StateSequence))
{
timestamps = default;
return false;
}
timestamps = Current(gate);
return true;
}
internal bool TryAcceptDeferredVector(
VectorUpdate.Parsed update,
out AcceptedPhysicsTimestamps timestamps)
{
if (!TryGet(update.Guid, out PhysicsTimestampGate? gate, out _)
|| !gate.TryAcceptVectorEvent(
update.InstanceSequence,
update.VectorSequence))
{
timestamps = default;
return false;
}
timestamps = Current(gate);
return true;
}
/// <summary> /// <summary>
/// F751 is a notification gate only. Retail compares it to TELEPORT_TS but /// F751 is a notification gate only. Retail compares it to TELEPORT_TS but
/// advances that timestamp later, with the accepted Position packet. /// advances that timestamp later, with the accepted Position packet.
@ -465,12 +665,15 @@ public sealed class InboundPhysicsStateController
private static AcceptedPhysicsTimestamps Current( private static AcceptedPhysicsTimestamps Current(
PhysicsTimestampGate gate, PhysicsTimestampGate gate,
bool teleportAdvanced = false) => new( bool teleportAdvanced = false,
ushort previousTeleport = 0) => new(
gate.InstanceTimestamp, gate.InstanceTimestamp,
gate.ServerControlledMoveTimestamp, gate.ServerControlledMoveTimestamp,
gate.TeleportTimestamp, gate.TeleportTimestamp,
gate.ForcePositionTimestamp, gate.ForcePositionTimestamp,
teleportAdvanced); teleportAdvanced,
TeleportHookRequired: false,
previousTeleport);
private static WorldSession.EntitySpawn MirrorGateTimestamps( private static WorldSession.EntitySpawn MirrorGateTimestamps(
WorldSession.EntitySpawn spawn, WorldSession.EntitySpawn spawn,
@ -671,7 +874,8 @@ public readonly record struct AcceptedPhysicsTimestamps(
ushort Teleport, ushort Teleport,
ushort ForcePosition, ushort ForcePosition,
bool TeleportAdvanced = false, bool TeleportAdvanced = false,
bool TeleportHookRequired = false); bool TeleportHookRequired = false,
ushort PreviousTeleport = 0);
public readonly record struct CreateParentUpdate( public readonly record struct CreateParentUpdate(
uint ChildGuid, uint ChildGuid,

View file

@ -1,3 +1,4 @@
using AcDream.Core.Net;
using AcDream.Core.Net.Messages; using AcDream.Core.Net.Messages;
using AcDream.Core.Physics; using AcDream.Core.Physics;
@ -17,12 +18,119 @@ public sealed class ParentAttachmentState
private readonly Dictionary<uint, ParentAttachmentRelation> _recoveryByChild = new(); private readonly Dictionary<uint, ParentAttachmentRelation> _recoveryByChild = new();
private readonly Dictionary<uint, ParentAttachmentRelation> _lastAcceptedByChild = new(); private readonly Dictionary<uint, ParentAttachmentRelation> _lastAcceptedByChild = new();
private readonly Dictionary<ParentIncarnation, List<uint>> _committedChildrenByParent = new(); private readonly Dictionary<ParentIncarnation, List<uint>> _committedChildrenByParent = new();
private readonly Dictionary<uint, Queue<DeferredParentCreate>>
_deferredCreatesByParent = [];
private ulong _nextDeferredCreateAdmissionId;
public int UnresolvedRelationCount => public int UnresolvedRelationCount =>
_unresolvedByChild.Values.Sum(queue => queue.Count); _unresolvedByChild.Values.Sum(queue => queue.Count);
public int StagedRelationCount => _stagedByChild.Count; public int StagedRelationCount => _stagedByChild.Count;
public int RecoveryRelationCount => _recoveryByChild.Count; public int RecoveryRelationCount => _recoveryByChild.Count;
public int CommittedRelationCount => _lastAcceptedByChild.Count; public int CommittedRelationCount => _lastAcceptedByChild.Count;
internal int DeferredCreateCount =>
_deferredCreatesByParent.Values.Sum(queue => queue.Count);
/// <summary>
/// Retains the complete unaccepted CreateObject packet when its nonzero
/// parent is not addressable. Retail queues the raw blob before object
/// lookup and before any child timestamp is consumed; storing a decoded
/// relation after AcceptCreate would partially admit the child.
/// </summary>
internal void EnqueueDeferredCreate(
WorldSession.EntitySpawn spawn,
bool isLocalPlayer)
{
uint parentGuid = spawn.ParentGuid
?? spawn.Physics?.Parent?.Guid
?? 0u;
if (spawn.Guid == 0u || parentGuid == 0u)
{
throw new ArgumentException(
"A deferred parent CreateObject requires nonzero child and parent GUIDs.",
nameof(spawn));
}
if (_nextDeferredCreateAdmissionId == ulong.MaxValue)
{
throw new InvalidOperationException(
"The deferred parent CreateObject admission sequence is exhausted.");
}
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue))
{
queue = new Queue<DeferredParentCreate>();
_deferredCreatesByParent.Add(parentGuid, queue);
}
ulong admissionId = _nextDeferredCreateAdmissionId + 1UL;
queue.Enqueue(new DeferredParentCreate(
admissionId,
RuntimeInitialCreateAdmissionFreezer.Freeze(spawn),
isLocalPlayer));
_nextDeferredCreateAdmissionId = admissionId;
}
internal bool TryPeekDeferredCreate(
uint parentGuid,
out DeferredParentCreate deferred)
{
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue)
|| !queue.TryPeek(out deferred))
{
deferred = default;
return false;
}
return true;
}
internal bool ConsumeDeferredCreate(
uint parentGuid,
in DeferredParentCreate expected)
{
if (!_deferredCreatesByParent.TryGetValue(
parentGuid,
out Queue<DeferredParentCreate>? queue)
|| !queue.TryPeek(out DeferredParentCreate current)
|| current != expected)
{
return false;
}
_ = queue.Dequeue();
if (queue.Count == 0)
_deferredCreatesByParent.Remove(parentGuid);
return true;
}
internal bool ContainsDeferredCreate(
uint childGuid,
ushort instanceSequence)
{
foreach (Queue<DeferredParentCreate> queue
in _deferredCreatesByParent.Values)
{
if (queue.Any(candidate =>
candidate.Spawn.Guid == childGuid
&& candidate.Spawn.InstanceSequence == instanceSequence))
{
return true;
}
}
return false;
}
/// <summary>
/// Cancels only the raw, still-unaccepted child generation addressed by a
/// terminal packet. Instance zero is a normal retail timestamp and is not
/// treated as an empty sentinel.
/// </summary>
internal void CancelDeferredChildGeneration(
uint childGuid,
ushort terminalInstanceSequence) => FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid
|| PhysicsTimestampGate.IsNewer(
terminalInstanceSequence,
candidate.Spawn.InstanceSequence));
public void AcceptCreateObjectRelation(ParentAttachmentRelation relation) public void AcceptCreateObjectRelation(ParentAttachmentRelation relation)
{ {
@ -270,6 +378,7 @@ public sealed class ParentAttachmentState
public void RemoveObject(uint guid) public void RemoveObject(uint guid)
{ {
RemoveDeferredChildCreates(guid);
_stagedByChild.Remove(guid); _stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid); _recoveryByChild.Remove(guid);
RemoveCommittedChild(guid); RemoveCommittedChild(guid);
@ -298,6 +407,12 @@ public sealed class ParentAttachmentState
/// </summary> /// </summary>
public void EndGeneration(uint guid, ushort replacementGeneration) public void EndGeneration(uint guid, ushort replacementGeneration)
{ {
FilterDeferredCreates(candidate =>
candidate.Spawn.Guid != guid
|| candidate.Spawn.InstanceSequence == replacementGeneration
|| PhysicsTimestampGate.IsNewer(
replacementGeneration,
candidate.Spawn.InstanceSequence));
FilterChildCandidates( FilterChildCandidates(
guid, guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent); relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
@ -322,6 +437,7 @@ public sealed class ParentAttachmentState
/// </summary> /// </summary>
public void DeleteGeneration(uint guid, ushort deletedGeneration) public void DeleteGeneration(uint guid, ushort deletedGeneration)
{ {
CancelDeferredChildGeneration(guid, deletedGeneration);
FilterChildCandidates( FilterChildCandidates(
guid, guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent); relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
@ -351,6 +467,7 @@ public sealed class ParentAttachmentState
public void RemoveChild(uint childGuid) public void RemoveChild(uint childGuid)
{ {
RemoveDeferredChildCreates(childGuid);
_stagedByChild.Remove(childGuid); _stagedByChild.Remove(childGuid);
_recoveryByChild.Remove(childGuid); _recoveryByChild.Remove(childGuid);
RemoveCommittedChild(childGuid); RemoveCommittedChild(childGuid);
@ -359,6 +476,7 @@ public sealed class ParentAttachmentState
public void Clear() public void Clear()
{ {
_deferredCreatesByParent.Clear();
_unresolvedByChild.Clear(); _unresolvedByChild.Clear();
_stagedByChild.Clear(); _stagedByChild.Clear();
_recoveryByChild.Clear(); _recoveryByChild.Clear();
@ -368,6 +486,26 @@ public sealed class ParentAttachmentState
_committedChildrenByParent.Clear(); _committedChildrenByParent.Clear();
} }
private void RemoveDeferredChildCreates(uint childGuid)
=> FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid);
private void FilterDeferredCreates(
Func<DeferredParentCreate, bool> retain)
{
uint[] parents = _deferredCreatesByParent.Keys.ToArray();
for (int index = 0; index < parents.Length; index++)
{
uint parentGuid = parents[index];
Queue<DeferredParentCreate> retained = new(
_deferredCreatesByParent[parentGuid].Where(retain));
if (retained.Count == 0)
_deferredCreatesByParent.Remove(parentGuid);
else
_deferredCreatesByParent[parentGuid] = retained;
}
}
private void RemoveCommittedChild(uint childGuid) private void RemoveCommittedChild(uint childGuid)
{ {
if (!_lastAcceptedByChild.Remove( if (!_lastAcceptedByChild.Remove(
@ -462,6 +600,15 @@ public sealed class ParentAttachmentState
ushort InstanceSequence); ushort InstanceSequence);
} }
internal readonly record struct DeferredParentCreate(
ulong AdmissionId,
WorldSession.EntitySpawn Spawn,
bool IsLocalPlayer)
{
internal bool IsValid => AdmissionId != 0UL
&& Spawn.Guid != 0u;
}
public readonly record struct ParentAttachmentRelation( public readonly record struct ParentAttachmentRelation(
uint ParentGuid, uint ParentGuid,
uint ChildGuid, uint ChildGuid,

View file

@ -1,4 +1,5 @@
using AcDream.Core.Net; using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics; using AcDream.Core.Physics;
using AcDream.Runtime.Physics; using AcDream.Runtime.Physics;
@ -47,6 +48,10 @@ public sealed class RuntimeEntityDirectory
public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) => public InboundCreateResult AcceptCreate(WorldSession.EntitySpawn incoming) =>
_inbound.AcceptCreate(incoming); _inbound.AcceptCreate(incoming);
internal InboundCreateResult AcceptCreateDeferredSameGeneration(
WorldSession.EntitySpawn incoming) =>
_inbound.AcceptCreateDeferredSameGeneration(incoming);
public CreateObjectTimestampDisposition PreviewCreateDisposition( public CreateObjectTimestampDisposition PreviewCreateDisposition(
WorldSession.EntitySpawn incoming) => WorldSession.EntitySpawn incoming) =>
_inbound.PreviewCreateDisposition(incoming); _inbound.PreviewCreateDisposition(incoming);
@ -59,6 +64,11 @@ public sealed class RuntimeEntityDirectory
public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) => public bool TryGetSnapshot(uint guid, out WorldSession.EntitySpawn spawn) =>
_inbound.TryGetSnapshot(guid, out 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) => public bool TryGetActive(uint guid, out RuntimeEntityRecord record) =>
_activeByGuid.TryGetValue(guid, out record!); _activeByGuid.TryGetValue(guid, out record!);
@ -482,6 +492,58 @@ public sealed class RuntimeEntityDirectory
out accepted, out accepted,
out timestamps); 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) => public bool IsFreshTeleportStart(uint guid, ushort teleportSequence) =>
_inbound.IsFreshTeleportStart(guid, teleportSequence); _inbound.IsFreshTeleportStart(guid, teleportSequence);

View file

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using AcDream.Core.Items; using AcDream.Core.Items;
using AcDream.Core.Net; using AcDream.Core.Net;
using AcDream.Core.Net.Messages; using AcDream.Core.Net.Messages;
@ -11,7 +12,8 @@ public readonly record struct RuntimeEntityRegistrationResult(
RuntimeEntityRecord? Canonical, RuntimeEntityRecord? Canonical,
bool LogicalRegistrationCreated, bool LogicalRegistrationCreated,
bool ReplacedExistingGeneration, bool ReplacedExistingGeneration,
Exception? PriorGenerationCleanupFailure = null); Exception? PriorGenerationCleanupFailure = null,
bool DeferredForParent = false);
public readonly record struct RuntimeEntityObjectOwnershipSnapshot( public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int ActiveEntityCount, int ActiveEntityCount,
@ -19,6 +21,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int ClaimedLocalIdCount, int ClaimedLocalIdCount,
int AcceptedSnapshotCount, int AcceptedSnapshotCount,
int UnresolvedParentRelationCount, int UnresolvedParentRelationCount,
int DeferredParentCreateCount,
int StagedParentRelationCount, int StagedParentRelationCount,
int RecoveryParentRelationCount, int RecoveryParentRelationCount,
int CommittedParentRelationCount, int CommittedParentRelationCount,
@ -44,6 +47,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& ClaimedLocalIdCount == 0 && ClaimedLocalIdCount == 0
&& AcceptedSnapshotCount == 0 && AcceptedSnapshotCount == 0
&& UnresolvedParentRelationCount == 0 && UnresolvedParentRelationCount == 0
&& DeferredParentCreateCount == 0
&& StagedParentRelationCount == 0 && StagedParentRelationCount == 0
&& RecoveryParentRelationCount == 0 && RecoveryParentRelationCount == 0
&& CommittedParentRelationCount == 0 && CommittedParentRelationCount == 0
@ -205,6 +209,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Entities.ClaimedLocalIdCount, Entities.ClaimedLocalIdCount,
Entities.Snapshots.Count, Entities.Snapshots.Count,
parents.UnresolvedRelationCount, parents.UnresolvedRelationCount,
parents.DeferredCreateCount,
parents.StagedRelationCount, parents.StagedRelationCount,
parents.RecoveryRelationCount, parents.RecoveryRelationCount,
parents.CommittedRelationCount, parents.CommittedRelationCount,
@ -272,6 +277,35 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
throw new InvalidOperationException( throw new InvalidOperationException(
"A Runtime entity cannot register while its session lifetime is clearing."); "A Runtime entity cannot register while its session lifetime is clearing.");
} }
if (beginInitialResidence
&& !HasConsistentCreateIdentityAndParent(incoming))
{
throw new InvalidOperationException(
$"CreateObject 0x{incoming.Guid:X8} has inconsistent instance or parent projections.");
}
if (beginInitialResidence)
incoming = RuntimeInitialCreateAdmissionFreezer.Freeze(incoming);
uint parentGuid = incoming.ParentGuid
?? incoming.Physics?.Parent?.Guid
?? 0u;
if (beginInitialResidence
&& parentGuid != 0u
&& !Entities.TryGetActive(parentGuid, out _))
{
// SmartBox::HandleCreateObject resolves a nonzero parent before
// object lookup or timestamp admission. Retain the complete raw
// CreateObject so no child gate/canonical/projection state can
// escape before the parent becomes addressable.
Entities.ParentAttachments.EnqueueDeferredCreate(
incoming,
isLocalPlayer);
return new RuntimeEntityRegistrationResult(
SupersededCreateResult(),
Canonical: null,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false,
DeferredForParent: true);
}
CreateObjectTimestampDisposition preview = CreateObjectTimestampDisposition preview =
Entities.PreviewCreateDisposition(incoming); Entities.PreviewCreateDisposition(incoming);
bool requiresFreshResidenceAdmission = preview is bool requiresFreshResidenceAdmission = preview is
@ -285,7 +319,44 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
$"CreateObject 0x{incoming.Guid:X8} cannot acquire a structurally valid initial residence lease."); $"CreateObject 0x{incoming.Guid:X8} cannot acquire a structurally valid initial residence lease.");
} }
InboundCreateResult result = Entities.AcceptCreate(incoming); RuntimeEntityRecord? pendingResidenceRecord = null;
RuntimeInitialCreateResidenceLease pendingResidence = default;
bool admitIntoPendingResidence = beginInitialResidence
&& preview is CreateObjectTimestampDisposition.ExistingGeneration
&& Entities.TryGetActive(
incoming.Guid,
out pendingResidenceRecord)
&& InitialCreateResidences.TryGetTransaction(
pendingResidenceRecord,
out pendingResidence);
if (admitIntoPendingResidence
&& !InitialCreateResidences.CanEnqueue(
pendingResidenceRecord!,
pendingResidence))
{
throw new InvalidOperationException(
$"CreateObject 0x{incoming.Guid:X8} cannot append to its pending initial residence FIFO.");
}
if (admitIntoPendingResidence
&& !IsStructurallyValidDeferredCreate(incoming))
{
_ = Entities.TryGetAcceptedTimestamps(
incoming.Guid,
out AcceptedPhysicsTimestamps timestamps);
return new RuntimeEntityRegistrationResult(
new InboundCreateResult(
CreateObjectTimestampDisposition.ExistingGeneration,
pendingResidenceRecord!.Snapshot,
SameGenerationEvents: null,
timestamps),
pendingResidenceRecord,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
InboundCreateResult result = admitIntoPendingResidence
? Entities.AcceptCreateDeferredSameGeneration(incoming)
: Entities.AcceptCreate(incoming);
if (result.Disposition if (result.Disposition
is CreateObjectTimestampDisposition.StaleGeneration) is CreateObjectTimestampDisposition.StaleGeneration)
{ {
@ -307,6 +378,33 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
incoming.Guid, incoming.Guid,
out RuntimeEntityRecord retained)) out RuntimeEntityRecord retained))
{ {
if (admitIntoPendingResidence)
{
if (!ReferenceEquals(retained, pendingResidenceRecord)
|| !AdmitSameGenerationCreate(
retained,
pendingResidence,
incoming,
result,
isLocalPlayer))
{
throw FailInitialResidenceRegistration(
retained,
publishDeleted: true);
}
InboundCreateResult dormant = result with
{
Snapshot = retained.Snapshot,
SameGenerationEvents = null,
};
return new RuntimeEntityRegistrationResult(
dormant,
retained,
LogicalRegistrationCreated: false,
ReplacedExistingGeneration: false);
}
// Existing-generation CreateObject contributes untimestamped // Existing-generation CreateObject contributes untimestamped
// description fields here. Position/Parent/Pickup/State/etc. // description fields here. Position/Parent/Pickup/State/etc.
// remain separate freshness-gated events and must not churn a // remain separate freshness-gated events and must not churn a
@ -592,6 +690,33 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out WorldSession.EntitySpawn accepted) out WorldSession.EntitySpawn accepted)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.Guid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
return false;
}
bool acceptedByGate = Entities.TryAcceptDeferredObjDesc(
update,
out _);
accepted = pending.Snapshot;
if (!acceptedByGate)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.ObjDesc,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.ObjDesc,
update.Guid,
ObjDesc: update));
return true;
}
bool applied = Entities.TryApplyObjDesc(update, out accepted); bool applied = Entities.TryApplyObjDesc(update, out accepted);
if (!applied if (!applied
|| !Entities.TryGetActive( || !Entities.TryGetActive(
@ -617,6 +742,33 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out WorldSession.EntitySpawn accepted) out WorldSession.EntitySpawn accepted)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.Guid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
return false;
}
bool acceptedByGate = Entities.TryAcceptDeferredPickup(
update,
out _);
accepted = pending.Snapshot;
if (!acceptedByGate)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.Pickup,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Pickup,
update.Guid,
Pickup: update));
return true;
}
bool applied = Entities.TryApplyPickup(update, out accepted); bool applied = Entities.TryApplyPickup(update, out accepted);
if (!applied if (!applied
|| !Entities.TryGetActive( || !Entities.TryGetActive(
@ -655,6 +807,14 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out WorldSession.EntitySpawn accepted) out WorldSession.EntitySpawn accepted)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.ChildGuid,
out _,
out _))
{
throw new InvalidOperationException(
"A CreateObject parent relation must be admitted inside its atomic same-generation Create envelope.");
}
bool applied = Entities.TryApplyCreateParent(update, out accepted); bool applied = Entities.TryApplyCreateParent(update, out accepted);
return CommitPositionChannelUpdate( return CommitPositionChannelUpdate(
applied, applied,
@ -669,6 +829,44 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out WorldSession.EntitySpawn accepted) out WorldSession.EntitySpawn accepted)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.ChildGuid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
return false;
}
if (!Entities.TryGetActive(
update.ParentGuid,
out RuntimeEntityRecord parent)
|| parent.Incarnation != update.ParentInstanceSequence)
{
Entities.ParentAttachments.Enqueue(update);
accepted = pending.Snapshot;
return false;
}
bool acceptedByGate = Entities.TryAcceptDeferredParent(
update,
out AcceptedPhysicsTimestamps timestamps);
accepted = pending.Snapshot;
if (!acceptedByGate)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.Parent,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Parent,
update.ChildGuid,
Parent: update,
AcceptedTimestamps: timestamps));
return true;
}
bool applied = Entities.TryApplyParent(update, out accepted); bool applied = Entities.TryApplyParent(update, out accepted);
return CommitPositionChannelUpdate( return CommitPositionChannelUpdate(
applied, applied,
@ -750,6 +948,39 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out AcceptedPhysicsTimestamps timestamps) out AcceptedPhysicsTimestamps timestamps)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.Guid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
timestamps = default;
return false;
}
bool payloadApplied = Entities.TryAcceptDeferredMotion(
update,
out timestamps,
out bool timestampMutation);
accepted = pending.Snapshot;
if (!payloadApplied && !timestampMutation)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.Movement,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Movement,
update.Guid,
Movement: update,
AcceptedTimestamps: timestamps,
AppliesMovementPayload: payloadApplied,
RetainMovementPayload: retainPayload,
HasTimestampMutation: timestampMutation));
return payloadApplied;
}
bool applied = Entities.TryApplyMotion( bool applied = Entities.TryApplyMotion(
update, update,
retainPayload, retainPayload,
@ -790,6 +1021,35 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out WorldSession.EntitySpawn accepted) out WorldSession.EntitySpawn accepted)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.Guid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!IsFinite(update.Velocity)
|| !IsFinite(update.Omega)
|| !InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
return false;
}
bool acceptedByGate = Entities.TryAcceptDeferredVector(
update,
out _);
accepted = pending.Snapshot;
if (!acceptedByGate)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.Vector,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Vector,
update.Guid,
Vector: update));
return true;
}
bool applied = Entities.TryApplyVector(update, out accepted); bool applied = Entities.TryApplyVector(update, out accepted);
if (!applied if (!applied
|| !Entities.TryGetActive( || !Entities.TryGetActive(
@ -817,6 +1077,35 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out RetailPhysicsStateTransition transition) out RetailPhysicsStateTransition transition)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (TryGetPendingInitialResidence(
update.Guid,
out RuntimeEntityRecord pending,
out RuntimeInitialCreateResidenceLease lease))
{
if (!InitialCreateResidences.CanEnqueue(pending, lease))
{
accepted = pending.Snapshot;
transition = default;
return false;
}
bool acceptedByGate = Entities.TryAcceptDeferredState(
update,
out _);
accepted = pending.Snapshot;
transition = default;
if (!acceptedByGate)
return false;
EnqueueDormant(
pending,
lease,
RuntimeInitialCreateContinuationKind.State,
RuntimeAcceptedPositionSource.Unknown,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.State,
update.Guid,
State: update));
return true;
}
bool applied = Entities.TryApplyState(update, out accepted); bool applied = Entities.TryApplyState(update, out accepted);
transition = default; transition = default;
if (!applied if (!applied
@ -883,24 +1172,58 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out AcceptedPhysicsTimestamps timestamps) out AcceptedPhysicsTimestamps timestamps)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
RuntimeInitialCreateResidenceLease priorInitialResidence = default; if (TryGetPendingInitialResidence(
bool hadPendingInitialResidence =
Entities.TryGetActive(
update.Guid, update.Guid,
out RuntimeEntityRecord pendingCanonical) out RuntimeEntityRecord pendingCanonical,
&& InitialCreateResidences.TryGetTransaction( out RuntimeInitialCreateResidenceLease pendingLease))
pendingCanonical,
out priorInitialResidence);
if (hadPendingInitialResidence
&& !InitialCreateResidences.CanEnqueueAcceptedPosition(
pendingCanonical,
priorInitialResidence,
update))
{ {
disposition = PositionTimestampDisposition.Rejected; if (!RuntimeAuthoritativePositionRouteClassifier
accepted = default; .IsValidCreateWirePosition(update.Position)
timestamps = default; || update.Velocity is { } velocity
return false; && !IsFinite(velocity)
|| !InitialCreateResidences.CanEnqueue(
pendingCanonical,
pendingLease))
{
disposition = PositionTimestampDisposition.Rejected;
accepted = default;
timestamps = default;
return false;
}
bool deferredKnown = Entities.TryAcceptDeferredPosition(
update,
isLocalPlayer,
out disposition,
out timestamps,
out bool timestampMutation);
accepted = pendingCanonical.Snapshot;
if (!deferredKnown)
return false;
if (disposition is PositionTimestampDisposition.Rejected
&& !timestampMutation)
{
return true;
}
EnqueueDormant(
pendingCanonical,
pendingLease,
RuntimeInitialCreateContinuationKind.Position,
RuntimeAcceptedPositionSource.PositionEvent,
new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Position,
update.Guid,
Position: update,
PositionSource:
RuntimeAcceptedPositionSource.PositionEvent,
PositionDisposition: disposition,
PreviousTeleportSequence:
timestamps.PreviousTeleport,
AcceptedTimestamps: timestamps,
HasTimestampMutation: timestampMutation));
return true;
} }
bool hadCanonical = Entities.TryGetActive( bool hadCanonical = Entities.TryGetActive(
update.Guid, update.Guid,
@ -938,43 +1261,6 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
|| projectionRequiresTeleportHook, || projectionRequiresTeleportHook,
}; };
} }
// Initial CreateObject residence is one immutable transaction. A
// Position packet accepted before its 0x11 placement completes is
// retained in the transaction's ordered continuation batch; it must
// not replace the initial mover operation, mutate the Runtime record
// used to prepare that operation, or escape through a host callback.
if (hadPendingInitialResidence)
{
if (!acceptedPosition)
return true;
if (!ReferenceEquals(canonical, pendingCanonical))
throw FailInitialResidenceRegistration(
canonical,
publishDeleted: true);
RuntimeInitialCreateResidenceLease retained =
InitialCreateResidences.EnqueueAcceptedPosition(
canonical,
priorInitialResidence,
update,
accepted,
disposition,
timestamps,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
projectionRequiresTeleportHook);
if (!retained.IsValid)
{
throw FailInitialResidenceRegistration(
canonical,
publishDeleted: true);
}
return true;
}
RuntimePlacementCancellationReceipt cancellation = default; RuntimePlacementCancellationReceipt cancellation = default;
if (acceptedPosition) if (acceptedPosition)
{ {
@ -1118,6 +1404,15 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
out RuntimeEntityDeleteAcceptance acceptance) out RuntimeEntityDeleteAcceptance acceptance)
{ {
EnsureNotDisposed(); EnsureNotDisposed();
if (!isLocalPlayer)
{
// A child whose complete raw CreateObject is waiting on a missing
// parent has no timestamp gate yet. Cancel its exact/older raw
// generation before the normal known-object delete gate returns.
Entities.ParentAttachments.CancelDeferredChildGeneration(
delete.Guid,
delete.InstanceSequence);
}
if (!Entities.TryDelete(delete, isLocalPlayer)) if (!Entities.TryDelete(delete, isLocalPlayer))
{ {
acceptance = null!; acceptance = null!;
@ -1414,6 +1709,288 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Entities.IsCurrent(canonical) Entities.IsCurrent(canonical)
&& matchesCommittedMutation(); && matchesCommittedMutation();
private bool TryGetPendingInitialResidence(
uint guid,
out RuntimeEntityRecord canonical,
out RuntimeInitialCreateResidenceLease lease)
{
if (Entities.TryGetActive(guid, out canonical)
&& InitialCreateResidences.TryGetTransaction(
canonical,
out lease))
{
return true;
}
canonical = null!;
lease = default;
return false;
}
private void EnqueueDormant(
RuntimeEntityRecord canonical,
in RuntimeInitialCreateResidenceLease prior,
RuntimeInitialCreateContinuationKind kind,
RuntimeAcceptedPositionSource positionSource,
in RuntimeInitialCreateTailAction action)
{
RuntimeInitialCreateResidenceLease retained = InitialCreateResidences
.EnqueueAccepted(
canonical,
prior,
kind,
positionSource,
ImmutableArray.Create(action));
if (!retained.IsValid)
{
throw new InvalidOperationException(
$"Accepted {kind} for 0x{canonical.ServerGuid:X8}/{canonical.Incarnation} could not be retained by its initial-placement FIFO.");
}
}
private static bool IsFinite(System.Numerics.Vector3 value) =>
float.IsFinite(value.X)
&& float.IsFinite(value.Y)
&& float.IsFinite(value.Z);
private static bool HasConsistentCreateIdentityAndParent(
in WorldSession.EntitySpawn incoming)
{
if (incoming.Guid == 0u)
return false;
bool hasTopParentGuid = incoming.ParentGuid is not null;
bool hasTopParentLocation = incoming.ParentLocation is not null;
if (hasTopParentGuid != hasTopParentLocation)
return false;
if (incoming.Physics is not { } physics)
{
// These flattened values are parser projections of PhysicsDesc.
// Without that source block, accepting any of them would create
// contradictory admission authority before the raw create is
// either queued for a parent or assigned a residence lease.
return incoming.Position is null
&& incoming.SetupTableId is null
&& incoming.MotionState is null
&& incoming.MotionTableId is null
&& incoming.PhysicsState is null
&& incoming.ObjScale is null
&& incoming.Friction is null
&& incoming.Elasticity is null
&& incoming.InstanceSequence == 0
&& incoming.MovementSequence == 0
&& incoming.ServerControlSequence == 0
&& incoming.PositionSequence == 0
&& !hasTopParentGuid
&& incoming.PlacementId is null;
}
if (physics.Timestamps.Instance != incoming.InstanceSequence
|| physics.Timestamps.Position != incoming.PositionSequence
|| physics.Timestamps.Movement != incoming.MovementSequence
|| physics.Timestamps.ServerControlledMove
!= incoming.ServerControlSequence
|| physics.Position != incoming.Position)
return false;
PhysicsAttachment? flattenedParent = incoming.ParentGuid is { } parentGuid
&& incoming.ParentLocation is { } parentLocation
? new PhysicsAttachment(parentGuid, parentLocation)
: null;
if (flattenedParent != physics.Parent
|| incoming.PlacementId != physics.AnimationFrame)
{
return false;
}
return true;
}
private static bool IsStructurallyValidDeferredCreate(
in WorldSession.EntitySpawn incoming)
{
if (incoming.Guid == 0u)
return false;
if (incoming.Physics is not { } physics)
return true;
if (physics.Parent is null
&& physics.Position is { LandblockId: not 0u } position
&& !RuntimeAuthoritativePositionRouteClassifier
.IsValidCreateWirePosition(position))
{
return false;
}
if (physics.Velocity is { } velocity && !IsFinite(velocity)
|| physics.Acceleration is { } acceleration
&& !IsFinite(acceleration)
|| physics.AngularVelocity is { } angularVelocity
&& !IsFinite(angularVelocity)
|| physics.Scale is { } scale && !float.IsFinite(scale)
|| physics.Friction is { } friction && !float.IsFinite(friction)
|| physics.Elasticity is { } elasticity
&& !float.IsFinite(elasticity)
|| physics.Translucency is { } translucency
&& !float.IsFinite(translucency))
{
return false;
}
return true;
}
private bool AdmitSameGenerationCreate(
RuntimeEntityRecord canonical,
in RuntimeInitialCreateResidenceLease prior,
in WorldSession.EntitySpawn incoming,
in InboundCreateResult admitted,
bool isLocalPlayer)
{
if (!ReferenceEquals(
canonical,
Entities.TryGetActive(
incoming.Guid,
out RuntimeEntityRecord current)
? current
: null)
|| canonical.Incarnation != incoming.InstanceSequence
|| admitted.Disposition
is not CreateObjectTimestampDisposition.ExistingGeneration
|| !InitialCreateResidences.CanEnqueue(canonical, prior))
{
return false;
}
var actions = ImmutableArray.CreateBuilder<
RuntimeInitialCreateTailAction>();
RuntimeAcceptedPositionSource positionSource =
RuntimeAcceptedPositionSource.Unknown;
if (admitted.SameGenerationEvents is { } events)
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind
.PreTailDescriptionAdaptation,
incoming.Guid,
Description: events.Description));
if (Entities.TryAcceptDeferredObjDesc(
events.Appearance,
out _))
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.ObjDesc,
incoming.Guid,
ObjDesc: events.Appearance));
}
if (events.Parent is { } parent)
{
if (Entities.TryAcceptDeferredCreateParent(
parent,
out _))
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.CreateParent,
incoming.Guid,
CreateParent: parent));
}
}
else if (events.Position is { } position)
{
if (!RuntimeAuthoritativePositionRouteClassifier
.IsValidCreateWirePosition(position.Position)
|| position.Velocity is { } velocity
&& !IsFinite(velocity))
{
return false;
}
if (!Entities.TryAcceptDeferredPosition(
position,
isLocalPlayer,
out PositionTimestampDisposition disposition,
out AcceptedPhysicsTimestamps timestamps,
out bool timestampMutation))
{
return false;
}
if (disposition is not PositionTimestampDisposition.Rejected
|| timestampMutation)
{
positionSource = RuntimeAcceptedPositionSource
.SameIncarnationCreate;
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Position,
incoming.Guid,
Position: position,
PositionSource: positionSource,
PositionDisposition: disposition,
PreviousTeleportSequence:
timestamps.PreviousTeleport,
AcceptedTimestamps: timestamps,
HasTimestampMutation: timestampMutation));
}
}
else if (events.Pickup is { } pickup
&& Entities.TryAcceptDeferredPickup(pickup, out _))
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Pickup,
incoming.Guid,
Pickup: pickup));
}
if (events.Movement is { } movement)
{
bool payloadApplied = Entities.TryAcceptDeferredMotion(
movement,
out AcceptedPhysicsTimestamps timestamps,
out bool timestampMutation);
if (payloadApplied || timestampMutation)
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Movement,
incoming.Guid,
Movement: movement,
AcceptedTimestamps: timestamps,
AppliesMovementPayload: payloadApplied,
RetainMovementPayload: true,
HasTimestampMutation: timestampMutation));
}
}
if (Entities.TryAcceptDeferredState(events.State, out _))
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.State,
incoming.Guid,
State: events.State));
}
if (Entities.TryAcceptDeferredVector(events.Vector, out _))
{
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.Vector,
incoming.Guid,
Vector: events.Vector));
}
}
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.WeenieDescription,
incoming.Guid,
WeenieDescription: incoming));
actions.Add(new RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind.ResidentCellCleanup,
incoming.Guid));
RuntimeInitialCreateResidenceLease retained = InitialCreateResidences
.EnqueueAccepted(
canonical,
prior,
RuntimeInitialCreateContinuationKind.SameIncarnationCreate,
positionSource,
actions.ToImmutable());
return retained.IsValid;
}
private void EnsureNotDisposed() => private void EnsureNotDisposed() =>
ObjectDisposedException.ThrowIf(_disposed, this); ObjectDisposedException.ThrowIf(_disposed, this);

View file

@ -0,0 +1,92 @@
using System.Collections.Immutable;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
namespace AcDream.Runtime.Entities;
/// <summary>
/// Takes ownership of decoded wire collections retained beyond the inbound
/// callback. The protocol records expose read-only views, but those views may
/// still wrap parser-owned arrays. Initial-placement admission must therefore
/// copy every collection before an asynchronous residence lease can retain it.
/// </summary>
internal static class RuntimeInitialCreateAdmissionFreezer
{
internal static WorldSession.EntitySpawn Freeze(
in WorldSession.EntitySpawn spawn) => spawn with
{
AnimPartChanges = spawn.AnimPartChanges.ToImmutableArray(),
TextureChanges = spawn.TextureChanges.ToImmutableArray(),
SubPalettes = spawn.SubPalettes.ToImmutableArray(),
MotionState = Freeze(spawn.MotionState),
Physics = Freeze(spawn.Physics),
};
internal static ObjDescEvent.Parsed Freeze(
in ObjDescEvent.Parsed update) => update with
{
ModelData = Freeze(update.ModelData),
};
internal static WorldSession.EntityMotionUpdate Freeze(
in WorldSession.EntityMotionUpdate update) => update with
{
MotionState = Freeze(update.MotionState),
};
internal static RuntimeInitialCreateTailAction Freeze(
in RuntimeInitialCreateTailAction action) => action with
{
Description = Freeze(action.Description),
ObjDesc = action.ObjDesc is { } objDesc
? Freeze(objDesc)
: null,
Movement = action.Movement is { } movement
? Freeze(movement)
: null,
WeenieDescription = action.WeenieDescription is { } spawn
? Freeze(spawn)
: null,
};
private static CreateObject.ModelData Freeze(
in CreateObject.ModelData model) => model with
{
SubPalettes = model.SubPalettes.ToImmutableArray(),
TextureChanges = model.TextureChanges.ToImmutableArray(),
AnimPartChanges = model.AnimPartChanges.ToImmutableArray(),
};
private static CreateObject.ServerMotionState Freeze(
in CreateObject.ServerMotionState motion) => motion with
{
Commands = motion.Commands?.ToImmutableArray(),
};
private static CreateObject.ServerMotionState? Freeze(
CreateObject.ServerMotionState? motion) => motion is { } value
? Freeze(value)
: null;
private static PhysicsSpawnData? Freeze(PhysicsSpawnData? physics)
{
if (physics is not { } value)
return null;
PhysicsMovementData? movement = value.Movement is { } source
? source with
{
RawData = source.RawData.ToArray(),
MotionState = Freeze(source.MotionState),
}
: null;
ReadOnlyMemory<PhysicsAttachment>? children = value.Children is { } list
? list.ToArray()
: null;
return value with
{
Movement = movement,
Children = children,
};
}
}

View file

@ -30,11 +30,14 @@ internal readonly record struct RuntimeInitialCreateResidenceLease(
RuntimeInitialCreateResidenceToken Token, RuntimeInitialCreateResidenceToken Token,
RuntimeAuthoritativePositionRoute Route, RuntimeAuthoritativePositionRoute Route,
RuntimeEntityPlacementToken Placement, RuntimeEntityPlacementToken Placement,
WorldSession.EntitySpawn InitialCreate,
ImmutableArray<RuntimeInitialCreateResidenceContinuation> Continuations) ImmutableArray<RuntimeInitialCreateResidenceContinuation> Continuations)
{ {
internal bool IsValid => Token.IsValid internal bool IsValid => Token.IsValid
&& Route.Accepted && Route.Accepted
&& Route.Authority.Entity == Token.Entity && Route.Authority.Entity == Token.Entity
&& InitialCreate.Guid != 0u
&& InitialCreate.InstanceSequence == Token.Entity.Incarnation
&& !Continuations.IsDefault && !Continuations.IsDefault
&& HasValidContinuationChain() && HasValidContinuationChain()
&& (!Route.PerformsSetPosition && (!Route.PerformsSetPosition
@ -43,7 +46,7 @@ internal readonly record struct RuntimeInitialCreateResidenceLease(
private bool HasValidContinuationChain() private bool HasValidContinuationChain()
{ {
ushort previousTeleport = Route.Authority.AcceptedTeleportSequence; uint ownerGuid = InitialCreate.Guid;
for (int index = 0; index < Continuations.Length; index++) for (int index = 0; index < Continuations.Length; index++)
{ {
RuntimeInitialCreateResidenceContinuation continuation = RuntimeInitialCreateResidenceContinuation continuation =
@ -51,52 +54,291 @@ internal readonly record struct RuntimeInitialCreateResidenceLease(
if (!continuation.IsValid if (!continuation.IsValid
|| continuation.Sequence != (ulong)index + 1UL || continuation.Sequence != (ulong)index + 1UL
|| continuation.InstanceSequence != Token.Entity.Incarnation || continuation.InstanceSequence != Token.Entity.Incarnation
|| continuation.PositionAuthorityVersion || continuation.Actions.Any(
!= Token.PositionAuthorityVersion action => action.OwnerGuid != ownerGuid))
|| continuation.PreviousTeleportSequence
!= previousTeleport)
{ {
return false; return false;
} }
previousTeleport = continuation.AcceptedTeleportSequence;
} }
return true; return true;
} }
} }
internal enum RuntimeInitialCreateContinuationKind : byte
{
SameIncarnationCreate,
ObjDesc,
Parent,
Pickup,
Position,
Movement,
State,
Vector,
}
internal enum RuntimeInitialCreateTailActionKind : byte
{
/// <summary>
/// Isolated AP-119 compatibility behavior. Retail's equal-generation
/// Create tail starts at ObjDesc and does not call set_description again.
/// </summary>
PreTailDescriptionAdaptation,
ObjDesc,
CreateParent,
Parent,
Pickup,
Position,
Movement,
State,
Vector,
WeenieDescription,
ResidentCellCleanup,
}
internal readonly record struct RuntimeInitialCreateTailAction(
RuntimeInitialCreateTailActionKind Kind,
uint OwnerGuid,
PhysicsSpawnData? Description = null,
ObjDescEvent.Parsed? ObjDesc = null,
CreateParentUpdate? CreateParent = null,
ParentEvent.Parsed? Parent = null,
PickupEvent.Parsed? Pickup = null,
WorldSession.EntityPositionUpdate? Position = null,
WorldSession.EntityMotionUpdate? Movement = null,
SetState.Parsed? State = null,
VectorUpdate.Parsed? Vector = null,
WorldSession.EntitySpawn? WeenieDescription = null,
RuntimeAcceptedPositionSource PositionSource =
RuntimeAcceptedPositionSource.Unknown,
PositionTimestampDisposition PositionDisposition =
PositionTimestampDisposition.Rejected,
ushort PreviousTeleportSequence = 0,
AcceptedPhysicsTimestamps AcceptedTimestamps = default,
bool AppliesMovementPayload = false,
bool RetainMovementPayload = true,
bool HasTimestampMutation = false)
{
internal bool IsStructurallyValid => HasExclusivePayload()
&& Kind switch
{
RuntimeInitialCreateTailActionKind.PreTailDescriptionAdaptation =>
Description is not null,
RuntimeInitialCreateTailActionKind.ObjDesc => ObjDesc is { } objDesc
&& objDesc.Guid == OwnerGuid,
RuntimeInitialCreateTailActionKind.CreateParent =>
CreateParent is { } createParent
&& createParent.ChildGuid == OwnerGuid,
RuntimeInitialCreateTailActionKind.Parent => Parent is { } parent
&& parent.ChildGuid == OwnerGuid,
RuntimeInitialCreateTailActionKind.Pickup => Pickup is { } pickup
&& pickup.Guid == OwnerGuid,
RuntimeInitialCreateTailActionKind.Position => Position is { } position
&& position.Guid == OwnerGuid
&& PositionSource is RuntimeAcceptedPositionSource.PositionEvent
or RuntimeAcceptedPositionSource.SameIncarnationCreate
&& (PositionDisposition is PositionTimestampDisposition.Apply
or PositionTimestampDisposition.ForcePosition
|| PositionDisposition is PositionTimestampDisposition.Rejected
&& HasTimestampMutation),
RuntimeInitialCreateTailActionKind.Movement => Movement is { } movement
&& movement.Guid == OwnerGuid
&& (AppliesMovementPayload || HasTimestampMutation),
RuntimeInitialCreateTailActionKind.State => State is { } state
&& state.Guid == OwnerGuid,
RuntimeInitialCreateTailActionKind.Vector => Vector is { } vector
&& vector.Guid == OwnerGuid,
RuntimeInitialCreateTailActionKind.WeenieDescription =>
WeenieDescription is { } weenie
&& weenie.Guid == OwnerGuid,
RuntimeInitialCreateTailActionKind.ResidentCellCleanup => true,
_ => false,
};
internal bool MatchesInstance(ushort instanceSequence) => Kind switch
{
RuntimeInitialCreateTailActionKind.PreTailDescriptionAdaptation =>
Description is { } description
&& description.Timestamps.Instance == instanceSequence,
RuntimeInitialCreateTailActionKind.ObjDesc =>
ObjDesc is { } objDesc
&& objDesc.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.CreateParent =>
CreateParent is { } createParent
&& createParent.ChildInstanceSequence == instanceSequence,
// ParentEvent carries only the parent's INSTANCE_TS. Acceptance
// already exact-gated the child's current incarnation; preserve that
// proof in AcceptedTimestamps for the later no-regate executor.
RuntimeInitialCreateTailActionKind.Parent =>
AcceptedTimestamps.Instance == instanceSequence,
RuntimeInitialCreateTailActionKind.Pickup =>
Pickup is { } pickup
&& pickup.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.Position =>
Position is { } position
&& position.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.Movement =>
Movement is { } movement
&& movement.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.State =>
State is { } state
&& state.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.Vector =>
Vector is { } vector
&& vector.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.WeenieDescription =>
WeenieDescription is { } weenie
&& weenie.InstanceSequence == instanceSequence,
RuntimeInitialCreateTailActionKind.ResidentCellCleanup => true,
_ => false,
};
private bool HasExclusivePayload()
{
int payloadCount = (Description is null ? 0 : 1)
+ (ObjDesc is null ? 0 : 1)
+ (CreateParent is null ? 0 : 1)
+ (Parent is null ? 0 : 1)
+ (Pickup is null ? 0 : 1)
+ (Position is null ? 0 : 1)
+ (Movement is null ? 0 : 1)
+ (State is null ? 0 : 1)
+ (Vector is null ? 0 : 1)
+ (WeenieDescription is null ? 0 : 1);
return Kind is RuntimeInitialCreateTailActionKind.ResidentCellCleanup
? payloadCount == 0
: payloadCount == 1;
}
}
/// <summary> /// <summary>
/// One accepted Position packet which arrived before the immutable initial /// One immutable inbound envelope which arrived while retail's synchronous
/// CreateObject admission completed. These records remain dormant until a /// CreateObject critical section was virtualized by an asynchronous initial
/// host adopts the completed batch; accepting them never replaces or mutates /// SetPosition. Envelopes are never coalesced. A same-incarnation Create is
/// the initial 0x11 SetPosition operation. /// one atomic envelope whose ordered tail is expanded only by the executor.
/// Position retains the raw PositionPack and is classified only at the FIFO
/// head, after every earlier envelope has committed.
/// </summary> /// </summary>
internal readonly record struct RuntimeInitialCreateResidenceContinuation( internal readonly record struct RuntimeInitialCreateResidenceContinuation(
ulong Sequence, ulong Sequence,
RuntimePositionEntityKind EntityKind, RuntimeInitialCreateContinuationKind Kind,
ushort InstanceSequence, ushort InstanceSequence,
ushort PositionSequence, RuntimeAcceptedPositionSource PositionSource,
ushort PreviousTeleportSequence, ImmutableArray<RuntimeInitialCreateTailAction> Actions)
ushort TeleportSequence,
ushort ForcePositionSequence,
ushort AcceptedTeleportSequence,
ushort AcceptedForcePositionSequence,
PositionTimestampDisposition TimestampDisposition,
CreateObject.ServerPosition AcceptedWirePosition,
Vector3? PositionPackVelocity,
Vector3? AcceptedVelocity,
Quaternion? ForcePositionRotation,
Vector3? CurrentLocalVelocity,
bool ProjectionRequiresTeleportHook,
uint PlacementFrame,
ulong PositionAuthorityVersion)
{ {
internal bool IsValid => Sequence != 0UL internal bool IsValid => Sequence != 0UL
&& EntityKind is RuntimePositionEntityKind.LocalPlayer && !Actions.IsDefaultOrEmpty
or RuntimePositionEntityKind.Remote && Actions.All(static action => action.IsStructurallyValid)
or RuntimePositionEntityKind.Projectile && HasMatchingInstances()
&& PositionAuthorityVersion != 0UL && HasValidShape();
&& TimestampDisposition is PositionTimestampDisposition.Apply
or PositionTimestampDisposition.ForcePosition; private bool HasMatchingInstances()
{
for (int index = 0; index < Actions.Length; index++)
{
if (!Actions[index].MatchesInstance(InstanceSequence))
return false;
}
return true;
}
private bool HasValidShape()
{
if (Kind is not RuntimeInitialCreateContinuationKind.SameIncarnationCreate)
{
if (Actions.Length != 1)
return false;
RuntimeInitialCreateTailAction action = Actions[0];
RuntimeInitialCreateTailActionKind expected = Kind switch
{
RuntimeInitialCreateContinuationKind.ObjDesc =>
RuntimeInitialCreateTailActionKind.ObjDesc,
RuntimeInitialCreateContinuationKind.Parent =>
RuntimeInitialCreateTailActionKind.Parent,
RuntimeInitialCreateContinuationKind.Pickup =>
RuntimeInitialCreateTailActionKind.Pickup,
RuntimeInitialCreateContinuationKind.Position =>
RuntimeInitialCreateTailActionKind.Position,
RuntimeInitialCreateContinuationKind.Movement =>
RuntimeInitialCreateTailActionKind.Movement,
RuntimeInitialCreateContinuationKind.State =>
RuntimeInitialCreateTailActionKind.State,
RuntimeInitialCreateContinuationKind.Vector =>
RuntimeInitialCreateTailActionKind.Vector,
_ => throw new InvalidOperationException(
$"Unsupported initial-Create continuation kind {Kind}."),
};
return action.Kind == expected
&& (Kind is RuntimeInitialCreateContinuationKind.Position
? PositionSource is RuntimeAcceptedPositionSource.PositionEvent
&& action.PositionSource == PositionSource
: PositionSource is RuntimeAcceptedPositionSource.Unknown);
}
if (Actions.Length < 2
|| Actions[^2].Kind
is not RuntimeInitialCreateTailActionKind.WeenieDescription
|| Actions[^1].Kind
is not RuntimeInitialCreateTailActionKind.ResidentCellCleanup)
{
return false;
}
bool hasPosition = Actions.Any(
static action => action.Kind
is RuntimeInitialCreateTailActionKind.Position);
if (hasPosition
? PositionSource
is not RuntimeAcceptedPositionSource.SameIncarnationCreate
: PositionSource is not RuntimeAcceptedPositionSource.Unknown)
{
return false;
}
int previousStage = -1;
int positionBranchCount = 0;
for (int index = 0; index < Actions.Length; index++)
{
RuntimeInitialCreateTailAction action = Actions[index];
int stage = SameCreateStage(action.Kind);
if (stage <= previousStage)
return false;
if (action.Kind is RuntimeInitialCreateTailActionKind.Position
&& action.PositionSource != PositionSource)
{
return false;
}
if (action.Kind is RuntimeInitialCreateTailActionKind.CreateParent
or RuntimeInitialCreateTailActionKind.Pickup
or RuntimeInitialCreateTailActionKind.Position)
{
positionBranchCount++;
}
previousStage = stage;
}
bool hasPhysicsDescription = Actions.Any(
static action => action.Kind
is RuntimeInitialCreateTailActionKind
.PreTailDescriptionAdaptation);
return hasPhysicsDescription
? positionBranchCount <= 1
: positionBranchCount == 0;
}
private static int SameCreateStage(RuntimeInitialCreateTailActionKind kind) =>
kind switch
{
RuntimeInitialCreateTailActionKind.PreTailDescriptionAdaptation => 0,
RuntimeInitialCreateTailActionKind.ObjDesc => 1,
RuntimeInitialCreateTailActionKind.CreateParent
or RuntimeInitialCreateTailActionKind.Pickup
or RuntimeInitialCreateTailActionKind.Position => 2,
RuntimeInitialCreateTailActionKind.Movement => 3,
RuntimeInitialCreateTailActionKind.State => 4,
RuntimeInitialCreateTailActionKind.Vector => 5,
RuntimeInitialCreateTailActionKind.WeenieDescription => 6,
RuntimeInitialCreateTailActionKind.ResidentCellCleanup => 7,
_ => int.MinValue,
};
} }
internal readonly record struct RuntimeInitialCreateResidenceAdoptionToken( internal readonly record struct RuntimeInitialCreateResidenceAdoptionToken(
@ -259,125 +501,37 @@ internal sealed class RuntimeInitialCreateResidenceState
return Own(record, route); return Own(record, route);
} }
internal RuntimeInitialCreateResidenceLease EnqueueAcceptedPosition( internal RuntimeInitialCreateResidenceLease EnqueueAccepted(
RuntimeEntityRecord record, RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceLease prior, in RuntimeInitialCreateResidenceLease prior,
WorldSession.EntityPositionUpdate update, RuntimeInitialCreateContinuationKind kind,
in WorldSession.EntitySpawn accepted, RuntimeAcceptedPositionSource positionSource,
PositionTimestampDisposition disposition, ImmutableArray<RuntimeInitialCreateTailAction> actions)
in AcceptedPhysicsTimestamps timestamps,
bool isLocalPlayer,
Quaternion? forcePositionRotation,
Vector3? currentLocalVelocity,
bool projectionRequiresTeleportHook)
{ {
ArgumentNullException.ThrowIfNull(record); var owned = ImmutableArray.CreateBuilder<
if (!_entities.IsCurrent(record) RuntimeInitialCreateTailAction>(actions.Length);
|| record.Key is not { } key foreach (RuntimeInitialCreateTailAction action in actions)
|| update.Guid != record.ServerGuid
|| accepted.Guid != record.ServerGuid
|| disposition is PositionTimestampDisposition.Rejected)
{ {
return default; owned.Add(RuntimeInitialCreateAdmissionFreezer.Freeze(action));
} }
return Enqueue(
RuntimeInitialCreateResidenceLease current; record,
Entry? active = null; prior,
CompletedEntry? completed = null; new RuntimeInitialCreateResidenceContinuation(
if (_entries.TryGetValue(key, out active) NextSequence(prior),
&& ReferenceEquals(active.Record, record) kind,
&& active.Lease.Token == prior.Token record.Incarnation,
&& IsCurrent(active)) positionSource,
{ owned.MoveToImmutable()));
current = active.Lease;
}
else if (_completed.TryGetValue(key, out completed)
&& ReferenceEquals(completed.Record, record)
&& completed.Lease.Token == prior.Token
&& IsCompletedCurrent(completed))
{
current = completed.Lease;
}
else
{
return default;
}
if (current.Continuations.Length == int.MaxValue
|| completed is not null
&& completed.Receipt.Adoption.Revision == ulong.MaxValue)
return default;
RuntimePositionEntityKind entityKind = isLocalPlayer
? RuntimePositionEntityKind.LocalPlayer
: (record.FinalPhysicsState & PhysicsStateFlags.Missile) != 0
? RuntimePositionEntityKind.Projectile
: RuntimePositionEntityKind.Remote;
CreateObject.ServerPosition acceptedPosition =
accepted.Position ?? update.Position;
ushort previousTeleport = current.Continuations.IsEmpty
? current.Route.Authority.AcceptedTeleportSequence
: current.Continuations[^1].AcceptedTeleportSequence;
ulong sequence = (ulong)current.Continuations.Length + 1UL;
var continuation = new RuntimeInitialCreateResidenceContinuation(
sequence,
entityKind,
update.InstanceSequence,
update.PositionSequence,
previousTeleport,
update.TeleportSequence,
update.ForcePositionSequence,
timestamps.Teleport,
timestamps.ForcePosition,
disposition,
acceptedPosition,
update.Velocity,
accepted.Physics?.Velocity,
forcePositionRotation,
currentLocalVelocity,
projectionRequiresTeleportHook,
accepted.PlacementId ?? 0u,
record.PositionAuthorityVersion);
if (!continuation.IsValid)
return default;
RuntimeInitialCreateResidenceLease revised = current with
{
Continuations = current.Continuations.Add(continuation),
};
if (active is not null)
{
active.Lease = revised;
}
else
{
completed!.Lease = revised;
RuntimeInitialCreateResidenceAdoptionToken revisedAdoption =
completed.Receipt.Adoption with
{
Revision = completed.Receipt.Adoption.Revision + 1UL,
};
completed.Receipt = completed.Receipt with
{
Adoption = revisedAdoption,
Continuations = revised.Continuations,
};
}
return revised;
} }
internal bool CanEnqueueAcceptedPosition( internal bool CanEnqueue(
RuntimeEntityRecord record, RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceLease prior, in RuntimeInitialCreateResidenceLease prior)
in WorldSession.EntityPositionUpdate update)
{ {
ArgumentNullException.ThrowIfNull(record); ArgumentNullException.ThrowIfNull(record);
if (record.Key is not { } key if (record.Key is not { } key)
|| update.Guid != record.ServerGuid
|| !RuntimeAuthoritativePositionRouteClassifier
.IsValidCreateWirePosition(update.Position))
{
return false; return false;
}
if (_entries.TryGetValue(key, out Entry? entry)) if (_entries.TryGetValue(key, out Entry? entry))
{ {
return ReferenceEquals(entry.Record, record) return ReferenceEquals(entry.Record, record)
@ -393,6 +547,61 @@ internal sealed class RuntimeInitialCreateResidenceState
&& completed.Receipt.Adoption.Revision < ulong.MaxValue; && completed.Receipt.Adoption.Revision < ulong.MaxValue;
} }
private RuntimeInitialCreateResidenceLease Enqueue(
RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceLease prior,
in RuntimeInitialCreateResidenceContinuation continuation)
{
ArgumentNullException.ThrowIfNull(record);
if (!continuation.IsValid
|| continuation.InstanceSequence != record.Incarnation
|| !CanEnqueue(record, prior))
{
return default;
}
RuntimeEntityKey key = record.Key!.Value;
Entry? active = null;
CompletedEntry? completed = null;
RuntimeInitialCreateResidenceLease current;
if (_entries.TryGetValue(key, out active))
current = active.Lease;
else if (_completed.TryGetValue(key, out completed))
current = completed.Lease;
else
return default;
if (continuation.Sequence != NextSequence(current))
return default;
RuntimeInitialCreateResidenceLease revised = current with
{
Continuations = current.Continuations.Add(continuation),
};
if (active is not null)
{
active.Lease = revised;
}
else
{
completed!.Lease = revised;
RuntimeInitialCreateResidenceAdoptionToken adoption =
completed.Receipt.Adoption with
{
Revision = completed.Receipt.Adoption.Revision + 1UL,
};
completed.Receipt = completed.Receipt with
{
Adoption = adoption,
Continuations = revised.Continuations,
};
}
return revised;
}
private static ulong NextSequence(
in RuntimeInitialCreateResidenceLease lease) =>
(ulong)lease.Continuations.Length + 1UL;
internal bool TryGetTransaction( internal bool TryGetTransaction(
RuntimeEntityRecord record, RuntimeEntityRecord record,
out RuntimeInitialCreateResidenceLease lease) out RuntimeInitialCreateResidenceLease lease)
@ -457,6 +666,7 @@ internal sealed class RuntimeInitialCreateResidenceState
token, token,
route, route,
placement, placement,
RuntimeInitialCreateAdmissionFreezer.Freeze(record.Snapshot),
ImmutableArray<RuntimeInitialCreateResidenceContinuation>.Empty); ImmutableArray<RuntimeInitialCreateResidenceContinuation>.Empty);
_entries.Add(key, new Entry _entries.Add(key, new Entry
{ {
@ -600,6 +810,12 @@ internal sealed class RuntimeInitialCreateResidenceState
Retire(current); Retire(current);
return false; return false;
} }
// Admission-only checkpoint: a completed initial placement remains
// owned until the next slice's continuation executor has drained the
// exact FIFO revision. Letting a host acknowledge here would silently
// discard accepted packets.
if (!current.Lease.Continuations.IsEmpty)
return false;
if (current.Lease.Route.PerformsSetPosition if (current.Lease.Route.PerformsSetPosition
&& !_setPosition.ConsumeAcknowledgedPlacement( && !_setPosition.ConsumeAcknowledgedPlacement(
current.Lease.Placement, current.Lease.Placement,

View file

@ -54,6 +54,19 @@ internal enum RuntimeTeleportHookPhase : byte
AfterEnterWorld, AfterEnterWorld,
} }
/// <summary>
/// Exact placement of retail's <c>ConstrainTo</c> relative to the selected
/// position operation. Local ordinary corrections constrain before their
/// optional interpolation; teleports and remote hard moves constrain only
/// after the position operation succeeds.
/// </summary>
internal enum RuntimePositionConstrainPhase : byte
{
None,
BeforePositionOperation,
AfterPositionOperation,
}
/// <summary> /// <summary>
/// Exact accepted-wire authority for one presentation-independent position /// Exact accepted-wire authority for one presentation-independent position
/// route. <see cref="RuntimeEntityKey"/> identifies the incarnation while /// route. <see cref="RuntimeEntityKey"/> identifies the incarnation while
@ -147,7 +160,7 @@ internal readonly record struct RuntimeAuthoritativePositionRoute(
bool LeaveWorld, bool LeaveWorld,
RuntimeTeleportHookPhase TeleportHookPhase, RuntimeTeleportHookPhase TeleportHookPhase,
bool StopInterpolating, bool StopInterpolating,
bool ConstrainAfterRouting, RuntimePositionConstrainPhase ConstrainPhase,
bool PreserveHeading, bool PreserveHeading,
bool ZeroVelocity, bool ZeroVelocity,
bool SendPositionImmediately, bool SendPositionImmediately,
@ -163,6 +176,12 @@ internal readonly record struct RuntimeAuthoritativePositionRoute(
internal bool RunsTeleportHook => internal bool RunsTeleportHook =>
TeleportHookPhase is not RuntimeTeleportHookPhase.None; TeleportHookPhase is not RuntimeTeleportHookPhase.None;
internal bool ConstrainBeforeRouting =>
ConstrainPhase is RuntimePositionConstrainPhase.BeforePositionOperation;
internal bool ConstrainAfterRouting =>
ConstrainPhase is RuntimePositionConstrainPhase.AfterPositionOperation;
} }
/// <summary> /// <summary>
@ -214,7 +233,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -246,7 +265,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
? RuntimeTeleportHookPhase.AfterEnterWorld ? RuntimeTeleportHookPhase.AfterEnterWorld
: RuntimeTeleportHookPhase.None, : RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -286,7 +305,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: true, PreserveHeading: true,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: true, SendPositionImmediately: true,
@ -309,7 +328,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.AfterPositionOperation, TeleportHookPhase: RuntimeTeleportHookPhase.AfterPositionOperation,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: true, ConstrainPhase: RuntimePositionConstrainPhase.AfterPositionOperation,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: true, ZeroVelocity: true,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -331,7 +350,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: true, ConstrainPhase: RuntimePositionConstrainPhase.BeforePositionOperation,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -357,7 +376,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.BeforePositionOperation, TeleportHookPhase: RuntimeTeleportHookPhase.BeforePositionOperation,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: true, ConstrainPhase: RuntimePositionConstrainPhase.AfterPositionOperation,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -383,7 +402,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -413,7 +432,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: !nearby, StopInterpolating: !nearby,
ConstrainAfterRouting: true, ConstrainPhase: RuntimePositionConstrainPhase.AfterPositionOperation,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -445,7 +464,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: true, LeaveWorld: true,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -532,7 +551,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,
@ -552,7 +571,7 @@ internal static class RuntimeAuthoritativePositionRouteClassifier
LeaveWorld: false, LeaveWorld: false,
TeleportHookPhase: RuntimeTeleportHookPhase.None, TeleportHookPhase: RuntimeTeleportHookPhase.None,
StopInterpolating: false, StopInterpolating: false,
ConstrainAfterRouting: false, ConstrainPhase: RuntimePositionConstrainPhase.None,
PreserveHeading: false, PreserveHeading: false,
ZeroVelocity: false, ZeroVelocity: false,
SendPositionImmediately: false, SendPositionImmediately: false,

View file

@ -140,6 +140,7 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
playerDistance: distance); playerDistance: distance);
Assert.Equal(expected, route.Disposition); Assert.Equal(expected, route.Disposition);
Assert.False(route.ConstrainBeforeRouting);
Assert.True(route.ConstrainAfterRouting); Assert.True(route.ConstrainAfterRouting);
} }
@ -326,7 +327,8 @@ public sealed class RuntimeAuthoritativePositionRouteClassifierTests
: null); : null);
Assert.Equal(expected, route.Disposition); Assert.Equal(expected, route.Disposition);
Assert.True(route.ConstrainAfterRouting); Assert.True(route.ConstrainBeforeRouting);
Assert.False(route.ConstrainAfterRouting);
} }
[Theory] [Theory]