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);
+ }
+ }
}