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.

View this page as Markdown