The automatons awaken (branch only — not for the public droplet yet)

Phase 4 begins. The automaton is a pure function in the engine —
automatonCommand(view) — playing from its own redacted GameView, the
same information a human seat receives: hidden hands stay hidden from
the clockwork. It ranks simple damage spells, counters what hurts
(full shield at 3+, reflection at 4+, blunt at 2+), discards its
worst cards by a value order, BFS-pathfinds to enemy treasures and
home again, refuses to path through hazards, brawls when there is
nothing to steal, and always has a safe fallback; the server's drive
loop steps any bot-held seat through the same runCommand path as
humans, so bot commands log, persist, replay, and broadcast like
anyone's.

Hosts seat them pre-start with "⚙ seat an automaton" (Automaton,
Automaton II, ... V); bot seats persist as tokenless join lines and
restore on boot. Proven three ways: bot-vs-bot engine games conclude
across seeds in 8-14 rounds (~100 commands — human-scale), a full
four-automaton table finishes, and a live websocket game of human
vs. automaton ended with the clockwork carrying two treasures home
through a do-nothing opponent.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 16:11:54 -04:00
co-authored by Claude Fable 5
parent 5802e1df05
commit ab52628d2f
9 changed files with 559 additions and 5 deletions
+4 -1
View File
@@ -1025,7 +1025,7 @@
{:else}
<span class="dot" style:background="#b3a687"></span>
{/if}
{p}{p === net.hostId ? " — host" : ""}{chosen === undefined ? " — choosing…" : ""}
{p}{p === net.hostId ? " — host" : ""}{net.roomBots.includes(p) ? " ⚙" : chosen === undefined ? " — choosing…" : ""}
</li>
{/each}
</ul>
@@ -1046,6 +1046,9 @@
{/each}
</div>
{#if net.you === net.hostId}
{#if net.players.length < 6}
<button class="stamp tiny" onclick={() => net.addBot()}>⚙ seat an automaton</button>
{/if}
<label class="check">
<input type="checkbox" bind:checked={withExpansion} />
Include Expansion Set #1 — monsters &amp; wands
+6
View File
@@ -225,6 +225,7 @@ class Net {
started = $state(false);
/** Lobby standee choices, by player name. */
roomColors = $state<Record<string, number>>({});
roomBots = $state<string[]>([]);
you = $state<string | null>(null);
view = $state<GameView | null>(null);
log = $state<string[]>([]);
@@ -295,6 +296,7 @@ class Net {
case "room":
this.roomId = msg.roomId;
this.roomColors = msg.colors ?? {};
this.roomBots = msg.bots ?? [];
if (this.you && this.token) {
const seat: Seat = { name: this.you, roomId: msg.roomId, token: this.token };
localStorage.setItem(SEAT_KEY, JSON.stringify(seat));
@@ -426,6 +428,10 @@ class Net {
}
/** Ask the server how all our games are doing. */
addBot(): void {
this.send({ type: "addBot" });
}
rollTableDie(): void {
this.send({ type: "rollDie" });
}