fix(input): Phase K live-test fixes — default-run, Q-autorun toggle, free cursor, no Holtburg flash
Four issues from K.3 live verification (2026-04-26 user report):
1. Default movement speed should be RUN, not walk.
PlayerMovementController.MovementInput.Run was sourced from
IsActionHeld(MovementRunLock) (Q held). Inverted to
!IsActionHeld(MovementWalkMode) (Shift held = walk; default = run).
Also fixed RetailDefaults() — MovementWalkMode was bound to
(ShiftLeft, ModifierMask.None), but when LShift IS the primary
key the OS keyboard reports CurrentModifiers=Shift and the
chord lookup mismatches. Bind both LShift+Shift and RShift+Shift
to match (the same fix AcdreamCurrentDefaults already had).
2. Q is autorun TOGGLE, not hold-to-run. Added _autoRunActive
field; OnInputAction toggles it on MovementRunLock Press;
MovementInput.Forward now ORs in _autoRunActive so autorun
stays latched until canceled. Pressing Backup / Stop /
StrafeLeft / StrafeRight clears the latch (deliberate movement
wins, retail-faithful). Pressing Forward AGAIN does NOT cancel —
matches retail's stack semantics.
3. Mouse cursor visible by default in chase mode + no Y-axis
steering without an explicit hold input. OnCameraModeChanged
now uses CursorMode.Normal for chase (was Raw — invisible
pointer). MouseMove handler's "neither RMB nor MMB held"
branch dropped its AdjustPitch call — pitch is gated to
deliberate hold inputs only. Fly mode keeps Raw (continuous
look-and-fly affordance).
Restored AcdreamRmbOrbitHold binding in RetailDefaults() —
K.1c silently dropped it when SelectRight took the RMB Press
slot; the Hold-type binding coexists with Press so RMB orbit
still works in addition to (future) SelectRight click.
4. Holtburg flashes briefly at live login. Added
IsLiveModeWaitingForLogin gate (true iff ACDREAM_LIVE=1 AND
chase camera has not yet been entered) that:
* suppresses StreamingController.Tick in OnUpdate so no
landblocks load around the hardcoded startup center
0xA9B4 (Holtburg);
* skips terrain + entity rendering in OnRender via a
SkipWorldGeometry label after the sky pass.
Sky still draws so the user sees a live, time-of-day-correct
sky during the connection / character-list / EnterWorld
handshake. Latches off once chase mode has been entered, so
later fly-mode toggles render the world normally.
Tests still 1220 green.
Also commits .gitignore tmp/ rule (left over from K.3
session) — gitignored per-session scratch (commit message
drafts, ad-hoc temp files).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
f42c164b90
commit
bc9ee9fdfa
3 changed files with 141 additions and 16 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -35,3 +35,6 @@ refs/
|
||||||
# Python tooling (under tools/) — bytecode caches
|
# Python tooling (under tools/) — bytecode caches
|
||||||
__pycache__/
|
__pycache__/
|
||||||
*.pyc
|
*.pyc
|
||||||
|
|
||||||
|
# Per-session scratch (Claude commit message drafts, ad-hoc temp files)
|
||||||
|
tmp/
|
||||||
|
|
|
||||||
|
|
@ -403,6 +403,13 @@ public sealed class GameWindow : IDisposable
|
||||||
// the orbited position (no snap back).
|
// the orbited position (no snap back).
|
||||||
private bool _rmbHeld;
|
private bool _rmbHeld;
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): autorun is a TOGGLE — Press Q to start
|
||||||
|
// forward-running, press Q again (or any movement-cancel key like
|
||||||
|
// X / S / Backward / Forward) to stop. Mirrors retail's
|
||||||
|
// AutoRun action. While true, MovementInput.Forward is forced
|
||||||
|
// true regardless of W's state.
|
||||||
|
private bool _autoRunActive;
|
||||||
|
|
||||||
// Phase K.2 — auto-enter player mode after a successful login. Armed
|
// Phase K.2 — auto-enter player mode after a successful login. Armed
|
||||||
// by the EnterWorld branch in BeginLiveSessionAsync; ticked from
|
// by the EnterWorld branch in BeginLiveSessionAsync; ticked from
|
||||||
// OnUpdate; disarmed if the user manually enters fly mode (or any
|
// OnUpdate; disarmed if the user manually enters fly mode (or any
|
||||||
|
|
@ -459,6 +466,34 @@ public sealed class GameWindow : IDisposable
|
||||||
private int _liveCenterY;
|
private int _liveCenterY;
|
||||||
private uint _liveEntityIdCounter = 1_000_000u; // well above any dat-hydrated id
|
private uint _liveEntityIdCounter = 1_000_000u; // well above any dat-hydrated id
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): cached at startup so per-frame branches are
|
||||||
|
// single-flag reads instead of env-var lookups. True iff
|
||||||
|
// ACDREAM_LIVE=1 was set when the window came up.
|
||||||
|
private static readonly bool LiveModeEnabled =
|
||||||
|
Environment.GetEnvironmentVariable("ACDREAM_LIVE") == "1";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// K-fix1 (2026-04-26): true iff live mode is configured AND we have
|
||||||
|
/// NOT yet handed control to the chase camera. Gates initial
|
||||||
|
/// streaming + scene rendering so the user never sees Holtburg flash
|
||||||
|
/// before login completes — the screen stays at the sky/fog clear
|
||||||
|
/// color until the player entity has spawned + the auto-entry guard
|
||||||
|
/// has triggered <see cref="EnterPlayerModeFromAutoEntry"/>.
|
||||||
|
/// Offline (LiveModeEnabled false) returns false → unchanged
|
||||||
|
/// orbit-camera Holtburg view stays the foreground. Once chase mode
|
||||||
|
/// is active the property latches false for the rest of the
|
||||||
|
/// session, even if the user later toggles into fly mode (we don't
|
||||||
|
/// want to re-blank the world after they've seen it).
|
||||||
|
/// </summary>
|
||||||
|
private bool IsLiveModeWaitingForLogin =>
|
||||||
|
LiveModeEnabled
|
||||||
|
&& !_chaseModeEverEntered;
|
||||||
|
|
||||||
|
// Latches true the first time chase mode becomes active. Used by
|
||||||
|
// IsLiveModeWaitingForLogin to suppress the pre-login render gate
|
||||||
|
// for subsequent fly-mode toggles.
|
||||||
|
private bool _chaseModeEverEntered;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase 6.6/6.7: server-guid → local WorldEntity lookup so
|
/// Phase 6.6/6.7: server-guid → local WorldEntity lookup so
|
||||||
/// UpdateMotion and UpdatePosition handlers can find the entity the
|
/// UpdateMotion and UpdatePosition handlers can find the entity the
|
||||||
|
|
@ -646,13 +681,11 @@ public sealed class GameWindow : IDisposable
|
||||||
_chaseCamera.YawOffset -= dx * 0.004f * sens;
|
_chaseCamera.YawOffset -= dx * 0.004f * sens;
|
||||||
_chaseCamera.AdjustPitch(dy * 0.003f * sens);
|
_chaseCamera.AdjustPitch(dy * 0.003f * sens);
|
||||||
}
|
}
|
||||||
else
|
// K-fix1 (2026-04-26): no default-pitch path. With
|
||||||
{
|
// neither MMB nor RMB held, mouse moves the cursor
|
||||||
// Without RMB or MMB held, mouse only pitches the
|
// freely so the user can interact with panels +
|
||||||
// chase camera (Y-axis). Mouse X is dropped —
|
// (eventually) click selectables in the world. Pitch
|
||||||
// character turning is keyboard-only.
|
// is gated on a deliberate hold input.
|
||||||
_chaseCamera.AdjustPitch(dy * 0.003f * sens);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (_cameraController.IsFlyMode)
|
else if (_cameraController.IsFlyMode)
|
||||||
{
|
{
|
||||||
|
|
@ -3661,7 +3694,14 @@ public sealed class GameWindow : IDisposable
|
||||||
// login to land before any landblock was loaded; AppendLiveEntity
|
// login to land before any landblock was loaded; AppendLiveEntity
|
||||||
// is a no-op for unloaded landblocks, so all 40+ NPCs/weenies were
|
// is a no-op for unloaded landblocks, so all 40+ NPCs/weenies were
|
||||||
// silently dropped on the first frame and never rendered.
|
// silently dropped on the first frame and never rendered.
|
||||||
if (_streamingController is not null && _cameraController is not null)
|
//
|
||||||
|
// K-fix1 (2026-04-26): skip streaming entirely while live mode is
|
||||||
|
// configured but the chase camera hasn't engaged yet — otherwise
|
||||||
|
// the orbit camera at startup centers on the hardcoded
|
||||||
|
// 0xA9B4 (Holtburg) and Holtburg landblocks render briefly
|
||||||
|
// until the player spawn arrives + auto-entry switches to chase.
|
||||||
|
if (_streamingController is not null && _cameraController is not null
|
||||||
|
&& !IsLiveModeWaitingForLogin)
|
||||||
{
|
{
|
||||||
int observerCx = _liveCenterX;
|
int observerCx = _liveCenterX;
|
||||||
int observerCy = _liveCenterY;
|
int observerCy = _liveCenterY;
|
||||||
|
|
@ -3789,15 +3829,27 @@ public sealed class GameWindow : IDisposable
|
||||||
if (_inputDispatcher is null) return;
|
if (_inputDispatcher is null) return;
|
||||||
_playerMouseDeltaX = 0f; // defensive: ensure no leakage even if some path writes it
|
_playerMouseDeltaX = 0f; // defensive: ensure no leakage even if some path writes it
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): retail-faithful movement semantics.
|
||||||
|
// * Default speed = RUN. Forward / backward / strafe all run
|
||||||
|
// by default; holding Shift (MovementWalkMode) drops to
|
||||||
|
// walk speed.
|
||||||
|
// * Q = AUTORUN TOGGLE: pressing Q latches forward-running
|
||||||
|
// until Q is pressed again. Handled in OnInputAction; here
|
||||||
|
// we just OR _autoRunActive into the Forward flag.
|
||||||
|
// * Mouse never drives character yaw (K.1b regression-prevention).
|
||||||
|
bool walking = _inputDispatcher.IsActionHeld(
|
||||||
|
AcDream.UI.Abstractions.Input.InputAction.MovementWalkMode);
|
||||||
|
bool wHeld = _inputDispatcher.IsActionHeld(
|
||||||
|
AcDream.UI.Abstractions.Input.InputAction.MovementForward);
|
||||||
var input = new AcDream.App.Input.MovementInput(
|
var input = new AcDream.App.Input.MovementInput(
|
||||||
Forward: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementForward),
|
Forward: wHeld || _autoRunActive,
|
||||||
Backward: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementBackup),
|
Backward: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementBackup),
|
||||||
StrafeLeft: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementStrafeLeft),
|
StrafeLeft: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementStrafeLeft),
|
||||||
StrafeRight: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementStrafeRight),
|
StrafeRight: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementStrafeRight),
|
||||||
TurnLeft: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementTurnLeft),
|
TurnLeft: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementTurnLeft),
|
||||||
TurnRight: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementTurnRight),
|
TurnRight: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementTurnRight),
|
||||||
Run: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementRunLock),
|
Run: !walking, // default = run; Shift held = walk
|
||||||
MouseDeltaX: 0f, // K.1b: mouse never drives character yaw
|
MouseDeltaX: 0f,
|
||||||
Jump: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementJump));
|
Jump: _inputDispatcher.IsActionHeld(AcDream.UI.Abstractions.Input.InputAction.MovementJump));
|
||||||
|
|
||||||
var result = _playerController.Update((float)dt, input);
|
var result = _playerController.Update((float)dt, input);
|
||||||
|
|
@ -3941,9 +3993,13 @@ public sealed class GameWindow : IDisposable
|
||||||
var mouse = _input.Mice.FirstOrDefault();
|
var mouse = _input.Mice.FirstOrDefault();
|
||||||
if (mouse is null) return;
|
if (mouse is null) return;
|
||||||
|
|
||||||
// Raw cursor mode for both fly AND chase (player) mode — both need
|
// K-fix1 (2026-04-26): cursor visible by default in chase / orbit
|
||||||
// mouse deltas for look/turn. Only orbit mode uses normal cursor.
|
// modes — the user needs to click panels, dropdowns, the future
|
||||||
bool needsRawCursor = isFlyMode || _playerMode;
|
// 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;
|
||||||
mouse.Cursor.CursorMode = needsRawCursor ? CursorMode.Raw : CursorMode.Normal;
|
mouse.Cursor.CursorMode = needsRawCursor ? CursorMode.Raw : CursorMode.Normal;
|
||||||
_capturedMouse = needsRawCursor ? mouse : null;
|
_capturedMouse = needsRawCursor ? mouse : null;
|
||||||
}
|
}
|
||||||
|
|
@ -4074,6 +4130,18 @@ public sealed class GameWindow : IDisposable
|
||||||
_activeDayGroup, kf);
|
_activeDayGroup, kf);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): suppress terrain + entity rendering
|
||||||
|
// while live mode is configured but the chase camera hasn't
|
||||||
|
// engaged yet — pairs with the streaming-Tick gate in
|
||||||
|
// OnUpdate so absolutely nothing of the world (Holtburg or
|
||||||
|
// otherwise) renders pre-login. The sky still draws above so
|
||||||
|
// the user sees a live, time-of-day-correct sky during the
|
||||||
|
// brief connection + character-list + EnterWorld handshake.
|
||||||
|
if (IsLiveModeWaitingForLogin)
|
||||||
|
{
|
||||||
|
goto SkipWorldGeometry;
|
||||||
|
}
|
||||||
|
|
||||||
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
_terrain?.Draw(camera, frustum, neverCullLandblockId: playerLb);
|
||||||
|
|
||||||
// Conditional depth clear: when camera is inside a building, clear
|
// Conditional depth clear: when camera is inside a building, clear
|
||||||
|
|
@ -4214,6 +4282,14 @@ public sealed class GameWindow : IDisposable
|
||||||
_lastNearestObjDist = bestDist < 0f ? 0f : bestDist;
|
_lastNearestObjDist = bestDist < 0f ? 0f : bestDist;
|
||||||
_lastNearestObjLabel = bestLabel;
|
_lastNearestObjLabel = bestLabel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): jump target for IsLiveModeWaitingForLogin —
|
||||||
|
// skips the world geometry pass before login. ImGui (chat,
|
||||||
|
// debug, settings panels) and the menu bar still render
|
||||||
|
// below. Sky has already drawn before this label so the
|
||||||
|
// pre-login screen shows a live, correctly-tinted sky and
|
||||||
|
// nothing else.
|
||||||
|
SkipWorldGeometry: ;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Phase D.2a — end ImGui frame. Runs AFTER all scene + debug draws
|
// Phase D.2a — end ImGui frame. Runs AFTER all scene + debug draws
|
||||||
|
|
@ -5193,6 +5269,29 @@ public sealed class GameWindow : IDisposable
|
||||||
// re-fire.
|
// re-fire.
|
||||||
if (activation != AcDream.UI.Abstractions.Input.ActivationType.Press) return;
|
if (activation != AcDream.UI.Abstractions.Input.ActivationType.Press) return;
|
||||||
|
|
||||||
|
// K-fix1 (2026-04-26): Q is autorun TOGGLE, not hold-to-run. Press
|
||||||
|
// Q to start, press Q again to stop. Pressing Backup / Stop /
|
||||||
|
// StrafeLeft / StrafeRight while autorun is active also cancels it
|
||||||
|
// — retail-faithful: any deliberate movement input wins. (Pressing
|
||||||
|
// Forward AGAIN does NOT cancel — retail's autorun stays active
|
||||||
|
// even when you press W; the two stack.)
|
||||||
|
if (action == AcDream.UI.Abstractions.Input.InputAction.MovementRunLock)
|
||||||
|
{
|
||||||
|
_autoRunActive = !_autoRunActive;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (_autoRunActive
|
||||||
|
&& (action == AcDream.UI.Abstractions.Input.InputAction.MovementBackup
|
||||||
|
|| action == AcDream.UI.Abstractions.Input.InputAction.MovementStop
|
||||||
|
|| action == AcDream.UI.Abstractions.Input.InputAction.MovementStrafeLeft
|
||||||
|
|| action == AcDream.UI.Abstractions.Input.InputAction.MovementStrafeRight))
|
||||||
|
{
|
||||||
|
_autoRunActive = false;
|
||||||
|
// Fall through — these actions still need their normal handling
|
||||||
|
// (e.g. Stop is currently a no-op in the switch, but keeping the
|
||||||
|
// fall-through means future logic fires).
|
||||||
|
}
|
||||||
|
|
||||||
switch (action)
|
switch (action)
|
||||||
{
|
{
|
||||||
case AcDream.UI.Abstractions.Input.InputAction.AcdreamToggleDebugPanel:
|
case AcDream.UI.Abstractions.Input.InputAction.AcdreamToggleDebugPanel:
|
||||||
|
|
@ -5391,6 +5490,12 @@ public sealed class GameWindow : IDisposable
|
||||||
// into a future code path that re-enables mouse-yaw.
|
// into a future code path that re-enables mouse-yaw.
|
||||||
_playerMouseDeltaX = 0f;
|
_playerMouseDeltaX = 0f;
|
||||||
_cameraController?.EnterChaseMode(_chaseCamera);
|
_cameraController?.EnterChaseMode(_chaseCamera);
|
||||||
|
// K-fix1 (2026-04-26): latch the "we have entered chase at least
|
||||||
|
// once" flag so the live-mode pre-login render gate stops
|
||||||
|
// suppressing the scene. From here on, the orbit camera (if the
|
||||||
|
// user ever returns to it via Escape) shows whatever's loaded —
|
||||||
|
// we don't re-blank the world.
|
||||||
|
_chaseModeEverEntered = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -152,8 +152,14 @@ public sealed class KeyBindings
|
||||||
b.Add(new(new KeyChord(Key.D, ModifierMask.Alt), InputAction.MovementStrafeRight));
|
b.Add(new(new KeyChord(Key.D, ModifierMask.Alt), InputAction.MovementStrafeRight));
|
||||||
b.Add(new(new KeyChord(Key.Right, ModifierMask.Alt), InputAction.MovementStrafeRight));
|
b.Add(new(new KeyChord(Key.Right, ModifierMask.Alt), InputAction.MovementStrafeRight));
|
||||||
// Walk-mode modifier — Hold so a subscriber can latch state on
|
// Walk-mode modifier — Hold so a subscriber can latch state on
|
||||||
// press and unlatch on release.
|
// press and unlatch on release. K-fix1 (2026-04-26): the chord
|
||||||
b.Add(new(new KeyChord(Key.ShiftLeft, ModifierMask.None), InputAction.MovementWalkMode, ActivationType.Hold));
|
// modifier MUST be Shift, not None — when LShift/RShift is the
|
||||||
|
// primary key the OS keyboard reports CurrentModifiers=Shift
|
||||||
|
// alongside the key-down. Bind both left + right shift to match.
|
||||||
|
// This is the same pattern AcdreamCurrentDefaults uses for its
|
||||||
|
// Shift→RunLock binding (see lines 98-99 above).
|
||||||
|
b.Add(new(new KeyChord(Key.ShiftLeft, ModifierMask.Shift), InputAction.MovementWalkMode, ActivationType.Hold));
|
||||||
|
b.Add(new(new KeyChord(Key.ShiftRight, ModifierMask.Shift), InputAction.MovementWalkMode, ActivationType.Hold));
|
||||||
b.Add(new(new KeyChord(Key.Q, ModifierMask.None), InputAction.MovementRunLock));
|
b.Add(new(new KeyChord(Key.Q, ModifierMask.None), InputAction.MovementRunLock));
|
||||||
b.Add(new(new KeyChord(Key.S, ModifierMask.None), InputAction.MovementStop));
|
b.Add(new(new KeyChord(Key.S, ModifierMask.None), InputAction.MovementStop));
|
||||||
b.Add(new(new KeyChord(Key.Y, ModifierMask.None), InputAction.Ready));
|
b.Add(new(new KeyChord(Key.Y, ModifierMask.None), InputAction.Ready));
|
||||||
|
|
@ -333,6 +339,17 @@ public sealed class KeyBindings
|
||||||
b.Add(new(new KeyChord(Key.F9, ModifierMask.Ctrl), InputAction.AcdreamSensitivityUp));
|
b.Add(new(new KeyChord(Key.F9, ModifierMask.Ctrl), InputAction.AcdreamSensitivityUp));
|
||||||
b.Add(new(new KeyChord(Key.F10, ModifierMask.Ctrl), InputAction.AcdreamCycleWeather));
|
b.Add(new(new KeyChord(Key.F10, ModifierMask.Ctrl), InputAction.AcdreamCycleWeather));
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// chase camera can free-orbit while the user drags the mouse.
|
||||||
|
// Without this, RMB-orbit silently broke when K.1c flipped the
|
||||||
|
// default keymap from AcdreamCurrentDefaults to RetailDefaults.
|
||||||
|
b.Add(new(
|
||||||
|
new KeyChord(InputDispatcher.MouseButtonToKey(MouseButton.Right), ModifierMask.None, Device: 1),
|
||||||
|
InputAction.AcdreamRmbOrbitHold,
|
||||||
|
ActivationType.Hold));
|
||||||
|
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue