v0.4.9d — three playtest bugs: a train that looked gone, an unload that ignored the pick, a hang that wasn't one
Patched directly onto 0.4.9a rather than the in-progress 0.5.0 line. A switching train vanished from the board the moment it left the Office. `selectedTrain` in `officeSvg` (board-svg.ts), which gates whether a card draws a train's crew badge, was only ever assigned inside the `cell.adTracks !== null` branch — true for the Office card alone. A train standing anywhere else, which is everywhere it stands while actually being switched, drew no badge at all. Game state was never affected; confirmed against the reported save with the engine directly. Fix hoists the assignment out of the guard so it runs for any card with a train on it. Unloading always took the westmost car, whichever one was picked. `laborer.beginUnload` carried the player's chosen `carIndex`, and `check()` validated that specific car, but the `unloadBegan` event it produced carried only the car's type — the reducer that performs the swap re-derived the target with `industryTrack.cars.findIndex(c => c.loaded)`, which always answers the first loaded car in track order regardless of what was requested. Fix adds `carIndex` to the event and uses it directly. A legal decision could render with zero buttons, which looked exactly like a hang. The "where does this Extra start" decision is titled by `trainCardTitle`, beginning "Making up Extra X22…" — the same prefix `renderActions()` stripped from the action list on the assumption it only ever belonged to the separate yard-chip car-placement panel. With no tray yet being filled, that panel is null, so nothing rendered the Extra's decision either: a legal, correctly-computed option with no button anywhere on the page. Replaying the reported save found no engine deadlock at any step — the engine always had a move. Fix matches the exact title of the one group the yard-chip panel covers instead of a title-prefix regex. New tests for all three: a train parked on an ordinary facility card must draw its crew badge; three differently-typed loaded cars, unloading index 2, must leave indices 0 and 1 alone; the existing softlock regression test now mirrors the real render filter instead of only the raw menu, plus a deterministic test for the exact reported scenario. 590 tests, 0 failures.
This commit is contained in:
@@ -19,6 +19,81 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.4.9d — 2026-08-21
|
||||
|
||||
Three bugs from the same playtest session, patched directly onto 0.4.9a rather than the in-progress
|
||||
0.5.0 line: a display bug that made a switching train look like it had vanished, an engine bug that
|
||||
silently ignored which car the player chose to unload, and a UI bug that could render a legal
|
||||
decision with zero buttons — indistinguishable from a hang.
|
||||
|
||||
### A switching train vanished from the board the moment it left the Office
|
||||
|
||||
**REPORTED:** Train 3 stood at the Office; dropping the loaded boxcar off the back at the Freight
|
||||
House made the train "vanish off the face of the earth," regardless of which way it moved
|
||||
afterward. The game state was never wrong — replaying the reported save
|
||||
(`docs/station-master-seed116956197-day2.json`) through the engine directly showed `tray3` fully
|
||||
intact, in the right place, with the right consist, for the rest of the game. Nothing was ever lost
|
||||
from `state.trays`.
|
||||
|
||||
The bug was in `officeSvg` (`board-svg.ts`): `selectedTrain` — the value that decides whether a
|
||||
card draws a train's crew badge (its label, engine arrow, and cars) — was assigned only inside the
|
||||
`cell.adTracks !== null` branch, which is true for the Office card and nothing else. A train
|
||||
standing on any other card, which is every card it stands on while actually being switched, got no
|
||||
badge at all. It disappeared on the very first move off the Office, not only at the Freight House —
|
||||
that was simply the first place a player was likely to pause and look. Traced to the Roster Pass
|
||||
(37b1e5b), which rewrote `CellView.train` into `CellView.trains[]` to show every train sharing the
|
||||
Office's A/D tracks and, in doing so, nested the general consist-drawing logic inside that
|
||||
Office-only branch instead of leaving it to run for any card with a train on it.
|
||||
|
||||
**Fixed** by hoisting the `selectedTrain` assignment out of the `adTracks !== null` guard so it
|
||||
runs for every card; the A/D roster-chip loop itself stays inside the guard, since only the Office
|
||||
has more than one A/D track to draw chips for. New test: a train parked on an ordinary facility
|
||||
card, `adTracks: null`, now has to draw its crew badge — it did not before this fix.
|
||||
|
||||
### Unloading always took the westmost car, whichever one was picked
|
||||
|
||||
**REPORTED:** three loaded cars stood at a Freight House; asking to unload the east (right-hand)
|
||||
car unloaded the west one instead, every time, no matter which car was actually chosen.
|
||||
|
||||
`laborer.beginUnload` already carried the player's choice as `carIndex`, and `check()` validated
|
||||
that specific car — but the event it produced, `unloadBegan`, carried only the car's `carType`, not
|
||||
which one it was. The reducer that actually performs the swap re-derived the target itself with
|
||||
`industryTrack.cars.findIndex(c => c.loaded)`, which always answers the first loaded car in track
|
||||
order regardless of what was requested — so the player's choice was thrown away between `check` and
|
||||
the reducer, and the westmost loaded car came off every time.
|
||||
|
||||
**Fixed** by adding `carIndex` to the `unloadBegan` event and using it directly in the reducer
|
||||
(`src/engine/apply.ts`, `case 'unloadBegan'`), instead of re-deriving a car by scanning for
|
||||
`loaded`. New test in `apply.test.ts`: three loaded cars of three different types, asking to unload
|
||||
index 2 (east) now leaves indices 0 and 1 alone and empties exactly the one requested — it did not
|
||||
before this fix.
|
||||
|
||||
### A legal decision could render with zero buttons — the game looked hung
|
||||
|
||||
**REPORTED:** working the Freight House mid-cycle (unloading three cars, then reloading them), the
|
||||
game "seemed to have hung up. Cannot advance." No trains were on the Division or the Limits.
|
||||
|
||||
Replaying the reported save turned up no engine deadlock at any point — every step had a legal
|
||||
action — but the exact final state (an Extra train due to start, no tray yet being made up) exposed
|
||||
a UI bug: `newTrain.startExtra`'s action group is titled by `trainCardTitle`, which begins "Making
|
||||
up Extra X22…" — the same prefix `renderActions()` (`main.ts`) used to strip from the action list on
|
||||
the assumption it always belonged to the separate yard-chip car-placement panel. With no tray being
|
||||
filled yet, that panel (`menu.makeUp`) is `null`, so nothing else rendered the Extra's "choose where
|
||||
it starts" decision either — a legal, present, correctly-computed option with no button anywhere on
|
||||
the page. Clicking nothing did nothing, which is indistinguishable from a hang.
|
||||
|
||||
**Fixed** by matching the exact title of the one group `menu.makeUp` actually covers
|
||||
(`g.title !== menu.makeUp?.title`) instead of a title-prefix regex, so any other group that happens
|
||||
to start "Making up …" — however it got that title — stays on screen. The existing softlock
|
||||
regression test (`never strands the player with a legal move and no way to make it`) now mirrors the
|
||||
real render filter instead of only checking the raw menu, so this class of bug fails that test
|
||||
directly; a new deterministic test constructs the exact reported scenario (a pending Extra, no
|
||||
active make-up) and asserts its group survives the filter.
|
||||
|
||||
590 tests, 0 failures.
|
||||
|
||||
---
|
||||
|
||||
## 0.4.9a — 2026-08-20
|
||||
|
||||
Four small tweaks to the splash page (`index.html`, `splash.ts`), no engine changes.
|
||||
|
||||
Reference in New Issue
Block a user