refactor(app): compose session and player startup

Move streaming, live-session, hydration, local-player, combat, and teleport construction behind the typed Phase-7 boundary. Add exact-owner runtime bindings and focused spawn-claim classification so partial startup rolls back without retaining old session targets while preserving the accepted construction and frame dependencies.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 18:28:32 +02:00
parent 3573da12e1
commit 7771c07fb6
31 changed files with 1759 additions and 532 deletions

View file

@ -42,6 +42,29 @@ public sealed class DeferredLiveEntityRuntimeComponentLifecycleTests
Assert.Throws<ArgumentException>(() => bridge.Bind(bridge));
}
[Fact]
public void OwnedBindingReleasesExactlyAndAllowsRebind()
{
var bridge = new DeferredLiveEntityRuntimeComponentLifecycle();
int firstCalls = 0;
int secondCalls = 0;
var first = new DelegateLiveEntityRuntimeComponentLifecycle(
_ => firstCalls++);
var second = new DelegateLiveEntityRuntimeComponentLifecycle(
_ => secondCalls++);
IDisposable firstBinding = bridge.BindOwned(first);
Assert.Throws<InvalidOperationException>(() => bridge.BindOwned(second));
firstBinding.Dispose();
using IDisposable secondBinding = bridge.BindOwned(second);
firstBinding.Dispose();
bridge.TearDown(CreateRecord());
Assert.Equal(0, firstCalls);
Assert.Equal(1, secondCalls);
}
[Fact]
public async Task ConcurrentBind_HasOneWinnerAndPublishesThatOwner()
{

View file

@ -32,6 +32,24 @@ public sealed class LiveEntityHydrationControllerTests
Assert.Throws<InvalidOperationException>(() => bridge.Bind(_ => false));
}
[Fact]
public void ParentAcceptanceOwnedBindingReleasesExactlyAndAllowsRebind()
{
var bridge = new DeferredLiveEntityParentAcceptance();
Func<ParentEvent.Parsed, bool> first = _ => true;
Func<ParentEvent.Parsed, bool> second = _ => false;
IDisposable firstBinding = bridge.BindOwned(first);
Assert.True(bridge.TryAccept(default));
Assert.Throws<InvalidOperationException>(() => bridge.BindOwned(second));
firstBinding.Dispose();
using IDisposable secondBinding = bridge.BindOwned(second);
firstBinding.Dispose();
Assert.False(bridge.TryAccept(default));
}
[Fact]
public void FreshCreate_PublishesCanonicalOrderAndReadyAfterMaterialization()
{
@ -1580,6 +1598,25 @@ public sealed class LiveEntityHydrationControllerTests
deferred.Bind(new RecordingNetworkSink()));
}
[Fact]
public void DeferredNetworkOwnedBindingReleasesExactlyAndAllowsRebind()
{
var deferred = new DeferredLiveEntityNetworkUpdateSink();
var first = new RecordingNetworkSink();
var second = new RecordingNetworkSink();
IDisposable firstBinding = deferred.BindOwned(first);
Assert.Throws<InvalidOperationException>(() => deferred.BindOwned(second));
firstBinding.Dispose();
using IDisposable secondBinding = deferred.BindOwned(second);
firstBinding.Dispose();
deferred.ApplySameGeneration(default);
Assert.Equal(0, first.ApplyCount);
Assert.Equal(1, second.ApplyCount);
}
private const uint Guid = 0x70000001u;
private const uint Cell = 0x01010001u;

View file

@ -343,9 +343,15 @@ public sealed class UpdateFrameOrchestratorTests
"AcDream.App",
"Composition",
"LivePresentationComposition.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new AcDream.App.Update.LiveObjectFrameController(", source);
Assert.Contains("new AcDream.App.Update.LiveSpatialPresentationReconciler(", source);
Assert.Contains("new LiveObjectFrameController(", sessionPlayer);
Assert.Contains("new LiveSpatialPresentationReconciler(", sessionPlayer);
Assert.Contains("new AcDream.App.World.RetailLiveFrameCoordinator(", source);
Assert.DoesNotContain("AdvanceLiveObjectRuntime", source, StringComparison.Ordinal);
Assert.DoesNotContain("ReconcileLiveObjectSpatialPresentation", source,
@ -363,8 +369,14 @@ public sealed class UpdateFrameOrchestratorTests
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new AcDream.App.Streaming.StreamingFrameController(", source);
Assert.Contains("new StreamingFrameController(", sessionPlayer);
Assert.DoesNotContain("_streamingFrame", source, StringComparison.Ordinal);
Assert.Equal(1, CountOccurrences(
source,
@ -385,8 +397,14 @@ public sealed class UpdateFrameOrchestratorTests
"AcDream.App",
"Rendering",
"GameWindow.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
FindRepoRoot(),
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains("new AcDream.App.Input.GameplayInputFrameController(", source);
Assert.Contains("new GameplayInputFrameController(", sessionPlayer);
Assert.DoesNotContain("_gameplayInputFrame!.Tick", source,
StringComparison.Ordinal);
Assert.DoesNotContain("_inputDispatcher?.Tick()", source,
@ -468,10 +486,16 @@ public sealed class UpdateFrameOrchestratorTests
"AcDream.App",
"Physics",
"LiveEntityNetworkUpdateController.cs"));
string sessionPlayer = File.ReadAllText(Path.Combine(
root,
"src",
"AcDream.App",
"Composition",
"SessionPlayerComposition.cs"));
Assert.Contains(
"new AcDream.App.Streaming.LocalPlayerTeleportController(",
source,
"new LocalPlayerTeleportController(",
sessionPlayer,
StringComparison.Ordinal);
Assert.DoesNotContain("_localPlayerTeleport!.Tick", source,
StringComparison.Ordinal);
@ -491,7 +515,7 @@ public sealed class UpdateFrameOrchestratorTests
AssertAppearsInOrder(
source,
"new AcDream.App.Update.LiveEntityLivenessFramePhase(",
"_localPlayerTeleport,",
"sessionPlayer.LocalTeleport,",
"new AcDream.App.Update.PlayerModeAutoEntryFramePhase(",
"cameraFrame);");
}
@ -799,7 +823,7 @@ public sealed class UpdateFrameOrchestratorTests
StringComparison.Ordinal);
Assert.Contains("d.WorldOrigin.CellLocalForSeed", livePresentation,
StringComparison.Ordinal);
Assert.Contains("_liveEntitySessionEvents.CreateSink()", source,
Assert.Contains("Live entity session routing was not composed.", source,
StringComparison.Ordinal);
}