The clockwork learns three lessons from losing to Kestrel

Game six's table play, distilled into rungs. LOCK IN PLACE now roots
an enemy carrier closing on their home — the tempo theft that opened
game five, turned against the table. A pressed carrier of ANY
temperament casts MIST-BODY with its biggest number: an untouchable
delivery beats fear's radius, as the winning heist demonstrated. And
the pathfinder learns the thornbush is a jail, not a wade — entering
ends the turn, eats the next, and stings — so only a wizard with no
road at all dives in ("few will dive into one unless they are
desperate"). Also acquits Automaton II of game-six dithering: replay
showed its loop-de-loop was the true 13-step shortest path through a
walled quadrant.

Brains ship ungated; all 28 ledgers replay clean.

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-27 21:53:47 -04:00
co-authored by Claude Fable 5
parent 3c70b9aedc
commit cbd4d80dea
2 changed files with 123 additions and 3 deletions
+83
View File
@@ -979,3 +979,86 @@ describe("the denial planner offers only castable blocks", () => {
}
});
});
describe("game-six lessons from the table", () => {
function toBot(state: GameState): GameState {
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("roots a carrier closing on home with LOCK IN PLACE", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
state = toBot(state);
const bot = state.players.find((p) => p.id === "bot")!;
const foe = state.players.find((p) => p.id === "foe")!;
// The foe stands beside the bot, laden, a short walk from banking.
foe.position = { x: bot.position.x, y: bot.position.y };
const gold = state.treasures.find((t) => t.owner === "bot" && t.position)!;
gold.position = null;
gold.carriedBy = "foe";
foe.carriedTreasureId = gold.id;
foe.home = { ...bot.position };
bot.hand = [{ cardId: "lock-in-place", instanceId: "LK" }];
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
expect(cmd).toMatchObject({ type: "cast", instanceId: "LK", target: { kind: "player", playerId: "foe" } });
expect(applyCommand(state, "bot", cmd!).ok).toBe(true);
});
it("a pressed carrier of any temperament turns to mist", () => {
let { state } = createGame({ playerIds: ["foe", "bot"], seed: 42, sets: ["basic", "expansion1"] });
state = toBot(state);
const bot = state.players.find((p) => p.id === "bot")!;
const foe = state.players.find((p) => p.id === "foe")!;
// Bot hauls the foe's gold mid-journey with the foe breathing down its neck.
const gold = state.treasures.find((t) => t.owner === "foe" && t.position)!;
gold.position = null;
gold.carriedBy = "bot";
bot.carriedTreasureId = gold.id;
bot.position = { ...foe.home };
foe.position = { ...bot.position };
bot.hand = [
{ cardId: "mist-body", instanceId: "MB" },
{ cardId: "number-3", instanceId: "N3" },
{ cardId: "number-5", instanceId: "N5" },
];
const cmd = automatonCommand(viewFor(state, "bot"), "berserker", "archmage");
expect(cmd).toMatchObject({ type: "cast", instanceId: "MB", numberInstanceIds: ["N5"] });
const r = applyCommand(state, "bot", cmd!);
if (!r.ok) throw new Error(r.error);
expect(r.state.sustained.some((s) => s.cardId === "mist-body" && s.targetId === "bot")).toBe(true);
});
it("detours around a thornbush jail rather than diving in", () => {
let { state } = createGame({ playerIds: ["bot", "foe"], seed: 7, sets: ["basic", "expansion1"] });
state = toBot(state);
const bot = state.players.find((p) => p.id === "bot")!;
bot.hand = [];
// Two exits from the bot's square: the short road east holds a
// thornbush, the long road stays clean. A jail is not a shortcut.
const sides = ["N", "S", "E", "W"] as const;
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 state.board.cells[cellKey(n)] !== undefined;
});
expect(open.length).toBeGreaterThanOrEqual(2);
const bushSide = open[0]!;
const freeSide = open[1]!;
for (const s2 of sides) {
state.edgeOverrides[edgeKey(bot.position, s2)] =
s2 === bushSide || s2 === freeSide ? "open" : "wall";
}
const bushCell = { x: bot.position.x + (bushSide === "E" ? 1 : bushSide === "W" ? -1 : 0),
y: bot.position.y + (bushSide === "S" ? 1 : bushSide === "N" ? -1 : 0) };
state.squareContents[cellKey(bushCell)] = { kind: "thornbush", damage: 0, createdBy: "foe" };
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
// Whatever goal it picks, the first stride must not be into the bush.
expect(cmd).toMatchObject({ type: "move" });
expect((cmd as { direction: string }).direction).not.toBe(bushSide);
expect(applyCommand(state, "bot", cmd!).ok).toBe(true);
});
});