refactor(runtime): move session lifetime and ordered transport

Move the canonical WorldSession generation, connect/enter/tick/stop transaction, inbound subscription owner, and retryable teardown acknowledgements into AcDream.Runtime. Keep App as a borrowing graphical host with a single inertable command projection and no mirrored session state.

Validated by 79 Runtime tests, 3,776 App tests with three existing skips, the Release solution build, and 8,428 complete Release tests with five existing skips.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-25 19:39:24 +02:00
parent ecc4816c5a
commit 7593078774
37 changed files with 884 additions and 355 deletions

View file

@ -26,6 +26,7 @@ using AcDream.Core.Player;
using AcDream.Core.Social;
using AcDream.Core.Spells;
using AcDream.Core.World;
using AcDream.Runtime.Session;
using Silk.NET.Windowing;
namespace AcDream.App.Composition;
@ -437,13 +438,17 @@ internal sealed class SessionPlayerCompositionPhase
static () => new LiveSessionController(),
static value => value.Dispose());
LiveSessionController liveSession = liveSessionLease.Resource;
var liveSessionCommands = new LiveSessionCommandSurface();
var liveSessionSource = new LiveSessionAppSource(
liveSession,
liveSessionCommands);
bindings.Adopt(
"retained-UI live session",
interaction.LateBindings.Session.Bind(liveSession));
interaction.LateBindings.Session.Bind(liveSessionSource));
var localPhysicsTimestamps =
new LiveSessionLocalPhysicsTimestampPublisher(
d.PlayerIdentity,
liveSession);
liveSessionSource);
Fault(SessionPlayerCompositionPoint.LiveSessionCreated);
var teardown = new LiveEntityRuntimeTeardownController(
@ -520,7 +525,7 @@ internal sealed class SessionPlayerCompositionPhase
d.PlayerHost,
d.PlayerIdentity,
d.UpdateClock,
liveSession,
liveSessionSource,
localPhysicsTimestamps.Publish,
d.MovementDiagnostics);
var liveness = new LiveEntityLivenessController(
@ -569,8 +574,8 @@ internal sealed class SessionPlayerCompositionPhase
d.Settings,
d.PlayerController,
d.PlayerOutbound,
liveSession,
liveSession,
liveSessionSource,
liveSessionSource,
d.CombatFeedback)));
Fault(SessionPlayerCompositionPoint.CombatOperationsBound);
@ -585,7 +590,7 @@ internal sealed class SessionPlayerCompositionPhase
d.ChaseCameraInput,
d.MovementInput,
d.PlayerOutbound,
liveSession,
liveSessionSource,
host.MouseLookCursor,
new EnvironmentInputMonotonicClock())
: null;
@ -606,7 +611,7 @@ internal sealed class SessionPlayerCompositionPhase
d.Options.LiveMode,
d.PlayerMode,
d.PlayerController,
liveSession,
liveSessionSource,
d.WorldOrigin,
networkUpdates,
new FlyCameraStreamingObserverSource(host.CameraController),
@ -663,7 +668,7 @@ internal sealed class SessionPlayerCompositionPhase
d.PlayerHost,
localPlayerProjection,
d.PlayerOutbound,
liveSession);
liveSessionSource);
var localPlayerFrame = new RetailLocalPlayerFrameController(
localPlayerFrameRuntime,
d.MovementInput);
@ -703,7 +708,7 @@ internal sealed class SessionPlayerCompositionPhase
localPlayerShadow,
d.PlayerApproachCompletions,
gameplayInput,
liveSession,
liveSessionSource,
d.MovementDiagnostics,
d.PlayerSkills,
d.ViewportAspect);
@ -714,11 +719,11 @@ internal sealed class SessionPlayerCompositionPhase
devTools.LateBindings.PlayerModeCommands.BindOwned(playerMode));
bindings.Adopt(
"developer command bus",
devTools.CommandBus.BindOwned(liveSession));
devTools.CommandBus.BindOwned(liveSessionSource));
}
var playerModeAutoEntry = new PlayerModeAutoEntry(
new LivePlayerModeAutoEntryContext(
liveSession,
liveSessionSource,
live.LiveEntities,
d.PlayerIdentity,
worldReveal,
@ -750,7 +755,7 @@ internal sealed class SessionPlayerCompositionPhase
d.ChaseCameraInput,
d.WorldOrigin,
liveSpatialReconciler),
new LocalPlayerTeleportSession(liveSession),
new LocalPlayerTeleportSession(liveSessionSource),
new LocalPlayerTeleportPresentation(portalTunnel)));
var teleportLease = scope.Own(
"local-player teleport",
@ -845,8 +850,16 @@ internal sealed class SessionPlayerCompositionPhase
live.Presentation,
d.RemoteMovementObservations,
live.RenderSceneShadow),
liveSessionCommands,
d.Log);
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(liveSession);
LiveSessionHost sessionHost = sessionRuntimeFactory.Create(
liveSession,
new LiveSessionConnectOptions(
d.Options.LiveMode,
d.Options.LiveHost,
d.Options.LivePort,
d.Options.LiveUser ?? string.Empty,
d.Options.LivePass ?? string.Empty));
Fault(SessionPlayerCompositionPoint.SessionHostCreated);
Action<string>? debugToast = d.SettingsDevTools.DevTools is { } devOwner
@ -867,9 +880,9 @@ internal sealed class SessionPlayerCompositionPhase
"live combat-mode commands",
d.CombatModeCommands.BindOwned(combatCommand));
var gameRuntime = new CurrentGameRuntimeAdapter(
d.Options,
liveSession,
sessionHost,
liveSessionCommands,
d.PlayerIdentity,
live.LiveEntities,
d.Objects,