The clockwork learns numbers, summons, creatures — and temperament

The automaton now plays number cards where they matter: attached to
value-scaled attacks (waterbolt, powerthrust) choosing the biggest
for damage, and played for movement when the goal is just out of
stride. It raises its monsters when no wizard is in spell range and
commands them every turn — creatures march at enemies by the same
hazard-shy BFS and maul whoever shares their square. Targets are
picked by lowest life.

And it has moods. The HUNTER plays the classic game: gold first,
violence when convenient. The BERSERKER hunts wizards over treasure
and finishes games by last-wizard-standing. The WORRIER paths around
enemies, counters at a lower threshold, shields against chaos, and
never brawls. Hosts pick the temperament when seating one (or the
workshop assigns at random); it persists with the seat, shows in the
roster, and the tournament harness proves every pairing finishes.
Across ten seeds of berserker versus hunter: six treasure wins to
four kills — the moods play genuinely different games.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 16:29:36 -04:00
co-authored by Claude Fable 5
parent 01101ca24a
commit 5a407a1e4c
7 changed files with 223 additions and 75 deletions
+14 -2
View File
@@ -1028,7 +1028,7 @@
{:else}
<span class="dot" style:background="#b3a687"></span>
{/if}
{p}{p === net.hostId ? " — host" : ""}{net.roomBots.includes(p) ? "" : chosen === undefined ? " — choosing…" : ""}
{p}{p === net.hostId ? " — host" : ""}{net.roomBots[p] ? ` ${net.roomBots[p]}` : chosen === undefined ? " — choosing…" : ""}
</li>
{/each}
</ul>
@@ -1050,7 +1050,12 @@
</div>
{#if net.you === net.hostId}
{#if net.players.length < 6}
<button class="stamp tiny" onclick={() => net.addBot()}>⚙ seat an automaton</button>
<span class="bot-row">
⚙ seat an automaton:
<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("worrier")}>worrier</button>
</span>
{/if}
<label class="check">
<input type="checkbox" bind:checked={withExpansion} />
@@ -1995,6 +2000,13 @@
.big-peek :global(.card:hover) { transform: scale(2.1); }
.big-peek { display: flex; flex-direction: column; align-items: center; }
.big-peek-note { margin-top: 6.8rem; max-width: 15rem; font-size: 0.8rem; }
.bot-row {
display: inline-flex;
align-items: center;
gap: 0.35rem;
font-size: 0.85rem;
color: #6b5a41;
}
.discard-link {
background: none;
border: none;
+4 -4
View File
@@ -225,7 +225,7 @@ class Net {
started = $state(false);
/** Lobby standee choices, by player name. */
roomColors = $state<Record<string, number>>({});
roomBots = $state<string[]>([]);
roomBots = $state<Record<string, string>>({});
you = $state<string | null>(null);
view = $state<GameView | null>(null);
log = $state<string[]>([]);
@@ -296,7 +296,7 @@ class Net {
case "room":
this.roomId = msg.roomId;
this.roomColors = msg.colors ?? {};
this.roomBots = msg.bots ?? [];
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));
@@ -428,8 +428,8 @@ class Net {
}
/** Ask the server how all our games are doing. */
addBot(): void {
this.send({ type: "addBot" });
addBot(style?: string): void {
this.send({ type: "addBot", ...(style ? { style } : {}) });
}
rollTableDie(): void {