Your game, your code,
in real time.

Multiplayer hosting for web games. Write your rooms in TypeScript, ship them with your site in one deploy, and pay nothing for a room while nobody is in it.

Request an invite$npx @irt/cli init my-game

Invite-only while we build it.

activewakinghibernatedEach square is a room. Most rooms, most of the time, have nobody in them.

Multiplayer hosting on demand.

Every room is a small server that runs your code, on Node in a Firecracker VM that belongs to you alone. It wakes when a player connects and hibernates when the last one leaves.

terminal
$ npx @irt/cli deploy
site https://dots.irt-serve.com
rooms wss://eu.irt.io
running rooms move on their next hibernate

One command deploy

irt deploy bundles your room code and your site into one build and makes it live. The site and the rooms switch together, so a client always finds the room build it was built against. The dashboard can make an earlier build live again.

Rooms start when players join

A room hibernates 30 seconds after its last connection leaves, and the next connection wakes it with its state exactly as it was. Your code writes no persistence logic for this.

Nothing idle holds a process

Rooms share room hosts, room hosts share a VM, and a VM with no rooms stops after five minutes.

Read how rooms hibernate and wake →

Billed while awake, free while asleep.

Room-hours run from the start of a wake until the room has hibernated, counted outside your VM. In between, the room costs nothing for as long as it sleeps.

  1. 1

    Active

    Players are connected and the room runs its ticks. This is the time you pay for.

  2. 2

    Hibernating

    The last player left 30 seconds ago. Room state already streams out of the VM as it changes, so hibernating is a flush, not a dump.

  3. 3

    Hibernated

    The room's state sits in object storage. No process, no room-hours.

  4. 4

    Waking

    A player connects and the router holds them. Everyone held joins at once when the room is back.

Write multiplayer like the rest of your code.

Same language, same types, one project. The client imports the schema from the same module as the room, so a site and its room build always agree.

Declare the room

Typed entities, and handlers for joins, messages and leaves. Each tick, every changed entity is encoded once as a binary delta, however many clients there are.

Room SDK →
shared.ts and room.ts
// shared.ts, imported by the room and the client
import { defineSchema, entity } from "@irt/room";

export const Player = entity("player", {
  owner: "string", name: "string",
  x: "f32", z: "f32", hp: "u8",
});
export const schema = defineSchema({ entities: [Player] });

// room.ts
import { defineRoom, grid } from "@irt/room";
import { Player, schema } from "./shared.js";

export default defineRoom({
  schema,
  interest: grid({ cellSize: 32, radius: 2, dims: "xz", around: (c) => c.data.me }),
  onJoin(room, conn) {
    const { name } = conn.meta as { name: string };
    conn.data.me = room.entities.create(Player, { owner: conn.player, name, hp: 100 });
  },
  onMessage(room, conn, msg) {
    const m = msg as { type?: string; text?: string };
    if (m.type === "chat") room.broadcast({ type: "chat", text: String(m.text), from: conn.player });
  },
  onLeave(room, conn) {
    room.entities.delete(conn.data.me);
  },
});

Join from the client

Connect, interpolate remote entities and send messages. A player who connects to a hibernated room sees it wake and then gets its state.

Client SDK →
client.ts
// client.ts
import { connect } from "@irt/client";
import { Player, schema } from "./shared.js";

const room = await connect({
  router: "wss://eu.irt.io", project: "my-game", room: "forest-7", schema,
  onWaking: () => banner.show("Waking the room"),
});
room.on("restored", () => banner.hide());

const drawn = room.interpolate(Player, ["x", "z"]);
room.entities.onEnter(Player, (p) => scene.add(p.id, p.name));
room.entities.onLeave(Player, (p) => scene.remove(p.id));
scene.onFrame(() => {
  for (const p of room.entities.all(Player)) scene.move(p.id, drawn.get(p));
});

chat.onsubmit = (text: string) => room.send({ type: "chat", text });

Send each player what they can see

Grid cells for open worlds; teams, zones or dungeon levels when space doesn't split that way. Cap each connection to its closest entities, and patch voxel chunks by range.

State sync →
interest management
interest: {
  ...grid({ cellSize: 32, radius: 2, dims: "xz", around: (c) => c.data.me }),
  limit: { max: 64, priority: (c, e) => -distance(c.data.me, e) },
},

// A block edit sends a few bytes, not the chunk.
chunk.patch("blocks", offset, new Uint8Array([STONE]));

Predict the local player

The client runs the room's own step function, so the local player moves at once. When the room's answer arrives, the client replays the inputs it hasn't confirmed.

Prediction →
stepper
export const stepper = defineStepper<Input>({
  step(world, inputs, dt) {
    for (const p of world.all(Player)) {
      const input = inputs.get(p.owner);
      if (input) move(p, input, dt);
    }
  },
});

// On the client: sampled once per room tick, replayed on correction.
const me = room.predict(stepper, { find: mine, sample: readKeys });

And the hard parts, handled.

Client-side prediction

Tick clock sync, inputs tagged with the tick they are for, and unreliable messages as WebTransport datagrams with a WebSocket fallback.

Crashes and reconnection

A crashed room host restarts its rooms with nothing lost. A room whose host fails restarts on another host, and the router moves its held connections there.

Proximity voice

Each listener hears the speakers it can see, closest first. A speaker's audio is permitted only to its listeners, so a modified client can't overhear anyone.

Sites

Your web client is served at <project>.irt-serve.com. Cross-origin isolation, forSharedArrayBuffer, is one flag.

Regions

Players connect to a router in the same region as their room, and router-to-room-host traffic never crosses regions.

A backend you may not need

Guests, client-created rooms, join codes and public room lists are project settings. When you do have a backend, there's an HTTP API and signed webhooks.

Numbers you can check.

Measured in our spikes. We'll publish production figures once rooms are serving players.

~35 msrestore a VM

For a VM to restore from its base snapshot and answer, in our Firecracker spike.

µswake a room

Inside a room host that's already running.

30 sto hibernate

After the last player leaves, a room hibernates on its own.

0room-hours asleep

Billed while a room hibernates, however long it sleeps.

Verifiably correct.

@irt/testing runs rooms in process with fake clients and a controlled clock. Catch the bugs multiplayer hides before a build goes live.

With coding agents working on their own, being able to check multiplayer logic matters. A test suite lets an agent check its work before it calls it done, and the output is written for an LLM to read.

See how testing works →
room.test.ts
  • advances ticks on a controlled clock
  • hibernates and wakes the room
  • crashes the room host and restarts it
  • asserts on messages and room state

Your agent already speaks IRT.

Multiplayer is hard for agents: lots of moving parts that aren't normally testable. IRT gives your agent the docs and the tools to get it right without a human in the loop.

Try it yourself:

Read https://irt.io/llms.txt, then write a plan to add multiplayer to this game with IRT.

Docs written for agents

The docs are published as llms.txt, as one file atllms-full.txt, and every page as Markdown.

Verified without a human

Agents can't open two browsers and click, but they can run fake clients against a room and read the result.

Examples that compile

Every TypeScript example in the docs and on this page is compiled against the real packages, so the code your agent copies is the code that works.

No vendor lock-in.

We aim to be the best place to host your game, but you can run it wherever you like.

Your code is yours

Room code is ordinary TypeScript on Node, and the client SDKs are open source.

Host it yourself

The room host is open source and runs on its own: it accepts WebSocket connections directly and has pluggable storage.

Same code both ways

The client SDKs work against a self-hosted room host as well as against our router.

One dashboard for your live game.

  • Builds. Every build you've deployed, and the one that's live. Make an earlier one live again.
  • Rooms. The rooms running now. Read a room's state, which wakes it if it's hibernated.
  • Logs. Room host output from the last 15 minutes, then live. Also in your terminal with irt logs.
  • Usage. Room-hours against spending limits per day, week and month.
Open the dashboard →

Dead-simple pricing.

Pay for usage from prepaid credit, with no monthly fee yet. Usage prices will be published before launch.

Room-hours

One room awake for an hour, however many players are in it. Priced by room size: relay, standard, large or extra large.

Traffic

Counted by the router, outside your VMs.

Voice-minutes

Only if you use voice chat.

Room state

What your hibernated rooms keep in object storage.

No CCUs. Billed only on what we can measure.

Instead of per-player pricing, every room is a tiny server and you pay for the time it's awake. Code inside a VM could report anything, so everything we bill is counted outside it.

Every project has spending limits per day, week and month, so a bug that creates rooms in a loop can't run up a bill.

Prepaid credit, topped up by card.

Usage is paid from credit you buy in advance through Stripe, so there's no surprise invoice. Top up $10, $25 or $50, or any whole amount from $5 to $500; tax is added at checkout where it applies. Every invite comes with $10 of credit to start.

If your credit runs out, your projects keep running for 24 hours. After that they stop waking rooms and running rooms hibernate until you top up. Running out never deletes a room.

Free while you try it, by invite

We're inviting web game developers in small groups, and may give you credit to try IRT. Free projects run one room at a time with fewer connections.

Your game, your code, in real time.

Tell us what you're building. Email hello@irt.io with your game and your GitHub username.