refactor(runtime): own teleport destination correlation

This commit is contained in:
Erik 2026-07-26 17:52:34 +02:00
parent 38b3773cb9
commit 6a063a27d4
23 changed files with 1209 additions and 487 deletions

View file

@ -92,6 +92,23 @@ public enum RuntimePortalKind
Portal,
}
/// <summary>
/// Immutable, presentation-independent destination from one canonical
/// accepted local-player Position packet. The frame remains cell-local;
/// graphical hosts may translate it into their current render origin, while
/// headless hosts consume the same full retail cell identity directly.
/// </summary>
public readonly record struct RuntimeTeleportDestination(
uint EntityGuid,
ushort InstanceSequence,
ushort PositionSequence,
ushort TeleportSequence,
ushort ForcePositionSequence,
Position Position)
{
public uint CellId => Position.ObjCellId;
}
public readonly record struct RuntimeDestinationReadiness(
long Generation,
uint DestinationCell,

View file

@ -3,13 +3,16 @@ using System.Globalization;
namespace AcDream.Runtime.World;
/// <summary>
/// Canonical presentation-independent owner of one login/portal reveal
/// generation. The graphical or headless host supplies typed destination
/// readiness acknowledgements; Runtime owns which generation and destination
/// those acknowledgements are allowed to advance.
/// Canonical presentation-independent owner of local F751/Position
/// correlation and one login/portal reveal generation. The graphical or
/// headless host supplies typed destination readiness acknowledgements;
/// Runtime owns which teleport sequence, generation, and destination those
/// acknowledgements are allowed to advance.
/// </summary>
/// <remarks>
/// Retail ordering follows <c>SmartBox::TeleportPlayer @ 0x00453910</c>,
/// Retail ordering follows
/// <c>SmartBox::HandlePlayerTeleport @ 0x00452150</c>,
/// <c>SmartBox::TeleportPlayer @ 0x00453910</c>,
/// <c>SmartBox::UseTime @ 0x00455410</c>, and
/// <c>gmSmartBoxUI::EndTeleportAnimation @ 0x004D65A0</c>. Destination
/// materialization resumes simulation before the later normal-world viewport
@ -22,8 +25,19 @@ public sealed class RuntimeWorldTransitState
TimeSpan.FromSeconds(5);
private readonly Action<string> _log;
private readonly Dictionary<ushort, RuntimeTeleportDestination>
_bufferedDestinations = [];
private RuntimePortalSnapshot _snapshot = RuntimePortalSnapshot.Idle;
private long _nextGeneration;
private bool _hasLastTeleportStart;
private ushort _lastTeleportStartSequence;
private bool _hasPendingTeleportStart;
private ushort _pendingTeleportStartSequence;
private bool _teleportActive;
private ushort _activeTeleportSequence;
private bool _destinationAccepted;
private bool _hasAcceptedDestination;
private RuntimeTeleportDestination _acceptedDestination;
public RuntimeWorldTransitState(Action<string>? log = null)
{
@ -33,10 +47,70 @@ public sealed class RuntimeWorldTransitState
public RuntimePortalSnapshot Snapshot => _snapshot;
public bool IsWorldSimulationAvailable =>
_snapshot.WorldSimulationAvailable;
public bool IsTeleportActive => _teleportActive;
public ushort ActiveTeleportSequence =>
_teleportActive ? _activeTeleportSequence : (ushort)0;
public bool HasPendingTeleportStart => _hasPendingTeleportStart;
public bool HasAcceptedTeleportDestination => _hasAcceptedDestination;
public int BufferedTeleportDestinationCount =>
_bufferedDestinations.Count;
public int DiagnosticFailureCount { get; private set; }
public Exception? LastDiagnosticFailure { get; private set; }
public long BeginReveal(
/// <summary>
/// Starts initial-login reveal. Portal reveals must instead claim the
/// exact F751-correlated destination through
/// <see cref="TryBeginPortalReveal"/>.
/// </summary>
public long BeginLoginReveal(uint destinationCell) =>
BeginRevealCore(RuntimePortalKind.Login, destinationCell);
/// <summary>
/// Atomically claims the one accepted Position paired with the active F751
/// sequence and starts its reveal generation.
/// </summary>
public bool TryBeginPortalReveal(
ushort teleportSequence,
uint destinationCell,
out long generation)
{
generation = 0;
if (!_teleportActive
|| teleportSequence != _activeTeleportSequence
|| !_hasAcceptedDestination)
{
LogRejected(
"portal-begin-without-destination",
$"active={_teleportActive} "
+ $"expectedSequence={_activeTeleportSequence} "
+ $"actualSequence={teleportSequence} "
+ $"accepted={_hasAcceptedDestination}");
return false;
}
RuntimeTeleportDestination destination = _acceptedDestination;
if (destination.TeleportSequence != teleportSequence
|| destination.CellId == 0u
|| destination.CellId != destinationCell)
{
FailInvariant(
"portal-begin-destination-mismatch",
$"sequence={teleportSequence} "
+ $"acceptedSequence={destination.TeleportSequence} "
+ $"expectedCell=0x{destination.CellId:X8} "
+ $"actualCell=0x{destinationCell:X8}");
return false;
}
generation = BeginRevealCore(
RuntimePortalKind.Portal,
destinationCell);
_hasAcceptedDestination = false;
_acceptedDestination = default;
return true;
}
private long BeginRevealCore(
RuntimePortalKind kind,
uint destinationCell)
{
@ -79,6 +153,151 @@ public sealed class RuntimeWorldTransitState
return generation;
}
/// <summary>
/// Returns whether an F751 sequence is newer than this transit owner's
/// already-observed notification history. Canonical player TELEPORT_TS is
/// checked independently by the Runtime entity/physics owner.
/// </summary>
public bool CanQueueTeleportStart(ushort sequence) =>
!_hasLastTeleportStart
|| IsNewer(_lastTeleportStartSequence, sequence);
/// <summary>
/// Records one fresh F751 notification without advancing canonical
/// TELEPORT_TS. Presentation activation is a separate host edge.
/// </summary>
public bool TryQueueTeleportStart(ushort sequence)
{
if (!CanQueueTeleportStart(sequence))
return false;
PruneDestinationsOlderThan(sequence);
_teleportActive = false;
_activeTeleportSequence = 0;
_hasPendingTeleportStart = true;
_pendingTeleportStartSequence = sequence;
_hasLastTeleportStart = true;
_lastTeleportStartSequence = sequence;
_destinationAccepted = false;
_hasAcceptedDestination = false;
_acceptedDestination = default;
return true;
}
/// <summary>
/// Promotes the queued F751 lifetime once its host can enter portal space.
/// A Position accepted before that host edge becomes the same active
/// destination; no position or physics state is applied here.
/// </summary>
public bool ActivateQueuedTeleport()
{
if (!_hasPendingTeleportStart)
return false;
_teleportActive = true;
_activeTeleportSequence = _pendingTeleportStartSequence;
_hasPendingTeleportStart = false;
_pendingTeleportStartSequence = 0;
_destinationAccepted = false;
_hasAcceptedDestination = false;
_acceptedDestination = default;
if (!_bufferedDestinations.Remove(
_activeTeleportSequence,
out RuntimeTeleportDestination buffered))
{
return true;
}
_destinationAccepted = true;
_hasAcceptedDestination = true;
_acceptedDestination = buffered;
return true;
}
/// <summary>
/// Offers a destination only after canonical Runtime physics has accepted
/// and applied its Position packet. Ordinary, non-advancing movement can
/// never become a future portal destination.
/// </summary>
public bool OfferTeleportDestination(
in RuntimeTeleportDestination destination,
bool teleportTimestampAdvanced)
{
ushort sequence = destination.TeleportSequence;
if (destination.CellId == 0u)
return false;
if (teleportTimestampAdvanced)
PruneDestinationsOlderThan(sequence);
if (_teleportActive && sequence == _activeTeleportSequence)
{
if (_destinationAccepted)
return false;
_destinationAccepted = true;
_hasAcceptedDestination = true;
_acceptedDestination = destination;
return true;
}
if (_hasPendingTeleportStart
&& sequence == _pendingTeleportStartSequence)
{
_bufferedDestinations.TryAdd(sequence, destination);
return false;
}
ushort correlationSequence = _hasPendingTeleportStart
? _pendingTeleportStartSequence
: _activeTeleportSequence;
bool mayBelongToFutureStart =
(!_teleportActive && !_hasPendingTeleportStart)
|| IsNewer(correlationSequence, sequence);
if (!teleportTimestampAdvanced || !mayBelongToFutureStart)
return false;
_bufferedDestinations.TryAdd(sequence, destination);
return false;
}
public bool TryGetAcceptedTeleportDestination(
out RuntimeTeleportDestination destination)
{
destination = _acceptedDestination;
return _teleportActive && _hasAcceptedDestination;
}
/// <summary>
/// Read-only preflight used immediately before the host mutates its
/// presentation placement. The post-placement acknowledgement repeats the
/// same exact validation.
/// </summary>
public bool CanPlacePortalDestination(
long generation,
ushort teleportSequence,
uint destinationCell) =>
IsCurrentPortalDestination(
generation,
teleportSequence,
destinationCell);
/// <summary>
/// Ends one graphical/headless portal consumer while preserving
/// duplicate/reorder history for the current network session.
/// </summary>
public void EndTeleport()
{
_teleportActive = false;
_activeTeleportSequence = 0;
_hasPendingTeleportStart = false;
_pendingTeleportStartSequence = 0;
_destinationAccepted = false;
_hasAcceptedDestination = false;
_acceptedDestination = default;
}
public bool AcknowledgeDestinationReadiness(
in RuntimeDestinationReadiness acknowledgement)
{
@ -116,10 +335,24 @@ public sealed class RuntimeWorldTransitState
return true;
}
public bool AcknowledgeMaterialized(
public bool AcknowledgePortalMaterialized(
long generation,
ushort teleportSequence,
uint destinationCell)
{
if (!IsCurrentPortalDestination(
generation,
teleportSequence,
destinationCell))
{
LogRejected(
"materialized-portal-mismatch",
$"generation={generation} "
+ $"sequence={teleportSequence} "
+ $"cell=0x{destinationCell:X8}");
return false;
}
if (!ValidateActive(generation, destinationCell, "materialized"))
return false;
if (_snapshot.Materialized)
@ -247,8 +480,26 @@ public sealed class RuntimeWorldTransitState
public void ResetSession()
{
_snapshot = RuntimePortalSnapshot.Idle;
EndTeleport();
_hasLastTeleportStart = false;
_lastTeleportStartSequence = 0;
_bufferedDestinations.Clear();
}
private bool IsCurrentPortalDestination(
long generation,
ushort teleportSequence,
uint destinationCell) =>
_teleportActive
&& teleportSequence == _activeTeleportSequence
&& generation != 0
&& generation == _snapshot.Generation
&& _snapshot.Kind == RuntimePortalKind.Portal
&& !_snapshot.Cancelled
&& !_snapshot.Completed
&& destinationCell != 0u
&& destinationCell == _snapshot.DestinationCell;
private bool ValidateActive(
long generation,
uint destinationCell,
@ -360,6 +611,21 @@ public sealed class RuntimeWorldTransitState
+ $"failures={snapshot.InvariantFailureCount}");
}
private static bool IsNewer(ushort current, ushort incoming) =>
AcDream.Core.Physics.PhysicsTimestampGate.IsNewer(
current,
incoming);
private void PruneDestinationsOlderThan(ushort sequence)
{
foreach (ushort bufferedSequence
in _bufferedDestinations.Keys.ToArray())
{
if (IsNewer(bufferedSequence, sequence))
_bufferedDestinations.Remove(bufferedSequence);
}
}
private static bool IsIndoor(uint cellId) =>
(cellId & 0xFFFFu) >= 0x0100u;
}