# 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:

```sh
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](/docs/guides/state-sync#hidden-entities), 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 |

```sh
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](/docs/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:

```ts
// 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.
