v0.4.1 - bug fixes. initial d12 rolls determine what player in which seat.

This commit is contained in:
Jesse
2026-08-13 15:28:53 -04:00
parent 49f8504b05
commit a7221dcf20
22 changed files with 736 additions and 237 deletions
+23 -7
View File
@@ -127,13 +127,18 @@ Ordered within each section by how much it is currently costing us.
can be tuned until this is settled**. The fix is a decision, not a patch: either a load stops
being a `RollingStock` and becomes its own type, or `inboundCleared` discards rather than
banking. Found by a conservation audit, not by a failing test.
- [ ] **`state = fold(events)` is not literally true, and the README says it is.** Replaying the
event log onto a fresh state throws: the phase driver mutates state directly and emits a
descriptive event afterwards — `newTrainPhase` does `s.trays.set(...)` and then pushes
`trainMadeUp`. Replay works because it re-applies INTENTS (`fromSave`), not because folding
events reconstructs the position. Nothing is broken today, but the claim underwrites
reconnection and restart recovery, which are unbuilt — so it should be either made true or
restated before anything is built on it.
- [x] **`state = fold(events)` was not true, and the docs said it was. Settled: the INTENTS are
canonical.** Measured before deciding — `advance.ts` never calls `reduce`, so **14 of the 46
event types are never reduced**: the clock, and the entire Mainline phase, which is every train
movement in the game. Folding the log rebuilds a district and not a railroad. Jesse's call, and
the cheap one: the plan never needed fold — persistence is `{ engineVersion, seed, config,
history }` (`multiplayer.md` §10) and the wire carries `Frame`s, not events (D2/D3), so
reconnection is a fresh Frame rather than an event tail. Making the phase driver reduce would
have been a rewrite of the most rule-dense code in the project to buy something nothing uses.
Corrected in the README, four architecture documents and six source comments; `test/events.test.ts`
pins the unreduced set so that closing the gap later is a deliberate act, and asserts the
property that does hold. **If you ever do make the phase driver reduce, that test fails and
tells you which docs now understate the engine.**
- [ ] **Engines are not a SUPPLY yet, only a position.** `engineAt` now records where the engine
sits in the tray and the consist shows it, but an engine is still conjured with the tray
rather than drawn from the Division Yard and returned to it. The rules put engines in the
@@ -477,6 +482,17 @@ target is settled and freight carries its intended share.
another player's score. Bots fill empty seats at lobby time only (D8).
- **Let a player resign and hand their railroad to a bot** to finish. Same care needed as
above, but it is consented rather than imposed.
- **A forcing turn timer — explicitly NOT in the design.** `lobby-and-sessions.md` §5 used to
specify one: on expiry the server took "the safest legal action", including denying a
clearance. Cut in the review, because it is the same objection as a bot playing for an absent
player — the clearance decision changes somebody else's score, so anything that answers it
automatically changes the game. Explore later if halted games turn out to be a real problem
at a real table; the reasoning worth keeping is that **deny** is the safe default, since a
held train costs a Stage and a wrecked one costs 5 Revenue and feeds the collision floor.
- **~~The opening D12 for the Eastern Division Point (§4.4) decides nothing.~~ Done in
v0.4.1** — it orders the whole chain now, west to east by ascending roll. The lobby still owes
it a display: `state.openingRolls` is kept so clients can show the rolls forming the chain
rather than only the result (`lobby-and-sessions.md` §4).
- **Revisit the join secret** (D14). One server-wide secret, passed out of band, gates create
and join. Enough for a private box, probably not enough if `stationmaster.<domain>` is
pointed at the open internet for long. Note that one-game-at-a-time per person is expected