Room configuration
defineRoom(schema, config) defines a room type. Export its result as the default export of irtio/room.ts, or a file in irtio/rooms/.
function defineRoom<S extends AnySchema>(schema: S, config: RoomConfig<S>): RoomDefinition<S>; Config properties
All properties below belong to RoomConfig. Times are milliseconds unless a row specifies seconds or ticks. Callbacks receive the server runtime objects.
| Property | Type or signature | Required / default | Description |
|---|---|---|---|
mode | 'tick' or 'event' | 'tick' | Fixed-rate simulation or input-driven updates |
tickRate | number | 20 | Integer from 1 to 240, in ticks per second; tick mode only |
idleMs | number | 30000 | Quiet period before hibernation; 0 disables it |
reconnectGraceMs | number | 30000 | Time to hold a disconnected client’s session |
maxClients | number | 64 | Maximum connected clients; excess joins are refused |
class | 'small', 'medium', 'large' | Small billing class when omitted | Declares the room size; see sizing |
retention | string | Kept indefinitely | Duration such as '10m', '6h', or '30d'; see retention |
backfill | boolean | false | Allow matchmaking to offer open seats in a running room |
physics | PhysicsConfig<S> | None | Physics configuration, tick mode only |
replay | ReplayConfig | None | Recording configuration |
lobby | LobbyConfig | None | Ready/start policy; requires lobbyCollections in the schema |
bus | BusConfig<S> | None | Messages between rooms |
npcs | Record<string, NpcScript<S>> | None | Named NPC scripts |
rpc | { [name]: (state, params, ctx) => result } | Required for declared server RPCs | Implements every server RPC in the schema |
validate | { [collection]: (prev, next, ctx) => instance } | No validators | Accept, reject, or clamp writes to client-owned entities |
alarms | { [name]: (state, room) => void } | No handlers | Handles durable alarms |
onCreate | (state, room) => void | Optional | Runs on first creation |
onJoin | (state, ctx) => void | Optional | Runs on each join, including reconnection |
onLeave | (state, ctx, reason) => void | Optional | Runs on departure; reasons are left, timeout, kicked, closed |
onSleep | (state, room) => void | Optional | Runs before saving for hibernation |
onWake | (state, room) => void | Optional | Runs after restoring state |
tick | (state, dt, room) => void | Required in tick mode | Runs after queued input; dt is seconds. Omit in event mode |
onOwnershipRequest | (state, entity, id, ctx) => boolean | Schema policy or grant if unowned | Decides ownership requests; see handler behavior |
onMessage | (state, from, target, bytes, ctx, typed?) => boolean or void | Relay messages | Return false to drop a peer message |
onChat | (state, line, ctx) => boolean or void | Accept chat | line contains from, text, target; false or a throw drops it |
onPlatformNotice | (state, notice, room) => boolean or void | irtio tells the players | Runs when irtio warns a room about its server. notice.phase is warning, recovered, or closing. Return false to send the news yourself; a throw does not stop it. See servers and room placement |
Modes and lifecycle
Event rooms hibernate after idleMs without incoming frames, even with clients connected. Tick rooms hibernate after idleMs without connected clients. Keep state that must survive a wake in the schema, and guard player creation with if (ctx.reconnecting) return.
defineRoom checks options when the module loads. It rejects invalid ranges, incompatible physics declarations, missing RPC implementations, and async room handlers. See lifecycle and return behavior.
Sizing
class | Declared room memory |
|---|---|
'small' | 64 MB |
'medium' | 256 MB |
'large' | 1 GB |
Declare a class to set a predictable ceiling. Without an explicit class, the room is billed as Small and its heap ceiling is derived from the server environment. See capacity for load testing and limits for server room budgets.
Retention
retention accepts one integer followed by m, h, or d, from '1m' to '3650d'. Expiry deletes room snapshots, saves, and alarms; player storage and leaderboard scores remain. Expiration is checked periodically after the room sleeps, so the duration is a minimum. See room retention.
Physics
| Property | Type | Required / default | Description |
|---|---|---|---|
engine | 'rapier3d', 'rapier2d', 'matter2d' | Required | Selects the engine and handle types |
gravity | { x, y, z } for 3D; { x, y } for 2D | Required | Gravity in the engine’s units |
timestep | number | 1 / tickRate | Seconds per step |
setup | (world, engineModule, room) => void | Optional | Builds geometry and tunes the world; Matter receives its Engine as the first argument |
bodies | Map of body factories | Required | One (engineModule, instance, id) => factoryResult per physics collection |
intents | Map of steering functions | Optional; 2D engines only | Shared hooks; call them from your room’s tick |
history | Number or { depth, channels? } | 0 | Recorded pose history in ticks, from 0 to 240 |
Rapier body factories return { body, colliders? } using body and collider descriptors. Matter factories return { body, constraints? } using a Matter body. Engine-specific examples: 3D Rapier, 2D Rapier, Matter.
History options
| Property | Type | Required / default | Description |
|---|---|---|---|
depth | number | Required in object form | Number of ticks, 0–240 |
channels | Map keyed by physics collection | None | Extra values captured with each pose |
channels.<collection>.count | number | Required | 1–8 floats per instance |
channels.<collection>.read | (instance, out, id) => void | Required | Fills the supplied Float32Array |
See lag compensation for a shot query using this history.
Replay
| Property | Type | Required / default | Description |
|---|---|---|---|
seconds | number | 60 | Recording buffer duration, up to 300 seconds; also bounded by bytes |
view | Role name | All state | Restricts recording to that role’s collections; spatial collections are recorded whole |
record | boolean | false | Stores the whole match as the buffer advances |
ttl | Duration string | '30d' | Clip lifetime, '1m' to '3650d' |
A clip is accessible to anyone holding its id. Choose view carefully for games with hidden information. See replays.
Lobby
| Property | Type | Required / default | Description |
|---|---|---|---|
start | 'when-full', 'when-ready', 'manual' | 'when-full' | Start policy |
min | number | 2 | Minimum players for when-ready |
size | number | maxClients | Players needed for when-full, capped at maxClients |
queue | string | 'default' | Public queue, unless the room was created under another public queue |
onStart | (state, room) => void | Optional | Runs when the lobby starts |
onSetPublic | (state, value) => boolean or void | Accept | Return false or throw to refuse a public toggle |
See the lobby panel for the shared schema and ready controls.
Bus
| Property | Signature | Default | Description |
|---|---|---|---|
channels.<name> | (state, event, room) => void | No subscription | Subscribes at start and wake; receives published events |
onMessage | (state, message, room) => void | Drop with a warning | Receives directed messages; make this handler idempotent |
Directed delivery is at least once. See messages between rooms for delivery rules and payloads.
NPCs
npcs maps script names to (npc) => void or Promise<void>. Scripts use npc.room, npc.wait(ms), and npc.stopped to control a simulated player. Spawn one with room.spawnNPC({ brain: { kind: 'script', script: 'name' } }). See scripted NPCs.
Example
import { defineRoom } from '@irtio/server';
import { schema } from './schema';
export default defineRoom(schema, {
mode: 'tick',
tickRate: 20,
class: 'small',
maxClients: 16,
tick(state, dt, room) {
// Update your simulation here.
},
}); This excerpt uses a schema without server RPCs. The quickstart includes a complete project with player spawning.