v0.8.1.0 — the car comes back empty, and the lobby lets you leave
A second-digit bump, deliberately. 0.8.1 had been reserved for the seatless display table; that work is getting more thought, and this table pass over v0.8.0.17 earned the number on its own. Six reports: one was a rules question, one a wording complaint with a real bug underneath, four straightforward. A CAR CLEARED FROM A RED INBOUND BOX CAME BACK STILL LOADED. Reported as wording — "technically accurate but doesn't make any sense" — and the wording was the visible half. `inboundCleared` pushed `pooled(e.stock)` under a comment reading "a car back in a yard is back in the common supply, carrying nothing", and `pooled` does not do that: it strips the load's origin stamp and keeps `loaded` ON PURPOSE, because a train can retire at a Division Point with freight aboard. So the comment described an intention the call never carried out, and every car the Freight Agent cleared reached the Classification Yard carrying a load already delivered and already paid for. It bites hardest on coaches: `passengersDetrained` takes `coach && !loaded` out of the Division Yard and §2.2 refills that yard from Classification, so a cleared coach came back as stock that could never unload another passenger. Measured over five three-Day solitaire games: 18 loaded coaches in Classification against 6 empty. The red box is where a journey ENDS; clearing it sends the passengers out of the station, or the delivered load into the industry, and returns the CAR, empty. The option says that now instead of describing the counter that moves. SCOPED TO THE RED BOX ON PURPOSE. `retireTrain` also returns loaded cars and is left alone: that is what `pooled`'s own documentation describes, and a loaded car in a yard is pre-loaded cargo rather than dead stock — it can be made up and delivered, and a loaded coach can still detrain. Only the red box's contents had already finished their journey. GAMES IN PROGRESS DO RESUME, MEASURED RATHER THAN ARGUED. Yard contents change, so the worry was real. All twelve saves on the test server were pulled and replayed through `tryResumeSession` — the server's own boot check — against this build. Six resume, six refuse, and the six refusals are the SAME six, at the same moves, with the same codes, that 0.8.0.17 already logged. Nothing new was stranded. That pre-install replay is a better check than reading the next boot log, because it answers before the install rather than after. THE HISTORY SAID "Mainline card 7" and left the reader to remember what card 7 was — with two Plains dealt, which is why the slot is kept beside the name rather than replaced by it. A second fault sat one word to its left and nobody reported it: the name was built as `e.key.replace(/([A-Z])/g, ' $1')`, so `absSignals` printed as "abs Signals" while the action list directly above said "ABS Signals". `narrate` takes `mainlineAt` and `enhancementName` beside the `facilityAt` it already had, and `simpleCardName` is exported so the log reads the same table the buttons do. `mainlineModified` had both faults and is fixed with it. LEAVING A RUNNING GAME WAS A DEAD END. `enterSeating` hides the choice section and only the lobby's own two leave paths put it back; leaving a running game is a third route, so the lobby came back holding nothing but "Games you are in" with both doors on the page at display:none and no control able to reveal them. Reset in `runLobby`, which is the one function every route onto that screen goes through — which is exactly why the two paths that did it themselves missed a third. THE LOBBY'S ACTION BUTTONS CARRY THE BOARD'S AMBER. A list of the actions rather than `#lobby button`: the settings form under Create is a field of inputs, and amber on all of it would say everything is a move and so say nothing. A disabled Start game drops back to chrome. THE DEPARTMENT REFILL IS A RULE AND IS NOW WRITTEN DOWN. §6.2 — "if any of the Department decks is empty, draw a Home Office card and place it in the empty spot" — firing only when the draw actually empties the pile. Kept as implemented (Jesse's ruling) and stated in rules.md and home-deck.md, neither of which had ever mentioned it. A rule implemented from the prototype and never written down is a rule that surprises the table. 1016 fast tests and 35 sim tests pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MUizFYCMHRWhbWwXhp7WPR
This commit is contained in:
co-authored by
Claude Opus 5
parent
b90c0413d2
commit
517238a727
+119
@@ -19,6 +19,125 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.8.1.0 — 2026-09-22
|
||||
|
||||
A second-digit bump, and a deliberate one. **0.8.1 had been reserved for the seatless display
|
||||
table**; that work is getting more thought, and this batch — a table pass over v0.8.0.17 — earned
|
||||
the number on its own. Six reports, one of which turned out to be a rules question, one a wording
|
||||
complaint with a real bug underneath, and four straightforward.
|
||||
|
||||
### A car cleared from a red Inbound box came back still loaded
|
||||
|
||||
Reported as a wording problem — *"the option to send the loaded coach in the red inbound box to the
|
||||
classification yard is technically accurate but doesn't make any sense"* — and the wording was only
|
||||
the visible half.
|
||||
|
||||
`inboundCleared` did this:
|
||||
|
||||
```ts
|
||||
// `pooled` — a car back in a yard is back in the common supply, carrying nothing (state.ts).
|
||||
s.yards.classificationYard.push(pooled(e.stock));
|
||||
```
|
||||
|
||||
**`pooled` does not do that.** It strips the load's `origin` stamp and keeps `loaded` on purpose,
|
||||
because a train can retire at a Division Point with freight still aboard (`advance.ts`). So the
|
||||
comment described an intention the call never carried out, and every car the Freight Agent cleared
|
||||
reached the Classification Yard still carrying a load that had already been delivered and already
|
||||
been paid for.
|
||||
|
||||
It bites hardest on coaches. `passengersDetrained` takes `type === 'coach' && !loaded` out of the
|
||||
Division Yard, and §2.2 refills the Division Yard from Classification — so a cleared coach came back
|
||||
as stock that could never unload another passenger. Measured over five three-Day solitaire games
|
||||
before the fix: **18 loaded coaches in the Classification Yard against 6 empty.**
|
||||
|
||||
The red box is where a journey ENDS. Clearing it sends the passengers out of the station, or the
|
||||
delivered load into the industry, and returns the **car** — empty — to the common supply. The
|
||||
option now says that rather than describing the counter that moves: *"let the passengers out of the
|
||||
station — the coach in the red Inbound box at (2,-1) goes back to the Classification Yard empty"*.
|
||||
|
||||
**Not everything loaded in a yard is this bug**, and the fix is scoped to the red box. A train that
|
||||
completes its run returns its cars as they stand, loaded ones included — that is `retireTrain`, it
|
||||
is what `pooled`'s own documentation describes, and a loaded car in a yard is pre-loaded cargo
|
||||
rather than dead stock: it can be made up into a train and delivered, and a loaded coach can still
|
||||
detrain. Only the red box's contents had already finished their journey.
|
||||
|
||||
**Games in progress DO resume, and it was measured rather than argued.** What is in the yards
|
||||
changes, so the worry was real: a save whose later moves depend on a cleared car being loaded would
|
||||
stop replaying. All twelve saves on the test server were pulled and replayed through
|
||||
`tryResumeSession` — the server's own boot check — against this build. Six resume and six refuse,
|
||||
and **the six refusals are the same six, at the same moves, with the same codes**, that 0.8.0.17
|
||||
already logged: three `CARD_NOT_IN_HAND` at move 3 in saves from engines 0.5.3, 0.7.0 and 0.7.1,
|
||||
two `NO_PENDING_DECISION` from the Gitea#26 clearance fix, and WHISTLE-6945 stopping at move 529 on
|
||||
v0.8.0.14's Modifier ruling. Nothing new was stranded.
|
||||
|
||||
**That replay is the cheap check to repeat on any release that touches state**, and it is a better
|
||||
one than reading the next boot log: it answers before the install rather than after it.
|
||||
|
||||
### The history said "Mainline card 7" and left you to remember what that was
|
||||
|
||||
*"It should specify both. Note there were two plains cards dealt in this hand."* — which is exactly
|
||||
why both: the name informs, and the slot is the only thing telling two Plains apart.
|
||||
|
||||
The line also had a second fault nobody reported, one word to its left. It built the card's name as
|
||||
`e.key.replace(/([A-Z])/g, ' $1')`, so `absSignals` printed as **"abs Signals"** — uncapitalised,
|
||||
and bypassing the authored names `cardName` started honouring in v0.8.0.17 for precisely this
|
||||
reason: no key-splitter recovers an acronym. The action list one line above it said "ABS Signals"
|
||||
while the log said "abs Signals".
|
||||
|
||||
Both are resolver-shaped, so `narrate` takes `mainlineAt` and `enhancementName` alongside the
|
||||
`facilityAt` it already had, and `simpleCardName` is exported so the log reads the same table the
|
||||
buttons do. `mainlineModified` had both faults in the neighbouring case and is fixed with it.
|
||||
|
||||
### Leaving a running game was a dead end
|
||||
|
||||
*"I see the games that I'm in, and I have the ability to rejoin or forget. But what if I want to
|
||||
join a different game or start a new one? How do I get back there?"*
|
||||
|
||||
There was no way. `enterSeating` hides `#lb-choice-section`, and the only things that put it back
|
||||
were the lobby's own two leave paths. Leaving a **running** game is a third route — `main.ts` calls
|
||||
`runLobby` again — so the section stayed hidden from the previous seating and the lobby came back
|
||||
holding nothing but "Games you are in". The two doors were on the page the whole time, at
|
||||
`display:none`, with no control that could reveal them.
|
||||
|
||||
Reset in `runLobby` rather than in the leave handler, because that function is the one thing every
|
||||
route onto this screen goes through — which is why the two paths that did it themselves missed a
|
||||
third.
|
||||
|
||||
### The lobby's buttons say they are buttons
|
||||
|
||||
*"Create new game should have that same amber border when it's enabled and ready to click, just
|
||||
like the other action buttons in the rest of the game."*
|
||||
|
||||
`#actions button` has meant "this is a move you can make" all game. Look up game, Join this game,
|
||||
Create new game, Copy code, Copy invite link, Start game and Rejoin now carry it. The selector is a
|
||||
list of those actions rather than `#lobby button`: the settings form under Create is a field of
|
||||
inputs, and amber on all of it would say everything is a move and so say nothing. A disabled Start
|
||||
game drops back to plain chrome, because the amber is a promise the button would not keep.
|
||||
|
||||
### The Department refill is a rule, and now it is written down
|
||||
|
||||
*"Why was a department slot refilled? If I take the last card there, then there should just be no
|
||||
cards available in that stack."*
|
||||
|
||||
The game is right and the documentation was silent. §6.2: *"If any of the Department decks is empty,
|
||||
draw a Home Office card and place it in the empty spot."* It fires only when the draw actually
|
||||
empties the pile — refilling on every draw would grow the Departments without limit and drain the
|
||||
Home Office deck into them. **Kept as implemented** (Jesse's ruling), and stated in `rules.md` and
|
||||
`home-deck.md`, neither of which had ever mentioned it. A rule implemented from the prototype and
|
||||
never written down is a rule that surprises the table.
|
||||
|
||||
### Games in the Manage Game dropdown say how old they are
|
||||
|
||||
*"I've got four games out there, but I have no idea which one's which or how old they are."* The
|
||||
label carried the code, the seat count and the position — everything except the fact that says
|
||||
whether a game is live or abandoned. `lastMoveAt` was already on the wire from the server's
|
||||
`summary()` and simply unused.
|
||||
|
||||
Both forms, because they answer different questions: **"5h ago"** answers the one that was asked,
|
||||
and the timestamp beside it is what you quote when deciding to end a game. A lobby reports when it
|
||||
was created and says so. The date formatter moved to `utils.ts`, shared with Games in Progress so
|
||||
the two cannot drift.
|
||||
|
||||
## 0.8.0.17 — 2026-09-21
|
||||
|
||||
Four things a table found on Day 1 of v0.8.0.16, all of them the same shape: the game knew
|
||||
|
||||
Reference in New Issue
Block a user