# eggox playtest

Play the pushed draft on the server with no browser, and read what its scripts did.

```
eggox playtest [dir] --steps "click Kettle; key space; wait 2"
eggox playtest --push --script scenario.json --room "Brew Room"
eggox playtest --save visits=3 --seed 7 --steps "click Kettle; expect save brews = 1"
eggox logs [dir]
```

`eggox playtest` starts a private copy of the draft on the server, the same
copy a playtest in Eggox would start, and walks in as you. It plays the
steps, then prints, step by step, everything the scripts did: windows,
`set_state`, floor paints, moves, saves, `send`, `log()` lines and every
handler error. At the end come the saves written and any limit the run hit.
It plays the game **as pushed**: push first, or add `--push`.

| step | does |
| --- | --- |
| `click Kettle` | click the first thing with that name (walks to it if it is far) |
| `click 4,7` | click what stands on that tile, or walk there if nothing does |
| `key space` | press a key: `space`, `w a s d`, arrows (`up`...), `q`, `e`, `1` to `9` |
| `ui brew 3` | press a window's button `brew`, with the value `3` (optional) |
| `walk 4,7` | walk to a tile |
| `close` | close the open window |
| `wait 2.5` | let the game run for 2.5 seconds (real time) |
| `leave` | leave the copy |
| `expect save brews = 1` | check the game here (see below) |

Entering is implied: the first step in the report is `enter`, with what the
room's `start` and `enter` handlers did. `walk`, `click` and `key` wait until
the player stands still again (arrived, or blocked), and a click made from
far away is made on arrival, so the next step starts from there. Every step
says which room the player is in and on which tile. A `send()` takes the
playtester into the other room the moment it happens, and the report follows.
A step that did nothing says `(nothing happened)`: a click on a thing with no
`click` handler, say, or a key no script listens to.

## Expectations

An `expect` step checks the game at that point of the run. A failed one does
not stop the run; it is listed at the end, and the command exits with 1.

| expect | passes when |
| --- | --- |
| `expect save visits = 2` | the player's save `visits` is 2 (the value reads as JSON: `2`, `true`, `"a"`, `{"n":1}`; bare text is a string) |
| `expect save visits unset` | the player has no save `visits` |
| `expect game_save best = 10` | the game's own save (`save(key, value)`) |
| `expect window shop` | the open window has `id = "shop"` |
| `expect no window` | no window is open |
| `expect state Kettle = boiling` | the thing called Kettle (or on tile `4,7`) shows state `boiling` |
| `expect at 4,7` | the player stands on 4,7 |
| `expect room Hall` | the player is in the room Hall |
| `expect log brewed` | a `log()` line so far contains `brewed` |
| `expect no errors` | no handler error so far |

## Saves and chance

Playtest saves last between runs, per game, until `--reset` (or until the
server restarts), so a second run starts where the first left off, as a
player coming back would. To start from a given state instead, put saves in
before the run: `--save key=value` for the player, `--game-save key=value`
for the game (both repeatable), or `--saves file.json` with
`{"player": {...}, "game": {...}}`. They go in after `--reset`.

`--seed 7` seeds `math.random` in every room of the run, so the same scenario
rolls the same numbers every time.

Steps come from `--steps "a; b; c"`, or from a file with `--script`: one step
per line, or a JSON list such as `[{"click": "Kettle"}, {"key": "space"},
{"ui": "brew", "value": "3"}, {"wait": 2}]`. Waits add up to 60 seconds at
most, and a scenario has at most 200 steps.

| option | meaning |
| --- | --- |
| `--room <name>` | start in this room instead of the root |
| `--reset` | forget the playtest saves first |
| `--save k=v`, `--game-save k=v`, `--saves file.json` | saves to start from |
| `--seed N` | the same `math.random` rolls every run |
| `--push` | push the folder first |
| `--json` | the report as JSON |

The command exits with 1 when the run had errors or a failed expectation,
so a script or an agent can test for it. With `--json`, `ok` is false and
`failures` lists what failed. You cannot run one while you are playtesting the same account in
Eggox; leave that playtest first.

## Cost

A run ends with what each room cost, measured against what the players in it
bring:

```text
Cost [Hub]: yellow, 161% of the work budget and 12% of the bytes budget per player, would run at 62%. Most: room timer 88% (40x).
```

Every player brings a fixed budget of server work and of bytes per second.
A room within its players' budget is **green** and runs at full speed on any
server. Above it, a room runs slower, and only that game slows down:
**yellow** down to half speed, **red** below that. A red game leaves the
game lists until it is back under budget, and it stays playable through its
door. `Most` names the handlers that cost the most (`killed` is a handler
stopped at its deadline, charged in full). A short run is dominated by its
`start` and the compile; play for a while to see the steady cost. The
Studio's playtest bar shows the same tier while you play.

A game over budget is never stopped for it. Its handlers wait their turn:
a repeating timer that is already waiting is not queued twice, and each
player keeps at most 8 keys, clicks and window buttons waiting. The room's
walking and tick slow to the same speed, which players see on the TiDi
readout. `eggox games` shows how the published game measured in live play.

## eggox logs

The last playtest of the game, in Eggox or headless: every `log()` line,
handler error and limit hit, with the time and the room, and the cost of
each room. Playtest in the
browser, leave, and read what happened from the terminal. The server keeps
only the last run per account, and only until it restarts.

Runtime errors name the handler (`room: key: error: boom`) but not the file
and line; compile errors do.

