v0.6.2 — an Extra starts where you put it, a train card is never discarded
Three more from the v0.4.9e gameplay-testing round, filed as Gitea issues, plus two bugs found underneath them. Gitea#2 is diagnosed but NOT fixed: it needs a ruling, and the reasoning is in TODO.md under Play Balance. GITEA#4 — AN EXTRA STARTS WHERE THE PLAYER PUTS IT. Only one Division Point was ever offered, chosen by number parity. The number no longer decides an Extra's direction — the start does, which supersedes the recorded ruling that "the number decides, like everything else on the timetable". The two cannot both hold: an odd, westbound Extra placed at the WEST end would leave the Division on its first move having crossed nothing, and be paid for the run. Either end now runs the train away from itself; at an Interchange or a Control Point the player picks the direction. The Interchange start is a YARD, off the running line, which is what makes the Superintendent clause work: placing it can never force a collision, a guaranteed one holds it there for another Stage, and a potential one is the Superintendent's to rule on — exactly evaluateClearance's `blocked` and `ask`, so nothing new decides collisions. Where an Extra may start is now a house rule (divisionPointsOnly / ownOffice / anyOffice, defaulting to what the engine already did). The legacy `atSeat` intent field still replays as it always meant. FOUND UNDERNEATH IT: an Extra started away from a Division Point ran empty. isBeingMadeUp tested position alone, so the Control Point start has been shipping since it was added with a train that could never be given a consist. Found by playing it, not by the tests, which had only asserted where the tray landed. FOUND UNDERNEATH IT: collide left the wrecks on the card. Destroyed trains kept their Transit entries, and evaluateClearance counts every transit as an occupant, so one rear-end collision permanently poisoned that Mainline card for every later train. THE MAINLINE CARDS WERE ROLLED, NOT DEALT — drawn from the nine types with replacement, so a Division could hold two Interchanges and Plains carried the weight of a card printed once. "An Extra may start at the Interchange if one is on the board" only reads as a rule if the board holds at most one. Now dealt from the printed deck without replacement, verified over 1600 deals. This re-deals every seed: the published replays were re-recorded, and the saved games in docs/ are retired too — two of those were already dead before this release and nobody had noticed. GITEA#6 — A TRAIN CARD IS NEVER DISCARDED, Timetabled and Extra alike. The forced play needed no mechanism: nothing discardable plus a hand over the limit leaves exactly one legal way to end the turn, and playing a train is unconditionally legal, so the corner cannot trap anyone. The bot needed no rule either. 400/400 games finished, revenue unmoved, trains scheduled 1.2 -> 1.3. The player is told on the card and on the button. GITEA#7 — COACH COUNTS. 1/2 Crack Limited 3 -> 2, 5/6 The Sparrow 2 -> 3. A change to the cards, so Trains3.pdf and the transcription keep the original numbers with a footnote while content.ts and the Home Deck reference carry what the game plays. CONTENT.TS COMMENT PASS — no data changed, only comments. Four were factually wrong, including an office table naming counts doubled long ago and a pointer to a DEALT_DECK_SIZE that has never existed. Every Enhancement row cited its implementation by line number and every citation had rotted; they name functions now. Card counts came out of the comments, since they move with play balance; source-sheet figures and dated measurements stayed. TODO.md gains an item for a card reference generated from content.ts, in six sections, so the documentation cannot disagree with the game. 715 tests pass, tsc clean, site builds.
This commit is contained in:
+155
@@ -19,6 +19,161 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
||||
|
||||
---
|
||||
|
||||
## 0.6.2 — 2026-08-22
|
||||
|
||||
Three more from the v0.4.9e gameplay-testing round, now filed as Gitea issues: **#4** extras did not
|
||||
start where the player said, **#6** train cards could be discarded, **#7** four train cards had the
|
||||
wrong coach counts. Two further bugs were found underneath #4 and are fixed with it. **Gitea#2 is
|
||||
diagnosed but NOT fixed** — it needs a ruling, and the reasoning is in `TODO.md` under Play Balance.
|
||||
|
||||
The same change ships as **0.4.9f** on the 0.4.9 line.
|
||||
|
||||
### Gitea#4 — an Extra starts where the player puts it
|
||||
|
||||
**REPORTED:** "When extras are played the player doing so may choose where the extra starts. They may
|
||||
choose either division point. And if the interchange mainline card has been played, they may start
|
||||
the extra on that card and choose the direction from there. If there is potential for conflict with
|
||||
other trains in that area the superintendent may hold the extra."
|
||||
|
||||
Only one Division Point was ever offered, chosen by the train's number parity, plus any Control Point.
|
||||
Confirmed against a live game before touching anything: a pending X17 offered exactly one placement.
|
||||
|
||||
**The number no longer decides an Extra's direction — the START does.** This supersedes a ruling
|
||||
recorded in `content.ts` ("the number decides, like everything else on the timetable") and the two
|
||||
cannot both hold: an odd, westbound Extra placed at the WEST end would leave the Division on its
|
||||
first move having crossed nothing, and be paid the completion Revenue for the run. A Division Point
|
||||
now runs the train away from itself; at an Interchange or a Control Point, where both ways are real
|
||||
runs, the player says which. Timetabled trains are unchanged. The Extra cards were always printed
|
||||
`direction: 'playerChoice'` and the engine had been overriding it; they now mean it.
|
||||
|
||||
**The Interchange start is a YARD, not a spot on the running line.** This is what makes §7's last
|
||||
clause work. An Extra started there stands in the card's yard, off the road, and highballs onto the
|
||||
card itself at a later Mainline Phase. Three things follow, all of them Jesse's rule: placing it can
|
||||
never force a collision however busy the card is; a guaranteed collision holds it in the yard for
|
||||
another Stage and it tries again; a potential collision is the Superintendent's to rule on. Those last
|
||||
two are exactly `evaluateClearance`'s `blocked` and `ask`, so the Extra leaves the yard through the
|
||||
same §8.1 check a train leaves a Division Point through — **nothing new decides collisions.**
|
||||
|
||||
**Where an Extra may start is now a setting** (`extraStart`), because the Division Points and the
|
||||
Interchange belong to nobody and a player's own district does not: `divisionPointsOnly` /
|
||||
`ownOffice` / `anyOffice`, in the New Game dialog, defaulting to `anyOffice` — what the engine did
|
||||
before the setting existed, so the 0.4.9 playtest line does not change under its testers mid-release.
|
||||
A Whistle Post never qualifies at any setting.
|
||||
|
||||
`atSeat` on the intent is kept as a legacy field: absent `start`, it replays exactly as it always
|
||||
meant, so a save written before the choice existed is unaffected.
|
||||
|
||||
### Found underneath #4: an Extra started away from a Division Point ran empty
|
||||
|
||||
`isBeingMadeUp` tested the position alone — "standing at a Division Point" — which was the whole
|
||||
truth while that was the only place to build a train. **The Control Point start has therefore been
|
||||
shipping since it was added with a train that could never be given a consist**, and the Interchange
|
||||
start would have shipped the same way, against a report that says an Extra started there "would be
|
||||
Loaded with cars". A `beingMadeUp` flag now marks exactly those trays and is cleared the moment the
|
||||
train starts running, rather than a second positional rule — a train that ARRIVED at an Office must
|
||||
never be fillable from the Division Yard, which is the bug `isBeingMadeUp` was tightened to kill.
|
||||
|
||||
**Found by playing it, not by the tests**, which had only ever asserted where the tray landed.
|
||||
|
||||
### Found underneath #4: a collision left the wreck on the card
|
||||
|
||||
`collide` deleted the trains from `s.trays` and left their `Transit` entries sitting on the Mainline
|
||||
card they died on. `evaluateClearance` counts every transit as an occupant, so **one rear-end
|
||||
collision permanently poisoned that card**: every later train was either held against a ghost or put
|
||||
to the Superintendent about one. The only other place a transit is removed is a train rolling off the
|
||||
far end, which a destroyed train never does. Found because the Interchange highball clears through
|
||||
that same occupant list.
|
||||
|
||||
### The Mainline cards were rolled, not dealt
|
||||
|
||||
`buildDivision` drew uniformly from the nine card TYPES **with replacement**, so a Division could be
|
||||
dealt two Interchanges or two Tunnels, and Plains — printed twice in the deck — carried the same
|
||||
weight as cards printed once. `docs/StationMaster-Mainline-Deck-v0.4.5.md` had flagged the mismatch
|
||||
as needing correction; "an Extra may start at the Interchange if one is on the board" is what forced
|
||||
it, since that only reads as a rule if the board holds at most one. Now dealt from `MAINLINE_DECK`
|
||||
without replacement, verified over 1600 deals across 1–4 players.
|
||||
|
||||
**This re-deals every seed**, which retired the published replays (re-recorded) — and also, noticed
|
||||
only after the fact, the saved games in `docs/`. Jesse's Gitea#2 repro now replays 250 of 323 intents
|
||||
instead of reaching the reported position. Two saves there (`seed493290760-day2`, `seed58228926-day6`)
|
||||
were already dead at 2 intents before any of this, retired by some earlier change and never noticed,
|
||||
because the replay-fidelity test guards `public/replays` and nothing else.
|
||||
|
||||
### Gitea#6 — a train card is never discarded
|
||||
|
||||
**REPORTED:** "Players are not allowed to discard Train cards. They may keep the card in their hand
|
||||
for multiple stages and even multiple days, but they may not discard it. If a player has three train
|
||||
cards in their hand, and they draw a fourth, then they must play one of those cards." Extras count.
|
||||
|
||||
The interesting property is that **the forced play needed no mechanism**. A train cannot be discarded,
|
||||
and `draw.end` already refuses while the hand is over the limit, so a player holding four trains has
|
||||
exactly one legal way to conclude the turn without anything ever computing "you must play a train".
|
||||
The corner cannot trap anyone: playing a train card is unconditionally legal — `card.play`'s train
|
||||
case refuses only a board placement, and a card played into a full timetable still leaves the hand.
|
||||
Confirmed in a running game: a hand of four trains offers zero discards, no `draw.end`, four plays.
|
||||
|
||||
The bot needed no rule either. `legal.ts` filters candidates through `check`, so the option stopped
|
||||
being offered, and the developer bot already reaches for `card.play` before a discard. Over 400 games:
|
||||
400/400 finished, revenue unmoved, **trains scheduled 1.2 → 1.3** — the rule's intended effect.
|
||||
|
||||
**The player is told, on the card and on the button.** `handDiscardable` on the Frame marks what may
|
||||
be shed; the hand panel says so in the train's own tooltip, and when every card held is a train the
|
||||
blocked end-turn button changes its text. That is the Gitea#2 lesson applied early: a rule the player
|
||||
cannot see is a board with nothing to click and no reason given.
|
||||
|
||||
### Gitea#7 — coach counts on four train cards
|
||||
|
||||
**1/2 Crack Limited 3 coaches → 2. 5/6 The Sparrow 2 → 3.** A change to the cards, not a
|
||||
transcription fix, so `Trains3.pdf` and the transcription in `implications.md` §5 keep the original
|
||||
numbers with a footnote; `content.ts` and `StationMaster-Home-Deck-v0.4.5.md` carry what the game
|
||||
plays. Both consists remain inside the four-car Crew Tray limit.
|
||||
|
||||
A test had to follow: `multiplayer.test.ts` used Train 1 *because* it had three cars, to exercise a
|
||||
placement round that wraps at two players. It now uses Train 5, which has the three coaches.
|
||||
|
||||
### The `content.ts` comment pass
|
||||
|
||||
Asked for after #7 raised "where do we actually keep track of train cards?" — the answer being five
|
||||
documents of three vintages. **No data changed; only comments.** Four were factually wrong:
|
||||
|
||||
- `// Offices — Depot 4, Station 2, Terminal 1` — the real copies are 8/4/2, doubled by Q12 long ago.
|
||||
- A doc pointed at **`DEALT_DECK_SIZE`, which has never existed**; the symbol is `SOLITAIRE_DECK_SIZE`,
|
||||
defined eight lines below the comment that could not name it.
|
||||
- "the ten Mainline card types" — there are nine kinds; ten only holds if both Division Points count
|
||||
as one.
|
||||
- "the developer bot averages 7.0 Revenue against a target of 20", in the present tense. 7.0 was
|
||||
measured while `trainPerTransit` still defaulted to 1, and the next paragraph explains that setting
|
||||
was defaulted to 0 for being worth ~5.4 of it. Re-measured over 400 games at current defaults: about
|
||||
**zero**. Replaced with an instruction to run the harness rather than trust a number in a comment.
|
||||
|
||||
**And every Enhancement row cited its implementation as `file.ts:NNN`, and every citation had
|
||||
rotted** — `apply.ts:405` for Small Yard was pointing ~440 lines short, stale long before this
|
||||
release. All eight now name functions, which do not move, with a note never to cite a line number
|
||||
there. Swept `src/`: no others remain.
|
||||
|
||||
**Card counts came out of the comments**, Jesse's call — they move with play balance, so a comment
|
||||
that prints one is stale at the next retune. Kept: figures attributed to the recovered source sheet
|
||||
(a fixed document, and the audit trail for the transcription) and dated experimental findings.
|
||||
|
||||
### Documentation queued, not built
|
||||
|
||||
`TODO.md` gains an item for a card reference **generated from `content.ts`** so it cannot disagree
|
||||
with the game, in six sections — Mainline, then Home Deck Trains / Track / Industry / Modifiers /
|
||||
PVP. Each card wants its name, effect, placement, and the part only the implementation knows: whether
|
||||
its printed effect resolves yet. Deliberately no card counts. `enhancementText()` and
|
||||
`mainlineDescription()` are the model.
|
||||
|
||||
### Not in this release
|
||||
|
||||
**Gitea#2** — the Sparrow arrives with two loaded coaches, two passengers wait, four porters stand
|
||||
idle, and only one action can be taken. Reproduced and diagnosed: the engine is faithful to §9.2 and
|
||||
§2.2 at every step, but both directions of porter work move coaches one-way into a Classification
|
||||
Yard that returns only when the Division Yard is bare of all ~60 cars. Three ways out are written up
|
||||
in `TODO.md`; the choice is Jesse's. The unambiguous half — `impediments()` skips passenger
|
||||
facilities entirely, so a blocked platform gives the player no reason at all — is also still open.
|
||||
|
||||
---
|
||||
|
||||
## 0.6.1 — 2026-08-22
|
||||
|
||||
Six bugs came back from a gameplay-testing session on 0.4.9d. Five are fixed here; the sixth could not
|
||||
|
||||
Reference in New Issue
Block a user