# The Midgarth protocol

This is how to write a client for Midgarth: a bot, a terminal client, a
different user interface. Every client plays over the same websocket the
browser uses, speaks the same messages, and is held to the same rules and the
same limits. The server does not know or care what is at the other end of a
socket: there are no bot accounts, no bot detection, and no special cases.

Everything here is checked against the code. The client and server message
lists are tested against the server's own command switch and the types in
`internal/proto` (`TestEveryMessageIsDocumented`), the reference section at
the end is generated from `internal/proto/proto.go` (`TestProtocolDoc`), and
the tile table is generated from `internal/game/map.go`
(`TestProtocolDocTiles`).

`cmd/exampleclient` is a small, readable client that logs in, walks out of the
temple, attacks a monster, picks up loot, says something and logs out. It is
built on `internal/botclient`, a Go library that keeps a view of the world
from the messages and paces what it sends. Start from either.

```sh
go run ./cmd/exampleclient -server http://localhost:8080 -name Example -password 'a good password'
```

## Contents

- [Connecting](#connecting)
- [Logging in and the protocol version](#logging-in-and-the-protocol-version)
- [Limits](#limits)
- [Ordering and timing](#ordering-and-timing)
- [Coordinates, levels and the map](#coordinates-levels-and-the-map)
- [Walking](#walking)
- [Client to server](#client-to-server)
- [Server to client](#server-to-client)
- [Tiles](#tiles)
- [Reference](#reference)

## Connecting

Open a websocket to `/ws` on the server: `wss://host/ws` for a server behind
HTTPS, `ws://localhost:8080/ws` for a local one. Every message in both
directions is one websocket text frame holding one JSON object, and every
object has a `"t"` field that says what it is.

- The server offers per-message deflate. Take it if your library can: the
  welcome is the only message that is ever compressed (one of 16 KB or more,
  which for a character who has explored a lot is the whole map they have
  seen, up to about 300 KB uncompressed). Set your read limit to several
  megabytes for the same reason.
- The server sends a websocket ping every 25 seconds and closes a socket that
  has not answered one for 60 seconds. Browsers and most libraries answer
  pings by themselves, but only while something is reading the socket: keep
  reading all the time.
- The server queues up to 256 messages for a client. A client that stops
  reading long enough for that to fill is disconnected.
- `GET /status` answers JSON about the server without logging in, including
  `"proto"`, the protocol version it speaks (see below), `"online"`,
  `"players"` (their names), `"bots"` (those of the players logged in with
  `"bot": true`, and the server's own bots) and `"build"`, the browser
  bundle's id, and `"commit"`, the git commit the server was built from
  (empty when the build did not say).

## Logging in and the protocol version

The first message is a login:

```json
{"t":"login","name":"Example","password":"a good password","proto":2}
```

A name that has no character yet makes a new one with that password. A
login may say which it means with `"mode"`: `"login"` comes in only as a
character already made, and is refused with an `error` for a name nobody
has; `"new"` only makes a new character, and is refused for a name that is
taken. The login page's Log in and New character tabs send these. Left out,
a login does either, as it always has. A name is a single word of 2 to 20 letters starting with a
capital. Logging in to a character that is already online puts the older
connection out with a `bye`.

The answer is a `welcome`, followed by a `state` (or a `chooserace`, for a
character with no race yet: see below), or an `error` saying why not, after which you may try again on the same socket. Before a login the
server answers only `login` and `ping`; anything else gets
`{"t":"error","text":"not logged in"}`.

**The protocol version.** The server speaks protocol version `proto` (now 2;
`proto.Version` in Go, `PROTOCOL_VERSION` in `web/src/net.ts`). It says so in
the welcome's `"proto"` and in `/status`. A client says which version it
speaks with the login's `"proto"`:

- the same version: the login goes ahead;
- another version: refused with an `error` that names both, for example
  `This server speaks protocol version 3 and your client speaks version 2.`;
- no version at all: on a deployed server, refused with an `error` saying to
  send `"proto"`. (A development server with no browser build lets it in.)

The version goes up when a change would break a client written against the
old protocol: a message or field removed or renamed, or one whose meaning
changes. Adding a message or a field does not change it, so a client must
ignore messages and fields it does not know.

**`build` is for the browser only.** The browser also sends `"build"`, the id
of its own bundle, so that a tab holding an out-of-date page is told to
reload. Leave it out: a build that does not match the server's is refused with
`Your game client is out of date...`, and the build changes with every deploy.

**A new character** has no race yet, and is not put in the world until it
has one: after the welcome it is sent a `chooserace` instead of a `state`, and
should answer with `{"t":"race","text":"hobbit"}` (`elf`, `man`, `dwarf` or
`hobbit`). Until then it stands nowhere, nobody sees it, and the server
ignores everything from it but `race` and `logout`; the choice brings it in,
and its first `state` follows. A character saved before there were races is
held the same way. (Protocol version 2, 2026-10-08. A character that already
has a race, or that typed `/race` to choose again, comes straight in, and is
asked in the world: `you.chooserace` in the state.)

**Leaving.** `{"t":"logout"}` is answered with `{"t":"bye"}` and the server
closes the socket. It is refused, with a `text` of kind `error` saying why,
within 10 seconds of a fight (unless you are on protected ground) or while
wearing a skull. Just closing the socket takes the character out of the world
at once, except under a skull: then they stay standing where they are until it
runs out.

## Limits

These limits are the same for every connection, browser or not. They are set
so that the browser can never reach them, and so that a program loses nothing
by keeping under them: the game paces every step, swing and spell on its own
clock, so a message sent early buys nothing.

| Limit | Value | What happens past it |
|---|---|---|
| Messages per connection | 40 a second, refilled continuously, with room for 200 at once | Each message over is dropped. The connection is sent `{"t":"error","text":"Slow down: ..."}`, at most once a second, and stays open. |
| Flooding | 800 messages dropped within 10 seconds | The connection is sent `{"t":"bye","text":"Too many messages: ..."}` and closed. That is three times the rate for ten seconds while being told to slow down, or about a thousand in one second. |
| Message size | 16 KB | The connection is closed (close code 1009). A 1000-step route, the longest there is, is about 13 KB. |
| Chat line | 200 characters | Longer speech is cut to 200; a longer `/command` is refused. |
| `walk` | 40 tiles (the larger of the two axes) | Refused with a `text` saying so. |
| `route` | 1000 steps | Refused with a `text` saying so. |

Every message counts, a `ping` included. The limits are in
`internal/server/limit.go`.

**What the browser sends**, read off its own timers in `web/src/main.ts`,
which is what the numbers above were chosen against:

| Source | Rate |
|---|---|
| A held direction key, or the touch stick held | one `move` every 90 ms: 11 a second |
| A new direction (a key pressed, the stick turned) | at once, but never sooner than 50 ms after the last `move`: at most 20 a second, however fast the keys or a thumb go |
| `ping` | one every 2 seconds |
| A click, tap or key: `attack`, `look`, `pickup`, `route`, a spell button's `say`, a line of chat, a panel's button | one message each: a very fast hand manages 10 to 12 a second, briefly |

Ordinary walking and fighting is about 12 messages a second. The most the
browser can send is about 35 a second, with the stick turning every 50 ms and
the other hand clicking as fast as it can, which is still under 40, and the
200 burst covers five seconds of that on top. `TestABrowsersBusiestPlayPasses`
replays a minute of it, with a spell button mashed 25 times in one second on
top, and none of it is dropped. `TestABrowsersBurstPassesOverTheWire` sends
five seconds of it over a real socket as fast as the socket takes it.

**Pace your own client** at the rate it needs, not the limit. `botclient`
sends at most four messages a second by default (`Conn.MinGap`), which is
plenty for anything but holding a direction.

## Ordering and timing

The world runs on one goroutine, ticking every 100 ms. Messages from one
connection are handled in the order they were sent, between ticks.

- **`welcome`** comes once per login, first. It carries what does not change:
  your creature's id, the map you have explored, item definitions, the
  temple, the protected zones, the world's NPCs.
- **`chooserace`** follows the welcome instead of a state for a character
  with no race, and nothing else comes until a `race` has chosen one.
- **`state`** follows the welcome at once (or the race, after a
  `chooserace`), and then comes on the world's tick
  whenever anything in it changed (and straight away after a few things, such
  as choosing a race): about ten a second at most. It is the whole view (you,
  every creature and ground item in sight), not a difference, so a client can
  replace what it had. Its `now` is the server's clock in Unix milliseconds.
- **`seen`** brings tiles you are seeing for the first time, and is always
  sent before the `state` that shows them. Add its tiles to your map.
- **`steps`** reports every step in sight the moment it is taken, between
  states, with the server time `at` it began and how long it lasts (`ms`).
  The next state agrees with it. A client that only wants positions can
  ignore steps and read the states.
- **`text`** and **`effect`** come when things are said and done. A `text` of
  kind `error` is the answer to something you asked that was refused, or
  could not be done ("There is no way.").
- **`pong`** answers `ping` with its `seq`, after going through the same
  queue as every other message: it measures how long the world takes to hear
  you.
- **`error`** is a refusal of a login or a malformed message, and the limits'
  word to slow down.
- **`bye`** is always the last message before the server closes the socket.

## Coordinates, levels and the map

- **x** grows east and **y** grows south; `(0, 0)` is the north-west corner.
  The map is `map.w` by `map.h` tiles (the welcome's `map`). North is `y - 1`.
- **Distance** is the larger of the two axes (Chebyshev): a diagonal
  neighbour is one tile away. Melee reaches one tile, including diagonals.
  The view is 10 tiles each way.
- **Directions** are `n`, `ne`, `e`, `se`, `s`, `sw`, `w`, `nw`.
- **Levels** (`z`): `0` is the ground, `1` is above it (rooftops, hilltops,
  ramparts), and negative levels are under it (`-1`, `-2`, ...: cellars,
  mines, caves). `z` is omitted from many objects when it is `0`.
- **The ground** comes in the welcome as `map.runs`: base64 of run-length
  pairs, a byte for the tile and then the run's length as an unsigned
  varint (seven bits a byte, low bits first, the top bit set on every byte
  but the last), row-major, a run carrying on from one row into the next.
  Tile `255` is ground you have not seen. `botclient.DecodeRuns` decodes it.
- **Other levels** come in the welcome's `levels` and in every `seen`: per
  level, `tiles` is a flat list of three numbers a tile, `x, y, tile`. A
  place on a level other than the ground with no tile is air above the
  ground or rock below it. Every level that exists is listed in the welcome,
  even with no tiles, so a client knows which floors there are.
- **`map`** replaces the whole map you had, ground and levels (an admin's
  `/fullmap` and `/nomap`).
- **Protected zones**: `pz` (rectangles on the ground and the sky above) and
  `pzbelow` (rectangles on one level each, that level only; most are
  underground, but one is on the ground). No fighting there,
  and no monster enters.

## Walking

A character is in one of two states: standing still, or in a step. A step
takes a time; when it ends, the character may take the next one.

### How long a step takes

- The character's pace is the state's `step` on their own creature: the
  milliseconds an ordinary step takes over grass. It is `100000 / speed`,
  where speed is 150 at level 1 and 5 more for every level after (10 less
  for a dwarf), and changes with spells such as haste: about 667 ms a step
  at level 1, 500 at level 11.
- That is multiplied by the ground stepped onto (the tile table's *speed*:
  1.8 for swamp, 0.7 for a road, and so on; snow is grass to an elf).
- A **diagonal** step costs 2.5 times as much as a straight one. That is
  deliberate: a diagonal is never quicker than the two straight steps it
  replaces, so it is only worth taking where it is needed. There is no
  corner rule: a diagonal may pass between two tiles that cannot be stood
  on.
- A step **through a passage** (stairs, ramps, holes, ladders) costs one
  ordinary step more, added: 2 for a straight one, 3.5 for a diagonal.
- Every creature's state carries `move`, the length of the step it has just
  taken, all of that counted in, and `steps` carries the same as `ms`.

### `move`: one step at a time

`{"t":"move","dir":"n"}` asks for one step. It cancels any walk, route or
chase in progress.

- **Standing still**, it is taken at once.
- **In the middle of a step**, it is held until the step ends and then taken.
  Only one is held: a newer `move` replaces it. A held move older than
  150 ms when the step ends is dropped, so a released key never walks a
  tile that was not asked for.
- So **to walk continuously**, send the direction again at least every
  150 ms while you want to keep going. The browser sends it every 90 ms.
  Sending moves faster than that gains nothing: they replace each other,
  and the step comes when the last one ends.
- A move into something that cannot be walked into turns the character to
  face it and is not taken. A player standing there is pushed aside when
  their own next step comes; a monster smaller than you is pushed aside one
  tile, if it has somewhere to go. Otherwise a `text` of kind `blocked` may
  say what is in the way (at most once a second).

`{"t":"turn","dir":"e"}` faces a way without stepping.

### Changing level

The tiles that change level are **passages**: stairs, ramps, slopes, holes
and ladders, and their heads on the other level (see the tile table). Stepping
onto a passage tile is going through it: the character comes out on the other
level, at the place the table gives, in the same step, and never stands on the
passage itself. So a step onto the stairs at `(x, y)` on the ground lands on
`(x, y-1)` one level up. A ramp goes up onto whichever side has the mountain
(the side that is rock on its own level, with ground on the level above), and
its head comes down off the other end. If somebody stands where a passage
lets out, they are pushed aside. Monsters never use passages.

### `walk`: the server finds the way

`{"t":"walk","x":120,"y":80}` walks to a tile on your own level, up to 40
tiles away, by the server's own path finder, which may go by other levels on
the way. If the tile cannot be stood on (a creature, a wall, a chest), the
walk ends beside it. "There is no way." if there is none. It is the simplest
way to get somewhere near, and what `cmd/exampleclient` uses.

### `route`: the client finds the way

For anything further, plan the route yourself and send it, as the browser
does for every click on the map (`web/src/pathfind.ts`, up to 300 tiles off):

```json
{"t":"route","steps":[101,80,0, 102,80,0, 102,79,1]}
```

`steps` is three numbers a place, `x, y, z`, every place to stand on in order,
at most 1000 of them. The server checks the route and then walks it a step at
a time exactly as it walks anything:

- Each place must be one step from the one before: a neighbour on the same
  level, or the place a passage beside it lets out on (so a climb is written
  as the landing, never as the stairs tile). A route that jumps is refused
  whole with "There is no way."
- The first place should be one step from where you stand. If you have moved
  since you planned it and are standing on one of its first 12 places, the
  walk goes on from there; otherwise a short search joins the nearest of
  them. If none can be joined, "There is no way."
- A creature standing on the route is gone round by a short search that
  rejoins it further on.
- Ground you have never seen (tile 255 on level 0) may be planned over as
  open ground. When a step there turns out not to be one, the server plans
  the rest of the way to the route's end from where you stand, and says
  "You have lost the way. Click again from here to go on." if it cannot.
- Nothing about the ground is taken on trust: a step onto something that
  cannot be stood on ends the walk, as a key would.

To plan routes as the server walks them, search (A* or Dijkstra) over the
tiles you know, eight directions, with these costs (`terrainCost` and
`levelChangeCost` in `internal/game/map.go`, ported in `web/src/pathfind.ts`):

- a straight step costs `2 * u` and a diagonal `5 * u`, where `u` is the
  speed of the tile stepped onto times 20, rounded: 40 and 100 onto grass,
  28 and 70 onto a road, 72 and 180 into a swamp. Every cost is in proportion
  to the time the step takes, so the cheapest route is the quickest;
- a step that goes through a passage costs 400 more (ten straight steps over
  grass): far more than the clock charges, so that a route does not dive
  down a hole and up a ladder to cut a corner;
- tiles that are not walkable, tiles with a blocking item on them
  (`blocking` in the welcome's item definitions), and tiles with a creature on
  them are shut; a skulled character may not enter protected ground.

### What else moves you

- `{"t":"attack","id":N}` on a monster fights it, and walks after it too
  while chasing is on (`chaseon` in the state; it is on unless turned off).
  The same monster picked a second time is let go, so pick it once (unless
  a step of your own has dropped the chase: then the second pick takes the
  chase up again). `"id":0`
  lets go. Picking a player you may not fight follows them.
  `{"t":"chase","on":false}` turns chasing off: a pick is then fought from
  where you stand.
- `{"t":"pickup","x":X,"y":Y}` from more than a tile away walks beside the
  tile first, and picks up when it gets there.
- `{"t":"approach","id":N}` walks up to within three tiles of an NPC.
- Every one of these, and a `move`, a `walk` or a `route`, replaces whatever
  walk was in progress.

## Client to server

Every message is one object with `"t"` and only the fields that message
uses; anything else is ignored. A field the server does not find is zero.
Messages that name an item in your bag send its `id`, and its `owner` when it
has one (a skull); the browser also sends `slot`, which the server does not
need. The fields are those of `ClientMsg` in the [reference](#clientmsg).

A dead character may only `say`, `respawn` and choose a `race`; everything
else is ignored until they stand again.

### `login`

`name`, `password`, `proto`: see [Logging in](#logging-in-and-the-protocol-version).
`build` is the browser's alone.

### `ping`

`seq`: any number, echoed in the `pong`. Answered before or after login.

### `logout`

Leave the world: answered with `bye`, or refused with a `text` of kind
`error` within 10 seconds of a fight (off protected ground) or under a skull.

### `race`

`text`: `elf`, `man`, `dwarf` or `hobbit`. Once only: after a `chooserace`,
which it answers and which brings the character into the world, or while
`you.race` is empty or `you.chooserace` is set.

### `respawn`

While dead: be taken to the temple.

### `move`

`dir`: one step. See [Walking](#walking).

### `turn`

`dir`: face that way without stepping.

### `walk`

`x`, `y`: walk to that tile on your own level, up to 40 tiles off, by the
server's path finder. See [Walking](#walking).

### `route`

`steps`: a route you planned, `x, y, z` a place. See [Walking](#walking).

### `attack`

`id`: a creature to attack (a monster or a player you may fight) or follow;
picking the same monster again chases it, a third time lets go; `0` lets go.
NPCs are never picked. While a target is in reach the character swings at it
on its own clock; there is no message per swing.

### `say`

`text`: said aloud to everybody within 8 tiles. A spell is cast by saying its
words (the state's `you.spells` lists them, and whether each can be cast
now), and a line starting with `/` is a command answered to you alone:
`/commands` lists them.

### `ask`

`id`, `topic`: ask an NPC about the topic at that place on their menu (the
welcome's `npcs`). Everything done with an NPC is done from within three
tiles of them, on their level and in sight of them; from further off this
walks you up that far first. The NPC stops and turns to face whoever dealt
with them last.

### `approach`

`id`: walk up to within three tiles of an NPC, once. A `reached` answers
it once you are within reach, at once if you already are; a walk that is
called off, or never gets there, has no answer.

### `pickup`

`x`, `y`: take the top thing lying on that tile of your level (reaching past
blood and bodies), walking beside it first if it is further than a tile.
On a chest or a door it opens it.

### `drop`

`id`, `owner`, and `count` (how many) or `all` (the whole pile): put it down
where you stand.

### `destroy`

`id`, `owner`, and `count` (how many, 0 for the whole pile): an admin's
Destroy, gone for good rather than put down. The One Ring is lost in the
Gladden Fields again rather than unmade. Ignored from anybody but an admin;
`you.admin` says who that is.

### `pin`

`id` and `on`: pin that kind of item to the top of the bag (`on` true) or
unpin it. A pin is on the kind, not on a pile, and is kept with the
character; `you.pins` lists the pinned ids and the client sorts them first.
An id that is no item, or more than 64 pins, changes nothing.

### `cast`

`id`: cast the One Ring (the only item whose def has `cast`) into the Fire.
Beside the Cracks of Doom it is unmade, as a `use` there is; anywhere else
the server only says where the Fire is, and the Ring is not put on. Any
other id, or a One the player does not carry, changes nothing.

### `pinspell`

`text` and `on`: pin the spell of that name to the head of the Spells panel
(`on` true) or unpin it. The name is the one on the spell's view, in any
case. Pins are kept with the character; the server sorts pinned spells
first in `you.spells`, each lot in the panel's own order, and marks them
`pinned`. A name that is no spell changes nothing.

### `pinsummon`

`text` and `on`: pin the creature with that key to the top of summon
creature's menu (`on` true) or unpin it. Pins are kept with the character;
the spell view's `creatures` lists the pinned first, each lot cheapest first,
and marks them `pinned`. A key that is no monster changes nothing.

### `use`

`id`, `owner`: use an item in your bag: drink, eat, read.

### `equip`

`place`, `id`: fill a place on the Equipment panel (`you.worn` lists them)
with an item from the bag; `0` gives it back to the bag's own choosing, `-1`
leaves it empty on purpose.

### `deposit`

`id`, `owner`, `count` (0 for the whole pile): from bag to depot, standing on
a depot floor.

### `withdraw`

`id`, `owner`, `count` (0 for the whole pile): from depot to bag, standing on
a depot floor.

### `depositall`

`text`: `"gold"` stores every coin in the bag; `"loot"` stores everything but
gold, potions, food, what is in use and the pinned kinds. Beside a depot box
only.

### `look`

`x`, `y`: what is on that tile, answered with a `text`.

### `lookslot`

`id`, `owner`: what a thing in your bag is.

### `lookdepot`

`id`, `owner`: what a thing in your depot is.

### `lookvendor`

`id`: what a line of the vendor's stock is, within three tiles of the vendor.

### `buy`

`id`, `lots`, `panel`: buy `lots` lots (0 is one) of a line of the vendor's
stock, within three tiles of the vendor. Without `panel`, at most ten.

### `sell`

`id`, `owner`, `lots`, `panel`: sell that many of a pile in your bag to the
vendor beside you. Without `panel`, at most ten.

### `marketsell`

`id`, `owner`, `count`, `price`: leave `count` of an item from your depot
with the market keeper to sell to other players at `price` gold each.

### `marketbuy`

`id`, `count`: buy `count` from the market listing `id`. Beside a depot box the
goods go into the depot and the gold comes from the depot first, then the bag.

### `marketcancel`

`id`: take back your own market listing, into your depot.

### `marketlook`

`id`: look at what market listing `id` is (a long press on it).

### `marketcollect`

Collect the gold the market keeper is holding for you from sales: into the
depot beside a depot box, otherwise into the bag as far as it can be carried.

### `trade`

`id`: ask a player beside you to trade, or answer their ask.

### `tend`

`id`: crush a leaf of athelas from your bag for the player beside you: it
heals them as a health potion does and draws out their poison. Refused, and
nothing used, from further off or with nothing to tend.

### `tradeoffer`

`id`, `owner`, `count`: set how many of an item are in your offer; `0` takes
it out.

### `tradeaccept`

Accept both offers as they stand now. Any change clears both accepts.

### `tradecancel`

Close the trade, or withdraw or decline an ask.

### `luxout`

Put out your light spell at once. Free, and no mana comes back; with no
light burning the server says so. The client sends it for a long press on
the light's spell button or its status icon while the light is burning.

### `stance`

`text`: whom you may attack: `white` (only whoever attacked you), `yellow`
(those and anybody skulled) or `red` (anybody).

### `pvp`

`on`: the old toggle before stances: on is `red`, off is `white`.

### `autostance`

`text`: what you swing at by reflex when you have picked nothing: `none`,
`aware` or `all`.

### `auto`

`on`: the old toggle before auto stances: on is `all`, off is `none`.

### `chase`

`on`: whether picking a monster a second time, or a player, walks after them.

### `ride`

`on`: get on your horse, or down from it. Refused, with a `text` saying
why, without a horse (Éomer's, in Edoras, for ten orcs), below the ground
or in a fight. `/ride` does the same.

### `debug`

`on`: whether every blow you deal and take is explained in a `text` of kind
`debug`.

### `highscores`

Ask for the boards: answered with `highscores`.

### `who`

Ask who is online: answered with `who`.

### `changes`

Ask for the changelog: answered with `changes`.

## Server to client

Each is one object with `"t"`. The fields are in the [reference](#reference),
under the type named.

### `welcome`

[Welcome](#welcome). Once per login, first: your creature's `id`, the
protocol version `proto`, the explored map (`map`, `levels`), every item
definition (`items`), the `temple`, the protected zones (`pz`, `pzbelow`),
open spaces across levels (`open`), place names (`places`, `known`), the
places quests have sent you to (`marks`), and the world's people and what
they can be asked (`npcs`, keyed by creature kind).

### `chooserace`

[ChooseRace](#chooserace). After the welcome, in place of a state, to a
character with no race: the character's `name` and the `races` to choose
from. Answer with a `race`; until then the character is not in the world.

### `state`

[State](#state). Everything in sight, whenever it changes, at most ten a
second: `you` (your own numbers: hp, mana, level, skills, bag `inv`,
equipment `worn`, `spells`, stances, depot and vendor when beside them, the
market when beside its keeper), `creatures` (every creature in sight,
yourself included, with `id`, `kind`, `x`, `y`, `z`, `hp`, `maxhp`,
`step`, `move`), `items` (what lies on the ground: `id`, `count`, `x`, `y`,
`z`), `fish`, and `tracks` (the people
you are being shown the way to, wherever they are: for a minute after a find
person, the person it named, and while you wear the One, everybody wearing a
ring of the Seven or the Nine; see [Track](#track)). A creature's `kind` is
`player`, an NPC's kind (a key of the welcome's `npcs`), or a monster's.

### `steps`

[Steps](#steps). Steps in sight as they are taken: per step the creature `id`,
where it ended up (`x`, `y`, `z`), facing `d`, when it began (`at`, server
Unix milliseconds) and how long it lasts (`ms`), and `via`, the passage, for a
step that changed level.

### `seen`

[Seen](#seen). Tiles seen for the first time, by level, before the state that
shows them: add them to your map.

### `map`

[MapReset](#mapreset). Your whole map again, replacing what you had.

### `marks`

[Marks](#marks). Every place a quest has sent you to, replacing what the
welcome or the last `marks` gave: per place its `key`, the `name` to write,
the tile `x`, `y` and its level `z`, and `done` once the quest that needs it
is done. Sent when someone you ask about a quest points you somewhere new,
and when a quest that needs a marked place is done or, by a death, undone.

### `text`

[Text](#text). Something said or told, by `kind`: `say` (spoken aloud by
`from` at `x`, `y`; `spell` when it was a spell's words), `noise`, `answer`
(the world's answer to you alone, such as a look), `server`, `event`,
`loot`, `level`, `error` (a refusal of what you asked), `blocked` (why a step
was not taken), `dealt`, `debug`, `critical`, and others the browser files
under its own filters. A client that only wants to know what went wrong reads
`error`.

### `effect`

[Effect](#effect). Something seen happening on a tile: damage (`dmg`, with
`value`), `heal`, `miss`, `block`, `death`, `levelup`, a gear `break`,
`attack` (your swing landed on `id`), a `shot`, a lightning `arc`, a fire
`nova`, a `cleanse`.

### `trade`

[Trade](#trade). The trade window: `state` (`asked`, `open`, `closed`), who it
is `with`, both offers (`mine`, `theirs`) and both accepts, and the `reason`
when it closes.

### `players`

[Players](#players). Everybody online, with level, skull and platform, pushed
when it changes.

### `who`

[Who](#who). Everybody online and their level, when asked.

### `highscores`

[Highscores](#highscores). The boards, when asked.

### `changes`

[Changes](#changes). The changelog, when asked.

### `character`

[Character](#character). A character's page, opened from a name on the
highscores. It is not sent over the socket: it is served over HTTP at
`/character?name=`, to anybody, and a hidden character or a name nobody has
is a 404.

### `password`

[Password](#password). Your character's password has just been changed (by
`/password`): forget the one you kept.

### `pong`

[Pong](#pong). The answer to a `ping`, with its `seq`.

### `reached`

[Reached](#reached). An `approach` has brought you within three tiles of
the NPC `id`, in sight of them: sent at once if you were already that near,
and otherwise on arriving. The browser opens the NPC's menu on it. Nothing
is sent for a walk up that is called off or never arrives.

### `error`

[Error](#error). A login refused (with why), a message that was not JSON
(`bad message`), a message before login (`not logged in`), or the limits'
word to slow down. In the world, a refusal of something asked is usually a
`text` of kind `error` instead.

### `bye`

[Bye](#bye). The last message: a logout granted, another login to the same
character, the server stopping, or a flood. `text` says why, when it was not
your own logout. Do not reconnect automatically after a `bye`.

## Tiles

What each tile id in the map is, and how it walks. *Walkable* tiles can be
stood on; *passages* are walkable by players only and take you to another
level in the same step; *speed* multiplies the time of a step onto the tile
(a step through a passage is charged by the tile it lets out on). Shut doors
and other items marked `blocking` in the welcome's item definitions also stop
a step onto walkable ground. The ids are fixed: new tiles are only ever added
at the end.

<!-- BEGIN tiles: generated from internal/game by TestProtocolDocTiles; run go test ./internal/game -run TestProtocolDocTiles -update -->

| Id | Tile | Name | Walkable | Passage | Speed |
|---|---|---|---|---|---|
| 0 | `TileGrass` | grass | yes |  | 1 |
| 1 | `TileDirt` | dirt | yes |  | 1 |
| 2 | `TileSand` | sand | yes |  | 1.15 |
| 3 | `TileStone` | stone floor | yes |  | 0.9 |
| 4 | `TileWater` | water | no |  |  |
| 5 | `TileWall` | wall | no |  |  |
| 6 | `TileTree` | tree | no |  |  |
| 7 | `TileFlowers` | flowers | yes |  | 1 |
| 8 | `TileCobble` | cobblestone road | yes |  | 0.8 |
| 9 | `TileTemple` | temple floor | yes |  | 0.9 |
| 10 | `TileRock` | rock | no |  |  |
| 11 | `TileSwamp` | swamp | yes |  | 1.8 |
| 12 | `TileStair` | stairs | yes | up a level, one tile north |  |
| 13 | `TileRoof` | rooftop | yes |  | 1 |
| 14 | `TileStairTop` | stairs | yes | down a level, one tile south |  |
| 15 | `TileDepot` | wooden floor | yes |  | 1 |
| 16 | `TileHole` | hole | yes | down a level, one tile south |  |
| 17 | `TileLadder` | ladder | yes | up a level, one tile south |  |
| 18 | `TileCave` | cave floor | yes |  | 1 |
| 19 | `TileCaveWall` | cave wall | no |  |  |
| 20 | `TileBridge` | drawbridge | yes |  | 0.8 |
| 21 | `TileHilltop` | hilltop | yes |  | 1.15 |
| 22 | `TilePalisade` | palisade | no |  |  |
| 23 | `TileRampart` | rampart | yes |  | 1 |
| 24 | `TileTower` | tower | yes |  | 1 |
| 25 | `TileLadderTop` | ladder | yes | down a level, one tile south |  |
| 26 | `TileForge` | scorched ground | yes |  | 1 |
| 27 | `TilePitProp` | pit prop | yes |  | 1 |
| 28 | `TileRamp` | ramp | yes | up a level, one tile onto the side with the mountain |  |
| 29 | `TileRampTop` | top of a ramp | yes | down a level, one tile off the far end of its ramp |  |
| 30 | `TileSlope` | slope | yes | up a level, one tile onto the side with the mountain |  |
| 31 | `TileSlopeTop` | top of a slope | yes | down a level, one tile off the far end of its slope |  |
| 32 | `TileSnow` | snow | yes |  | 1.5 |
| 33 | `TileHedge` | hedge | no |  |  |
| 34 | `TileHillside` | hillside | no |  |  |
| 35 | `TileHobbitDoor` | round door | no |  |  |
| 36 | `TileThatch` | thatched roof | yes |  | 1 |
| 37 | `TileMillWheel` | mill wheel | no |  |  |
| 38 | `TilePartyTree` | Party Tree | no |  |  |
| 39 | `TileShip` | ship | no |  |  |
| 40 | `TileFrozenWaste` | frozen waste | no |  |  |
| 41 | `TileChasm` | chasm | no |  |  |
| 255 | | not seen | | | |

<!-- END tiles -->

## Reference

<!-- BEGIN reference: generated from internal/proto by TestProtocolDoc; run go test ./internal/proto -run TestProtocolDoc -update -->

Every type in `internal/proto`, in alphabetical order: its first sentence, and each field's JSON name, type and first sentence. A field marked *omitempty* is left off the wire when it is zero, empty or false, and a client reads it as that.

### AdminStatus

AdminStatus is the switches an admin has on for themselves: /invisible, /invincible, /infinitemana (Mana), /seeall, /fullmap, /hide and /nopz, and /boss, with Form the monster they are playing ("a cave troll", "the Balrog") when they have taken one's shape, empty for a boss in their own.

| Field | JSON | Notes |
|---|---|---|
| `invisible` | bool | *omitempty.* |
| `invincible` | bool | *omitempty.* |
| `mana` | bool | *omitempty.* |
| `seeall` | bool | *omitempty.* |
| `fullmap` | bool | *omitempty.* |
| `hidden` | bool | *omitempty.* |
| `nopz` | bool | *omitempty.* |
| `boss` | bool | *omitempty.* |
| `form` | string | *omitempty.* |

### Bye

Sent as `{"t":"bye"}`. Bye acknowledges a logout.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "bye" |
| `text` | string | *omitempty.* |

### Change

Change is one commit from the git log: the day it landed in Sydney, which is what the window groups by, its title, and whether it landed since this player was last in the world.

| Field | JSON | Notes |
|---|---|---|
| `when` | string |  |
| `time` | string | *omitempty.* Time is the time of day it landed, in Sydney too, as "3:04 pm" (2026-10-20: "include a timestamp with the changes listed"). |
| `title` | string |  |
| `new` | bool | *omitempty.* |

### Changes

Sent as `{"t":"changes"}`. Changes answers the "changes" request: the whole log, newest first, with a count of how many of them the player has not seen.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "changes" |
| `entries` | array of [Change](#change) |  |
| `new` | number |  |

### CharLine

CharLine is one death or kill on a character page, newest first: who did it or was killed, when and where.

| Field | JSON | Notes |
|---|---|---|
| `who` | string |  |
| `link` | bool | *omitempty.* |
| `when` | string |  |
| `where` | string |  |
| `note` | string | *omitempty.* |

### Character

Sent as `{"t":"character"}`. Character is a character's page, opened by clicking their name on the highscores (2026-10-17: "clicking a character name in the highscores shows the character page, which lists deaths and kills").

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "character" |
| `name` | string |  |
| `race` | string | *omitempty.* |
| `level` | number |  |
| `deaths` | number | Deaths and Kills are the counts, kept for good; the logs below are only the newest of them, and the Unlisted lines say how many more there are, empty when every one is listed. |
| `kills` | number |  |
| `online` | string | Online is the time they have spent in the world, "3d 4h", and Seen when they were last in, "online now" or "2h 5m ago", absent where the Last online board would leave them out. |
| `seen` | string | *omitempty.* |
| `deathlog` | array of [CharLine](#charline) |  |
| `deathsunlisted` | string | *omitempty.* |
| `killlog` | array of [CharLine](#charline) |  |
| `killsunlisted` | string | *omitempty.* |
| `hunted` | array of [HSRow](#hsrow) | Hunted is the monsters they have killed, by kind, most first. |

### ChooseRace

Sent as `{"t":"chooserace"}`. ChooseRace comes after the welcome, in place of a state, to a character who has no race yet: they are logged in but not in the world, and the server holds them out of it — no tile, no state, nobody sees them — until a "race" message has chosen one (2026-10-08: "don't put the player in the game until they choose their race (if they haven't yet)").

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "chooserace" |
| `name` | string | Name is the character's name. |
| `races` | array of string | Races is the races there are to choose from, in the order they are offered: "elf", "man", "dwarf" and "hobbit". |

### ClientMsg

ClientMsg is the single envelope for all client -> server messages.

| Field | JSON | Notes |
|---|---|---|
| `t` | string |  |
| `name` | string | *omitempty.* |
| `password` | string | *omitempty.* |
| `dir` | string | *omitempty.* |
| `x` | number | *omitempty.* |
| `y` | number | *omitempty.* |
| `z` | number | *omitempty.* Z is the level a look asks for (2026-10-19): the client sends it for somebody or something up on a roof it looked at from outside the building. |
| `id` | number | *omitempty.* |
| `place` | string | *omitempty.* Place is the Equipment panel's place an equip fills: see Worn. |
| `text` | string | *omitempty.* |
| `slot` | number | *omitempty.* |
| `on` | bool | *omitempty.* |
| `build` | string | *omitempty.* Build is the client's own build id, sent with a login. |
| `proto` | number | *omitempty.* Proto is the protocol version the client speaks, sent with a login: see Version. |
| `bot` | bool | *omitempty.* Bot, on a login, says a program is at the reins rather than a person: anything built on internal/botclient sends it (2026-10-13: "add a bot flag (i assume for anything using the botclient code makes the most sense)"). |
| `mode` | string | *omitempty.* Mode, on a login, says which of the login page's two tabs it came from (2026-11-09: "go with phone-C", the Log in and New character tabs): "login" comes in only as a character already made, and is refused for a name nobody has, so a name spelt wrong is not a new character made by mistake; "new" makes a character only, and is refused for a name that is taken. |
| `seq` | number | *omitempty.* Seq numbers a ping so the pong that comes back can be matched to the moment it was sent. |
| `steps` | array of number | *omitempty.* Steps is a route the client planned, for a "route": the places to step through in order, three numbers a place — x, y and level — the first a step from where the walker stood when it was planned. |
| `all` | bool | *omitempty.* All says a drop should let go of the whole stack rather than one of it. |
| `lots` | number | *omitempty.* Lots is how many of a thing a buy asks for, counted in what the counter sells it in: one for a click, ten for a right-click, and zero from anything that has not heard of the idea, which the server reads as one. |
| `panel` | bool | *omitempty.* Panel marks a buy or a sale whose number came from the quantity dialog, so the server does not hold it to a right-click's ten. |
| `count` | number | *omitempty.* Count is how many of an item a trade offer holds. |
| `owner` | string | *omitempty.* Owner goes along with ID wherever a click names an item rather than a slot: it is the character a skull came off, and it is what tells two piles of skulls apart. |
| `var` | string | *omitempty.* Var goes along with ID and Owner the same way: which pile of the kind, where piles of it differ (a ring's number). |
| `topic` | number | *omitempty.* Topic is what an "ask" asks: the place of a topic on the NPC's menu, counted from nought, as Welcome.NPCs lists them. |
| `price` | number | *omitempty.* Price is the gold each a "marketsell" asks for every one of the lot it leaves with Grubb (2026-10-07: "add a market feature Grubb. you can buy and sell items to other players"). |

### CounterItem

CounterItem is one line of the shop as the Vendor panel lists it: a lot Grubb sells, or a pile in the bag he would buy.

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `owner` | string | *omitempty.* Owner names the pile this line is about when the kind alone does not: two skulls off two people are two piles and two rows, and a click on one of them has to hand back which. |
| `var` | string | *omitempty.* Var and Name are the pile's variant and what it is called, as on Item. |
| `name` | string | *omitempty.* |
| `def` | [ItemDef](#itemdef) or absent | *omitempty.* Def is what a made piece is (see Item.Def). |
| `count` | number | Count is how many the line is about. |
| `price` | number | Price is what the line costs: the whole lot, on a stock line, and what she pays for one lot of them (see Lot) on a sell line. |
| `lot` | number | *omitempty.* Lot is how many of the thing one sale takes, on a sell line: one of most things, and two or three of the cheapest, whose fifth of a worth does not reach half a coin until there are that many of them (see sellLot). |

### Creature

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `name` | string |  |
| `kind` | string | "player", monster key, or "boss": an admin's /boss, to all but them |
| `outfit` | number |  |
| `z` | number | Z is the level the creature stands on: 0 the ground, 1 a rooftop, negative a dungeon floor. |
| `x` | number |  |
| `y` | number |  |
| `dir` | string |  |
| `hp` | number |  |
| `maxhp` | number |  |
| `step` | number | ms per orthogonal tile, for client interpolation |
| `move` | number | *omitempty.* MoveMs is how long the step this creature has just taken lasts: the whole of the wait before they may move again, the ground they walked onto and the diagonal and the passage all counted in. |
| `via` | [Point](#point) or absent | *omitempty.* Via is where a step that changed level should look as though it came from: the passage tile itself — the stairs, hole or ladder walked onto — which sits a level away from the exit the walker lands on. |
| `peek` | [Point](#point) or absent | *omitempty.* Peek is the passage mouth this creature is being seen through: it stands a level away, on the tile the passage lets out on, and the viewer is beside the stairs, hole or ladder that leads there. |
| `level` | number | *omitempty.* Level is a character's level, for players only: the player menu greys Attack by it, and the client keys its sprites by it. |
| `light` | number | *omitempty.* Light is how far this player's light reaches, in tiles: the same number their own You.Light carries, sent on their creature so that everyone who can see them sees by it. |
| `tint` | string | *omitempty.* Tint is the colour part of that light is drawn in, "red" for a fire or "blue" for an elven light, and TintR how far that part reaches, in tiles; both off the wire for plain light or none. |
| `tintr` | number | *omitempty.* |
| `color` | string | *omitempty.* Color is a player's own colour: tunic, name and damage numbers. |
| `weapon` | number | Weapon, Shield, Armor and Helm are the item ids a player is fighting with, holding and wearing (the best of each carried), so everyone can see them on the character; 0 means bare hands, no shield, a plain tunic or a bare head. |
| `shield` | number |  |
| `armor` | number |  |
| `helm` | number |  |
| `cloak` | number | Cloak is the cloak over the armor, the same way and for the same reason (2026-10-04: "make the various elf items equippable in addition to other items where it makes sense"), and not omitempty for the same reason as the four above. |
| `belt` | number | *omitempty.* Belt is the belt at a player's waist (2026-10-22), 0 for none and for every monster. |
| `wmake` | string | *omitempty.* WMake and SMake are who made a player's weapon and shield in hand, "dwarf" and the rest (see ItemDef.Make), for the outline in the make's colour round each where it is drawn on the character (2026-10-21: "when wielding an item with a make, can we display that differently too?"); empty for plain gear and for every monster. |
| `smake` | string | *omitempty.* |
| `amake` | string | *omitempty.* AMake, HMake and CMake are the same for the armor, the helm and the cloak worn (2026-10-22: "my orc-forged leather armor doesn't show the glint or outline when i wear it"). |
| `hmake` | string | *omitempty.* |
| `cmake` | string | *omitempty.* |
| `skull` | bool | Skull marks a player with a kill's timer running: it shows beside their name. |
| `locked` | bool | Locked marks a player who is protection zone locked, for attacking somebody who was not or for a skull, and it turns their eyes red (2026-10-24: "Eyes should be red if you are protection zone locked"). |
| `admin` | bool | Admin marks somebody who runs the place, so the client can draw them as one. |
| `race` | string | *omitempty.* Race is a player's race (elf, man, dwarf or hobbit), so everyone sees them drawn as one; empty for a monster, and for a player who has not chosen yet, who is drawn as a man. |
| `hair` | number | *omitempty.* Hair is where a player's name puts them in their race's spread of hair and eye colours, 1 to 100; zero for a monster. |
| `alert` | number | *omitempty.* Alert is this monster's alertness range in tiles: how far its own half of noticing reaches, its kind's keenness. |
| `clank` | number or absent | *omitempty.* Clank is this monster's own noise, in tiles, for the same overlay and under the same switch: how far past a player's alertness it is seen (World.triggerRange, its light included under the ground). |
| `hostile` | bool | Hostile is set on another player when the receiver may fight them: the receiver's stance allows it — red, or that player attacked them lately and may be hit back, or (yellow) wears a skull. |
| `npc` | bool | *omitempty.* NPC marks one of the world's own people, Grubb and the rest, whom a click opens a menu on and walks up to, and who is never fought or followed (2026-10-07: "don't allow npcs to be followed"). |
| `ally` | bool | *omitempty.* Ally marks a monster a wizard has convinced to fight for them (2026-10-26), and AllyColor is that wizard's colour, which its name is written in so it reads as theirs rather than as a monster's red. |
| `allycolor` | string | *omitempty.* |
| `invisible` | bool | *omitempty.* Invisible marks an admin gone /invisible, for the only viewers who are sent them at all: themselves and the other admins, who draw them greyed out — sprite, name and bar — so that it is plain they are out of everybody else's sight (2026-10-07: "when a player/admin is invisible, show them greyed out"). |
| `fade` | number | *omitempty.* Fade is how see-through this player is drawn, in parts of a hundred of their opacity: seven tenths of how far a ring has faded them (see You.Fading), so fading alone leaves them at least 30% opaque, and 30 more for a great ring on their finger, to a hundred at most; nought for anybody else. |
| `greatring` | bool | *omitempty.* GreatRing is a player with a Great Ring on their finger, whose see-through figure shimmers and whose ring hand glints now and then (2026-11-10: "Do 1 and 4", of a slow shimmer and a glint on the hand). |
| `riding` | bool | Riding is a player on a horse, drawn mounted (2026-10-12: "Add mounts"). |
| `steed` | string | *omitempty.* Steed is what a mounted monster rides, when it is not a horse of the Mark: "black" for a Nazgûl's black horse, "fell" for a fell beast (2026-11-01; see steeds.go in the game). |

### Effect

Sent as `{"t":"effect"}`. Effect is a transient visual: kind "dmg", "heal", "miss", "block", "death", "levelup", "break" (a piece of the player's gear gave out; Value is the item id, so the client can say which), "attack" (your swing landed on this creature; the client outlines it in yellow, which outranks the target outline, since a reflex swing at a neighbour can make the thing you hit differ from the thing you targeted), "shot" (ID shot an arrow at the tile X, Y), "wide" (a shot by ID at Value, on the tile X, Y, went wide; the client sends that shot on past them), "shadow" (a blow or shot by ID went through Value, on the tile X, Y, whom a Great Ring is fading; the client draws an afterimage of them slipping aside, and sends a shot on past), "arc" (a link of a chain lightning: the bolt leapt from the creature ID to the tile X, Y), "nova" (a fire nova went off on the tile X, Y and reached Value tiles in every direction), "cleanse" (poison was drawn out of whoever is standing on the tile X, Y), "shriek" (the Witch-king shrieked on the tile X, Y, and it carried Value tiles; whoever it caught is paralysed), "lull" (Old Man Willow sang on the tile X, Y, and it carried Value tiles; whoever it caught is asleep, paralysed, and whoever was out of his reach was brought in beside him), "lure" (a corpse candle, ID, drew in whoever was standing on the tile X, Y: they are beside it now, paralysed for a moment), "smoke" (a pipe of Longbottom Leaf was lit, or puffed, by whoever is standing on the tile X, Y), "regen" (whoever is standing on the tile X, Y mended a beat faster than the ordinary passive rate inside the Party Tree's flowers), "mana" (sent only to its own player: Value mana came back on a beat faster than the ordinary rate, with a lit pipe), "summon" (an admin's /summon brought something to the tile X, Y: a creature, or with Value 1 a thing) or "web" (Shelob, ID, threw her web onto the tile X, Y; whoever stands there is entangled for Value milliseconds).

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "effect" |
| `kind` | string |  |
| `id` | number | *omitempty.* |
| `x` | number |  |
| `y` | number |  |
| `value` | number | *omitempty.* |
| `z` | number | *omitempty.* Z is the level the effect happened on; it is sent only to viewers who can see that level. |
| `color` | string | *omitempty.* Color is the colour the effect lands in on every screen: a damage number in its dealer's colour. |

### Error

Sent as `{"t":"error"}`. 

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "error" |
| `text` | string |  |

### Eyrie

SpellView is one spell as the panel down the side of the client draws it: what it is called, the words that cast it, what it costs, what it does, whether it will go off right now, and if it will, whether it would do anything.

| Field | JSON | Notes |
|---|---|---|
| `key` | string |  |
| `name` | string |  |

### Fish

Fish is one fish in the water, as seen from the shore: where it is and which way it is pointing.

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `x` | number |  |
| `y` | number |  |
| `dir` | string |  |

### Fixture

Fixture is one piece of scenery on the map: see Welcome.Fixtures.

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `id` | number |  |

### GroundItm

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* Z is the level the stack lies on. |
| `id` | number |  |
| `count` | number |  |
| `color` | string | *omitempty.* Color is the colour a skull's eye sockets are drawn in: the colour of whoever lost the head. |
| `kind` | string | *omitempty.* Kind is the kind of monster a body is, for drawing it: "wolf", "dwarfqueen". |
| `race` | string | *omitempty.* Race is the people a player's body was, "elf", "dwarf", "hobbit" or "man", for drawing it in their shape (2026-10-07: "make player corpses more distinctive"). |
| `hair` | number | *omitempty.* Hair is the body's hair, the player's own: see Creature.Hair. |
| `show` | bool | *omitempty.* Show marks a mathom on show in the museum, which is looked at and never taken, so a click on it is a look rather than a walk. |
| `name` | string | *omitempty.* Name is what the pile is called where its variant names it differently from its kind ("the Third of the Seven Rings"), and empty for everything else. |
| `def` | [ItemDef](#itemdef) or absent | *omitempty.* Def is what a made piece is (see Item.Def). |

### HSBoard

| Field | JSON | Notes |
|---|---|---|
| `name` | string |  |
| `rows` | array of [HSRow](#hsrow) |  |
| `of` | string | *omitempty.* Of says what the rows are when they are not characters: "monsters" for the board of what has killed the most players, "spells" for the board of the spells cast most, "races" for the board of how many characters each race has. |
| `races` | bool | *omitempty.* Races says the rows carry each character's race, to show beside the name: only the Level board and the skill boards do (2026-10-18: "only skills and level care about the race of the scorer"). |

### HSRow

| Field | JSON | Notes |
|---|---|---|
| `name` | string |  |
| `value` | number |  |
| `text` | string | *omitempty.* Text is how the value should be read, when the number is not how a person would say it: a board of time played sends seconds, which rank the rows, and "3d 4h", which is what goes in the column. |
| `race` | string | *omitempty.* Race is the character's race, "Elf", "Man", "Dwarf" or "Hobbit", on the boards whose rows are characters (2026-10-16: "Add races to the highscores"). |

### Highscores

Sent as `{"t":"highscores"}`. Highscores answers a "highscores" request: one board per ranking — character level first, then each skill — over every character ever made, online or not, top first.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "highscores" |
| `boards` | array of [HSBoard](#hsboard) |  |

### Item

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `count` | number |  |
| `owner` | string | *omitempty.* Owner is the character a skull came off, empty for everything else in the world. |
| `var` | string | *omitempty.* Var is which pile of the kind this is, where piles of it differ (a ring's number), sent back with a click like Owner; and Name what the pile is called, where its variant names it differently from its kind ("the Third of the Seven Rings"). |
| `name` | string | *omitempty.* |
| `def` | [ItemDef](#itemdef) or absent | *omitempty.* Def is what the pile is, where its variant makes it more than a renamed kind (a dwarf-wrought sword), and nil for everything else, which is its kind as the welcome said. |
| `color` | string | *omitempty.* Color is what that character's colour is, worked out here because the colour wheel is dealt on the server and the client has no way to ask it. |

### ItemDef

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `name` | string |  |
| `make` | string | *omitempty.* Make is who forged the piece, "dwarf", "gondolin", "elven", "arnor", "orc", "morgul" or "gilded", and empty for everything else: the client draws the outline round its picture in the make's colour. |
| `attack` | number | *omitempty.* |
| `armor` | number | *omitempty.* |
| `shield` | number | *omitempty.* what a shield turns aside, before skill |
| `helm` | number | *omitempty.* Helm is what a helmet soaks, on top of the armor, for the one thing in the world that goes on a head. |
| `cloak` | number | *omitempty.* Cloak is what a cloak soaks on top of the armor under it, worn beside the rest rather than in place of any of it (2026-10-04: "make the various elf items equippable in addition to other items where it makes sense"). |
| `belt` | number | *omitempty.* Belt is how much more a belt lets its wearer carry, in coins (2026-10-22); 0 for everything that is not a belt. |
| `ring` | string | *omitempty.* Ring marks a ring, and says in a few words what wearing it does that the other fields do not (2026-10-05: "Implement the lesser, Seven, Nine and One Ring"): "noise halved", "poison halved" and the like. |
| `feels` | bool | *omitempty.* Feels marks a ring that, looked at in its owner's bag, feels the other rings: only the One, which feels the Seven and the Nine (2026-10-17: "only the One Ring can feel the other rings, and only Great Rings (Seven or Nine)"), so a client offers the look as a button only on it. |
| `cast` | bool | *omitempty.* Cast marks the thing that can be cast into the Fire: only the One, unmade at the Cracks of Doom, so a client offers "Cast it into the Fire" in its menu (2026-10-19: "The One should have an entry in the context menu "Cast it into the Fire""). |
| `reflect` | number | *omitempty.* Reflect is the share of every blow that gets through to the wearer that goes back into whoever struck it. |
| `skill` | string | *omitempty.* the skill the item trains and draws on |
| `range` | number | *omitempty.* tiles a ranged weapon reaches |
| `defense` | number | *omitempty.* Defense is what a melee weapon parries, before melee skill scales it. |
| `price` | number | *omitempty.* Price is what the piece is worth in gold: what the outfitter charges for the ones she stocks. |
| `cn` | number | *omitempty.* Cn is what one of them weighs, in coins — the weight of a gold coin, which is the unit a world with no scales and a great many coins in it would arrive at. |
| `bane` | number | *omitempty.* Bane is how much harder, in percent, the weapon lands on the dead: an Arnor-forged blade's. |
| `twohanded` | bool | *omitempty.* TwoHanded weapons leave no hand for a shield, and are carried across the body rather than swung from one fist. |
| `offhand` | bool | *omitempty.* OffHand marks a thing held in the off hand that blocks nothing (the wizard's staff), which is otherwise known by its shield number. |
| `use` | string | *omitempty.* Use is what a click in the bag does with a thing that is none of gear, food or a draught: "spent" for one that is used up doing it (a pinch of leaf), "kept" for one that stays (the rod, the pipe, Boromir's horn). |
| `heal` | number | *omitempty.* Heal is how long drinking one mends for, in milliseconds, and absent for anything that is not a potion. |
| `mana` | number | *omitempty.* Mana is what using it puts back into the pool. |
| `ranged` | number | *omitempty.* Ranged is how many levels of the ranged skill the piece counts for while it is worn, as Magic is levels of magic. |
| `speed` | number | *omitempty.* Speed is how many levels of pace the piece counts for while it is worn: the spidersilk cloak's five. |
| `clank` | number | *omitempty.* Clank is how much noise the piece makes while it is in use, in tiles of extra notice: how much further off a monster hears whoever has it on. |
| `food` | number | *omitempty.* seconds of nourishment when eaten |
| `breaks` | number | *omitempty.* Breaks is the chance that the item has broken after 15 minutes of continuous use: a weapon is used on each swing that connects, armor and a shield on each hit they take. |
| `decor` | bool | *omitempty.* Decor marks the things that are not things: blood, bones and the like, which cannot be picked up, so a click on them means something else. |
| `raise` | number | *omitempty.* Raise is how far up, in pixels of the 32-pixel art, whatever lies on top of this is drawn: the museum's display stands (2026-10-20). |
| `door` | bool | *omitempty.* Door marks the undercroft's door, which is the one thing on the ground a left click walks onto rather than uses: an open door is a doorway, and the point of a doorway is to go through it. |
| `blocking` | bool | *omitempty.* Blocking marks what cannot be walked over — a chest, a shut door — so the client can plan a walk round it as the server would (2026-09-28: "do "map walking" client side so the server doesn't have to calculate it"). |
| `static` | bool | *omitempty.* Static marks scenery: the boxes, the anvil, the door, the board out on the broken bridge. |
| `works` | bool | *omitempty.* Works is the scenery a pickup does something with: a chest, a depot box, the bridge's board, a door. |
| `opens` | bool | *omitempty.* Opens is the scenery a pickup opens like a chest: the quest chests, the last apple tree, the Púkel-man. |
| `venom` | number | *omitempty.* Venom, GlowsNear, Magic and FireWard are the four things the rare finds do that nothing else in the world does (2026-09-26, "Implement them all"), sent for the tooltip, which is the only place a player is told them. |
| `glowsNear` | string | *omitempty.* |
| `magic` | number | *omitempty.* |
| `fireWard` | bool | *omitempty.* |
| `shines` | bool | *omitempty.* Shines is Andúril's: a blade that lights its wielder underground whatever is near (2026-09-28, "Add a quest to find the Shards of Narsil"), sent for the tooltip like the four above. |
| `light` | number | *omitempty.* Light is how far, in tiles, the thing lights the dark underground while it is in use: the phial's 6, a torch's 3, and glowLight for a blade that Shines without a number of its own. |
| `cures` | bool | *omitempty.* Cures is athelas's: using it draws out every dose of poison, as the purgo spell does (2026-10-01, "Implement the entire proposal in the background"), sent for the tooltip like the five above. |
| `ammo` | number | *omitempty.* Ammo is the item id a launcher shoots, one a shot, and absent for everything else: the bow's arrows and the crossbow's bolts (2026-09-27, "implement arrows and bolts"). |
| `missile` | string | *omitempty.* Missile is how the thing is drawn crossing the air — "arrow", "bolt" or "rock" — on the items that fly and on nothing else. |
| `look` | string | *omitempty.* Look is what a look at the thing says of it, sent so the quantity dialog a press on the bag opens can say it too (2026-09-27: "include the look text always"). |
| `facts` | array of string | *omitempty.* Facts is what the kind is, in the tooltip's short words — "atk 7 ranged, range 4", "uses arrows", "arm: 3" — worked out once by the server, which says the same words when the thing is looked at (2026-10-08: "looking at an item should show its stats, i'm only seeing that on tooltip"). |

### LevelTiles

LevelTiles is some of the tiles of one level of the map.

| Field | JSON | Notes |
|---|---|---|
| `z` | number |  |
| `tiles` | array of number | Tiles is three numbers a tile: x, y and the tile. |

### Light

Light is one light in view whose bearer is not: the Light, Tint and TintR of their Creature, at where they stand.

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `light` | number | *omitempty.* |
| `tint` | string | *omitempty.* |
| `tintr` | number | *omitempty.* |

### MapData

| Field | JSON | Notes |
|---|---|---|
| `w` | number |  |
| `h` | number |  |
| `runs` | string | Runs is the ground, row-major and run-length encoded: base64 of a byte for the tile and then how many of it there are in a row as an unsigned varint (seven bits a byte, low first, the top bit set on every byte but the last), a run carrying on from the end of one row into the next. |

### MapReset

Sent as `{"t":"map"}`. Seen is the tiles a player has just seen for the first time, sent before the state that shows them.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "map" |
| `map` | [MapData](#mapdata) |  |
| `levels` | array of [LevelTiles](#leveltiles) |  |

### Mark

Mark is one place a quest has sent the player to, to draw on their map: a marker on the tile x, y with the name beside it.

| Field | JSON | Notes |
|---|---|---|
| `key` | string |  |
| `name` | string |  |
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `done` | bool | *omitempty.* |

### Market

Market is Grubb's market as one player sees it (2026-10-07: "add a market feature Grubb. you can buy and sell items to other players"): every lot players have left with him to sell, what of the bag may be left with him, and the gold he is holding for this player from what of theirs has sold.

| Field | JSON | Notes |
|---|---|---|
| `keeper` | string | Keeper is who keeps the market: Grubb. |
| `listings` | array of [MarketListing](#marketlisting) |  |
| `goods` | array of [Item](#item) | Goods is every pile in the bag that may be put up for sale: the bag less gold, rings and errands (see game.marketRefusal). |
| `held` | number | *omitempty.* Held is the gold the keeper is holding for this player from sales, for a Collect beside him. |
| `mine` | number | Mine is how many of the listings are this player's, and Max the most one player may have at once. |
| `max` | number |  |

### MarketListing

MarketListing is one lot left with the keeper to sell: what and how many are left of it, the gold each, whether it is the viewer's own, and when it was listed, in Unix seconds.

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `item` | [Item](#item) |  |
| `price` | number |  |
| `batch` | number | *omitempty.* |
| `asked` | number |  |
| `each` | number |  |
| `next` | number |  |
| `lots` | number | *omitempty.* Lots is how many sellers' lots the row is, when more than one: lots of one thing at one price are one row, and the oldest listed sells first (2026-10-24: "Batches between players that have the same price sell oldest first"). |
| `mine` | bool | *omitempty.* |
| `at` | number |  |

### Marks

Sent as `{"t":"marks"}`. Marks is every quest mark the player has, sent whenever one is added or one's Done changes, or a quest on an NPC's menu is finished or undone; it replaces what the client had, as MapReset does.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "marks" |
| `marks` | array of [Mark](#mark) |  |
| `topicsdone` | object of array of number | *omitempty.* TopicsDone is, under each NPC's kind (as Welcome.NPCs keys them), the places on its menu of the quest topics this character has finished: the client draws them as plain topics, without the "!" (2026-10-08: "when a quest is done, remove the ! from the context menu"). |

### NPC

NPC is what the menu a click on one of the world's people opens is made of (2026-10-05: "Make clicking an NPC provide options like starting quests, vendor, etc"): what can be asked of them, whether they keep a shop, and what they look like.

| Field | JSON | Notes |
|---|---|---|
| `topics` | array of [Topic](#topic) | *omitempty.* |
| `shop` | bool | *omitempty.* |
| `look` | string | *omitempty.* |

### OpenFloor

OpenFloor is the part of level Z that is open to the rest of its hollow.

| Field | JSON | Notes |
|---|---|---|
| `z` | number |  |
| `x` | number |  |
| `y` | number |  |
| `w` | number |  |
| `h` | number |  |

### OpenSpace

OpenSpace is one hollow open across levels: each level's share of it.

| Field | JSON | Notes |
|---|---|---|
| `floors` | array of [OpenFloor](#openfloor) |  |

### Password

Sent as `{"t":"password"}`. Password tells a client that its own character's password has just been changed (2026-10-07: "add a resetpassword command, for admins it can be used with a name, /resetpassword <name> <password>"), so that what it keeps to log back in with — the reconnect after a dropped connection, the form after a deploy — is not the old one.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "password" |
| `typed` | string | *omitempty.* |

### Place

Welcome is sent once after a successful login.

| Field | JSON | Notes |
|---|---|---|
| `name` | string |  |
| `x` | number |  |
| `y` | number |  |

### PlayerRow

PlayerRow is one person on the Players panel.

| Field | JSON | Notes |
|---|---|---|
| `name` | string |  |
| `level` | number |  |
| `skull` | number | *omitempty.* Skull is the seconds left on their skull as the message was sent, rounded up so a skull with anything on it never reads as over; the client counts it down on its own clock. |
| `race` | string | *omitempty.* Race is their people, one word, and absent for somebody who has not chosen one yet. |
| `platform` | string | *omitempty.* Platform is the browser and system they are playing on, "Firefox on Windows", as their browser named itself when they connected; absent when it said nothing recognisable (2026-10-06: "Show what OS platform/browser people are on in the players list"). |
| `hidden` | string | *omitempty.* Hidden is set on the reader's own row, and only there, when they are off everybody else's list — gone /hide, or an admin gone /invisible — and says which and how to come back, in the words the mark's hover shows (2026-10-07: "if a character is hidden from the player list, show an icon on the player list so they know they are hidden"). |

### Players

Sent as `{"t":"players"}`. Players is the same list as Who, kept up on a panel of its own rather than asked for, with each person's skull on it (asked for on 2026-09-27: "show a panel with online players and their levels, and skull status").

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "players" |
| `rows` | array of [PlayerRow](#playerrow) |  |

### Point

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* Z is the level, for the points that can be off the ground (a depot box since 2026-10-23); nought, and left out, for the ground. |

### Pong

Sent as `{"t":"pong"}`. 

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "pong" |
| `seq` | number |  |

### Reached

Sent as `{"t":"reached"}`. Reached says an "approach" has brought the player within reach of the NPC ID (three tiles, on their level and in sight of them): at once, for somebody already that near, or on arriving after the walk up.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "reached" |
| `id` | number |  |

### Rect

Rect is a tile-space rectangle anchored at its top-left corner.

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `w` | number |  |
| `h` | number |  |

### Seen

Sent as `{"t":"seen"}`. 

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "seen" |
| `levels` | array of [LevelTiles](#leveltiles) |  |

### SkillBoost

SkillBoost is one item's part in a skill's effective level: From names the piece as a sentence would ("the One Ring", "the geomancer's staff") and Levels is how many it counts for, below nought for a piece that takes levels away.

| Field | JSON | Notes |
|---|---|---|
| `from` | string |  |
| `levels` | number |  |

### SkillView

SkillView is one skill as the panel shows it: its level, how far through it the character is in whole percent, and what the level reached costs.

| Field | JSON | Notes |
|---|---|---|
| `key` | string |  |
| `name` | string |  |
| `level` | number |  |
| `pct` | number |  |
| `time` | string | Time is what the level reached costs to earn, read the way a person says a span — "3h 20m" — so every client says it the same way, as with a board of time played. |
| `effect` | string | Effect is what the level reached does, in the server's own words and worked out from the same numbers the fight uses: "220% attack", "a 45% chance of a fish a cast". |
| `uses` | number | Uses is the skill's raw count, what the level is worked out from, and Next is the count the next level starts at. |
| `next` | number |  |
| `boost` | number | *omitempty.* Boost is how many levels the items in use add to the skill, sent for every skill an item adds levels to and for no other (asked for on 2026-10-08: "when skills are modified by items, show the adjustment in the skills panel"). |
| `boosteffect` | string | *omitempty.* BoostEffect is Effect at Level+Boost: what the skill does while the items are in use, in the same words. |
| `boosts` | array of [SkillBoost](#skillboost) | *omitempty.* Boosts is which item in use adds how many levels, in the order the server asks them: for magic the weapon in hand, then the ring; for ranged the armor, then the cloak. |

### SpellView

| Field | JSON | Notes |
|---|---|---|
| `name` | string | Name is the plain English name, capitalised — what goes on the button — and Words the incantation, which is what saying it means. |
| `words` | string |  |
| `mana` | number | Mana is what it costs. |
| `look` | string | Look is what the spell does, in the words the panel's tooltip shows. |
| `why` | string | *omitempty.* Why is the refusal this character would get for casting it at this moment, and empty when there would be none. |
| `moot` | string | *omitempty.* Moot is why the spell, which would go off, would do nothing worth its mana from where the caster stands — homecoming on the temple floor, provoke with nothing near, a light over a light — and empty when it would be worth casting. |
| `arg` | string | *omitempty.* Arg is what has to be said after the words — "name" for the one spell asked about somebody — and empty for the rest. |
| `putout` | bool | *omitempty.* PutOut is set on the light while one is burning: a long press on its button sends "luxout" and puts it out rather than opening the menu (2026-10-17: "Long press light or the status condition to cancel the spell"). |
| `places` | array of [Eyrie](#eyrie) | *omitempty.* Places is what a spell said with a place after it ("place": the Eagles) can be said with: the places the caster could be flown to, the one they are standing in left out, Hobbiton first. |
| `unvisited` | array of [Eyrie](#eyrie) | *omitempty.* Unvisited is the rest of where it goes: the places the caster has not stood in yet, in the map's order, which the panel shows greyed out under Places so the list says where else there is to find (2026-10-10: "in the eagle menu, show all destinations, but grey out the ones the player hasn't visited yet"). |
| `creatures` | array of [SummonKind](#summonkind) | *omitempty.* Creatures is what a spell said with a creature after it ("creature": summon creature) can call up: every kind whose hitpoints the caster's pool could hold, cheapest first, with what each costs. |
| `icon` | string | *omitempty.* Icon names the drawing the panel puts on the button, which is what the button is now: a glyph rather than the spell's name, with the name moved into the hover. |
| `left` | number | *omitempty.* Left is how many seconds are left of this spell's effect on the player being sent it, rounded up so the last second still says one, and Lasts is how long a casting runs, both in whole seconds. |
| `lasts` | number | *omitempty.* |
| `ready` | number | *omitempty.* Ready is when the spell's cooldown group lets it be cast again, in the server's Unix milliseconds (the clock State.Now is on), and Cool is how long that group's whole cooldown is, in milliseconds. |
| `cool` | number | *omitempty.* |
| `utility` | bool | *omitempty.* Utility marks the spells that are about getting about rather than fighting, sanctification, find person, levitate, blood bond and the Eagles. |
| `pinned` | bool | *omitempty.* Pinned marks a spell the player has pinned (2026-10-17: "Support pinning spells"). |
| `ability` | bool | *omitempty.* Ability marks one of a monster's abilities rather than a spell: the panel of an admin playing a monster with /boss <monster> holds the monster's abilities in place of spells (2026-10-26: "spells should be replaced with special abilities with appropriate cooldown indication"). |

### State

Sent as `{"t":"state"}`. State is the per-tick view of the world around the player.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "state" |
| `now` | number | *omitempty.* Now is the server's clock when the state went out, in Unix milliseconds: what a client lines the times on step messages up against its own clock with. |
| `you` | [You](#you) |  |
| `creatures` | array of [Creature](#creature) |  |
| `items` | array of [GroundItm](#grounditm) |  |
| `fish` | array of [Fish](#fish) | *omitempty.* Fish is every fish in the water on screen. |
| `tracks` | array of [Track](#track) | *omitempty.* Tracks is everybody this player has been given the whereabouts of, wherever they are in the world: a client points an arrow at each from the edge of the screen, or marks them where they stand once they are on it. |
| `lights` | array of [Light](#light) | *omitempty.* Lights is the light carried by everybody in view the player cannot see (2026-10-20: "if someone isn't visible, their light should still be"): somebody too quiet or too far off to make out still lights the cave round them, and a coloured light still tints the ground. |
| `unlight` | array of [Unlight](#unlight) | *omitempty.* Unlight is every Unlight reaching into view (2026-10-27: "Ungoliant creates 5 squares of unlight that supercedes all other light"): the square of R tiles round X, Y on level Z is drawn black whatever lights it, for everybody, wraith-sight or none. |
| `night` | bool | *omitempty.* Night is whether it is night above the ground (2026-10-21: "Add a day night cycle above ground. 10 minutes of day, then 5 minutes of night"; night 3 minutes since 2026-11-04, day 7 minutes since 2026-11-09): the sky lights nothing, and the surface is drawn and seen by the lights in it, as a cave is. |
| `sky` | number | *omitempty.* Sky is how dark the sky is drawn above the ground, in hundredths: nought by day, a hundred by night, and between the two over the half minute either side of dusk and dawn. |
| `turn` | number | *omitempty.* Turn is how many seconds are left until the sky turns, day to night or night to day, rounded up (2026-10-26: "add a condition that shows time of day so we know if it's night or day"). |
| `skyheld` | bool | *omitempty.* SkyHeld is whether an admin's /sky is holding the sky at day or night, so it will not turn until they give it back to the clock. |

### Step

Step is one creature's step: where it ended up and facing which way, when the step began by the server's clock and how long it lasts.

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `d` | string |  |
| `at` | number |  |
| `ms` | number |  |
| `via` | [Point](#point) or absent | *omitempty.* |

### Steps

Sent as `{"t":"steps"}`. Pong answers a "ping" with the sequence number it carried.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "steps" |
| `now` | number | Now is the server's clock as the batch went out, in Unix milliseconds, the same clock as State.Now. |
| `s` | array of [Step](#step) |  |

### SummonKind

SummonKind is one creature summon creature can call up: the key to say after the words, the name to show and the mana it costs, and whether the character has pinned it to the top of the menu.

| Field | JSON | Notes |
|---|---|---|
| `key` | string |  |
| `name` | string |  |
| `mana` | number |  |
| `pinned` | bool | *omitempty.* |

### Text

Sent as `{"t":"text"}`. Text carries chat and server messages.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "text" |
| `kind` | string |  |
| `from` | string | *omitempty.* |
| `id` | number | *omitempty.* |
| `x` | number | *omitempty.* |
| `y` | number | *omitempty.* |
| `text` | string |  |
| `spell` | bool | *omitempty.* Spell marks a say that was the words of a spell going off, which the client shows in a colour of its own, over the head and in the log (2026-09-24: "show the words for spells in another color"). |
| `yell` | bool | *omitempty.* Yell marks a say that was yelled (2026-10-08: "add the ability to yell ... it puts the say in all caps, and can be heard by players 30 tiles away"). |
| `to` | string | *omitempty.* To is whom a "private" message went to, on the sender's own copy of it, and empty on the one the recipient gets. |
| `list` | array of string | *omitempty.* List is the items of a list a command answered with (2026-10-09: "instead of paging long lists in the chat log, can we return a table with wrapping or something?"). |
| `bubble` | string | *omitempty.* Bubble is what an "answer" hangs over the asker's head when that is less than Text: its first sentence, with the whole of Text in the log (2026-10-20). |
| `far` | string | *omitempty.* Far is where a "say" or a "noise" was made, for somebody who heard it from the Seat of Hearing on Amon Lhaw beyond where it carries ("in the Shire", "out in the wild"). |

### Topic

Topic is one thing an NPC can be asked about, by name, and whether it starts or hands in a quest.

| Field | JSON | Notes |
|---|---|---|
| `label` | string |  |
| `quest` | bool | *omitempty.* |

### Track

Track is one person a player is being shown the way to (2026-10-08: "change the find person spell to show the location players for 1 minute, with a direction indicator at the edge of the screen. use the same indicator for the wielder of the one, when one of the other great rings is being worn").

| Field | JSON | Notes |
|---|---|---|
| `name` | string |  |
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `why` | string | Why is what shows them: "find" for a find person, "seven" or "nine" for the ring they wear, felt through the One, "gollum" for Gollum and "nazgul" for one of the Nine, both shown to an admin, and "seat" for a player shown from the Seat of Seeing on Amon Hen. |
| `far` | string | *omitempty.* Far is how far off they are in find person's own words — "close by", "far" or "very far" — and empty for the distance between, which find person says nothing about either. |

### Trade

Sent as `{"t":"trade"}`. Trade is one player's view of a safe trade with another: what each side has put up, and who has agreed to it.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "trade" |
| `state` | string |  |
| `with` | string |  |
| `withid` | number |  |
| `mine` | array of [Item](#item) |  |
| `theirs` | array of [Item](#item) |  |
| `iaccept` | bool |  |
| `theyaccept` | bool |  |
| `reason` | string | *omitempty.* |

### Unlight

Unlight is one Unlight: the darkness Ungoliant gives off, R tiles round where she stands (see State.Unlight).

| Field | JSON | Notes |
|---|---|---|
| `x` | number |  |
| `y` | number |  |
| `z` | number | *omitempty.* |
| `r` | number |  |

### Welcome

Sent as `{"t":"welcome"}`. 

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "welcome" |
| `proto` | number | Proto is the protocol version the server speaks: see Version. |
| `places` | array of [Place](#place) | *omitempty.* Places are where the far zoom writes the names of the land: each seeded area's name and the tile to write it on, biggest first, for the ground only (2026-10-05: "Show the names of locations when zooming out on the map"). |
| `known` | array of string | *omitempty.* Known is the names of the areas this character has seen any of the ground of: the places whose names the far zoom writes. |
| `fixtures` | array of [Fixture](#fixture) | *omitempty.* Fixtures is every piece of scenery standing in the world when it came up — the static ground items: depot boxes, the Seats of Seeing and Hearing, doors, anvils, tombs, stools — with its level and its item. |
| `marks` | array of [Mark](#mark) | *omitempty.* Marks is the places quests have sent this character to, as Marks carries them (2026-10-08: "when npcs give quests, add the locations they mention to the map"). |
| `topicsdone` | object of array of number | *omitempty.* TopicsDone is the quests on NPCs' menus this character has finished, as Marks carries them. |
| `id` | number |  |
| `map` | [MapData](#mapdata) |  |
| `items` | array of [ItemDef](#itemdef) |  |
| `temple` | [Point](#point) |  |
| `pz` | array of [Rect](#rect) | every patch of protected ground |
| `maxfood` | number | seconds a stomach holds, for the food bar |
| `levels` | array of [LevelTiles](#leveltiles) | *omitempty.* Levels are every level but the ground, sparse: rooftops and stair heads above (z 1), cave floors and walls below (negative z). |
| `open` | array of [OpenSpace](#openspace) | *omitempty.* Open is every hollow under the ground that is one open space across more than one level, where the levels see each other in the dark (2026-10-02: "Light like from the spell should apply to other levels when underground"): the orcs' cavern, the yard and the walk over it. |
| `pzbelow` | array of [OpenFloor](#openfloor) | *omitempty.* PZBelow is every patch of protected ground on its own level and no other, where PZ is the ground and the sky over it: Galion's corner of the Elvenking's cellars, since 2026-10-07, and the Prancing Pony's ground floor at Bree but not the room over it, since 2026-10-08. |
| `newchanges` | number | *omitempty.* NewChanges is how many changelog entries landed while this character was away, so the Changes button can say as much without being opened. |
| `npcs` | object of [NPC](#npc) | *omitempty.* NPCs is every one of the world's people, under the kind their creatures carry (the spec's key): the menu a click on them opens. |

### Who

Sent as `{"t":"who"}`. Who is everybody in the world right now: the names behind the Online count in the stat panel, which is a bare number until somebody clicks it.

| Field | JSON | Notes |
|---|---|---|
| `t` | string | "who" |
| `rows` | array of [HSRow](#hsrow) |  |

### Worn

Worn is one place on the Equipment panel.

| Field | JSON | Notes |
|---|---|---|
| `place` | string | Place is its key, sent back with an equip; Label is what the panel calls it. |
| `label` | string |  |
| `id` | number | ID is what is there, nought for nothing. |
| `var` | string | *omitempty.* Var is which pile of that kind is in use, where piles differ. |
| `pick` | string | Pick is how: "chosen" by hand, "auto" the bag's best, or "off" on purpose (or, for the ring, nothing on). |
| `auto` | bool | Auto is whether the bag fills the place by itself when nothing is chosen, which the ring's place never does. |
| `busy` | bool | *omitempty.* Busy marks the shield's place while both hands are on a weapon. |
| `ammo` | number | *omitempty.* Ammo is what the weapon in hand shoots, shown in the off hand while it is a bow or a crossbow, and AmmoCount how many of it are in the bag, nought when there are none left to shoot. |
| `ammocount` | number | *omitempty.* |
| `smoke` | bool | *omitempty.* Smoke marks the two hands while a pipe is lit: the pipe in the hand, and the off hand's Ammo the leaf for it, smoked rather than shot. |
| `options` | array of number | Options are the kinds in the bag that go here, by item id, and OptionVars which pile of each, beside them, where piles of a kind differ (a ring's number): left out while every one is the plain pile. |
| `optionvars` | array of string | *omitempty.* |

### You

| Field | JSON | Notes |
|---|---|---|
| `id` | number |  |
| `area` | string | *omitempty.* Area is the name of where you are standing, for the top of the view (2026-09-27: "show the current level and area name at the top of the view"). |
| `music` | string | *omitempty.* Music is the region you stand in, for the music (2026-10-27: "make different music for different regions"): "mordor", "east" (Wilderland), "rohan", "gondor" and the rest of game.landMusic's names on and over the ground, empty in the Shire; under the ground "dwarf", "tomb" or "elvenhalls", and empty for the caves. |
| `name` | string |  |
| `race` | string | *omitempty.* Race is the character's people: "elf", "man", "dwarf" or "hobbit". |
| `chooserace` | bool | *omitempty.* ChooseRace is whether the chooser should be up: true for a character with no race. |
| `hp` | number |  |
| `maxhp` | number |  |
| `projected` | number | Projected is where the healing window a potion opened will leave you when it runs out: what you have now plus what the window still owes, capped at MaxHP. |
| `projectedmana` | number | ProjectedMana is the same for the pool: where the mana a draught is still paying out will leave it, capped at the maximum, and equal to Mana when nothing is running (2026-10-05: "Mana potions should restore over time like health potions do"). |
| `level` | number |  |
| `xp` | number |  |
| `xpnext` | number | xp required for next level |
| `xpbase` | number | xp required for current level |
| `gold` | number | Gold is what the purse holds. |
| `cn` | number | Cn is what the bag is carrying, in coins, and MaxCn what the character can carry. |
| `maxcn` | number |  |
| `atk` | number | top of the damage range, weapon and skill included |
| `atkmin` | number | AtkMin is the bottom of it: a swing rolls AtkMin to Atk, evenly (tryAttack). |
| `atkbig` | number | *omitempty.* AtkBig and AtkBigMin are the roll against Large and Huge creatures when the weapon in hand swings harder at them, the spear's +50%, and nought otherwise; the panel writes them after the usual range (2026-10-17: "show the range in the attack field in the info panel. so attack 13-25 (X-Y vs large/huge)"). |
| `atkbigmin` | number | *omitempty.* |
| `def` | number | the most a blow can lose: what is worn plus what is held at its top roll |
| `defworn` | number | DefWorn is the part of Def that is worn rather than held: natural armor and the armor on your back, rolled from it to twice it against every blow. |
| `held` | number |  |
| `speed` | number | Speed is how fast the character walks, in Tibia's own units: the number the step time is derived from, so bigger is quicker. |
| `stepms` | number |  |
| `clank` | number | Clank is the noise the four pieces in use add up to: how much further off, in tiles, every monster in the world notices this character than it would notice one in nothing at all. |
| `atkcalc` | string | *omitempty.* AtkCalc, DefCalc and ClankCalc are the working behind Atk, Held and Clank, written out as sums for the hovers of the panel's Attack, Defence and Clank rows (2026-10-08: "when hovering over clank, attack, and defence, in addition to a short description of how it works, show the calculation for the value"). |
| `defcalc` | string | *omitempty.* |
| `clankcalc` | string | *omitempty.* |
| `hpregenms` | number | *omitempty.* HPRegenMs and ManaRegenMs are how long a point of each takes to come back on its own right now, in milliseconds as the world's ticks pay it, and nought while it is not coming back at all, with the reason in the matching Why: "hungry", "PZ", "no mana yet", "dead". |
| `hpregenformulams` | number | *omitempty.* |
| `hpregenwhy` | string | *omitempty.* |
| `manaregenms` | number | *omitempty.* |
| `manaregenformulams` | number | *omitempty.* |
| `manaregenwhy` | string | *omitempty.* |
| `target` | number |  |
| `chase` | bool | Chase is set when the player walks after the target rather than fighting it from where they stand: a monster picked a second time, or a player being followed. |
| `walking` | bool | *omitempty.* Walking is set while the server has a clicked walk of the player's still to walk. |
| `foe` | number | Foe is the creature the player is swinging at right now: the target when it is within reach, else whatever is standing next to them, else zero. |
| `inv` | array of [Item](#item) |  |
| `equip` | array of number | Equip lists what is in use, by item id: the weapon in hand, the shield held, the armor worn and the helm on the head, and since 2026-10-04 the cloak over the armor, the light carried and the fillet on the brow. |
| `equipvars` | array of string | *omitempty.* EquipVars is which pile of each kind in Equip is in use, beside it, where piles of a kind differ (a ring's number); left out while every one is the plain pile. |
| `chosen` | array of number | Chosen lists the pieces picked by hand rather than by the bag, by item id. |
| `pins` | array of number | *omitempty.* Pins lists the kinds of thing the player has pinned to the top of the bag, by item id (internal/game/pins.go). |
| `worn` | array of [Worn](#worn) | Worn is every place something is worn or held, for the Equipment panel (2026-10-06: "Add a panel for equipped items"): what is there, how it came to be, and what in the bag could go there instead. |
| `skills` | array of [SkillView](#skillview) |  |
| `indepot` | bool | *omitempty.* InDepot is set while standing on the depot floor, where Depot lists what the character has stored; right-clicks then move items between the bag and the depot instead of dropping. |
| `depot` | array of [Item](#item) | *omitempty.* |
| `admin` | bool | *omitempty.* Admin is set for a character who is an admin, dressed for it or not (the creature's own Admin is the dress): the bag's quantity dialog then offers Destroy beside Drop (2026-10-08). |
| `atvendor` | bool | *omitempty.* AtVendor is set while within three tiles of a shopkeeper, in sight of them (beside them until 2026-10-08): Grubb (Wenna's counter until 2026-09-24), or Elrond since 2026-09-27, where Vendor lists what he sells, a lot to a line, for the Vendor panel: a click on a line buys one lot of it. |
| `vendor` | array of [CounterItem](#counteritem) | *omitempty.* |
| `vendorsell` | array of [CounterItem](#counteritem) | *omitempty.* VendorSell is the other side of the counter, sent with Vendor: one line per pile in the bag that Grubb would buy, naming its slot, with what he pays for one lot and how many make the lot. |
| `vendorname` | string | *omitempty.* VendorName is who the Vendor panel is buying from and selling to, sent with Vendor: Grubb in the castle or Elrond in Rivendell, since 2026-09-27 ("make elrond a vendor"). |
| `market` | [Market](#market) or absent | *omitempty.* Market is Grubb's market, for the Market panel: sent while the player has opened it from his menu and stays within three tiles of him, and while they stand at a depot (2026-10-13: "Allow the market whenever the depot is available"), and absent otherwise. |
| `food` | number | seconds of nourishment left; 0 means hungry |
| `foodmul` | number | *omitempty.* FoodMul is how many times as long as the ordinary a second of food lasts this character: 1.5 for a hobbit, whose stomach empties at two thirds of the rate (2026-09-29: "implement all the race differences as proposed"), and absent for everybody else. |
| `dead` | bool |  |
| `pz` | bool | PZ is set while standing in a protection zone; CanLogout says whether a logout would be accepted right now, so the client can grey the button rather than let the player discover the rule by being refused. |
| `canlogout` | bool |  |
| `logoutwhy` | string | *omitempty.* LogoutWhy is the server's own sentence for why a logout would be refused, empty when it would not: the button's hover and the In combat status show it rather than guessing at the rule. |
| `fight` | number | *omitempty.* Fight is how many seconds are left of the fight window, 0 when out of a fight (2026-10-22: "there should be a "in a fight" status icon"): a blow struck or taken, or anything having you picked to attack, under ten seconds ago, outside a protection zone. |
| `online` | number | Online is how many players are in the world, you included. |
| `stance` | string | Stance is whom the player has chosen to be able to attack: "white" (whoever attacked them), "yellow" (those, and anybody wearing a skull) or "red" (anybody). |
| `pvp` | bool | PvP is the toggle the stance replaced, kept for clients written against it (the bot in internal/botclient): true exactly when the stance is red, which is what the toggle's on was. |
| `autostance` | string | AutoStance is what the reflex swings at without being asked: "none" (nothing but the chosen target), "aware" (only monsters that know about you) or "all" (any monster in reach, those that know about you first). |
| `auto` | bool | Auto is the toggle the auto stance replaced, kept for clients written against it (the bot in internal/botclient): true for any stance but none, which is what the toggle's on was. |
| `chaseon` | bool | ChaseOn is the player's standing choice about walking after what they pick a fight with; Chase, above, is whether the target they have now is being walked after. |
| `horse` | bool | Horse is having a horse of the Mark to ride (2026-10-12: "Add mounts"), which puts the Ride button among the Controls; whether you are on it is your own creature's Riding. |
| `riding` | bool | Riding is being on it, for the Ride button's word and the status beside the info button. |
| `rideready` | number | *omitempty.* RideReady is when the horse will stand for you again after a fight, in the server's Unix milliseconds (the clock State.Now is on): sent only while you have a horse, are off it, and the fight window that refuses a mount is still running, and not where there is no riding anyway (under the ground without a wizard's staff up: 2026-11-02, "Don't show the mount timer if we're in an area we can't ride anyway"), and the Ride button counts it down (2026-10-13: "show a timer for how long until a mount can be used, in the button"). |
| `rideno` | string | *omitempty.* RideNo is why you may not get on your horse now, the word a press of Ride would be answered with: sent only while you have a horse and are off it, and empty when you may ride. |
| `debug` | bool | Debug is whether every swing this player is part of is written into the log as the sum behind it. |
| `adm` | [AdminStatus](#adminstatus) or absent | *omitempty.* Adm is what an admin has switched on for themselves, for the status line (2026-10-27: "there should be a condition/status icon to tell if i'm invisible/invincible etc."): nil for everybody else, and for an admin with none of it on. |
| `skull` | number | *omitempty.* Skull is how many seconds are left of the skull over your own head, 0 for none: the status line counts it down, since what matters about a skull is when it goes. |
| `locked` | bool | *omitempty.* Locked is being protection zone locked: an unjust attack's lock, which lasts as long as the fight does, or a skull's. |
| `mana` | number | Mana is what is left to cast with and MaxMana what the character can hold — five a level, so the bar is worth drawing from level one even though nothing can be cast until level five. |
| `maxmana` | number |  |
| `haste` | number | *omitempty.* Haste is how many seconds of haste are left, 0 for none, counted down on the status line: what matters about a spell with a clock on it is when it stops. |
| `lux` | number | *omitempty.* Lux is how many seconds of light are left, 0 for none, counted down on the status line beside the other two and for the third time for the same reason: what matters about a spell with a clock on it is when it stops — and for a lamp in a cave, rather more than for the others, since what happens when it stops is the dark coming back to two tiles with whatever was in it. |
| `pipe` | number | *omitempty.* Pipe is how many seconds are left of a lit pipe of Longbottom Leaf that is doing the smoker good, 0 for none (2026-10-17: "add a smoking condition/status icon"): sat where it was lit and out of a fight, so the status line shows it exactly while the mending is doubled. |
| `rate` | number | *omitempty.* Rate is the temporary rate over experience and skill training while an admin has one up with /temprate, 0 otherwise (2026-10-22: "allow admin to modify a server training/exp multiplier ... it should show up ... as a condition"; 2026-11-03: "the condition status shows N to players"). |
| `rateleft` | number | *omitempty.* |
| `zonesoff` | bool | *omitempty.* ZonesOff is set while an admin has taken every protection zone away from everybody with /pzoff, until /pzon (2026-10-27: "add a command to remove pz server wide"). |
| `poison` | number | *omitempty.* Poison is the dose running in you: what the next beat will take, one less each beat after, and nought for none (2026-09-23). |
| `poisonleft` | number | *omitempty.* PoisonLeft is the health the dose will still take if nothing bites again and nothing draws it out: every beat left, each as it would land on you now (a ring of clean blood halves them), and nought on protected ground, where the beats take nothing. |
| `burning` | number | *omitempty.* Burning is the fire on you the same way: what the next beat takes, one less each beat after (2026-10-09; see game/burning.go). |
| `paralysed` | number | *omitempty.* Paralysed is how many seconds are left of a paralysis, rounded up, and nought for none (2026-10-12): the Witch-king's shriek, while which the character cannot move or strike. |
| `entangled` | number | *omitempty.* Entangled is the same for Shelob's web, which holds the feet and leaves the arms free: the character cannot move but can fight. |
| `slowed` | number | *omitempty.* Slowed is the same for a slow, which halves the pace walked at (2026-10-27): Sauron's rift. |
| `fading` | number | *omitempty.* Fading is how much of the character's health a great ring has taken, in parts of a hundred, and nought for none (2026-10-05: "Implement the lesser, Seven, Nine and One Ring"): a part a minute while it is worn (every three for a hobbit or a dwarf), down to thirty, and a part a minute back once it is off. |
| `fadingon` | bool | *omitempty.* |
| `fadingone` | bool | *omitempty.* |
| `bond` | number | *omitempty.* The blood bond, which goes out in two pieces because it has two ends and they are no longer two views of one fact. |
| `bondwith` | string | *omitempty.* |
| `bonded` | number | *omitempty.* |
| `bondedby` | array of string | *omitempty.* |
| `light` | number | *omitempty.* Light is how far, in tiles, this character's light reaches on the level they are standing on: a lamp, a glowing blade or something that shines. |
| `tint` | string | *omitempty.* Tint is the colour part of that light is drawn in, "red" for a fire or "blue" for an elven light, and TintR how far that part reaches, in tiles; both off the wire for plain light or none. |
| `tintr` | number | *omitempty.* |
| `alert` | number | *omitempty.* Alert is this character's own alertness, in tiles: how far round them they notice other players (playerAlertness, or more with a great ring on). |
| `dark` | number | *omitempty.* Dark is this character's dark sight, in tiles: how far round them anybody is seen without a light (PlayerData.darkSight), which is what the client now draws at full brightness instead of Alert (2026-10-19). |
| `lit` | number | *omitempty.* Lit is this character's light sight, in tiles: how far off a monster standing in a light is seen (PlayerData.lightSight), the whole view of 10 but for a Great Ring's wearer, who sees 5. |
| `blind` | number | *omitempty.* Blind is how far round this character the floor is drawn as seen by blind sight, past their dark sight: the whole view for an admin who sees everything (/seeall, or gone /invisible), and absent for everybody else, whose blind sight of a tile is inside Dark. |
| `wraith` | bool | *omitempty.* Wraith is whether this character has wraith-sight, which a Great Ring gives: they see whoever a Great Ring is fading as anybody, where others make such a one out only within their dark sight. |
| `spells` | array of [SpellView](#spellview) | Spells is every spell this character's pool can hold, each carrying whether they can cast it at this moment and, if not, why not — and if so, whether it would be worth the mana. |
| `spellsnote` | string | *omitempty.* SpellsNote is a line the panel shows in place of the buttons, when there is nothing on it to press: why, and what to do about it — "You are dead. Return to the temple to cast again.", "No spells yet. Light, the first, comes at level 5." Absent while there are buttons. |

<!-- END reference -->
