refactor(runtime): own canonical action state
Move selection, combat, and interaction target mode under one Runtime owner; make plugins, retained UI, session routing, and typed runtime views borrow its exact children; and add failure-safe reset, instance isolation, source ownership, and normalized checkpoint coverage without changing retail ordering. Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
parent
bb45afef33
commit
b298f99f91
38 changed files with 711 additions and 93 deletions
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<Using Include="Xunit" />
|
||||
<Using Include="AcDream.Runtime.Gameplay" />
|
||||
<Using Include="AcDream.App.Tests.BoundedTestDatCollection" Alias="DatCollection" />
|
||||
</ItemGroup>
|
||||
|
||||
|
|
|
|||
|
|
@ -186,9 +186,8 @@ public sealed class InteractionRetainedUiCompositionTests
|
|||
RetainedInputCapture: null!,
|
||||
InputDispatcher: null,
|
||||
Settings: null!,
|
||||
Combat: null!,
|
||||
Actions: null!,
|
||||
CombatAttackOperations: null!,
|
||||
Selection: null!,
|
||||
Inventory: null!,
|
||||
MagicCatalog: null!,
|
||||
Character: null!,
|
||||
|
|
|
|||
|
|
@ -234,6 +234,7 @@ public sealed class InteractionUiRuntimeSourcesTests
|
|||
public IRuntimeCharacterView Character => null!;
|
||||
public IRuntimeSocialView Social => null!;
|
||||
public IRuntimeChatView Chat => null!;
|
||||
public IRuntimeActionView Actions => null!;
|
||||
public IRuntimeMovementView Movement => null!;
|
||||
public IRuntimePortalView Portal => null!;
|
||||
public IRuntimeSessionCommands Session => null!;
|
||||
|
|
|
|||
|
|
@ -199,6 +199,7 @@ public sealed class GameplayInputCommandControllerTests
|
|||
throw new NotSupportedException();
|
||||
public IRuntimeSocialView Social => throw new NotSupportedException();
|
||||
public IRuntimeChatView Chat => throw new NotSupportedException();
|
||||
public IRuntimeActionView Actions => throw new NotSupportedException();
|
||||
public IRuntimeMovementView Movement => throw new NotSupportedException();
|
||||
public IRuntimePortalView Portal => throw new NotSupportedException();
|
||||
public RuntimeStateCheckpoint CaptureCheckpoint() =>
|
||||
|
|
|
|||
|
|
@ -161,6 +161,7 @@ public sealed class SelectionInteractionControllerTests
|
|||
Items = new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
new InteractionState(),
|
||||
() => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Assert.Equal(new RuntimeGenerationToken(1), start.Generation);
|
||||
Assert.Equal(Harness.PlayerGuid, harness.Identity.ServerGuid);
|
||||
Assert.Equal(RuntimeLifecycleState.InWorld, harness.Runtime.Lifecycle.State);
|
||||
Assert.Same(harness.Actions.View, harness.Runtime.Actions);
|
||||
|
||||
LiveEntityRecord liveRecord =
|
||||
harness.Entities.RegisterAndMaterializeProjection(Spawn(
|
||||
|
|
@ -117,6 +118,10 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Assert.Equal(1, checkpoint.ChatCount);
|
||||
Assert.Equal(1L, checkpoint.ChatRevision);
|
||||
Assert.Equal(1UL, checkpoint.FrameNumber);
|
||||
Assert.Equal(
|
||||
Harness.TargetGuid,
|
||||
checkpoint.Actions.SelectedObjectId);
|
||||
Assert.Equal(1, checkpoint.Actions.SelectionRevision);
|
||||
Assert.Equal(RuntimePortalKind.Portal, checkpoint.Portal.Kind);
|
||||
Assert.Equal(0x12340001u, checkpoint.Portal.DestinationCell);
|
||||
Assert.True(checkpoint.Portal.IsReady);
|
||||
|
|
@ -676,7 +681,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
EntityObjects);
|
||||
Objects = EntityObjects.Objects;
|
||||
Communication = new RuntimeCommunicationState();
|
||||
Selection = new SelectionState();
|
||||
Actions = new RuntimeActionState();
|
||||
MovementInput = new DispatcherMovementInputSource();
|
||||
GameplayInput = new GameplayInputFrameController(
|
||||
dispatcher: null,
|
||||
|
|
@ -697,6 +702,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
_items = new ItemInteractionController(
|
||||
Objects,
|
||||
InventoryState.Transactions,
|
||||
Actions.Interaction,
|
||||
() => PlayerGuid,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -726,10 +732,10 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
InventoryState,
|
||||
Character,
|
||||
Communication,
|
||||
Actions,
|
||||
new LocalPlayerControllerSlot(),
|
||||
WorldReveal,
|
||||
Clock,
|
||||
Selection,
|
||||
selectionController,
|
||||
GameplayInput,
|
||||
Combat);
|
||||
|
|
@ -744,7 +750,8 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
public ClientObjectTable Objects { get; }
|
||||
public RuntimeCommunicationState Communication { get; }
|
||||
public ChatLog Chat => Communication.Chat;
|
||||
public SelectionState Selection { get; }
|
||||
public RuntimeActionState Actions { get; }
|
||||
public SelectionState Selection => Actions.Selection;
|
||||
public DispatcherMovementInputSource MovementInput { get; }
|
||||
public GameplayInputFrameController GameplayInput { get; }
|
||||
public RecordingCombatCommand Combat { get; }
|
||||
|
|
@ -763,6 +770,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Communication.Dispose();
|
||||
_session.Dispose();
|
||||
_items.Dispose();
|
||||
Actions.Dispose();
|
||||
Entities.Clear();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
159
tests/AcDream.App.Tests/Runtime/RuntimeActionOwnershipTests.cs
Normal file
159
tests/AcDream.App.Tests/Runtime/RuntimeActionOwnershipTests.cs
Normal file
|
|
@ -0,0 +1,159 @@
|
|||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace AcDream.App.Tests.Runtime;
|
||||
|
||||
public sealed class RuntimeActionOwnershipTests
|
||||
{
|
||||
[Fact]
|
||||
public void ProductionConstructsOneCanonicalActionOwner()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
string gameWindow = ReadAppSource(
|
||||
root,
|
||||
"Rendering",
|
||||
"GameWindow.cs");
|
||||
string program = ReadAppSource(root, "Program.cs");
|
||||
|
||||
Assert.Contains(
|
||||
"private readonly RuntimeActionState _runtimeActions = new();",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_runtimeActions.Selection;",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"_runtimeActions.Combat;",
|
||||
gameWindow,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"window.Selection,",
|
||||
program,
|
||||
StringComparison.Ordinal);
|
||||
|
||||
string[] productionFiles = Directory
|
||||
.EnumerateFiles(
|
||||
Path.Combine(root, "src", "AcDream.App"),
|
||||
"*.cs",
|
||||
SearchOption.AllDirectories)
|
||||
.Where(static path =>
|
||||
!path.Contains(
|
||||
$"{Path.DirectorySeparatorChar}Studio{Path.DirectorySeparatorChar}",
|
||||
StringComparison.Ordinal))
|
||||
.ToArray();
|
||||
string production = string.Join(
|
||||
"\n",
|
||||
productionFiles.Select(File.ReadAllText));
|
||||
|
||||
Assert.Empty(Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+(?:AcDream\.Core\.Selection\.)?SelectionState\s*\("));
|
||||
Assert.Empty(Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+(?:AcDream\.Core\.Combat\.)?CombatState\s*\("));
|
||||
Assert.Empty(Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+(?:AcDream\.Runtime\.Gameplay\.)?InteractionState\s*\("));
|
||||
Assert.Equal(
|
||||
1,
|
||||
Regex.Matches(
|
||||
production,
|
||||
@"\bnew\s+RuntimeActionState\s*\(").Count
|
||||
+ Regex.Matches(
|
||||
production,
|
||||
@"RuntimeActionState\s+\w+\s*=\s*new\s*\(\s*\)").Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UiSessionRuntimeAndShutdownBorrowTheExactActionChildren()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
string ui = ReadAppSource(
|
||||
root,
|
||||
"Composition",
|
||||
"InteractionRetainedUiComposition.cs");
|
||||
string session = ReadAppSource(
|
||||
root,
|
||||
"Composition",
|
||||
"SessionPlayerComposition.cs");
|
||||
string liveSession = ReadAppSource(
|
||||
root,
|
||||
"Net",
|
||||
"LiveSessionRuntimeFactory.cs");
|
||||
string commands = ReadAppSource(
|
||||
root,
|
||||
"Runtime",
|
||||
"CurrentGameRuntimeCommandAdapter.cs");
|
||||
string itemInteraction = ReadAppSource(
|
||||
root,
|
||||
"UI",
|
||||
"ItemInteractionController.cs");
|
||||
string shutdown = ReadAppSource(
|
||||
root,
|
||||
"Rendering",
|
||||
"GameWindowLifetime.cs");
|
||||
|
||||
Assert.Contains("d.Actions.Interaction,", ui, StringComparison.Ordinal);
|
||||
Assert.Contains("d.Actions.Selection", ui, StringComparison.Ordinal);
|
||||
Assert.Contains("d.Actions.Combat", ui, StringComparison.Ordinal);
|
||||
Assert.Contains("d.Actions.Selection", session, StringComparison.Ordinal);
|
||||
Assert.Contains("d.Actions.Combat", session, StringComparison.Ordinal);
|
||||
Assert.Contains("_domain.Actions.Combat", liveSession, StringComparison.Ordinal);
|
||||
Assert.Contains("_actions.Selection", commands, StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"InteractionState interactionState,",
|
||||
itemInteraction,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"new InteractionState",
|
||||
itemInteraction,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Contains(
|
||||
"\"runtime action state\"",
|
||||
shutdown,
|
||||
StringComparison.Ordinal);
|
||||
AssertAppearsInOrder(
|
||||
shutdown,
|
||||
"\"runtime inventory state\"",
|
||||
"\"runtime action state\"",
|
||||
"\"runtime entity/object lifetime\"");
|
||||
Assert.False(File.Exists(Path.Combine(
|
||||
root,
|
||||
"src",
|
||||
"AcDream.App",
|
||||
"UI",
|
||||
"InteractionState.cs")));
|
||||
}
|
||||
|
||||
private static string ReadAppSource(string root, params string[] relative) =>
|
||||
File.ReadAllText(Path.Combine(
|
||||
[root, "src", "AcDream.App", .. relative]));
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "AcDream.slnx")))
|
||||
return current.FullName;
|
||||
current = current.Parent;
|
||||
}
|
||||
throw new DirectoryNotFoundException("AcDream.slnx was not found.");
|
||||
}
|
||||
|
||||
private static void AssertAppearsInOrder(
|
||||
string source,
|
||||
params string[] fragments)
|
||||
{
|
||||
int cursor = 0;
|
||||
foreach (string fragment in fragments)
|
||||
{
|
||||
int index = source.IndexOf(
|
||||
fragment,
|
||||
cursor,
|
||||
StringComparison.Ordinal);
|
||||
Assert.True(index >= 0, $"Missing source fragment: {fragment}");
|
||||
cursor = index + fragment.Length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -154,6 +154,7 @@ public sealed class CursorFeedbackControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -190,6 +191,7 @@ public sealed class CursorFeedbackControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -229,6 +231,7 @@ public sealed class CursorFeedbackControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -258,6 +261,7 @@ public sealed class CursorFeedbackControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ public sealed class ItemInteractionControllerTests
|
|||
Controller = new ItemInteractionController(
|
||||
Objects,
|
||||
SharedTransactions,
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: requestUse is null ? Uses.Add : null,
|
||||
sendExamine: Examines.Add,
|
||||
|
|
@ -341,6 +342,7 @@ public sealed class ItemInteractionControllerTests
|
|||
Assert.Throws<ArgumentException>(() => new ItemInteractionController(
|
||||
objects,
|
||||
transactions,
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -859,6 +859,7 @@ public sealed class AppraisalUiControllerTests
|
|||
=> new(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => 0x50000002u,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ public sealed class ExternalContainerControllerTests
|
|||
Interaction = new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: Uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -509,6 +509,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -546,6 +547,7 @@ public class InventoryControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -715,6 +717,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -760,6 +763,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -812,6 +816,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -870,6 +875,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -939,6 +945,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -1002,6 +1009,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -1062,6 +1070,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -1109,6 +1118,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -1149,6 +1159,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -1188,6 +1199,7 @@ public class InventoryControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -53,6 +53,7 @@ public class PaperdollControllerTests
|
|||
var itemInteraction = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
() => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -266,6 +266,7 @@ public class ToolbarControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
playerGuid: () => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: (source, target) => useWithTarget.Add((source, target)),
|
||||
|
|
@ -332,6 +333,7 @@ public class ToolbarControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
playerGuid: () => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -381,6 +383,7 @@ public class ToolbarControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
playerGuid: () => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -510,6 +513,7 @@ public class ToolbarControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
() => player,
|
||||
sendUse: uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -568,6 +572,7 @@ public class ToolbarControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -633,6 +638,7 @@ public class ToolbarControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -799,6 +805,7 @@ public class ToolbarControllerTests
|
|||
using var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
playerGuid: () => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -848,6 +855,7 @@ public class ToolbarControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: (s, t) => (sentSource, sentTarget) = (s, t),
|
||||
|
|
@ -890,6 +898,7 @@ public class ToolbarControllerTests
|
|||
var interaction = new ItemInteractionController(
|
||||
repo,
|
||||
new InventoryTransactionState(repo),
|
||||
new InteractionState(),
|
||||
() => player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ public sealed class RetailItemConfirmationControllerTests
|
|||
var items = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -54,6 +55,7 @@ public sealed class RetailItemConfirmationControllerTests
|
|||
var items = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
@ -83,6 +85,7 @@ public sealed class RetailItemConfirmationControllerTests
|
|||
var items = new ItemInteractionController(
|
||||
objects,
|
||||
new InventoryTransactionState(objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: uses.Add,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -164,6 +164,7 @@ public sealed class RetailUiInteractionFlowTests
|
|||
var interaction = new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
new InteractionState(),
|
||||
playerGuid: () => Player,
|
||||
sendUse: Uses.Add,
|
||||
sendUseWithTarget: (source, target) => UseWithTarget.Add((source, target)),
|
||||
|
|
@ -199,6 +200,7 @@ public sealed class RetailUiInteractionFlowTests
|
|||
itemInteraction ??= new ItemInteractionController(
|
||||
Objects,
|
||||
new InventoryTransactionState(Objects),
|
||||
new InteractionState(),
|
||||
() => Player,
|
||||
sendUse: null,
|
||||
sendUseWithTarget: null,
|
||||
|
|
|
|||
|
|
@ -169,6 +169,17 @@ public sealed class GameRuntimeContractTests
|
|||
new RuntimeSocialSnapshot(9, 10, 11, 0, 0, 0, 0),
|
||||
ChatRevision: 12,
|
||||
ChatCount: 13,
|
||||
new RuntimeActionSnapshot(
|
||||
SelectionRevision: 14,
|
||||
SelectedObjectId: 0x50000001u,
|
||||
PreviousObjectId: 0u,
|
||||
PreviousValidObjectId: 0u,
|
||||
CombatRevision: 15,
|
||||
CombatMode: AcDream.Core.Combat.CombatMode.Missile,
|
||||
TrackedTargetHealthCount: 1,
|
||||
InteractionRevision: 16,
|
||||
InteractionMode: InteractionModeKind.Use,
|
||||
InteractionSourceObjectId: 0u),
|
||||
default,
|
||||
default);
|
||||
|
||||
|
|
@ -178,6 +189,9 @@ public sealed class GameRuntimeContractTests
|
|||
Assert.Contains("inventory-state=2:4:1:3", entry.Text);
|
||||
Assert.Contains("character=5:6:7:8:0:0", entry.Text);
|
||||
Assert.Contains("social=9:10:11", entry.Text);
|
||||
Assert.Contains(
|
||||
"actions=14:50000001:15:4:1:16:1:00000000",
|
||||
entry.Text);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -190,6 +204,8 @@ public sealed class GameRuntimeContractTests
|
|||
typeof(RuntimeCharacterState),
|
||||
typeof(RuntimeCharacterOptionsState),
|
||||
typeof(RuntimeMovementSkillState),
|
||||
typeof(RuntimeActionState),
|
||||
typeof(InteractionState),
|
||||
];
|
||||
|
||||
foreach (Type owner in owners)
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
using AcDream.App.UI;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.App.Tests.UI;
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
public sealed class InteractionStateTests
|
||||
{
|
||||
|
|
@ -26,7 +26,8 @@ public sealed class InteractionStateTests
|
|||
public void UseItemOnTarget_RejectsMissingSource()
|
||||
{
|
||||
var state = new InteractionState();
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => state.EnterUseItemOnTarget(0));
|
||||
Assert.Throws<ArgumentOutOfRangeException>(
|
||||
() => state.EnterUseItemOnTarget(0));
|
||||
Assert.Equal(InteractionMode.None, state.Current);
|
||||
}
|
||||
|
||||
120
tests/AcDream.Runtime.Tests/Gameplay/RuntimeActionStateTests.cs
Normal file
120
tests/AcDream.Runtime.Tests/Gameplay/RuntimeActionStateTests.cs
Normal file
|
|
@ -0,0 +1,120 @@
|
|||
using AcDream.Core.Combat;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Runtime.Gameplay;
|
||||
|
||||
namespace AcDream.Runtime.Tests.Gameplay;
|
||||
|
||||
public sealed class RuntimeActionStateTests
|
||||
{
|
||||
[Fact]
|
||||
public void ViewProjectsTheExactCanonicalChildrenAndRevisions()
|
||||
{
|
||||
using var actions = new RuntimeActionState();
|
||||
|
||||
actions.Selection.Select(
|
||||
0x50000001u,
|
||||
SelectionChangeSource.World);
|
||||
actions.Combat.SetCombatMode(CombatMode.Missile);
|
||||
actions.Combat.OnUpdateHealth(0x50000001u, 0.625f);
|
||||
actions.Interaction.EnterUseItemOnTarget(0x70000001u);
|
||||
|
||||
RuntimeActionSnapshot snapshot = actions.View.Snapshot;
|
||||
|
||||
Assert.Equal(1, snapshot.SelectionRevision);
|
||||
Assert.Equal(0x50000001u, snapshot.SelectedObjectId);
|
||||
Assert.Equal(2, snapshot.CombatRevision);
|
||||
Assert.Equal(CombatMode.Missile, snapshot.CombatMode);
|
||||
Assert.Equal(1, snapshot.TrackedTargetHealthCount);
|
||||
Assert.Equal(1, snapshot.InteractionRevision);
|
||||
Assert.Equal(
|
||||
InteractionModeKind.UseItemOnTarget,
|
||||
snapshot.InteractionMode);
|
||||
Assert.Equal(0x70000001u, snapshot.InteractionSourceObjectId);
|
||||
Assert.True(
|
||||
actions.View.TryGetHealth(0x50000001u, out float health));
|
||||
Assert.Equal(0.625f, health);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetSessionConvergesEveryChildAfterObserverFailures()
|
||||
{
|
||||
var actions = new RuntimeActionState();
|
||||
actions.Selection.Select(
|
||||
0x50000001u,
|
||||
SelectionChangeSource.World);
|
||||
actions.Combat.SetCombatMode(CombatMode.Melee);
|
||||
actions.Combat.OnUpdateHealth(0x50000001u, 0.25f);
|
||||
actions.Interaction.EnterExamine();
|
||||
|
||||
actions.Selection.Changed += static _ =>
|
||||
throw new InvalidOperationException("selection observer");
|
||||
actions.Combat.CombatModeChanged += static _ =>
|
||||
throw new InvalidOperationException("combat observer");
|
||||
actions.Interaction.Changed += static _ =>
|
||||
throw new InvalidOperationException("interaction observer");
|
||||
|
||||
Assert.Throws<AggregateException>(actions.ResetSession);
|
||||
|
||||
RuntimeActionOwnershipSnapshot snapshot =
|
||||
actions.CaptureOwnership();
|
||||
Assert.Equal(0u, snapshot.SelectedObjectId);
|
||||
Assert.Equal(0u, snapshot.PreviousObjectId);
|
||||
Assert.Equal(0u, snapshot.PreviousValidObjectId);
|
||||
Assert.Equal(CombatMode.NonCombat, snapshot.CombatMode);
|
||||
Assert.Equal(0, snapshot.TrackedTargetHealthCount);
|
||||
Assert.Equal(InteractionMode.None, snapshot.InteractionMode);
|
||||
Assert.Throws<AggregateException>(actions.Dispose);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DisposeIsTerminalAndConvergedAfterObserverFailures()
|
||||
{
|
||||
var actions = new RuntimeActionState();
|
||||
actions.Selection.Select(
|
||||
0x50000001u,
|
||||
SelectionChangeSource.World);
|
||||
actions.Combat.SetCombatMode(CombatMode.Magic);
|
||||
actions.Combat.OnUpdateHealth(0x50000001u, 0.5f);
|
||||
actions.Interaction.EnterUse();
|
||||
actions.Selection.Changed += static _ =>
|
||||
throw new InvalidOperationException("selection observer");
|
||||
actions.Combat.CombatModeChanged += static _ =>
|
||||
throw new InvalidOperationException("combat observer");
|
||||
actions.Interaction.Changed += static _ =>
|
||||
throw new InvalidOperationException("interaction observer");
|
||||
|
||||
Assert.Throws<AggregateException>(actions.Dispose);
|
||||
|
||||
RuntimeActionOwnershipSnapshot snapshot =
|
||||
actions.CaptureOwnership();
|
||||
Assert.True(snapshot.IsConverged);
|
||||
actions.Dispose();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void InstancesAreFullyIsolated()
|
||||
{
|
||||
using var first = new RuntimeActionState();
|
||||
using var second = new RuntimeActionState();
|
||||
|
||||
first.Selection.Select(
|
||||
0x50000001u,
|
||||
SelectionChangeSource.World);
|
||||
first.Combat.SetCombatMode(CombatMode.Missile);
|
||||
first.Interaction.EnterUse();
|
||||
|
||||
Assert.Equal(
|
||||
new RuntimeActionSnapshot(
|
||||
0,
|
||||
0u,
|
||||
0u,
|
||||
0u,
|
||||
0,
|
||||
CombatMode.NonCombat,
|
||||
0,
|
||||
0,
|
||||
InteractionModeKind.None,
|
||||
0u),
|
||||
second.View.Snapshot);
|
||||
}
|
||||
}
|
||||
|
|
@ -15,6 +15,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
var inventory = new RuntimeInventoryState(entities);
|
||||
var character = new RuntimeCharacterState();
|
||||
var communication = new RuntimeCommunicationState();
|
||||
var actions = new RuntimeActionState();
|
||||
inventory.Shortcuts.Changed += static () => { };
|
||||
inventory.Shortcuts.Load([new ShortcutEntry(1, 2u, 3u)]);
|
||||
inventory.ItemMana.OnQueryItemManaResponse(2u, 0.5f, true);
|
||||
|
|
@ -60,12 +61,19 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
new HashSet<uint> { 4u })));
|
||||
IDisposable subscription =
|
||||
communication.Events.Subscribe(new NoopObserver());
|
||||
actions.Selection.Select(
|
||||
0x50000002u,
|
||||
AcDream.Core.Selection.SelectionChangeSource.World);
|
||||
actions.Combat.SetCombatMode(AcDream.Core.Combat.CombatMode.Melee);
|
||||
actions.Combat.OnUpdateHealth(0x50000002u, 0.75f);
|
||||
actions.Interaction.EnterUse();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot populated =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
communication,
|
||||
actions);
|
||||
|
||||
Assert.False(populated.IsConverged);
|
||||
Assert.Equal(1, populated.Inventory.ShortcutCount);
|
||||
|
|
@ -73,7 +81,13 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
Assert.Equal(1, populated.Communication.StreamSubscriberCount);
|
||||
Assert.True(populated.Communication.HasReplyTarget);
|
||||
Assert.True(populated.Communication.HasRetellTarget);
|
||||
Assert.Equal(0x50000002u, populated.Actions.SelectedObjectId);
|
||||
Assert.Equal(
|
||||
AcDream.Core.Combat.CombatMode.Melee,
|
||||
populated.Actions.CombatMode);
|
||||
Assert.Equal(1, populated.Actions.TrackedTargetHealthCount);
|
||||
|
||||
actions.Dispose();
|
||||
communication.Dispose();
|
||||
character.Dispose();
|
||||
inventory.Dispose();
|
||||
|
|
@ -83,7 +97,8 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
communication,
|
||||
actions);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(0, retired.Inventory.ShortcutSubscriberCount);
|
||||
|
|
@ -99,6 +114,7 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
var inventory = new RuntimeInventoryState(entities);
|
||||
var character = new RuntimeCharacterState();
|
||||
var communication = new RuntimeCommunicationState();
|
||||
var actions = new RuntimeActionState();
|
||||
|
||||
inventory.ExternalContainers.RequestOpen(0x70000001u);
|
||||
inventory.ExternalContainers.ApplyViewContents(0x70000001u);
|
||||
|
|
@ -119,12 +135,14 @@ public sealed class RuntimeGameplayOwnershipTests
|
|||
Assert.Throws<AggregateException>(inventory.Dispose);
|
||||
Assert.Throws<AggregateException>(character.Dispose);
|
||||
communication.Dispose();
|
||||
actions.Dispose();
|
||||
|
||||
RuntimeGameplayOwnershipSnapshot retired =
|
||||
RuntimeGameplayOwnership.Capture(
|
||||
inventory,
|
||||
character,
|
||||
communication);
|
||||
communication,
|
||||
actions);
|
||||
|
||||
Assert.True(retired.IsConverged);
|
||||
Assert.Equal(1, retired.Communication.DispatchFailureCount);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue