refactor(runtime): own interaction transactions

Move use throttling, appraisal identity, queued interactions, and exact post-arrival pickup state beneath RuntimeActionState. Keep App as the picker, movement, transport, and retained-presentation adapter while preserving retail send and UseDone ordering. Add reset, disposal, GUID-reuse, callback-reentrancy, and transport-failure coverage.

Co-authored-by: Codex <noreply@openai.com>
This commit is contained in:
Erik 2026-07-26 11:13:09 +02:00
parent be73bccf5a
commit f5f7b4177f
31 changed files with 1365 additions and 432 deletions

View file

@ -1,47 +0,0 @@
namespace AcDream.App.Input;
/// <summary>
/// Orders input-originated item interactions after the local player's movement
/// edge has been serialized for the same frame, but before inbound network
/// dispatch.
/// </summary>
/// <remarks>
/// Silk input callbacks arrive before the retail object phase. A movement-key
/// release and a later Use press can therefore be observed in one frame even
/// though the movement controller has not yet emitted the release packet.
/// Draining here preserves that event order on the wire: the release is sent
/// first, then the one-shot interaction. ACE otherwise interprets the later
/// MoveToState as cancellation of the Use-created MoveTo chain.
/// </remarks>
public sealed class OutboundInteractionQueue
{
private readonly Queue<Action> _pending = new();
private uint _clearEpoch;
public int Count => _pending.Count;
public void Enqueue(Action action)
{
ArgumentNullException.ThrowIfNull(action);
_pending.Enqueue(action);
}
/// <summary>
/// Drain only the actions present at the frame boundary. An action that
/// enqueues another action leaves it for the next frame instead of making
/// the current dispatch recursively unbounded.
/// </summary>
public void Drain()
{
int count = _pending.Count;
uint epoch = _clearEpoch;
while (count-- > 0 && epoch == _clearEpoch && _pending.Count > 0)
_pending.Dequeue().Invoke();
}
public void Clear()
{
_pending.Clear();
_clearEpoch++;
}
}