refactor(runtime): own world reveal generation

This commit is contained in:
Erik 2026-07-26 17:09:54 +02:00
parent 4ab98b080e
commit a6860d5563
26 changed files with 1294 additions and 502 deletions

View file

@ -92,15 +92,59 @@ public enum RuntimePortalKind
Portal,
}
public readonly record struct RuntimeDestinationReadiness(
long Generation,
uint DestinationCell,
bool IsIndoor,
bool IsUnhydratable,
int RequiredRenderRadius,
bool IsRenderNeighborhoodReady,
bool AreCompositeTexturesReady,
bool IsCollisionReady)
{
public bool HasDestination => DestinationCell != 0u;
public bool IsReady => HasDestination
&& (IsUnhydratable
|| (IsRenderNeighborhoodReady
&& AreCompositeTexturesReady
&& IsCollisionReady));
}
public readonly record struct RuntimePortalSnapshot(
long Generation,
RuntimePortalKind Kind,
uint DestinationCell,
bool IsReady,
bool IsMaterialized,
bool IsCompleted,
bool IsCancelled,
bool IsWorldVisible);
RuntimeDestinationReadiness Readiness,
bool Materialized,
bool Completed,
bool Cancelled,
bool WorldViewportObserved,
bool WorldSimulationAvailable,
int InvariantFailureCount,
bool WaitCueShown,
int PortalMaterializationCount)
{
public static RuntimePortalSnapshot Idle { get; } = new(
Generation: 0,
RuntimePortalKind.None,
Readiness: default,
Materialized: false,
Completed: false,
Cancelled: false,
WorldViewportObserved: false,
WorldSimulationAvailable: true,
InvariantFailureCount: 0,
WaitCueShown: false,
PortalMaterializationCount: 0);
public uint DestinationCell => Readiness.DestinationCell;
public bool IsReady => Readiness.IsReady;
public bool IsMaterialized => Materialized;
public bool IsCompleted => Completed;
public bool IsCancelled => Cancelled;
public bool IsWorldVisible => WorldViewportObserved;
public bool IsActive => Generation != 0 && !Cancelled;
}
public interface IRuntimePortalView
{

View file

@ -0,0 +1,385 @@
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.
/// </summary>
/// <remarks>
/// Retail ordering follows <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
/// edge.
/// </remarks>
public sealed class RuntimeWorldTransitState
: IRuntimePortalView
{
public static readonly TimeSpan RetailWaitCueDelay =
TimeSpan.FromSeconds(5);
private readonly Action<string> _log;
private RuntimePortalSnapshot _snapshot = RuntimePortalSnapshot.Idle;
private long _nextGeneration;
public RuntimeWorldTransitState(Action<string>? log = null)
{
_log = log ?? (_ => { });
}
public RuntimePortalSnapshot Snapshot => _snapshot;
public bool IsWorldSimulationAvailable =>
_snapshot.WorldSimulationAvailable;
public int DiagnosticFailureCount { get; private set; }
public Exception? LastDiagnosticFailure { get; private set; }
public long BeginReveal(
RuntimePortalKind kind,
uint destinationCell)
{
if (kind is RuntimePortalKind.None)
throw new ArgumentOutOfRangeException(nameof(kind));
if (destinationCell == 0u)
throw new ArgumentOutOfRangeException(nameof(destinationCell));
if (_snapshot.Generation != 0
&& !_snapshot.Cancelled
&& !_snapshot.WorldViewportObserved)
{
Log("superseded", _snapshot);
}
long generation = checked(++_nextGeneration);
int failures = _snapshot.InvariantFailureCount;
int materializations = _snapshot.PortalMaterializationCount;
_snapshot = new RuntimePortalSnapshot(
generation,
kind,
new RuntimeDestinationReadiness(
generation,
destinationCell,
IsIndoor: IsIndoor(destinationCell),
IsUnhydratable: false,
RequiredRenderRadius: 0,
IsRenderNeighborhoodReady: false,
AreCompositeTexturesReady: false,
IsCollisionReady: false),
Materialized: false,
Completed: false,
Cancelled: false,
WorldViewportObserved: false,
WorldSimulationAvailable: false,
InvariantFailureCount: failures,
WaitCueShown: false,
PortalMaterializationCount: materializations);
Log("begin", _snapshot);
return generation;
}
public bool AcknowledgeDestinationReadiness(
in RuntimeDestinationReadiness acknowledgement)
{
if (!ValidateActive(
acknowledgement.Generation,
acknowledgement.DestinationCell,
"readiness"))
{
return false;
}
if (_snapshot.Readiness.IsReady)
{
if (_snapshot.Readiness == acknowledgement)
return false;
FailInvariant(
"readiness-changed-after-ready",
$"accepted={DescribeReadiness(_snapshot.Readiness)} "
+ $"actual={DescribeReadiness(acknowledgement)}");
return false;
}
bool isIndoor = IsIndoor(acknowledgement.DestinationCell);
int requiredRenderRadius = isIndoor ? 0 : 1;
if (acknowledgement.IsIndoor != isIndoor
|| acknowledgement.RequiredRenderRadius
!= requiredRenderRadius)
{
FailInvariant(
"invalid-readiness-shape",
$"indoor={acknowledgement.IsIndoor} "
+ $"expectedIndoor={isIndoor} "
+ $"radius={acknowledgement.RequiredRenderRadius} "
+ $"expectedRadius={requiredRenderRadius}");
return false;
}
if (_snapshot.Readiness == acknowledgement)
return false;
_snapshot = _snapshot with { Readiness = acknowledgement };
Log("readiness", _snapshot);
return true;
}
public bool AcknowledgeMaterialized(
long generation,
uint destinationCell)
{
if (!ValidateActive(generation, destinationCell, "materialized"))
return false;
if (_snapshot.Materialized)
return false;
if (!_snapshot.Readiness.IsReady)
{
FailInvariant("materialized-before-ready", null);
return false;
}
int count = _snapshot.PortalMaterializationCount;
if (_snapshot.Kind == RuntimePortalKind.Portal)
count = checked(count + 1);
_snapshot = _snapshot with
{
Materialized = true,
WorldSimulationAvailable = true,
PortalMaterializationCount = count,
};
Log("materialized", _snapshot);
return true;
}
public bool AcknowledgeWorldViewportVisible(long generation)
{
if (!ValidateGeneration(
generation,
"world-visible",
allowCompleted: true))
{
return false;
}
if (_snapshot.WorldViewportObserved)
return false;
if (!_snapshot.Readiness.IsReady)
{
FailInvariant("viewport-before-ready", null);
return false;
}
_snapshot = _snapshot with { WorldViewportObserved = true };
Log("world-visible", _snapshot);
return true;
}
public bool ObserveWait(long generation, TimeSpan elapsed)
{
if (generation == 0
|| generation != _snapshot.Generation
|| _snapshot.Cancelled
|| _snapshot.Completed
|| elapsed < RetailWaitCueDelay)
{
return false;
}
if (!_snapshot.WaitCueShown)
{
_snapshot = _snapshot with { WaitCueShown = true };
SafeLog(
$"[world-reveal] event=wait-cue "
+ $"elapsedMs={elapsed.TotalMilliseconds:F0} "
+ Describe(_snapshot));
}
return true;
}
public bool Complete(long generation)
{
if (!ValidateGeneration(
generation,
"complete",
allowCompleted: true))
{
return false;
}
if (_snapshot.Completed)
return false;
if (!_snapshot.Readiness.IsReady)
{
FailInvariant("complete-before-ready", null);
return false;
}
if (_snapshot.Kind == RuntimePortalKind.Portal
&& !_snapshot.Materialized)
{
FailInvariant("portal-complete-before-materialized", null);
return false;
}
_snapshot = _snapshot with
{
Completed = true,
WorldSimulationAvailable = true,
};
Log("complete", _snapshot);
return true;
}
public bool Cancel(long generation)
{
if (generation == 0
|| generation != _snapshot.Generation
|| _snapshot.Cancelled
|| _snapshot.Completed)
{
return false;
}
_snapshot = _snapshot with
{
Cancelled = true,
WorldSimulationAvailable = true,
};
Log("cancel", _snapshot);
return true;
}
/// <summary>
/// Clears session-visible state while retaining the monotonically
/// increasing generation counter so a delayed prior-session host
/// acknowledgement cannot match a future generation.
/// </summary>
public void ResetSession()
{
_snapshot = RuntimePortalSnapshot.Idle;
}
private bool ValidateActive(
long generation,
uint destinationCell,
string eventName)
{
if (!ValidateGeneration(
generation,
eventName,
allowCompleted: false))
{
return false;
}
if (destinationCell == 0u
|| destinationCell != _snapshot.DestinationCell)
{
FailInvariant(
$"{eventName}-destination-mismatch",
$"expected=0x{_snapshot.DestinationCell:X8} "
+ $"actual=0x{destinationCell:X8}");
return false;
}
return true;
}
private bool ValidateGeneration(
long generation,
string eventName,
bool allowCompleted)
{
if (generation == 0 || generation != _snapshot.Generation)
{
LogRejected(
$"{eventName}-generation-mismatch",
$"expected={_snapshot.Generation} actual={generation}");
return false;
}
if (_snapshot.Cancelled
|| (!allowCompleted && _snapshot.Completed))
{
LogRejected(
$"{eventName}-after-terminal",
$"completed={_snapshot.Completed} "
+ $"cancelled={_snapshot.Cancelled}");
return false;
}
return true;
}
private void FailInvariant(string reason, string? detail)
{
_snapshot = _snapshot with
{
InvariantFailureCount =
checked(_snapshot.InvariantFailureCount + 1),
};
string suffix =
string.IsNullOrWhiteSpace(detail) ? string.Empty : $" {detail}";
SafeLog(
$"[world-reveal] event=invariant-failure "
+ $"reason={reason}{suffix} {Describe(_snapshot)}");
}
private void LogRejected(string reason, string? detail)
{
string suffix =
string.IsNullOrWhiteSpace(detail) ? string.Empty : $" {detail}";
SafeLog(
$"[world-reveal] event=rejected "
+ $"reason={reason}{suffix} {Describe(_snapshot)}");
}
private void Log(string eventName, RuntimePortalSnapshot snapshot) =>
SafeLog($"[world-reveal] event={eventName} {Describe(snapshot)}");
private void SafeLog(string message)
{
try
{
_log(message);
}
catch (Exception error)
{
DiagnosticFailureCount =
checked(DiagnosticFailureCount + 1);
LastDiagnosticFailure = error;
}
}
private static string Describe(RuntimePortalSnapshot snapshot)
{
RuntimeDestinationReadiness ready = snapshot.Readiness;
return string.Create(
CultureInfo.InvariantCulture,
$"generation={snapshot.Generation} kind={snapshot.Kind} "
+ $"cell=0x{ready.DestinationCell:X8} "
+ $"indoor={ready.IsIndoor} "
+ $"radius={ready.RequiredRenderRadius} "
+ $"unhydratable={ready.IsUnhydratable} "
+ $"render={ready.IsRenderNeighborhoodReady} "
+ $"composites={ready.AreCompositeTexturesReady} "
+ $"collision={ready.IsCollisionReady} ready={ready.IsReady} "
+ $"materialized={snapshot.Materialized} "
+ $"completed={snapshot.Completed} "
+ $"cancelled={snapshot.Cancelled} "
+ $"visible={snapshot.WorldViewportObserved} "
+ $"simulation={snapshot.WorldSimulationAvailable} "
+ $"failures={snapshot.InvariantFailureCount}");
}
private static string DescribeReadiness(
in RuntimeDestinationReadiness readiness) =>
string.Create(
CultureInfo.InvariantCulture,
$"indoor={readiness.IsIndoor},"
+ $"unhydratable={readiness.IsUnhydratable},"
+ $"radius={readiness.RequiredRenderRadius},"
+ $"render={readiness.IsRenderNeighborhoodReady},"
+ $"composites={readiness.AreCompositeTexturesReady},"
+ $"collision={readiness.IsCollisionReady}");
private static bool IsIndoor(uint cellId) =>
(cellId & 0xFFFFu) >= 0x0100u;
}