Server & slots
The server side coordinates connected clients. These builtins and events let it track who's connected, run periodic work, and move clients between shards.
Connection events
event connect(client: client, handoff: u8[]) // a client joined
event disconnect(client: client) // a client leftconnect fires on a fresh connection (empty handoff) or when a client hopped in from another shard (handoff is whatever they brought). event disconnect fires on any departure — clean leave, crash, or hop away. Maintain your per-client state in both.
Slots
The runtime assigns each connected client a slot in [0, max_clients()) — the lowest free index at connect time. Slots key parallel arrays to specific clients.
slot(c: client): i32 // server-only: a client's slot index
max_clients(): i32 // both roles: the shard's configured capacityThe standard per-player pattern: a client[] of handles for outbound send, an occupied[] of liveness flags, and parallel data arrays — all maintained in connect / disconnect:
server {
var clients: client[1024]
var occupied: bool[1024]
var playerX: u8[1024]
var playerY: u8[1024]
event connect(c: client, handoff: u8[]) {
var s = slot(c)
clients[s] = c
occupied[s] = true
playerX[s] = 0
playerY[s] = 0
}
event disconnect(c: client) {
occupied[slot(c)] = false
}
}Slot rules
- Slots are reused. A freed slot may go to the next joiner — clear stale data on
disconnect(or overwrite on the nextconnect). - Size arrays to a max, loop to
max_clients(). Array sizes are compile-time constants, so allocate generously; usemax_clients()for loop bounds so the script ports across differently-sized shards. - Pair handles with
occupied[]. Aclienthandle is valid only while its slot is occupied — callingslot()orsend()on a stale handle traps. Checkoccupied[i]before usingclients[i], like null-checking a pointer.
Server tick
event tick(tick_num: i32)
tick_rate(): i32 // current rate in Hz
set_tick_rate(hz: i32) // change it; takes effect immediatelyevent tick is the server heartbeat — there's no game on the server, so this is where periodic work lives (state fan-out, interest management, AI). It fires tick_rate() times per second; the default is 30 Hz. tick_num is monotonic from 0.
Fan out state from tick, not from receive — see the relay walkthrough.
event tick(tick_num: i32) {
for (var i = 0; i < max_clients(); i = i + 1) {
if !occupied[i] { continue }
// ...send each client what it needs...
}
}Moving clients between shards
A client can move to another shard, initiated from either side:
hop(url: string, handoff: u8[]) // client initiates
redirect(client: client, url: string, handoff: u8[]) // server pushes a clientBoth disconnect-and-reconnect the client to url, delivering handoff to the destination's event connect. If the destination runs the same script, local client state (variables, loaded images) persists and only the server-side partner changes; if it runs a different script, the new one is fetched and instantiated and only the handoff payload carries over.
hop is client-side; redirect is server-side.