Memory
Read and write the emulator's address space. Client-side — the ROM lives on the client; calling these on the server has no game to address.
Addresses are u32. Out-of-range addresses trap at runtime. The set of valid ranges (RAM, registers, CHR-ROM, …) is console-specific.
Reading
read_u8(addr: u32): u8
read_u16(addr: u32): u16
read_u32(addr: u32): u32
read_i8(addr: u32): i8
read_i16(addr: u32): i16
read_i32(addr: u32): i32One builtin per width and signedness. Multi-byte reads use the console's native byte order, so you get the assembled value directly.
const PLAYER_X: u32 = 0x0086
const SCORE: u32 = 0x07DD
var x = read_u8(PLAYER_X) // u8
var score = read_u16(SCORE) // u16, native byte orderPick the width that matches how the game stores the value, and the sign that matches its meaning (a value that can go negative — a velocity — wants read_i8).
Writing
write_u8(addr: u32, value: u8)
write_u16(addr: u32, value: u16)
write_u32(addr: u32, value: u32)
write_i8(addr: u32, value: i8)
write_i16(addr: u32, value: i16)
write_i32(addr: u32, value: i32)Writing mutates the running game's state — the basis of randomizers, difficulty mods, and cheats.
const LIVES: u32 = 0x075A
write_u8(LIVES, 99)The value argument must match the builtin's width type; cast if needed (write_u8(addr, u8(n))).
write_u8 is the live machine
A write does exactly what the console's CPU does when it stores to that address — full bus, all side effects. Plain RAM takes the byte, but the same call also reaches hardware: video/scroll and sound registers, and the mapper/MBC bank-switch window ($0000–$7FFF on Game Boy, $8000–$FFFF on NES). That range is not the cartridge's bytes — writing there switches banks and toggles cart-RAM, exactly as on hardware. There is no restricted region: write_u8 gives you the whole address space.
The flip side of that power: a stray write to a hardware register or the mapper can wedge or crash the running game. Poke registers deliberately.
To edit the cartridge image itself — NOP a branch, swap an opcode permanently — use write_rom, which addresses a physical bank/offset rather than a live CPU address. Rule of thumb: write_u8 is the live machine, write_rom is the cartridge.
Casting reads
Reads come back in their natural width (read_u8 → u8), but most arithmetic and most other builtins want i32. Casting at the read site is idiomatic:
var x = i32(read_u8(PLAYER_X)) // ready for i32 mathSee Types → Conversions.
Identifying the ROM
These builtins read the mapped CPU address space, not the cartridge file itself. To identify which game is loaded, use local_rom_hash().