Credibility pass: one voice, no scars

A three-reviewer sweep for tells of piecemeal machine generation,
every finding verified against the code before touching it. No
behavior changes; the full suite passes unchanged (plus two
strengthened pins).

Engine: removed four void-silenced fossils (a parseEdgeKey call
voided where it stood, stoneEffect's ignored cardId parameter, the
actualTarget remnant in doCast, a voided loop variable in shadow
upkeep); fixed the initialize-then-overwrite narration in
spawnCreature; replaced a filter(() => false) no-op; waterwall now
rides waveFromEdge instead of carrying its own verbatim copy (and the
single-caller washBack wrapper went with it); blind wall-bumps and
LOS blockers each collapsed to one implementation; the wand-id list
and the "permanent" duration sentinel became named constants; the
ambush number local no longer shadows the imported numberValue
function; assorted reviewer-aimed phrasings rewritten as the
constraints they guard.

Server/deploy: the protocol header now documents all eleven message
types; dropped an eslint pragma with no eslint, a test script with no
tests, and an rsync exclude anchored at a path that never existed
(the real data/ dir now excluded); the Caddy vhost has one source of
truth; stale "pending DNS" note removed — the record resolves.

Web: ~90 lines of CSS swallowed verbatim into a mobile media query
deduplicated; the reduced-motion guard on the board now actually
stops the marked-cell pulse; one shared color module replaces two
drifted palettes; an orphaned doc comment rejoined its function.

Tests: the ten-times-pasted helper block became test/helpers.ts;
wave-numbered files renamed for the behaviors they pin; deliberation
comments and void-ed corpses of unwritten assertions deleted; silent
seed-dependent early-returns now fail loudly; one assertion that
compared a value to itself now pins the home-translation it meant to;
the stored-log single-number command form gained the explicit
compatibility test it deserved.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 11:56:55 -04:00
co-authored by Claude Fable 5
parent f62fcf2510
commit 695307daa8
24 changed files with 240 additions and 716 deletions
+165
View File
@@ -0,0 +1,165 @@
import { describe, expect, it } from "vitest";
import { applyCommand, activePlayer, displays, handLimit, sustainedOn, type GameState, type PlayerId } from "../src/game";
import type { CardInstance } from "../src/cards";
import { newGame, must, giveCard, toRound2, faceOff, castAt } from "./helpers";
/** Display a stone for a player during their turn. */
function displayStone(state: GameState, playerId: PlayerId, stoneId: string, slot = 0): GameState {
const stone = giveCard(state, playerId, stoneId, "S" + stoneId, slot);
return must(state, playerId, { type: "cast", instanceId: stone.instanceId });
}
describe("magic stones", () => {
it("bloodstone shaves one point off every hit", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
// Defender displays bloodstone on their own turn first.
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = displayStone(state, defender, "bloodstone");
expect(displays(state.players.find((p) => p.id === defender)!, "bloodstone")).toBe(true);
state = must(state, defender, { type: "endTurn", draw: 0 });
const fb = giveCard(state, attacker, "fireball", "F", 1);
state = castAt(state, attacker, defender, fb);
expect(state.players.find((p) => p.id === defender)!.life).toBe(11); // 5-1
});
it("soulstone keeps the last three points safe from spells", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = displayStone(state, defender, "soulstone");
const d = state.players.find((p) => p.id === defender)!;
d.life = 5;
state = must(state, defender, { type: "endTurn", draw: 0 });
const fb = giveCard(state, attacker, "fireball", "F", 1);
state = castAt(state, attacker, defender, fb);
expect(state.players.find((p) => p.id === defender)!.life).toBe(3); // floored, not 0
// But a punch (physical) can finish the job past the floor.
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = must(state, defender, { type: "endTurn", draw: 0 });
state = must(state, attacker, { type: "punch", targetId: defender });
state = must(state, defender, { type: "pass" });
expect(state.players.find((p) => p.id === defender)!.life).toBe(2);
});
it("brainstone draws two now and raises the hand limit to nine", () => {
let { state } = newGame();
const me = activePlayer(state).id;
state = displayStone(state, me, "brainstone");
const p = state.players.find((pl) => pl.id === me)!;
expect(p.hand.length).toBe(9); // 7 - 1 slot replaced + kept + 2 drawn
expect(handLimit(p)).toBe(9);
// Re-displaying is refused (no double draw).
const again = applyCommand(state, me, { type: "cast", instanceId: "brainstone#Sbrainstone" });
expect(again.ok).toBe(false);
});
it("powerstone adds one to number cards; speedstone adds movement", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
state = displayStone(state, attacker, "powerstone");
const lb = giveCard(state, attacker, "lightning-blast", "L", 1);
giveCard(state, attacker, "number-3", "N", 2);
state = castAt(state, attacker, defender, lb, { numberInstanceIds: ["number-3#N"] });
expect(state.players.find((p) => p.id === defender)!.life).toBe(11); // 3+1
// Speedstone: allowance 4 on the next turn. (The stunned defender's turn
// is skipped, so play comes straight back to the attacker.)
state = displayStone(state, attacker, "speedstone", 1);
state = must(state, attacker, { type: "endTurn", draw: 0 });
expect(activePlayer(state).id).toBe(attacker);
expect(state.turn.movementAllowance).toBe(4);
});
it("shieldstone lets number cards counteract spells", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = displayStone(state, defender, "shieldstone");
state = must(state, defender, { type: "endTurn", draw: 0 });
const fb = giveCard(state, attacker, "fireball", "F", 1);
giveCard(state, defender, "number-4", "N4", 1);
state = must(state, attacker, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender },
});
state = must(state, defender, { type: "counteract", instanceId: "number-4#N4" });
state = must(state, attacker, { type: "pass" });
state = must(state, defender, { type: "pass" });
expect(state.players.find((p) => p.id === defender)!.life).toBe(14); // 5-4
});
it("shadowstone feeds physical damage back as life", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
state = displayStone(state, attacker, "shadowstone");
const dagger = giveCard(state, attacker, "dagger", "D", 1);
state = castAt(state, attacker, defender, dagger);
expect(state.players.find((p) => p.id === defender)!.life).toBe(12);
expect(state.players.find((p) => p.id === attacker)!.life).toBe(18); // +3
});
it("fireball burns displayed stones off when damage lands", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = displayStone(state, defender, "speedstone");
state = must(state, defender, { type: "endTurn", draw: 0 });
const fb = giveCard(state, attacker, "fireball", "F", 1);
state = castAt(state, attacker, defender, fb);
const d = state.players.find((p) => p.id === defender)!;
expect(d.hand.some((c) => c.cardId === "speedstone")).toBe(false);
expect(d.displayed.length).toBe(0);
});
});
describe("slow death", () => {
it("bleeds one life per card drawn, forever", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
const sd = giveCard(state, attacker, "slow-death");
state = castAt(state, attacker, defender, sd);
expect(sustainedOn(state, defender, "slow-death").length).toBe(1);
state = must(state, attacker, { type: "endTurn", draw: 0 });
// Defender discards two to make room, then draws two: 2 damage.
const d = state.players.find((p) => p.id === defender)!;
const toDiscard = d.hand.slice(0, 2).map((c) => c.instanceId);
state = must(state, defender, { type: "discard", instanceIds: toDiscard });
state = must(state, defender, { type: "endTurn", draw: 2 });
expect(state.players.find((p) => p.id === defender)!.life).toBe(13);
// It never expires — many turns later it still bleeds.
for (let i = 0; i < 4; i++) {
state = must(state, activePlayer(state).id, { type: "endTurn", draw: 0 });
}
expect(sustainedOn(state, defender, "slow-death").length).toBe(1);
});
it("bloodstone cancels slow death's per-draw point", () => {
let { state } = newGame();
state = toRound2(state);
const { attacker, defender } = faceOff(state);
const sd = giveCard(state, attacker, "slow-death");
state = castAt(state, attacker, defender, sd);
state = must(state, attacker, { type: "endTurn", draw: 0 });
state = displayStone(state, defender, "bloodstone");
const d = state.players.find((p) => p.id === defender)!;
const toDiscard = d.hand.filter((c) => c.cardId !== "bloodstone").slice(0, 2).map((c) => c.instanceId);
state = must(state, defender, { type: "discard", instanceIds: toDiscard });
state = must(state, defender, { type: "endTurn", draw: 2 });
expect(state.players.find((p) => p.id === defender)!.life).toBe(15); // 1-1=0 per draw
});
});