v0.4.9 — X,Y coordinates, no-switching can still clear the mainline, Q3 corrected, a clearance bug, three sounds, and the box art

A playtest review of seed 58228926 (day 6), plus one long-standing display complaint and the
first real audio beyond a placeholder.

- Coordinate labels read X,Y everywhere shown to a player, not the internal Y,X storage order.
  Display-only.
- "No switching" now means may not add or drop cars, not "never touch it" — these trains can
  still be moved onto Secondary Track to clear the mainline.
- Q3 corrected: Expedite governs WHERE a train may be left standing, not WHEN it leaves. The
  forced same-Stage departure is gone; a new fault costs 1 Revenue if an expedited train is left
  off the station when a Mainline Phase begins. Resolves "3/4 Express prints a rule it can never
  use" as a side effect.
- evaluateClearance now checks every occupant on a Mainline card before offering a judgment
  call, instead of returning on whichever it found first — found while explaining a playtest
  report, fixed with a regression test.
- Three new synthesised sounds: arrive, depart, crash.
- The splash page shows the box art.

Full detail, measurements and reasoning in CHANGELOG.md.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FvU99NEakShRMg3nN3fHAZ
This commit is contained in:
Jesse
2026-08-19 23:51:35 -04:00
co-authored by Claude Sonnet 5
parent 37b1e5b969
commit 859817d173
25 changed files with 1048 additions and 739 deletions
+94
View File
@@ -19,6 +19,100 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
---
## 0.4.9 — 2026-08-19
A playtest review of seed 58228926 (day 6), plus one long-standing display complaint and the first
real audio beyond a placeholder. Six changes: one display fix, two rules corrections, one engine bug
found while explaining a report rather than reported directly, three new sound cues, and the splash
page's box art.
### Office coordinates read X,Y now, not Y,X
Reported as confusing for anyone reading them the ordinary way: "at game start the office is 0,0;
the western limit should be -1,0, the eastern +1,0; a restaurant due north should be 0,1." The
engine's internal `GridCoord{row,col}` already has exactly that geometry — row increases north, col
increases east (`track.ts:59`) — so this was a **display-only fix**, not a data-model change: every
place a coordinate is shown to a player now prints `(col,row)` instead of `(row,col)`. The on-card
label in `board-svg.ts` (the one everyone sees), the switching-crew tooltip and button label in
`main.ts`, the rejected-option and switching-group text in `game.ts` and `view.ts`, and the
blocked-move text in `narrate.ts`. Internal `Map` keys built from `${row},${col}` are untouched —
nothing reads them but lookup code.
### "No switching" now means no adding or dropping cars — not "never touch it"
Reported from play: a train sitting at the Office with NO SWITCHING printed on its card (the two
expresses, Light Engine, Campaign, Circus, Military) could not be moved onto Secondary Track to
clear the mainline for other traffic, even though nothing on the card says that. `switchingRefusal()`
(`apply.ts`) blocked `switch.move` outright for these six cards; it now only refuses a move that
would COUPLE a fresh car — the same way `rules.dropOnly` was already handled for X13 — so the crew
can still be shunted clear, it just cannot pick up, set out or sort. `switch.dropCars` and
`switch.sortConsist` stay blocked. The switching-moves list (`view.ts`) no longer excludes these
trains, and the card description text was rewritten from "it runs the Division and does not shunt"
to "may not add or drop cars, but may still be moved clear of the mainline."
### Q3, corrected: Expedite is about WHERE a train may be left, not WHEN it leaves
**The reported symptom:** Train 1 arrived at an Office and was forced out at the end of the same
Stage — the read since v0.4.2 — leaving no Local Operations turn to switch it. **The rule question
underneath it:** why should "Expedite" mean a train is rushed out early at all, rather than simply
never being left parked off the station?
Corrected to the latter. An expedited train now arrives and stands exactly like any other train,
released by the ordinary §8.1 "stood a full Stage at the Office" rule — it can be switched normally
in between. Deleted entirely: `arriveAtOffice`'s `departsThisStage` flag and the whole
expedited-departures pass that used to run in `shiftChange`, plus the now-dead `pass: 'mainline' |
'expedite'` parameter on `moveTrain`.
In its place: a new fault. At the top of every Mainline Phase, any expedited train sitting on the
grid but not on the Office square itself — left on Secondary Track to clear a switching move, say —
costs 1 Revenue (`EXPEDITE_FAULT_PENALTY`, `expediteFault`), charged again every Phase it is still
caught there. A train the ordinary rules are legitimately holding AT the station — even one §8.1 is
asking the Superintendent to rule on — is not a fault; only being left elsewhere in the district is.
**This resolves "3/4 EXPRESS PRINTS A RULE IT CAN NEVER USE"** (`TODO.md`) as a side effect: the
Express can now actually reach a Local Operations turn to use its printed "may drop or pick up one
freight car at every location," which the old same-stage departure made structurally impossible.
**Measured (30 fresh bot games, standard length):** the fault fires, but the bot has no strategy for
it yet — one game left Train 4 (3/4 Express) parked off the station from Day 3 Stage 10 to the end of
the game, taking the -1 penalty 26 times. Not an engine bug: the mechanism is working as designed and
correctly reveals that the bot doesn't know to bring an expedited train home before ending a
switching turn. Logged in `TODO.md` under Bot Performance for a future pass.
### `evaluateClearance` now checks every occupant, not just the first one it finds
Found while explaining a second playtest report from the same game: the Superintendent was asked to
rule on a same-direction train instead of being automatically held against an opposite-direction one
also occupying the card. Root cause: the occupant loop (`advance.ts`) returned on whichever occupant
it examined first, in `node.transits` insertion order — correct while a Subdivision held at most one
train, but a card can now legitimately hold two at once, which is exactly what the
Telegraph/Telephone/Radio dispatch exception creates (a facing train dispatched past another sits on
the same card as it). Fixed to two passes: every occupant is checked for an opposite-direction
absolute bar first (subject to the dispatch exception) before any same-direction judgment call is
offered. Pinned with a regression test that fails against the old single-pass code
(`enhancements.test.ts`).
### Three more sounds, synthesised like the rest
`arrive`, `depart` and `crash` join `stage`/`couple`/`drop`/`schedule`/`completed`/`day`/`train` in
`sound.ts` — a train pulling into an Office (decelerating chuffs, a soft bell tap), one highballing
out of one (accelerating chuffs, a falling horn note), and a collision (dissonant tones bent sharply
down over a noise boom and a sub-bass thump), wired to `trainArrived`, `trainHighballed` (Office
departures only, so it doesn't double up with a fresh make-up's "All aboard" or a full division run's
`completed`), and `trainsDestroyed`. Three real WAV clips were sourced for this and considered, but
carried no license information anywhere — no `LICENSE` file, no embedded metadata, no note of where
they came from — so they were not committed; the synthesised versions are good enough to keep as the
real thing rather than a placeholder.
### The splash page has its box art
`docs/StationMasterSplashScreen.png` (1122×1402, 3.0 MB) is now a 560×700 JPEG at 145 KB
(`public/images/`, copied verbatim into `dist/images/` by a small addition to `build-web.ts`) and
sits beside the title, tagline, blurb and both buttons in a side-by-side hero on `index.html`,
stacking to image-above-text under the existing mobile breakpoint.
---
## 0.4.8 — 2026-08-19
Four reports from the same game, all about squares: which ones a card may go on, which one a button