diff --git a/src/AcDream.App/Input/GameplayInputCommandController.cs b/src/AcDream.App/Input/GameplayInputCommandController.cs index 948e406f..f3fbb3a1 100644 --- a/src/AcDream.App/Input/GameplayInputCommandController.cs +++ b/src/AcDream.App/Input/GameplayInputCommandController.cs @@ -239,8 +239,6 @@ internal sealed class GameplayInputCommandController : IGameplayInputCommandTarg { if (_targetMode.IsAnyTargetModeActive) _targetMode.CancelTargetMode(); - else if (_camera.IsFlyMode) - _camera.ExitFlyMode(); else if (_playerMode.IsPlayerMode) _playerMode.ExitPlayerMode(); else diff --git a/src/AcDream.UI.Abstractions/Input/KeyBindings.cs b/src/AcDream.UI.Abstractions/Input/KeyBindings.cs index 0102dc29..52a55e5c 100644 --- a/src/AcDream.UI.Abstractions/Input/KeyBindings.cs +++ b/src/AcDream.UI.Abstractions/Input/KeyBindings.cs @@ -122,7 +122,6 @@ public sealed class KeyBindings b.Add(new(new KeyChord(Key.F9, ModifierMask.None), InputAction.AcdreamSensitivityUp)); b.Add(new(new KeyChord(Key.M, ModifierMask.Ctrl), InputAction.AcdreamToggleAudioMute)); b.Add(new(new KeyChord(Key.F10, ModifierMask.None), InputAction.AcdreamCycleWeather)); - b.Add(new(new KeyChord(Key.F, ModifierMask.None), InputAction.AcdreamToggleFlyMode)); b.Add(new(new KeyChord(Key.Tab, ModifierMask.None), InputAction.AcdreamTogglePlayerMode)); b.Add(new(new KeyChord(Key.Escape, ModifierMask.None), InputAction.EscapeKey)); @@ -368,14 +367,15 @@ public sealed class KeyBindings // collide with anything retail-faithful. b.Add(new(new KeyChord(Key.M, ModifierMask.Ctrl), InputAction.AcdreamToggleAudioMute)); - // K-fix2 (2026-04-26): free-fly toggle keyboard shortcut. - // Retail leaves Ctrl+Shift+F unbound (retail F = SelectionPickUp, - // Ctrl+F = unused) so this is non-conflicting. Also discoverable - // via View → Camera in the ImGui MainMenuBar and the - // "Toggle Free-Fly Mode" button in the Debug panel. - b.Add(new( - new KeyChord(Key.F, ModifierMask.Ctrl | ModifierMask.Shift), - InputAction.AcdreamToggleFlyMode)); + // The free-fly camera has NO key binding. It is a developer camera + // retail never had, and a player who reaches it finds the client in a + // state nothing in the retail UI explains. Its two discovery routes + // named by the old comment here — the ImGui View menu and the Debug + // panel button — both went away with AcDream.UI.ImGui at Campaign V, + // so the shortcut was the last way in. + // + // The mode's implementation is still present and is scheduled for + // removal; unbinding it is what makes it unreachable today. // K-fix1 (2026-04-26): RMB-hold camera orbit. Coexists with the // SelectRight Click binding above resolves only after a stationary diff --git a/tests/AcDream.App.Tests/Input/GameplayInputCommandControllerTests.cs b/tests/AcDream.App.Tests/Input/GameplayInputCommandControllerTests.cs index e74d6031..c0cfce5e 100644 --- a/tests/AcDream.App.Tests/Input/GameplayInputCommandControllerTests.cs +++ b/tests/AcDream.App.Tests/Input/GameplayInputCommandControllerTests.cs @@ -79,12 +79,22 @@ public sealed class GameplayInputCommandControllerTests Assert.Empty(harness.Calls); } + /// + /// Escape's priority chain: cancel a target mode, else leave player mode, + /// else close a window. + /// + /// + /// The free-fly rung was REMOVED (2026-08-21, user direction: free-fly + /// "should not be in the client"). The middle row below is the proof — a + /// session that is somehow in fly mode now falls through to the next rung + /// rather than silently exiting a camera the player has no way to enter. + /// [Theory] [InlineData(true, true, true, "cancel-target")] - [InlineData(false, true, true, "exit-fly")] + [InlineData(false, true, true, "exit-player")] [InlineData(false, false, true, "exit-player")] [InlineData(false, false, false, "close")] - public void Escape_PreservesTargetFlyPlayerWindowPriority( + public void Escape_PreservesTargetPlayerWindowPriority( bool targetMode, bool flyMode, bool playerMode, diff --git a/tests/AcDream.UI.Abstractions.Tests/Input/KeyBindingsRetailTests.cs b/tests/AcDream.UI.Abstractions.Tests/Input/KeyBindingsRetailTests.cs index b51eed80..6e616f76 100644 --- a/tests/AcDream.UI.Abstractions.Tests/Input/KeyBindingsRetailTests.cs +++ b/tests/AcDream.UI.Abstractions.Tests/Input/KeyBindingsRetailTests.cs @@ -243,4 +243,28 @@ public class KeyBindingsRetailTests Assert.NotNull(bound); Assert.Equal(InputAction.ToggleOptionsPanel, bound!.Value.Action); } + + /// + /// The developer free-fly camera must not be reachable from the keyboard. + /// + /// + /// It is a camera retail never had, and a player who lands in it finds the + /// client in a state nothing in the retail UI explains. Its two other ways + /// in — the ImGui View menu and the Debug panel button — went away with + /// AcDream.UI.ImGui at Campaign V, so a binding was the last route. + /// + [Fact] + public void NoDefaultBindingReachesTheFreeFlyCamera() + { + foreach (KeyBindings bindings in new[] + { + KeyBindings.RetailDefaults(), + KeyBindings.AcdreamCurrentDefaults(), + }) + { + Assert.DoesNotContain( + bindings.All, + binding => binding.Action == InputAction.AcdreamToggleFlyMode); + } + } }