v0.7.0 — four game types, a lobby you can read and leave, and a multiplayer game that makes a sound
The multiplayer set-up, the lobby, the start of a game, and four signals a remote client had never been sent. Reasoning, the preset table and what was verified how: CHANGELOG.md. - Co-op, Competitive, Cutthroat, Solitaire and Custom, on both screens, from one shared block — they had drifted, and each was missing a question the other asked. - A player reads the whole rule set before taking a seat, may leave a lobby or a running game, and keeps a seat across a reload. The host may clear a chair. The browser remembers every game it is in, not just the last one. - The start of a game is drawn: a handoff beat, an announcement, the code and type in the header. - Sound, the timetable flash, announcements and the just-drawn badge now reach a remote client; justDrawn goes to the seat that drew it and nobody else. - Played on StartOS, which found the rest: an Extra belongs to the player who played it, the board never named the Superintendent, bot seats were reported as absent players, and rule section numbers are out of every string a player reads. Also carries the previous session's Heavy Grade documentation work — asked again, answer unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016JczK5i33ZNSf2PtzZqdhS
This commit is contained in:
co-authored by
Claude Opus 5
parent
42adfda390
commit
06db36e5b5
@@ -10,29 +10,48 @@ train into an occupied Subdivision. Get that wrong and two trains meet at speed.
|
||||
|
||||
## Status
|
||||
|
||||
**v0.4.8 — 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.
|
||||
**Solitaire is playable in a browser and multiplayer runs against a server.** The solitaire game runs
|
||||
entirely client-side — the engine is pure, imports nothing outside itself, and never touches
|
||||
`Math.random`, so a static host is all it needs. Multiplayer adds an authoritative server for the
|
||||
seats a shared game requires.
|
||||
|
||||
- **Rules** — specified, with **three open questions** left. Thirteen gaps in the original prototype
|
||||
rules were found and closed; three more came after, and are in `TODO.md`: where the Local's coach
|
||||
stands while its engine works (a §A.4 question), Poling — the one card in the deck with no defined
|
||||
behaviour — and whether a Heavy Grade's orientation is rolled or chosen at setup.
|
||||
The current version is in [`package.json`](package.json), and every page stamps it with the commit
|
||||
and build date, so what is deployed can always be identified from the page itself. This section
|
||||
deliberately no longer names one: it went stale for six releases.
|
||||
|
||||
- **Rules** — specified. Thirteen gaps in the original prototype rules were found and closed, and the
|
||||
three that came after were all settled in v0.5.0: a coach may be set out at the Office (§A.4),
|
||||
Poling stays out of the deck at zero copies since the source records its effect only as "TBD", and
|
||||
**a Heavy Grade's orientation is rolled from the seed, never chosen by a player** — see
|
||||
[`docs/rules/implications.md`](docs/rules/implications.md) §10 for each ruling and its reasoning.
|
||||
- **Card faces** — every card's printed values specified.
|
||||
- **Architecture** — seven documents, including a 20-component build plan and the multiplayer 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** — the multiplayer server (Phases 0 and 1 of the plan are done: seat and player are
|
||||
separate, turn state is per player, and the page talks to a `Session` rather than to the engine, so
|
||||
a remote one drops in without the page changing — but there is no server, no turn submission and no
|
||||
per-player push), the 22 opponent-directed cards, and real audio.
|
||||
- **Code** — the engine, the bot, the balance harness, the replay viewer, the playable page, and the
|
||||
server. A game can be saved, shared, replayed and stepped back through.
|
||||
- **Multiplayer — built and running.** `src/server/` serves a lobby (create, join, preview, leave,
|
||||
add a bot, start, and a stream), turn submission, per-session state and persistence, with its own
|
||||
tests under `test/server/`. Games survive a release rather than being destroyed by one. A player
|
||||
weighing a join reads the **whole rule set before taking a seat**; a seat survives a browser
|
||||
reload; anybody may leave and the host may clear a chair; and the four transient signals that make
|
||||
a game feel alive — sound, the timetable flash, an announcement, the badge on the card you just
|
||||
drew — reach a remote client, which they did not before v0.7.0. What is still open is in `TODO.md`
|
||||
under Multiplayer — chiefly that **a player cannot see what the others did**, and that a lost
|
||||
session token still locks someone out of a running game from a genuinely fresh browser.
|
||||
- **Not built** — the opponent-directed cards (the Action and Space-use categories, held out of every
|
||||
deck until they have an implementation, along with the defensive cards whose only purpose is to
|
||||
answer them), and real audio. No screen offers a control for the opponent cards any more: the
|
||||
toggle could not do anything, so both screens state the fact in words instead.
|
||||
|
||||
Balance is *not* where it should be: the developer bot averaged 7.0 Revenue against a target of 20 —
|
||||
of which ~5.4 was the "one Revenue per train that completes its run" rule, so the working freight and
|
||||
passenger economy is still only ~2. That rule is now a **setting that defaults to off**, along with
|
||||
the passenger and freight rates and the opening hand, so the economy can be read on its own and the
|
||||
alternatives can be played rather than argued about. Nothing in this file or in `TODO.md` has been
|
||||
re-measured at the new defaults; `TODO.md` says why, and says which of it is the bot and which is the
|
||||
deck.
|
||||
Balance is *not* where it should be, and this file no longer quotes a figure for it. It used to say
|
||||
"the developer bot averages 7.0 Revenue against a target of 20", which stopped being true the moment
|
||||
the transit rule it names was defaulted to off — that rule was worth ~5.4 of the 7.0, for traffic
|
||||
nobody had to work. Measured at the current defaults the bot means about **zero**.
|
||||
|
||||
The three rates — passenger per coach, freight per load, train per transit — are **settings fixed when
|
||||
the game is dealt**, along with the opening hand and where an Extra may start, so the economy can be
|
||||
read on its own and the alternatives played rather than argued about. Run
|
||||
`node src/sim/harness.ts 400 standard` for today's number rather than trusting one written here;
|
||||
`TODO.md` says which of the gap 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.
|
||||
@@ -46,14 +65,18 @@ station-master/
|
||||
├── docs/
|
||||
│ ├── rules/ ← the ruleset, card reference, glossary, decision record
|
||||
│ ├── architecture/ ← how it is built, and what the pieces are
|
||||
│ ├── plans/ ← worked plans for a single change, kept for the reasoning
|
||||
│ └── design/ ← board layout studies and rendering samples
|
||||
├── public/replays/ ← saved games published to the site's replay directory
|
||||
├── public/
|
||||
│ ├── replays/ ← saved games published to the site's replay directory
|
||||
│ └── images/ ← art the build copies into the site
|
||||
├── scripts/ ← build and deploy the static site
|
||||
├── src/
|
||||
│ ├── engine/ ← pure rules engine: no I/O, no clock, deterministic from a seed
|
||||
│ ├── server/ ← the authoritative multiplayer server: lobby, sessions, persistence
|
||||
│ ├── sim/ ← bot, harness, replay, board rendering
|
||||
│ └── web/ ← the playable site: splash, game, replay viewer
|
||||
└── test/
|
||||
│ └── web/ ← the playable site: splash, game, lobby, replay viewer
|
||||
└── test/ ← including test/server/ for the server's own suite
|
||||
```
|
||||
|
||||
Start with [`docs/design.md`](docs/design.md) — it indexes everything. Commit messages stay high
|
||||
@@ -62,7 +85,9 @@ level; [`CHANGELOG.md`](CHANGELOG.md) carries the reasoning and the measurements
|
||||
|
||||
## Development
|
||||
|
||||
Requires **Node 22.18+**, which runs TypeScript directly by type stripping. There is no build step.
|
||||
Requires **Node 22.18+**, which runs TypeScript directly by type stripping — so there is no compile
|
||||
step, and the engine, the bot and the tests all run straight from source. (`npm run build:web` is a
|
||||
separate thing: it assembles the static SITE into `dist/`.)
|
||||
|
||||
```sh
|
||||
npm install
|
||||
@@ -104,16 +129,30 @@ is the thing this machinery exists to prevent.
|
||||
`fromSave` replays it exactly — that one property gives save, share, undo, restart recovery and
|
||||
post-game replay. Events are a DERIVED stream: they narrate what happened and drive the display,
|
||||
and they do not reconstruct the position. The phase driver mutates state and then describes it, so
|
||||
fourteen of the forty-six event types are never reduced. Anything that needs to rebuild a game
|
||||
roughly a third of the event types are never reduced at all. Anything that needs to rebuild a game
|
||||
replays the intents.
|
||||
- **Never call `Math.random()`.** One ambient random call silently breaks replay.
|
||||
- **Track is a deck card, but the opening district is dealt.** 96 of the 235 cards 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. The opening hand is the exception, and it is now **chosen when
|
||||
the game is dealt**: three random cards (the default, and the prototype rule), six random cards, or
|
||||
three track and three other from two separately shuffled piles. The last of those is the only one
|
||||
that guarantees you a district to build; deal six and you open over the limit of three, so the
|
||||
first turn is spent choosing. See `TODO.md`.
|
||||
- **A game is one of four TYPES, and a type is a set of defaults rather than a ruleset.** Co-op,
|
||||
Competitive, Cutthroat and Solitaire (`src/web/presets.ts`) each name an opening hand, an Extra
|
||||
rule, three revenue rates and the victory conditions; picking one fills the form, and changing any
|
||||
of them selects **Custom**, which keeps the scoring of the type it came from. The type is *derived*
|
||||
from the numbers rather than stored, so a saved game carries no name that can disagree with what it
|
||||
actually is. Both screens that deal a game — the lobby and the New Game dialog — ask the same
|
||||
eleven questions through one shared block (`src/web/settings-form.ts`), because for two releases
|
||||
they each had a question the other lacked. What separates the types: Co-op alone pays for a
|
||||
transit, Cutthroat alone lets an Extra be planted in another player's district and has no shared
|
||||
failure condition at all beyond three collisions in a Day, and the Revenue floor is a formula in
|
||||
the table size and the length (3 per player per Day in Co-op, 2 in Competitive) rather than a
|
||||
number.
|
||||
- **Track is a deck card, but the opening district is dealt.** Track is the largest category in the
|
||||
deck by a distance — so a district is built from what you draw, and building it costs you the
|
||||
industry or train you drew instead. The opening hand is the exception, and it is **chosen when the
|
||||
game is dealt**: three random cards (the prototype rule), six random cards, or three track and
|
||||
three other from two separately shuffled piles. The last of those is the only one that guarantees
|
||||
you a district to build; deal six and you open over the limit of three, so the first turn is spent
|
||||
choosing. **Every game type now deals six** (Jesse’s call, v0.7.0) — the engine's own fallback,
|
||||
`SOLO_CONFIG`, deliberately did not move with it, because every sim measurement is taken against
|
||||
that. See `TODO.md`.
|
||||
- **What the work pays is a setting too.** Passenger revenue per coach, freight revenue per load and
|
||||
train revenue per transit each run 0–5 and are fixed when the game is dealt. The first two default
|
||||
to 1 and pay at both ends of a movement — boarding *and* detraining, loading *and* unloading. The
|
||||
|
||||
Reference in New Issue
Block a user