Correct 15 cards against photos of the actual 6e card faces

Eric spotted mismatches between the 5e-database-derived card data and
his physical 6e cards (photos IMG_4688-4690). All 15 texts are now
verbatim from the faces. Four were mechanical, now fixed in the
engine: BLIND's 6e text adds "engage in combat" — blinded punches
flail on a die roll; PICK LOCK's face states "This is not a spell" —
the lock cards and thrown weapons are physical actions NO SPELL cannot
silence; MIST-BODY passes doors and drifts through thornbushes but
does NOT pass the maze's stone walls (was backwards); WALL OF FIRE's
counteraction mode stops a Waterbolt (previously deferred). Metadata:
UGLY is L.O.S.-marked, MASTER KEY is NEUTRAL/ADJACENT, WIZARDBLADE
and PICK LOCK carry ADJACENT markings (new `adjacent` field), LARGE
ROCK is rethrowable by any player (already engine behavior),
BRAINSTONE's face has no corner type. 84 tests passing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-15 21:58:17 -04:00
co-authored by Claude Fable 5
parent 1924114b6d
commit 5a0003f4ce
8 changed files with 200 additions and 49 deletions
+52 -7
View File
@@ -1817,8 +1817,9 @@ function doMove(prev: GameState, direction: Side): CommandResult {
p.position = dest;
via = "step";
crossedFirewall = true;
} else if (misted && (edge === "wall" || edge === "door")) {
// MIST-BODY passes through anything but solid stone.
} else if (misted && edge === "door") {
// MIST-BODY: "pass through anything but solid stone or stone walls" —
// doors yes, the maze's stone walls no.
p.position = dest;
via = "passWall";
} else if (edge === "wall" && p.passWallCharges > 0) {
@@ -1852,8 +1853,8 @@ function doMove(prev: GameState, direction: Side): CommandResult {
}
// THORNBUSH: "his turn ends, he loses his following turn, and he takes one
// point of physical damage from thorns."
if (content?.kind === "thornbush" && p.alive) {
// point of physical damage from thorns." Mist drifts through unharmed.
if (content?.kind === "thornbush" && p.alive && !misted) {
events.push({ type: "enteredThornbush", player: p.id, at: p.position });
applyDamage(state, events, p, 1, "thorns", null, "physical");
p.lostTurns++;
@@ -1945,6 +1946,24 @@ function doPunch(prev: GameState, targetId: PlayerId): CommandResult {
(s) => !(s.cardId === "buddy" && s.casterId === attacker.id && s.targetId === target.id),
);
// BLIND: "...engage in combat..." — a blinded brawler flails on a die
// roll; the swing connects only on a 1 (same convention as INVISIBLE).
if (sustainedOn(state, attacker.id, "blind").length > 0) {
const [roll, rngNext] = rollDie(state.rng);
state.rng = rngNext;
if (roll !== 1) {
state.turn.attackUsed = true;
return {
ok: true,
state,
events: [{
type: "attackMisdirected", attacker: attacker.id, intended: target.id,
rolledDirection: SIDES[roll - 1]!, newTarget: null,
}],
};
}
}
state.turn.attackUsed = true;
state.stack = {
attackerId: attacker.id,
@@ -2086,9 +2105,13 @@ function doCast(prev: GameState, cmd: Extract<Command, { type: "cast" }>): Comma
return err(`${def.name} is already displayed`);
}
// Physical actions (objects like MASTER KEY) are not spells; spells are
// blocked by NO SPELL / MEDUSA.
const isSpell = def.cardType !== "object";
// "Some cards involve physical actions, like picking locks, removing
// locks, jamming locks, and throwing daggers. These are not spells."
// (PICK LOCK's 6e face: "This is not a spell.")
const NOT_SPELLS = new Set([
"pick-lock", "master-key", "jam-lock", "remove-lock", "dagger", "large-rock",
]);
const isSpell = def.cardType !== "object" && !NOT_SPELLS.has(inHand.cardId);
if (isSpell) {
const castBlock = castingBlocked(state, caster.id);
if (castBlock) return err(castBlock);
@@ -2290,6 +2313,22 @@ function doCounteract(prev: GameState, playerId: PlayerId, instanceId: string):
return { ok: true, state, events };
}
// WALL OF FIRE: "As a COUNTERACTION, it will stop a WATERBOLT."
if (card.cardId === "wall-of-fire") {
if (stack.attackCard?.cardId !== "waterbolt") {
return err("as a counteraction, Wall of Fire only stops a Waterbolt");
}
takeFromHand(player, instanceId);
state.discard.push(card);
stack.counters.push({ player: playerId, card, nullified: false });
stack.waitingOn = stack.attackerId;
return {
ok: true,
state,
events: [{ type: "counteractionPlayed", player: playerId, card, cardId: card.cardId, against: "waterbolt" }],
};
}
// SHIELDSTONE: "use a NUMBER card as a counteraction against point- or
// duration-based spells, reducing effects by [its] value."
if (isNumberCard(card.cardId)) {
@@ -2411,6 +2450,12 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
pipe.duration = Math.max(0, pipe.duration - v);
continue;
}
if (counter.card.cardId === "wall-of-fire") {
// The wave meets the fire: the waterbolt is entirely stopped.
pipe.damage = 0;
pipe.fullyStopped = true;
continue;
}
const ce = CARD_EFFECTS[counter.card.cardId];
if (ce && ce.kind === "counter") ce.apply(pipe);
}