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>
75 lines
3.5 KiB
Text
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
|
|
}
|
|
}
|