fix(input): Phase K live-test fixes pt2 — visible cursor in chase, free-fly discoverable

Two issues from the K-fix1 launch (2026-04-26 user report):

1. Mouse pointer invisible after login.
   Root cause: CameraController.EnterChaseMode invokes
   ModeChanged?.Invoke(IsChaseMode) — passing TRUE when chase
   becomes active. The OnCameraModeChanged handler interpreted
   that bool as `isFlyMode`, so chase entry wrongly triggered
   the Raw cursor branch (raw = invisible pointer). The bool is
   unreliable: ToggleFly passes IsFlyMode, ExitChaseMode passes
   IsFlyMode, but EnterChaseMode passes IsChaseMode. Read the
   controller state directly inside the handler instead — fly
   mode IS the only state that needs Raw, everything else stays
   Normal so the user can click panels / future selectables.

2. No way to enter free-fly mode.
   The DebugPanel already had a "Toggle Free-Fly Mode" button
   wired in K.2, but the user didn't know to look there. Added
   two more discovery paths:

     - Keyboard shortcut: Ctrl+Shift+F → AcdreamToggleFlyMode
       in RetailDefaults() (retail leaves Ctrl+Shift+F unbound;
       Ctrl+F is unused too, so this is conflict-free).

     - View → Camera submenu in the ImGui MainMenuBar with a
       "Enter / Exit Free-Fly Mode" entry whose label flips with
       the active state. Shortcut hint shows "Ctrl+Shift+F".

   The keyboard handler now also cancels _playerModeAutoEntry on
   manual fly toggle (matches the DebugPanel button + new menu
   entry — user's choice wins, the chase camera doesn't snap on
   top of the fly camera mid-inspection).

   Also corrected the View → Debug menu shortcut hint (was "F1",
   actual binding is Ctrl+F1 since K.1c).

Tests still 1220 green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-26 14:30:28 +02:00
parent bc9ee9fdfa
commit 6481169cb9
2 changed files with 50 additions and 15 deletions

View file

@ -3987,19 +3987,23 @@ public sealed class GameWindow : IDisposable
return new System.Numerics.Quaternion(0f, 0f, z, w);
}
private void OnCameraModeChanged(bool isFlyMode)
private void OnCameraModeChanged(bool _modeBool)
{
if (_input is null) return;
var mouse = _input.Mice.FirstOrDefault();
if (mouse is null) return;
// K-fix1 (2026-04-26): cursor visible by default in chase / orbit
// modes — the user needs to click panels, dropdowns, the future
// selection picker, etc. Mouse-look (raw mode) only happens
// transiently while MMB is held (HideCursorForMouseLook /
// RestoreCursorAfterMouseLook). Fly mode still needs raw because
// it's a continuous look-and-fly affordance.
bool needsRawCursor = isFlyMode;
// K-fix2 (2026-04-26): the bool passed to ModeChanged is NOT
// reliably "isFlyMode" — CameraController.EnterChaseMode invokes
// it with IsChaseMode (true), CameraController.ToggleFly invokes
// it with IsFlyMode, and CameraController.ExitChaseMode invokes
// it with IsFlyMode. Reading the controller state directly is
// the only correct gate. Cursor visible by default in chase /
// orbit modes; Raw cursor only in fly mode (continuous
// look-and-fly affordance). Mouse-look (raw mode) when MMB is
// held is handled separately by HideCursorForMouseLook /
// RestoreCursorAfterMouseLook.
bool needsRawCursor = _cameraController?.IsFlyMode == true;
mouse.Cursor.CursorMode = needsRawCursor ? CursorMode.Raw : CursorMode.Normal;
_capturedMouse = needsRawCursor ? mouse : null;
}
@ -4330,10 +4334,27 @@ public sealed class GameWindow : IDisposable
&& ImGuiNET.ImGui.MenuItem("Chat"))
_chatPanel.IsVisible = !_chatPanel.IsVisible;
if (_debugPanel is not null
&& ImGuiNET.ImGui.MenuItem("Debug", "F1"))
&& ImGuiNET.ImGui.MenuItem("Debug", "Ctrl+F1"))
_debugPanel.IsVisible = !_debugPanel.IsVisible;
ImGuiNET.ImGui.EndMenu();
}
// K-fix2 (2026-04-26): Camera submenu — discoverable
// free-fly toggle for users who don't know the
// Ctrl+Shift+F shortcut or the Debug-panel button.
if (ImGuiNET.ImGui.BeginMenu("Camera"))
{
if (_cameraController is not null)
{
string flyLabel = _cameraController.IsFlyMode
? "Exit Free-Fly Mode" : "Enter Free-Fly Mode";
if (ImGuiNET.ImGui.MenuItem(flyLabel, "Ctrl+Shift+F"))
{
_playerModeAutoEntry?.Cancel();
_cameraController.ToggleFly();
}
}
ImGuiNET.ImGui.EndMenu();
}
ImGuiNET.ImGui.EndMainMenuBar();
}
@ -5327,6 +5348,12 @@ public sealed class GameWindow : IDisposable
break;
case AcDream.UI.Abstractions.Input.InputAction.AcdreamToggleFlyMode:
// K-fix2 (2026-04-26): manual fly toggle pre-empts the
// auto-entry trigger so the chase camera doesn't snap on
// top of the fly camera mid-inspection. Mirrors the
// DebugPanel "Toggle Free-Fly Mode" button + Camera menu
// entry.
_playerModeAutoEntry?.Cancel();
_cameraController?.ToggleFly();
break;

View file

@ -325,12 +325,11 @@ public sealed class KeyBindings
b.Add(new(new KeyChord(Key.Down, ModifierMask.Ctrl), InputAction.ScrollDown));
// ── Acdream debug actions: relocated to Ctrl+F* to avoid retail
// conflicts. AcdreamToggleFlyMode + AcdreamTogglePlayerMode have
// NO keyboard binding in retail-default; K.2 adds a DebugPanel
// button for free-fly toggle and player-mode is auto-entered at
// login. AcdreamRmbOrbitHold is intentionally absent — retail
// binds RMB to SelectRight; the chase-camera orbit lives behind
// a debug-mode flag in K.2.
// conflicts. AcdreamTogglePlayerMode has NO keyboard binding
// in retail-default (player-mode is auto-entered at login).
// AcdreamRmbOrbitHold is intentionally absent — retail binds
// RMB to SelectRight; the chase-camera orbit lives behind a
// debug-mode flag in K.2.
b.Add(new(new KeyChord(Key.F1, ModifierMask.Ctrl), InputAction.AcdreamToggleDebugPanel));
b.Add(new(new KeyChord(Key.F2, ModifierMask.Ctrl), InputAction.AcdreamToggleCollisionWires));
b.Add(new(new KeyChord(Key.F3, ModifierMask.Ctrl), InputAction.AcdreamDumpNearby));
@ -339,6 +338,15 @@ public sealed class KeyBindings
b.Add(new(new KeyChord(Key.F9, ModifierMask.Ctrl), InputAction.AcdreamSensitivityUp));
b.Add(new(new KeyChord(Key.F10, ModifierMask.Ctrl), InputAction.AcdreamCycleWeather));
// 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));
// K-fix1 (2026-04-26): RMB-hold camera orbit. Coexists with the
// SelectRight Press binding above — Press fires on click,
// AcdreamRmbOrbitHold fires on hold/release transitions so the