v0.7.9.8 — the test command did not typecheck, and the plan had gone stale
Housekeeping before v0.8.0: the answer to "anything else that should be looked at first". One real hole, one stale document, and my own leavings. #102 — `npm test` passed green on a type error. `pretest` ran `scripts/build-web.ts`, which invokes `tsc --ignoreConfig` against three web entry points, so it saw only what those three transitively import and under a WEAKER configuration than tsconfig.json — no `noUncheckedIndexedAccess`, no `exactOptionalPropertyTypes`, `--types ''`. It never saw `src/server/` or a single file under `test/`. Demonstrated rather than argued: a planted `const DELIBERATE_TYPE_ERROR: number = 'not a number';` in src/server/session.ts gives `npm run typecheck` a TS2322 and `npm test` a clean `# fail 0`. `pretest` is `tsc --noEmit && node scripts/build-web.ts` now, and the same error exits 1 with the tests never running. This mattered THIS week rather than generally: v0.8.0 is steps 2-7 of the common board — display stream, credentials, persistence, Chromium supervisor — which is almost entirely src/server/, exactly the half the test command could not see. #103 — the plan had drifted from the code it is the source for. docs/plans/jitsi-common-board.md was written 2026-08-27, still said "No implementation has been performed", and is what steps 2-7 get built from. Step 1 shipped across four releases since, so every "current code finding" under it described a fault that is now fixed — a document reading as present tense and nine days stale sends the next reader to fix things twice. Measured: its PublicFrame sketch lists four properties never built (protocolVersion, config, scoring, deckCounts) and omits 28 that exist, and the shape is the real difference — the implementation is FLAT where the plan grouped things into objects, so a renderer written from the sketch would not compile. The plan now says so at the top and at step 1, names src/sim/view.ts and the redaction allow-list as the authority, keeps the original sketch for its reasoning, and calls out `protocolVersion` as unbuilt rather than dropping it quietly — step 2 is the reconnecting display stream and is the first thing that would want one. One step-1 item is STRUCK OFF rather than built: "add the Red Flag holder to the public player projection". The premise does not hold here. `decks.redFlags` is written once, in setup.ts, from `optionalRules.emergencyToolbox`, and never again — `redFlag.play` emits `phaseEnded` and does not spend it — so every player holds one or none does, decided before the deal. A per-player `redFlagHeld` would be one already-public option copied N times, while telling every reader of the common board that it varies by player and might change mid-game. Worse than the absence. Pinned by test so it is not re-raised from the plan. Four dead imports removed, all mine: `HAND_LIMIT` left unused in apply.ts, view.ts and web/game.ts when 0.7.9.6 consolidated the three copies of the §6.2 test, and `actingPlayer` in web/game.ts, dead since 0.7.9.5 made `currentActor` delegate. Finding them re-measured #46: `tsc --noUnusedLocals` now reports 40, up from 29 on 2026-08-30. That entry's "without the flag this list simply regrows" is a measurement rather than a forecast now. The other 36 and the flag stay open. 946 tests pass, up from 943. No behaviour changes: three new tests pin an invariant, and the rest is a build command, dead imports and a document. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Y5boPxP6JHRYMm8adXaF5R
This commit is contained in:
co-authored by
Claude Opus 5
parent
88a42ae9e2
commit
312e0301e0
@@ -19,6 +19,77 @@ page as `v0.1.0 · <sha> · <date>`, so what is deployed can always be identifie
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 0.7.9.8 — 2026-09-07
|
||||||
|
|
||||||
|
Housekeeping before v0.8.0 — the answer to "anything else that should be looked at first", which
|
||||||
|
turned up one real hole and one stale document.
|
||||||
|
|
||||||
|
### `npm test` did not typecheck, and passed green on a type error (#102)
|
||||||
|
|
||||||
|
`pretest` ran `scripts/build-web.ts`, which invokes `tsc --ignoreConfig` against three web entry
|
||||||
|
points. So it saw only what those three transitively import, under a **weaker** configuration than
|
||||||
|
`tsconfig.json` — no `noUncheckedIndexedAccess`, no `exactOptionalPropertyTypes`, `--types ''` — and
|
||||||
|
never saw `src/server/` or a single file under `test/`.
|
||||||
|
|
||||||
|
Demonstrated rather than argued. Planting `const DELIBERATE_TYPE_ERROR: number = 'not a number';` in
|
||||||
|
`src/server/session.ts`:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run typecheck → src/server/session.ts(441,7): error TS2322
|
||||||
|
npm test → # fail 0
|
||||||
|
```
|
||||||
|
|
||||||
|
`pretest` is `tsc --noEmit && node scripts/build-web.ts` now, and the same planted error exits 1 with
|
||||||
|
the tests never running.
|
||||||
|
|
||||||
|
**Why this week rather than generally.** v0.8.0 is steps 2-7 of the common board — a display stream,
|
||||||
|
credentials, persistence, a Chromium supervisor — which is almost entirely `src/server/`, exactly the
|
||||||
|
half the test command could not see.
|
||||||
|
|
||||||
|
### The common-board plan had drifted from the code it is the source for (#103)
|
||||||
|
|
||||||
|
`docs/plans/jitsi-common-board.md` was written on 2026-08-27, still said "No implementation has been
|
||||||
|
performed", and is what steps 2-7 will be built from. Step 1 has since shipped across four releases,
|
||||||
|
so every "current code finding" under it described a fault that is now fixed. A document that reads
|
||||||
|
as present tense and is nine days stale sends the next reader to fix things twice.
|
||||||
|
|
||||||
|
Measured: the plan's `PublicFrame` sketch lists four properties never built (`protocolVersion`,
|
||||||
|
`config`, `scoring`, `deckCounts`) and omits **28** that exist. The shape is the real difference —
|
||||||
|
the implementation is flat where the plan grouped things into `clock`, `config`, `scoring` and
|
||||||
|
`deckCounts` objects, so a renderer written from the sketch would not compile against the projection.
|
||||||
|
|
||||||
|
The plan now says all of this at the top and at step 1, names `src/sim/view.ts` and
|
||||||
|
`test/redaction.test.ts`'s allow-list as the authority, keeps the original sketch for its reasoning,
|
||||||
|
and lists what has been gained since. `protocolVersion` is called out as unbuilt rather than quietly
|
||||||
|
dropped — step 2 is the reconnecting display stream and is the first thing that would want one.
|
||||||
|
|
||||||
|
**And one step-1 item is struck off rather than built.** The plan asks for the Red Flag holder on the
|
||||||
|
public player projection, "public game state but currently absent from `Frame`". The premise does not
|
||||||
|
hold here: `decks.redFlags` is written once, in `setup.ts`, from `optionalRules.emergencyToolbox`,
|
||||||
|
and never again — `redFlag.play` emits `phaseEnded` and does not spend it — so every player holds one
|
||||||
|
or none does, decided before the deal. A per-player `redFlagHeld` would be one already-public option
|
||||||
|
copied N times, while telling every reader of the common board that it varies by player and might
|
||||||
|
change mid-game. That is worse than the absence. The invariant is pinned by test so the item is not
|
||||||
|
re-raised from the plan text.
|
||||||
|
|
||||||
|
### Four dead imports, and a measurement for #46
|
||||||
|
|
||||||
|
Removed: `HAND_LIMIT`, left unused in `apply.ts`, `view.ts` and `web/game.ts` when 0.7.9.6
|
||||||
|
consolidated the three copies of the §6.2 test into one, and `actingPlayer` in `web/game.ts`, dead
|
||||||
|
since 0.7.9.5 made `currentActor` delegate. All four were mine.
|
||||||
|
|
||||||
|
Finding them re-measured #46: **`tsc --noUnusedLocals` now finds 40, up from 29 on 2026-08-30.** That
|
||||||
|
entry's prediction — "without the flag this list simply regrows — it is regrowing now" — is a
|
||||||
|
measurement rather than a forecast. The remaining 36 and the flag itself are still open; ten of them
|
||||||
|
still wait on #48 settling what `sim/replay.ts` is for.
|
||||||
|
|
||||||
|
### Proof
|
||||||
|
|
||||||
|
946 tests pass, up from 943. Nothing in this release changes behaviour: the three new tests pin an
|
||||||
|
invariant and the rest is the build command, dead imports and a document.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 0.7.9.7 — 2026-09-07
|
## 0.7.9.7 — 2026-09-07
|
||||||
|
|
||||||
The last item off 0.7.9.6's sweep — the one parked as a maybe, which turned out to have a second half
|
The last item off 0.7.9.6's sweep — the one parked as a maybe, which turned out to have a second half
|
||||||
|
|||||||
@@ -130,7 +130,13 @@ specific paths below have not been exercised at a table. **More testing is plann
|
|||||||
|
|
||||||
**This is v0.8.0.** One shared, seatless display of the public game, usable on a TV or in OBS on its
|
**This is v0.8.0.** One shared, seatless display of the public game, usable on a TV or in OBS on its
|
||||||
own and publishable into the table's Jitsi meeting. The plan is `docs/plans/jitsi-common-board.md`,
|
own and publishable into the table's Jitsi meeting. The plan is `docs/plans/jitsi-common-board.md`,
|
||||||
seven steps, of which 1-4 are the useful release and 5-7 are the Jitsi publisher. Items that look
|
seven steps, of which 1-4 are the useful release and 5-7 are the Jitsi publisher.
|
||||||
|
|
||||||
|
**Step 1 is BUILT** — v0.7.9.2 through v0.7.9.5 (#91, #92, #95, #97), with one item struck off
|
||||||
|
rather than implemented (#103). **The plan was reconciled against the code in v0.7.9.8** and now says
|
||||||
|
which of its "current code findings" are history: it had drifted badly enough to send the next reader
|
||||||
|
fixing things twice. Steps 2-7 were never implemented and their findings have NOT been re-verified —
|
||||||
|
check each before building on it. See **Reference · #103**. Items that look
|
||||||
like screen polish live here because they need step 4's ordered presentation mechanism and nothing
|
like screen polish live here because they need step 4's ordered presentation mechanism and nothing
|
||||||
cheaper.
|
cheaper.
|
||||||
|
|
||||||
@@ -1521,6 +1527,14 @@ discarded wrongly.
|
|||||||
run it.** Swept 2026-08-30 and deliberately NOT fixed in the same pass, at Jesse's call — it is
|
run it.** Swept 2026-08-30 and deliberately NOT fixed in the same pass, at Jesse's call — it is
|
||||||
a wide, mechanical change and v0.7.9 had enough in it.
|
a wide, mechanical change and v0.7.9 had enough in it.
|
||||||
|
|
||||||
|
**RE-MEASURED 2026-09-07: 40, up from 29 in eight days.** The prediction below — "without the
|
||||||
|
flag this list simply regrows" — is now a measurement rather than a forecast. **Four of the new
|
||||||
|
ones were mine and are removed in v0.7.9.8**: `HAND_LIMIT` left unused in `apply.ts`, `view.ts`
|
||||||
|
and `web/game.ts` when the three copies of the §6.2 test were consolidated into one (#45), and
|
||||||
|
`actingPlayer` in `web/game.ts`, dead since `currentActor` began delegating (#96). Removing my
|
||||||
|
own leavings is not doing this item — the remaining 36 and the FLAG are still open, and the
|
||||||
|
`sim/replay.ts` ten still wait on #48.
|
||||||
|
|
||||||
**The recurrence is the point, not the 29.** `SIDE_GAP` and `CHIP_W` in `board-svg.ts` are both
|
**The recurrence is the point, not the 29.** `SIDE_GAP` and `CHIP_W` in `board-svg.ts` are both
|
||||||
Gitea#18 leftovers — constants for a wrapped layout that no longer exists. `SIDE_GAP` was
|
Gitea#18 leftovers — constants for a wrapped layout that no longer exists. `SIDE_GAP` was
|
||||||
removed by hand on 2026-08-30 only because someone happened to read the file while closing the
|
removed by hand on 2026-08-30 only because someone happened to read the file while closing the
|
||||||
@@ -1970,6 +1984,52 @@ the numbers stay so cross-references above and below still resolve.
|
|||||||
it existed report no device spent, which is exactly what they drew at the time, so every
|
it existed report no device spent, which is exactly what they drew at the time, so every
|
||||||
published replay is unchanged.
|
published replay is unchanged.
|
||||||
|
|
||||||
|
102. ~~**`npm test` did not typecheck, and passed green on a type error in the server.**~~ — done
|
||||||
|
2026-09-07 in v0.7.9.8. `pretest` ran `scripts/build-web.ts`, which invokes
|
||||||
|
`tsc --ignoreConfig` against three web entry points — so it saw only what those three
|
||||||
|
transitively import, under a WEAKER configuration than `tsconfig.json` (no
|
||||||
|
`noUncheckedIndexedAccess`, no `exactOptionalPropertyTypes`, `--types ''`). `src/server/` and
|
||||||
|
every file under `test/` were never checked by the test command at all.
|
||||||
|
|
||||||
|
Demonstrated rather than argued: `const DELIBERATE_TYPE_ERROR: number = 'not a number';` in
|
||||||
|
`src/server/session.ts` gives `npm run typecheck` a TS2322 and `npm test` a clean
|
||||||
|
`# fail 0`. `pretest` is `tsc --noEmit && node scripts/build-web.ts` now, and the same planted
|
||||||
|
error exits 1 with the tests never running.
|
||||||
|
|
||||||
|
**Why it mattered THIS week rather than generally.** v0.8.0 is steps 2-7 of the common board —
|
||||||
|
a display stream, credentials, persistence, and a Chromium supervisor — which is almost
|
||||||
|
entirely `src/server/` and is exactly the half the test command could not see. Found while
|
||||||
|
answering "anything else before 0.8.0", which is the only reason it was found at all: nothing
|
||||||
|
about a green suite would ever have said so.
|
||||||
|
|
||||||
|
103. ~~**The common-board plan had drifted from the code it is the source for.**~~ — done 2026-09-07
|
||||||
|
in v0.7.9.8. `docs/plans/jitsi-common-board.md` was written 2026-08-27, still said "No
|
||||||
|
implementation has been performed", and is what steps 2-7 will be built from. Step 1 has since
|
||||||
|
shipped across four releases, so every "current code finding" under it described a fault that
|
||||||
|
is now fixed — a document that reads as present tense and is nine days stale sends the next
|
||||||
|
reader to fix things twice.
|
||||||
|
|
||||||
|
Measured: the plan's `PublicFrame` sketch lists four properties that were never built
|
||||||
|
(`protocolVersion`, `config`, `scoring`, `deckCounts`) and omits **28** that exist. The shape
|
||||||
|
is the real difference — the implementation is FLAT where the plan grouped things into `clock`,
|
||||||
|
`config`, `scoring` and `deckCounts` objects. A renderer written from the sketch would not
|
||||||
|
compile against the projection.
|
||||||
|
|
||||||
|
The plan now says so at the top and at step 1, names `src/sim/view.ts` and
|
||||||
|
`test/redaction.test.ts`'s allow-list as the authority, keeps the original sketch for its
|
||||||
|
reasoning, and lists what has been gained since (`crewTrays`, `queued`, `heldAtLimits`,
|
||||||
|
`enhancementsSpent`). **`protocolVersion` is called out as unbuilt** rather than quietly
|
||||||
|
dropped: step 2 is the reconnecting display stream and is the first thing that would want one.
|
||||||
|
|
||||||
|
**One step-1 item is STRUCK OFF rather than deferred:** "add the Red Flag holder to the public
|
||||||
|
player projection." The premise does not hold here. `decks.redFlags` is written once, in
|
||||||
|
`setup.ts`, from `config.optionalRules.emergencyToolbox`, and never again — `redFlag.play`
|
||||||
|
emits `phaseEnded` and does not spend it — so every player holds one or none does, decided
|
||||||
|
before the deal. A per-player `redFlagHeld` would be `optionalRules.emergencyToolbox` copied N
|
||||||
|
times, already public, while telling every reader of the common board that it varies by player
|
||||||
|
and might change mid-game. That is worse than the absence. Pinned by test so it is not
|
||||||
|
re-raised from the plan text: if the rule ever becomes per-player, the test fails.
|
||||||
|
|
||||||
32. ~~**Tell the 0.4.9 playtesters their saves are dead, before they find out.**~~ — done
|
32. ~~**Tell the 0.4.9 playtesters their saves are dead, before they find out.**~~ — done
|
||||||
2026-09-07. `PLAYTEST-0.7.4.md` was written for exactly this and did its job; Jesse, 2026-09-07:
|
2026-09-07. `PLAYTEST-0.7.4.md` was written for exactly this and did its job; Jesse, 2026-09-07:
|
||||||
"a temporary document to help some of the playtesters out on making the big jump, but that is no
|
"a temporary document to help some of the playtesters out on making the big jump, but that is no
|
||||||
|
|||||||
@@ -1,6 +1,17 @@
|
|||||||
# Station Master Jitsi Common Board Implementation Plan
|
# Station Master Jitsi Common Board Implementation Plan
|
||||||
|
|
||||||
**Status:** Final planning document. No implementation has been performed.
|
**Status (2026-09-07):** **STEP 1 IS BUILT AND SHIPPED. Steps 2-7 are unimplemented.**
|
||||||
|
|
||||||
|
Step 1 landed across four releases rather than one — v0.7.9.2 (the two narration leaks), v0.7.9.4
|
||||||
|
(the projection helpers and the redaction net), v0.7.9.5 (the narration path), and v0.7.9.8 (this
|
||||||
|
reconciliation). One of its items is struck off rather than built; see § Public game projection.
|
||||||
|
|
||||||
|
**This document has drifted from the code and is no longer the authority on what exists.** It was
|
||||||
|
written on 2026-08-27 against the code of that date, and the "Current code findings" under each step
|
||||||
|
describe faults that were then real — several are now fixed, and reading them as present tense will
|
||||||
|
send you to fix things twice. Where a step is marked built, `src/sim/view.ts`, `src/server/`, and the
|
||||||
|
tests named in TODO.md are the authority. Steps 2-7 were never implemented and their findings have
|
||||||
|
NOT been re-verified against the current code; check each before building on it.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
@@ -44,6 +55,38 @@ The sibling repository had unrelated local modifications and untracked files. Th
|
|||||||
|
|
||||||
Introduce dedicated allow-listed types. Do not derive them with `Omit<Frame, ...>` because new private `Frame` fields could then leak automatically.
|
Introduce dedicated allow-listed types. Do not derive them with `Omit<Frame, ...>` because new private `Frame` fields could then leak automatically.
|
||||||
|
|
||||||
|
> **RECONCILED 2026-09-07 (v0.7.9.8).** Step 1 is BUILT, and what shipped is not shaped like the
|
||||||
|
> sketch below. This block was the design; `PublicFrame` in `src/sim/view.ts` is now the authority,
|
||||||
|
> and `test/redaction.test.ts`'s allow-list is the enumeration of it that fails when it changes.
|
||||||
|
> **Read those two, not this**, when building steps 2-7. The differences that matter:
|
||||||
|
>
|
||||||
|
> - **The shape is FLAT, not grouped.** There is no `clock`, `config`, `scoring` or `deckCounts`
|
||||||
|
> object. Their contents sit at the top level — `day`, `stage`, `clock` (a time string), `phase`,
|
||||||
|
> `phaseKey`, `actor`, `superintendent`, `deck`, `departments`, `departmentDepth`, `salvage`,
|
||||||
|
> `yards`, `mode`, `days`, `optionalRules`, `houseRules`, `minCombinedRevenue`,
|
||||||
|
> `maxCollisionsPerDay`, `maxCollisionsTotal`, `collisionsToday`, `collisionsTotal`, `status`,
|
||||||
|
> `outcome`, `extraDays`, `extensionVotes`, `official`, `tally`, `openingRolls`, `timetable`,
|
||||||
|
> `timetableWhat`, `trains`, `players`, `division`, `districts`.
|
||||||
|
> - **`protocolVersion` was NOT built** and exists nowhere in the repo. Step 2 is the reconnecting
|
||||||
|
> display stream, which is the first thing that would want one — decide there whether to add it,
|
||||||
|
> rather than assuming it is already on the wire.
|
||||||
|
> - **`redFlagHeld` is STRUCK OFF**, not deferred. See below.
|
||||||
|
> - **Fields gained since this was written** that the renderer should know about: `crewTrays` and
|
||||||
|
> `queued` (#98, the Crew Tray pool and the trains waiting for one), and on each district's cells
|
||||||
|
> `heldAtLimits` (#99, a train stopped on the Limit Track) and `enhancementsSpent` (#101, a
|
||||||
|
> dispatch device spent for the Day).
|
||||||
|
>
|
||||||
|
> **Why `redFlagHeld` is struck off rather than built.** The premise does not hold in this codebase.
|
||||||
|
> `decks.redFlags` is written in exactly one place — `setup.ts`, from
|
||||||
|
> `config.optionalRules.emergencyToolbox` — and never again; `redFlag.play` emits `phaseEnded` and
|
||||||
|
> does not spend it. So every player holds one or none does, decided before the deal. A per-player
|
||||||
|
> `redFlagHeld` would be `optionalRules.emergencyToolbox` copied N times, already public, while
|
||||||
|
> telling every reader of the common board that it varies by player and may change mid-game. The
|
||||||
|
> invariant is pinned by test (`test/display-gaps.test.ts`) so this does not get re-raised from the
|
||||||
|
> plan text: if the rule ever becomes per-player, that test fails.
|
||||||
|
|
||||||
|
The design as originally written, kept for the reasoning:
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
type PublicPlayerView = {
|
type PublicPlayerView = {
|
||||||
index: PlayerIndex;
|
index: PlayerIndex;
|
||||||
@@ -145,6 +188,24 @@ Expose sanitized state, last transition time, and a safe error summary through t
|
|||||||
|
|
||||||
## Step 1 — Secure public-state projection
|
## Step 1 — Secure public-state projection
|
||||||
|
|
||||||
|
> **BUILT — v0.7.9.2 through v0.7.9.5.** Everything in "Required changes" below shipped except the
|
||||||
|
> Red Flag holder, which is struck off (§ Public game projection). Mapping to the work items in
|
||||||
|
> TODO.md: the projection helpers and `currentActorOfState` are **#95**, the systematic redaction
|
||||||
|
> net and its allow-list are **#91**, the seed and blind-draw narration leaks are **#92**, and
|
||||||
|
> "stop passing the full game log into `frameFor()`" is **#97** — which also fixed a reconnect bug
|
||||||
|
> the duplicate had been masking, so read that entry before touching narration in step 2.
|
||||||
|
>
|
||||||
|
> **The findings below are as of 2026-08-27 and are now HISTORY, not a task list.** Two are worth
|
||||||
|
> carrying forward anyway: districts must be keyed by SEAT with ownership resolved through
|
||||||
|
> `playerAtSeat` (Employee Rotation), and the public view must be composed UPWARD from shared
|
||||||
|
> helpers, never by calling the player `snapshot()` once per seat. Both are load-bearing for step 3.
|
||||||
|
>
|
||||||
|
> One finding was struck off on measurement rather than fixed: it warns that a display reading
|
||||||
|
> `clock.currentActor` could highlight the wrong district during a decision. Across six seeds and
|
||||||
|
> 3,600 decision points that field and `actingPlayer` never disagreed. `currentActorOfState` exists
|
||||||
|
> anyway as the one place to ask — and **#96** later found a real instance of the same class in a
|
||||||
|
> state nobody had checked, the §3.3 vote, where the game is not `active` at all.
|
||||||
|
|
||||||
### Current code findings
|
### Current code findings
|
||||||
|
|
||||||
`src/sim/view.ts` currently builds `Frame` for one real viewer and defaults that viewer to player zero. It combines shared table state, one district, and viewer-private fields. Calling it for a spectator would silently expose player zero’s district and private information.
|
`src/sim/view.ts` currently builds `Frame` for one real viewer and defaults that viewer to player zero. It combines shared table state, one district, and viewer-private fields. Calling it for a spectator would silently expose player zero’s district and private information.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "station-master",
|
"name": "station-master",
|
||||||
"version": "0.7.9.7",
|
"version": "0.7.9.8",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Station Master — a railroad operations game",
|
"description": "Station Master — a railroad operations game",
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
"pretest": "node scripts/build-web.ts",
|
"pretest": "tsc --noEmit && node scripts/build-web.ts",
|
||||||
"test": "node --test test/*.test.ts test/**/*.test.ts",
|
"test": "node --test test/*.test.ts test/**/*.test.ts",
|
||||||
"build:web": "node scripts/build-web.ts",
|
"build:web": "node scripts/build-web.ts",
|
||||||
"serve:web": "node scripts/build-web.ts && npx --yes http-server dist -p 8080 -c-1",
|
"serve:web": "node scripts/build-web.ts && npx --yes http-server dist -p 8080 -c-1",
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
FREIGHT_PROFILES,
|
FREIGHT_PROFILES,
|
||||||
HAND_LIMIT,
|
|
||||||
LABORER_ACTIONS_PER_LOAD,
|
LABORER_ACTIONS_PER_LOAD,
|
||||||
MAX_CONSIST,
|
MAX_CONSIST,
|
||||||
REALIGNMENTS,
|
REALIGNMENTS,
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import {
|
|||||||
import {
|
import {
|
||||||
ACTION_CARDS,
|
ACTION_CARDS,
|
||||||
ENHANCEMENT_CARDS,
|
ENHANCEMENT_CARDS,
|
||||||
HAND_LIMIT,
|
|
||||||
MAINLINE_MODIFIER_CARDS,
|
MAINLINE_MODIFIER_CARDS,
|
||||||
MAINLINE_PROFILES,
|
MAINLINE_PROFILES,
|
||||||
MANEUVER_CARDS,
|
MANEUVER_CARDS,
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ import type { Intent } from '../engine/intents.ts';
|
|||||||
import { legalActions } from '../engine/legal.ts';
|
import { legalActions } from '../engine/legal.ts';
|
||||||
import { createGame } from '../engine/setup.ts';
|
import { createGame } from '../engine/setup.ts';
|
||||||
import type { CardId, GameConfig, GameState, PlayerIndex } from '../engine/state.ts';
|
import type { CardId, GameConfig, GameState, PlayerIndex } from '../engine/state.ts';
|
||||||
import { actingPlayer } from '../engine/state.ts';
|
|
||||||
import { overHandLimit as overHandLimitOf } from '../engine/state.ts';
|
import { overHandLimit as overHandLimitOf } from '../engine/state.ts';
|
||||||
import { playerAtSeat } from '../engine/state.ts';
|
import { playerAtSeat } from '../engine/state.ts';
|
||||||
import { cuesFor, narrate } from '../sim/narrate.ts';
|
import { cuesFor, narrate } from '../sim/narrate.ts';
|
||||||
@@ -51,7 +50,6 @@ import {
|
|||||||
DEFAULT_HOUSE_RULES,
|
DEFAULT_HOUSE_RULES,
|
||||||
DEFAULT_MAX_COLLISIONS_PER_DAY,
|
DEFAULT_MAX_COLLISIONS_PER_DAY,
|
||||||
DEFAULT_MAX_COLLISIONS_TOTAL,
|
DEFAULT_MAX_COLLISIONS_TOTAL,
|
||||||
HAND_LIMIT,
|
|
||||||
LEGACY_HOUSE_RULES,
|
LEGACY_HOUSE_RULES,
|
||||||
collectiveRevenueFloor,
|
collectiveRevenueFloor,
|
||||||
houseRules,
|
houseRules,
|
||||||
|
|||||||
@@ -388,3 +388,50 @@ describe('a dispatch device says whether it is still available today (#101)', ()
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* THE RED FLAG HOLDER IS NOT A PER-PLAYER FACT, so it does not belong on the public player view.
|
||||||
|
*
|
||||||
|
* `docs/plans/jitsi-common-board.md` step 1 asks for one: "Add the Red Flag holder to the public
|
||||||
|
* player projection. It is public game state but is currently absent from `Frame`", and its
|
||||||
|
* `PublicPlayerView` carries `redFlagHeld: boolean`. Every other step-1 item shipped across v0.7.9.2
|
||||||
|
* to v0.7.9.5; this one is STRUCK OFF instead, because the premise does not hold in this codebase.
|
||||||
|
*
|
||||||
|
* `decks.redFlags` is written in exactly one place — `setup.ts`, from
|
||||||
|
* `config.optionalRules.emergencyToolbox` — and never again. There is no `.set` anywhere else, and
|
||||||
|
* `redFlag.play` (the intent gated on holding one) emits a `phaseEnded` event and does not spend it.
|
||||||
|
* So every player holds one or none of them does, decided before the first card is dealt.
|
||||||
|
*
|
||||||
|
* A `redFlagHeld` on each player would therefore be `optionalRules.emergencyToolbox` copied N times
|
||||||
|
* — already on the public projection — while implying to every reader of the common board that it
|
||||||
|
* varies by player and might change during a game. That is worse than the absence.
|
||||||
|
*
|
||||||
|
* This test exists so the plan item is not re-raised from the plan text: if the rule ever DOES
|
||||||
|
* become per-player, this fails and the projection is the right place to look.
|
||||||
|
*/
|
||||||
|
describe('the Red Flag holding is the Emergency Toolbox option, not a per-player fact', () => {
|
||||||
|
const dealt = (emergencyToolbox: boolean): GameState =>
|
||||||
|
createGame({
|
||||||
|
id: 'g',
|
||||||
|
seed: 99,
|
||||||
|
config: { ...config, optionalRules: { ...config.optionalRules, emergencyToolbox } },
|
||||||
|
playerNames: ['Ann', 'Bob', 'Cy'],
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const toolbox of [true, false]) {
|
||||||
|
it(`gives every player the same answer with the toolbox ${toolbox ? 'on' : 'off'}`, () => {
|
||||||
|
const s = dealt(toolbox);
|
||||||
|
const held = s.players.map((p) => s.decks.redFlags.get(p.index) === true);
|
||||||
|
assert.deepEqual(held, [toolbox, toolbox, toolbox], 'the Red Flag has become a per-player fact');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
it('is already public, through the option it comes from', () => {
|
||||||
|
const pub = publicSnapshot(dealt(true));
|
||||||
|
assert.equal(
|
||||||
|
pub.optionalRules.emergencyToolbox,
|
||||||
|
true,
|
||||||
|
'a spectator cannot tell whether the hand limit is three or four',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user