fix(input): Key.Up/Down/Left/Right are the held set's 128-131

The arrow Key constants folded to the codes of w/s/a/d, which is what the
per-frame key reports for an arrow, but cocoa.ll has always stored an arrow in
the held-key set under 128-131. So Input.key_down(Key.Up) read the W bit and
Input.move_i's arrow half never moved anything. Input.key keeps its WASD alias,
so games comparing it with 'w' (snake, chronorift) still take the arrows.

New example input_arrows.ludic, checked by ludic-dev test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-13 03:00:29 +03:00
parent 572e09b2b1
commit 6117f73602
4 changed files with 54 additions and 10 deletions

9
changes/arrow-keys.md Normal file
View file

@ -0,0 +1,9 @@
bump: patch
type: fix
**Held arrow keys register** — `Key.Up`, `Key.Down`, `Key.Left` and `Key.Right` are 128-131.
They folded to the codes of w, s, a and d, which is what the single per-frame key
(`Input.key`) reports for an arrow. The held-key set has always stored an arrow under
128-131, so `Input.key_down(Key.Up)` tested the W bit and `Input.move_i`'s arrow half never
moved anything. `Input.key` keeps its WASD alias: a game comparing it with `'w'` still
takes the arrows.

View file

@ -0,0 +1,30 @@
# input_arrows.ludic — the arrow keys in the held-key set. Key.Up/Down/Left/Right are
# 128-131, the codes the platform stores an arrow under; they used to fold to w/s/a/d,
# so Input.key_down(Key.Up) read the W bit and Input.move_i ignored the arrows. Driven
# headless by injection, the same way the #50 device tests are.
#
# Deterministic; a full run prints: 128 129 130 131 1 0 -1 1 0
program InputArrows {
function bi(b: bool) -> int { if b { return 1 }; return 0 }
entry {
print(Key.Up)
print(Key.Down)
print(Key.Left)
print(Key.Right)
Input.press(Key.Up) # hold the up arrow
Input.poll()
print(bi(Input.key_down(Key.Up))) # 1 — the arrow is held
print(bi(Input.key_down('w'))) # 0 — and it is not W
let m = Input.move_i()
print(m.y) # -1 — move_i takes the arrows as well as WASD
Input.release(Key.Up)
Input.press(Key.Right)
Input.poll()
let r = Input.move_i()
print(r.x) # 1
print(bi(Input.key_down(Key.Up))) # 0 — released
}
}

View file

@ -1,7 +1,12 @@
# emit_key.ludic — `Key.Name` -> the key code the input layer delivers, folded at # emit_key.ludic — `Key.Name` -> the key code the input layer delivers, folded at
# compile time (like Color.*). Letters are the lowercase character (what the held-key # compile time (like Color.*). Letters are the lowercase character (what the held-key
# set stores), digits their ASCII, plus the specials. Arrow keys alias WASD because the # set stores), digits their ASCII, plus the specials; Escape is 27 (0.3, #88).
# platform layer maps them that way; Escape is 27 (0.3, #88). #
# The arrows are 128-131, the codes the platform stores them under in the held-key set
# (cocoa.ll's ev_keyval). They used to fold to w/s/a/d, which is what the single
# per-frame key (Input.key) reports for an arrow - but the held set never had those bits
# for an arrow, so Input.key_down(Key.Up) and Input.move_i's arrow half never fired.
# Input.key keeps its WASD alias, so a game comparing it with 'w' still takes the arrows.
function key_lookup(name: pointer) -> int { function key_lookup(name: pointer) -> int {
if (name == "Space") { return 32 } if (name == "Space") { return 32 }
if (name == "Enter") { return 10 } if (name == "Enter") { return 10 }
@ -11,10 +16,10 @@ function key_lookup(name: pointer) -> int {
if (name == "Shift") { return 16 } if (name == "Shift") { return 16 }
if (name == "Tab") { return 9 } if (name == "Tab") { return 9 }
if (name == "Backspace") { return 8 } if (name == "Backspace") { return 8 }
if (name == "Up") { return 119 } if (name == "Up") { return 128 }
if (name == "Down") { return 115 } if (name == "Down") { return 129 }
if (name == "Left") { return 97 } if (name == "Left") { return 130 }
if (name == "Right") { return 100 } if (name == "Right") { return 131 }
if (name == "Digit0") { return 48 } if (name == "Digit0") { return 48 }
if (name == "Digit1") { return 49 } if (name == "Digit1") { return 49 }
if (name == "Digit2") { return 50 } if (name == "Digit2") { return 50 }

View file

@ -61197,7 +61197,7 @@ ifend23:
%t27 = icmp ne i32 %t26, 0 %t27 = icmp ne i32 %t26, 0
br i1 %t27, label %then24, label %ifend26 br i1 %t27, label %then24, label %ifend26
then24: then24:
store i32 119, ptr %retval store i32 128, ptr %retval
br label %ret br label %ret
ifend26: ifend26:
%t28 = load ptr, ptr %t0 %t28 = load ptr, ptr %t0
@ -61205,7 +61205,7 @@ ifend26:
%t30 = icmp ne i32 %t29, 0 %t30 = icmp ne i32 %t29, 0
br i1 %t30, label %then27, label %ifend29 br i1 %t30, label %then27, label %ifend29
then27: then27:
store i32 115, ptr %retval store i32 129, ptr %retval
br label %ret br label %ret
ifend29: ifend29:
%t31 = load ptr, ptr %t0 %t31 = load ptr, ptr %t0
@ -61213,7 +61213,7 @@ ifend29:
%t33 = icmp ne i32 %t32, 0 %t33 = icmp ne i32 %t32, 0
br i1 %t33, label %then30, label %ifend32 br i1 %t33, label %then30, label %ifend32
then30: then30:
store i32 97, ptr %retval store i32 130, ptr %retval
br label %ret br label %ret
ifend32: ifend32:
%t34 = load ptr, ptr %t0 %t34 = load ptr, ptr %t0
@ -61221,7 +61221,7 @@ ifend32:
%t36 = icmp ne i32 %t35, 0 %t36 = icmp ne i32 %t35, 0
br i1 %t36, label %then33, label %ifend35 br i1 %t36, label %then33, label %ifend35
then33: then33:
store i32 100, ptr %retval store i32 131, ptr %retval
br label %ret br label %ret
ifend35: ifend35:
%t37 = load ptr, ptr %t0 %t37 = load ptr, ptr %t0