0.8.0.12:0 — bundle Station Master v0.8.0.12, and a Restore a Seat action

A player who has lost their browser storage can be put back in their seat
(Gitea#33 in the game repo). The session token is the only identity the game
has and it lives in one browser's localStorage, scoped to the origin joined at,
so a cleared profile, a private window or a different browser locks a player
out of a game that is still running with their session still on disk. Seen at a
real table: of two humans in one game, the host reloaded straight back in and
the joiner met an empty lobby.

New action, restore-seat: pick a seat from a dropdown of the players actually
holding a session — bots never appear, since seatedPlayers is read from the
server's session map rather than guessed from display names — and get back a
link built from this interface's own address. serverApi gained mintClaim; the
dictionary gained 57-64 in all five locales.

THE LINK CARRIES A CODE, NOT THE TOKEN. lobby-and-sessions.md §1 says to keep
the token out of URLs so it is not shoulder-surfed or pasted into a chat, and a
recovery link is exactly what gets pasted into a chat. The code is single-use,
expires in 30 minutes, and the page trades it for the real token over a POST.
Minting is admin-gated because deciding that somebody has lost a seat is a
judgement no route can make safely; spending needs no secret, because the
player following the link holds none.

GAMES IN PROGRESS RESUME NORMALLY, measured rather than assumed:
`git diff v0.8.0.11..v0.8.0.12 -- src/engine/` is EMPTY. The release is the
server's HTTP surface, the client and the docs. The codes live in memory and
are deliberately not persisted, so nothing new reaches the volume and there is
nothing to migrate. README, instructions.md and the release notes in all five
locales say so.

NOT YET EXERCISED END TO END: the two routes and the action are typechecked and
the claim store is unit-tested, but nothing has run them against a live server.
This build is what makes that possible.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017nnuCv8UodHucFfx3LWEoX
This commit is contained in:
Jesse
2026-09-16 20:21:15 -04:00
co-authored by Claude Opus 5
parent da0e5d49d6
commit a70337cf8b
9 changed files with 341 additions and 121 deletions
+26 -14
View File
@@ -13,19 +13,18 @@ Station Master is a railroad operations board game with an authoritative multipl
solitaire needs no server and is not what this package is for. This package runs that server —
the browser client, the lobby, and the intent/SSE API — as a single StartOS service.
**Bundled version: 0.8.0.11.** Sixteen fixes from the second multiplayer playtest. Making up a train
now says what the consist STILL needs, that a player adds one car before the round passes on, marks the
train being loaded on the Division map, and gives an addable car in the yard the page's action colour. A
train's arrival names whose Office it reached. A player watching the board catch up can pause it, look at
another player's Office Area, and sees one consistent Day/Stage/phase rather than a chart running ahead of
the board. Saves download named after the game. v0.8.0.10 before it corrected when the Superintendent is
asked to rule; v0.8.0.9 rebuilt the bot and made the engine about three times faster.
**Bundled version: 0.8.0.12.** A player who has lost their browser storage can be put back in their
seat, through the new **Restore a Seat** action — see Actions below. The session token is the only
identity the game has and it lives in one browser's `localStorage`, so a cleared profile, a private
window or a different browser locks a player out of a game that is still running with their session
still on disk. v0.8.0.11 before it was sixteen playtest fixes, including the New Train panel saying what
a consist still needs; v0.8.0.10 corrected when the Superintendent is asked to rule.
**Games in progress resume normally on this one**, and it was checked the narrow way rather than assumed:
`git diff v0.8.0.10..v0.8.0.11 -- src/engine/` is three files, nineteen insertions — an `owner` field on
the `trainArrived` event, the one line that fills it, and a reworded card description. No predicate
changed its answer, so no once-legal move became illegal. (0.8.0.10 was the exception: it corrected a
ruling and deliberately stranded games holding one. That warning does not apply here.)
**Games in progress resume normally on this one**, and it was checked the narrow way rather than
assumed: `git diff v0.8.0.11..v0.8.0.12 -- src/engine/` is **empty**. The release is the server's HTTP
surface, the browser client and the docs — no predicate was edited, so no once-legal move became
illegal. (0.8.0.10 was the exception: it corrected a ruling and deliberately stranded games holding
one. That warning does not apply here.)
The paragraphs below were written for the 0.7.9.8 bump and still describe how a bump is checked.
@@ -285,8 +284,8 @@ progress on every update, including updates that changed only how the board is d
## Actions
Two of the three read or change the games on the server, and both are `only-running`: what they
report exists only inside the live server process. A save is a seed plus a list of moves, so
Three of the four read or change the games on the server, and all three are `only-running`: what
they report exists only inside the live server process. A save is a seed plus a list of moves, so
"whose turn is it" is answerable only by replaying the game through the engine — which lives in
the game repo, not in this package. The server has already done that work and is asked for the
answer.
@@ -313,6 +312,19 @@ answer.
worth replaying. This is the only way a game ends other than being played to a finish: an
abandoned game otherwise stays active and is resumed on every restart indefinitely.
- **Restore a Seat** (`restore-seat`) — pick a seat from a dropdown of the players actually holding
a session, and get back a one-time link that puts that player into it. **This is the answer to a
lost seat token** (Gitea#33 in the game repo): the token is the only identity the game has and it
lives in one browser's `localStorage`, so a cleared profile, a private window or a different
browser leaves a player locked out of a game that is still running with their session still on
disk. The link carries a **code, never the token** — single-use, expires in 30 minutes — which the
page trades for the real token over a POST as it loads (`lobby-and-sessions.md` §1: keep the token
out of URLs). Anyone who opens the link takes that seat, so it is sent to one person and not to a
public channel. Minting is administrative because deciding that somebody has lost a seat is a
judgement; spending needs no secret, because the player following the link holds none. The
dropdown lists only seats a human holds a token for — bots never appear, read from the server's
session map rather than guessed from player names.
## Tasks
- **Get the join secret to share with players** — raised on install, severity `critical`. Points