The clockwork plays at a watchable pace

One command per beat — 1.5 seconds, the replay's 1x cadence — each
broadcast with its board state as it lands, starting a beat after
the human's own action settles. The pump is per-room, re-entrancy
guarded, and stops the moment the maze wants a human (or the game
ends). Watching an automaton stalk your treasure across the maze is
now a spectacle instead of a teleport.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 16:17:28 -04:00
co-authored by Claude Fable 5
parent ab52628d2f
commit 3842307bf0
3 changed files with 129 additions and 26 deletions
+18 -7
View File
@@ -37,7 +37,7 @@ import {
loadPersistedRooms,
addAutomaton,
addChat,
driveAutomatons,
driveOneAutomaton,
makeTransferCode,
redactFor,
roomCount,
@@ -184,15 +184,26 @@ function broadcast(room: Room, makeMessage: (playerId: PlayerId) => unknown): vo
}
}
/** After a human acts, the clockwork answers; every step is broadcast. */
/**
* The clockwork plays at a watchable pace: one command every beat, each
* broadcast as it lands, until the maze wants a human again.
*/
const BOT_STEP_MS = 1500;
const pumping = new Set<string>();
function runBots(room: Room): void {
const steps = driveAutomatons(room);
for (const step of steps) {
if (pumping.has(room.id)) return;
pumping.add(room.id);
const tick = () => {
const step = driveOneAutomaton(room);
if (!step) {
pumping.delete(room.id);
return;
}
broadcast(room, (playerId) => ({ type: "events", events: redactFor(step.events, playerId) }));
}
if (steps.length > 0) {
broadcast(room, (playerId) => ({ type: "state", view: viewForPlayer(room, playerId), seq: room.log.length }));
}
setTimeout(tick, BOT_STEP_MS);
};
setTimeout(tick, 800); // a beat after the human's own action settles
}
function broadcastRoomState(room: Room): void {