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/
├── docs/
│ ├── rules/ ← the ruleset, card reference, glossary, decision record
│ └── architecture/ ← how it is built, and what the pieces are
├── src/
│ └── engine/ ← pure rules engine: no I/O, no clock, deterministic from a seed
└── test/
Start with docs/design.md — it indexes everything.
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.