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
+184
View File
@@ -19,6 +19,190 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
---
## 0.8.2 — 2026-09-23
A playtest read back against the save file, and the rules that came out of it. Nine questions were
asked of one three-Day game; three were bugs, three were the rules working and undocumented, and
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.
### Every district opens on a Depot
**The single biggest change.** 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. Every district opens on a **Depot** now: two A/D tracks, passengers from Stage 1.
**"Players start with Whistle Posts, not Depots"** is a setting for a table that wants the harder
game. 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 in a test rather than an argument: the cue-coverage pool needed
widening from 24 seeded games to 60 before it contained a single 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. Saves written before the setting existed name the rules that
existed then and cannot name this one, so `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.
### An Office held two trains on one A/D track
Reported from the table and confirmed on the save: at Day 2 Stage 9 a Whistle Post with one A/D
track held Trains 8 and 19 at once.
An ordering fault in `arriveAtOffice`. The capacity test passed with nothing standing, the train the
Interlocking had been holding at the Limits was then 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 its priority, because it has been waiting. The NEWCOMER takes the consequence,
and it is the same one it would have met had the held train arrived first: held at its own Limits
where there is an Interlocking, a collision where there is not.
### The history froze, permanently, and the log cap was not really the cause
One player's history stopped gaining lines at Day 2 Stage 8 and the other's at Day 2 Stage 4, in a
two-player game that never reached Day 5.
The log is trimmed to a limit — but each seat's "what have I sent you" bookmark was an **index** into
that array. Once a seat's bookmark reached the limit the array never grew past it again, so the
slice returned nothing for the rest of the game. Different moments per seat because each holds its
own bookmark. That game's log ended at exactly the cap.
Raising the cap only delays it. Lines carry a **sequence number** now, which survives trimming, so
the bookmark stays meaningful however much is dropped. Proven by pushing twice the cap through a
simulated seat and asserting all of it arrives. The limit is also much larger, on its own merits: a
four-player game over ten Days is several times the game that first hit it.
### §8.1 asked the wrong question twice
**"Trains may pass" was short-circuiting the whole Subdivision.** The check returned `clear` before
the Subdivision was looked at, so a train entering a Double Track was released however busy the rest
of it was — including against a train coming the other way three cards deeper in. That is what let
Train 8 out of the Western Division Point with no ruling asked, and the reason had nothing to do
with Control Points. The card prints that TWO TRAINS MAY SHARE IT, so it excuses occupants on that
card and nothing else.
**A train standing at an Office was invisible to it.** Train 19 was released from the Eastern
Division Point towards Train 14 and nobody was asked — because at that moment Train 14 was not in
transit at all, it was standing in a district. §8.1 was only ever reading trains on Mainline cards,
so a train about to re-enter the very Subdivision being entered counted for nothing.
**Capacity is the test, not presence** (Jesse's reasoning exactly): at a Whistle Post, one A/D track
with a train on it means there is nowhere for the two to pass and no choice to be made. At a Depot
or a Terminal with a track still free, the train at the Office is not in the way.
### Things that happened silently now say so
**A train held against a facing one.** An absolute bar that returned without a word — the train
simply did not depart, Stage after Stage. Only the ABS Signals case announced itself, and it had
been given a line for exactly this reason. The line names the train that is coming and says there is
no Control Point between them to pass at.
**A train released from the Limits.** It happened as a side effect of somebody else's arrival, so
the held train appeared at the Office with nothing said — "wasn't clear what changed and why train 8
was suddenly released". The line names the train whose arrival freed the track, because *why now* is
the whole question.
**A train the Interlocking is holding.** The tooltip explaining it has existed since #99 and **no
renderer ever read the flag**, so the train drew like any other crew and nothing told a player to
hover. It is drawn held now — red and dashed, the same "stopped, and not by choice" the Red Flag
means elsewhere on the map.
### Where a move is refused, and why
Asked directly: *"how does a user know what rule is violated and why you can't go there?"* Nowhere,
was the answer. `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 no reason given.
Those squares are blocked with the rule that blocks them now, as `cardRule`. The reasons are got by
**asking `check`**, not by re-deriving the rules: a second implementation is exactly the failure the
block list was built to avoid, and a reason that does not match the refusal is worse than none.
**And a train may always recover its own caboose.** X13 prints "may drop MTs but not pick up
anything", and a train needs its caboose at the far end to be made up — so a train that parted with
its caboose could never legally leave again. It stranded itself, permanently and silently. The
caboose only, not "your own cars" generally: it is the one car whose absence stops the train
departing.
### Two Modifier rules, decided in September and applied here
**A Modifier must sit square against its host** — north, south, east or west. No diagonals: touching
at a corner is not touching. This reverses an earlier report in the other direction, and the test
that asserted the old rule now asserts the new one on the very square that prompted it.
**A passenger Modifier may not be played at a Whistle Post.** A Waiting Area, Restaurant or Hotel
needs an Office upgraded to at least a Depot. It reverses the ruling that let them stand dormant: a
Whistle Post allows neither direction, so the outbound slot was discarded on the spot and only the
porter landed, and a card that can be played to no effect is a trap however well it is labelled.
The grant-recovery path in `officeUpgraded` is kept and is now unreachable by play.
Both were built, measured, backed out for a fortnight so a playtest could finish, and applied here.
The cost is known rather than guessed: of the thirteen saves on the test server, the Whistle Post
rule is what strands six of them.
### The documentation is a set of pages now, not five text files
They were served as `text/plain`, which is honest and unreadable: a card reference is mostly tables,
and as plain text a table is rows of pipes. That was TODO #109, taken deliberately as the short
version to get the references in front of testers for one round.
**Markdown is still the one copy.** `docs/*.md` is what is written and reviewed; the build renders
it. A hand-written HTML twin drifts on the first edit, which is the whole lesson of #15a. The
Markdown is published beside each page too — it costs nothing, it is what a reader wanting a diff
actually wants, and it keeps every link handed out while the documents were text working.
**No Markdown library.** `scripts/markdown.ts` covers the subset these five documents use. This
project has no runtime dependencies at all and one would be a poor first — and the renderer is
ten tests' worth of behaviour, not a general-purpose parser: it escapes unconditionally, and there
is no raw-HTML passthrough.
What the page adds over the text, each because the Markdown cannot carry it without drifting: a nav
across the five documents; a contents list built from the headings actually rendered; an anchor on
every heading, so a section can be linked in a bug report; a ~70-character measure, because long
lines are the single biggest thing making long documents hard to read; and tables that are tables,
with numeric columns right-aligned and wide ones scrolling inside the page rather than widening it
on a phone. It prints as ink on paper, nav and contents dropped — a rules reference is a thing
people print.
### The references caught up with the rules
Checked against this release rather than assumed, and two statements had gone from stale to
misleading. The Quickstart told a new player they start on a **Whistle Post** and to "get a Depot
down as soon as one appears" — advice for a game that no longer exists. Components listed "Whistle
Post cards ×4" as what everyone starts on.
Also added, because the playtest showed each was a rule nobody could look up: the **Office tier
table** (A/D tracks, Porters, passengers, Control Point) and what A/D tracks decide; **§8.1 in
practice**, including that a card printing "trains may pass" excuses only that card and that an
Office with no free A/D track occupies the Subdivision; the three conditions the **Circus Train**
pays on; and that a **Realignment can be a card with no legal target**, which is exactly what
happened at the table.
### Smaller, from the same session
**An industry may be built over a straight** off the Running Track, the way a turnout may upgrade
one — so rail can go down before the industry that will serve it. Only a plain straight: a Facility
carries east–west track, so the swap cannot break a neighbour's join, where a curve or turnout
could.
**Revenue lines name the player who earned them.** The history prefixes the ACTOR, and revenue is
not always the actor's — a train completing its run pays everybody with no actor at all, so those
lines carried no name whatsoever.
**"Waiting on" flashes when it is your turn**, amber, reading the move on screen rather than the
live one so it does not flash while your board is still catching up.
**The Circus Train says what it pays for** on its own line while it is being made up, instead of as a
clause trailing the consist — it pays for STOPPING, once per district, and only if every car but the
caboose is loaded. The history line says the same when the point lands.
**A Realignment says what it can convert.** "Convert one Mainline type to another" is true and
useless when only four of the nine types convert at all and the Division may have dealt none of
them — which is exactly what happened.
## 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