Issue stable Runtime identities at canonical registration, publish entity and inventory commits through one generation-stamped synchronous stream, and make graphical adapters borrow the same direct views and events as a no-window host. Preserve exact projection teardown and retail mutation order while removing App-side event reconstruction. Make the hard-recenter ordering fixture independent of the production two-millisecond frame budget so its injected-failure gate is deterministic. Co-authored-by: Codex <codex@openai.com>
250 lines
6.9 KiB
C#
250 lines
6.9 KiB
C#
namespace AcDream.Runtime;
|
|
|
|
public readonly record struct RuntimeEventStamp(
|
|
RuntimeGenerationToken Generation,
|
|
ulong Sequence,
|
|
ulong FrameNumber);
|
|
|
|
public enum RuntimeCommandDomain
|
|
{
|
|
Session,
|
|
Selection,
|
|
Combat,
|
|
Movement,
|
|
Chat,
|
|
Portal,
|
|
}
|
|
|
|
public readonly record struct RuntimeLifecycleDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeLifecycleState Previous,
|
|
RuntimeLifecycleState Current);
|
|
|
|
public readonly record struct RuntimeCommandDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeCommandDomain Domain,
|
|
int Operation,
|
|
RuntimeCommandStatus Status,
|
|
uint PrimaryObjectId = 0u,
|
|
string? Text = null);
|
|
|
|
public enum RuntimeEntityChange
|
|
{
|
|
Registered,
|
|
Updated,
|
|
Rebucketed,
|
|
Hidden,
|
|
Withdrawn,
|
|
Deleted,
|
|
}
|
|
|
|
public readonly record struct RuntimeEntityDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeEntityChange Change,
|
|
RuntimeEntitySnapshot Entity);
|
|
|
|
public enum RuntimeInventoryChange
|
|
{
|
|
Added,
|
|
Updated,
|
|
Moved,
|
|
Removed,
|
|
Cleared,
|
|
}
|
|
|
|
public readonly record struct RuntimeInventoryDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeInventoryChange Change,
|
|
RuntimeInventoryItemSnapshot Item);
|
|
|
|
public readonly record struct RuntimeChatEntry(
|
|
long Revision,
|
|
uint SenderGuid,
|
|
int Kind,
|
|
string Sender,
|
|
string Text,
|
|
string ChannelName);
|
|
|
|
public readonly record struct RuntimeChatDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeChatEntry Entry);
|
|
|
|
public readonly record struct RuntimeMovementDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeMovementSnapshot Movement);
|
|
|
|
public readonly record struct RuntimePortalDelta(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimePortalSnapshot Portal);
|
|
|
|
public interface IRuntimeEventObserver
|
|
{
|
|
void OnLifecycle(in RuntimeLifecycleDelta delta);
|
|
|
|
void OnCommand(in RuntimeCommandDelta delta);
|
|
|
|
void OnEntity(in RuntimeEntityDelta delta);
|
|
|
|
void OnInventory(in RuntimeInventoryDelta delta);
|
|
|
|
void OnChat(in RuntimeChatDelta delta);
|
|
|
|
void OnMovement(in RuntimeMovementDelta delta);
|
|
|
|
void OnPortal(in RuntimePortalDelta delta);
|
|
}
|
|
|
|
public interface IRuntimeEventSource
|
|
{
|
|
IDisposable Subscribe(IRuntimeEventObserver observer);
|
|
}
|
|
|
|
public enum RuntimeTraceKind
|
|
{
|
|
Lifecycle,
|
|
Command,
|
|
Entity,
|
|
Inventory,
|
|
Chat,
|
|
Movement,
|
|
Portal,
|
|
Checkpoint,
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stable normalized trace row used to compare a direct host with an adapter
|
|
/// host without retaining either owner's object graph.
|
|
/// </summary>
|
|
public readonly record struct RuntimeTraceEntry(
|
|
RuntimeEventStamp Stamp,
|
|
RuntimeTraceKind Kind,
|
|
int Code,
|
|
long Value,
|
|
uint PrimaryObjectId,
|
|
uint SecondaryObjectId,
|
|
string Text);
|
|
|
|
public sealed class RuntimeTraceRecorder : IRuntimeEventObserver
|
|
{
|
|
private readonly List<RuntimeTraceEntry> _entries = [];
|
|
|
|
public IReadOnlyList<RuntimeTraceEntry> Entries => _entries;
|
|
|
|
public void AddCheckpoint(
|
|
RuntimeEventStamp stamp,
|
|
in RuntimeStateCheckpoint checkpoint)
|
|
{
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
stamp,
|
|
RuntimeTraceKind.Checkpoint,
|
|
(int)checkpoint.Lifecycle,
|
|
checkpoint.ChatRevision,
|
|
(uint)checkpoint.EntityCount,
|
|
(uint)checkpoint.InventoryObjectCount,
|
|
string.Create(
|
|
System.Globalization.CultureInfo.InvariantCulture,
|
|
$"frame={checkpoint.FrameNumber};materialized={checkpoint.MaterializedEntityCount};" +
|
|
$"containers={checkpoint.InventoryContainerCount};chat={checkpoint.ChatCount};" +
|
|
$"portal={checkpoint.Portal.Generation}:{(int)checkpoint.Portal.Kind}:" +
|
|
$"{checkpoint.Portal.DestinationCell:X8}:{checkpoint.Portal.IsReady}")));
|
|
}
|
|
|
|
public void OnLifecycle(in RuntimeLifecycleDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Lifecycle,
|
|
(int)delta.Current,
|
|
(int)delta.Previous,
|
|
0u,
|
|
0u,
|
|
string.Empty));
|
|
|
|
public void OnCommand(in RuntimeCommandDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Command,
|
|
((int)delta.Domain << 16) | (delta.Operation & 0xFFFF),
|
|
(int)delta.Status,
|
|
delta.PrimaryObjectId,
|
|
0u,
|
|
delta.Text ?? string.Empty));
|
|
|
|
public void OnEntity(in RuntimeEntityDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Entity,
|
|
(int)delta.Change,
|
|
delta.Entity.Identity.Incarnation,
|
|
delta.Entity.Identity.ServerGuid,
|
|
delta.Entity.CellId,
|
|
string.Empty));
|
|
|
|
public void OnInventory(in RuntimeInventoryDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Inventory,
|
|
(int)delta.Change,
|
|
delta.Item.StackSize,
|
|
delta.Item.ObjectId,
|
|
delta.Item.ContainerId,
|
|
delta.Item.Name ?? string.Empty));
|
|
|
|
public void OnChat(in RuntimeChatDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Chat,
|
|
delta.Entry.Kind,
|
|
delta.Entry.Revision,
|
|
delta.Entry.SenderGuid,
|
|
0u,
|
|
$"{delta.Entry.ChannelName}|{delta.Entry.Sender}|{delta.Entry.Text}"));
|
|
|
|
public void OnMovement(in RuntimeMovementDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Movement,
|
|
delta.Movement.IsAirborne ? 1 : 0,
|
|
BitConverter.DoubleToInt64Bits(delta.Movement.SimulationTimeSeconds),
|
|
delta.Movement.LocalEntityId,
|
|
delta.Movement.Position.ObjCellId,
|
|
string.Empty));
|
|
|
|
public void OnPortal(in RuntimePortalDelta delta) =>
|
|
_entries.Add(new RuntimeTraceEntry(
|
|
delta.Stamp,
|
|
RuntimeTraceKind.Portal,
|
|
(int)delta.Portal.Kind,
|
|
delta.Portal.Generation,
|
|
delta.Portal.DestinationCell,
|
|
0u,
|
|
$"{delta.Portal.IsReady}:{delta.Portal.IsMaterialized}:" +
|
|
$"{delta.Portal.IsCompleted}:{delta.Portal.IsCancelled}:" +
|
|
$"{delta.Portal.IsWorldVisible}"));
|
|
}
|
|
|
|
/// <summary>Monotonic sequence owner for one runtime instance.</summary>
|
|
public sealed class RuntimeEventSequencer
|
|
{
|
|
private RuntimeGenerationToken _generation;
|
|
private bool _hasGeneration;
|
|
private ulong _sequence;
|
|
|
|
public RuntimeEventStamp Next(
|
|
RuntimeGenerationToken generation,
|
|
ulong frameNumber)
|
|
{
|
|
if (!_hasGeneration || generation != _generation)
|
|
{
|
|
_generation = generation;
|
|
_sequence = 0;
|
|
_hasGeneration = true;
|
|
}
|
|
|
|
return new RuntimeEventStamp(
|
|
generation,
|
|
checked(++_sequence),
|
|
frameNumber);
|
|
}
|
|
|
|
public ulong LastSequence => _sequence;
|
|
}
|