Players own every key; servers declare actions, not keys
Status: Accepted (2026-10-26)
Context
A multiplayer client needs keys of its own (chat, player list, screenshot), the platform offers vehicle shortcuts (engine, lights, entering as a passenger), and game modes need actions of their own (open a shop, use an item). If each of these hard-codes a key, they collide with each other and with the game's controls, and the player cannot fix the collision. If a script may listen to any key, the server can see every key the player presses, which turns it into a keylogger. Game modes still need a way to move or disable platform shortcuts that do not fit them.
Decision
Keys are split into four layers. In every layer the player has the final say over which physical key does what.
| Layer | Who sets the default key | What the server can do | What the player can do | Members |
|---|---|---|---|---|
| Core | the client | nothing: it cannot move, disable or listen to these keys | rebind | open chat (T), player list (Tab), help (F1, reserved), diagnostics (F5), hide the interface (F7), screenshot (F8), disconnect (no default key), server menu (Up, Down, Enter, Backspace), answer an offer (Y, N); the pause menu stays on Esc |
| Platform | the client | move the default key or turn the action off, server-wide | rebind | engine (K), lights (L), enter as a passenger (G) |
| Script | the resource that declares the action | declare and withdraw actions, each with an optional default key | rebind; the choice is stored per server | the game mode's own actions |
| Game | the game's own controls | only read them in player sync (the keys bitmask) | rebind in the game's own settings screen | walking, firing, aiming, entering a vehicle (F) and the rest of the controls |
- The server hears actions, never keys. A resource declares a named action (
dlmp.register_action, ActionDeclare); the client binds it to a key and reports only presses and releases of declared actions (ActionInput, Lua eventplayer_action). The server never learns which key the player chose. Actions do not fire while the chat or dialog input or a menu is open, or while the window is not focused; an action held at that moment is released. - Defaults must not collide. The server refuses a script default key that is a core key, a default key of the game's controls, a platform action's stock or current key, or another declared action's default. A modifier chord (
Ctrl+,Shift+,Alt+) is not a stock key of anyone and is allowed. If the player has already rebound something to a declared default, the client leaves the action without a key until the player picks one; remaining conflicts are resolved by the player rebinding. - Platform keys stay reserved.
K,LandGbelong to their platform actions even while a server moves an action elsewhere or turns it off (dlmp.set_platform_action_key, PlatformKeys). An action can therefore always return to its stock key, including when the resource that moved it stops, and two defaults never end up on the same key. A turned-off action does not fire even for a player who bound it themselves. - Core keys belong to the player. They give the player control over their own client, so no server may occupy them. The server also never hands out F11 (switches the game window) or F12 (the Steam overlay's screenshot).
- Server menu and offer keys do not capture input. They work only while a server menu or an offer card is open, and the game keeps them too, so a menu never takes control away from the player. The menu uses the arrows and Enter rather than spare keys such as PageUp and Home, which not every keyboard has. The cost: in some vehicles the game uses the arrows for steering, so the player scrolls and steers at once or rebinds.
YandNread as "yes" and "no" on any layout. - Tab is the player list. The game's weapon wheel, which uses Tab by default, gets another default key for the session without that choice being saved, unless the player has rebound the wheel themselves.
- Game settings are kept apart. The client keeps the game's settings, including its controls, in a profile of its own: on first launch it copies the player's single-player settings once, and from then on multiplayer changes never touch the single-player profile.
- Rebinding happens in the client's pause menu (the Key bindings tab), reached with Esc.
Consequences
- Resources get a stable, conflict-free way to offer actions, and players can always remap them.
- A resource cannot implement "press any key" logic or read raw key state; it designs around named actions instead.
- A server that wants engine or lights on a command instead of a key turns the platform action off; the stock key stays reserved and cannot be reused for a script action.
- Cursor and keyboard input for resource-owned pages are covered by ADR 0049. Gamepads are out of scope.
Alternatives considered
- Scripts bind any key directly. The script picks the key, the player cannot resolve conflicts, and the server can listen to arbitrary keys. Rejected.
- Only the game's own controls are visible to scripts. Game modes lack actions of their own. Rejected.
- A fixed core layer the player cannot rebind. Inconvenient for players with other layouts or habits. Rejected.
- Engine and lights in the core layer. A game mode could not move them to a command or another key. Rejected.
