Guide3 min read3 sections

Actions and player controls

docs/INPUT_ACTIONS.mdSource updated 2026-10-25
Markdown
On this page 3 sections

packages/input is a device-neutral action map with a keyboard adapter. Games define named axes and buttons, binding slots and reserved navigation keys. The player sends semantic input frames to simulation; physical key codes stay outside the worker and network protocol. games/engine-zoo/controls.ts demonstrates unrelated puzzle actions without importing Darkwind.

#Try it

In the development player, press Escape → Make yourself comfortable → Set your controls. Select a binding and press a key. Escape cancels capture; Tab cancels capture and advances focus. Conflicts identify the existing control that owns the key. Escape, Tab and function keys remain reserved for navigation and diagnostics. Restore defaults is available at the bottom of the panel.

Movement, looking, jumping, running, interaction, journal, satchel and chat can be rebound. HUD prompts and the world’s accessible description update immediately. Mouse look remains available. Bindings use physical keyboard codes, so letter labels describe key positions using the Latin layout; automatic localization and layout-aware labels remain open.

Preferences use darkwind:controls:v1, separate from world saves and room state. A profile has format:1, a game-defined map ID and a complete binding-slot map. Invalid profiles leave defaults active without overwriting stored bytes. Failed storage writes leave the edited controls usable in the current session, with a visible notice. Successful changes survive reload. Different origins retain separate browser preferences.

#Public contract

  • new ActionMap(definition) owns validated copies of definitions and bindings. Axes combine into [-1, 1]; buttons combine into [0, 1]. Opposing keyboard bindings cancel. Alternative keys in one slot contribute only once.
  • pressKey(code, repeat) and releaseKey(code) return semantic button edges. OS repeat cannot re-arm a key after a context release. A fresh physical press is required.
  • profile, keys and definition return owned copies. applyProfile, bind and reset validate before mutation and clear held/source/relative state on success. Rejected changes preserve the previous state.
  • setSource(id, values) and releaseSource(id) admit bounded named analog/button sources. addRelative and takeRelative handle consumable axis deltas, used by mouse look. The standard-controller adapter and touch adapter now use these extension points.
  • releaseAll() clears keyboard, source and relative input. The player calls it for menus, chat, dialogue, blur, hidden documents, graphics recovery, room disconnection, profile changes and disposal. Modified browser shortcuts and text composition also clear held input. Gameplay actions do not fire while editing text.

Maps support at most 32 actions, 64 binding slots, four key alternatives per slot and eight non-keyboard sources. Source values and relative deltas are validated before application. Runtime input timers retain the existing 50 ms cadence and simulation expiry rules.

#Acceptance and remaining scope

tests/input.test.ts checks action aggregation, ownership, profile admission, atomic rejection, source limits, relative deltas, release/repeat behavior and the independent puzzle map. tests/browser/controls.spec.ts exercises real movement/jumping, interaction/chat, focus loss, transport disconnection, reload, conflicting keys, default restoration and unavailable storage in production-built clients. See recorded acceptance.

This is the initial public action layer and keyboard editor. Standard-controller discovery/deadzones and touch controls have later scoped implementations linked above. Chord bindings, multiple profile selection, localization, general UI bridging, complete hot-reload teardown and hardware accessibility acceptance remain open. This increment does not close the entire player-runtime milestone.

End of Input actions