0.7.9.8:0 — bundle Station Master v0.7.9.8

Eight releases of fixes since v0.7.9, most of them multiplayer, which is
what this package is for. Submodule pin and `startos/versions/current.ts`
only — no Dockerfile, main.ts or manifest change, as an ordinary bump
should be.

Bumped in place rather than spun off: v0.7.9's migration body is empty,
which is `versions.md`'s common case, and git history keeps its release
notes.

FOUR-COMPONENT VERSION, CHECKED. Every example in `versions.md` has
three, so `0.7.9.8:0` was parsed with the SDK's own ExVer parser before
being written down: upstream `[0,7,9,8]`, which sorts above `[0,7,9]`, so
a box on 0.7.9:0 takes this as an update rather than refusing it as a
downgrade.

GAMES IN PROGRESS ON THE BOX RESUME, and that was established rather than
hoped for. v0.7.9 broke solitaire saves because it changed a RULE, and
the rule behind that is that a save is a seed plus the moves played,
replayed through the CURRENT rules — so the question for any bump is
whether something became illegal. `git diff v0.7.9..HEAD -- src/engine/`
is, in full: `isExpedited` exported and widened to a structural parameter
type, `isFreight` exported, one new read-only helper
(`freightRuleSpentHere`, which only the blocked panel asks), and
`check('draw.end')`'s inline hand-limit test replaced by a call to
`overHandLimit(state, player)` holding the identical expression. No
predicate changed its answer. The release notes say so in all five
locales rather than leaving it to be discovered.

UPDATING.md CORRECTED: it said a running server's saves "are stamped with
`engineVersion` and refuse to resume under a mismatched one". They are
stamped with it and it is reported, but `src/server/index.ts` decides by
attempting the replay and names the versions only inside the failure
message — its own comment says why, that "some version differs" was never
enough to act on. A doc that sends the next person to schedule downtime
for a bump that needs none costs about as much as the reverse. It now
carries the diff command to ask the question of the code instead.

What players get, briefly: two multiplayer information leaks closed (the
seed in the shared log, and a blind Home Office draw naming its card); a
history panel that came back EMPTY after a mid-game browser reload; the
extended-play vote no longer naming an arbitrary player when the vote is
open to everyone at once; a train held at your Limits by an Interlocking
no longer vanishing off the board until a track frees; trains queued for
a Crew Tray saying so with the free count; a Red Flag drawn on the map; a
spent Telegraph/Telephone/Radio struck through; the Campaign Train saying
whether its speeches are made; and two bugs from the last playtest — the
Express's one-freight-car-per-location rule explained where it refuses
you, and westbound trains drawn in the correct half of a card.

README.md and instructions.md updated in the same change.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y5boPxP6JHRYMm8adXaF5R
This commit is contained in:
Jesse
2026-09-08 03:56:40 -04:00
co-authored by Claude Opus 5
parent bd802cb512
commit 8f2f733a66
5 changed files with 232 additions and 92 deletions
+53
View File
@@ -13,6 +13,59 @@ 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.7.9.8.** Eight releases of fixes since 0.7.9, most of them multiplayer, and
**games in progress on this server resume normally** — which was checked rather than assumed.
**Why saves are safe this time, and how that was established.** 0.7.9's breakage came from a rules
change, and the general rule behind it is that a save is a seed plus the moves played, replayed
through the *current* rules — so any change that makes a once-legal move illegal stops an older save.
The question for this bump is therefore narrow: did anything become illegal? `git diff v0.7.9..HEAD
-- src/engine/` is, in its entirety, four things — `isExpedited` exported and widened to a structural
parameter type, `isFreight` exported, one new read-only helper (`freightRuleSpentHere`, which only
the blocked panel asks), and `check('draw.end')`'s inline hand-limit test replaced by a call to
`overHandLimit(state, player)` holding the identical expression. **No predicate changed its answer.**
Eight releases, none of them in the rules.
Note that `engineVersion` is *not* a gate. `src/server/index.ts` attempts the replay and reports the
stored and running versions only in the failure message; a game is refused because a move no longer
replays, never because the version string differs.
**Two multiplayer information leaks.** The random seed was announced in the shared narration log at
game creation, and a blind Home Office draw resolved the drawn card to its real name for every seat.
Both were found by reading a plan rather than by a test, which is why 0.7.9.4 added a systematic
redaction net: a seat's Frame, the spectator projection and the narration are serialised and searched
for every opponent card id, every card name unique to one hand, the seed, and any private decision
data — with an allow-list of every public property that fails the suite when a field is added.
**The history panel came back empty after a mid-game reload.** `Frame.lines` carried the whole
narration log on every push to every seat and nothing read it — `RemoteSession` accumulates from
`Push.lines` alone. The waste was masking the fault: `connect()` cleared the frame cache but not the
narration watermark, so a reconnecting seat was told "nothing new since your last push" while the
browser it was answering had just reloaded from an empty accumulator.
**Whose turn it is, during the extended-play vote.** The §3.3 vote is parallel — every un-voted seat
may vote at once — so there is no actor to be, and the engine says so. The turn chart named the last
seat to move anyway, beside a tally correctly showing three seats outstanding. Cause: two functions
answering one question, one carrying a status guard and one not; there is one now, and it is the same
function that *refuses* an intent, so the screen can no longer name somebody the server would turn
away.
**A train held at the Limits was drawn nowhere.** An Interlocking stops an inbound train on the Limit
Track instead of colliding with a full Office. `arriveAtOffice` removes the tray from the Mainline
node's `transits` and the Interlocking branch never assigns `tray.position`, so with the map drawing
mainline nodes from `transits` and squares from `position.at === 'grid'`, the train was in neither —
it vanished off the board until an A/D track freed. Fixed in the view; the engine state was right.
**Also in this range.** Trains queued for a Crew Tray (a Timetabled departure, a played Extra, an
ordered second section) now report themselves with the free-tray count, where two of the three had
been waiting invisibly behind an exhausted pool. A Red Flag standing at an Office's Limits is drawn
on the map. A spent Telegraph/Telephone/Radio is struck through and says when another district holds
the Fedora — it is the Superintendent's own devices that get spent, and the Fedora moves every three
Stages. The Campaign Train says whether its speeches are made, which is what decides whether leaving
it off the Office square is a fault. Two bugs from the last playtest session: the Express's one
freight car per location rule is explained where it refuses you (the panel had been describing the
industry instead), and westbound trains are drawn in the correct half of a Mainline card.
**Bundled version: 0.7.9.** Enforces solitaire's collision limits, which had been offered as
settings and never checked — and that is a rules change, so **a solitaire game in progress will not
resume if it has had collisions**.