diff --git a/packages/engine/src/automaton.ts b/packages/engine/src/automaton.ts index 1dd3a90..bdb271b 100644 --- a/packages/engine/src/automaton.ts +++ b/packages/engine/src/automaton.ts @@ -1396,7 +1396,15 @@ export function automatonCommand( (c.cardId === "blaster-wand" && view.wandCharges[c.instanceId] == null); }); const spendable = wantsNumber ? numbers.slice(0, -1) : numbers; - if (!view.turn.numberPlayedForMovement && path.distance > movesLeft) { + // A treasure within free walking ends this turn at the grab (the + // gold-underfoot rung outranks everything) — a number played first + // would evaporate. Even a charging berserker stoops for loot. + const goldWithinLegs = !self.carriedTreasureId && + (() => { + const dHere = distancesFrom(view, [self.position], canUnlock); + return [...treasureGoals(view)].some((k) => (dHere.get(k) ?? Infinity) <= movesLeft); + })(); + if (!view.turn.numberPlayedForMovement && path.distance > movesLeft && !goldWithinLegs) { const helper = spendable.find( (n) => movesLeft + (cardDef(n.cardId).value ?? 0) >= path.distance, ) ?? (path.distance > movesLeft + 2 ? spendable[spendable.length - 1] : undefined); diff --git a/packages/engine/test/automaton.test.ts b/packages/engine/test/automaton.test.ts index 8f45c9d..60c6b91 100644 --- a/packages/engine/test/automaton.test.ts +++ b/packages/engine/test/automaton.test.ts @@ -494,3 +494,29 @@ describe("a pact once signed is honored", () => { } }); }); + +describe("no number is wasted on a turn that ends at a grab", () => { + it("gold two free steps away: the berserker charges without spending its 5", () => { + let { state } = createGame({ playerIds: ["bot", "other"], seed: 42, sets: ["basic", "expansion1"], deckRev: 29 }); + 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")!; + // Enemy far across the maze; their treasure lies one step from the bot. + prey.position = { x: 0, y: 0 }; + bot.position = { x: 5, y: 5 }; + const t = state.treasures.find((t) => t.owner === "other")!; + t.position = { x: 5, y: 6 }; + t.carriedBy = null; + state.edgeOverrides[edgeKey({ x: 5, y: 5 }, "S")] = "open"; + bot.hand = [ + { instanceId: "number-5#T", cardId: "number-5" }, + { instanceId: "number-2#T", cardId: "number-2" }, + ]; + const cmd = automatonCommand(viewFor(state, "bot"), "berserker", "archmage"); + expect(cmd?.type).not.toBe("playNumberForMovement"); + }); +});