v0.7.9.2 — two things the table could hear that only one seat should
Both leaks were found while planning the common board (Gitea#20 step 1), and both are live multiplayer bugs with or without that display, so they are fixed now rather than with 0.8.0. `game.log` is one shared list and `linesSince(seat)` slices it with no per-seat filter, so every line reaches every player. It carried the SEED in the opening line of each multiplayer game — the whole future of the deal — and the NAME OF A CARD DRAWN BLIND from the face-down Home Office deck. Solitaire deliberately keeps both: a one-seat table has nobody to leak to, the seed is what a bug report quotes, and a player's own history naming their own draw is the record. A Department slot is face up and stays named. The drawer still learns their card through `justDrawn`, which already goes to that seat alone. Neither was found by a test. Every test in `redaction.test.ts` passes an empty log, so the whole of narration has sat outside the redaction net since the net was built. Both now have tests there; TODO #91 carries what is still owed and supersedes #78, which described a gap that had already been closed and never mentioned this one. `docs/rules/` had no current description of the game, and `content.ts` named `card-reference.md` as the file that carries what the cards say — a file whose own banner says not to use its numbers, describing the v0.4.5 deck where 3/4 is a Mail-Express with three coaches. Every file in that directory is a deliberate historical record, so none of them is rewritten. `as-built.md` is new and GENERATED from the same catalogues the engine instantiates from, with a test that re-runs the generator and fails when the checked-in file disagrees. A hand-written replacement would have drifted the same way, for the same reason. TODO.md: #32 closed — the playtest migration note did its job and the jump is made; the durable fact it carried is kept. #78 retired in favour of #91. The "play it at a table" section now records that 0.7.4-0.7.9 were test-run without change requests, and that more testing comes at the end of the 0.7.9 series. 897 tests pass, up from 891. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3Qk7uresKCHksdZajXCLg
This commit is contained in:
co-authored by
Claude Opus 5
parent
7ade60e21f
commit
819996faa2
+34
-2
@@ -333,7 +333,21 @@ export function newMultiplayerGame(seed: number, config: GameConfig, playerNames
|
||||
const state = createGame({ id: `mp-${seed}`, seed, config, playerNames });
|
||||
const game: Game = { state, seed, history: [], log: [], mustPlayCard: false, cues: [], scheduled: null, justDrawn: null, announced: null };
|
||||
game.log.push({ text: 'Game Begins', tone: 'start' });
|
||||
game.log.push({ text: `${config.mode} · ${playerNames.length} players · seed ${seed}`, tone: 'quiet' });
|
||||
/**
|
||||
* NO SEED AT A TABLE WITH MORE THAN ONE SEAT (Gitea#20 step 1).
|
||||
*
|
||||
* `game.log` is one shared list and `linesSince(seat)` (`server/session.ts`) slices it with no
|
||||
* per-seat filter, so every line here reaches every player. Announcing the seed therefore handed
|
||||
* each of them the whole future of the deal — every card order, every die — in the opening line
|
||||
* of the game. Found while planning the public common board; it is a multiplayer leak with or
|
||||
* without that display, which is why it is fixed here rather than waiting for it.
|
||||
*
|
||||
* `newGame` still records it, deliberately: a solitaire table has nobody to leak to, and the seed
|
||||
* in the log is what a bug report quotes. The rule is "do not tell the OTHER seats", not "write
|
||||
* less down". The seed remains in `game.seed`, in every save (`session.ts` persistence) and in the
|
||||
* lobby record, so nothing administrative or replayable loses it.
|
||||
*/
|
||||
game.log.push({ text: `${config.mode} · ${playerNames.length} players`, tone: 'quiet' });
|
||||
drain(game);
|
||||
return game;
|
||||
}
|
||||
@@ -1149,10 +1163,28 @@ function record(game: Game, events: GameEvent[], actor: PlayerIndex | null = nul
|
||||
cardName: (id) => cardName(game.state, id),
|
||||
trainName: (id) => trainName(game.state, id),
|
||||
});
|
||||
/**
|
||||
* A BLIND DRAW IS PUBLIC; WHICH CARD CAME UP IS NOT (Gitea#20 step 1).
|
||||
*
|
||||
* Everybody at the table sees a hand go to the Home Office deck, so the draw itself belongs in
|
||||
* the shared log. The card's NAME does not: the deck is face down, and this log goes to every
|
||||
* seat unfiltered, so naming it told three opponents exactly what the fourth was holding.
|
||||
*
|
||||
* A DEPARTMENT SLOT IS NOT THE SAME and stays named. Those piles are face up — a discard goes
|
||||
* onto one precisely so a rival can take it — so the card was public before it was drawn, and
|
||||
* hiding it would lose real information for no gain.
|
||||
*
|
||||
* The drawing seat still learns what it got. `justDrawn` below is the owner-only channel and
|
||||
* `session.ts` sends it to that seat alone, so this costs the drawer nothing. Solitaire keeps
|
||||
* the name for the same reason it keeps the seed: a one-seat table has nobody to leak to, and
|
||||
* a solo player's history naming their own draw is the record rather than a leak.
|
||||
*/
|
||||
const blindDraw = e.type === 'cardDrawn' && e.source === 'homeOffice' && game.state.players.length > 1;
|
||||
const said = blindDraw ? 'Drew a card from the Home Office deck' : n.text;
|
||||
// "Chose to DRAW a card" does not say WHO, which is unreadable the moment there is more than
|
||||
// one seat. Only events the player caused are attributed; the Division running itself is not.
|
||||
const mine = who !== null && 'player' in e;
|
||||
const text = mine ? `Player ${who} ${uncapitalise(n.text)}` : n.text;
|
||||
const text = mine ? `Player ${who} ${uncapitalise(said)}` : said;
|
||||
game.log.push({ text, tone: mine ? 'act' : n.tone });
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user