From 1aafb9e837476393e2c879f568bfd932b116fef2 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Sun, 16 Aug 2026 16:36:21 -0400 Subject: [PATCH] The mystery machine keeps its mood to itself MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit "Random" read as random ACTIONS, and a revealed random pick is barely a mystery anyway. The fourth workshop button is now "mystery": the server rolls the temperament and keeps it — the join line records it for replay, the drive loop plays it, but the room tells the table only "mystery". Roster and scoresheet show ⚙ mystery; the machine's behavior is the only tell. Co-Authored-By: Claude Fable 5 --- packages/server/src/index.ts | 4 +++- packages/server/src/rooms.ts | 19 ++++++++++++------- packages/server/src/store.ts | 2 ++ packages/web/src/App.svelte | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index 45f6d10..0d47965 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -172,7 +172,9 @@ function roomInfo(room: Room) { hostId: room.hostId, started: room.state !== null, colors: Object.fromEntries(room.colorChoices), - bots: Object.fromEntries(room.bots), + bots: Object.fromEntries( + [...room.bots].map(([name, b]) => [name, b.secret ? "mystery" : b.style]), + ), }; } diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index f5dfecc..6d2819e 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -47,8 +47,8 @@ export interface Room { events: GameEvent[]; // full history (unredacted — redact per recipient) /** Table talk, persisted with the room (public to all seats). */ chat: { player: PlayerId; text: string; at: string }[]; - /** Seats the server itself plays, and each one's temperament. */ - bots: Map; + /** Seats the server itself plays: temperament, and whether it is told. */ + bots: Map; } const rooms = new Map(); @@ -247,12 +247,14 @@ export function addAutomaton(room: Room, styleWanted?: string): { name: PlayerId if (room.players.length >= 6) return { error: "room is full" }; const name = AUTOMATON_NAMES.find((n) => !room.players.includes(n)); if (!name) return { error: "the workshop is empty" }; - const style: AutomatonStyle = AUTOMATON_STYLES.includes(styleWanted as AutomatonStyle) + const known = AUTOMATON_STYLES.includes(styleWanted as AutomatonStyle); + const style: AutomatonStyle = known ? (styleWanted as AutomatonStyle) : AUTOMATON_STYLES[randomInt(AUTOMATON_STYLES.length)]!; + const secret = !known; // the mystery machine keeps its mood to itself room.players.push(name); - room.bots.set(name, style); - appendLine(room.id, { kind: "join", name, bot: true, style }); + room.bots.set(name, { style, secret }); + appendLine(room.id, { kind: "join", name, bot: true, style, ...(secret ? { secret: true } : {}) }); return { name }; } @@ -277,7 +279,7 @@ export function driveOneAutomaton(room: Room): { seat: PlayerId; events: GameEve const seat = actingSeat(room); if (!seat || !room.bots.has(seat)) return null; const view = viewFor(room.state!, seat); - const cmd = automatonCommand(view, room.bots.get(seat)) ?? automatonFallback(view); + const cmd = automatonCommand(view, room.bots.get(seat)?.style) ?? automatonFallback(view); let r = runCommand(room, seat, cmd); if ("error" in r) { r = runCommand(room, seat, automatonFallback(view)); @@ -494,7 +496,10 @@ export function loadPersistedRooms(): void { if (line.kind === "join") { if (line.bot) { room.players.push(line.name); - room.bots.set(line.name, (line.style as AutomatonStyle) ?? "hunter"); + room.bots.set(line.name, { + style: (line.style as AutomatonStyle) ?? "hunter", + secret: line.secret === true, + }); } else { const joinHash = line.tokenHash ?? (line.token ? hashToken(line.token) : null); if (!joinHash) throw new Error("join line has no token"); diff --git a/packages/server/src/store.ts b/packages/server/src/store.ts index f63d107..6a3f71d 100644 --- a/packages/server/src/store.ts +++ b/packages/server/src/store.ts @@ -28,6 +28,8 @@ export interface JoinLine { bot?: true; /** The automaton's temperament. */ style?: string; + /** A mystery machine: the temperament is not revealed to the table. */ + secret?: true; } export interface StartLine { diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 49a7739..3018e63 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -1055,7 +1055,7 @@ - + {/if}