ludic/examples/library/input_device.ludic
Orkuncakilkaya 53bb441f23
All checks were successful
bootstrap / cfree-fixpoint (push) Successful in 18s
ci / build-and-test (push) Successful in 1m24s
commit-lint / conventional-commits (push) Successful in 5s
docs / build-and-deploy (push) Successful in 20s
feat(input): raw device layer — multi-key held state, analog, mouse, gamepad, touch, full-state replay (#50)
The raw device layer the input proposal sketched, over the action maps +
record/replay of #7. Beyond one key per frame, gameplay can read:

- Multiple simultaneous held keys: Input.key_down / key_pressed / key_released,
  with clean rising/falling edges (hold left AND jump).
- Analog from keys: Input.axis(neg, pos) and a normalized Input.vector(l,r,u,d)
  (diagonals scaled by 1/sqrt(2)), plus Input.strength(action).
- Mouse: Input.mouse_x/y, mouse_dx/dy (per-frame delta), mouse_down(btn), wheel.
- Gamepads: Input.pad_connected/pad_button/pad_axis (SDL-order buttons, -1..1
  sticks); touch: Input.touch_count/touch_x/touch_y.

The held set is fed by the platform when windowed — cocoa.ll now tracks
keyDown/keyUp into a 256-bit held-key bitset (win_held) and the mouse
buttons/wheel (win_mouse), gated so headless builds DCE the native calls — and
by the Input.press / Input.set_mouse / Input.set_pad / Input.set_touch injection
on every target (Godot-style action injection: replays, AI, network-fed input).
Input.record / replay now snapshot the full per-frame device state (held set +
mouse), extending #7's single-key tape.

Everything is integer and deterministic, so the same inputs reproduce the same
frame on every run and headless. The gamepad/touch native hardware bindings
(GameController.framework / NSTouch) feed the same injected state and are the one
remaining platform-glue follow-up; the software layer, semantics and replay are
complete and driven deterministically today.

Worked example + regression: examples/library/input_device.ludic
(1 1 0 1 0 1 71 -71 5 1 3 1 2 1 0 0 1, injection-driven headless). 23 new
docs/language/input pages. Full suite 78 passed, self-host C-free fixpoint
intact, no golden drift; cocoa.ll assembles and a windowed build links.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-31 17:14:24 +03:00

75 lines
3.5 KiB
Text

# input_device.ludic — the raw device layer from #50, on top of the action maps
# and record/replay of #7. Beyond one key per frame, gameplay can read:
#
# * multiple simultaneous held keys — Input.key_down / key_pressed / key_released
# * analog axes and vectors derived from keys — Input.axis / Input.vector
# * the mouse — Input.mouse_x/y, mouse_dx/dy, mouse_down, wheel
# * gamepads and touch — Input.pad_button/pad_axis, Input.touch_count/touch_x
#
# The held set is fed by the platform when windowed, and on every target by the
# Input.press / Input.set_* injection (the same idea as Godot's action_press —
# what a replay, an AI, or the network feeds). Everything is integer and
# deterministic, and Input.record / replay snapshot the whole per-frame state, so
# a recorded run reproduces exactly. Driven headless by injection, a full run:
# bin/ludic examples/library/input_device.ludic -> 1 1 0 1 0 1 71 -71 5 1 3 1 2 1 0 0 1
program InputDevice {
function bi(b: bool) -> int { if b { return 1 }; return 0 }
function r(f: fixed) -> int { return Math.round(f) }
entry {
# --- multiple simultaneous held keys, with clean edges -------------------
Input.press('a') # hold left AND jump at once
Input.press(' ')
Input.poll()
print(bi(Input.key_down('a'))) # 1
print(bi(Input.key_down(' '))) # 1
print(bi(Input.key_down('d'))) # 0 — not held
print(bi(Input.key_pressed('a'))) # 1 — went down this frame
Input.poll()
print(bi(Input.key_pressed('a'))) # 0 — held, not a fresh press
Input.release('a')
Input.poll()
print(bi(Input.key_released('a'))) # 1 — went up this frame
# --- analog axis + a normalized 2D vector from four keys -----------------
Input.release(' ')
Input.press('d') # right
Input.press('w') # and up -> a diagonal
Input.poll()
let v = Input.vector('a', 'd', 'w', 's')
print(r(Vector.x(v) * 100)) # 71 — 0.7071, normalized so diagonals aren't faster
print(r(Vector.y(v) * 100)) # -71
# --- mouse: position, per-frame delta, buttons, wheel --------------------
Input.set_mouse(10, 20, 1, 3) # x, y, button-mask (left), wheel delta
Input.poll()
Input.set_mouse(15, 20, 1, 0)
Input.poll()
print(Input.mouse_dx()) # 5 — moved 10 -> 15
print(bi(Input.mouse_down(0))) # 1 — left button
Input.set_mouse(15, 20, 0, 3)
Input.poll()
print(Input.wheel()) # 3
# --- gamepad + touch (injected; a native binding feeds the same state) ---
Input.set_pad(0, true, 1, 1.0, 0.0, 0.0, 0.0) # pad 0: button 0 down, left stick x = 1.0
print(r(Input.pad_axis(0, 0))) # 1
Input.set_touch(0, 100, 200, true)
Input.set_touch(1, 50, 60, true)
print(Input.touch_count()) # 2
# --- full-state record / replay: the held set replays exactly ------------
Input.record()
Input.press('a'); Input.release('d'); Input.release('w')
Input.poll() # recorded frame 0: only 'a' held
Input.release('a'); Input.press('d')
Input.poll() # recorded frame 1: only 'd' held
Input.replay()
Input.poll() # replay frame 0
print(bi(Input.key_down('a'))) # 1
print(bi(Input.key_down('d'))) # 0
Input.poll() # replay frame 1
print(bi(Input.key_down('a'))) # 0
print(bi(Input.key_down('d'))) # 1
}
}