acdream/tests/AcDream.UI.Abstractions.Tests/Input/KeyBindingsRetailTests.cs
Erik 9d26ecc623 feat(ui): Campaign OP slice OP3 — Options panel shell, open paths, Gameplay tab
Mounts retail's Options panel (LayoutDesc 0x2100002B resolved through host
0x2100006E slot 0x1000018D, gmPanelUI key 10) via the same catalog-import
pattern CharacterController already validates, registered through
RetailPanelUiController so it shares retail's "one active gmPanelUI child"
mutual exclusion with every other sibling panel for free. F11 and the
toolbar's options button (0x1000019B, already authoring panel id 10) both
now open it; the close button fires the same ToggleOptionsPanel action.

OptionPageModel (OptionPage/BoolOptionRow) ports retail's exact
Apply/Reset/Defaults/visibility semantics from
UIOption_Checkbox/PlayerOptionPage — LED clicks apply live immediately,
Apply commits every row unconditionally + flushes the batched blob, Reset
reverts only Changed rows, Defaults restores without committing, and
tab-switch/window-hide revert uncommitted edits. Wired for all four tabs;
this slice registers real rows on none of them (Gameplay authentically has
none — a pure button list). UiTabPanel gains an ActivePageChanged event so
the page model can hook every tab transition, including the initial
default-tab activation.

The seven Gameplay-tab buttons: Exit Game reuses the existing graceful
window-close path; Exit to Character Selection gets retail's confirmation
dialog and byte-verified mid-air refusal but still behaves as Exit Game
(AD-74 — no pre-world character-select flow exists); Configure Keyboard
and In-Game Help Files are inert this slice (AD-76 for Help — the
plugin retail depends on doesn't exist); Urgent Assistance/Report Abuse
short-circuit to their own byte-verified failure text through the
interface-text seam instead of ShellExecute against a dead URL (AD-75);
Use Mouse Turning Settings runs the pure MouseTurningSettingsMacro port,
persisting five new CameraTurningSettings preferences and sending
PlayerOption.UseMouseTurning — TS-74 records that acdream has no
persistent mouse-turning camera mode for the bit to drive yet.

Full Release suite: 12,918 passed / 4 skipped / 0 failed (baseline
12,871/4/0 — only new tests added).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 02:14:40 +02:00

246 lines
9.2 KiB
C#

using System.Linq;
using AcDream.UI.Abstractions.Input;
using Silk.NET.Input;
namespace AcDream.UI.Abstractions.Tests.Input;
/// <summary>
/// K.1c: <see cref="KeyBindings.RetailDefaults"/> now returns the full
/// retail-faithful preset, byte-precise to
/// <c>docs/research/named-retail/retail-default.keymap.txt</c>. These
/// tests pin the canonical mappings the user-visible keyboard layout
/// shifts to (W/X movement instead of W/S, Z/C strafe instead of Z/X,
/// Tab → ToggleChatEntry instead of fly-mode toggle, etc).
/// </summary>
public class KeyBindingsRetailTests
{
[Fact]
public void MovementForward_bound_to_W_and_Up()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.MovementForward).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.W, ModifierMask.None));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Up, ModifierMask.None));
}
[Fact]
public void MovementBackup_bound_to_X_and_Down_NOT_S()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.MovementBackup).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.X, ModifierMask.None));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Down, ModifierMask.None));
// Retail: S is NOT MovementBackup. S → MovementStop.
Assert.DoesNotContain(binds, x => x.Chord.Key == Key.S);
}
[Fact]
public void S_key_is_MovementStop_in_retail()
{
var b = KeyBindings.RetailDefaults();
var stop = b.Find(new KeyChord(Key.S, ModifierMask.None), ActivationType.Press);
Assert.NotNull(stop);
Assert.Equal(InputAction.MovementStop, stop!.Value.Action);
}
[Fact]
public void MovementStrafeLeft_bound_to_Z_and_AltA_and_AltLeft()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.MovementStrafeLeft).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Z, ModifierMask.None));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.A, ModifierMask.Alt));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Left, ModifierMask.Alt));
}
[Fact]
public void MovementStrafeRight_bound_to_C_and_AltD_and_AltRight()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.MovementStrafeRight).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.C, ModifierMask.None));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.D, ModifierMask.Alt));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Right, ModifierMask.Alt));
}
[Fact]
public void Sleeping_bound_to_B()
{
var b = KeyBindings.RetailDefaults();
Assert.Contains(b.ForAction(InputAction.Sleeping),
x => x.Chord == new KeyChord(Key.B, ModifierMask.None));
}
[Fact]
public void MovementWalkMode_uses_Hold_activation()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.MovementWalkMode).ToList();
Assert.NotEmpty(binds);
Assert.All(binds, x => Assert.Equal(ActivationType.Hold, x.Activation));
Assert.Contains(binds, x => x.Chord.Key == Key.ShiftLeft);
}
[Fact]
public void ToggleChatEntry_bound_to_Tab_NOT_fly_toggle()
{
var b = KeyBindings.RetailDefaults();
var hit = b.Find(new KeyChord(Key.Tab, ModifierMask.None), ActivationType.Press);
Assert.NotNull(hit);
Assert.Equal(InputAction.ToggleChatEntry, hit!.Value.Action);
}
[Fact]
public void LOGOUT_is_Shift_Escape()
{
var b = KeyBindings.RetailDefaults();
var hit = b.Find(new KeyChord(Key.Escape, ModifierMask.Shift), ActivationType.Press);
Assert.NotNull(hit);
Assert.Equal(InputAction.LOGOUT, hit!.Value.Action);
}
[Fact]
public void EscapeKey_is_bare_Escape()
{
var b = KeyBindings.RetailDefaults();
var hit = b.Find(new KeyChord(Key.Escape, ModifierMask.None), ActivationType.Press);
Assert.NotNull(hit);
Assert.Equal(InputAction.EscapeKey, hit!.Value.Action);
}
[Fact]
public void SelectRightIsAReleaseCompletedClick()
{
var bindings = KeyBindings.RetailDefaults();
Binding binding = Assert.Single(bindings.ForAction(InputAction.SelectRight));
Assert.Equal(1, binding.Chord.Device);
Assert.Equal(
InputDispatcher.MouseButtonToKey(MouseButton.Right),
binding.Chord.Key);
Assert.Equal(ActivationType.Click, binding.Activation);
}
[Theory]
[InlineData(Key.Delete, InputAction.CombatLowAttack)]
[InlineData(Key.End, InputAction.CombatMediumAttack)]
[InlineData(Key.PageDown, InputAction.CombatHighAttack)]
public void CombatAttackHeightBindings_EmitHoldTransitions(Key key, InputAction action)
{
var b = KeyBindings.RetailDefaults();
var hit = b.Find(new KeyChord(key, ModifierMask.None), ActivationType.Hold);
Assert.Equal(action, hit?.Action);
}
[Fact]
public void QuickSlot_5_bareUsesAndCtrlSelects()
{
var b = KeyBindings.RetailDefaults();
var bare = b.Find(new KeyChord(Key.Number5, ModifierMask.None), ActivationType.Press);
var ctrl = b.Find(new KeyChord(Key.Number5, ModifierMask.Ctrl), ActivationType.Press);
Assert.Equal(InputAction.UseQuickSlot_5, bare?.Action);
Assert.Equal(InputAction.SelectQuickSlot_5, ctrl?.Action);
}
[Fact]
public void UseQuickSlot_18_bound_to_Alt_Number9()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.UseQuickSlot_18).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.Number9, ModifierMask.Alt));
}
[Fact]
public void TogglePluginManager_is_Shift_Ctrl_F1()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.TogglePluginManager).ToList();
Assert.Contains(binds, x =>
x.Chord.Key == Key.F1
&& x.Chord.Modifiers.HasFlag(ModifierMask.Shift)
&& x.Chord.Modifiers.HasFlag(ModifierMask.Ctrl));
}
[Fact]
public void Acdream_debug_actions_relocated_to_Ctrl_F_keys()
{
var b = KeyBindings.RetailDefaults();
Assert.Contains(b.ForAction(InputAction.AcdreamToggleDebugPanel),
x => x.Chord == new KeyChord(Key.F1, ModifierMask.Ctrl));
Assert.Contains(b.ForAction(InputAction.AcdreamToggleCollisionWires),
x => x.Chord == new KeyChord(Key.F2, ModifierMask.Ctrl));
Assert.Contains(b.ForAction(InputAction.AcdreamDumpNearby),
x => x.Chord == new KeyChord(Key.F3, ModifierMask.Ctrl));
}
[Fact]
public void ToggleHelp_is_bare_F1_NOT_acdream_debug()
{
var b = KeyBindings.RetailDefaults();
var hit = b.Find(new KeyChord(Key.F1, ModifierMask.None), ActivationType.Press);
Assert.NotNull(hit);
Assert.Equal(InputAction.ToggleHelp, hit!.Value.Action);
}
[Fact]
public void Total_binding_count_is_at_least_80()
{
var b = KeyBindings.RetailDefaults();
Assert.True(b.All.Count >= 80,
$"Expected at least 80 bindings, got {b.All.Count}");
}
[Fact]
public void Cry_emote_bound_to_U()
{
var b = KeyBindings.RetailDefaults();
Assert.Contains(b.ForAction(InputAction.Cry),
x => x.Chord == new KeyChord(Key.U, ModifierMask.None));
}
[Fact]
public void CombatToggleCombat_bound_to_GraveAccent()
{
var b = KeyBindings.RetailDefaults();
Assert.Contains(b.ForAction(InputAction.CombatToggleCombat),
x => x.Chord == new KeyChord(Key.GraveAccent, ModifierMask.None));
}
[Fact]
public void CameraActivateAlternateMode_bound_to_F2_and_NumpadDivide()
{
var b = KeyBindings.RetailDefaults();
var binds = b.ForAction(InputAction.CameraActivateAlternateMode).ToList();
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.F2, ModifierMask.None));
Assert.Contains(binds, x => x.Chord == new KeyChord(Key.KeypadDivide, ModifierMask.None));
}
[Fact]
public void ScrollUp_bound_to_Ctrl_Up()
{
var b = KeyBindings.RetailDefaults();
Assert.Contains(b.ForAction(InputAction.ScrollUp),
x => x.Chord == new KeyChord(Key.Up, ModifierMask.Ctrl));
}
/// <summary>
/// Campaign OP slice OP3: F11 opens retail's Options panel
/// (<c>docs/research/named-retail/retail-default.keymap.txt:148</c> —
/// <c>ToggleOptionsPanel [ "" [ 0 DIK_F11 ] ]</c>). This row already
/// existed in <see cref="KeyBindings.RetailDefaults"/> before OP3 (the
/// binding was correct; only the routing it fed — the old, unrendered
/// Settings-panel no-op — needed to change). This test pins the ONLY
/// production table stays authoritative going forward (#358's lesson).
/// </summary>
[Fact]
public void ToggleOptionsPanel_bound_to_F11()
{
var b = KeyBindings.RetailDefaults();
var bound = b.Find(new KeyChord(Key.F11, ModifierMask.None), ActivationType.Press);
Assert.NotNull(bound);
Assert.Equal(InputAction.ToggleOptionsPanel, bound!.Value.Action);
}
}