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>
This commit is contained in:
parent
1f5e3c1c1a
commit
53bb441f23
35 changed files with 23630 additions and 20813 deletions
|
|
@ -190,6 +190,31 @@ function emit_ns_call(ns: pointer, meth: pointer, e: Node) -> Val {
|
|||
if (meth == "pressed") { bare = "input_pressed" }
|
||||
if (meth == "record") { bare = "input_record" }
|
||||
if (meth == "replay") { bare = "input_replay" }
|
||||
# device layer (#50): multi-key held state, analog axes/vectors, mouse,
|
||||
# gamepads, touch — reached as ordinary @fn_input_* calls into input.ludic.
|
||||
if (meth == "key_down") { bare = "input_key_down"; push(labels, "key") }
|
||||
if (meth == "key_pressed") { bare = "input_key_pressed"; push(labels, "key") }
|
||||
if (meth == "key_released") { bare = "input_key_released"; push(labels, "key") }
|
||||
if (meth == "press") { bare = "input_press"; push(labels, "key") }
|
||||
if (meth == "release") { bare = "input_release"; push(labels, "key") }
|
||||
if (meth == "axis") { bare = "input_axis"; push(labels, "neg"); push(labels, "pos") }
|
||||
if (meth == "vector") { bare = "input_vector"; push(labels, "left"); push(labels, "right"); push(labels, "up"); push(labels, "down") }
|
||||
if (meth == "strength") { bare = "input_strength"; push(labels, "action") }
|
||||
if (meth == "mouse_x") { bare = "input_mouse_x" }
|
||||
if (meth == "mouse_y") { bare = "input_mouse_y" }
|
||||
if (meth == "mouse_dx") { bare = "input_mouse_dx" }
|
||||
if (meth == "mouse_dy") { bare = "input_mouse_dy" }
|
||||
if (meth == "mouse_down") { bare = "input_mouse_down"; push(labels, "button") }
|
||||
if (meth == "wheel") { bare = "input_wheel" }
|
||||
if (meth == "set_mouse") { bare = "input_set_mouse"; push(labels, "x"); push(labels, "y"); push(labels, "buttons"); push(labels, "wheel") }
|
||||
if (meth == "pad_connected"){ bare = "input_pad_connected"; push(labels, "pad") }
|
||||
if (meth == "pad_button") { bare = "input_pad_button"; push(labels, "pad"); push(labels, "button") }
|
||||
if (meth == "pad_axis") { bare = "input_pad_axis"; push(labels, "pad"); push(labels, "axis") }
|
||||
if (meth == "set_pad") { bare = "input_set_pad"; push(labels, "pad"); push(labels, "connected"); push(labels, "buttons"); push(labels, "lx"); push(labels, "ly"); push(labels, "rx"); push(labels, "ry") }
|
||||
if (meth == "touch_count") { bare = "input_touch_count" }
|
||||
if (meth == "touch_x") { bare = "input_touch_x"; push(labels, "index") }
|
||||
if (meth == "touch_y") { bare = "input_touch_y"; push(labels, "index") }
|
||||
if (meth == "set_touch") { bare = "input_set_touch"; push(labels, "index"); push(labels, "x"); push(labels, "y"); push(labels, "active") }
|
||||
}
|
||||
# Phase 3: the bare reflection / networking / process builtins, namespaced.
|
||||
# Each is a pure alias — the callee is rewritten to the bare name below.
|
||||
|
|
|
|||
|
|
@ -63,6 +63,8 @@ function emit_header() -> void {
|
|||
emith("declare void @win_present(ptr, i32, i32)\n")
|
||||
emith("declare i32 @win_running()\n")
|
||||
emith("declare void @win_close()\n")
|
||||
emith("declare void @win_held(ptr)\n")
|
||||
emith("declare void @win_mouse(ptr)\n")
|
||||
emith("@__stderrp = external global ptr\n")
|
||||
emith("@__stdoutp = external global ptr\n")
|
||||
emith("@.fmt_int = private unnamed_addr constant [4 x i8] c\"%d\\0A\\00\"\n")
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ function is_intrinsic2(name: pointer) -> bool {
|
|||
if (name == "is_windowed") or (name == "game_title") { return true }
|
||||
if (name == "win_open") or (name == "win_poll") or (name == "win_present") { return true }
|
||||
if (name == "win_running") or (name == "win_close") { return true }
|
||||
if (name == "win_held") or (name == "win_mouse") { return true } # #50 device layer
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -49,5 +50,10 @@ function emit_intrinsic2(name: pointer, e: Node) -> Val {
|
|||
return val("0", "void")
|
||||
}
|
||||
if (name == "win_close") { emit(" call void @win_close()\n"); return val("0", "void") }
|
||||
# #50 — fill a caller buffer with the platform device state (windowed only,
|
||||
# DCE'd headless). win_held: an 8-word held-key bitset; win_mouse: [x, y,
|
||||
# button-mask, wheel-delta].
|
||||
if (name == "win_held") { let a = arg_code(e, 0); emit(" call void @win_held(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||
if (name == "win_mouse") { let a = arg_code(e, 0); emit(" call void @win_mouse(ptr "); emit(a); emit(")\n"); return val("0", "void") }
|
||||
return val("0", "void")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -183,6 +183,9 @@ function p_postfix() -> Node {
|
|||
# Input.* action-map / record-replay methods (#7) -> splice input.ludic.
|
||||
# Input.key stays bare (no runtime), so gate on the new methods only.
|
||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "bind" or e.s == "rebind" or e.s == "poll" or e.s == "down" or e.s == "pressed" or e.s == "record" or e.s == "replay") { g_uses_input = true }
|
||||
# #50 device layer — any of the multi-key / analog / mouse / gamepad / touch
|
||||
# methods also splices input.ludic (Input.key stays bare, no runtime).
|
||||
if e.a.kind == E_ID and e.a.s == "Input" and (e.s == "key_down" or e.s == "key_pressed" or e.s == "key_released" or e.s == "press" or e.s == "release" or e.s == "axis" or e.s == "vector" or e.s == "strength" or e.s == "mouse_x" or e.s == "mouse_y" or e.s == "mouse_dx" or e.s == "mouse_dy" or e.s == "mouse_down" or e.s == "wheel" or e.s == "set_mouse" or e.s == "pad_connected" or e.s == "pad_button" or e.s == "pad_axis" or e.s == "set_pad" or e.s == "touch_count" or e.s == "touch_x" or e.s == "touch_y" or e.s == "set_touch") { g_uses_input = true }
|
||||
# Anim.play/clip/on_frame/fired + Motion.to (#48): the ergonomic writes over
|
||||
# the SpriteAnim/Motion components live in systems.ludic and use the world
|
||||
# table, so splice it and force the reflection ABI even if the game leaves
|
||||
|
|
|
|||
43321
selfhost/ludicc.seed.ll
43321
selfhost/ludicc.seed.ll
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue