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:
Jesse.Markowitz
2026-09-08 03:41:39 -04:00
co-authored by Claude Opus 5
parent 88a42ae9e2
commit 312e0301e0
8 changed files with 243 additions and 8 deletions
+71
View File
@@ -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
+61 -1
View File
@@ -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
+62 -1
View File
@@ -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
View File
@@ -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",
-1
View File
@@ -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,
-1
View File
@@ -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,
-2
View File
@@ -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,
+47
View File
@@ -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',
);
});
});