v0.5.5 — a remembered session for a game that no longer exists
Reported after updating to v0.5.4: clicking Multiplayer went straight into a game with no lobby and no controls, and the board was blank. Three things lined up. start() enters a remembered session WITHOUT checking it still exists — that is what makes reconnection seamless, and it is why the lobby was skipped. The v0.5.4 update had refused to resume that game, its save being recorded under v0.5.3 and the engine-version check being exact (D7). And createRemoteSession had no onerror at all, so EventSource retried the resulting 404 forever in silence while frame stayed null and nothing rendered. The only escape was clearing site data, and nothing on screen said so. v0.5.3's Manage Game -> End had just widened the same dead end: it closes every watcher's stream, so a player whose game an administrator ended would sit frozen on a stale board indefinitely, for exactly the same reason. GET /api/session?token= is new: a cheap yes/no on whether a token still names a live game. EventSource fires error identically for a transient blip — the expected shape of a game idle for minutes (§9) — and for a 404 it will retry forever, and exposes no status code either way, so the client asks rather than guessing. Only a definite 404 closes the stream and reports the game gone; a flaky network still self-heals. The page then forgets the stored session, says why (ended by an administrator, or the service was updated, which does not carry games across), and drops into the lobby. Forgetting the token is what stops the next load repeating it. It also stops rendering nothing while it waits — "… connecting to the game" sits in the presence banner until the first push arrives, because a page showing nothing is indistinguishable from a broken one, which is what this looked like. Recorded but NOT fixed, in TODO.md: three releases in a row destroyed every game in progress, and v0.5.4's changes were rendering only. The refusal is right, but the test is exact equality against the PACKAGE version, which moves for reasons unrelated to the rules. Three options costed; the recommendation is to replay the save and refuse only if an intent actually rejects — the real question rather than a proxy for it, and a full replay measures ~100 ms. Verified live: /api/session answers 200 for a seated token, 404 once an administrator ends the game, 404 for a garbage token, and /api/stream 404s in the same state — which is the response EventSource had been retrying silently. 673 tests pass.
This commit is contained in:
@@ -29,6 +29,9 @@ Queued 2026-08-21, from playing the StartOS build:
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -500,6 +503,47 @@ 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.**
|
||||
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.
|
||||
|
||||
**Why it happens, and why the design is right as far as it goes.** A save is a seed plus a
|
||||
list of intents (D5), so loading one means replaying those intents through the current engine.
|
||||
A move that was legal under the old rules may be rejected under the new ones, and a
|
||||
half-replayed game is worse than no game — so `loadGame` refuses on any `engineVersion`
|
||||
mismatch and `index.ts` logs it and carries on (D7). Nothing is deleted; rolling the version
|
||||
back makes the games loadable again. That is all correct. The problem is only that the test is
|
||||
**exact equality against the package version**, which moves for reasons that have nothing to
|
||||
do with the rules.
|
||||
|
||||
**Why it is getting worse rather than better.** It was harmless while Jesse was the only
|
||||
player. It stops being acceptable the moment other people are seated: their game is destroyed
|
||||
because somebody shipped a CSS fix. It also interacts badly with the stranded-session bug
|
||||
fixed in v0.5.5 — the refusal is precisely what stranded a browser on a blank page.
|
||||
|
||||
Three ways out, cheapest first:
|
||||
|
||||
1. **A separate rules version, bumped by hand.** `RULES_VERSION` in `content.ts`, stamped into
|
||||
the save instead of `package.json`'s version, and raised only when a change can alter
|
||||
whether an intent is legal. v0.5.4 would not have touched it and both games would have
|
||||
survived. Cheapest and the least clever, but it is a judgement call on every release, and
|
||||
getting it wrong silently corrupts a game rather than refusing it — the failure is worse
|
||||
than the one it replaces.
|
||||
2. **A declared compatibility floor.** The save records the version that wrote it; the engine
|
||||
declares the oldest save it will accept. Loading checks `saved >= floor` rather than
|
||||
`saved === current`. Same judgement call as (1), but expressed as a range, which makes
|
||||
"this release breaks saves" an explicit act rather than the default.
|
||||
3. **Verify rather than assume — replay and see.** Load the save, replay it, and refuse only
|
||||
if an intent actually rejects. This is the honest test and needs no judgement at all: it
|
||||
answers the real question ("does this game still replay?") instead of a proxy for it. It
|
||||
costs a full replay per game on boot, which is ~100 ms per finished game (measured
|
||||
2026-08-21) and only unfinished games are loaded — so at any realistic table count it is
|
||||
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.
|
||||
|
||||
- [ ] **THE FOUR `optionalRules` ARE SETTABLE BY NOTHING, AND TWO OF THEM DO NOTHING.** 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`,
|
||||
|
||||
Reference in New Issue
Block a user