Pacts are honored; Swap Meet trades items, not card names (rev 26)

The pact fix: a clockwork no longer attacks a wizard it holds a BUDDY
pact on — Eric watched one sign the pact and punch him the next turn.
A pact is torn up for exactly one thing: a kill. And the post-attack
pact is signed only by a wizard who wants OUT of the fight (hauling
gold, bleeding, or with other enemies left) — a healthy duelist keeps
its options, and mutual-pact stalemates stay out of bot wars.

Swap Meet drops the 'name a card' prompt for a real trade picker:
click the other trader, choose one of your carried items, then claim
one of their displayed items. Rules rev 26 widens the engine's match
from object-typed cards to every movable object (daggers, rocks,
wands, stones); older games matched narrowly and replay so.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc
This commit is contained in:
Eric Wagoner
2026-08-17 22:14:57 -04:00
co-authored by Claude Fable 5
parent 06faea0c79
commit 00517e08b1
7 changed files with 140 additions and 14 deletions
+27
View File
@@ -439,6 +439,7 @@ describe("the fireball-then-buddy lockout", () => {
const bot = state.players.find((p) => p.id === "bot")!;
const prey = state.players.find((p) => p.id === "other")!;
prey.position = { ...bot.position };
bot.life = 8; // bleeding: the clockwork wants out of this fight
bot.hand = [
{ instanceId: "fireball#T", cardId: "fireball" },
{ instanceId: "buddy#T", cardId: "buddy" },
@@ -467,3 +468,29 @@ describe("the fireball-then-buddy lockout", () => {
)).toBe(true);
});
});
describe("a pact once signed is honored", () => {
it("will not attack the wizard it just buddied", () => {
let { state } = createGame({ playerIds: ["bot", "other"], seed: 42, sets: ["basic", "expansion1"], deckRev: 25 });
for (let guard = 0; guard < 10 && !(actingSeat(state) === "bot" && state.turn.round > 1); guard++) {
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 prey = state.players.find((p) => p.id === "other")!;
prey.position = { ...bot.position };
// The pact already stands; a fireball waits in hand as temptation.
state.sustained.push({
id: "fx-test", cardId: "buddy", casterId: "bot", targetId: "other",
turnsLeft: 1000, edge: undefined,
} as never);
bot.hand = [{ instanceId: "fireball#T", cardId: "fireball" }];
const cmd = automatonCommand(viewFor(state, "bot"), "hunter", "archmage");
// Anything but an attack on the pacted wizard: no cast at them, no punch.
expect(cmd?.type === "punch").toBe(false);
if (cmd?.type === "cast") {
expect((cmd as { target?: { playerId?: string } }).target?.playerId).not.toBe("other");
}
});
});
@@ -335,3 +335,33 @@ describe("ambushes (async interrupts)", () => {
expect(state.ambushes.length).toBe(0);
});
});
describe("swap meet trades carried items (rules rev 26)", () => {
function rig(deckRev: number) {
let { state } = createGame({ playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev });
state = toRound2(state);
const { attacker, defender } = faceOff(state);
const sm = giveCard(state, attacker, "swap-meet");
giveCard(state, attacker, "dagger", "D", 1);
giveCard(state, defender, "blaster-wand", "W", 0);
state = must(state, attacker, {
type: "cast", instanceId: sm.instanceId,
target: { kind: "player", playerId: defender },
params: { cardId: "dagger;blaster-wand" },
});
state = must(state, defender, { type: "pass" });
return { state, attacker, defender };
}
it("a dagger trades for a wand — both are carried items", () => {
const { state, attacker, defender } = rig(26);
expect(state.players.find((p) => p.id === attacker)!.hand.some((c) => c.cardId === "blaster-wand")).toBe(true);
expect(state.players.find((p) => p.id === defender)!.hand.some((c) => c.cardId === "dagger")).toBe(true);
});
it("older revisions matched only object-typed cards and replay so", () => {
const { state, attacker, defender } = rig(25);
expect(state.players.find((p) => p.id === attacker)!.hand.some((c) => c.cardId === "dagger")).toBe(true);
expect(state.players.find((p) => p.id === defender)!.hand.some((c) => c.cardId === "blaster-wand")).toBe(true);
});
});