Your first script
Let's draw something. By the end of this page you'll have a complete mod that paints a square on top of a running game — and you'll understand every line.
The whole program
// A 32×32 solid block of palette index 1.
var block: image = image_literal(32, 32,
"AQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB…") // (truncated for the page)
// Index 0 is transparent; index 1 is bright magenta.
var palette: u32[] = [0x00000000, 0x00FF00FF]
event frame(frame_num: i32) {
draw_image(block, palette, 40, 40)
}That's it. No imports, no main, no configuration, and — note — no wrapper of any kind. Let's read it top to bottom.
No boilerplate
You might expect a "this is the client" declaration somewhere. There isn't one. A Munos script with no server section is a client-only program by default — there's always a player running it, so the whole file just is the client. You write your overlay at the top level and it runs.
The client { … } / server { … } blocks exist for multiplayer: when your mod also has a server half (relaying positions, holding shared state), the server-only code goes in a server { … } block and the client-only code in a client { … } block. A purely local overlay like this one needs neither. We'll meet them in Talking to the server.
Making an image
var block: image = image_literal(32, 32, "AQEB…")image is a built-in type: a 2D grid of palette indices. Each pixel is a small number (a u8) that says "use color #N from the palette" — not an RGB color directly. This is exactly how the original consoles worked, and it's what makes recoloring cheap (more on that in Drawing sprites).
image_literal(width, height, data) builds an image from base64-encoded pixel data, decoded when your script compiles. Here every pixel is the byte 1, so the whole 32×32 block is "color #1."
The palette
var palette: u32[] = [0x00000000, 0x00FF00FF]A palette is just an array of colors — u32[]. Each entry holds a 24-bit 0xRRGGBB color in its low bytes. Index by index:
palette[0] = 0x00000000— slot 0. Palette index 0 is always transparent, no matter what color you put there. Pixels that reference it simply aren't drawn.palette[1] = 0x00FF00FF— magenta (redFF, green00, blueFF).
Our image is full of index-1 pixels, so it draws as a solid magenta square with nothing else.
Why does index 0 not draw?
Transparency in Munos is by palette slot, not by an alpha channel. Slot 0 is the see-through one. You'll see scripts write its color as 0x00000000 by convention, but any value there is still transparent.
The frame event
event frame(frame_num: i32) {
draw_image(block, palette, 40, 40)
}event frame is the heartbeat of a client mod. The runtime calls it once per emulator frame — about 60 times a second for an NTSC NES game. The frame_num parameter counts up from 0, so it doubles as a clock.
Inside, we call draw_image(image, palette, x, y) to paint our block with its top-left corner at screen pixel (40, 40). We pass no conditions, so it draws every single frame and the square is rock-steady on screen.
draw_image has many more options — flipping, clipping, sub-regions — but (image, palette, x, y) is the form you'll use most. We'll meet the rest in Drawing sprites.
Running it
To try a mod locally, start the web player from the repository root:
npm run dev -w @multinostalgia/webThat serves the player at http://localhost:5173/. Load a ROM, point the player at your script, and your magenta square appears over the game.
This square is a debugging tool
A fixed, unconditional block like this is the classic "is my draw path working at all?" probe. If you can see it, the emulator → draw_image → framebuffer chain is healthy, and any other invisibility (a ghost that won't show up, say) is a bug in your coordinates or your network logic — not in drawing itself.
What you learned
- A script with no server section is a client-only program — no wrapper needed.
imageis indexed pixels; au32[]palette turns indices into colors.- Palette index 0 is transparent.
event frame(frame_num)runs once per frame; that's where you draw.draw_image(img, pal, x, y)paints onto the screen.
Next
A static square is nice, but the point of a mod is to react to the game. Next we'll read the game's own memory to find out what's happening inside it.