docs play ▸

Replays

A replay is a round's start and every input, at its millisecond. Played through the engine, it is the round again: the same text, the same heat, the same score, on any machine. That's what the server scores, what share links play, what the history and the boards play, and what typepper --replay shows. Code: typepper-engine/src/replay.rs.

Format 1

Little-endian, no padding.

FieldTypeExampleMeaning
magic3 bytes54 50 52 (TPR)this is a typepper replay
formatu801the byte layout; readers refuse others
engineu3201 00 00 00the rules that recorded it (engine::VERSION)
mode lengthu807at most 32
modeUTF-8time:15Mode::key
seedu64the round's rng
countvarint03how many inputs follow
inputscount × (Δms varint, op varint)

Everything up to the seed is the header: fixed-width and readable without the engine, so the day the server keeps two engine versions it can read a replay's version before choosing which engine plays it.

Varints

Unsigned LEB128: 7 bits per byte, low bits first, the top bit meaning "another byte follows". 95 is 5f; 130 is 82 01; 16384 is 80 80 01. A reader stops at 64 bits.

Inputs

That's about 2.5 bytes a key: a 15 s blitz is about 330 bytes, a 60 s one about 1.5 KB.

Example

BLITZ 15s, h, then i 130 ms later, then a backspace 95 ms after that:

54 50 52                   "TPR"
01                         format 1
01 00 00 00                engine 1
07 74 69 6d 65 3a 31 35    "time:15"
08 07 06 05 04 03 02 01    seed 0x0102030405060708
03                         3 inputs
00 6a                      +0 ms    'h' (0x68 + 2)
82 01 6b                   +130 ms  'i'
5f 00                      +95 ms   Backspace

These 35 bytes are the format_1_bytes_are_these test: if format 1 changes by accident, it fails.

Left out on purpose

Decoding

Replay::decode(bytes) takes exactly one replay; Replay::decode_prefix(bytes) reads one off the front and returns the rest, for a submission's back-to-back replays. Neither panics on any input.

Refused asWhen
TooBigover 1 MiB (MAX_BYTES, about 7 hours of zen at 60 wpm)
NotAReplaythe magic isn't TPR
Format(n)a format other than 1
Truncatedbytes run out, or count claims more inputs than bytes could hold
Modethe mode key is over 32 bytes or not UTF-8
Charan op that isn't a Unicode scalar value (a surrogate, past char::MAX)
Leftoverbytes after the last input (decode only)
Clockthe inputs' times run past u32 ms

Whether the mode is one the game offers, and whether the engine is this one, is checked by whoever plays the replay: Replay::setup() parses the mode, and the server and the game compare engine with VERSION.

Tests: the golden bytes, round trips (long gaps, é, 🌶, u32::MAX ms), every truncation point, junk, and two replays read off one stream.

Where replays live

WhereWhat
the game, after every roundlast.replay in the data dir (hex text, so localStorage can hold it on the web)
the game, your last 50 roundshistory, one line per round, with its share link once it has one (see game client)
the game, linked and not yet sentoutbox/<unix ms> (see game client)
the serverruns.replay, a bytea next to the server's numbers (see server)

Watching

Either way the replay goes into a fresh Round and its inputs land as their time comes, on the usual screens, under a REPLAY banner. Keys are ignored except esc (back to the menu) and tab or enter (play that mode yourself). Nothing is saved: it isn't your round.

Sharing

sequenceDiagram
    participant G as game
    participant S as server
    participant F as a friend's browser
    G->>S: v1 submit (claimed score + replay bytes)
    S-->>G: ok 7Xf9qyuC 108 - https://r.typepper.com/7Xf9qyuC
    F->>S: GET r.typepper.com/7Xf9qyuC
    S-->>F: page with preview tags, then refresh to typepper.com/?replay=7Xf9qyuC
    F->>S: GET api.typepper.com/v1/runs/7Xf9qyuC
    S-->>F: the bytes
    Note over F: the web game plays the round

The share page's title says what happened (108 wpm · BLITZ 15s · typepper, 31 words · HOT PURSUIT from lv7 · typepper) for link previews; its og:image is the site's. Runs are kept for 8 days, or longer while they're a player's monthly best; after that the link's page says the replay is gone.