Difficulty without stupidity: apprentice, adept, archmage

Tiers degrade resources and repertoire, never judgment — the design
constraint was that no tier may ever look dumb. The APPRENTICE draws
one card a turn instead of two (a poorer wizard, not a worse one),
spends counters only on heavy hits (thrift, not blindness), and
carries a modest spellbook: damage, summons, stones, keys, and
treasure play, with no afflictions, amplifies, ambushes, guarding
tricks, or deja-vu. The ADEPT draws fully and knows everything except
ambushes and amplify. The ARCHMAGE is the full curriculum. Every card
any tier plays, it plays correctly.

The lobby workshop gains a tier picker beside the temperaments
(default adept); the tier persists with the seat, shows in roster and
scoresheet ("⚙ apprentice mystery"), and rides the drive loop.
Measured where it should matter: in berserker combat mirrors the
archmage beats the apprentice two to one, while pure treasure races
stay honest — the handicap lives in the card exchanges a human
actually feels, pinned deterministically in the tournament suite.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 17:59:11 -04:00
co-authored by Claude Fable 5
parent a6ede6ca1e
commit b9c784a10c
7 changed files with 115 additions and 36 deletions
+6 -2
View File
@@ -173,7 +173,7 @@ function roomInfo(room: Room) {
started: room.state !== null,
colors: Object.fromEntries(room.colorChoices),
bots: Object.fromEntries(
[...room.bots].map(([name, b]) => [name, b.secret ? "mystery" : b.style]),
[...room.bots].map(([name, b]) => [name, `${b.tier} ${b.secret ? "mystery" : b.style}`]),
),
};
}
@@ -331,7 +331,11 @@ wss.on("connection", (socket) => {
const room = session.roomId ? getRoom(session.roomId) : undefined;
if (!room || !session.playerId) return send(socket, { type: "error", message: "not in a room" });
if (session.playerId !== room.hostId) return send(socket, { type: "error", message: "only the host seats automatons" });
const result = addAutomaton(room, typeof msg.style === "string" ? msg.style : undefined);
const result = addAutomaton(
room,
typeof msg.style === "string" ? msg.style : undefined,
typeof msg.tier === "string" ? msg.tier : undefined,
);
if ("error" in result) return send(socket, { type: "error", message: result.error });
broadcastRoomState(room);
break;