Input
Read the player's gamepad. Client-side — the server has no controller. Reading input has the same blast radius as reading memory: the script already sees everything the player does on screen.
event input
event input(button: i32, pressed: bool)Fires once on every button edge — pressed = true on press, pressed = false on release. Use it for discrete actions (toggle a menu, fire a one-shot).
event input(button: i32, pressed: bool) {
if button == BUTTON_START && pressed {
// ...
}
}Polling
input_held(button: i32): bool // is the button down right now?
input_pressed(button: i32): bool // did it go down this frame? (rising edge)Call these inside event frame for continuous controls (move a cursor while a direction is held). Both are sampled against the frame-start input snapshot, so multiple checks within one frame are mutually consistent.
event frame(frame_num: i32) {
if input_pressed(BUTTON_DOWN) { cursor += 1 }
if input_held(BUTTON_B) { /* run */ }
}consume_input
consume_input()Valid only inside event input — calling it elsewhere traps. It hides the triggering button-edge (and its matching release) from the ROM, so the underlying game never sees that press.
By default the ROM does see every press (the script is an observer). Consume when your UI must capture a button — e.g. Start opens your menu and must not also pause the cartridge:
event input(button: i32, pressed: bool) {
if button == BUTTON_START && pressed {
consume_input() // ROM won't see this Start
toggle_menu()
}
}Button constants
Eight predefined constants, normalized across NES, Game Boy, and the common SNES subset:
BUTTON_UP BUTTON_DOWN BUTTON_LEFT BUTTON_RIGHT
BUTTON_A BUTTON_B BUTTON_START BUTTON_SELECTA script written against these works unchanged on every console in scope. The v1 surface reads controller 1 only. See Buttons & palette.