The mystery machine keeps its mood to itself
"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 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
af75a024ff
commit
1aafb9e837
@@ -172,7 +172,9 @@ function roomInfo(room: Room) {
|
|||||||
hostId: room.hostId,
|
hostId: room.hostId,
|
||||||
started: room.state !== null,
|
started: room.state !== null,
|
||||||
colors: Object.fromEntries(room.colorChoices),
|
colors: Object.fromEntries(room.colorChoices),
|
||||||
bots: Object.fromEntries(room.bots),
|
bots: Object.fromEntries(
|
||||||
|
[...room.bots].map(([name, b]) => [name, b.secret ? "mystery" : b.style]),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -47,8 +47,8 @@ export interface Room {
|
|||||||
events: GameEvent[]; // full history (unredacted — redact per recipient)
|
events: GameEvent[]; // full history (unredacted — redact per recipient)
|
||||||
/** Table talk, persisted with the room (public to all seats). */
|
/** Table talk, persisted with the room (public to all seats). */
|
||||||
chat: { player: PlayerId; text: string; at: string }[];
|
chat: { player: PlayerId; text: string; at: string }[];
|
||||||
/** Seats the server itself plays, and each one's temperament. */
|
/** Seats the server itself plays: temperament, and whether it is told. */
|
||||||
bots: Map<PlayerId, AutomatonStyle>;
|
bots: Map<PlayerId, { style: AutomatonStyle; secret: boolean }>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const rooms = new Map<string, Room>();
|
const rooms = new Map<string, Room>();
|
||||||
@@ -247,12 +247,14 @@ export function addAutomaton(room: Room, styleWanted?: string): { name: PlayerId
|
|||||||
if (room.players.length >= 6) return { error: "room is full" };
|
if (room.players.length >= 6) return { error: "room is full" };
|
||||||
const name = AUTOMATON_NAMES.find((n) => !room.players.includes(n));
|
const name = AUTOMATON_NAMES.find((n) => !room.players.includes(n));
|
||||||
if (!name) return { error: "the workshop is empty" };
|
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)
|
? (styleWanted as AutomatonStyle)
|
||||||
: AUTOMATON_STYLES[randomInt(AUTOMATON_STYLES.length)]!;
|
: AUTOMATON_STYLES[randomInt(AUTOMATON_STYLES.length)]!;
|
||||||
|
const secret = !known; // the mystery machine keeps its mood to itself
|
||||||
room.players.push(name);
|
room.players.push(name);
|
||||||
room.bots.set(name, style);
|
room.bots.set(name, { style, secret });
|
||||||
appendLine(room.id, { kind: "join", name, bot: true, style });
|
appendLine(room.id, { kind: "join", name, bot: true, style, ...(secret ? { secret: true } : {}) });
|
||||||
return { name };
|
return { name };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +279,7 @@ export function driveOneAutomaton(room: Room): { seat: PlayerId; events: GameEve
|
|||||||
const seat = actingSeat(room);
|
const seat = actingSeat(room);
|
||||||
if (!seat || !room.bots.has(seat)) return null;
|
if (!seat || !room.bots.has(seat)) return null;
|
||||||
const view = viewFor(room.state!, seat);
|
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);
|
let r = runCommand(room, seat, cmd);
|
||||||
if ("error" in r) {
|
if ("error" in r) {
|
||||||
r = runCommand(room, seat, automatonFallback(view));
|
r = runCommand(room, seat, automatonFallback(view));
|
||||||
@@ -494,7 +496,10 @@ export function loadPersistedRooms(): void {
|
|||||||
if (line.kind === "join") {
|
if (line.kind === "join") {
|
||||||
if (line.bot) {
|
if (line.bot) {
|
||||||
room.players.push(line.name);
|
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 {
|
} else {
|
||||||
const joinHash = line.tokenHash ?? (line.token ? hashToken(line.token) : null);
|
const joinHash = line.tokenHash ?? (line.token ? hashToken(line.token) : null);
|
||||||
if (!joinHash) throw new Error("join line has no token");
|
if (!joinHash) throw new Error("join line has no token");
|
||||||
|
|||||||
@@ -28,6 +28,8 @@ export interface JoinLine {
|
|||||||
bot?: true;
|
bot?: true;
|
||||||
/** The automaton's temperament. */
|
/** The automaton's temperament. */
|
||||||
style?: string;
|
style?: string;
|
||||||
|
/** A mystery machine: the temperament is not revealed to the table. */
|
||||||
|
secret?: true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StartLine {
|
export interface StartLine {
|
||||||
|
|||||||
@@ -1055,7 +1055,7 @@
|
|||||||
<button class="stamp tiny" onclick={() => net.addBot("hunter")}>hunter</button>
|
<button class="stamp tiny" onclick={() => net.addBot("hunter")}>hunter</button>
|
||||||
<button class="stamp tiny" onclick={() => net.addBot("berserker")}>berserker</button>
|
<button class="stamp tiny" onclick={() => net.addBot("berserker")}>berserker</button>
|
||||||
<button class="stamp tiny" onclick={() => net.addBot("worrier")}>worrier</button>
|
<button class="stamp tiny" onclick={() => net.addBot("worrier")}>worrier</button>
|
||||||
<button class="stamp tiny" onclick={() => net.addBot()}>random</button>
|
<button class="stamp tiny" onclick={() => net.addBot()}>mystery</button>
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
<label class="check">
|
<label class="check">
|
||||||
|
|||||||
Reference in New Issue
Block a user