The clockwork wades, shelters, and honors pacts it can actually break

Game 6XDY's prisoners expose a nest of turn-burners. The pathfinder
treated pits, ooze, bushes, and slime as walls in transit, so bots stood
"trapped" in pockets any human would wade out of: the march now falls
back to a teeth-gritted hazard route when no clean road exists. Bots no
longer aim attacks into (or out of) a thornbush's shelter the engine
refuses; WIZARDBLADE learns it is a blade (same square only); a cursed
IDIOT skips every rung the curse would refuse instead of burning turns
on them; BUDDY's orientation is righted — whoever pacted YOU is
untouchable, your own pacts are merely precious — and a thief standing
underfoot no longer becomes a null destination that freezes two laden
wizards on the same square forever.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-21 17:53:26 -04:00
co-authored by Claude Fable 5
parent 76207c6264
commit 39ddbb452d
2 changed files with 318 additions and 212 deletions
+75
View File
@@ -283,6 +283,81 @@ describe("the clockwork honors IDIOT's march", () => {
});
});
describe("the clockwork wades hazards rather than surrender", () => {
it("takes the slime road when no clean path to anything exists", () => {
let { state } = createGame({ playerIds: ["bot", "foe"], seed: 7, sets: ["basic", "expansion1"] });
while (actingSeat(state) !== "bot") {
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
const bot = state.players.find((p) => p.id === "bot")!;
bot.hand = [];
// Wall the bot into its square except one side; slime that one exit.
const sides = ["N", "S", "E", "W"] as const;
const board = state.board;
const open = sides.filter((s) => {
const n = { x: bot.position.x + (s === "E" ? 1 : s === "W" ? -1 : 0),
y: bot.position.y + (s === "S" ? 1 : s === "N" ? -1 : 0) };
return board.cells[cellKey(n)] !== undefined;
});
const exit = open[0]!;
for (const s2 of sides) {
if (s2 !== exit) state.edgeOverrides[edgeKey(bot.position, s2)] = "wall";
else state.edgeOverrides[edgeKey(bot.position, s2)] = "open";
}
const beyond = { x: bot.position.x + (exit === "E" ? 1 : exit === "W" ? -1 : 0),
y: bot.position.y + (exit === "S" ? 1 : exit === "N" ? -1 : 0) };
state.squareContents[cellKey(beyond)] = { kind: "slime", damage: 0, createdBy: "foe" };
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
expect(cmd).toEqual({ type: "move", direction: exit });
expect(applyCommand(state, "bot", cmd!).ok).toBe(true);
});
});
describe("the clockwork respects the bush's shelter", () => {
function faceOffWithFireball() {
let { state } = createGame({ playerIds: ["bot", "foe"], seed: 7, sets: ["basic", "expansion1"] });
for (let i = 0; i < 2; i++) {
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
while (actingSeat(state) !== "bot") {
const r = applyCommand(state, actingSeat(state), { type: "endTurn", draw: 0 });
if (!r.ok) throw new Error(r.error);
state = r.state;
}
const bot = state.players.find((p) => p.id === "bot")!;
const foe = state.players.find((p) => p.id === "foe")!;
foe.position = { ...bot.position };
bot.hand = [{ instanceId: "fireball#T", cardId: "fireball" }];
return { state, bot, foe };
}
it("never aims at a wizard sheltered in a thornbush", () => {
const { state, foe } = faceOffWithFireball();
state.squareContents[cellKey(foe.position)] = { kind: "thornbush", damage: 0, createdBy: "foe" };
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage")
?? automatonFallback(viewFor(state, "bot"), "archmage");
// Whatever it picks, the engine must accept it — and it must not be
// the refused fireball that would burn the whole turn.
expect(cmd).not.toMatchObject({ type: "cast", instanceId: "fireball#T" });
expect(applyCommand(state, "bot", cmd).ok).toBe(true);
});
it("never attacks out of its own bush", () => {
const { state, bot, foe } = faceOffWithFireball();
foe.position = { x: bot.position.x, y: bot.position.y };
state.squareContents[cellKey(bot.position)] = { kind: "thornbush", damage: 0, createdBy: "foe" };
foe.position = { ...bot.position };
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage")
?? automatonFallback(viewFor(state, "bot"), "archmage");
expect(cmd).not.toMatchObject({ type: "cast", instanceId: "fireball#T" });
expect(applyCommand(state, "bot", cmd).ok).toBe(true);
});
});
describe("a clogged hand gets shed, not hoarded", () => {
it("the bot discards dead weight so the end-of-turn draw has room", () => {
let { state } = createGame({ playerIds: ["bot", "other"], seed: 42, sets: ["basic"] });