feat(runtime): execute initial placement continuations

The admission checkpoint (30012361) sealed accepted updates behind a
pending initial placement; nothing could apply them, so AcknowledgeAdoption
refused any non-empty FIFO and the residence system had no path to
completion. RuntimeInitialCreateContinuationExecutor is that missing
mechanism: a synchronous, retry-idempotent Execute transaction that adopts
the acknowledged initial placement exactly once (consuming the retained
completion so later authored placements for the key can begin), emits the
AfterEnterWorld hook request for the local player, replays deferred
missing-parent raw Creates and queued parent relations by parent GUID
(retail ProcessObjectNetBlobs order: whole-bucket detach, FIFO dispatch,
cancellation-aware restore), and drains the mixed continuation FIFO
strictly by sequence with retail route decisions taken at execution time
via ClassifyAcceptedPosition on live inputs (server-asserted wire contact,
data-driven animation proxy, live distance/options).

Apply bodies are shared with the legacy fused paths through new gate-less
instance seams on InboundPhysicsStateController that keep the one snapshot
store in lockstep; SameIncarnationCreate envelopes apply atomically with
per-stage idempotency and buffered publication after the final stage;
every abandonment path retires the residence through the lifetime choke
point and converges the ownership ledger (executor progress, deferred
buckets, replay windows, placement watches all folded into IsConverged).
Position/placement side effects are exactly-once under retry, external
mutations are detected via a field-masked executor baseline, and
AwaitingContinuationPlacement yields keep the FIFO head retryable.

Production routes are deliberately untouched: graphical and headless
Create still use legacy RegisterEntity, and no host calls Execute. The
cutover is the next checkpoint; AP-1/AD-1 remain open until it lands.
Register rows AD-59/AD-60/AP-130/AP-131/AP-132/TS-62/TS-63 document the
slice's deviations in this commit.

Reviewed: retail-conformance PASS + architecture/adversarial PASS after
five implementation rounds (wire-contact source, snapshot lockstep,
WeenieDescription merge, abandonment convergence, reentrant retirement
windows, acknowledged-completion leak, baseline precision, replay
containment/restore, queue-by-parent-GUID relation deferral all fixed at
root cause). Runtime tests 903/903; complete Release solution 10,696
passed / 4 intentional skips; focused executor gate 161/161.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-02 03:49:56 +02:00
parent 4a8f74dc72
commit 5db3de3c7a
9 changed files with 8002 additions and 86 deletions

View file

@ -114,6 +114,48 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyAcceptedObjDesc(old, update);
_snapshots[update.Guid] = accepted;
return true;
}
/// <summary>
/// Shared ObjDesc snapshot mutation. A retained residence continuation was
/// only ever enqueued after the exact same
/// <see cref="PhysicsTimestampGate.TryAcceptObjDescEvent"/> call already
/// succeeded at admission time, so the retained update's own
/// <see cref="ObjDescEvent.Parsed.ObjDescSequence"/> IS the stamped gate
/// value; the executor must not re-derive it from a live gate.
/// </summary>
/// <summary>
/// Instance seam for <see cref="ApplyAcceptedObjDesc"/>: reads
/// <c>_snapshots[guid]</c> as the merge base and writes the result back,
/// keeping this store and the continuation executor's canonical
/// <c>RuntimeEntityRecord.Snapshot</c> in lockstep (Round 3 A1). Without
/// this seam the executor merged directly against the record's own
/// snapshot and never touched <c>_snapshots</c>, so the FIRST later
/// legacy <c>TryApplyXxx</c> call would re-merge onto a stale base and
/// silently revert every drained continuation.
/// </summary>
internal bool ApplyAcceptedObjDescSnapshot(
uint guid,
ObjDescEvent.Parsed update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedObjDesc(old, update);
_snapshots[guid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedObjDesc(
WorldSession.EntitySpawn old,
ObjDescEvent.Parsed update)
{
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
@ -121,7 +163,7 @@ public sealed class InboundPhysicsStateController
Timestamps = desc.Timestamps with { ObjDesc = update.ObjDescSequence },
};
accepted = old with
return old with
{
AnimPartChanges = update.ModelData.AnimPartChanges,
TextureChanges = update.ModelData.TextureChanges,
@ -129,8 +171,6 @@ public sealed class InboundPhysicsStateController
BasePaletteId = update.ModelData.BasePaletteId,
Physics = physics,
};
_snapshots[update.Guid] = accepted;
return true;
}
public bool TryApplyPickup(
@ -145,11 +185,33 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyUnparentedPosition(old, null, update.PositionSequence);
accepted = ApplyAcceptedPickup(old, update);
_snapshots[update.Guid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedPickup(
WorldSession.EntitySpawn old,
PickupEvent.Parsed update) =>
ApplyUnparentedPosition(old, null, update.PositionSequence);
/// <summary>Instance seam for <see cref="ApplyAcceptedPickup"/> - see the
/// remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedPickupSnapshot(
uint guid,
PickupEvent.Parsed update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedPickup(old, update);
_snapshots[guid] = accepted;
return true;
}
/// <summary>
/// Applies the parent branch embedded in a same-generation PhysicsDesc.
/// Unlike standalone ParentEvent it carries no parent INSTANCE_TS, so only
@ -167,11 +229,33 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyPositionTimestampOnly(child, update.ChildPositionSequence);
accepted = ApplyAcceptedCreateParent(child, update);
_snapshots[update.ChildGuid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedCreateParent(
WorldSession.EntitySpawn child,
CreateParentUpdate update) =>
ApplyPositionTimestampOnly(child, update.ChildPositionSequence);
/// <summary>Instance seam for <see cref="ApplyAcceptedCreateParent"/> -
/// see the remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedCreateParentSnapshot(
uint guid,
CreateParentUpdate update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedCreateParent(old, update);
_snapshots[guid] = accepted;
return true;
}
public bool TryApplyParent(
ParentEvent.Parsed update,
out WorldSession.EntitySpawn accepted)
@ -186,11 +270,33 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyPositionTimestampOnly(child, update.ChildPositionSequence);
accepted = ApplyAcceptedParent(child, update);
_snapshots[update.ChildGuid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedParent(
WorldSession.EntitySpawn child,
ParentEvent.Parsed update) =>
ApplyPositionTimestampOnly(child, update.ChildPositionSequence);
/// <summary>Instance seam for <see cref="ApplyAcceptedParent"/> - see the
/// remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedParentSnapshot(
uint guid,
ParentEvent.Parsed update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedParent(old, update);
_snapshots[guid] = accepted;
return true;
}
public bool TryCommitParent(
uint childGuid,
uint parentGuid,
@ -235,17 +341,28 @@ public sealed class InboundPhysicsStateController
update.MovementSequence,
update.ServerControlSequence);
timestamps = Current(gate);
WorldSession.EntitySpawn stamped = MirrorGateTimestamps(old, gate) with
{
MovementSequence = gate.MovementTimestamp,
ServerControlSequence = gate.ServerControlledMoveTimestamp,
};
_snapshots[update.Guid] = stamped;
// Retail consumes MOVEMENT_TS before it discovers that the
// SERVER_CONTROLLED_MOVE_TS is stale. Preserve that timestamp-only
// mutation in the canonical snapshot even though no motion payload
// is applied.
// SERVER_CONTROLLED_MOVE_TS is stale (PhysicsTimestampGate.
// TryAcceptMovementEvent checks MOVEMENT_TS first and always advances
// it before the SERVER_CONTROLLED_MOVE_TS check can fail). Preserve
// that timestamp-only mutation in the canonical snapshot even though
// no motion payload is applied. Stamp from the GATE's post-call
// values, not the wire's own proposed values: TryAcceptMovementEvent
// has THREE rejection flavors (bad instance; stale MOVEMENT_TS; stale
// SERVER_CONTROLLED_MOVE_TS) and only the last one actually advances
// MOVEMENT_TS. Stamping update.MovementSequence unconditionally would
// move the snapshot to a rejected packet's value in the first two
// flavors - gate.MovementTimestamp is a no-op there and correct in
// the third, exactly like this method's legacy predecessor.
WorldSession.EntitySpawn stamped = ApplyAcceptedMotion(
old,
gate.MovementTimestamp,
gate.ServerControlledMoveTimestamp,
update,
retainPayload: false);
_snapshots[update.Guid] = stamped;
if (!applyPayload)
{
accepted = default;
@ -258,6 +375,76 @@ public sealed class InboundPhysicsStateController
return true;
}
accepted = ApplyAcceptedMotion(
stamped,
gate.MovementTimestamp,
gate.ServerControlledMoveTimestamp,
update,
retainPayload: true);
_snapshots[update.Guid] = accepted;
return true;
}
/// <summary>
/// Shared Movement snapshot mutation. The top-level and nested
/// Movement/ServerControlledMove timestamp fields are ALWAYS stamped to
/// exactly <paramref name="movementSequence"/>/<paramref name="acceptedServerControlledMove"/>
/// (regardless of <paramref name="retainPayload"/>); only the actual
/// movement payload (raw bytes/MotionState) is gated on it. This
/// deliberately does NOT mirror every OTHER timestamp channel from a
/// live gate the way the legacy path's old <c>MirrorGateTimestamps</c>
/// helper did (redundant there, since gate and snapshot stay in
/// lockstep on the immediate-apply path, but wrong for the executor's
/// out-of-band residence replay, where other channels may have advanced
/// far beyond what THIS retained continuation is allowed to observe).
///
/// The movement/server-control VALUES are explicit inputs, not derived
/// from <paramref name="update"/> internally - one shared apply body,
/// two different sources of truth for its two callers. The legacy
/// immediate-apply caller MUST pass the live gate's own post-call
/// <c>MovementTimestamp</c>/<c>ServerControlledMoveTimestamp</c>
/// (PhysicsTimestampGate.TryAcceptMovementEvent has three rejection
/// flavors - bad instance, stale MOVEMENT_TS, stale
/// SERVER_CONTROLLED_MOVE_TS - and only reading the gate AFTER the call
/// is a no-op in the first two and correct in the third; the wire's own
/// proposed <see cref="WorldSession.EntityMotionUpdate.MovementSequence"/>
/// would silently corrupt the snapshot to a rejected packet's value in
/// the first two flavors). The continuation executor instead passes the
/// retained action's own <c>Movement.Value.MovementSequence</c>/
/// <see cref="AcceptedPhysicsTimestamps.ServerControlledMove"/> - safe
/// there specifically because a Movement continuation is only ever
/// retained when <c>AppliesMovementPayload || HasTimestampMutation</c>,
/// which structurally guarantees MOVEMENT_TS itself already advanced to
/// that exact wire value at admission time (the same three-flavor gate
/// logic that makes stamping the wire value unsafe for an UNGATED legacy
/// call makes it exactly correct for an ALREADY-GATED retained one).
/// </summary>
internal static WorldSession.EntitySpawn ApplyAcceptedMotion(
WorldSession.EntitySpawn old,
ushort movementSequence,
ushort acceptedServerControlledMove,
WorldSession.EntityMotionUpdate update,
bool retainPayload)
{
PhysicsSpawnData? stampedPhysics = old.Physics;
if (stampedPhysics is { } stampedDesc)
stampedPhysics = stampedDesc with
{
Timestamps = stampedDesc.Timestamps with
{
Movement = movementSequence,
ServerControlledMove = acceptedServerControlledMove,
},
};
WorldSession.EntitySpawn stamped = old with
{
MovementSequence = movementSequence,
ServerControlSequence = acceptedServerControlledMove,
Physics = stampedPhysics,
};
if (!retainPayload)
return stamped;
PhysicsSpawnData? physics = stamped.Physics;
if (physics is { } desc)
physics = desc with
@ -266,19 +453,37 @@ public sealed class InboundPhysicsStateController
ReadOnlyMemory<byte>.Empty,
update.MotionState,
update.IsAutonomous),
Timestamps = desc.Timestamps with
{
Movement = gate.MovementTimestamp,
ServerControlledMove = gate.ServerControlledMoveTimestamp,
},
};
accepted = stamped with
return stamped with
{
MotionState = update.MotionState,
Physics = physics,
};
_snapshots[update.Guid] = accepted;
}
/// <summary>Instance seam for <see cref="ApplyAcceptedMotion"/> - see the
/// remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedMotionSnapshot(
uint guid,
ushort movementSequence,
ushort acceptedServerControlledMove,
WorldSession.EntityMotionUpdate update,
bool retainPayload,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedMotion(
old,
movementSequence,
acceptedServerControlledMove,
update,
retainPayload);
_snapshots[guid] = accepted;
return true;
}
@ -293,6 +498,15 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyAcceptedVector(old, update);
_snapshots[update.Guid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedVector(
WorldSession.EntitySpawn old,
VectorUpdate.Parsed update)
{
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
@ -302,8 +516,23 @@ public sealed class InboundPhysicsStateController
Timestamps = desc.Timestamps with { Vector = update.VectorSequence },
};
accepted = old with { Physics = physics };
_snapshots[update.Guid] = accepted;
return old with { Physics = physics };
}
/// <summary>Instance seam for <see cref="ApplyAcceptedVector"/> - see the
/// remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedVectorSnapshot(
uint guid,
VectorUpdate.Parsed update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedVector(old, update);
_snapshots[guid] = accepted;
return true;
}
@ -318,6 +547,15 @@ public sealed class InboundPhysicsStateController
return false;
}
accepted = ApplyAcceptedState(old, update);
_snapshots[update.Guid] = accepted;
return true;
}
internal static WorldSession.EntitySpawn ApplyAcceptedState(
WorldSession.EntitySpawn old,
SetState.Parsed update)
{
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
@ -326,12 +564,27 @@ public sealed class InboundPhysicsStateController
Timestamps = desc.Timestamps with { State = update.StateSequence },
};
accepted = old with
return old with
{
PhysicsState = update.PhysicsState,
Physics = physics,
};
_snapshots[update.Guid] = accepted;
}
/// <summary>Instance seam for <see cref="ApplyAcceptedState"/> - see the
/// remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>.</summary>
internal bool ApplyAcceptedStateSnapshot(
uint guid,
SetState.Parsed update,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
accepted = ApplyAcceptedState(old, update);
_snapshots[guid] = accepted;
return true;
}
@ -339,6 +592,20 @@ public sealed class InboundPhysicsStateController
/// Returns true when the addressed live incarnation exists, even when the
/// position payload is rejected. This lets callers publish a freshly
/// consumed FORCE_POSITION_TS without applying a stale pose.
///
/// Round 4 R4-15: this legacy immediate-apply path has no HasContact or
/// route-classification concept at all - it merges unconditionally on
/// the retained <see cref="PositionTimestampDisposition"/> alone. The
/// continuation executor's <c>ApplyPositionAction</c> instead runs
/// <c>RuntimeAuthoritativePositionRouteClassifier</c> and derives
/// contact solely from the retained wire packet's own
/// <c>IsGrounded</c> bit. This is internal refactor debt tracked for
/// the eventual cutover unification (this file's <c>TryApplyPosition</c>
/// is today's only PRODUCTION Position wire caller; the classifier-based
/// path is test-only until a host wires the executor) - it is NOT a
/// retail divergence and does not belong in
/// docs/architecture/retail-divergence-register.md. See docs/ISSUES.md
/// for the tracked follow-up.
/// </summary>
public bool TryApplyPosition(
WorldSession.EntityPositionUpdate update,
@ -370,12 +637,153 @@ public sealed class InboundPhysicsStateController
gate,
teleportAdvanced: disposition is PositionTimestampDisposition.Apply
&& advancesTeleport);
if (disposition is PositionTimestampDisposition.Rejected)
accepted = ApplyAcceptedPosition(
old,
update,
disposition,
timestamps,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
// Legacy immediate-apply reproduces EXACT prior behavior: the
// placement frame and parent clear were always unconditional
// here (see the Round 3 A1/B6 admission handoff). Only the
// continuation executor threads the classified route's own
// ApplyPlacementFrameBeforeRouting/UnparentBeforeRouting flags.
installPlacementFrame: true,
clearParent: true);
_snapshots[update.Guid] = accepted;
return true;
}
/// <summary>Instance seam for <see cref="ApplyAcceptedPosition"/> - see
/// the remarks on <see cref="ApplyAcceptedObjDescSnapshot"/>. The
/// executor passes its classified route's own
/// <c>ApplyPlacementFrameBeforeRouting</c>/<c>UnparentBeforeRouting</c>
/// flags rather than the legacy path's unconditional true/true.</summary>
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)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = MirrorGateTimestamps(old, gate);
_snapshots[update.Guid] = accepted;
return true;
accepted = default;
return false;
}
accepted = ApplyAcceptedPosition(
old,
update,
disposition,
timestamps,
isLocalPlayer,
forcePositionRotation,
currentLocalVelocity,
installPlacementFrame,
clearParent);
_snapshots[guid] = accepted;
return true;
}
/// <summary>
/// Round 3 B10: a retained Position action whose ADMISSION-time
/// disposition was Apply/ForcePosition (the gate genuinely advanced
/// POSITION_TS/TELEPORT_TS/FORCE_POSITION_TS at admission) but whose
/// EXECUTION-time classification rejects (RejectedAuthority/RejectedData
/// from the live route classifier - e.g. a malformed live input) must
/// still stamp every timestamp channel the gate actually moved; it must
/// not silently freeze the snapshot at pre-admission values. Distinct
/// from <see cref="ApplyAcceptedPositionTimestampOnly"/>, which is the
/// ADMISSION-time-gate-rejected case where only FORCE_POSITION_TS can
/// have moved - here Position, Teleport, AND ForcePosition are all
/// replayed, still without installing any pose/parent/placement field.
/// </summary>
internal bool ApplyAcceptedPositionExecutionRejectedSnapshot(
uint guid,
ushort acceptedPositionSequence,
AcceptedPhysicsTimestamps timestamps,
out WorldSession.EntitySpawn accepted)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn old))
{
accepted = default;
return false;
}
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
{
Timestamps = desc.Timestamps with
{
Position = acceptedPositionSequence,
Teleport = timestamps.Teleport,
ForcePosition = timestamps.ForcePosition,
},
};
accepted = old with
{
PositionSequence = acceptedPositionSequence,
Physics = physics,
};
_snapshots[guid] = accepted;
return true;
}
/// <summary>
/// Shared Position snapshot mutation, reconstructed from the RETAINED
/// disposition + accepted-gate facts rather than a live gate read (the
/// executor drains a Position continuation long after the timestamp gate
/// itself moved on to later packets). Mirrors
/// <c>SmartBox::HandleReceivedPosition</c> (0x00453FD0) /
/// <c>PositionPack::UnPack</c> (0x00516740) exactly as the legacy
/// immediate-apply path did.
///
/// A <see cref="PositionTimestampDisposition.Rejected"/> retained
/// continuation only ever exists because
/// <see cref="AcceptedPhysicsTimestamps.TeleportHookRequired"/>-adjacent
/// bookkeeping mutated (see <see cref="PhysicsTimestampGate.TryAcceptPositionEvent"/>:
/// a Rejected outcome always leaves POSITION_TS and TELEPORT_TS net
/// unchanged, so the ONLY dimension that can differ is FORCE_POSITION_TS
/// from the local-player force-position fallthrough branch) — apply the
/// timestamp-only mutation and nothing else.
///
/// For Apply/ForcePosition, the retained wire's own
/// <see cref="WorldSession.EntityPositionUpdate.PositionSequence"/> IS the
/// stamped POSITION_TS value (PhysicsTimestampGate always sets the stored
/// channel to exactly the incoming value on acceptance); Teleport and
/// ForcePosition stamps come from the retained
/// <see cref="AcceptedPhysicsTimestamps"/> captured at admission time.
///
/// <paramref name="installPlacementFrame"/>/<paramref name="clearParent"/>
/// (Round 3 B6) let the two callers reproduce two different retail
/// gates: the legacy immediate-apply path always passes true/true
/// (retail's HandleReceivedPosition unconditionally runs
/// unset_parent/SetPlacementFrame there), while the continuation
/// executor passes its classified route's own
/// <c>ApplyPlacementFrameBeforeRouting</c>/<c>UnparentBeforeRouting</c> -
/// both false only for the FORCE_POSITION branch, which retail's
/// MoveOrTeleport returns from immediately, BEFORE either call.
/// </summary>
internal static WorldSession.EntitySpawn ApplyAcceptedPosition(
WorldSession.EntitySpawn old,
WorldSession.EntityPositionUpdate update,
PositionTimestampDisposition disposition,
AcceptedPhysicsTimestamps timestamps,
bool isLocalPlayer,
System.Numerics.Quaternion? forcePositionRotation,
System.Numerics.Vector3? currentLocalVelocity,
bool installPlacementFrame,
bool clearParent)
{
if (disposition is PositionTimestampDisposition.Rejected)
return ApplyAcceptedPositionTimestampOnly(old, timestamps);
CreateObject.ServerPosition appliedPosition = update.Position;
if (disposition is PositionTimestampDisposition.ForcePosition
@ -392,9 +800,14 @@ public sealed class InboundPhysicsStateController
// PositionPack::UnPack (0x00516740) initializes an absent placement
// id to zero; HandleReceivedPosition (0x00453FD0) forwards that exact
// value to SetPlacementFrame on a normal accepted update.
uint? appliedPlacement = disposition is PositionTimestampDisposition.Apply
? update.PlacementId ?? 0u
// value to SetPlacementFrame on a normal accepted update - but only
// when the caller's route actually runs that step
// (installPlacementFrame; retail skips it entirely while HasAnimations
// is true).
uint? appliedPlacement = installPlacementFrame
? (disposition is PositionTimestampDisposition.Apply
? update.PlacementId ?? 0u
: old.PlacementId)
: old.PlacementId;
System.Numerics.Vector3? appliedVelocity = disposition switch
@ -407,7 +820,7 @@ public sealed class InboundPhysicsStateController
// A fresh local teleport explicitly installs zero velocity. A
// normal local correction does not consume PositionPack velocity.
PositionTimestampDisposition.Apply when isLocalPlayer =>
advancesTeleport
timestamps.TeleportAdvanced
? System.Numerics.Vector3.Zero
: currentLocalVelocity ?? old.Physics?.Velocity,
@ -418,6 +831,22 @@ public sealed class InboundPhysicsStateController
_ => old.Physics?.Velocity,
};
// Round 4 R4-12: FORCE_POSITION_TS's retail Gate A
// (retail-notes.md function 3, SmartBox::HandleReceivedPosition
// 0x00453FD0, "GATE A: local-player force-position self-echo
// shortcut") returns BEFORE CPhysicsObj::unset_parent ever runs -
// clearParent stays false on that route (Round 3 B6), so a
// ForcePosition-merged snapshot can legitimately carry BOTH a
// non-null Position (the force-applied pose, below) AND a non-null
// ParentGuid/ParentLocation/Physics.Parent (the retained
// attachment) simultaneously. This combined shape is deliberate,
// not a bug: every OTHER accepted disposition either clears the
// parent (a genuine unparented Position) or never touches Position
// at all (a Parent/CreateParent-only merge) - ForcePosition is the
// one case that does both without touching parent state at all.
uint? parentGuid = clearParent ? null : old.ParentGuid;
uint? parentLocation = clearParent ? null : old.ParentLocation;
PhysicsAttachment? physicsParent = clearParent ? null : old.Physics?.Parent;
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
@ -425,26 +854,47 @@ public sealed class InboundPhysicsStateController
Position = appliedPosition,
AnimationFrame = appliedPlacement,
Velocity = appliedVelocity,
Parent = null,
Parent = physicsParent,
Timestamps = desc.Timestamps with
{
Position = gate.PositionTimestamp,
Teleport = gate.TeleportTimestamp,
ForcePosition = gate.ForcePositionTimestamp,
Position = update.PositionSequence,
Teleport = timestamps.Teleport,
ForcePosition = timestamps.ForcePosition,
},
};
accepted = old with
return old with
{
Position = appliedPosition,
PositionSequence = gate.PositionTimestamp,
ParentGuid = null,
ParentLocation = null,
PositionSequence = update.PositionSequence,
ParentGuid = parentGuid,
ParentLocation = parentLocation,
PlacementId = appliedPlacement,
Physics = physics,
};
_snapshots[update.Guid] = accepted;
return true;
}
/// <summary>
/// The Rejected-disposition-but-mutated branch of
/// <see cref="ApplyAcceptedPosition"/>: only FORCE_POSITION_TS can have
/// legitimately moved (see that method's remarks). Deliberately narrower
/// than the legacy path's old full-gate mirror, which was only safe
/// in-lockstep and is unsound for the executor's out-of-band replay.
/// </summary>
private static WorldSession.EntitySpawn ApplyAcceptedPositionTimestampOnly(
WorldSession.EntitySpawn old,
AcceptedPhysicsTimestamps timestamps)
{
PhysicsSpawnData? physics = old.Physics;
if (physics is { } desc)
physics = desc with
{
Timestamps = desc.Timestamps with
{
ForcePosition = timestamps.ForcePosition,
},
};
return old with { Physics = physics };
}
/// <summary>
@ -675,31 +1125,6 @@ public sealed class InboundPhysicsStateController
TeleportHookRequired: false,
previousTeleport);
private static WorldSession.EntitySpawn MirrorGateTimestamps(
WorldSession.EntitySpawn spawn,
PhysicsTimestampGate gate)
{
if (spawn.Physics is not { } desc)
return spawn;
return spawn with
{
Physics = desc with
{
Timestamps = new PhysicsTimestamps(
gate.PositionTimestamp,
gate.MovementTimestamp,
gate.StateTimestamp,
gate.VectorTimestamp,
gate.TeleportTimestamp,
gate.ServerControlledMoveTimestamp,
gate.ForcePositionTimestamp,
gate.ObjDescTimestamp,
gate.InstanceTimestamp),
},
};
}
private static WorldSession.EntitySpawn MergeUntimestampedCreate(
WorldSession.EntitySpawn retained,
WorldSession.EntitySpawn incoming) =>
@ -727,6 +1152,32 @@ public sealed class InboundPhysicsStateController
Physics = retained.Physics,
};
/// <summary>
/// Instance seam for the SameIncarnationCreate envelope's
/// WeenieDescription stage (Round 3 A2). This must NOT be a wholesale
/// snapshot replacement of the raw retained packet - it merges exactly
/// like every other same-generation Create
/// (<see cref="MergeUntimestampedCreate"/>), keeping the retained
/// Position/appearance/physics-timestamp fields that earlier stages in
/// THIS envelope (and any earlier FIFO entry) already committed to
/// <c>_snapshots</c>, and taking only the incoming packet's untimestamped
/// identity/description fields.
/// </summary>
internal bool ApplyAcceptedWeenieDescriptionSnapshot(
uint guid,
WorldSession.EntitySpawn incoming,
out WorldSession.EntitySpawn merged)
{
if (!_snapshots.TryGetValue(guid, out WorldSession.EntitySpawn retained))
{
merged = default;
return false;
}
merged = MergeUntimestampedCreate(retained, incoming);
_snapshots[guid] = merged;
return true;
}
private static SameGenerationCreateObjectEvents BuildSameGenerationEvents(
WorldSession.EntitySpawn incoming)
{

View file

@ -1,3 +1,4 @@
using System.Collections.Immutable;
using AcDream.Core.Net;
using AcDream.Core.Net.Messages;
using AcDream.Core.Physics;
@ -20,8 +21,53 @@ public sealed class ParentAttachmentState
private readonly Dictionary<ParentIncarnation, List<uint>> _committedChildrenByParent = new();
private readonly Dictionary<uint, Queue<DeferredParentCreate>>
_deferredCreatesByParent = [];
/// <summary>
/// Round 5 R5-1: retail-faithful queue-by-parent-GUID deferral for an
/// ACCEPTED parent relation (standalone Parent continuation or envelope
/// CreateParent stage) whose parent is unaddressable or names a
/// not-yet-arrived incarnation. Shares the SAME per-guid "blobs waiting
/// on guid X" shape as <see cref="_deferredCreatesByParent"/> - retail's
/// <c>QueueBlobForObject</c>/<c>CObjectMaint</c> bucket does not
/// distinguish a raw Create blob from any other blob type queued
/// against the same guid.
/// </summary>
private readonly Dictionary<uint, Queue<DeferredAcceptedParentRelation>>
_deferredAcceptedRelationsByParent = [];
private ulong _nextDeferredCreateAdmissionId;
/// <summary>
/// Round 5 R5-2: cancellation-aware detach/restore window state, shared
/// by BOTH deferred buckets. <see cref="DetachDeferredCreates"/> and
/// <see cref="DetachDeferredAcceptedRelations"/> register one window
/// entry per detach; while it is open, every cancellation primitive
/// (<see cref="CancelDeferredChildGeneration"/>, <see cref="EndGeneration"/>,
/// <see cref="DeleteGeneration"/>, <see cref="RemoveObject"/>,
/// <see cref="RemoveChild"/>) ADDITIONALLY records its own retain
/// predicate into every currently-open window of the matching bucket
/// kind, so a later Restore call can apply the SAME filtering to the
/// detached remainder that would have applied had the batch never left
/// the live dictionary. <see cref="Clear"/> wipes both window
/// dictionaries outright, which is what makes a stale token
/// (post-Clear/Dispose) restore nothing - the token's Id is simply
/// gone, an ABA-safe no-op via the same "TryRemove fails" pattern
/// <see cref="ConsumeDeferredCreate"/> already relies on.
/// </summary>
private sealed class CreateWindowState
{
internal required uint ParentGuid { get; init; }
internal List<Func<DeferredParentCreate, bool>> Filters { get; } = [];
}
private sealed class RelationWindowState
{
internal required uint ParentGuid { get; init; }
internal List<Func<DeferredAcceptedParentRelation, bool>> Filters { get; } = [];
}
private readonly Dictionary<ulong, CreateWindowState> _createWindows = [];
private readonly Dictionary<ulong, RelationWindowState> _relationWindows = [];
private ulong _nextWindowId;
public int UnresolvedRelationCount =>
_unresolvedByChild.Values.Sum(queue => queue.Count);
public int StagedRelationCount => _stagedByChild.Count;
@ -29,6 +75,8 @@ public sealed class ParentAttachmentState
public int CommittedRelationCount => _lastAcceptedByChild.Count;
internal int DeferredCreateCount =>
_deferredCreatesByParent.Values.Sum(queue => queue.Count);
internal int DeferredAcceptedRelationCount =>
_deferredAcceptedRelationsByParent.Values.Sum(queue => queue.Count);
/// <summary>
/// Retains the complete unaccepted CreateObject packet when its nonzero
@ -102,6 +150,81 @@ public sealed class ParentAttachmentState
return true;
}
/// <summary>
/// Round 3 B7: retail's <c>PartArray::add_child</c>-owning CreateObject
/// handler detaches the ENTIRE queued netblob list for one parent
/// atomically before dispatching any of it (pseudo-C ~93617) - there is
/// no separate peek-then-remove step; the detach itself IS the consume.
/// Returns an empty array when nothing was queued. Structurally rules
/// out the stale-AdmissionId race the previous peek/consume replay loop
/// had to special-case: a Create arriving for this parent AFTER this
/// call enqueues into a brand-new queue instance, never the one already
/// removed here. Round 5 R5-2: opens a cancellation-aware window
/// (<paramref name="window"/>) for the detached batch - see the window-
/// machinery remarks at this class's field declarations.
/// </summary>
internal ImmutableArray<DeferredParentCreate> DetachDeferredCreates(
uint parentGuid,
out DeferredReplayWindowToken window)
{
if (!_deferredCreatesByParent.Remove(
parentGuid,
out Queue<DeferredParentCreate>? queue))
{
window = default;
return ImmutableArray<DeferredParentCreate>.Empty;
}
ulong id = ++_nextWindowId;
_createWindows[id] = new CreateWindowState { ParentGuid = parentGuid };
window = new DeferredReplayWindowToken(id, parentGuid, DeferredReplayBucketKind.Creates);
return [.. queue];
}
/// <summary>
/// Round 4 R4-1 / Round 5 R5-2: restores the unprocessed remainder of a
/// previously-detached replay batch, in original FIFO order and with
/// original <see cref="DeferredParentCreate.AdmissionId"/> values, at the
/// FRONT of the window's parent guid's queue - ahead of anything enqueued
/// for the same parent guid AFTER the detach. Every cancellation
/// primitive that fired WHILE this exact window was open recorded its
/// own retain predicate; those predicates are applied here before
/// re-insertion, so a child deleted (or otherwise cancelled) mid-replay
/// is never resurrected. ALWAYS call this once replay of the detached
/// batch concludes - successful or not - passing the empty remainder on
/// full success; this releases the window (a stale/already-released/
/// Clear-invalidated token is an ABA-safe no-op, since its Id is simply
/// no longer tracked).
/// </summary>
internal void RestoreDeferredCreates(
in DeferredReplayWindowToken window,
ReadOnlySpan<DeferredParentCreate> entries)
{
if (window.Kind != DeferredReplayBucketKind.Creates
|| !_createWindows.Remove(window.Id, out CreateWindowState? state))
{
return;
}
if (entries.Length == 0)
return;
IEnumerable<DeferredParentCreate> filtered = entries.ToArray();
foreach (Func<DeferredParentCreate, bool> filter in state.Filters)
filtered = filtered.Where(filter);
DeferredParentCreate[] survivors = filtered.ToArray();
if (survivors.Length == 0)
return;
var restored = new Queue<DeferredParentCreate>(survivors.Length);
foreach (DeferredParentCreate entry in survivors)
restored.Enqueue(entry);
if (_deferredCreatesByParent.TryGetValue(
window.ParentGuid,
out Queue<DeferredParentCreate>? existing))
{
foreach (DeferredParentCreate entry in existing)
restored.Enqueue(entry);
}
_deferredCreatesByParent[window.ParentGuid] = restored;
}
internal bool ContainsDeferredCreate(
uint childGuid,
ushort instanceSequence)
@ -119,18 +242,151 @@ public sealed class ParentAttachmentState
return false;
}
/// <summary>
/// Round 5 R5-1: retail-faithful replacement for the Round 4 discard -
/// a missing/not-yet-arrived parent QUEUES the accepted relation under
/// the PARENT's guid (standalone parent handler 0x004535D0 -&gt;
/// <c>QueueBlobForObject</c>, pseudo-C 92326; GUID-keyed placeholder
/// bucket in <c>CObjectMaint</c>, 271082-271088) and replays it when
/// that guid is created, exactly like a raw missing-parent Create.
/// Shares the SAME monotonic AdmissionId source as
/// <see cref="EnqueueDeferredCreate"/> (never reset) - both buckets are
/// "blobs waiting on guid X," the same general retail mechanism.
/// </summary>
internal void EnqueueDeferredAcceptedRelation(
uint childGuid,
RuntimeEntityKey childKey,
ParentEvent.Parsed? standalone,
CreateParentUpdate? envelope,
AcceptedPhysicsTimestamps acceptedTimestamps)
{
uint parentGuid = standalone?.ParentGuid ?? envelope?.ParentGuid ?? 0u;
if (parentGuid == 0u || childGuid == 0u)
{
throw new ArgumentException(
"A deferred accepted parent relation requires nonzero parent and child GUIDs.");
}
if (_nextDeferredCreateAdmissionId == ulong.MaxValue)
{
throw new InvalidOperationException(
"The deferred parent CreateObject admission sequence is exhausted.");
}
ulong admissionId = _nextDeferredCreateAdmissionId + 1UL;
EnqueueDeferredAcceptedRelation(new DeferredAcceptedParentRelation(
admissionId, childGuid, childKey, standalone, envelope, acceptedTimestamps));
_nextDeferredCreateAdmissionId = admissionId;
}
/// <summary>
/// Re-enqueues an EXISTING relation verbatim, preserving its original
/// <see cref="DeferredAcceptedParentRelation.AdmissionId"/> - used at
/// replay time when the relation still names a parent incarnation that
/// has not yet arrived (wait for the next matching incarnation).
/// </summary>
internal void EnqueueDeferredAcceptedRelation(
in DeferredAcceptedParentRelation relation)
{
uint parentGuid = relation.Standalone?.ParentGuid
?? relation.Envelope?.ParentGuid
?? 0u;
if (!_deferredAcceptedRelationsByParent.TryGetValue(
parentGuid,
out Queue<DeferredAcceptedParentRelation>? queue))
{
queue = new Queue<DeferredAcceptedParentRelation>();
_deferredAcceptedRelationsByParent.Add(parentGuid, queue);
}
queue.Enqueue(relation);
}
/// <summary>Round 5 R5-2 window-aware detach - see <see cref="DetachDeferredCreates"/>'s remarks.</summary>
internal ImmutableArray<DeferredAcceptedParentRelation> DetachDeferredAcceptedRelations(
uint parentGuid,
out DeferredReplayWindowToken window)
{
if (!_deferredAcceptedRelationsByParent.Remove(
parentGuid,
out Queue<DeferredAcceptedParentRelation>? queue))
{
window = default;
return ImmutableArray<DeferredAcceptedParentRelation>.Empty;
}
ulong id = ++_nextWindowId;
_relationWindows[id] = new RelationWindowState { ParentGuid = parentGuid };
window = new DeferredReplayWindowToken(id, parentGuid, DeferredReplayBucketKind.AcceptedRelations);
return [.. queue];
}
/// <summary>Round 5 R5-2 window-aware restore - see <see cref="RestoreDeferredCreates"/>'s remarks.</summary>
internal void RestoreDeferredAcceptedRelations(
in DeferredReplayWindowToken window,
ReadOnlySpan<DeferredAcceptedParentRelation> entries)
{
if (window.Kind != DeferredReplayBucketKind.AcceptedRelations
|| !_relationWindows.Remove(window.Id, out RelationWindowState? state))
{
return;
}
if (entries.Length == 0)
return;
IEnumerable<DeferredAcceptedParentRelation> filtered = entries.ToArray();
foreach (Func<DeferredAcceptedParentRelation, bool> filter in state.Filters)
filtered = filtered.Where(filter);
DeferredAcceptedParentRelation[] survivors = filtered.ToArray();
if (survivors.Length == 0)
return;
var restored = new Queue<DeferredAcceptedParentRelation>(survivors.Length);
foreach (DeferredAcceptedParentRelation entry in survivors)
restored.Enqueue(entry);
if (_deferredAcceptedRelationsByParent.TryGetValue(
window.ParentGuid,
out Queue<DeferredAcceptedParentRelation>? existing))
{
foreach (DeferredAcceptedParentRelation entry in existing)
restored.Enqueue(entry);
}
_deferredAcceptedRelationsByParent[window.ParentGuid] = restored;
}
internal bool ContainsDeferredAcceptedRelation(
uint childGuid,
RuntimeEntityKey childKey)
{
foreach (Queue<DeferredAcceptedParentRelation> queue
in _deferredAcceptedRelationsByParent.Values)
{
if (queue.Any(candidate =>
candidate.ChildGuid == childGuid
&& candidate.ChildKey == childKey))
{
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.
/// treated as an empty sentinel. Round 5 R5-1: also cancels a deferred
/// ACCEPTED relation for the same child incarnation - "child-addressed
/// candidates die with the child" applies identically to both buckets.
/// </summary>
internal void CancelDeferredChildGeneration(
uint childGuid,
ushort terminalInstanceSequence) => FilterDeferredCreates(
ushort terminalInstanceSequence)
{
FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid
|| PhysicsTimestampGate.IsNewer(
terminalInstanceSequence,
candidate.Spawn.InstanceSequence));
FilterDeferredAcceptedRelations(
candidate => candidate.ChildGuid != childGuid
|| PhysicsTimestampGate.IsNewer(
terminalInstanceSequence,
candidate.ChildKey.Incarnation));
}
public void AcceptCreateObjectRelation(ParentAttachmentRelation relation)
{
@ -379,6 +635,7 @@ public sealed class ParentAttachmentState
public void RemoveObject(uint guid)
{
RemoveDeferredChildCreates(guid);
RemoveDeferredAcceptedRelationsForChild(guid);
_stagedByChild.Remove(guid);
_recoveryByChild.Remove(guid);
RemoveCommittedChild(guid);
@ -413,6 +670,12 @@ public sealed class ParentAttachmentState
|| PhysicsTimestampGate.IsNewer(
replacementGeneration,
candidate.Spawn.InstanceSequence));
FilterDeferredAcceptedRelations(candidate =>
candidate.ChildGuid != guid
|| candidate.ChildKey.Incarnation == replacementGeneration
|| PhysicsTimestampGate.IsNewer(
replacementGeneration,
candidate.ChildKey.Incarnation));
FilterChildCandidates(
guid,
relation => relation.WaitOwner is ParentAttachmentWaitOwner.Parent);
@ -468,6 +731,7 @@ public sealed class ParentAttachmentState
public void RemoveChild(uint childGuid)
{
RemoveDeferredChildCreates(childGuid);
RemoveDeferredAcceptedRelationsForChild(childGuid);
_stagedByChild.Remove(childGuid);
_recoveryByChild.Remove(childGuid);
RemoveCommittedChild(childGuid);
@ -477,6 +741,12 @@ public sealed class ParentAttachmentState
public void Clear()
{
_deferredCreatesByParent.Clear();
_deferredAcceptedRelationsByParent.Clear();
// Round 5 R5-2: wipes every open window outright - a later
// Restore call for a token minted before this Clear() finds
// nothing to remove by Id and correctly no-ops (ABA-safe).
_createWindows.Clear();
_relationWindows.Clear();
_unresolvedByChild.Clear();
_stagedByChild.Clear();
_recoveryByChild.Clear();
@ -490,6 +760,15 @@ public sealed class ParentAttachmentState
=> FilterDeferredCreates(
candidate => candidate.Spawn.Guid != childGuid);
private void RemoveDeferredAcceptedRelationsForChild(uint childGuid)
=> FilterDeferredAcceptedRelations(
candidate => candidate.ChildGuid != childGuid);
/// <summary>
/// Round 5 R5-2: filters the LIVE bucket exactly as before, then
/// records the SAME retain predicate into every currently-open create
/// window so a later Restore applies it to the detached remainder too.
/// </summary>
private void FilterDeferredCreates(
Func<DeferredParentCreate, bool> retain)
{
@ -504,6 +783,27 @@ public sealed class ParentAttachmentState
else
_deferredCreatesByParent[parentGuid] = retained;
}
foreach (CreateWindowState state in _createWindows.Values)
state.Filters.Add(retain);
}
/// <summary>Round 5 R5-2 relation-bucket counterpart of <see cref="FilterDeferredCreates"/>.</summary>
private void FilterDeferredAcceptedRelations(
Func<DeferredAcceptedParentRelation, bool> retain)
{
uint[] parents = _deferredAcceptedRelationsByParent.Keys.ToArray();
for (int index = 0; index < parents.Length; index++)
{
uint parentGuid = parents[index];
Queue<DeferredAcceptedParentRelation> retained = new(
_deferredAcceptedRelationsByParent[parentGuid].Where(retain));
if (retained.Count == 0)
_deferredAcceptedRelationsByParent.Remove(parentGuid);
else
_deferredAcceptedRelationsByParent[parentGuid] = retained;
}
foreach (RelationWindowState state in _relationWindows.Values)
state.Filters.Add(retain);
}
private void RemoveCommittedChild(uint childGuid)
@ -609,6 +909,52 @@ internal readonly record struct DeferredParentCreate(
&& Spawn.Guid != 0u;
}
/// <summary>
/// Round 5 R5-1: one ACCEPTED parent relation (the gate was already
/// consumed - the child's own POSITION_TS channel advanced at admission)
/// queued under its parent's guid because that parent was unaddressable or
/// named a not-yet-arrived incarnation. Carries exactly ONE of
/// <see cref="Standalone"/> (a standalone Parent continuation, which HAS a
/// parent incarnation to compare) or <see cref="Envelope"/> (an envelope
/// CreateParent stage, which does not).
/// </summary>
internal readonly record struct DeferredAcceptedParentRelation(
ulong AdmissionId,
uint ChildGuid,
RuntimeEntityKey ChildKey,
ParentEvent.Parsed? Standalone,
CreateParentUpdate? Envelope,
AcceptedPhysicsTimestamps AcceptedTimestamps)
{
internal bool IsValid => AdmissionId != 0UL
&& ChildGuid != 0u
&& ChildKey.LocalEntityId != 0u
&& (Standalone.HasValue ^ Envelope.HasValue);
/// <summary>Null for the envelope flavor - <see cref="CreateParentUpdate"/> carries no parent INSTANCE_TS.</summary>
internal ushort? ParentInstanceSequence => Standalone?.ParentInstanceSequence;
}
/// <summary>Round 5 R5-2: which deferred bucket a <see cref="DeferredReplayWindowToken"/> belongs to.</summary>
internal enum DeferredReplayBucketKind : byte
{
Creates,
AcceptedRelations,
}
/// <summary>
/// Round 5 R5-2: opaque handle for one open detach/restore window. See the
/// window-machinery remarks at <see cref="ParentAttachmentState"/>'s field
/// declarations for the full cancellation-awareness contract.
/// </summary>
internal readonly record struct DeferredReplayWindowToken(
ulong Id,
uint ParentGuid,
DeferredReplayBucketKind Kind)
{
internal bool IsValid => Id != 0UL;
}
public readonly record struct ParentAttachmentRelation(
uint ParentGuid,
uint ChildGuid,

View file

@ -547,6 +547,101 @@ public sealed class RuntimeEntityDirectory
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);
private bool IsKnown(RuntimeEntityRecord record)
{
if (IsCurrent(record))

View file

@ -31,6 +31,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int EquipmentOwnerCount,
int PendingMoveCount,
int InitialCreateResidenceLeaseCount,
int InitialCreateExecutorProgressCount,
int StreamSubscriberCount,
int PlacementStreamSubscriberCount,
long StreamDispatchFailureCount,
@ -38,7 +39,12 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
int PendingDispatchCount,
bool IsDispatching,
bool IsSessionClearInProgress,
bool IsDisposed)
bool IsDisposed,
/// <summary>Round 5 R5-1: pending queue-by-parent-GUID accepted relations (see <see cref="ParentAttachmentState.DeferredAcceptedRelationCount"/>).</summary>
int DeferredAcceptedRelationCount = 0,
/// <summary>Round 5 R5-3: mirrors StreamDispatchFailureCount/HasLastStreamDispatchFailure for the executor's contained-replay failure surface. Diagnostic only - like its stream precedent, NOT gated by <see cref="IsConverged"/>.</summary>
long ReplayFailureCount = 0,
bool HasLastReplayFailure = false)
{
public bool IsConverged =>
IsDisposed
@ -48,6 +54,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& AcceptedSnapshotCount == 0
&& UnresolvedParentRelationCount == 0
&& DeferredParentCreateCount == 0
&& DeferredAcceptedRelationCount == 0
&& StagedParentRelationCount == 0
&& RecoveryParentRelationCount == 0
&& CommittedParentRelationCount == 0
@ -57,6 +64,7 @@ public readonly record struct RuntimeEntityObjectOwnershipSnapshot(
&& EquipmentOwnerCount == 0
&& PendingMoveCount == 0
&& InitialCreateResidenceLeaseCount == 0
&& InitialCreateExecutorProgressCount == 0
&& StreamSubscriberCount == 0
&& PlacementStreamSubscriberCount == 0
&& PendingDispatchCount == 0
@ -127,6 +135,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -155,6 +180,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -183,6 +225,23 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
InitialCreateResidences = new RuntimeInitialCreateResidenceState(
Entities,
Physics.SetPosition);
InitialCreateExecution = new RuntimeInitialCreateContinuationExecutor(
Entities,
InitialCreateResidences,
Physics,
Events,
(spawn, isLocalPlayer) =>
RegisterEntityWithInitialResidence(spawn, isLocalPlayer),
(canonical, version, spawn, replaceGeneration) =>
ApplyAcceptedSpawn(canonical, version, spawn, replaceGeneration));
// Round 3 B3: every residence retirement path - not only the
// executor's own DiscardProgress calls - must converge the
// executor's progress AND its separately-tracked pending
// continuation placement token. This class owns both sides of the
// relationship, so it binds the delegate here rather than the
// residence state referencing the executor type directly.
InitialCreateResidences.BindRetirementNotification(
key => InitialCreateExecution.DiscardProgress(key));
Placements = new RuntimePlacementProjectionChannel(
Events,
Physics.SetPosition);
@ -197,6 +256,8 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
public RuntimePlacementProjectionChannel Placements { get; }
internal RuntimeInitialCreateResidenceState InitialCreateResidences
{ get; }
internal RuntimeInitialCreateContinuationExecutor InitialCreateExecution
{ get; }
public RuntimeEntityObjectOwnershipSnapshot CaptureOwnership()
{
@ -220,6 +281,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Objects.PendingMoveCount,
initialResidence.ActiveLeaseCount
+ initialResidence.PendingAdoptionCount,
InitialCreateExecution.ProgressCount,
Events.SubscriberCount,
Events.PlacementSubscriberCount,
Events.DispatchFailureCount,
@ -227,7 +289,10 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.PendingDispatchCount,
Events.IsDispatching,
_sessionClearInProgress,
_disposed);
_disposed,
parents.DeferredAcceptedRelationCount,
InitialCreateExecution.ReplayFailureCount,
InitialCreateExecution.LastReplayFailure is not null);
}
public void BindEventContext(
@ -238,6 +303,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
Events.BindContext(generation, frameNumber);
Placements.BindGeneration(generation);
InitialCreateResidences.BindGeneration(generation);
InitialCreateExecution.BindGeneration(generation);
}
/// <summary>
@ -1515,6 +1581,7 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
_sessionClearInProgress = true;
RuntimeEntityRecord[] active = Entities.ActiveRecords.ToArray();
InitialCreateResidences.Clear();
InitialCreateExecution.DiscardAll();
Physics.CollisionReports.LeaveWorldBatch(active);
Physics.ResetSessionPhysics();
Entities.BeginSessionClear();
@ -2028,12 +2095,20 @@ public sealed class RuntimeEntityObjectLifetime : IDisposable
private RuntimePlacementCancellationReceipt ForgetInitialCreateResidence(
RuntimeEntityRecord canonical)
{
return InitialCreateResidences.Forget(
bool forgotten = InitialCreateResidences.Forget(
canonical,
out _,
out RuntimePlacementCancellationReceipt cancellation)
? cancellation
: default;
out RuntimePlacementCancellationReceipt cancellation);
// Round 3 B3: InitialCreateResidences.Forget's own retirement
// notification already routes to InitialCreateExecution.DiscardProgress
// for a successful Forget. This explicit call is defensive-in-depth
// for the (record never held a residence) case where Forget returns
// false without ever reaching the notification - DiscardProgress is
// idempotent, so a redundant call after a successful Forget is a
// guaranteed no-op, never a double-discard.
if (canonical.Key is { } key)
InitialCreateExecution.DiscardProgress(key);
return forgotten ? cancellation : default;
}
private static RuntimePlacementCancellationReceipt PreferCancellation(

File diff suppressed because it is too large Load diff

View file

@ -362,6 +362,20 @@ internal enum RuntimeInitialCreateResidenceCompletionStatus : byte
RejectedAuthority,
}
/// <summary>
/// Result of <see cref="RuntimeInitialCreateResidenceState.ConsumeExecuted"/>,
/// the executor-only release that supersedes the host's
/// <see cref="RuntimeInitialCreateResidenceState.AcknowledgeAdoption"/> once
/// the initial placement has been adopted.
/// </summary>
internal enum RuntimeInitialCreateResidenceExecutorReleaseStatus : byte
{
Released,
Revised,
RejectedToken,
RejectedAuthority,
}
/// <summary>
/// Exact post-residence receipt. A local graphical or no-window host may run
/// the retail after-enter teleport suffix only when this receipt carries
@ -385,6 +399,29 @@ internal readonly record struct RuntimeInitialCreateResidenceOwnershipSnapshot(
&& PendingAdoptionCount == 0;
}
/// <summary>
/// Round 4 R4-4: field-masked precision for
/// <see cref="RuntimeInitialCreateResidenceState.AdvanceExecutorBaseline"/>.
/// The blanket four-field re-sync the executor previously called after
/// EVERY apply silently absorbed an external race on whichever field(s) a
/// given apply did NOT itself move - e.g. an ObjDesc/Movement/State/Vector
/// apply never touches PositionAuthorityVersion/CreateIntegrationVersion/
/// FullCellId/PlacementCommitVersion, so blanket-resyncing all four there
/// would mask a genuine concurrent bump to one of them instead of letting
/// the next <see cref="RuntimeInitialCreateResidenceState.IsCompletedCurrent"/>
/// check catch it. Each caller now passes exactly the field(s) its OWN
/// mutation moved.
/// </summary>
[Flags]
internal enum RuntimeExecutorBaselineFields : byte
{
None = 0,
PositionAuthorityVersion = 1 << 0,
CreateIntegrationVersion = 1 << 1,
FullCellId = 1 << 2,
PlacementCommitVersion = 1 << 3,
}
/// <summary>
/// Owns only initial CreateObject residence leases. DAT lookup, body creation,
/// and presentation stay outside this owner; their immutable preparation is
@ -403,6 +440,48 @@ internal sealed class RuntimeInitialCreateResidenceState
internal required RuntimeEntityRecord Record { get; init; }
internal required RuntimeInitialCreateResidenceLease Lease { get; set; }
internal required RuntimeInitialCreateResidenceReceipt Receipt { get; set; }
/// <summary>
/// True once the continuation executor has consumed the initial
/// placement's acknowledged completion through
/// <see cref="AdoptCompletedPlacement"/>. A retained
/// <c>_acknowledgedPlacementCompletions</c> entry on
/// <see cref="RuntimeSetPositionState"/> blocks EVERY later placement
/// begin for the same key (see
/// <see cref="RuntimeSetPositionState.BeginAcceptedPlacementCore"/>'s
/// <c>HasRetainedCompletion</c> guard) — a Position continuation could
/// never start its own authored placement while the initial one still
/// sits unconsumed. Adoption resolves that deadlock by consuming the
/// proof exactly once, while this flag keeps the completed entry
/// itself "current" for placement-tracking purposes even though the
/// placement token is no longer separately tracked.
/// </summary>
internal bool PlacementAdopted { get; set; }
/// <summary>
/// Executor-tracked baseline for the four version/cell fields
/// <see cref="IsCompletedCurrent"/> compares against the LIVE record.
/// Seeded from <see cref="Receipt"/>'s own (frozen, identity-matching)
/// <c>Token</c>/<c>FullCellId</c>/<c>PlacementCommitVersion</c> at the
/// moment <see cref="Complete"/> first produces this entry, then kept
/// in sync by <see cref="AdvanceExecutorBaseline"/> every time the
/// continuation executor legitimately advances one of them while
/// applying a retained continuation. <see cref="Receipt"/>.Token
/// itself must NEVER be rebaselined — a caller (the executor) always
/// re-presents the SAME original token instance on every retry, and
/// <see cref="Complete"/>'s own token-identity match
/// (<c>completed.Receipt.Token == token</c>) depends on that struct
/// staying byte-identical. Splitting "identity" (the frozen token)
/// from "expected current value" (these fields) is what lets the
/// executor's own sequential mutations keep the entry current
/// without the residence mistaking its own controlled progress for
/// an external race - see the 2026-08-01 admission handoff's own
/// warning about exactly this risk.
/// </summary>
internal ulong ExpectedPositionAuthorityVersion { get; set; }
internal ulong ExpectedCreateIntegrationVersion { get; set; }
internal uint ExpectedFullCellId { get; set; }
internal ulong ExpectedPlacementCommitVersion { get; set; }
}
private readonly RuntimeEntityDirectory _entities;
@ -410,6 +489,7 @@ internal sealed class RuntimeInitialCreateResidenceState
private readonly Dictionary<RuntimeEntityKey, Entry> _entries = [];
private readonly Dictionary<RuntimeEntityKey, CompletedEntry> _completed = [];
private Func<RuntimeGenerationToken>? _generation;
private Action<RuntimeEntityKey>? _retirementNotification;
private ulong _nextLeaseId;
internal RuntimeInitialCreateResidenceState(
@ -432,6 +512,31 @@ internal sealed class RuntimeInitialCreateResidenceState
_generation = generation;
}
/// <summary>
/// Round 3 B3: the ONE choke point every residence retirement path -
/// <see cref="Retire(Entry)"/>, <see cref="Retire(CompletedEntry)"/>,
/// <see cref="Forget"/>, and <see cref="Clear"/> - notifies through,
/// regardless of which caller (a host query, a staleness check inside
/// this class, or the continuation executor itself) triggered the
/// retirement. Without this, a residence retired by a path OTHER than
/// the executor's own <c>DiscardProgress</c> call (e.g. a host's
/// <see cref="TryGetTransaction"/> silently discovering staleness) would
/// leave the executor's progress AND its separately-tracked pending
/// continuation placement token orphaned - this class owns no reference
/// to the executor type, so the lifetime binds a plain delegate here
/// instead.
/// </summary>
internal void BindRetirementNotification(Action<RuntimeEntityKey> notify)
{
ArgumentNullException.ThrowIfNull(notify);
if (_retirementNotification is not null)
{
throw new InvalidOperationException(
"The initial Create residence retirement notification is already bound.");
}
_retirementNotification = notify;
}
internal bool CanAcceptCreate(WorldSession.EntitySpawn incoming)
{
bool parented = (incoming.ParentGuid
@ -788,10 +893,59 @@ internal sealed class RuntimeInitialCreateResidenceState
Record = record,
Lease = lease,
Receipt = receipt,
ExpectedPositionAuthorityVersion = token.PositionAuthorityVersion,
ExpectedCreateIntegrationVersion = token.CreateIntegrationVersion,
ExpectedFullCellId = receipt.FullCellId,
ExpectedPlacementCommitVersion = receipt.PlacementCommitVersion,
});
return RuntimeInitialCreateResidenceCompletionStatus.Completed;
}
/// <summary>
/// Executor-only: re-synchronizes the completed entry's staleness
/// baseline (see <see cref="CompletedEntry.ExpectedPositionAuthorityVersion"/>
/// remarks) to the record's CURRENT live values, but ONLY for the
/// field(s) named in <paramref name="fields"/> (Round 4 R4-4). Called
/// after the continuation executor legitimately advances one or more of
/// PositionAuthorityVersion/CreateIntegrationVersion/FullCellId/
/// PlacementCommitVersion while applying a retained continuation, so a
/// LATER <see cref="Complete"/>/<see cref="IsCompletedCurrent"/> check
/// does not mistake the executor's own controlled progress for an
/// external race. Passing a field NOT actually moved by the caller's own
/// mutation would defeat the whole point - it would silently bless an
/// external race on that field instead of letting the next currency
/// check catch it - so every call site names exactly its own field(s);
/// an apply that moves none of the four tracked fields (ObjDesc,
/// Movement, State, Vector) must not call this method at all. A no-op
/// (returns false) if the token no longer matches a live completed
/// entry - the executor's own currency checks catch that condition
/// independently and this call is purely advisory bookkeeping, never a
/// source of truth by itself.
/// </summary>
internal bool AdvanceExecutorBaseline(
RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceToken token,
RuntimeExecutorBaselineFields fields)
{
ArgumentNullException.ThrowIfNull(record);
if (!token.IsValid
|| !_completed.TryGetValue(token.Entity, out CompletedEntry? entry)
|| !ReferenceEquals(entry.Record, record)
|| entry.Receipt.Token != token)
{
return false;
}
if ((fields & RuntimeExecutorBaselineFields.PositionAuthorityVersion) != 0)
entry.ExpectedPositionAuthorityVersion = record.PositionAuthorityVersion;
if ((fields & RuntimeExecutorBaselineFields.CreateIntegrationVersion) != 0)
entry.ExpectedCreateIntegrationVersion = record.CreateIntegrationVersion;
if ((fields & RuntimeExecutorBaselineFields.FullCellId) != 0)
entry.ExpectedFullCellId = record.FullCellId;
if ((fields & RuntimeExecutorBaselineFields.PlacementCommitVersion) != 0)
entry.ExpectedPlacementCommitVersion = record.PlacementCommitVersion;
return true;
}
internal bool AcknowledgeAdoption(
RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceAdoptionToken token)
@ -816,7 +970,15 @@ internal sealed class RuntimeInitialCreateResidenceState
// discard accepted packets.
if (!current.Lease.Continuations.IsEmpty)
return false;
// The executor's own release path is ConsumeExecuted, not this host
// method. If the executor already adopted the placement proof
// (RuntimeInitialCreateContinuationExecutor.AdoptCompletedPlacement),
// it is gone from RuntimeSetPositionState's tracking table entirely —
// do not re-consume it a second time, just tolerate the already-
// satisfied state and fall through to the same removal every other
// caller of this host method observes.
if (current.Lease.Route.PerformsSetPosition
&& !current.PlacementAdopted
&& !_setPosition.ConsumeAcknowledgedPlacement(
current.Lease.Placement,
current.Receipt.Projection))
@ -841,6 +1003,7 @@ internal sealed class RuntimeInitialCreateResidenceState
lease = entry.Lease;
cancellation = _setPosition.ForgetExactPlacement(
lease.Placement);
_retirementNotification?.Invoke(key);
return true;
}
if (record.Key is { } completedKey
@ -853,6 +1016,7 @@ internal sealed class RuntimeInitialCreateResidenceState
lease = completed.Lease;
cancellation = _setPosition.ForgetExactPlacement(
lease.Placement);
_retirementNotification?.Invoke(completedKey);
return true;
}
lease = default;
@ -888,6 +1052,13 @@ internal sealed class RuntimeInitialCreateResidenceState
{
_setPosition.PublishCancellation(cancellations[index]);
}
if (_retirementNotification is { } notify)
{
foreach (Entry entry in active)
notify(entry.Lease.Token.Entity);
foreach (CompletedEntry entry in completed)
notify(entry.Receipt.Token.Entity);
}
}
internal RuntimeInitialCreateResidenceOwnershipSnapshot CaptureOwnership() =>
@ -917,6 +1088,24 @@ internal sealed class RuntimeInitialCreateResidenceState
return _generation?.Invoke() ?? default;
}
/// <summary>
/// The staleness check every completed-entry caller shares. Compares the
/// live record against <see cref="CompletedEntry.ExpectedPositionAuthorityVersion"/>
/// et al — an executor-tracked, continuously re-synchronized baseline —
/// rather than against <see cref="RuntimeInitialCreateResidenceReceipt.Token"/>'s
/// own FROZEN admission-time fields directly. This is what lets the
/// continuation executor's own legitimate mutations
/// (AdvancePositionAuthority, AdvanceCreateAuthority, SetFullCell,
/// AdvancePlacementCommit — all driven by applying a retained
/// continuation) keep this entry current across the many
/// <see cref="Complete"/> re-entries a multi-call drain requires, while
/// still correctly detecting a genuine EXTERNAL race (anything that
/// changes one of these fields WITHOUT going through
/// <see cref="AdvanceExecutorBaseline"/>) exactly as it always did. The
/// token itself remains the untouched identity/match key -
/// <see cref="Complete"/>'s <c>completed.Receipt.Token == token</c> check
/// depends on that.
/// </summary>
private bool IsCompletedCurrent(CompletedEntry entry)
{
RuntimeInitialCreateResidenceReceipt receipt = entry.Receipt;
@ -925,35 +1114,147 @@ internal sealed class RuntimeInitialCreateResidenceState
&& _entities.SessionLifetimeVersion
== receipt.Token.SessionLifetimeVersion
&& entry.Record.PositionAuthorityVersion
== receipt.Token.PositionAuthorityVersion
== entry.ExpectedPositionAuthorityVersion
&& entry.Record.CreateIntegrationVersion
== receipt.Token.CreateIntegrationVersion
&& entry.Record.FullCellId == receipt.FullCellId
== entry.ExpectedCreateIntegrationVersion
&& entry.Record.FullCellId == entry.ExpectedFullCellId
&& entry.Record.PlacementCommitVersion
== receipt.PlacementCommitVersion
== entry.ExpectedPlacementCommitVersion
&& entry.Lease.Route.Authority.Generation
== CurrentGeneration()
&& receipt.Token.SessionLifetimeVersion
== receipt.Adoption.SessionLifetimeVersion
&& receipt.Token.LeaseId == receipt.Adoption.LeaseId
// A completed entry whose placement proof the executor already
// adopted remains current on the placement dimension without
// re-querying RuntimeSetPositionState: AdoptCompletedPlacement
// consumed (removed) the exact tracked token, so
// IsPlacementCompletionTracked would now report false even though
// nothing here has gone stale.
&& (!entry.Lease.Route.PerformsSetPosition
|| entry.PlacementAdopted
|| _setPosition.IsPlacementCompletionTracked(
entry.Lease.Placement));
}
/// <summary>
/// Executor-only: consumes the initial placement's acknowledged
/// completion exactly once so a later retained Position continuation can
/// begin its own authored placement for the same
/// <see cref="RuntimeEntityKey"/> (see the remarks on
/// <see cref="CompletedEntry.PlacementAdopted"/> for why this is
/// necessary). Idempotent: a retry after <see cref="PlacementAdopted"/> is
/// already true is a no-op success, never a double-consume.
/// </summary>
internal bool AdoptCompletedPlacement(
RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceToken token)
{
ArgumentNullException.ThrowIfNull(record);
if (!token.IsValid
|| !_completed.TryGetValue(token.Entity, out CompletedEntry? entry)
|| !ReferenceEquals(entry.Record, record)
|| entry.Receipt.Token != token)
{
return false;
}
if (entry.PlacementAdopted)
return IsCompletedCurrent(entry);
if (!IsCompletedCurrent(entry))
{
Retire(entry);
return false;
}
if (!entry.Lease.Route.PerformsSetPosition)
{
// A Parented/PickedUp lease never captured a real placement
// token; there is nothing to consume, but the tail must still be
// able to progress past this step exactly once.
entry.PlacementAdopted = true;
return true;
}
if (!_setPosition.ConsumeAcknowledgedPlacement(
entry.Lease.Placement,
entry.Receipt.Projection))
{
return false;
}
entry.PlacementAdopted = true;
return true;
}
/// <summary>
/// Executor-only release: consumes the residence entirely once the exact
/// adoption token still matches AND the caller has applied every
/// continuation through the CURRENT lease's full length. Placement
/// consumption already happened via <see cref="AdoptCompletedPlacement"/>,
/// so this does not call <see cref="RuntimeSetPositionState.ConsumeAcknowledgedPlacement"/>
/// a second time for an adopted entry — unlike the host-facing
/// <see cref="AcknowledgeAdoption"/>, which only ever runs for entries the
/// executor has not touched.
/// </summary>
internal RuntimeInitialCreateResidenceExecutorReleaseStatus ConsumeExecuted(
RuntimeEntityRecord record,
in RuntimeInitialCreateResidenceAdoptionToken token,
ulong executedThroughSequence)
{
ArgumentNullException.ThrowIfNull(record);
if (!token.IsValid
|| !_completed.TryGetValue(token.Entity, out CompletedEntry? entry)
|| !ReferenceEquals(entry.Record, record)
|| entry.Receipt.Adoption.Entity != token.Entity
|| entry.Receipt.Adoption.LeaseId != token.LeaseId)
{
return RuntimeInitialCreateResidenceExecutorReleaseStatus
.RejectedToken;
}
if (!IsCompletedCurrent(entry))
{
Retire(entry);
return RuntimeInitialCreateResidenceExecutorReleaseStatus
.RejectedAuthority;
}
if (entry.Receipt.Adoption.Revision != token.Revision)
{
// A newer continuation arrived mid-drain (Enqueue bumps Revision
// in place on the SAME completed entry). The executor must
// re-fetch via Complete and drain the tail, never replay the
// already-applied prefix.
return RuntimeInitialCreateResidenceExecutorReleaseStatus.Revised;
}
if (!entry.PlacementAdopted && entry.Lease.Route.PerformsSetPosition)
{
throw new InvalidOperationException(
"Executor release requires the initial placement to have been adopted first.");
}
if ((ulong)entry.Lease.Continuations.Length != executedThroughSequence)
{
return RuntimeInitialCreateResidenceExecutorReleaseStatus
.RejectedAuthority;
}
return _completed.Remove(token.Entity)
? RuntimeInitialCreateResidenceExecutorReleaseStatus.Released
: RuntimeInitialCreateResidenceExecutorReleaseStatus
.RejectedAuthority;
}
private void Retire(Entry entry)
{
_entries.Remove(entry.Lease.Token.Entity);
RuntimeEntityKey key = entry.Lease.Token.Entity;
_entries.Remove(key);
RuntimePlacementCancellationReceipt cancellation =
_setPosition.ForgetExactPlacement(entry.Lease.Placement);
_setPosition.PublishCancellation(cancellation);
_retirementNotification?.Invoke(key);
}
private void Retire(CompletedEntry entry)
{
_completed.Remove(entry.Receipt.Token.Entity);
RuntimeEntityKey key = entry.Receipt.Token.Entity;
_completed.Remove(key);
RuntimePlacementCancellationReceipt cancellation =
_setPosition.ForgetExactPlacement(entry.Lease.Placement);
_setPosition.PublishCancellation(cancellation);
_retirementNotification?.Invoke(key);
}
}