v0.8.2 — every district opens on a Depot, and the docs are pages now

A second-digit bump for a playtest read back against the save file. Nine questions
were asked of one three-Day game; three were bugs, three were the rules working
and undocumented, three were decisions. Every save on the test server was replayed
against this build BEFORE release, which is how the cost of each rule was known
before it was chosen rather than discovered after.

EVERY DISTRICT OPENS ON A DEPOT. A Whistle Post has one A/D track and is not a
Passenger Facility, so the opening of every game was spent unable to work a
passenger and one arrival away from a collision. Two A/D tracks and passengers
from Stage 1 now; "Players start with Whistle Posts, not Depots" is the harder
game, set when the game is created. The deck follows the choice — starting on
Depots the four Depot upgrade cards are left out, because an upgrade must be to
the next tier and a Depot card at a table of Depots is a dead draw. How much
easier it is showed up as a test failure rather than an argument: the cue-coverage
pool needed widening from 24 seeded games to 60 before it held one collision.

NO SAVE WAS STRANDED BY IT, which took care. This is the one house rule that
changes how a game is DEALT rather than how it plays, so replaying a save under
the wrong opening is a different railroad from intent one — silently, with no
error. `withSavedOpening` fills it on the replay paths ONLY. Putting it in the
resolver instead made a fresh Cutthroat game deal Whistle Posts and read as
Custom, which is how the distinction was found.

THREE BUGS, ALL REPORTED FROM ONE GAME AND ALL CONFIRMED ON ITS SAVE.

An Office held TWO TRAINS ON ONE A/D TRACK. The capacity test passed with nothing
standing, the train the Interlocking had been holding at the Limits was moved into
the free slot, and the arriving train was pushed in after it without anyone asking
again whether there was room — so the collision §8.3 calls for never happened. The
held train keeps priority; the newcomer now takes the consequence it would have
met had the held train arrived first.

THE HISTORY FROZE, permanently, and the log cap was not really the cause. Each
seat's "what have I sent you" bookmark was an INDEX into an array the game trims,
so once a seat's bookmark reached the limit the slice returned nothing for the
rest of the game — at a different moment per seat, because each holds its own.
That game's log ended at exactly the cap. Lines carry a sequence number now, which
survives trimming; proven by pushing twice the cap through a simulated seat.

§8.1 ASKED THE WRONG QUESTION TWICE. "Trains may pass" returned `clear` before the
Subdivision was looked at, so a train entering a Double Track was released however
busy the rest of it was — that, not anything about Control Points, is what let
Train 8 out with no ruling. And a train standing at an Office was invisible to the
scan, so one about to re-enter the very Subdivision being entered counted for
nothing. Capacity is the test, not presence: a Depot with a track free is not in
the way; a Whistle Post with its one track taken is.

THINGS THAT HAPPENED SILENTLY NOW SAY SO — a train held against a facing one, a
train released from the Limits (a side effect of somebody else's arrival, so it
simply appeared at the Office), and the train an Interlocking is holding, whose
explanatory tooltip has existed since #99 with NO renderer ever reading the flag.

WHERE A MOVE IS REFUSED, AND WHY. `exploreMoves` decides where the rails go and the
pick-up restrictions are enforced afterwards in `check`, so a square the rails
reached and the card forbade was reachable, un-offered, and absent from the block
list with nothing said. Those squares are blocked with the rule that blocks them
now, and the reasons are got by ASKING `check` rather than re-deriving: a second
implementation of the rules is exactly the failure the block list exists to avoid.
A train may also always recover its own caboose — X13 prints "may drop but not
pick up anything", and a train needs its caboose to be made up, so one that parted
with it could never legally leave again.

RULES DECIDED IN SEPTEMBER AND APPLIED HERE. A Modifier must sit square against its
host, no diagonals. A passenger Modifier may not be played at a Whistle Post. Both
were built, measured, held back for a fortnight so a playtest could finish, and
applied now. A Second Section costs its card: `SECOND_SECTION` was declared in
content.ts and never dealt, so the action was free and the bot ordered 26
accidental ones in a measured round. The card is dealt and spent — gating on a card
the deck never holds would have deleted the mechanic rather than fixed it.

THE DOCUMENTATION IS A SET OF PAGES, not five text files served as text/plain — a
card reference is mostly tables, and as plain text a table is rows of pipes.
Markdown is still the one copy; the build renders it, and publishes the .md beside
each page. No Markdown library: this project has no runtime dependencies and one
would be a poor first. The pages add what Markdown cannot carry without drifting —
a nav across the set, a contents list built from the headings actually rendered,
an anchor on every heading, a 70-character measure, and tables that are tables.
They print as ink on paper.

The references caught up with the rules, checked rather than assumed: two
statements had gone from stale to misleading (the Quickstart told a new player to
"get a Depot down as soon as one appears"), and four rules nobody could look up
are written down — the Office tier table, §8.1 in practice, what the Circus Train
pays for, and that a Realignment can be a card with no legal target.

Adding one card to the deck reshuffles every seeded deal, which broke five
fixtures. Each was a seed meaning "a game like this" — TODO #84, exactly — so
seeds moved and pools widened rather than assertions weakening, and the clearance
fixture pins its terrain the way `enhancements.test.ts` already does. The three
published replays were re-recorded.

Closes TODO #40, #42a, #108, #109 and #110.

1046 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:
Jesse.Markowitz
2026-09-23 07:07:21 -04:00
co-authored by Claude Opus 5
parent 517238a727
commit 3befc420da
66 changed files with 6828 additions and 3324 deletions
+42 -27
View File
@@ -1,12 +1,10 @@
# Station Master — Rules
**Describes the game as built at v0.8.1.0** (2026-09-22). These references are kept current with
every release rather than versioned as editions, so there is no version in the filename: this file
is always the latest, and the build it describes is stated here.
**Version 0.8.2** · 2026-09-23
**Authority:** observed code paths and tests. Where a card face, a prototype document and executable
behaviour differ, this document reports **executable behaviour** and marks unimplemented material.
Per-card numbers are not repeated here — [`rules/as-built.md`](rules/as-built.md) is generated from
Per-card numbers are not repeated here — the Home deck and Mainline deck references carry tables generated from
`src/engine/content.ts` and is the table of record.
**New to the game? Start with the [Quickstart](quickstart.md).**
@@ -30,7 +28,7 @@ This book is divided as follows:
The companion references are the [Quickstart](quickstart.md) for a new player,
[Mainline deck](mainline-deck.md), [Home deck](home-deck.md),
[components](components.md), and the generated per-card table
[`rules/as-built.md`](rules/as-built.md).
[Home deck](home-deck.md) and [Mainline deck](mainline-deck.md).
## 2. Definitions
@@ -119,9 +117,7 @@ The browser also stores the current local game and resumes it automatically when
Three modes: **Solitaire**, **Competitive** and **Co-op**. All three are playable.
**Length is a free `days` count**, not a preset. The old `short`/`standard`/`campaign` presets
carried a `target` and were dropped in 2026-08; they survive only as a convenience argument for the
simulation tooling, resolving to 3, 5 and 10 Days. The default is 5.
**Length is a free `days` count**, not a preset — any number of Days may be set. The default is 5.
**How a game ends and who wins:**
@@ -196,7 +192,7 @@ On a player’s Local Operations turn, choose exactly one available option.
**Switch.** Select any Crew Tray currently in that player’s Office Area. It gets six Moves, or five under Reduced Visibility on the listed night Stages. A Move travels any connected distance in one direction and must end on Operational Rail. Reversing is a separate Move. A train may pass through a turnout but cannot stop on it. It may not share or pass through another train except through an Office with a free A/D track.
Standing cars couple automatically when the train reaches them; it may not pass them, and the resulting consist may not exceed four cars. Coupling forward places cars ahead of the engine; coupling while backing places them behind it. Setting out cars does not spend a Move, but the cut must come from an outer end of the consist and may not be left on the Office. A **Small Yard** re-orders a consist for one Move, and since v0.8.0.14 may also place cars **ahead
Standing cars couple automatically when the train reaches them; it may not pass them, and the resulting consist may not exceed four cars. Coupling forward places cars ahead of the engine; coupling while backing places them behind it. Setting out cars does not spend a Move, but the cut must come from an outer end of the consist and may not be left on the Office. A **Small Yard** re-orders a consist for one Move, and may also place cars **ahead
of the engine** — which is how a cut is set up to be shoved into a facing industry. Each option on
the menu shows the train it would build, laid out west to east as the board draws it, and says
whether the result may leave the Office or would be held there. Flying Switch spends one Move to
@@ -211,18 +207,14 @@ roll a tail cut into a connected Freight Facility.
> draw actually empties the pile; a pile with cards buried under the one taken is not refilled, or
> the Departments would grow without limit and drain the deck into themselves.
>
> This surprised a player at the table (2026-09-22), which is why it is written down here: the rule
> was implemented from the prototype rules and had never reached this document.
**Freight Agent.** Make one of these operations, then the turn ends: stock one green outbound box from a matching loaded Division Yard car; clear one red inbound box to the Classification Yard; unjam one outbound, inbound, or MEN | AT | WORK load to the Classification Yard; or explicitly end without acting. Freight can be stocked only when an unclaimed empty matching car is already spotted at that industry. Passengers may wait in a green Office box without a train present.
> **A car cleared from a red Inbound box comes back empty.** Whatever was in it has arrived — the
> passengers are out of the station, or the load is in the industry — and the Revenue for it was
> paid on arrival, not on clearing. What returns to the Classification Yard is the *car*, in the
> common supply and carrying nothing. Before v0.8.1.0 it returned still marked loaded, which put
> coaches into the yards that could never be used to unload another passenger.
> common supply and carrying nothing.
Implementation note, still true at v0.8.1.0: `card.discard` is accepted during Local Operations
Implementation note: `card.discard` is accepted during Local Operations
without checking that the Draw option was chosen — unlike `card.play`, which does check. This is an
implementation quirk rather than a fourth published turn option.
@@ -294,10 +286,9 @@ Car permit no passenger work. A Whistle Post has no Porters.
> could board or detrain anywhere for the rest of the game, while trains whose cards call for coaches
> were made up empty.
>
> **This is the rules working as printed and the ruling is that it stands** (Jesse, 2026-09-17, the
> same ruling Gitea#2 got: running out is part of the game). What changed is that the game now says
> it — the yard panel warns while the shortage lasts, and a train made up short reports why. Whether
> the ratchet should be broken is open as TODO #108, to be decided on a second game's evidence.
> **This is the rules working as printed: running out of cars is part of the game.** The yard panel
> warns while the shortage lasts, and a train made up short reports what it wanted and why none is
> coming.
### 4.7 Freight work
@@ -334,7 +325,7 @@ score has to come principally from passenger and freight work.
### 6.1 What the engine supports
The rules engine has always supported multiple named players; since v0.7 the lobby, server and
The rules engine supports multiple named players; the lobby, server and
client around it are delivered too, so this section now describes a game people actually play. The
engine has player, seat, score, Office Area, Division, phase-order, co-op and competitive-mode data
for multiple named players. It deals each player a hand, creates one Office Area per seat, starts acting order at the Superintendent and proceeds eastward, and models the following multiplayer-specific outcomes:
@@ -423,16 +414,15 @@ train is still present for that Stage's Load/Unload phase.
### Can I choose the direction of an Extra or a Heavy Grade?
**An Extra, yes** — the player who played the card chooses where it starts and which way it runs, and
loads it as they choose (v0.6.2). **A Heavy Grade, no**: orientation is rolled from the seed. That is
loads it as they choose. **A Heavy Grade, no**: orientation is rolled from the seed. That is
a decision rather than a gap — the card sits between two districts and belongs to neither, so handing
the choice to one neighbour would advantage them permanently. See the Mainline deck reference.
### Can I use an Interchange to reorder a train?
**No, and the card used to claim otherwise.** Its printed car-sorting has never been implemented; the
description was corrected on 2026-09-20 to stop advertising it. What the Interchange actually offers
is the one Mainline card with a yard, so an **Extra may be made up and started there**. Re-ordering a
consist is done at a **Small Yard** in a district.
**No.** Its printed car-sorting is not implemented. What the Interchange offers is the one Mainline
card with a yard, so an **Extra may be made up and started there**. Re-ordering a consist is done at
a **Small Yard** in a district.
### Can I play attack cards on another player?
@@ -442,8 +432,8 @@ is rejected.
### Is multiplayer playable?
**Yes.** Lobby, game codes, seating, bots, an authoritative server that survives restarts, per-seat
reconnection and a replayed view of everyone else's turns are all delivered — see §3.5 and §6. The
opponent-directed cards remain unimplemented in every mode, so there are still no card attacks.
reconnection and a replayed view of everyone else's turns — see §3.5 and §6. The opponent-directed
cards are unimplemented in every mode, so there are no card attacks.
### Why did my passenger train arrive with no coaches?
@@ -456,3 +446,28 @@ has happened, and a train made up short says so in the log.
Because that train is **already made up**, so every re-order on offer would break it — most often by
moving the caboose off the rear, which §8.2 will not let a train depart with. If the train is *not*
currently fit to run, at least one option will be marked "MADE UP, ready to leave".
### Will an old save still replay?
Not always. A save is a seed and the list of moves, replayed through the rules of whatever build
opens it — so a change that makes a once-legal move illegal stops the replay at that move. The game
names the move and leaves the file untouched; the build the game was played on will still finish it.
## §8.1 in practice — what holds a train at the end of the line
A train is held, released or put to the Superintendent by what is in the **Subdivision** ahead of
it: the run of Mainline cards between two Control Points. Every Office that is not a Control Point
lies inside one, so a table of Whistle Posts is a single Subdivision from end to end.
- A train coming **towards** it is an absolute bar. The train holds, and the history says which
train is coming and that there is no Control Point between them to pass at.
- A train going the **same way** is the Superintendent's ruling.
- A card printing **"trains may pass"** excuses only what is standing on **that card**. It does not
clear the rest of the Subdivision.
- A train **standing at an Office with no free A/D track** occupies the Subdivision too: it is about
to re-enter and there is nowhere for the two to pass. An Office with a track still free does not
hold anyone up.
**An Office never holds more trains than it has A/D tracks.** A train the Interlocking is holding at
the Limits takes the first track to free, ahead of anything arriving afterwards — and the train that
arrives to find it taken is held at its own Limits, or collides if there is no Interlocking.