Input & pausing the ROM
A mod doesn't only watch the game — it can take the controller and pause the action to show its own UI. This chapter covers reading the gamepad and freezing the ROM, which together let you build pause menus, lobbies, and debug overlays.
Reading the gamepad
There are two ways to ask about a button, because "was it just pressed?" and "is it down right now?" are different questions.
Edge events — event input fires once each time a button changes:
event input(button: i32, pressed: bool) {
if button == BUTTON_START && pressed {
print("Start was just pressed")
}
}Polling — ask inside event frame whether a button is currently held or was pressed this frame:
input_held(button) // is it down right now?
input_pressed(button) // did it go down this frame? (rising edge)The button constants are predefined and normalized across consoles:
BUTTON_UP BUTTON_DOWN BUTTON_LEFT BUTTON_RIGHT
BUTTON_A BUTTON_B BUTTON_START BUTTON_SELECTA script written against these eight works unchanged on NES, Game Boy, and the common SNES subset.
Pausing the ROM
pause_rom() freezes the emulator's CPU — the game stops advancing and its RAM holds still — but your script keeps running. event frame still fires, so your overlay UI keeps updating on top of the frozen picture. resume_rom() lifts the freeze.
pause_rom() // game freezes; your overlay keeps drawing
resume_rom() // game continues from exactly where it stoppedThis is the foundation of any modal UI: freeze the game, draw your menu over the still frame, read the D-pad to move a cursor, then resume.
Who sees the button?
By default the game also sees every button the player presses — your script is an observer. That's usually what you want (an overlay that doesn't steal input). But a pause menu opened with Start must not also pause the underlying game. To capture a button, call consume_input() inside the event input handler:
event input(button: i32, pressed: bool) {
if button == BUTTON_START && pressed {
consume_input() // the ROM won't see this Start press
// ...open the menu...
}
}consume_input() is only valid inside event input, and it swallows the button-edge that triggered the event (and its matching release). The game never sees it.
The pause-menu pattern
Here's the whole thing together — Start toggles a menu that freezes the game, and the D-pad moves a cursor while it's open:
var menu_open = false
var cursor = 0
event input(button: i32, pressed: bool) {
if button == BUTTON_START && pressed {
consume_input()
menu_open = !menu_open
if menu_open {
pause_rom()
} else {
resume_rom()
}
}
}
event frame(frame_num: i32) {
if menu_open {
// react to the D-pad
if input_pressed(BUTTON_DOWN) { cursor = cursor + 1 }
if input_pressed(BUTTON_UP) { cursor = cursor - 1 }
// ...draw the menu and a cursor at `cursor`...
}
}A few things worth noticing:
menu_openandcursorlive outside the events, so they persist between frames.- We
pause_rom()/resume_rom()exactly when the menu opens and closes — the game is frozen only while the menu is up. - Polling (
input_pressed) drives the menu navigation; the edge event (event input) handles the open/close toggle, because that's the one press we need to consume.
Polling sees a consistent snapshot
input_held and input_pressed are sampled against the input state at the start of the current frame, so multiple checks inside one event frame all agree with each other.
Input is client-only
The server has no gamepad, so all of this is client-side — and a script with no server section is a client-only program, so it needs no role block at all. (Reading input is no more powerful than reading memory — the script already sees everything the player does on screen.)
What you learned
event input(button, pressed)fires on every button edge.input_held/input_pressedpoll insideevent frame.- The eight
BUTTON_*constants are normalized across consoles. pause_rom()freezes the game while your script keeps running;resume_rom()lifts it.consume_input()(only insideevent input) hides a press from the game — the key to a pause menu that doesn't also pause the cartridge.
Next
You've now met every major piece of the language. Let's read a complete, real-world mod and see how they fit together.