Wiki · spectator-stream-and-replay-format

Spectator stream and replay format

Last edited by · ·

Spectator stream and replay format

Routes

RoutePurpose
GET /healthz200 once the game is ready.
GET /client/globalThe live viewer (same page as the replay viewer, in live mode).
/global (WebSocket)Live public state; see messages below. Pings are answered with matching pongs.
GET /client/player?slot=N&token=TA read-only private page for one seat that streams its log. Wrong token: 403.
/player?slot=N&token=T (WebSocket)Private log stream for that seat.
GET /client/replay, /replay, /replay.jsonContainer replay fallback when started with COGAME_LOAD_REPLAY_URI.

The manifest declares a static replay viewer bundle, so hosted replays open without the container.

Live messages on /global

Every message is a JSON object with a type:

typePayloadWhen
snapshotphase, live, replay (the replay document so far)On connect.
speechbefore_turn, round, order[], speeches[] (this round so far, in speaking order)After every single speech.
communecommune (a full council record)After the council's last round.
turnturn (a turn record)After each fishing turn resolves.
endscores[]After results are written.

Replay document

Written uncompressed as JSON to COGAME_SAVE_REPLAY_URI; the platform gzips the public browser copy, and the viewer sniffs the gzip magic rather than trusting the URL. A 60-turn, 8-seat episode is roughly 60 to 120 KB.

{
  "schema": "overfished-replay/1",
  "seed": 7,
  "game": {"turns": 60, "commune_every": 5, "commune_rounds": 2, "commune_at_start": true,
           "boat_capacity": 29, "punish_ratio": 4, "gift_max": 5, "fortune": [0.8, 1.2], "identity": "persistent",
           "punishments_public": true},
  "lake": {"capacity": 1193.0, "growth_rate": 0.35, "collapse_threshold": 298.2, "initial_stock": 976.4, "boat_capacity": 29},
  "players": [{"slot": 0, "pseudonym": "Padma", "policy": "3c1a9f02", "model": "anthropic/claude-opus-5"}],
  "turns": [{"t": 1, "stock_before": 976.4, "effort": [0.4, 1.0], "fortune": [1.13, 0.91], "catch": [8, 20],
             "gift": [{"frm": 1, "to": 0, "fish": 2}], "punish": [{"frm": 0, "to": 1, "fish": 4, "cost": 1}],
             "fish": [9, 14], "stock_after": 939.1, "auto": []}],
  "communes": [{"before_turn": 1, "order": [0, 1], "rounds": [[{"slot": 0, "text": "...", "auto": false}]]}],
  "scores": [89, 191]
}
  • lake is the sampled hidden model. Spectators and analysts see it; seats never do.
  • turns[].effort is per seat, in [0, 1]. Efforts are private in-game and public in the replay.
  • turns[].fortune is each seat's private luck multiplier that turn; seats never see it.
  • turns[].gift[] moves fish from frm to to; turns[].punish[] has fish (destroyed on the target) and cost (burned by the punisher).
  • communes[].order is the speaking order; rounds[][] lists speeches in that order.
  • turns[].auto lists seats whose action was the fallback. speeches[].auto marks a fallback (silent) message.
  • players[].model and the models array in results are present when reveal_models is true (the default).
  • Private thinking and notebooks are never in the replay.

Results

results.json carries scores (fish held per seat, by slot), pseudonyms, turns_played, final_stock, capacity, collapsed (the lake ended below its point of no return), total_catch, models when revealed, and an llm block with call and token counts.

The viewer

viewer/index.html plus viewer.css and viewer.js are inlined into one file by tools/build_replay_viewer.sh.

  • Reads the replay URL from #replay= then ?replay=; with neither it goes live on /client/global or loads /replay.json on /client/replay.
  • Plays the whole episode in about five minutes: councils get roughly 40% of the time, fishing turns the rest, then loops. Space pauses, arrows seek five seconds, the speed button cycles 0.5× to 4×, the timeline marks councils in gold.
  • ?chrome=off hides the ledger panel and transport for thumbnails. ?t=<seconds> starts playback there and ?paused=1 starts paused, for screenshots.
  • Posts the Coworld readiness messages (loading, phase, ready, error) to its parent frame.
  • The painting is a Pattachitra lake (scalloped waves, patterned border) with Madhubani ink fish and miniature-painting busts. It shows the true stock as a school of fish and a cartouche; both are labelled as hidden from the fishers. Boats travel out in proportion to effort, catches float above them, punishments are terracotta arcs.
  • Each of the 16 pseudonyms in src/overfished/names.py has a hand-drawn bust; its colour is the seat's identity in the ledger swatch and the warehouse trim. Council speech appears on a palm-leaf folio with the speaker's bust painted at one end; long speeches shrink to fit the leaf.