v0.6.0 — saves survive a release, Employee Rotation is real, and the lobby

asks what game you want

Three queued items. The last matters most.

A RELEASE NO LONGER DESTROYS EVERY GAME IN PROGRESS.

Four consecutive releases killed every game on the box, one of them a
release that changed only how the board is drawn. The reasoning behind the
refusal was always right — a move legal under old rules may not be legal
under new ones, and half-replaying a save is worse than refusing it. The
TEST was wrong: it compared engineVersion for exact equality, and that
stamp is the package version, which moves for a CSS fix.

Whether a save still replays has an exact answer, so it is now asked
directly. loadGame reads the file and judges nothing; tryResumeSession
replays the intents and reports the first one the engine refuses. A save
stamped with a version this server has never run resumes fine provided its
moves replay — verified against a file hand-stamped 0.4.9-ancient. One that
genuinely does not replay is still refused, but the log names the move
rather than two version strings: "move 3 of 8 (localOps.choose) is rejected
by the current rules with OPTION_ALREADY_CHOSEN".

fromMultiplayerSave had to stop lying first. It has always stopped at the
first unacceptable intent and done so in silence, which was survivable only
because the version gate meant a doomed replay was never attempted. Now
that the replay IS the check, it returns where it stopped and why.

Deliberately not done: resuming a partly-replayable game at its last good
move. That silently rewinds a game to a position nobody played to while
every browser holding a later Frame carries on unaware. Refusing leaves the
file intact, so putting the previous version back still recovers it.

EMPLOYEE ROTATION IS IMPLEMENTED, SISTER TRAINS IS DELETED.

Two of the four optional-rule flags were read by nothing at all. Employee
Rotation is four lines in advance.ts, because the seat/player split (D9)
exists for precisely this rule: seating is the only thing that moves, so
Revenue, hands, the Superintendent and whose turn it is travel with the
player, and the Office, district, grid and any trains standing in it stay
with the chair. Inheriting the district you move into is the point of the
rule, not a side effect. "Left" is seat + 1, matching playerLeftOf.

Sister Trains is deleted rather than built: Q9 records that the Second
Section card supersedes it, and that card exists, so the flag was a toggle
for a rule the game no longer has.

THE LOBBY ASKS WHAT GAME YOU WANT TO PLAY.

Creating a game asked for a name, a mode and a table size; every other dial
was hardcoded. A Game settings block now carries the same set the solitaire
dialog does — seed, starting hand, the three revenue rates, Days, the
combined-Revenue floor, both collision caps, the opponent-card toggle —
plus the three surviving optional rules. Mode and table size set the
defaults and everything stays editable. The seed is honoured, so a game can
be reproduced or compared.

Verified: 682 tests pass (679 + 3). The rotation tests were mutation-checked
both ways — disabling the rotation and turning the table the wrong way each
fail the suite. Live: a save stamped 0.4.9-ancient resumed, an injected
illegal move was refused by name, and a create with every dial set to a
non-default value came back out of game.json with all of them intact,
including seed 777.

Two of my own assertions were wrong on the way and the tests caught them:
the Fedora legitimately passes at Stage 12 (§5) so it cannot be compared
against its own earlier value, and dispatchUsedToday is cleared at every
Day boundary so it cannot mark a district.
This commit is contained in:
Jesse.Markowitz
2026-08-21 21:45:34 -04:00
parent 51710498f5
commit 40f07b0710
37 changed files with 535 additions and 102 deletions
+37 -14
View File
@@ -23,15 +23,14 @@ Queued from the 2026-08-20 multiplayer planning session (reasoning in Multiplaye
Queued 2026-08-21, from playing the StartOS build:
4. **The lobby must offer every game parameter the solitaire New Game dialog does** — and it
currently offers none of them. Reasoning in Multiplayer below; carries a live balance bug with
it (the combined-Revenue floor is sized for four players whatever the table's real size), so
this is not purely a UI job.
5. **Decide what the four `optionalRules` are** before either dialog offers them — two are live,
two are read by nothing at all. Reasoning in Multiplayer below.
6. **Stop every release destroying every game in progress** — the check is exact equality against
the package version, and most releases do not touch the rules. Reasoning in Multiplayer below;
the recommendation is to replay-and-see rather than to guess from a version number.
4. ~~**The lobby must offer every game parameter the solitaire New Game dialog does**~~ — done in
v0.6.0.
5. ~~**Decide what the four `optionalRules` are**~~ — done in v0.6.0: `sisterTrains` deleted,
`employeeRotation` implemented, the other two were already live.
6. ~~**Stop every release destroying every game in progress**~~ — done in v0.6.0, by replaying the
save rather than comparing version strings.
Nothing else queued at the moment.
---
@@ -503,7 +502,7 @@ Deferred while planning the server; decisions and reasoning are in `docs/archite
shift it", "never grows the table, whoever asks", "refuses a chair that is not at the
table").
- [ ] **EVERY RELEASE DESTROYS EVERY GAME IN PROGRESS, AND MOST RELEASES DO NOT CHANGE THE RULES.**
- [x] **~~EVERY RELEASE DESTROYS EVERY GAME IN PROGRESS~~ — fixed in v0.6.0, by option 3.**
Raised 2026-08-21 after v0.5.2, v0.5.3 and v0.5.4 each killed the games on the StartOS box in
turn — v0.5.4's changes were *rendering only*, and it still refused two saved games.
@@ -541,10 +540,28 @@ Deferred while planning the server; decisions and reasoning are in `docs/archite
free. The work is in reporting a partial failure well: the game is intact up to the
rejected intent, and a player would probably rather resume there than lose it entirely.
**(3) is the one worth doing**, and (1)/(2) are what to reach for only if a replay ever
becomes too slow to do on boot. Decide before the next release that changes a rule, not after.
**(3) was done.** `loadGame` no longer looks at the version; `tryResumeSession` replays the
save and reports the first intent the engine refuses, and `index.ts` resumes or refuses on
that. A save stamped with a version the server has never run now resumes, provided its moves
replay — verified against a file hand-stamped `0.4.9-ancient`. A save that genuinely does not
replay is refused as before, but the log now names the move: *"move 3 of 8
(localOps.choose) is rejected by the current rules with OPTION_ALREADY_CHOSEN"*.
- [ ] **THE FOUR `optionalRules` ARE SETTABLE BY NOTHING, AND TWO OF THEM DO NOTHING.** Split out
One thing deliberately NOT done: resuming a partially-replayable game at the last good move.
The note above suggested a player would rather have that than nothing, and on reflection it
is worse — the game would silently rewind to a position nobody played to, and the browsers
holding a later Frame would have no idea. Refusing keeps the file intact, so putting the
previous version back still recovers the game. Revisit only with a way to tell the table what
happened.
- [x] **~~THE FOUR `optionalRules` ARE SETTABLE BY NOTHING, AND TWO OF THEM DO NOTHING~~ — resolved
in v0.6.0.** `sisterTrains` is deleted: Q9 records that the Second Section card supersedes it,
and that card is built. `employeeRotation` is implemented — the rotation is four lines in
`advance.ts` because the seat/player split (D9) exists precisely for it, so Revenue, hands and
the Fedora travel with the player and the district stays with the chair. All three survivors
are now settable from the lobby. Original reasoning kept below.
**Original note:** Split out
at Jesse's request 2026-08-21, to review on its own rather than as a footnote to the lobby
item below. `GameConfig.optionalRules` (`state.ts:585-588`) carries `reducedVisibility`,
`sisterTrains`, `employeeRotation` and `emergencyToolbox`. Neither the solitaire New Game
@@ -567,7 +584,13 @@ Deferred while planning the server; decisions and reasoning are in `docs/archite
them, or label them on screen the way an unbuilt Enhancement already labels itself. Doing
that is what decides whether this is a UI job or a rules job.
- [ ] **THE LOBBY OFFERS NO GAME PARAMETERS AT ALL, AND THE ONE IT INFERS IS WRONG.** Raised by
- [x] **~~THE LOBBY OFFERS NO GAME PARAMETERS AT ALL~~ — done in v0.6.0.** A "Game settings" block
on the create form carries the same dials the solitaire dialog has — seed, starting hand, the
three revenue rates, days, the combined-Revenue floor, both collision caps, the PvP toggle —
plus the three surviving optional rules. Mode and table size set the defaults and every field
stays editable, matching the solitaire dialog's own behaviour. Original note below.
**Original note:** Raised by
Jesse 2026-08-21 after playing the StartOS build. Creating a multiplayer game asks for a
display name and a mode, and nothing else — every other dial comes from
`defaultMultiplayerConfig(mode)` (`web/game.ts`), hardcoded, with no way to change it.