Build and deploy
Dev tools
The irt dev inspector for rooms, connections, state, entities, recent messages and calls; the per-room tick profiler; and simulated latency, jitter and loss in the client.
Three tools help while a game runs under irt dev: an inspector that
shows what each room holds, a tick profiler that says where a room’s time
goes, and simulated network conditions in the client. All three are for
development. None of them changes what a deployed room or client does.
The inspector
irt dev prints the inspector’s address when it starts:
irt dev: http://127.0.0.1:7350 serves the site and room host; watching for changes
irt dev: inspect rooms at http://127.0.0.1:7350/v1/dev/
The page lists the running rooms and refreshes every second. For the room you pick it shows:
- its generation, status, tick and tick rate
- the tick profile of the last second
- connections, with player ID and token meta
room.state- the private state of every player the room keeps it for, connected or not
- entities, with kind, owner and values, including the entities of hidden kinds, marked hidden. No client is sent those; the inspector shows them because it’s for you
- the last 200 messages and calls from clients, newest first: kind, name, connection and player, tick, size in bytes, and the result or error
A message’s name is its type when it’s a JSON object with a string
type. A call’s result is the size of its reply, or the error the client
got. bytes fields show as base64.
The same data is JSON, for scripts and agents:
| Endpoint | Returns |
|---|---|
GET /v1/dev/rooms |
{ rooms: [{ project, id, generation, status, tick, connections }], stats }, stats being the room host’s last stats frame |
GET /v1/dev/rooms/{project}/{id} |
{ project, id, generation, status, tick, tickRate, connections, state, private, entities, trace, profile }, each entity being { id, kind, owner, hidden, values }; 404 when the room isn’t running |
curl -s http://127.0.0.1:7350/v1/dev/rooms/my-game/main | jq '.entities'
The inspector shows every player’s private state and every hidden
entity, so it’s only there under irt dev (or irt-room-host with
IRT_INSPECTOR=1). It sends no CORS headers and answers only requests
addressed to localhost, 127.0.0.1 or [::1], so another web page
can’t read it through your browser.
The tick profiler
The room host times each room’s code with performance.now():
| Phase | What it times |
|---|---|
tick |
A whole tick |
writes |
Applying owner writes, onWrite included, in ticks that had any |
stepper |
The stepper’s step |
onTick |
onTick |
flush |
Sending the state update, interest functions and compression included |
interest |
The part of flush that works out what each connection sees: interest keys, sees and priority |
onPlayerLeave |
Each run, at the start of a tick or before hibernation |
onJoin, onLeave, onMessage, call |
Each run of the hook or call handler, between ticks |
Each second it reports, per phase, the count, total, p50, p95 and max in
milliseconds. Percentiles come from the second’s last 256 samples; count,
total and max cover all of them. Async hooks and calls count only up to
their first await. Phases that didn’t run are left out.
Under irt dev the numbers are in the inspector. A tick p95 near the
tick interval (50 ms at 20 ticks a second) means the room can’t keep up,
and every room in the process shares that event loop. The profile is
measured inside the room host, so it’s a guide for your code, not what IRT
bills: room-hours are counted outside the VM.
Deployed room hosts measure the same phases. Each build’s average and
longest tick per minute are in the dashboard’s Stats tab, irt stats
and the HTTP API; see Stats. An active deployed room’s
own profile of its last second is irt rooms profile <id> and GET /projects/{p}/rooms/{id}/profile; it doesn’t wake a hibernated room.
Simulated network conditions
simulateNetwork from @irt/client/dev wraps the client’s transport so
a local game feels like it’s on a real network:
// web/main.ts
import { connect } from "@irt/client";
// Open the game with ?slow to try it at 150 ms with jitter and loss.
const dial = new URLSearchParams(location.search).has("slow")
? (await import("@irt/client/dev")).simulateNetwork({ rttMs: 150, jitterMs: 20, loss: 0.05 })
: undefined;
const room = await connect({ router: location.origin, project: "my-game", room: "main", dial });
| Condition | Meaning |
|---|---|
rttMs |
Round-trip time to add, half of it each way |
jitterMs |
Each one-way delay is up to this much above or below rttMs / 2, never below 0 |
loss |
Fraction of unreliable messages dropped each way, 0 to 1 |
Reliable messages, calls, state updates and owner writes are never
dropped; they’re delayed and arrive in order, as on a real stream.
Unreliable messages (send with { unreliable: true }), inputs and
pings can be dropped and can overtake each other, as datagrams do.
It refuses a router that isn’t localhost, 127.0.0.1 or ::1, so
connect rejects with an IrtError saying so; pass
{ allowRemote: true } as the second argument to use it against another
router on purpose. Importing it dynamically, as above, keeps it out of the
game’s main bundle; it isn’t part of @irt/client’s core entry.
The second argument also takes dial, the transport to wrap, and clock
and random, which tests use to make delays and loss deterministic.