# lib-core.input P.0 action-mapping lib. Decouples physical keys from logical actions. Polling-based (`is_action_down`) + edge-based (`was_action_pressed`, `was_action_released`). Direction-vector helper composes 4 actions into `{x, y}` ∈ {-1, 0, +1}². - Lib-ID: `lib-core.input` - Version: `0.4.0` - Spec v0.4.0 (P.3.10): `meta/docs/superpowers/specs/2026-05-15-p3-housekeeping-design.md` - Spec v0.3.0 (P.3.3): `meta/docs/superpowers/specs/2026-05-12-p3-3-mouse-input-design.md` - 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, 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 binding - `input.is_action_down(action) → bool` — any bound key currently down - `input.was_action_pressed(action) → bool` — any bound key just-pressed (press-edge; true for exactly one frame per key-down event) - `input.was_action_released(action) → bool` — any bound key just-released (release-edge; true for exactly one frame per key-up event; triggers once per press-release cycle) - `input.direction(left, right, up, down) → {x, y}` — vec in {-1,0,+1}², Y-down-positive - `input.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(...)` for `bind()` misuse (caller-bug fast-fail). Atomic: invalid key fails the entire bind without partial-state mutation. ## Consumer pattern ```lua 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. ## CHANGELOG ## v0.4.0 (P.3.10, 2026-05-15) - Added `was_action_released(action_name)` — release-edge detection per action. Consumers can now detect key-up edges without per-frame state tracking. - Engine surface: `engine.input.was_released` + `was_mouse_released`. ## v0.3.0 (P.3.3, 2026-05-12) - `bind()` now accepts unified key+mouse strings via BIND_MAP with kind-tagged entries. - Mixed bindings supported (e.g. `{"e", "mouse_left"}`). - Backward-compatible with v0.2.0 callsites. ## v0.2.0 (P.2.6, 2026-05-11) - `bind()` accepts string-key-names only. Integer `engine.input.KEY_*` args are rejected.