The developer's guideFrom an account to a game on sale
Seven chapters, each true of the service as it runs. The reference for the bytes is docs/protocol.md; this is the path.
Awards and the daily
Retro Play knows no game's rules. Accounts, sessions, presence, the boards, the awards a player holds — those it keeps for every game. What a game's awards are, what its daily is, and what a score must look like to be believed — those are the game's, and there are two ways to give them to the service.
Declared on the game's page — no server
For most games this is the whole of it. Your organisation → Games → the game → Awards takes an awards.json:
{
"awards": [
{ "key": "FIRST_RUN", "name": "FIRST RUN", "description": "A game finished", "points": 50, "hidden": false },
{ "key": "MARATHON_MAN", "name": "MARATHON MAN", "description": "Marathon to level 15", "points": 100, "hidden": false },
{ "key": "GHOST", "name": "???", "description": "Something nobody expects", "points": 150, "hidden": true }
]
}
- Up to twenty awards, 1000 points between them.
keyis 3–16 ofA–Z0–9_, what the wire'sAWARDnames;name≤ 16 anddescription≤ 32 are what the pages and the client show; ahiddenaward shows as???until it is held. - Bake the same file into the build — the client and Tetriblox generate their award tables from it — so the two cannot disagree. An award is matched by key across edits, so its text may change; append, never reorder, because the bit in
AWARDSis the award's place in the file. - Mark an award on the card the moment it is earned, so it works offline, and post
AWARD <key> <mac>when you can;AWARDSat login brings the service's list back to a new card.
The daily comes with it. Send X DAILY and the service answers DAILY <yyyymmdd> <seed>: one seed a UTC day, SipHash-2-4 under your program key over the text DAILY <yyyymmdd>, the low 32 bits. Play the day's run on that seed and post it as a SCORE on mode D<yyyymmdd> — the day the seed named, taken today and yesterday — and the game's page shows DAILY — 19 SEP 2026 with a week of them. Everyone on the same seed, a board a day, nothing to host.
A game service of your own — rules beyond that
A game that wants to refuse a row its rules could not have produced ("too high for its lines", "a sprint is forty lines"), vouch for boards beyond its registered modes, or answer its own X verbs runs a small HTTP server that Retro Play forwards to — the game itself never dials it. Register it with the service's admins (rpm game service <ID> <url> <secret>), and implement four signed routes:
| Route | Retro Play sends | You answer |
|---|---|---|
POST /x | {user, game, platform, line} | {"reply": "…"} — one line, passed to the game verbatim |
POST /score | {user, game, platform, mode, score, lines, level, seconds, initials, build} | {"accept": true}, or {"accept": false, "reason": "…"} — the game sees ERR 422 SCORE: … |
GET /boards | — | [{"mode": "D20260919", "title": "DAILY — 19 SEP 2026", "by_time": false}, …] |
GET /awards | — | the awards.json shape above, with "game": "<ID>" |
Every request carries X-RPM-Timestamp and X-RPM-Signature: hex(HMAC-SHA256(secret, timestamp ‖ "\n" ‖ path ‖ "\n" ‖ body)); verify it and refuse a stale timestamp. Two seconds to answer, or the game sees ERR 503 and a SCORE it should have judged is refused rather than taken unjudged. A game with a service must play as well with it unreachable as with it there. The reference implementation is tetriblox-server; the contract is docs/game-service.md.
Retro Play checks the MAC, the rate and the line's shape before your service is asked; your service judges the row, not the player.