# 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}` in {-1, 0, +1}^2. | Field | Value | |---|---| | Version | 0.4.0 | | Lib-ID | lib-core.input | | Requires | (none) | | Tags | input, action-mapping | ```mermaid graph LR this["lib-core.input"] engine["engine.*"] this --> engine ``` ## API ### `input.bind(action_name, keys)` **Syntax:** `input.bind(action_name: string, keys: string[]) -> void` **Example:** ```lua input.bind("move_left", { "a", "left" }) ``` **Description:** Bindet einen logischen Action-Namen an ein Array von Key-Names. Atomic: wenn ein Key invalid ist, fehlt der gesamte Bind ohne partial state. Lua `error(...)` für `bind()` misuse (caller-bug fast-fail). ### `input.unbind(action_name)` **Syntax:** `input.unbind(action_name: string) -> void` **Example:** ```lua input.unbind("move_left") ``` **Description:** Entfernt eine Action-Binding. No-op wenn Action nicht gebunden. ### `input.is_action_down(action) -> bool` **Syntax:** `input.is_action_down(action: string) -> bool` **Example:** ```lua if input.is_action_down("move_left") then ... end ``` **Description:** Returns true wenn irgendein gebundener Key der Action gerade gedrückt ist. Silent-false für undefined actions. ### `input.was_action_pressed(action) -> bool` **Syntax:** `input.was_action_pressed(action: string) -> bool` **Example:** ```lua if input.was_action_pressed("quit") then engine.exit(0) end ``` **Description:** Press-Edge — true für genau einen Frame pro Key-Down-Event. Triggers einmal pro Press-Release-Cycle. ### `input.was_action_released(action) -> bool` **Syntax:** `input.was_action_released(action: string) -> bool` **Example:** ```lua if input.was_action_released("attack") then finish_swing() end ``` **Description:** Release-Edge — true für genau einen Frame pro Key-Up-Event. v0.4.0+. Triggers einmal pro Press-Release-Cycle. ### `input.direction(left, right, up, down)` **Syntax:** `input.direction(left: string, right: string, up: string, down: string) -> {x: int, y: int}` **Example:** ```lua local d = input.direction("move_left", "move_right", "move_up", "move_down") -- d.x, d.y in {-1, 0, +1} ``` **Description:** Komponiert 4 Actions in einen Direction-Vector. Y-down-positive per ADR-0031. ### `input.action_count() -> number` **Syntax:** `input.action_count() -> number` **Description:** Returns Anzahl der gebundenen Actions. Debug-friendly. ## Key-Names 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) | | Mouse | `mouse_left`, `mouse_right`, `mouse_middle` (v0.3.0+) | Right-modifier-aliases (`rshift`, `rctrl`, `ralt`) + function-keys (`f1`..`f12`) + special-keys (`delete`, `insert`, ...) are **forthcoming**. 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). - Lua `error(...)` for `bind()` misuse (caller-bug fast-fail). Atomic. ## 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 in {-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. | v0.1.0 | v0.2.0 | |---|---| | `bind("quit", { engine.input.KEY_ESCAPE })` | `bind("quit", { "escape" })` | ## CHANGELOG ### v0.4.0 (P.3.10, 2026-05-15) - Added `was_action_released(action_name)` — release-edge detection per action. ### v0.3.0 (P.3.3, 2026-05-12) - `bind()` now accepts unified key+mouse strings via BIND_MAP with kind-tagged entries. ### v0.2.0 (P.2.6, 2026-05-11) - `bind()` accepts string-key-names only. ### v0.1.0 (P.0) - Initial release. ## References - 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` - ADR-0001 (engine knows verbs, libs bring nouns) - ADR-0031 (pixel-convention: Y-down-positive) - ADR-0038 (API-Doc-Convention)