Support shared hotbar keys with native protected dispatch
Investigate prk/spiritech-known-issues#230: binding several hotbar actions to one key is rejected by the controls UI. Discuss a clean player-facing solution before implementation. Work isolated from other agents in /home/mathias/Projects/engines-shared-hotbar-bindings, branch fix/shared-hotbar-bindings, based on origin/develop. Implementation authorized after packet investigation. Acceptance: offer Share / Replace / Cancel for collisions between executable hotbar slot actions, preserve other conflict/reserved-key behavior, show shared slot membership, persist sharing using existing binding storage, execute explicitly grouped native slot actions in binding order once per fresh physical press with existing cooldown validation. Capture group commands before execution/observers; keep unrelated protected actions exclusive and Lua callbacks without authority. No server, wire, rotation, timer or retry changes. Validate regression tests red-green, client build and relevant suites. Implemented in isolated worktree; committed as 785557b09 and pushed to origin/fix/shared-hotbar-bindings. PR #2946: https://git.project-rk.com/prk/engines/pulls/2946 (base develop). Implementation: hotbar-only Share/Replace/Cancel using existing confirmation layout and binding storage; shared-key asterisks during Quick Binding and row hints listing peer slots; HotbarInputBindings owns executable slot group while row/bar controls remain exclusive; InputActionRouter captures the selected native owner's bound group before executing commands or observers; ShortcutBarsController captures immutable shortcut/macro values. Existing per-action cooldown/availability checks unchanged. Updated native-input docs. Verified: red-green UI sharing, native grouped dispatch and frozen-shortcut regressions; mutation checks fail when group commands execute before all captures or stale-share draft guard is removed. Restored code passes full client suite (4416 passed,188 environment-dependent skips), graphics suite (719 passed,1 skip), install-backed relevant UI/input tests (99 passed,0 skipped), warnings-as-errors client build (0 warnings/errors), git diff --check. Inspected rendered Share/Replace/Cancel dialog at /tmp/prk-shared-hotbar-final-results. User tested the implemented build and verified that shared-key behavior matches live AO. Windows execution was not independently verified by the agent. Stock-client packet captures remain outside repo. Source findings: KeyBindingSet already stores multiple actions per key. ControlsOptionsCategory.Bind only offers Replace/Cancel for collisions. CharacterInterface registers hotbar actions through OnProtected; InputActionRouter.Dispatch deliberately stops after the first protected registration. Thus a dialog-only fix cannot enable multiple hotbar actions. ShortcutBarsController.Use dispatches individual shortcut types through existing native controllers; PerformPerk checks grants and known locks then sends UsePerk, without a success result or action-group scheduler. Preserve native physical-input authority and Lua isolation. Proposed discussion: explicit shared-key membership using existing bindings, optionally exposed as an editable key group; no timed execution or automatic retries. User clarifies intent: repeated shared-key presses rely on server spell queuing, not a client rotation. Source inspection of origin/develop does NOT establish that server selection contract. CharacterActionMessageHandler applies separate player-wide 300ms throttles to UsePerk and CastNano before validation, so an ineligible first request consumes its action-type window. Nano requests while casting/non-idle are dropped, with recharge gates in NanoCastingService, not retained for later. QueuePerkAction rejects duplicate pending template IDs but appends distinct accepted perks as independent PerkCharge timers; Playfield heartbeat decrements all eligible timers and executes all expired entries. IsPerking is set only around callback execution, not throughout charge. BaseSession inbound pump awaits messages serially. Therefore first request reaching each throttle wins admission, not first usable action; separate nano/perk throttles do not imply one combined execution per key press. Live user test with stock AO and multiple perks on one key succeeded. Local playfield capture /tmp/prk-shared-key-capture-GfsGMG/playfield.pcap, decoded by adjacent analyze.py: client->server TCP reassembled without sequence gaps, 89 CharacterAction/UsePerk requests, five distinct action IDs, stable order 10870,10871,10873,10630,10880. Requests fan out repeatedly; not a client-side one-request-per-press rotation. Earlier actions stop appearing progressively: relative to first UsePerk, final requests at 1.160s,2.124s,2.470s,3.912s,5.369s respectively. Compatible with cooldown filtering but precise cause not established. Capture has no physical-key timestamps, so individual keypress boundaries are not proven; identical timestamps represent TCP coalescing, not execution simultaneity. Server->client stream begins mid-compression and raw inflate fails due to missing history; cannot correlate ShowPerkBar/LockPerk or confirm execution timing from this capture. Source throttle behavior must not be presented as demonstrated live starvation. Second login-inclusive capture /tmp/prk-shared-key-login-Ps902i/playfield.pcap resolves compression limitation: both TCP directions reassembled without gaps; server zlib stream decoded from negotiation, 182431 uncompressed bytes, all server messages parsed with zero skipped/tail bytes. 83 outgoing UsePerk attempts for same five actions. Server AddPerkAction maps actions to item IDs. Five TemplateAction.UsePerk executions matched those grants and five LockPerk replies confirmed cooldowns. Relative to capture payload start: executions/cooldowns A(10870)=10.612s/30s, B(10871)=13.417s/30s, D(10630)=15.001s/25s, C(10873)=15.476s/30s, E(10880)=16.917s execution,16.919s/50s cooldown. Stock continues attempting charging actions, then stops shortly after locks (one B request still sent 35ms after lock observation, consistent with in-flight/client processing). Shared attempts retain stable ordering; execution order is not necessarily that ordering. ShowPerkBar messages at 13.961s and14.379s are418ms apart, so cannot claim globally serialized perk charge. Initial isolated five-request burst at5.580-5.693s yielded no ShowPerkBar or execution; refusal cause not decoded. No physical key timestamps. Evidence supports shared-key native fan-out with existing cooldown filtering, no client rotation and no justified server change. Do not promise one action per physical press or one global perk charge.
Checklist
2 of 2 complete- Trace bindings through conflict UI, input dispatch and gameplay action execution; present a grounded design and remaining behavior questions.
- Implement shared hotbar binding choices, visible membership and native grouped execution; verify red-green regressions, protected-input behavior, persistence and client build.
- Area
- Project Rubi-Ka
- Project
- engines
- Kind
- Feature
- Task ID
tsk_gb2fj4s6nf- Updated