feat(ui): Campaign OP slice OP8 — Configure Keyboard
Ports retail's Configure Keyboard screen (gmKeyboardUI, LayoutDesc 0x21000009) — its own separate full-screen window, not a fifth Options- panel tab. Retires OP3's INERT contract for the Gameplay tab's Configure Keyboard button (0x10000204). DAT reader (src/AcDream.Core/Input/RetailActionMap.cs): reads the ActionMap singleton (DID 0x26000000, empirically the only one — not 0x27000000 as GetDBOType's Turbine-internal tag would suggest) and both MasterInputMap defaults (0x14000000 "gmDefaultMap"/0x14000002 "DefaultMap"), union-merged per (InputMapId, ActionId) — proven order- independent since the two maps' one shared context (0x5) has disjoint action-id sets. Empirically resolved three lane-D unknowns against the live DAT: the six ActionClass values (1=Movement, 2=Camera, 3=UI, 4=Combat, 5=Emote, 7=CharacterSettings — 6 is genuinely absent), that the six unnamed InputMaps are 100% non-bindable (render nothing, not an unlabeled group), and that the enum-to-DID pairing for the two master maps is inconsequential to the merge result. Identity table (src/AcDream.UI.Abstractions/Input/RetailActionIdentityTable.cs): maps DAT (InputMapId, ActionId) pairs to acdream's InputAction where a live consumer exists (~140 of 306 user-bindable rows — Movement/Camera/ Combat map almost completely; UI/Quickslot/Chat partially; only 5 of 87 Emotes and none of 48 CharacterSettings hotkeys, since acdream has no general emote player or hotkey-to-option-toggle dispatcher yet). Every entry cross-verified by label match AND a DAT-default-vs- KeyBindings.RetailDefaults() byte comparison (RetailActionIdentityRoundTripTests), which caught a real off-by-one in the Quickslot 13-18 block before it shipped and found three genuine pre-existing RetailDefaults() gaps (walk-mode's Shift-echoed chord, ten CameraAlternateControls arrow-key alternates, and the Quickslot Ctrl+N use-vs-select ambiguity) — none introduced by this slice, all documented rather than silently patched. KeyboardConfigController: six ActionClass list boxes built from the DAT, merged with live KeyBindings for mapped rows (rebind applies immediately through the same InputDispatcher every other input path uses) and a new sibling RetailUnmappedKeyBindings store for rows with no InputAction yet. Left-click a key button opens real InputDispatcher modal capture; right-click erases that slot. N-way conflict detection scans every other row plus the live KeyBindings table for acdream-only actions (Ctrl+M mute, debug F-keys) as the non-user-bindable refusal analogue, using retail's own byte-verified "Could not overwrite " string (table 0x23000004). OK/Cancel/Defaults/Revert reuse the OptionPage/IOptionRow verb model via a new ActionKeyMapOptionRow. Persistence is keybinds.json only (D4 — no .keymap file interchange). Five register rows: AP-202 (.keymap interchange narrowing), AP-203 (store-only rows with no live consumer), AP-204 (silent auto-reassign instead of retail's confirm dialog; OK/Cancel ported as left-click not right-click-release). Small supporting additions: UiButton.OnRightClick (additive, no existing behavior changed), InputDispatcher.Bindings getter (the screen's single live-truth read seam), RetailScanCodeMap (DIK scan code <-> Silk.NET Key, keyboard + the one mouse-device row). 19 new tests (6 ActionMap reader conformance incl. live-DAT row-count/ label pins, 1 DAT-vs-RetailDefaults round-trip, 12 controller behavior tests against the committed keyboard_config_21000009.json fixture) — full solution suite 13,147 passed / 4 skipped / 0 failed (baseline 13,128/4/0, zero regressions). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
ff5776415b
commit
b4edee970f
23 changed files with 35760 additions and 12 deletions
|
|
@ -89,7 +89,11 @@ public sealed class OptionsPanelController : IRetainedPanelController
|
|||
Action ExitGame,
|
||||
Action UseMouseTurningSettings,
|
||||
Action<string> DisplaySystemMessage,
|
||||
Action? AfterApply = null)
|
||||
Action? AfterApply = null,
|
||||
// Campaign OP slice OP8: opens the Configure Keyboard screen — retires
|
||||
// OP3's INERT contract for this button (0x10000204). Null leaves the
|
||||
// button inert (e.g. a test harness with no keyboard screen wired).
|
||||
Action? OpenConfigureKeyboard = null)
|
||||
{
|
||||
/// <summary>Urgent Assistance's own byte-verified retail failure text.</summary>
|
||||
public string UrgentAssistanceMessage { get; init; } =
|
||||
|
|
@ -190,9 +194,9 @@ public sealed class OptionsPanelController : IRetainedPanelController
|
|||
+ "not found in the built layout — its handler was not wired.");
|
||||
|
||||
BindButton(layout, ExitToCharacterSelectionId, callbacks.RequestExitToCharacterSelection);
|
||||
// ConfigureKeyboardId: INERT this slice — authored, clickable, no
|
||||
// handler. OP8 wires the real Configure Keyboard screen; the campaign
|
||||
// cannot close with this button still inert (plan §4 OP3).
|
||||
// ConfigureKeyboardId: Campaign OP slice OP8 wires the real Configure
|
||||
// Keyboard screen — the OP3 INERT contract is retired.
|
||||
BindButton(layout, ConfigureKeyboardId, callbacks.OpenConfigureKeyboard);
|
||||
// InGameHelpFilesId: INERT — retail's own KeyStone::OpenHelp fails
|
||||
// without the missing plugins\ACHelpPlugin.dll (D5, register row).
|
||||
BindButton(layout, UseMouseTurningSettingsId, callbacks.UseMouseTurningSettings);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue