Credibility pass: session residue swept from the rev-10 run

War-story comments become timeless constraints (carrier rule, boobytrap
tell, remove-curse prescription); the rev changelog reunites under
GameConfig.deckRev; three hand-copied cardless stack literals fold into
openCardlessStack; the visionstone one-edge loop is shared via
throughOneEdge with the edge-target hairpin exclusion stated; drain()
moves to test helpers and replaces four inline copies; the butt-head
sweep test loses its dead scaffolding and its expect(refused || true)
tautology and now asserts both branches; a process-named suite is
renamed for the behavior it pins; dead .bezel-spacer CSS and the fx
effect's fossilized cleanup story go. No behavior changes: 304 engine
tests pass, 36/36 ledgers replay, web typecheck 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-29 19:14:03 -04:00
co-authored by Claude Fable 5
parent f2af1e26f8
commit e93bcc15a2
11 changed files with 107 additions and 139 deletions
+19 -37
View File
@@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest";
import { applyCommand, activePlayer, boardView, createGame, gameLos, sustainedOn } from "../src/game";
import { cellKey, edgeKey, stepTarget } from "../src/board";
import type { CardInstance } from "../src/cards";
import { newExpansionGame as newGame, must, giveCard, toRound2, faceOff, castAt } from "./helpers";
import { newExpansionGame as newGame, must, drain, giveCard, toRound2, faceOff, castAt } from "./helpers";
describe("expansion combat cards", () => {
it("power attack burns life for extra damage", () => {
@@ -116,7 +116,7 @@ describe("expansion combat cards", () => {
state = must(state, me.id, { type: "pickUpTreasure" });
expect(state.wardPending).toEqual({ ownerId: enemy.id, takerId: me.id });
state = must(state, enemy.id, { type: "wardChoice", play: true });
while (state.stack) state = must(state, state.stack.waitingOn, { type: "pass" });
state = drain(state);
expect(state.players.find((p) => p.id === me.id)!.life).toBe(12);
expect(state.players.find((p) => p.id === enemy.id)!.hand.some((c) => c.cardId === "ward")).toBe(false);
});
@@ -684,52 +684,34 @@ describe("go away routs around walls", () => {
});
describe("the goat charges on legs, not wings (rev 10)", () => {
it("a ram beyond the turn's legal movement is refused", () => {
it("the charge walks real corridors: in reach it spends legs, beyond them it is refused", () => {
let { state } = newGame();
state = toRound2(state);
const attacker = activePlayer(state);
const defender = state.players.find((p) => p.id !== attacker.id)!;
// Find a cell more than 3 walked steps away and park the victim there.
const view = boardView(state);
let farCell: { x: number; y: number } | null = null;
for (const key of Object.keys(view.cells)) {
const [x, y] = key.split(",").map(Number) as [number, number];
const r0 = applyCommand(state, attacker.id, { type: "endTurn", draw: 0 });
void r0;
break;
}
// Use the engine itself as the oracle: try every cell until one refuses.
const bh = giveCard(state, attacker.id, "butt-head");
let refused = false;
for (const key of Object.keys(view.cells)) {
// The engine is the oracle: every other square is either within the
// turn's legal movement or beyond it, and this maze holds both kinds.
let refusals = 0;
let rams = 0;
for (const key of Object.keys(boardView(state).cells)) {
if (key === cellKey(attacker.position)) continue;
const [x, y] = key.split(",").map(Number) as [number, number];
defender.position = { x, y };
const r = applyCommand(state, attacker.id, {
type: "cast", instanceId: bh.instanceId, target: { kind: "player", playerId: defender.id },
});
if (!r.ok && /legs, not wings/.test(r.error)) { refused = true; break; }
if (r.ok) {
// In reach: the charge spends movement equal to the walked distance.
let st = r.state;
while (st.stack) {
const rr = applyCommand(st, st.stack.waitingOn, { type: "pass" });
if (!rr.ok) throw new Error(rr.error);
st = rr.state;
}
const a = st.players.find((p) => p.id === attacker.id)!;
expect(a.position).toEqual(st.players.find((p) => p.id === defender.id)!.position);
expect(st.turn.movementUsed).toBeGreaterThan(0);
break;
if (!r.ok) {
if (/legs, not wings/.test(r.error)) refusals += 1;
continue;
}
const st = drain(r.state);
const a = st.players.find((p) => p.id === attacker.id)!;
expect(a.position).toEqual(st.players.find((p) => p.id === defender.id)!.position);
expect(st.turn.movementUsed).toBeGreaterThan(0);
rams += 1;
}
// Whichever branch ran, exercise the other with the far corner.
if (!refused) {
defender.position = { x: 0, y: 9 };
const r = applyCommand(state, attacker.id, {
type: "cast", instanceId: bh.instanceId, target: { kind: "player", playerId: defender.id },
});
if (!r.ok) refused = /legs, not wings/.test(r.error);
}
expect(refused || true).toBe(true);
expect(refusals).toBeGreaterThan(0);
expect(rams).toBeGreaterThan(0);
});
});