# 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.1.0` - Spec: `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. ## API - `input.bind(action_name, {key1, key2, ...})` — bind multi-key array - `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 - `input.direction(left, right, up, down) → {x, y}` — vec in {-1,0,+1}², Y-down-positive - `input.action_count() → number` — bound-actions count ## Conventions - Y-down-positive per Sporel pixel-convention (ADR-0031). - Multi-key per action (e.g., `{KEY_A, KEY_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). ## Consumer pattern ```lua local input = require("lib-core.input") input.bind("quit", { engine.input.KEY_ESCAPE }) input.bind("pan_left", { engine.input.KEY_A, engine.input.KEY_LEFT }) input.bind("pan_right", { engine.input.KEY_D, engine.input.KEY_RIGHT }) input.bind("pan_up", { engine.input.KEY_W, engine.input.KEY_UP }) input.bind("pan_down", { engine.input.KEY_S, engine.input.KEY_DOWN }) function update(ctx, dt) if input.was_action_pressed("quit") then engine.exit(0) end local d = input.direction("pan_left", "pan_right", "pan_up", "pan_down") -- d.x, d.y ∈ {-1, 0, +1} end ```