using AcDream.App.Combat; using AcDream.App.Diagnostics; using AcDream.App.Input; using AcDream.Runtime; using AcDream.Runtime.Gameplay; using AcDream.UI.Abstractions.Input; namespace AcDream.App.Tests.Input; public sealed class GameplayInputCommandControllerTests { [Theory] [InlineData(InputAction.ToggleInventoryPanel, "inventory")] [InlineData(InputAction.AcdreamToggleFlyMode, "fly-or-chase")] [InlineData(InputAction.AcdreamTogglePlayerMode, "player-mode")] // Campaign OP slice OP3 (D1): F11 now opens the retail Options panel // through IRetainedGameplayWindowCommands, not the OP9-retired // IDevToolsGameplayCommands.ToggleSettingsPanel() no-op (which was // never dispatched from this switch in the first place). [InlineData(InputAction.ToggleOptionsPanel, "options")] [InlineData(InputAction.CombatToggleCombat, "combat")] [InlineData(InputAction.ToggleFloatingChatWindow1, "chat-window-1")] [InlineData(InputAction.ToggleFloatingChatWindow2, "chat-window-2")] [InlineData(InputAction.ToggleFloatingChatWindow3, "chat-window-3")] [InlineData(InputAction.ToggleFloatingChatWindow4, "chat-window-4")] public void RecognizedCommand_RoutesToTypedOwner( InputAction action, string expected) { var harness = new Harness(); bool handled = harness.Controller.Handle(action); Assert.True(handled); Assert.Equal([expected], harness.Calls); } // OP9: AcdreamToggleDebugPanel/ToggleChatEntry retired the // IDevToolsGameplayCommands seam they used to forward to — both // targets (the ImGui-era DebugPanel/ChatPanel) were already gone // (Campaign V slice V11), so the seam's own body was an unconditional // no-op. The action is still consumed (handled == true, matching the // prior no-op's contract) but claims no typed-owner call. [Theory] [InlineData(InputAction.AcdreamToggleDebugPanel)] [InlineData(InputAction.ToggleChatEntry)] public void RetiredDevToolsCommand_IsConsumedWithoutClaimingATypedOwner( InputAction action) { var harness = new Harness(); bool handled = harness.Controller.Handle(action); Assert.True(handled); Assert.Empty(harness.Calls); } [Fact] public void DiagnosticCommand_PrecedesRemainingCommandSwitch() { var harness = new Harness(); harness.Diagnostics.HandledAction = InputAction.AcdreamToggleDebugPanel; bool handled = harness.Controller.Handle( InputAction.AcdreamToggleDebugPanel); Assert.True(handled); Assert.Equal(["diagnostic"], harness.Calls); } [Fact] public void UnknownCommand_IsNotClaimed() { var harness = new Harness(); bool handled = harness.Controller.Handle(InputAction.MovementForward); Assert.False(handled); Assert.Empty(harness.Calls); } [Theory] [InlineData(true, true, true, "cancel-target")] [InlineData(false, true, true, "exit-fly")] [InlineData(false, false, true, "exit-player")] [InlineData(false, false, false, "close")] public void Escape_PreservesTargetFlyPlayerWindowPriority( bool targetMode, bool flyMode, bool playerMode, string expected) { var harness = new Harness { TargetMode = { IsActive = targetMode }, Camera = { IsFly = flyMode }, Player = { IsPlayer = playerMode }, }; bool handled = harness.Controller.Handle(InputAction.EscapeKey); Assert.True(handled); Assert.Equal([expected], harness.Calls); } private sealed class Harness { public Harness() { Retained = new FakeRetained(Calls); Diagnostics = new FakeDiagnostics(Calls); Player = new FakePlayerMode(Calls); TargetMode = new FakeTargetMode(Calls); Camera = new FakeCamera(Calls); Combat = new FakeCombat(Calls); Runtime = new FakeRuntimeView(); Window = new FakeWindow(Calls); Controller = new GameplayInputCommandController( Retained, Diagnostics, Player, TargetMode, Camera, Runtime, Combat, Window); } public List Calls { get; } = []; public FakeRetained Retained { get; } public FakeDiagnostics Diagnostics { get; } public FakePlayerMode Player { get; } public FakeTargetMode TargetMode { get; } public FakeCamera Camera { get; } public FakeCombat Combat { get; } public FakeRuntimeView Runtime { get; } public FakeWindow Window { get; } public GameplayInputCommandController Controller { get; } } private sealed class FakeRetained(List calls) : IRetainedGameplayWindowCommands { public void ToggleInventory() => calls.Add("inventory"); public void ToggleFloatingChatWindow(int windowId) => calls.Add($"chat-window-{windowId}"); public void ToggleOptionsPanel() => calls.Add("options"); } private sealed class FakeDiagnostics(List calls) : IRuntimeDiagnosticCommands { public InputAction? HandledAction { get; set; } public bool Handle(InputAction action) { if (action != HandledAction) return false; calls.Add("diagnostic"); return true; } public void CycleTimeOfDay() => calls.Add("time"); public void CycleWeather() => calls.Add("weather"); public void ToggleCollisionWireframes() => calls.Add("collision"); } private sealed class FakePlayerMode(List calls) : IPlayerModeGameplayCommands { public bool IsPlayer { get; set; } public bool IsPlayerMode => IsPlayer; public void ToggleFlyOrChase() => calls.Add("fly-or-chase"); public void TogglePlayerMode() => calls.Add("player-mode"); public void ExitPlayerMode() => calls.Add("exit-player"); } private sealed class FakeTargetMode(List calls) : IItemTargetModeCommands { public bool IsActive { get; set; } public bool IsAnyTargetModeActive => IsActive; public void CancelTargetMode() => calls.Add("cancel-target"); } private sealed class FakeCamera(List calls) : IGameplayCameraModeCommands { public bool IsFly { get; set; } public bool IsFlyMode => IsFly; public void ExitFlyMode() => calls.Add("exit-fly"); } private sealed class FakeCombat(List calls) : IRuntimeCombatCommands { public RuntimeCommandResult Execute( RuntimeGenerationToken expectedGeneration, RuntimeCombatCommand command) { Assert.Equal(RuntimeCombatCommand.ToggleMode, command); calls.Add("combat"); return new RuntimeCommandResult( RuntimeCommandStatus.Accepted, expectedGeneration); } public RuntimeCommandResult ExecuteAttack( RuntimeGenerationToken expectedGeneration, in RuntimeCombatAttackInput command) => new( RuntimeCommandStatus.Unsupported, expectedGeneration); } private sealed class FakeRuntimeView : IGameRuntimeView { public RuntimeGenerationToken Generation => new(7); public RuntimeLifecycleSnapshot Lifecycle => throw new NotSupportedException(); public IGameRuntimeClock Clock => throw new NotSupportedException(); public IRuntimeEntityView Entities => throw new NotSupportedException(); public IRuntimeInventoryView Inventory => throw new NotSupportedException(); public IRuntimeInventoryStateView InventoryState => throw new NotSupportedException(); public IRuntimeCharacterView Character => 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 AcDream.Runtime.World.IRuntimeWorldEnvironmentView Environment => throw new NotSupportedException(); public IRuntimePortalView Portal => throw new NotSupportedException(); public RuntimeStateCheckpoint CaptureCheckpoint() => throw new NotSupportedException(); } private sealed class FakeWindow(List calls) : IGameplayWindowCommands { public void Close() => calls.Add("close"); } }