feat(runtime): share chat commands and run login sequence

This commit is contained in:
Erik 2026-08-14 20:27:45 +02:00
parent 5535d0adac
commit 41b15efd4d
57 changed files with 1739 additions and 345 deletions

View file

@ -28,6 +28,7 @@ using AcDream.Runtime;
using AcDream.Runtime.Entities;
using AcDream.Runtime.Gameplay;
using AcDream.Runtime.Session;
using AcDream.Runtime.Chat;
using AcDream.UI.Abstractions.Panels.Chat;
using AcDream.UI.Abstractions.Panels.Vitals;
using DatReaderWriter;
@ -106,6 +107,9 @@ internal sealed class LiveSessionRuntimeFactory
private readonly LiveMovementStatsApplier _movementStats;
private readonly SessionStatusWriter _statusWriter;
private readonly string _sessionId;
private readonly IReadOnlyList<string> _loginCommands;
private readonly TimeSpan _loginCommandDelay;
private readonly TimeProvider _timeProvider;
public LiveSessionRuntimeFactory(
LiveSessionPlayerRuntime player,
@ -116,7 +120,10 @@ internal sealed class LiveSessionRuntimeFactory
LiveSessionCommandSurface commands,
Action<string> log,
SessionStatusWriter? statusWriter = null,
string sessionId = "app")
string sessionId = "app",
IReadOnlyList<string>? loginCommands = null,
int loginCommandDelayMs = 500,
TimeProvider? timeProvider = null)
{
_player = player ?? throw new ArgumentNullException(nameof(player));
_domain = domain ?? throw new ArgumentNullException(nameof(domain));
@ -130,6 +137,14 @@ internal sealed class LiveSessionRuntimeFactory
// status file configured — every call site below stays unconditional.
_statusWriter = statusWriter ?? new SessionStatusWriter(null);
_sessionId = sessionId ?? throw new ArgumentNullException(nameof(sessionId));
if (loginCommandDelayMs < 0)
{
throw new ArgumentOutOfRangeException(
nameof(loginCommandDelayMs));
}
_loginCommands = loginCommands is null ? [] : [.. loginCommands];
_loginCommandDelay = TimeSpan.FromMilliseconds(loginCommandDelayMs);
_timeProvider = timeProvider ?? TimeProvider.System;
// C3c-F1: stat recomputes route through the Runtime movement owner's
// typed application seam; App keeps zero direct controller mutations.
_movementStats = new LiveMovementStatsApplier(
@ -150,6 +165,17 @@ internal sealed class LiveSessionRuntimeFactory
LiveSessionResetPlan reset =
LiveSessionResetManifest.Create(
CreateResetBindings(resetHost));
var loginCommands = new LoginCommandSequence(
_loginCommands,
_loginCommandDelay,
new RuntimeChatCommandFeedback(_domain.Communication),
_commands,
failure => _statusWriter.LoginCommandFailed(
_sessionId,
failure.CommandIndex,
failure.Command,
failure.Error),
_timeProvider);
return new LiveSessionHost(controller, new LiveSessionHostBindings(
Routing: new(
CreateEventRouter,
@ -194,7 +220,8 @@ internal sealed class LiveSessionRuntimeFactory
CharacterEntered: selection => _statusWriter.EnteredWorld(
_sessionId,
selection.CharacterId,
selection.CharacterName)),
selection.CharacterName),
LoginCommands: loginCommands),
connectOptions);
}