Fifteen items from two playtest sessions. Three that read as drawing faults were engine bugs: cars could be added to a train that was not being made up (50 offers in 8 games), the make-up panel merged two trains and could couple a car to the wrong one, and an Office upgrade silently deleted what a Modifier had added. A fourth was a sentinel inside a coordinate's own value range — a Mainline placement travelling as row -1, which is an ordinary district row. Trains are now drawn the way they stand: west on the left, nose toward the way the engine faces, on both the district card and the Division chip. Undo steps back through the game by replaying the save without its last intent. The switching walk keeps its rejections, so the board can say why a square is not offered. Laborers and Porters are on the card, and the rule that a district only grows outwards is finally written down. Versions start here: third digit for fixes, second for a feature set, 1.0 for a release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GgtkX8JnvKa8y2tuJ8aQf4
88 lines
4.9 KiB
Markdown
88 lines
4.9 KiB
Markdown
# Station Master
|
||
|
||
A railroad operations game set in the era of timetable-and-train-order railroading (1840–1950),
|
||
being built as a multiplayer browser game with an authoritative server.
|
||
|
||
You are the Station Master of a lineside Office on a shared east–west Division. Trains run to a
|
||
timetable with no radios — just pocket watches and written orders. You switch cars, work freight and
|
||
passengers, and take your turn as Superintendent deciding whether it is safe to clear a following
|
||
train into an occupied Subdivision. Get that wrong and two trains meet at speed.
|
||
|
||
## Status
|
||
|
||
**v0.1.0 — solitaire is playable in a browser.** The whole game runs client-side: the engine is pure,
|
||
imports nothing outside itself, and never touches `Math.random`, so a static host is all it needs.
|
||
|
||
- **Rules** — fully specified. Ten gaps in the original prototype rules found and resolved.
|
||
- **Card faces** — every card's printed values specified.
|
||
- **Architecture** — six documents, including a 20-component build plan.
|
||
- **Code** — the engine, the bot, the balance harness, the replay viewer and the playable page. A
|
||
game can be saved, shared, replayed and stepped back through.
|
||
- **Not built** — multiplayer (the engine runs 2–5 player games and the bot plays them, but there is
|
||
no server, no turn submission and no per-player view), the 22 opponent-directed cards, and real
|
||
audio.
|
||
|
||
Balance is *not* where it should be: the developer bot averages 1.4 Revenue against a target of 20.
|
||
`TODO.md` says why, and says which of it is the bot and which is the deck.
|
||
|
||
Versions follow the convention at the top of [`CHANGELOG.md`](CHANGELOG.md): third digit for fixes,
|
||
second for a set of features, 1.0 for the first release that deserves the name.
|
||
|
||
## Layout
|
||
|
||
```
|
||
station-master/
|
||
├── CHANGELOG.md ← what changed and why, in detail, commit to commit
|
||
├── TODO.md ← open questions, provisional numbers, things to come back to
|
||
├── docs/
|
||
│ ├── rules/ ← the ruleset, card reference, glossary, decision record
|
||
│ ├── architecture/ ← how it is built, and what the pieces are
|
||
│ └── design/ ← board layout studies and rendering samples
|
||
├── public/replays/ ← saved games published to the site's replay directory
|
||
├── scripts/ ← build and deploy the static site
|
||
├── src/
|
||
│ ├── engine/ ← pure rules engine: no I/O, no clock, deterministic from a seed
|
||
│ ├── sim/ ← bot, harness, replay, board rendering
|
||
│ └── web/ ← the playable site: splash, game, replay viewer
|
||
└── test/
|
||
```
|
||
|
||
Start with [`docs/design.md`](docs/design.md) — it indexes everything. Commit messages stay high
|
||
level; [`CHANGELOG.md`](CHANGELOG.md) carries the reasoning and the measurements, and
|
||
[`TODO.md`](TODO.md) is what we have decided not to forget.
|
||
|
||
## Development
|
||
|
||
Requires **Node 22.18+**, which runs TypeScript directly by type stripping. There is no build step.
|
||
|
||
```sh
|
||
npm install
|
||
npm test # node --test
|
||
npm run typecheck # tsc --noEmit
|
||
```
|
||
|
||
Because Node strips types rather than compiling them, the codebase is restricted to **erasable
|
||
syntax**: no `enum`, no parameter properties, no namespaces. `tsconfig.json` enforces this.
|
||
|
||
## Design notes worth knowing
|
||
|
||
- **The rules engine is pure.** No I/O, no clock, no sockets, and all randomness derives from one
|
||
stored seed — so any game is exactly replayable, and a full game can be driven in a unit test with
|
||
no server at all.
|
||
- **It has two entry points, not one.** `apply(state, intent)` for player actions, and
|
||
`advance(state)` for everything the game does on its own — Mainline movement, collisions, the Stage
|
||
clock, Superintendent rotation.
|
||
- **State is `fold(events)`.** The event log is the source of truth, which is what gives reconnection,
|
||
restart recovery and post-game replay from a single decision.
|
||
- **Never call `Math.random()`.** One ambient random call silently breaks replay.
|
||
- **Track is a deck card.** 104 of the 243 cards in the Home Office deck are track — the largest
|
||
category — so a district is built from what you draw, and building it costs you the industry or
|
||
train you drew instead. There is no separate supply and no one-piece-a-turn cap.
|
||
- **The track is 45° geometry, not a graph on a grid.** Measured off `docs/tracks.png`: the through
|
||
rail runs east–west across the *exact vertical middle* of every card, there is no north–south track
|
||
anywhere, and everything that leaves through the north or south edge does so at **45°, through the
|
||
middle of that edge**. So two ports meeting is not enough to make a rail — two 45° legs can meet at
|
||
the same point and still form a V. Adjacency is `joins()` in `src/engine/track.ts`, never a bare
|
||
pair of `hasPort()` calls. A printed card turns 180° but never flips, so its handedness fixes which
|
||
diagonal its leg lies on for good, and a run-around needs one card of each hand.
|