bind() accepts lowercase string-keys only. Integer engine.input.KEY_*
args are rejected. Internal KEY_MAP translates strings to keycodes.
Coverage: 26 letters (a-z), 10 numbers ('0'-'9'), 4 arrows (left/
right/up/down), 5 whitespace+control (space/tab/enter/backspace/
escape), 3 modifiers (shift/ctrl/alt with l-prefix aliases for
explicit left-side), and '^' (console-toggle, DE-keyboard
Zirkumflex / US-keyboard backtick).
Right-modifier-aliases (rshift/rctrl/ralt) + function-keys + special-
keys forthcoming when engine.input exposes the corresponding KEY_*
constants.
Atomic-bind: invalid key fails entire bind without partial state
mutation. Loud Lua-errors on misuse with hint to README.
Adressiert audit-I3 (engine-internal-namespace leak).
Spec: meta/docs/superpowers/specs/2026-05-11-engine-input-symbolic-keys-design.md
lib-core.input
P.0 action-mapping lib. Decouples physical keys from logical actions.
Polling-based (is_action_down) + edge-based (was_action_pressed).
Direction-vector helper composes 4 actions into {x, y} ∈ {-1, 0, +1}².
- Lib-ID:
lib-core.input - Version:
0.2.0(breaking change from v0.1.0 — string-keys only, see §Migration) - Spec v0.2.0 (P.2.6):
meta/docs/superpowers/specs/2026-05-11-engine-input-symbolic-keys-design.md - Spec v0.1.0 (P.0):
meta/docs/superpowers/specs/2026-05-09-p0-lib-input-design.md
Forward-compat stubs (DEPRECATED-MVP) for mouse-button actions, gamepad bindings, action-context-stack, key-rebinding-config, modifier-combos, analog-axis, was_action_released edge, right-modifier-keys, function-keys, special-keys (delete/insert/home/end/pageup/pagedown).
API
input.bind(action_name, {"keyname1", "keyname2", ...})— bind multi-key array (string-keys only, see §Key-Names)input.unbind(action_name)— remove bindinginput.is_action_down(action) → bool— any bound key currently downinput.was_action_pressed(action) → bool— any bound key just-pressedinput.direction(left, right, up, down) → {x, y}— vec in {-1,0,+1}², Y-down-positiveinput.action_count() → number— bound-actions count
Key-Names (v0.2.0)
Strict-lowercase. Engine-internal engine.input.KEY_* integer-args are rejected.
| Category | Names |
|---|---|
| Letters | a, b, …, z |
| Numbers | 0, 1, …, 9 |
| Arrows | left, right, up, down |
| Whitespace + control | space, tab, enter, backspace, escape |
| Modifiers (left-side) | shift (= lshift), ctrl (= lctrl), alt (= lalt) |
| Console-toggle | ^ (DE: Zirkumflex / US: backtick — Quake/Source-Tradition) |
Right-modifier-aliases (rshift, rctrl, ralt) + function-keys (f1..f12) + special-keys (delete, insert, …) are forthcoming — additiv wenn engine.input die entsprechenden KEY_*-Konstanten exposed.
Unknown key-names raise a Lua-error with hint to this README.
Conventions
- Y-down-positive per Sporel pixel-convention (ADR-0031).
- Multi-key per action (e.g.,
{"a", "left"}for both A and Left-Arrow). - Silent-false for undefined actions (debug-friendly; lookup-misuse won't drown the game-loop in errors).
- Lua
error(...)forbind()misuse (caller-bug fast-fail). Atomic: invalid key fails the entire bind without partial-state mutation.
Consumer pattern
local input = require("lib-core.input")
input.bind("quit", { "escape" })
input.bind("move_left", { "a", "left" })
input.bind("move_right", { "d", "right" })
input.bind("move_up", { "w", "up" })
input.bind("move_down", { "s", "down" })
function update(ctx, dt)
if input.was_action_pressed("quit") then engine.exit(0) end
local d = input.direction("move_left", "move_right", "move_up", "move_down")
-- d.x, d.y ∈ {-1, 0, +1}
end
Migration v0.1.0 → v0.2.0
bind() switched from integer engine.input.KEY_* arguments to lowercase string-keys (per P.2.6). Reason: stop leaking engine-internal namespace into modules; align with ADR-0001 „engine knows verbs, libs bring nouns".
| v0.1.0 | v0.2.0 |
|---|---|
bind("quit", { engine.input.KEY_ESCAPE }) |
bind("quit", { "escape" }) |
bind("move", { engine.input.KEY_A, engine.input.KEY_LEFT }) |
bind("move", { "a", "left" }) |
engine.input.KEY_* constants remain available for direct engine-input use (e.g. engine.input.is_key_down(engine.input.KEY_F1) for edge-cases without bind), but lib-core.input.bind strict-rejects them.