fix(world): remove non-retail portal exit fade
This commit is contained in:
parent
842bd89c16
commit
dded9e6b17
21 changed files with 919 additions and 347 deletions
150
src/AcDream.App/Streaming/TeleportTransitCoordinator.cs
Normal file
150
src/AcDream.App/Streaming/TeleportTransitCoordinator.cs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
namespace AcDream.App.Streaming;
|
||||
|
||||
/// <summary>
|
||||
/// Correlates F751 notifications with accepted Position packets without
|
||||
/// imposing presentation order on canonical physics. F751 deliberately does
|
||||
/// not consume retail's TELEPORT_TS, and the matching Position may be observed
|
||||
/// on either side of the notification.
|
||||
/// </summary>
|
||||
internal sealed class TeleportTransitCoordinator<TDestination>
|
||||
where TDestination : struct
|
||||
{
|
||||
private bool _hasLastStart;
|
||||
private ushort _lastStartSequence;
|
||||
private readonly Dictionary<ushort, TDestination> _bufferedDestinations = new();
|
||||
private bool _hasPendingStart;
|
||||
private ushort _pendingStartSequence;
|
||||
private bool _destinationAccepted;
|
||||
|
||||
public bool IsActive { get; private set; }
|
||||
public ushort Sequence { get; private set; }
|
||||
public bool HasPendingStart => _hasPendingStart;
|
||||
|
||||
/// <summary>Reject retransmitted or older F751 notifications.</summary>
|
||||
public bool CanBegin(ushort sequence) =>
|
||||
!_hasLastStart || IsNewer(_lastStartSequence, sequence);
|
||||
|
||||
/// <summary>
|
||||
/// Record a fresh notification lifetime. Presentation activation is a
|
||||
/// separate edge because acdream's optional developer camera can
|
||||
/// temporarily withdraw the normal player projection.
|
||||
/// </summary>
|
||||
public bool QueueStart(ushort sequence)
|
||||
{
|
||||
if (!CanBegin(sequence))
|
||||
return false;
|
||||
|
||||
PruneDestinationsOlderThan(sequence);
|
||||
IsActive = false;
|
||||
Sequence = 0;
|
||||
_hasPendingStart = true;
|
||||
_pendingStartSequence = sequence;
|
||||
_hasLastStart = true;
|
||||
_lastStartSequence = sequence;
|
||||
_destinationAccepted = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Activate the queued presentation and consume a destination that was
|
||||
/// accepted before the player projection became available.
|
||||
/// </summary>
|
||||
public bool Activate(out TDestination bufferedDestination)
|
||||
{
|
||||
bufferedDestination = default;
|
||||
if (!_hasPendingStart)
|
||||
return false;
|
||||
|
||||
IsActive = true;
|
||||
Sequence = _pendingStartSequence;
|
||||
_hasPendingStart = false;
|
||||
_pendingStartSequence = 0;
|
||||
_destinationAccepted = false;
|
||||
|
||||
if (!_bufferedDestinations.Remove(Sequence, out bufferedDestination))
|
||||
return false;
|
||||
|
||||
_destinationAccepted = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Offer an accepted Position packet. A matching active transit consumes
|
||||
/// its first packet even when TELEPORT_TS was already advanced. Without a
|
||||
/// matching notification, only a timestamp-advancing packet is buffered;
|
||||
/// ordinary movement can never become a future portal destination.
|
||||
/// </summary>
|
||||
public bool OfferDestination(
|
||||
ushort sequence,
|
||||
bool teleportTimestampAdvanced,
|
||||
TDestination destination,
|
||||
out TDestination acceptedDestination)
|
||||
{
|
||||
acceptedDestination = default;
|
||||
|
||||
// An advancing TELEPORT_TS makes every older destination impossible
|
||||
// to match a future F751. Retire those generations now so a skipped
|
||||
// notification cannot retain a stale destination through u16 wrap.
|
||||
if (teleportTimestampAdvanced)
|
||||
PruneDestinationsOlderThan(sequence);
|
||||
|
||||
if (IsActive && sequence == Sequence)
|
||||
{
|
||||
if (_destinationAccepted)
|
||||
return false;
|
||||
|
||||
_destinationAccepted = true;
|
||||
acceptedDestination = destination;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_hasPendingStart && sequence == _pendingStartSequence)
|
||||
{
|
||||
_bufferedDestinations.TryAdd(sequence, destination);
|
||||
return false;
|
||||
}
|
||||
|
||||
ushort correlationSequence = _hasPendingStart
|
||||
? _pendingStartSequence
|
||||
: Sequence;
|
||||
bool mayBelongToFutureStart = (!IsActive && !_hasPendingStart)
|
||||
|| IsNewer(correlationSequence, sequence);
|
||||
if (!teleportTimestampAdvanced || !mayBelongToFutureStart)
|
||||
return false;
|
||||
|
||||
_bufferedDestinations.TryAdd(sequence, destination);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>End presentation while retaining duplicate and reorder history.</summary>
|
||||
public void EndActive()
|
||||
{
|
||||
IsActive = false;
|
||||
Sequence = 0;
|
||||
_hasPendingStart = false;
|
||||
_pendingStartSequence = 0;
|
||||
_destinationAccepted = false;
|
||||
}
|
||||
|
||||
/// <summary>Clear every correlation value at a network-session boundary.</summary>
|
||||
public void ClearSession()
|
||||
{
|
||||
EndActive();
|
||||
_hasLastStart = false;
|
||||
_lastStartSequence = 0;
|
||||
_bufferedDestinations.Clear();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue