The archmage learns the lessons humans taught it

Eric beats the clockwork four games in five; these close the holes he
walks through. The treasure race: no number card is hoarded for a war
chest when no enemy stands within six squares, and never against the
delivery that wins the game outright. The kill: bestAttack now shops
every number and the amplify separately, preferring a killing blow at
minimal spend (with three points of soak margin, or an ABSORB undoes
it) over maximum splash, banking the big numbers for the next fight.
The last stand: a blow that would be lethal escalates past every
thrift threshold — any counter that can save the clockwork is cheap
at that price, ABSORB's three soaked points included when they are
exactly the margin. The race denial: TELEPORT OPPONENT and DROP
OBJECT now fire on any carrier one delivery from winning, not just
the thief of the bot's own gold. And the MASTER KEY goes on display
the turn it is drawn, opening every workable lock on the march for
free. A/B against the prior brain across 300 mirror games: parity
(the features aim at human play patterns mirrors rarely exercise);
three scenario tests pin the new instincts. Ships ungated — brains
choose commands, ledgers record them.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
Eric Wagoner
2026-08-25 22:30:03 -04:00
co-authored by Claude Fable 5
parent 324853ce51
commit df2913f366
2 changed files with 170 additions and 38 deletions
+81
View File
@@ -775,3 +775,84 @@ describe("the thief-chase holds one goal per turn", () => {
}
});
});
describe("the archmage's sharpened instincts", () => {
function botTurn(seed = 42) {
let { state } = createGame({ playerIds: ["foe", "bot"], seed, sets: ["basic", "expansion1"] });
while (state.turn.round < 2 || state.players[state.turn.activeIndex]!.id !== "bot") {
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
return state;
}
it("a MASTER KEY drawn goes straight on display", () => {
const state = botTurn();
const bot = state.players.find((p) => p.id === "bot")!;
bot.hand.push({ cardId: "master-key", instanceId: "MK" } as never);
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
expect(cmd).toEqual({ type: "cast", instanceId: "MK" });
});
it("a lethal small blow is countered, thrift be damned", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
while (state.turn.round < 2 || state.players[state.turn.activeIndex]!.id !== "foe") {
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
const foe = state.players.find((p) => p.id === "foe")!;
const bot = state.players.find((p) => p.id === "bot")!;
bot.position = { ...foe.position };
bot.life = 2;
bot.hand = [{ cardId: "full-shield", instanceId: "FS" } as never];
foe.hand.push({ cardId: "fireball", instanceId: "FB" } as never);
const r = applyCommand(state, "foe", {
type: "cast", instanceId: "FB", target: { kind: "player", playerId: "bot" },
});
if (!r.ok) throw new Error(r.error);
// Base fireball, 2 points: below every thrift threshold, but fatal at
// 2 life — the shield comes out.
const cmd = automatonCommand(viewFor(r.state, "bot"), "hunter", "archmage");
expect(cmd).toEqual({ type: "counteract", instanceId: "FS" });
});
it("no number is hoarded against the delivery that wins the game", () => {
let state = botTurn();
const bot = state.players.find((p) => p.id === "bot")!;
const foe = state.players.find((p) => p.id === "foe")!;
// One of the foe's treasures already rests at the bot's home; the bot
// carries the second, four squares out with three legs — only the
// number bridges it home this turn.
const gold = state.treasures.filter((t) => t.owner === "foe");
expect(gold.length).toBeGreaterThanOrEqual(2);
gold[0]!.position = { ...bot.home };
gold[0]!.carriedBy = null;
gold[1]!.carriedBy = "bot";
gold[1]!.position = null;
bot.carriedTreasureId = gold[1]!.id;
foe.position = { ...bot.home }; // a foe in sight: the war chest would hoard
bot.hand = [
{ cardId: "fireball", instanceId: "FB" } as never,
{ cardId: "number-3", instanceId: "N3" } as never,
];
// Stand the bot a straight, open four squares from home.
const home = bot.home;
const column = [0, 1, 2, 3, 4].map((d) => ({ x: home.x, y: home.y + d }));
if (!column.every((c) => viewFor(state, "bot").board.cells[cellKey(c)])) return;
bot.position = { ...column[4]! };
for (const c of column.slice(0, 4)) state.edgeOverrides[edgeKey(c, "S")] = "open";
for (let guard = 0; guard < 12; guard++) {
const view = viewFor(state, "bot");
const cmd = automatonCommand(view, "hunter", "archmage") ?? automatonFallback(view, "archmage");
if (cmd.type === "playNumberForMovement") return; // the chest opened for the win
if (cmd.type === "endTurn") break;
const r = applyCommand(state, "bot", cmd);
if (!r.ok) break;
state = r.state;
if (state.phase !== "playing") return; // delivered and won outright
}
throw new Error("the clockwork hoarded its number instead of winning");
});
});