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
+14 -5
View File
@@ -11,7 +11,7 @@ that live nowhere else.
| Family | Direction | Authority |
| --- | --- | --- |
| **Intent** — a proposal, may be rejected | client → server | [`src/engine/intents.ts`](../../src/engine/intents.ts) — `Intent`, 28 variants |
| **Event** — a fact, ordered, the game's history | server → clients | [`src/engine/events.ts`](../../src/engine/events.ts) — `GameEvent`, 46 variants |
| **Event** — a fact, ordered; narration, not the record (§3) | server → clients | [`src/engine/events.ts`](../../src/engine/events.ts) — `GameEvent`, 46 variants |
| **View** — a redacted projection | server → one client | [`src/sim/view.ts`](../../src/sim/view.ts) — `Frame` and `snapshot(state, …, seat)` |
| **Rejection** — why an intent was refused | server → one client | `RejectionCode` in `intents.ts` |
@@ -79,8 +79,16 @@ which makes them worth logging server-side: a spike means client and server rule
## 3. Events
Events are the authoritative history: `state = fold(events)`, which buys reconnection, persistence and
post-game replay for one design decision.
**Events narrate; they do not reconstruct.** This section claimed `state = fold(events)` until
v0.4.0, and it was never true: `applyIntent` goes through `reduce`, but the phase driver mutates state
and *then* emits a descriptive event, so fourteen of the forty-six event types are never reduced — the
clock, and the whole Mainline phase, which is to say every train movement in the game.
**The canonical record is `{ seed, history: Intent[] }`**, replayed by `fromSave`. That is what save,
share, undo, restart recovery and post-game replay all run on, and it is what persistence stores
(`multiplayer.md` §10). Events drive the display and the notifications. Do not build anything on
folding them without first making the phase driver reduce, which is a rewrite of the most rule-dense
code in the project and buys nothing the current plan uses.
**Events must render standalone** — the rule is stated at the top of `events.ts` and it is the one that
gets broken by accident. Carry the `from`/`to`, not an id the renderer resolves against live state,
@@ -92,8 +100,9 @@ Two consequences worth knowing before adding an event:
Fault depends on *where* the wreck happened — Superintendent for a Mainline card, the local player
between their Limits (§10) — and getting it wrong misattributes a −5 and, in Competitive, feeds a
floor that ends the game.
- **Events are not what goes over the wire.** The server folds them and pushes the resulting `Frame`
(`multiplayer.md` D2/D3). They are the store and the replay format, not the protocol.
- **Events are not what goes over the wire.** The server applies the intent and pushes the resulting
`Frame` (`multiplayer.md` D2/D3). Events are narration and cues, not the protocol — and a reconnect
gets a fresh `Frame` rather than the tail it missed.
---