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:
co-authored by
Claude Fable 5
parent
324853ce51
commit
df2913f366
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user