Four things the game counted and never said, and two it said wrong
Stays in the unshipped v0.7.9. Prompted by Jesse asking the general question after two v0.7.9 fixes turned out to be the same shape: actingPlayer existed and the Frame threw it away, and collisionsToday / collisionsTotal rode the Frame for three releases with nothing drawing them. So what else is computed, serialised and sent to nobody? THE AUDIT, done rather than guessed. Every one of Frame's 59 top-level fields grepped for a read across the seven renderers, then the same for Tally's 26 members. 55 of 59 are read. Four are not. tally.unloadsBegun was visible rather than merely unused. §9.1 makes loading and unloading the same shape — begun, then carried through — and the results screen printed "Loads still in the pipeline" for one side and nothing for the other, reporting half of a symmetric mechanism. tally.cardsDiscarded was counted by the engine and listed beside "Cards drawn" and "Cards played" without it, though Gitea#9 made throwing a Timetabled train away a deliberate move — a player CHOICE the game counted and never reported. Both are reported now. viewerSeat and overHandLimit are deferred by Jesse. The second is the fullest version of the shape: engine computes it, view.ts puts it on the Frame, session.ts declares it on the Session interface AND implements it twice, and the only caller in the repo is its own test. Four layers of plumbing, no consumer. The decision when it comes is delete-or-document, not a patch. Fixing the two turned up a third thing: resultsHtml draws tallyHtml(report?.tally ?? f.tally), and report is f.official, so a finished game reports the tally frozen at the official ending rather than the live one. The first attempt at a test overrode f.tally alone, changed nothing on screen, and failed for a reason unrelated to the fix. A SHOUTED KEYWORD IS NOT A SENTENCE. `EXTRA X18 started…` attributed to a player rendered as `Player Solitaire eXTRA X18 started…`, and the same happened to TRAIN 1 MADE UP and COLLISION. `record` folds a narration's opening word into the middle of a sentence and did it with a flat charAt(0).toLowerCase(). It now folds only a sentence-cased word — ^[A-Z][a-z], a capital followed by a lower-case letter — which also leaves X22 Pee-Dee alone, where a naive uppercase test gets it wrong because '2'.toUpperCase() is '2'. It had been filed under Play Balance, where it has no business being, which is how it survived a session that had ruled balance work out of scope. A REPLAYED SAVE NOW NARRATES WHAT THE LIVE GAME NARRATED. fromSave's loop called record(game, result.events) with no actor, so every restored save, every undo (which rebuilds through fromSave) and the replay viewer stripped the "Player X" prefix off every attributed line. submit attributes and fromMultiplayerSave attributes; this was the one path of three that did not. One argument, with actor already computed on the line above. Why it survived: nothing ever compared a fromSave-built log against a LIVE-played one. The single log-comparing test compares undo's rebuilt log against another fromSave-built log — and undo itself rebuilds through fromSave — so the gap cancelled out on both sides. The suite was green with the bug in and green with it out. The new test plays a game, saves it, restores it and asserts the two logs are identical: the missing direction, not a new requirement. All three fixes were confirmed to go RED with the fix reverted before being called done. 884 tests pass, seventeen new. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YTaNBL1jVxNqgFdjHkHoo3
This commit is contained in:
co-authored by
Claude Opus 5
parent
d267f89a82
commit
e62ea54259
+65
-1
@@ -341,6 +341,70 @@ read `#houserules`/`#gametype` were rewritten to open the card rather than delet
|
||||
not verified** — there is no browser on this box, so nothing has confirmed the segmented control, the
|
||||
card, or the reversed panel actually look right on screen. That wants a play session.
|
||||
|
||||
### Four things the game counted and never said
|
||||
|
||||
Prompted by Jesse asking the general question after two separate v0.7.9 fixes turned out to have the
|
||||
same shape — `actingPlayer` existed and the Frame threw it away, and `collisionsToday`/`collisionsTotal`
|
||||
rode the Frame for three releases with nothing drawing them. So: **what else is being computed,
|
||||
serialised and sent to nobody?**
|
||||
|
||||
Audited rather than guessed. Every one of `Frame`'s 59 top-level fields grepped for a read across the
|
||||
seven renderers, then the same for `Tally`'s 26 members. **55 of 59 are read.** The four that are not:
|
||||
|
||||
- **`tally.unloadsBegun`** — and this one was visible rather than merely unused. §9.1 makes loading
|
||||
and unloading the same shape, begun then carried through, and the results screen printed "Loads
|
||||
still in the pipeline" for one side and nothing for the other. It reported half of a symmetric
|
||||
mechanism. There is an "Unloads still in the pipeline" line now.
|
||||
- **`tally.cardsDiscarded`** — counted by the engine, listed beside "Cards drawn" and "Cards played"
|
||||
without it. Gitea#9 made throwing a Timetabled train away a legal, deliberate move, so a discard is
|
||||
a player CHOICE the game counted and never reported. It reports it now.
|
||||
- **`viewerSeat`** and **`overHandLimit`** — deferred by Jesse. The second is the fullest version of
|
||||
the shape: the engine computes it, `view.ts` puts it on the Frame, `session.ts` declares it on the
|
||||
`Session` interface and implements it twice, and the only caller in the repo is its own test. Four
|
||||
layers of plumbing with no consumer. Left alone for now; the decision when it comes is
|
||||
delete-or-document, not a patch.
|
||||
|
||||
**Fixing the two turned up a third thing worth knowing.** `resultsHtml` draws
|
||||
`tallyHtml(report?.tally ?? f.tally)`, and `report` is `f.official`, which the engine writes the
|
||||
moment any game ends — so a finished game reports the tally frozen at the official ending, not the
|
||||
live one. The first attempt at a test overrode `f.tally` alone, changed nothing on screen, and failed
|
||||
for a reason that had nothing to do with the fix.
|
||||
|
||||
### A shouted keyword is not a sentence
|
||||
|
||||
`EXTRA X18 started…`, attributed to a player, rendered as `Player Solitaire eXTRA X18 started…`. The
|
||||
same happened to `TRAIN 1 MADE UP` and `COLLISION`, which are shouted deliberately.
|
||||
|
||||
`record` folds a narration's opening word into the middle of a sentence — "Chose to draw" has to read
|
||||
"Player Bob chose to draw" — and did it with a flat `charAt(0).toLowerCase()`. It now folds **only a
|
||||
sentence-cased word**: `^[A-Z][a-z]`, a capital followed by a lower-case letter, which is an ordinary
|
||||
word capitalised because it began a sentence and nothing else is. That also leaves `X22 Pee-Dee`
|
||||
alone, which a naive "is the first letter uppercase?" test gets wrong, because `'2'.toUpperCase()`
|
||||
is `'2'`.
|
||||
|
||||
**It had been filed under Play Balance**, where it has no business being — which is how it survived a
|
||||
session that had explicitly ruled balance work out of scope. Found by reading the section it did not
|
||||
belong to.
|
||||
|
||||
### A replayed save now narrates what the live game narrated
|
||||
|
||||
`fromSave`'s loop called `record(game, result.events)` with no `actor`, so every restored save, every
|
||||
undo (which rebuilds through `fromSave`) and the replay viewer stripped the "Player X" prefix off
|
||||
every attributed line. Live play attributes — `submit` passes `actor` — and so does multiplayer's
|
||||
replay; this was the one path of the three that did not, which meant the same moves were described in
|
||||
different words from the game that produced them. The fix is one argument, with `actor` already
|
||||
computed on the line above.
|
||||
|
||||
**Why it survived two years of green tests, which is the part worth keeping.** Nothing ever compared a
|
||||
`fromSave`-built log against a LIVE-played one. The single log-comparing test compares `undo`'s
|
||||
rebuilt log against another `fromSave`-built log — and since `undo` itself rebuilds through
|
||||
`fromSave`, the missing attribution cancelled out on both sides. The whole suite stayed green with the
|
||||
bug in place, and stayed green after the fix too.
|
||||
|
||||
The new test plays a game, saves it, restores it, and asserts the two logs are identical. That is the
|
||||
**missing direction**, not a new requirement. All three of this batch's fixes were confirmed to fail
|
||||
with the fix reverted before being called done — the discipline the v0.7.5→v0.7.8 sequence bought.
|
||||
|
||||
### Three wording and layout fixes
|
||||
|
||||
- **The collision entries** on all three screens now read "The game ends immediately and results in
|
||||
@@ -355,7 +419,7 @@ card, or the reversed panel actually look right on screen. That wants a play ses
|
||||
cost, not a danger, and red would outrank the actual rules above it. The buttons say **Continue
|
||||
Existing Saved Game** and **Deal New Game** rather than "Continue saved game" and "Deal".
|
||||
|
||||
881 tests pass, fourteen of them new; one existing test asserted the opposite of the collision ruling
|
||||
884 tests pass, seventeen of them new; one existing test asserted the opposite of the collision ruling
|
||||
above and says so where it was reversed.
|
||||
|
||||
---
|
||||
|
||||
Reference in New Issue
Block a user