Fixes the papercut: the generated frame loop called rt_poll() (feeding only Input.key) but never input_poll(), so Input.active/key_down/mouse/pad read empty unless the game called Input.poll() by hand. The loop now calls input_poll() when the game uses any Input runtime method — committing the held-key/mouse/gamepad state, and record/replay — and stores its return as the frame key so Input.key still works. A game using no Input runtime keeps the plain rt_poll path, byte-identical. Adds the Input-Manager API: Input.action(name,key) ships a default binding (kept if already bound, so a rebind/loaded map isn't clobbered), Input.bind_pad(name,button) makes an action device-agnostic (keyboard OR pad), and Input.active/just_pressed/just_released read the multi-key device layer with clean on-press/on-release edges (deterministic, dispatch-free — a handler polls the edge; a replay fires identically). Examples input_manager + input_auto, 5 docs pages. Full suite 107/0, goldens byte-identical, fixpoint holds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
42 lines
2 KiB
Text
42 lines
2 KiB
Text
# input_manager.ludic — the Input Manager (#83): default bindings a game ships and
|
|
# a player rebinds, and device-agnostic actions that read the multi-key device
|
|
# layer across keyboard and gamepad, with clean on-press / on-release edges. Driven
|
|
# headless by injection (Input.press / Input.set_pad), the same way #7/#50 test.
|
|
#
|
|
# Deterministic; a full run prints: 1 1 1 0 1 0 0 1
|
|
program InputManager {
|
|
function bi(b: bool) -> int { if b { return 1 }; return 0 }
|
|
|
|
entry {
|
|
Input.action("jump", ' ') # ship a default binding (space)
|
|
Input.action("jump", 'k') # ignored — jump already has a default, not clobbered
|
|
Input.bind_pad("jump", 0) # device-agnostic: also fire jump from pad button 0
|
|
|
|
# A. active via the keyboard, with on-press / on-release edges.
|
|
Input.press(' ')
|
|
Input.poll()
|
|
print(bi(Input.active("jump"))) # 1 — space held
|
|
print(bi(Input.just_pressed("jump"))) # 1 — went active this frame
|
|
print(bi(Input.active("jump"))) # 1 — still active
|
|
Input.poll()
|
|
print(bi(Input.just_pressed("jump"))) # 0 — held, not a fresh press
|
|
Input.release(' ')
|
|
Input.poll()
|
|
print(bi(Input.just_released("jump"))) # 1 — went inactive this frame
|
|
print(bi(Input.active("jump"))) # 0
|
|
|
|
# B. the default was not clobbered — 'k' never fires jump (2nd default ignored).
|
|
Input.press('k')
|
|
Input.poll()
|
|
print(bi(Input.active("jump"))) # 0 — 'k' isn't bound to jump
|
|
|
|
# C. device-agnostic — a gamepad button fires the same action, no key held.
|
|
# (The just_pressed/just_released *edges* also work across the pad in a real
|
|
# windowed game, where the platform refreshes the pad each frame; the headless
|
|
# injection path here exercises the device-agnostic `active` read.)
|
|
Input.release('k')
|
|
Input.set_pad(0, true, 1, 0.0, 0.0, 0.0, 0.0) # pad 0, button-0 mask (bit 0)
|
|
Input.poll()
|
|
print(bi(Input.active("jump"))) # 1 — active via the pad, no key held
|
|
}
|
|
}
|