Merge op8-keyboard: Campaign OP slice OP8 — Configure Keyboard

Brings b4edee97 (slice), b1968ce9 (M1/M2/M3 rework), f1d50207 (round-2
residuals). Review chain: REJECT -> rework -> REOPEN-narrow -> coordinator
third round; findings docs 2026-08-11-op8-review.md / -op8-rereview.md.
The merge lands OP8's six ListBoxes on top of 057d8cd7's #372 viewport
fix, which auto-heals the blank-pages hazard the re-review flagged — the
OP8 connected gate was contracted to run post-merge for exactly this.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-11 12:37:56 +02:00
commit 1c5cd969b4
23 changed files with 36178 additions and 12 deletions

File diff suppressed because one or more lines are too long

View file

@ -424,5 +424,5 @@ before anything builds on them.
| OP5 | CODE-COMPLETE, gate READY | `e71e5a96` (AP-195 retired) → fixes `6d0b0f92` → residuals `67b0815c` | combined APPROVE-WITH-FIXES (`2026-08-11-op5-review.md`) → re-check CLOSED (`2026-08-11-op5-recheck.md`) → coordinator drag residuals landed | connected gate OWED (script §OP5) |
| OP6 | CODE-COMPLETE, gate READY | `f5ac1742` (REJECTED) → rework `472525b9` → doc residuals (coordinator) | REJECT (`2026-08-11-op6-review.md`) → re-review CLOSED, all six caption sites byte-decoded (`2026-08-11-op6-rereview.md`) | connected gate OWED (script §OP6) |
| OP7 | CLOSED | `09cb548a` → fixes in `7b60e71b` (shared commit, see its message) | combined-lens APPROVE-WITH-FIXES (`2026-08-11-op7-review.md`); all nine findings closed | live bot-vs-ACE gate PASSED 2026-08-11 (coordinator; evidence in script §OP7) |
| OP8 | — | | | |
| OP8 | CODE-COMPLETE, gate READY | (this branch's commit) | not yet reviewed | connected gate OWED (script §OP8) |
| OP9 | — | | | |

View file

@ -1,8 +1,7 @@
# Campaign OP connected-gate test script
**Status:** OP3, OP4, OP5, OP6, and OP7 sections. Later slices (OP8) append
their own sections here as they land; the campaign's OP9 closeout gate is
this document complete plus every slice code-complete.
**Status:** OP3, OP4, OP5, OP6, OP7, and OP8 sections. The campaign's OP9
closeout gate is this document complete plus every slice code-complete.
This document is the script the user runs against the live connected
client (`ACDREAM_LIVE=1` against the local ACE server) to accept each
@ -851,3 +850,168 @@ Three live runs against local ACE (`127.0.0.1:9000`, `+Acdream`,
unmapped `[weenie-error] code=0x051D`, and the K4 resource envelope's
`handle-count` violation (the `k4-linux-30-session` Linux profile
evaluated on Windows).
---
## OP8 — Configure Keyboard
**Known, tracked behaviors — do NOT file as defects (read before testing):**
- **Shared combat keys prompt a false conflict (ISSUES #373).** Retail
legitimately shares Insert/Delete/End/PageUp/PageDown across the three
combat contexts (Melee/Missile/Magic) via the DAT's `ConflictingMaps`
table, which acdream does not read yet. Rebinding any of those keys — or
any chord one combat context already holds — will show a conflict
confirmation retail would not. DECLINE the prompt and move on; the
binding you declined stays untouched. Report only if declining CHANGES a
binding anyway.
- **Camera Alternate Controls rows are display/store-only (AP-203).** They
show retail's authored arrow-key defaults and accept edits, but do not
drive the camera — only the primary Camera scheme is live. Verify they
RENDER (arrow keys visible); do not expect camera behavior from them.
The screen is retail's own separate full-screen window (`gmKeyboardUI`,
LayoutDesc `0x21000009`), NOT a fifth tab of the Options panel. It opens
from the Gameplay tab's Configure Keyboard button — **the OP3 INERT
contract for that button is retired as of this slice**; OP3's own script
line calling it INERT no longer applies.
### Opening the screen
1. **Open the Options panel (F11), Gameplay tab, click "Configure
Keyboard."** A separate full-screen window opens (not layered inside
the Options panel) showing six tabs across the top: Movement, Camera,
Combat, UI, CharacterSettings, Emote — Movement is the default/first
tab. Each tab lists grouped rows: a bold-ish header naming the
sub-category (e.g. "Movement Commands"), then one row per action —
an action label on the left, up to three key-binding buttons to its
right (retail's "Mapping 1/2/3" columns) showing the currently bound
key(s) (e.g. "W", "Up").
2. **Not every group has a visible header/rows.** Debug/dialog/system
InputMaps (DialogBoxes, DebugConsole, ProfilerUI, UIDebugger,
DebugCommands, and six further unnamed contexts) are 100% non-user-
bindable in the shipped DAT and correctly show NOTHING — this is not
a bug, it is retail's own shipped data (byte-verified against the
live DAT, see `RetailActionMap.cs`'s class doc).
3. **The bottom-row buttons:** Load File…, a current-keymap-name label,
Save As…, Defaults, Revert, OK, Cancel. Load File / Save As are
**INERT** (D4 — no `.keymap` file interchange, AP-202) — clicking
them does nothing; this is correct, contracted behavior.
### Rebind a movement key live
4. **On the Movement tab, find "Move Forward"** (should show two bound
keys, "W" and "Up"). Click the SECOND key button (currently "Up").
The button should visually indicate it is listening for input
(retail's own capture-prompt text is not wired to a tooltip in this
port — a simple pressed/active state is enough to confirm capture
started).
5. **Press a different key**, e.g. `U`. The button should immediately
update to show "U".
6. **Move your character forward using W and U** (both should now work
— this is the live-effect proof: the rebind reached the SAME
`InputDispatcher`/`KeyBindings` every other input path uses, not a
screen-local shadow copy). The original "Up" arrow should no longer
move the character forward.
7. **Right-click the "U" key button** you just set. It should clear
back to blank/empty — right-click erases just that one slot
(`EraseBinding`), distinct from a hypothetical "Clear all" (there is
no Clear-all button on this screen — the shipped 2013 row template
authors none; a real, DAT-verified fact, not a limitation of this
port).
### Conflict on a taken chord
8. **Still on Movement, click "Move Backward"'s first key button**
(currently "X"), then press **W** — the SAME key you just confirmed
is bound to "Move Forward."
9. **Expect**: a confirmation dialog opens (retail's own
`OpenOverwriteBindingDialog`, ported through the same
`RetailDialogFactory` confirm mechanism the game's other Yes/No
prompts already use) naming "Move Forward" as the row that currently
holds the key and asking whether to reassign it to "Move Backward."
**Click Yes**: "Move Backward" takes W, and "Move Forward" loses its
W slot (down to just "U" from step 5); both rows' key-button labels
update to reflect the swap. Repeat steps 8-9 once more but **click
No** this time: neither row should change at all — the capture is
simply abandoned, exactly like Escape.
### Non-bindable refusal
10. **Click any key button, then press Ctrl+M** (acdream's own
debug-only mute toggle — retail has no equivalent, so no DAT row on
this screen owns it). **Expect**: the rebind is REFUSED — the key
button you clicked keeps its previous value, and a system message
appears reading "Could not overwrite " (retail's own byte-verified
`ID_KeyMapCantOverwriteReadOnlyKeymap_Label` string, table
`0x23000004` — it may read as an odd sentence fragment on its own;
that is the literal stored string, not a truncation bug in this
port). Confirm Ctrl+M still mutes/unmutes audio afterward — the
acdream-only binding was NOT touched.
### Erase, then Reset to Defaults
11. **Erase a couple more bindings** via right-click (any tab).
12. **Click Defaults.** Every row on EVERY tab should snap back to its
retail-default key(s) LIVE — including the ones you just erased,
the "Move Forward"/"Move Backward" pair from steps 5-9 (both back
to their original W/Up and X/Down), and the Ctrl+M row from step 10
is untouched (it has no DAT row, so Defaults cannot and does not
touch it). The OK/Cancel/Revert buttons should now read as
"changed" (retail never gates Defaults itself, but the page as a
whole is dirty after it runs) — confirm by tabbing away and back:
edits should still be there (Defaults applies live without
committing, exactly like a manual edit would).
13. **Click Revert** (not Cancel) with SOME rows still showing your
Defaults-restored values. Expect every changed row to revert to
whatever was bound when you last clicked OK (or, if you haven't
clicked OK yet this session, back to what was loaded from
`keybinds.json` at startup) — Revert and Cancel run the identical
verb (`RestoreSavedValues`); Revert just doesn't also close the
window.
### Persistence across relaunch
14. **Rebind one distinctive key** (e.g. change "Jump" from Space to
some other free key) and **click OK.** The window closes.
15. **Close acdream gracefully and relaunch it** (see CLAUDE.md's
logout-before-reconnect discipline — wait for the graceful-close
session-clear window before reconnecting). Open Configure Keyboard
again: the rebind from step 14 should still be there — proof it
persisted to `keybinds.json` on disk, not just the in-memory
dispatcher.
16. **Open a plain text editor on `%LOCALAPPDATA%\acdream\keybinds.json`**
(or the platform-portable equivalent) and confirm the rebound
action shows the new key. A sibling `keybinds-unmapped.json` should
also exist if you rebound anything on the CharacterSettings or
Emote tabs during this session (AP-203's store-only rows — e.g. try
rebinding one "Bow Deep"-style Emote row and confirm it shows up in
THAT file, not `keybinds.json`).
### Cancel discards uncommitted edits
17. **Rebind another key WITHOUT clicking OK**, then click **Cancel.**
The window closes; reopen it — the rebind from this step should be
GONE (reverted to the last-committed/loaded state), matching step
13's Revert behavior plus the window closing.
### What to report
- Any row that shows a DIFFERENT key than what `keybinds.json` /
`RetailDefaults()` says it should (a sign the DAT-vs-InputAction
identity table mis-mapped a row — see
`RetailActionIdentityRoundTripTests` for the automated half of this
check).
- Any tab/header that renders EMPTY where it should show rows, or vice
versa (the page-scoped element-lookup trap this slice's controller
explicitly guards against — a regression here would mean one page's
rows leaked into another, or the six reused element ids resolved to
the wrong page's instance).
- Whether the confirm dialog's wording (step 9) or the refusal
message's odd phrasing (step 10, the literal DAT string) reads
awkwardly enough in practice to warrant a follow-up polish pass.
- Any row whose Emote/CharacterSettings binding visibly DOES something
in-game despite AP-203 saying it shouldn't (would mean acdream grew a
consumer for it since this table was written, and the identity table
should be updated to route it live).