feat: P.0.lib.input action-mapping + direction-vector helper
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
build/
|
||||
*.log
|
||||
44
README.md
Normal file
44
README.md
Normal file
@@ -0,0 +1,44 @@
|
||||
# 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
|
||||
```
|
||||
70
init.lua
Normal file
70
init.lua
Normal file
@@ -0,0 +1,70 @@
|
||||
-- =====================================================================
|
||||
-- lib-core.input — Action-Mapping + Direction-Vector (P.0)
|
||||
-- See: meta/docs/superpowers/specs/2026-05-09-p0-lib-input-design.md
|
||||
--
|
||||
-- Scope: action-mapping (key-arrays per action) + direction-vector helper.
|
||||
-- DEPRECATED-MVP for: mouse-button actions, gamepad bindings, action-
|
||||
-- context-stack, key-rebinding config, modifier-combos, analog-axis,
|
||||
-- was_action_released edge.
|
||||
-- =====================================================================
|
||||
|
||||
local bindings = {} -- action_name -> array of key-codes
|
||||
|
||||
local M = {}
|
||||
|
||||
function M.bind(action_name, keys)
|
||||
if type(action_name) ~= "string" then
|
||||
error("input.bind: action_name must be a string")
|
||||
end
|
||||
if type(keys) ~= "table" or #keys == 0 then
|
||||
error(string.format("input.bind: keys must be a non-empty array of key-codes (action '%s')",
|
||||
action_name))
|
||||
end
|
||||
bindings[action_name] = keys
|
||||
end
|
||||
|
||||
function M.unbind(action_name)
|
||||
bindings[action_name] = nil
|
||||
end
|
||||
|
||||
function M.is_action_down(action_name)
|
||||
local keys = bindings[action_name]
|
||||
if not keys then return false end
|
||||
for _, k in ipairs(keys) do
|
||||
if engine.input.is_key_down(k) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function M.was_action_pressed(action_name)
|
||||
local keys = bindings[action_name]
|
||||
if not keys then return false end
|
||||
for _, k in ipairs(keys) do
|
||||
if engine.input.was_pressed(k) then return true end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
-- Direction-vector helper: returns {x, y} in {-1, 0, +1} each axis.
|
||||
-- Y-down-positive per Sporel pixel-convention (ADR-0031).
|
||||
function M.direction(left_action, right_action, up_action, down_action)
|
||||
local x = (M.is_action_down(right_action) and 1 or 0)
|
||||
- (M.is_action_down(left_action) and 1 or 0)
|
||||
local y = (M.is_action_down(down_action) and 1 or 0)
|
||||
- (M.is_action_down(up_action) and 1 or 0)
|
||||
return { x = x, y = y }
|
||||
end
|
||||
|
||||
function M.action_count()
|
||||
local n = 0
|
||||
for _ in pairs(bindings) do n = n + 1 end
|
||||
return n
|
||||
end
|
||||
|
||||
-- DEPRECATED-MVP: bind_mouse(action, button) -- mouse-button slice
|
||||
-- DEPRECATED-MVP: bind_gamepad(action, ...) -- gamepad slice
|
||||
-- DEPRECATED-MVP: push_context(name) / pop_context() -- action-context-stack
|
||||
-- DEPRECATED-MVP: load_bindings_from_config(path) -- key-rebinding slice
|
||||
-- DEPRECATED-MVP: was_action_released(action) -- lift-detection slice
|
||||
|
||||
return M
|
||||
1
manifest.lib
Normal file
1
manifest.lib
Normal file
@@ -0,0 +1 @@
|
||||
{"id":"lib-core.input","version":"0.1.0","api_min":"0.1"}
|
||||
Reference in New Issue
Block a user