refactor(runtime): publish canonical entity object deltas

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>
This commit is contained in:
Erik 2026-07-26 06:42:13 +02:00
parent d3e96ff912
commit ce3ac310d9
23 changed files with 2352 additions and 666 deletions

View file

@ -5,6 +5,7 @@ using AcDream.App.World;
using AcDream.Core.Chat;
using AcDream.Core.Items;
using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Session;
namespace AcDream.App.Runtime;
@ -21,8 +22,8 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
private readonly ClientObjectTable _objects;
private readonly ChatLog _chat;
private readonly IGameRuntimeClock _clock;
private readonly EntityView _entityView;
private readonly InventoryView _inventoryView;
private readonly IRuntimeEntityView _entityView;
private readonly IRuntimeInventoryView _inventoryView;
private readonly ChatView _chatView;
private readonly MovementView _movementView;
private readonly PortalView _portalView;
@ -32,7 +33,7 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
LiveSessionController session,
LocalPlayerIdentityState playerIdentity,
LiveEntityRuntime entities,
ClientObjectTable objects,
RuntimeEntityObjectLifetime entityObjects,
ChatLog chat,
ILocalPlayerControllerSource playerController,
WorldRevealCoordinator worldReveal,
@ -42,12 +43,13 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
_playerIdentity = playerIdentity
?? throw new ArgumentNullException(nameof(playerIdentity));
_entities = entities ?? throw new ArgumentNullException(nameof(entities));
_objects = objects ?? throw new ArgumentNullException(nameof(objects));
ArgumentNullException.ThrowIfNull(entityObjects);
_objects = entityObjects.Objects;
_chat = chat ?? throw new ArgumentNullException(nameof(chat));
_clock = clock ?? throw new ArgumentNullException(nameof(clock));
_entityView = new EntityView(_entities);
_inventoryView = new InventoryView(_objects, _entities);
_entityView = entityObjects.EntityView;
_inventoryView = entityObjects.InventoryView;
_chatView = new ChatView(_chat);
_movementView = new MovementView(
playerController
@ -108,103 +110,6 @@ internal sealed class CurrentGameRuntimeViewAdapter : IGameRuntimeView
internal void Deactivate() => _active = false;
internal static RuntimeEntitySnapshot Snapshot(LiveEntityRecord record) =>
new(
new RuntimeEntityIdentity(
record.ServerGuid,
record.LocalEntityId ?? 0u,
record.Generation),
record.FullCellId,
(uint)record.FinalPhysicsState,
record.PhysicsBody?.CellPosition,
record.LocalEntityId.HasValue,
record.IsSpatiallyVisible,
record.InitialHydrationCompleted);
internal static RuntimeInventoryItemSnapshot Snapshot(
ClientObject item,
LiveEntityRuntime entities,
ushort? exactGeneration = null)
{
ushort generation = exactGeneration
?? (entities.TryGetRecord(item.ObjectId, out LiveEntityRecord record)
? record.Generation
: (ushort)0);
return new RuntimeInventoryItemSnapshot(
item.ObjectId,
generation,
item.Name,
item.ContainerId,
item.ContainerSlot,
item.WielderId,
(uint)item.CurrentlyEquippedLocation,
item.StackSize,
item.Value);
}
private sealed class EntityView(LiveEntityRuntime owner)
: IRuntimeEntityView
{
public int Count => owner.Count;
public int MaterializedCount => owner.MaterializedCount;
public bool TryGet(uint serverGuid, out RuntimeEntitySnapshot entity)
{
if (owner.TryGetRecord(serverGuid, out LiveEntityRecord record))
{
entity = Snapshot(record);
return true;
}
entity = default;
return false;
}
public void Visit(IRuntimeEntityVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
foreach (LiveEntityRecord record in owner.Records)
{
RuntimeEntitySnapshot entity = Snapshot(record);
visitor.Visit(in entity);
}
}
}
private sealed class InventoryView(
ClientObjectTable owner,
LiveEntityRuntime entities)
: IRuntimeInventoryView
{
public int ObjectCount => owner.ObjectCount;
public int ContainerCount => owner.ContainerCount;
public bool TryGet(
uint objectId,
out RuntimeInventoryItemSnapshot item)
{
ClientObject? found = owner.Get(objectId);
if (found is not null)
{
item = Snapshot(found, entities);
return true;
}
item = default;
return false;
}
public void Visit(IRuntimeInventoryVisitor visitor)
{
ArgumentNullException.ThrowIfNull(visitor);
foreach (ClientObject item in owner.Objects)
{
RuntimeInventoryItemSnapshot snapshot = Snapshot(item, entities);
visitor.Visit(in snapshot);
}
}
}
private sealed class ChatView(ChatLog owner) : IRuntimeChatView
{
public long Revision => owner.Revision;