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.

PropertyType or signatureRequired / defaultDescription
mode'tick' or 'event''tick'Fixed-rate simulation or input-driven updates
tickRatenumber20Integer from 1 to 240, in ticks per second; tick mode only
idleMsnumber30000Quiet period before hibernation; 0 disables it
reconnectGraceMsnumber30000Time to hold a disconnected client’s session
maxClientsnumber64Maximum connected clients; excess joins are refused
class'small', 'medium', 'large'Small billing class when omittedDeclares the room size; see sizing
retentionstringKept indefinitelyDuration such as '10m', '6h', or '30d'; see retention
backfillbooleanfalseAllow matchmaking to offer open seats in a running room
physicsPhysicsConfig<S>NonePhysics configuration, tick mode only
replayReplayConfigNoneRecording configuration
lobbyLobbyConfigNoneReady/start policy; requires lobbyCollections in the schema
busBusConfig<S>NoneMessages between rooms
npcsRecord<string, NpcScript<S>>NoneNamed NPC scripts
rpc{ [name]: (state, params, ctx) => result }Required for declared server RPCsImplements every server RPC in the schema
validate{ [collection]: (prev, next, ctx) => instance }No validatorsAccept, reject, or clamp writes to client-owned entities
alarms{ [name]: (state, room) => void }No handlersHandles durable alarms
onCreate(state, room) => voidOptionalRuns on first creation
onJoin(state, ctx) => voidOptionalRuns on each join, including reconnection
onLeave(state, ctx, reason) => voidOptionalRuns on departure; reasons are left, timeout, kicked, closed
onSleep(state, room) => voidOptionalRuns before saving for hibernation
onWake(state, room) => voidOptionalRuns after restoring state
tick(state, dt, room) => voidRequired in tick modeRuns after queued input; dt is seconds. Omit in event mode
onOwnershipRequest(state, entity, id, ctx) => booleanSchema policy or grant if unownedDecides ownership requests; see handler behavior
onMessage(state, from, target, bytes, ctx, typed?) => boolean or voidRelay messagesReturn false to drop a peer message
onChat(state, line, ctx) => boolean or voidAccept chatline contains from, text, target; false or a throw drops it
onPlatformNotice(state, notice, room) => boolean or voidirtio tells the playersRuns 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

classDeclared 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

PropertyTypeRequired / defaultDescription
engine'rapier3d', 'rapier2d', 'matter2d'RequiredSelects the engine and handle types
gravity{ x, y, z } for 3D; { x, y } for 2DRequiredGravity in the engine’s units
timestepnumber1 / tickRateSeconds per step
setup(world, engineModule, room) => voidOptionalBuilds geometry and tunes the world; Matter receives its Engine as the first argument
bodiesMap of body factoriesRequiredOne (engineModule, instance, id) => factoryResult per physics collection
intentsMap of steering functionsOptional; 2D engines onlyShared hooks; call them from your room’s tick
historyNumber or { depth, channels? }0Recorded 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

PropertyTypeRequired / defaultDescription
depthnumberRequired in object formNumber of ticks, 0–240
channelsMap keyed by physics collectionNoneExtra values captured with each pose
channels.<collection>.countnumberRequired1–8 floats per instance
channels.<collection>.read(instance, out, id) => voidRequiredFills the supplied Float32Array

See lag compensation for a shot query using this history.

Replay

PropertyTypeRequired / defaultDescription
secondsnumber60Recording buffer duration, up to 300 seconds; also bounded by bytes
viewRole nameAll stateRestricts recording to that role’s collections; spatial collections are recorded whole
recordbooleanfalseStores the whole match as the buffer advances
ttlDuration 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

PropertyTypeRequired / defaultDescription
start'when-full', 'when-ready', 'manual''when-full'Start policy
minnumber2Minimum players for when-ready
sizenumbermaxClientsPlayers needed for when-full, capped at maxClients
queuestring'default'Public queue, unless the room was created under another public queue
onStart(state, room) => voidOptionalRuns when the lobby starts
onSetPublic(state, value) => boolean or voidAcceptReturn false or throw to refuse a public toggle

See the lobby panel for the shared schema and ready controls.

Bus

PropertySignatureDefaultDescription
channels.<name>(state, event, room) => voidNo subscriptionSubscribes at start and wake; receives published events
onMessage(state, message, room) => voidDrop with a warningReceives 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.