Client & server
A Munos script is a client-only program by default. Write your code at the top level and it runs in the player — no role block, no ceremony. You only reach for client { … } / server { … } blocks when a mod goes multiplayer and needs a server half.
// A single-player mod is just top-level code — this is the whole program.
event frame(frame_num: i32) {
var x = read_u8(0x0086)
// ...read, draw...
}When you add a server, the file carries both halves. client { … } and server { … } are compile-time selection, not runtime branching: the client build keeps the common (top-level) code plus the client block; the server build keeps the common code plus the server block. Each build is a separate, single-role program — they just share one source file.
const PLAYER_X: u32 = 0x0086 // common — available to whichever build uses it
client {
event frame(frame_num: i32) {
var x = read_u8(PLAYER_X)
// ...read, draw, send to the server...
}
}
server {
var playerXs: u8[256] // server-only state
// event receive(...), event tick(...), ...
}Rules
- A script with no
serverblock is a client-only program; its top-level code is the client. clientandserverblocks appear at the top level only.- Declarations inside a role block (
var,const,function,event) are scoped to that role and invisible to the other. - Top-level (common) declarations are compiled into whichever build uses them. A top-level
varis per-instance state, never synchronized between the client and the server — share live state withsend/send_to, not a shared global. clientandserverare contextual — they open a role block only at the top level immediately before a{. Elsewhere they're ordinary identifiers (clientis also the handle type, andserveris a usable variable name).
What goes where
| Concern | Role |
|---|---|
| Reading/writing game memory | client (the ROM lives there) |
| Drawing overlays | client |
| Reading input, pausing the ROM | client |
| Relaying messages between players | server |
| Per-player authoritative state | server |
| Memory addresses, wire-format widths, shared constants | outside both (shared) |
Putting the shared wire format (the pack/unpack width arrays) at the top level is the idiomatic way to guarantee the two sides agree — see Networking.
Role-specific builtins
Many builtins are restricted to one role. For example draw_image, read_u8, pause_rom, and the input builtins are client-only; slot, spawn, tell, and set_tick_rate are server-only. The two send builtins are separate names: a client uses send(data, …) (its only peer is the server, so there's no recipient to name), and a server uses send_to(to, data, …) (it names which client). Each builtin's page notes its role.
Some events are role-specific too: event frame is client-only, event tick is server-only, and event receive has a different signature on each side. See Events.