Compare commits
5 Commits
086c51ee28
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a82df480e | ||
|
|
0b2d70f43b | ||
|
|
8f4128ee34 | ||
|
|
26a38c74e2 | ||
|
|
837dc3cbc7 |
13
.githooks/pre-commit
Normal file
13
.githooks/pre-commit
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Sporel API-Doc-Convention pre-commit hook.
|
||||||
|
SPOREL_EXE="${SPOREL_EXE:-$(command -v Sporel.exe 2>/dev/null || command -v sporel 2>/dev/null)}"
|
||||||
|
if [ -z "$SPOREL_EXE" ]; then
|
||||||
|
echo "INFO: Sporel.exe not on PATH. Skipping lint." >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
"$SPOREL_EXE" --lint="$(pwd)" --fix
|
||||||
|
RC=$?
|
||||||
|
if git diff --cached --name-only | grep -qx 'README.md'; then
|
||||||
|
git add README.md
|
||||||
|
fi
|
||||||
|
exit $RC
|
||||||
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
Copyright (c) 2026 Calic. All rights reserved.
|
||||||
|
|
||||||
|
This software is part of the Sporel platform — **Tier 1 (Official /
|
||||||
|
Proprietary)** content per the Three-Tier Licensing Model documented in
|
||||||
|
`meta/docs/archive/design/vision.md §Licensing Model` (current source;
|
||||||
|
migration to `meta/docs/architecture/licensing-model.md` pending).
|
||||||
|
|
||||||
|
⚠ **WIP — Legal review required before public launch.** The terms below
|
||||||
|
reflect design intent only; the formalized license framework will be
|
||||||
|
finalized through legal counsel before the first public release. Until
|
||||||
|
then, this notice serves as a placeholder defending the platform owner's
|
||||||
|
rights against unintentional re-licensing.
|
||||||
|
|
||||||
|
No license is granted to copy, modify, distribute, sublicense, or otherwise
|
||||||
|
use this software in any form without prior written permission from the
|
||||||
|
copyright holder.
|
||||||
|
|
||||||
|
References:
|
||||||
|
- Tier 1 (this file): all rights reserved, proprietary, sold/distributed
|
||||||
|
via official channels (Steam, etc.)
|
||||||
|
- Tier 2 (Semi-Commercial Co-Development): bilateral contracts, revenue-
|
||||||
|
share — see vision.md §Licensing Model
|
||||||
|
- Tier 3 (Community Content): CC BY-NC-SA 4.0 + asymmetric CLA — applies
|
||||||
|
to community-uploaded libs/modules/assets, not this repo
|
||||||
143
README.md
143
README.md
@@ -1,42 +1,108 @@
|
|||||||
# lib-core.input
|
# lib-core.input
|
||||||
|
|
||||||
P.0 action-mapping lib. Decouples physical keys from logical actions.
|
P.0 action-mapping lib. Decouples physical keys from logical actions.
|
||||||
Polling-based (`is_action_down`) + edge-based (`was_action_pressed`).
|
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}².
|
Direction-vector helper composes 4 actions into `{x, y}` in {-1, 0, +1}^2.
|
||||||
|
|
||||||
- Lib-ID: `lib-core.input`
|
**Version:** 0.4.0
|
||||||
- Version: `0.2.0` (breaking change from v0.1.0 — string-keys only, see §Migration)
|
**Lib-ID:** lib-core.input
|
||||||
- Spec v0.2.0 (P.2.6): `meta/docs/superpowers/specs/2026-05-11-engine-input-symbolic-keys-design.md`
|
**Requires:** (none)
|
||||||
- Spec v0.1.0 (P.0): `meta/docs/superpowers/specs/2026-05-09-p0-lib-input-design.md`
|
**Tags:** input, action-mapping
|
||||||
|
|
||||||
Forward-compat stubs (DEPRECATED-MVP) for mouse-button actions, gamepad
|
## Topology
|
||||||
bindings, action-context-stack, key-rebinding-config, modifier-combos,
|
|
||||||
analog-axis, was_action_released edge, right-modifier-keys, function-keys,
|
<!-- topology:start (auto-generated; do not edit) -->
|
||||||
special-keys (delete/insert/home/end/pageup/pagedown).
|
```mermaid
|
||||||
|
graph LR
|
||||||
|
this["lib-core.input"]
|
||||||
|
engine["engine.*"]
|
||||||
|
this --> engine
|
||||||
|
```
|
||||||
|
<!-- topology:end -->
|
||||||
|
|
||||||
## API
|
## API
|
||||||
|
|
||||||
- `input.bind(action_name, {"keyname1", "keyname2", ...})` — bind multi-key array (string-keys only, see §Key-Names)
|
### `input.bind(action_name, keys)`
|
||||||
- `input.unbind(action_name)` — remove binding
|
**Syntax:** `input.bind(action_name: string, keys: string[]) -> void`
|
||||||
- `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
|
|
||||||
|
|
||||||
## Key-Names (v0.2.0)
|
**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.
|
Strict-lowercase. Engine-internal `engine.input.KEY_*` integer-args are rejected.
|
||||||
|
|
||||||
| Category | Names |
|
| Category | Names |
|
||||||
|---|---|
|
|---|---|
|
||||||
| Letters | `a`, `b`, …, `z` |
|
| Letters | `a`, `b`, ..., `z` |
|
||||||
| Numbers | `0`, `1`, …, `9` |
|
| Numbers | `0`, `1`, ..., `9` |
|
||||||
| Arrows | `left`, `right`, `up`, `down` |
|
| Arrows | `left`, `right`, `up`, `down` |
|
||||||
| Whitespace + control | `space`, `tab`, `enter`, `backspace`, `escape` |
|
| Whitespace + control | `space`, `tab`, `enter`, `backspace`, `escape` |
|
||||||
| Modifiers (left-side) | `shift` (= `lshift`), `ctrl` (= `lctrl`), `alt` (= `lalt`) |
|
| Modifiers (left-side) | `shift` (= `lshift`), `ctrl` (= `lctrl`), `alt` (= `lalt`) |
|
||||||
| Console-toggle | `^` (DE: Zirkumflex / US: backtick — Quake/Source-Tradition) |
|
| 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** — additiv wenn `engine.input` die entsprechenden `KEY_*`-Konstanten exposed.
|
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.
|
Unknown key-names raise a Lua-error with hint to this README.
|
||||||
|
|
||||||
@@ -44,8 +110,8 @@ Unknown key-names raise a Lua-error with hint to this README.
|
|||||||
|
|
||||||
- Y-down-positive per Sporel pixel-convention (ADR-0031).
|
- Y-down-positive per Sporel pixel-convention (ADR-0031).
|
||||||
- Multi-key per action (e.g., `{"a", "left"}` for both A and Left-Arrow).
|
- 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).
|
- Silent-false for undefined actions (debug-friendly).
|
||||||
- Lua `error(...)` for `bind()` misuse (caller-bug fast-fail). Atomic: invalid key fails the entire bind without partial-state mutation.
|
- Lua `error(...)` for `bind()` misuse (caller-bug fast-fail). Atomic.
|
||||||
|
|
||||||
## Consumer pattern
|
## Consumer pattern
|
||||||
|
|
||||||
@@ -60,17 +126,38 @@ input.bind("move_down", { "s", "down" })
|
|||||||
function update(ctx, dt)
|
function update(ctx, dt)
|
||||||
if input.was_action_pressed("quit") then engine.exit(0) end
|
if input.was_action_pressed("quit") then engine.exit(0) end
|
||||||
local d = input.direction("move_left", "move_right", "move_up", "move_down")
|
local d = input.direction("move_left", "move_right", "move_up", "move_down")
|
||||||
-- d.x, d.y ∈ {-1, 0, +1}
|
-- d.x, d.y in {-1, 0, +1}
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
## Migration v0.1.0 → v0.2.0
|
## 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".
|
`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 |
|
| v0.1.0 | v0.2.0 |
|
||||||
|---|---|
|
|---|---|
|
||||||
| `bind("quit", { engine.input.KEY_ESCAPE })` | `bind("quit", { "escape" })` |
|
| `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.
|
||||||
|
|
||||||
|
### 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)
|
||||||
|
|||||||
26
init.lua
26
init.lua
@@ -1,7 +1,12 @@
|
|||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
-- lib-core.input — Action-Mapping + Direction-Vector (v0.3.0)
|
-- lib-core.input — Action-Mapping + Direction-Vector (v0.4.0)
|
||||||
-- See: meta/docs/superpowers/specs/2026-05-12-p3-3-mouse-input-design.md
|
-- See: meta/docs/superpowers/specs/2026-05-12-p3-3-mouse-input-design.md
|
||||||
--
|
--
|
||||||
|
-- v0.4.0 (P.3.10 2026-05-15): was_action_released added — release-edge
|
||||||
|
-- detection parallel to was_action_pressed. Engine-Surface:
|
||||||
|
-- engine.input.was_released + was_mouse_released. Selection-Lib's
|
||||||
|
-- prev_click_down workaround promoted.
|
||||||
|
--
|
||||||
-- v0.3.0 (P.3.3 2026-05-12): bind() now accepts unified key+mouse
|
-- v0.3.0 (P.3.3 2026-05-12): bind() now accepts unified key+mouse
|
||||||
-- strings via BIND_MAP with kind-tagged entries ({kind="key"|"mouse",
|
-- strings via BIND_MAP with kind-tagged entries ({kind="key"|"mouse",
|
||||||
-- code=<engine-int>}). Mixed bindings supported: e.g.
|
-- code=<engine-int>}). Mixed bindings supported: e.g.
|
||||||
@@ -12,10 +17,10 @@
|
|||||||
-- v0.2.0 (P.2.6 2026-05-11): bind() accepts string-key-names only.
|
-- v0.2.0 (P.2.6 2026-05-11): bind() accepts string-key-names only.
|
||||||
-- Integer engine.input.KEY_* args are rejected.
|
-- Integer engine.input.KEY_* args are rejected.
|
||||||
--
|
--
|
||||||
-- Scope v0.3.0: action-mapping (string-key+mouse-arrays per action) +
|
-- Scope v0.4.0: action-mapping (string-key+mouse-arrays per action) +
|
||||||
-- direction-vector helper. DEPRECATED-MVP: mouse-wheel, was_released
|
-- press-edge + release-edge detection + direction-vector helper.
|
||||||
-- edge, mouse-delta helper, side-buttons, gamepad bindings,
|
-- DEPRECATED-MVP: mouse-wheel, mouse-delta helper, side-buttons,
|
||||||
-- action-context-stack, key-rebinding config.
|
-- gamepad bindings, action-context-stack, key-rebinding config.
|
||||||
-- =====================================================================
|
-- =====================================================================
|
||||||
|
|
||||||
-- Unified string → {kind, code} mapping. Lowercase convention.
|
-- Unified string → {kind, code} mapping. Lowercase convention.
|
||||||
@@ -126,6 +131,16 @@ function M.was_action_pressed(action_name)
|
|||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
function M.was_action_released(action_name)
|
||||||
|
local entries = bindings[action_name]
|
||||||
|
if not entries then return false end
|
||||||
|
for _, e in ipairs(entries) do
|
||||||
|
if e.kind == "key" and engine.input.was_released(e.code) then return true end
|
||||||
|
if e.kind == "mouse" and engine.input.was_mouse_released(e.code) then return true end
|
||||||
|
end
|
||||||
|
return false
|
||||||
|
end
|
||||||
|
|
||||||
-- Direction-vector helper: returns {x, y} in {-1, 0, +1} each axis.
|
-- Direction-vector helper: returns {x, y} in {-1, 0, +1} each axis.
|
||||||
-- Y-down-positive per Sporel pixel-convention (ADR-0031).
|
-- Y-down-positive per Sporel pixel-convention (ADR-0031).
|
||||||
function M.direction(left_action, right_action, up_action, down_action)
|
function M.direction(left_action, right_action, up_action, down_action)
|
||||||
@@ -144,7 +159,6 @@ end
|
|||||||
|
|
||||||
-- DEPRECATED-MVP (P.3.3-introduced):
|
-- DEPRECATED-MVP (P.3.3-introduced):
|
||||||
-- mouse-wheel binding (wheel_up/wheel_down) — P.3-future-slice (post-P.3.4); continuous-vs-edge brainstorming needed
|
-- mouse-wheel binding (wheel_up/wheel_down) — P.3-future-slice (post-P.3.4); continuous-vs-edge brainstorming needed
|
||||||
-- was_action_released(action) — lift-detection-edge (both key + mouse atomic)
|
|
||||||
-- mouse-delta helper (engine.input.get_mouse_delta) — P.3.5/6 drag-pan / drag-box
|
-- mouse-delta helper (engine.input.get_mouse_delta) — P.3.5/6 drag-pan / drag-box
|
||||||
-- mouse-button side-buttons (mouse_4 / mouse_5) — additive extension
|
-- mouse-button side-buttons (mouse_4 / mouse_5) — additive extension
|
||||||
-- mouse-position lib-wrapper (input.mouse_pos() → {x,y}) — convenience layer
|
-- mouse-position lib-wrapper (input.mouse_pos() → {x,y}) — convenience layer
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"id":"lib-core.input","version":"0.3.0","api_min":"0.1"}
|
{"id":"lib-core.input","version":"0.4.0","api_min":"0.1"}
|
||||||
3
scripts/install-hooks.sh
Normal file
3
scripts/install-hooks.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
git config core.hooksPath .githooks
|
||||||
|
echo "Hooks activated."
|
||||||
Reference in New Issue
Block a user