fix(runtime): close Campaign LA7b review findings

This commit is contained in:
Erik 2026-08-14 18:55:48 +02:00
parent 0e82cbf700
commit 1b9e7e41f9
11 changed files with 700 additions and 28 deletions

View file

@ -211,6 +211,74 @@ public sealed class CurrentGameRuntimeAdapterTests
Assert.Equal(Harness.PlayerGuid, harness.Identity.ServerGuid);
}
[Fact]
public void DisposedAdapterMakesRetainedSelectionRoutesInertWhileRuntimeLives()
{
using var harness = new Harness(awaitCharacterSelection: true);
using CurrentGameRuntimeAdapter survivor =
harness.CreateAdditionalAdapter();
Assert.Equal(
RuntimeSessionStartStatus.AwaitingCharacterSelection,
harness.Runtime.Session.Start(harness.Runtime.Generation).Status);
RuntimeGenerationToken generation = harness.Runtime.Generation;
IRuntimeCharacterSelectionView retainedView =
harness.Runtime.CharacterSelection;
IRuntimeCharacterSelectionCommands publicCommands =
harness.Runtime.CharacterSelectionCommands;
IRuntimeCharacterSelectionCommands interfaceCommands =
((IGameRuntimeCommands)harness.Runtime).CharacterSelection;
var observer = new CharacterSelectionObserver();
using IDisposable subscription = retainedView.Subscribe(observer);
RuntimeCharacterSelectionSnapshot before =
survivor.CharacterSelection.Snapshot;
harness.Runtime.Dispose();
Assert.False(harness.Runtime.Lifecycle.HasTransport);
Assert.Equal(
RuntimeCharacterSelectionLifecycle.Inactive,
retainedView.Snapshot.Lifecycle);
Assert.Equal(0, retainedView.Snapshot.RosterCount);
Assert.False(retainedView.TryGetAt(0, out _));
Assert.False(retainedView.TryGet(Harness.PlayerGuid, out _));
var visitor = new CharacterSelectionVisitor();
retainedView.Visit(visitor);
Assert.Empty(visitor.Entries);
Assert.Throws<ObjectDisposedException>(() =>
retainedView.Subscribe(new CharacterSelectionObserver()));
RuntimeCommandStatus[] statuses =
[
publicCommands.Highlight(generation, 0x50000001u).Status,
publicCommands.Enter(generation).Status,
publicCommands.RequestDelete(generation).Status,
interfaceCommands.ConfirmDelete(generation).Status,
interfaceCommands.Restore(generation).Status,
interfaceCommands.Cancel(generation).Status,
];
Assert.All(statuses, status =>
Assert.Equal(RuntimeCommandStatus.Inactive, status));
RuntimeCharacterSelectionSnapshot after =
survivor.CharacterSelection.Snapshot;
Assert.Equal(before.Revision, after.Revision);
Assert.Equal(before.HighlightedCharacterId, after.HighlightedCharacterId);
Assert.Equal(before.Operation, after.Operation);
// The second lease proves the canonical Runtime is still live, while
// the disposed adapter's already-returned observer stays detached.
Assert.True(survivor.CharacterSelectionCommands.Highlight(
generation,
0x50000001u).Accepted);
Assert.Equal(0x50000001u,
survivor.CharacterSelection.Snapshot.HighlightedCharacterId);
Assert.Empty(observer.Deltas);
Assert.Equal(
RuntimeCharacterSelectionLifecycle.Inactive,
harness.Runtime.CharacterSelection.Snapshot.Lifecycle);
}
[Fact]
public void GenerationGateRejectsStaleCommandsAndStopAcknowledgesTeardown()
{
@ -735,6 +803,7 @@ public sealed class CurrentGameRuntimeAdapterTests
private readonly LiveSessionController _session;
private readonly IDisposable _combatModeBinding;
private readonly GameRuntime _gameRuntime;
private readonly SelectionInteractionController _selectionController;
public Harness(bool awaitCharacterSelection = false)
{
@ -789,7 +858,7 @@ public sealed class CurrentGameRuntimeAdapterTests
sendWield: null,
sendDrop: null);
var query = new SelectionQuery(TargetGuid);
var selectionController = new SelectionInteractionController(
_selectionController = new SelectionInteractionController(
Selection,
query,
_items,
@ -805,7 +874,7 @@ public sealed class CurrentGameRuntimeAdapterTests
_gameRuntime,
Host,
Commands,
selectionController);
_selectionController);
}
public RuntimeOptions Options { get; }
@ -842,6 +911,13 @@ public sealed class CurrentGameRuntimeAdapterTests
public LiveSessionHost Host { get; }
public CurrentGameRuntimeAdapter Runtime { get; }
public CurrentGameRuntimeAdapter CreateAdditionalAdapter() =>
new(
_gameRuntime,
Host,
Commands,
_selectionController);
public void Dispose()
{
Runtime.Dispose();
@ -853,6 +929,25 @@ public sealed class CurrentGameRuntimeAdapterTests
}
}
private sealed class CharacterSelectionVisitor
: IRuntimeCharacterSelectionVisitor
{
public List<RuntimeCharacterSelectionEntry> Entries { get; } = [];
public void Visit(in RuntimeCharacterSelectionEntry character) =>
Entries.Add(character);
}
private sealed class CharacterSelectionObserver
: IRuntimeCharacterSelectionObserver
{
public List<RuntimeCharacterSelectionDelta> Deltas { get; } = [];
public void OnCharacterSelectionChanged(
in RuntimeCharacterSelectionDelta delta) =>
Deltas.Add(delta);
}
private static void PrepareCombatAndMagic(Harness harness)
{
harness.Actions.Combat.SetCombatMode(AcDream.Core.Combat.CombatMode.Melee);