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
Design complete, implementation just begun.
- 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 — build step 1 of 12 complete: card catalogue, state model, seeded RNG.
The MVP target is solitaire, one Office, a fixed number of Days, with a server talking to a single browser.
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 — it indexes everything. Commit messages stay high
level; CHANGELOG.md carries the reasoning and the measurements, and
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.
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, andadvance(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.