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
+37 -34
View File
@@ -140,14 +140,14 @@
"cardType": "attack", "cardType": "attack",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Blinds opponent, who must roll direction on D4 if attempting to move, throw objects, or cast spells, except for spells he casts on himself (e.g., BLUNT). Misdirected spells go intended distance. If movement is attempted, bumping into a wall counts as one space of movement. Reroll for each movement point. Duration equals NUMBER card.", "text": "Blinds opponent, who must roll direction on D4 if attempting to move, throw objects, engage in combat, or cast spells, except for spells he casts on himself (e.g., BLUNT). Misdirected spells go intended distance. If movement is attempted, bumping into a wall counts as one space of movement. Reroll for each movement point. Duration equals NUMBER card.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"Even if you're BLIND, you can still move your MONSTERS okay, since they aren't LOS controls, anyway. Also, non-LOS spells are not affected by your being blind. If you try to cast a spell on someone, if it goes the right direction, then it hits him. If you cast a spell that was supposed to go 3 spaces away, and you accidentally point it at the wall next to you instead, since the spell can't go the intended distance, it dissipates. If you are trying to attack one player while you are BLIND and the spell ends up going toward another player, it misses him." "Even if you're BLIND, you can still move your MONSTERS okay, since they aren't LOS controls, anyway. Also, non-LOS spells are not affected by your being blind. If you try to cast a spell on someone, if it goes the right direction, then it hits him. If you cast a spell that was supposed to go 3 spaces away, and you accidentally point it at the wall next to you instead, since the spell can't go the intended distance, it dissipates. If you are trying to attack one player while you are BLIND and the spell ends up going toward another player, it misses him."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording. 6e adds 'engage in combat' to the roll list - blinded punches roll too (RULES CHANGE vs 5e DB)."
} }
}, },
{ {
@@ -195,14 +195,14 @@
"object" "object"
], ],
"los": false, "los": false,
"text": "POWER: Draw two more card, now. Hand limit is now nine cards (including this card). This card is permanent. Display when used.", "text": "POWER: Draw two more cards, now. Hand limit is now nine cards (including this card). This card is permanent. Display when used.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"This has a loophole. If you drop it and pick it up, you get two new cards, then get to draw 2 more at the end of your turn. This is, so far, legal." "This has a loophole. If you drop it and pick it up, you get two new cards, then get to draw 2 more at the end of your turn. This is, so far, legal."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy). Fan DB commentary: With the loophole, the brainstone is better than the spell stone. It is more reasonable to close the loophole with a rule variant that you only get two new cards when drawing the card, not when picking it up or stealing it." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording. 6e face has NO corner type marking (stones are unlabeled)."
} }
}, },
{ {
@@ -229,12 +229,12 @@
"cardType": "attack", "cardType": "attack",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Remove and discard any one card from opponent's hand. You name which one. If opponent doesn't have it, this is still considered your attack for the turn.", "text": "Name a card, then remove and discard it from opponent's hand. If opponent doesn't have that card, this is still considered your attack for the turn.",
"quantity": 1, "quantity": 1,
"faqRulings": [], "faqRulings": [],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -263,14 +263,14 @@
"object" "object"
], ],
"los": true, "los": true,
"text": "You may throw it. Does three points physical damage. Cannot stab with it. Retrievable by anyone after it is thrown.", "text": "You may throw it. Does three points physical damage. You cannot stab with it. Retrievable by anyone after it is thrown.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"These are NOT spells (yes, the rules already say that) which \"create\" the objects, they are the objects themselves. Yes, they can be discarded!" "These are NOT spells (yes, the rules already say that) which \"create\" the objects, they are the objects themselves. Yes, they can be discarded!"
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -320,14 +320,14 @@
"cardType": "neutral", "cardType": "neutral",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Drags any moveable object within L.O.S. towards you until it tuns into a fixes object (such as a wall) or arrives in you square. This does not function on walls, bushes, items held by another player, or a player with LOCK IN PLACE cast on him.", "text": "Drags any moveable object within L.O.S. towards you until it runs into a fixed object (such as a wall) or arrives in your square. This does not function on walls, bushes, items held by another player, or a player with LOCK IN PLACE cast on him.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"This will work on Wizards and MONSTERS just fine. It will also work with AROUND THE CORNER." "This will work on Wizards and MONSTERS just fine. It will also work with AROUND THE CORNER."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -429,12 +429,12 @@
"cardType": "attack", "cardType": "attack",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Opponent must move away a number of spaces equal to the NUMBER card played, as straight as possible. Roll D4 for random direction, if necessary. Opponent also loses turn.", "text": "Opponent must move away a number of spaces equal to the NUMBER card played, in as straight a line as possible. Roll D4 for random direction, if necessary. Opponent also loses turn.",
"quantity": 1, "quantity": 1,
"faqRulings": [], "faqRulings": [],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4688.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -495,14 +495,14 @@
"object" "object"
], ],
"los": true, "los": true,
"text": "You may throw it. Does two points physical damage. Once thrown, it may picked up and thrown again by this player.", "text": "You may throw it. Does two points physical damage. Once thrown, it may be picked up and thrown again by any player.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"These are NOT spells (yes, the rules already say that) which \"create\" the objects, they are the objects themselves. Yes, they can be discarded!" "These are NOT spells (yes, the rules already say that) which \"create\" the objects, they are the objects themselves. Yes, they can be discarded!"
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording. 6e: 'by any player' (5e DB said only the thrower could re-throw)."
} }
}, },
{ {
@@ -541,20 +541,21 @@
"id": "master-key", "id": "master-key",
"name": "Master Key", "name": "Master Key",
"set": "basic", "set": "basic",
"cardType": "object", "cardType": "neutral",
"subtypes": [ "subtypes": [
"object" "object"
], ],
"los": false, "los": false,
"text": "Unlocks any door (but door relocks behind you). Do not discard when used. Display Immediately. Must be adjacent to door to use. Does not work on a JAMmed LOCK.", "text": "Unlocks any door (but door relocks behind you). You may \"hold the door open\" for others, if you wish. Must be adjacent to door to use. Do not discard when used. Display immediately. Does not work on a JAMmed LOCK.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"Will not work with a JAMMED LOCK." "Will not work with a JAMMED LOCK."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording. 6e corners: NEUTRAL / ADJACENT (was typed object from the 5e DB). Still a physical object per the rulebook's object list; using it is not a spell."
} },
"adjacent": true
}, },
{ {
"id": "medusa", "id": "medusa",
@@ -580,14 +581,14 @@
"cardType": "neutral", "cardType": "neutral",
"subtypes": [], "subtypes": [],
"los": false, "los": false,
"text": "You turn to mist. You can pass through anything but solid stone or stone walls. You may not attack or be attacked. Firewalls still damage you. Touched objects become \"misted\" when touched so you may pick up and carry objects. Duration equals NUMBER card played.", "text": "You turn to mist. You can pass through anything but solid stone or stone walls. You may not attack or be attacked. Firewalls still damage you. Objects become \"misted\" when touched, so you may pick up and carry objects. Duration equals NUMBER card played.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"Not affected by THORNBUSH or ROSEBUSH. A falling WALL would not hurt you. You can still use objects, even while misted. If you drop or throw something, it becomes unmisted (solid). No, Waterwalls will not affect you, as you are floating along in the air, and the water is rushing along on the floor. Yes, you can reach into a SAFE. MISTBODY is also immune to KILLER OOZE, which lies on the floor." "Not affected by THORNBUSH or ROSEBUSH. A falling WALL would not hurt you. You can still use objects, even while misted. If you drop or throw something, it becomes unmisted (solid). No, Waterwalls will not affect you, as you are floating along in the air, and the water is rushing along on the floor. Yes, you can reach into a SAFE. MISTBODY is also immune to KILLER OOZE, which lies on the floor."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording. Mist passes doors/bushes/etc. but NOT stone walls (the maze walls) or solid stone (RULES CLARIFICATION)."
} }
}, },
{ {
@@ -709,7 +710,7 @@
"cardType": "neutral", "cardType": "neutral",
"subtypes": [], "subtypes": [],
"los": false, "los": false,
"text": "Unlock any door (but the door will relock behind you). Can only use when adjacent to door. You may \"hold the door open\" for others, if you wish.", "text": "Unlock any door (but the door will relock behind you). Can only use when adjacent to door. You may \"hold the door open\" for others, if you wish. This is not a spell.",
"quantity": 4, "quantity": 4,
"alsoIn": [ "alsoIn": [
{ {
@@ -720,8 +721,9 @@
"faqRulings": [], "faqRulings": [],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Official lists: 4 copies in the 6e basic deck plus 2 more in Expansion Set #1 (see alsoIn). Expansion Set #2 fan lists include a further reprint. No type header in the card database (physical actions are 'not spells'); classed Neutral per cards-6e.md. Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording. 6e corners: NEUTRAL / ADJACENT. Face states 'This is not a spell' - NO SPELL does not block it."
} },
"adjacent": true
}, },
{ {
"id": "power-drain", "id": "power-drain",
@@ -780,12 +782,12 @@
"cardType": "attack", "cardType": "attack",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "A telekinetic blow that does magical damage equal to two points plus an accompanying NUMBER card (optional).", "text": "A telekinetic blow that does magical damage equal to two points plus an (optional) accompanying NUMBER card.",
"quantity": 3, "quantity": 3,
"faqRulings": [], "faqRulings": [],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -1177,7 +1179,7 @@
"set": "basic", "set": "basic",
"cardType": "neutral", "cardType": "neutral",
"subtypes": [], "subtypes": [],
"los": false, "los": true,
"text": "You momentarily become so ugly that all opponents in L.O.S. retreat as far away as necessary to avoid L.O.S., along the shortest path available. This happens during your turn. Passage through firewalls and bushes blocks L.O.S. For multiple corridor choices, opponent rolls D4 for random direction. Affects SHRUNK and INVISIBLE opponents.", "text": "You momentarily become so ugly that all opponents in L.O.S. retreat as far away as necessary to avoid L.O.S., along the shortest path available. This happens during your turn. Passage through firewalls and bushes blocks L.O.S. For multiple corridor choices, opponent rolls D4 for random direction. Affects SHRUNK and INVISIBLE opponents.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
@@ -1185,7 +1187,7 @@
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4689.jpeg) - supersedes 5e database wording. 6e corner: NEUTRAL / L.O.S. (was los=false)."
} }
}, },
{ {
@@ -1215,14 +1217,14 @@
"cardType": "neutral/counteraction", "cardType": "neutral/counteraction",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Creates a barrier of fire across any corridor. passing through it does four points of magical damage. As counteraction, it will stop a waterbolt. Duration equals NUMBER card played.", "text": "Creates a barrier of fire across any corridor. Passing through it does four points of magical damage. As a COUNTERACTION, it will stop a WATERBOLT. Duration equals NUMBER card played.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"This blocks LOS, even for a FIRE IMP. WALL OF FIRE does not destroy Magic Stones as a FIREBALL does. Damage done by a WALL OF FIRE is not considered an attack on you, thus REVERSE has no effect on the damage. You may NOT cast it on top of an ILLUSION WALL, as that violates the rule about creating an object in an occupied space." "This blocks LOS, even for a FIRE IMP. WALL OF FIRE does not destroy Magic Stones as a FIREBALL does. Damage done by a WALL OF FIRE is not considered an attack on you, thus REVERSE has no effect on the damage. You may NOT cast it on top of an ILLUSION WALL, as that violates the rule about creating an object in an occupied space."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4690.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -1232,12 +1234,12 @@
"cardType": "attack", "cardType": "attack",
"subtypes": [], "subtypes": [],
"los": true, "los": true,
"text": "Does magical damage equal to the accompanying NUMBER card, or knocks opponent back of squares equal to the NUMBER card, or a combination thereof, as chosen by the caster.", "text": "Does magical damage equal to the accompanying NUMBER card, or knocks opponent back NUMBER of squares equal to the NUMBER card, or a combination thereof, as chosen by the caster.",
"quantity": 3, "quantity": 3,
"faqRulings": [], "faqRulings": [],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4690.jpeg) - supersedes 5e database wording."
} }
}, },
{ {
@@ -1266,15 +1268,16 @@
"object" "object"
], ],
"los": false, "los": false,
"text": "Does magical damage equal to the NUMBER card played. Does NO damage without a NUMBER card. Must be on same square as opponent to attack.. If target fills an entire square, you must be in an adjacent square to use. Permanent. Display when used.", "text": "Does magical damage equal to the NUMBER card played. Does NO damage without a NUMBER card. Must be on same square as opponent to attack. If target fills an entire square, you must be in an adjacent square to use. This card is permanent. Display when used.",
"quantity": 1, "quantity": 1,
"faqRulings": [ "faqRulings": [
"This is most certainly an object. However, the damage it does is like a spell attack, but since it is any object, ABSORB SPELL has no effect on it. REFLECTIONS will work, putting magical damage back upon the user. NO SPELL would stop WIZARDBLADE for its duration. You have to supply a new Number card each time you use Wizardblade. You have to discard each Number card as it's used in each attack." "This is most certainly an object. However, the damage it does is like a spell attack, but since it is any object, ABSORB SPELL has no effect on it. REFLECTIONS will work, putting magical damage back upon the user. NO SPELL would stop WIZARDBLADE for its duration. You have to supply a new Number card each time you use Wizardblade. You have to discard each Number card as it's used in each attack."
], ],
"verification": { "verification": {
"status": "verbatim", "status": "verbatim",
"notes": "Quantity and 6e-deck membership from the official 6e rulebook card list (owner's copy)." "notes": "Corrected 2026-08-15 from a photo of the owner's 6e card face (research/scans/IMG_4690.jpeg) - supersedes 5e database wording. 6e corners: ATTACK / ADJACENT."
} },
"adjacent": true
}, },
{ {
"id": "adrenaline", "id": "adrenaline",
+2
View File
@@ -23,6 +23,8 @@ export interface CardDef {
cardType: CardType | null; cardType: CardType | null;
subtypes?: string[]; subtypes?: string[];
los: boolean | null; los: boolean | null;
/** Printed ADJACENT corner marking (Pick Lock, Master Key, Wizardblade). */
adjacent?: boolean;
text: string | null; text: string | null;
quantity: number | null; quantity: number | null;
value?: number; // number cards value?: number; // number cards
+52 -7
View File
@@ -1817,8 +1817,9 @@ function doMove(prev: GameState, direction: Side): CommandResult {
p.position = dest; p.position = dest;
via = "step"; via = "step";
crossedFirewall = true; crossedFirewall = true;
} else if (misted && (edge === "wall" || edge === "door")) { } else if (misted && edge === "door") {
// MIST-BODY passes through anything but solid stone. // MIST-BODY: "pass through anything but solid stone or stone walls" —
// doors yes, the maze's stone walls no.
p.position = dest; p.position = dest;
via = "passWall"; via = "passWall";
} else if (edge === "wall" && p.passWallCharges > 0) { } 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 // THORNBUSH: "his turn ends, he loses his following turn, and he takes one
// point of physical damage from thorns." // point of physical damage from thorns." Mist drifts through unharmed.
if (content?.kind === "thornbush" && p.alive) { if (content?.kind === "thornbush" && p.alive && !misted) {
events.push({ type: "enteredThornbush", player: p.id, at: p.position }); events.push({ type: "enteredThornbush", player: p.id, at: p.position });
applyDamage(state, events, p, 1, "thorns", null, "physical"); applyDamage(state, events, p, 1, "thorns", null, "physical");
p.lostTurns++; 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), (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.turn.attackUsed = true;
state.stack = { state.stack = {
attackerId: attacker.id, attackerId: attacker.id,
@@ -2086,9 +2105,13 @@ function doCast(prev: GameState, cmd: Extract<Command, { type: "cast" }>): Comma
return err(`${def.name} is already displayed`); return err(`${def.name} is already displayed`);
} }
// Physical actions (objects like MASTER KEY) are not spells; spells are // "Some cards involve physical actions, like picking locks, removing
// blocked by NO SPELL / MEDUSA. // locks, jamming locks, and throwing daggers. These are not spells."
const isSpell = def.cardType !== "object"; // (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) { if (isSpell) {
const castBlock = castingBlocked(state, caster.id); const castBlock = castingBlocked(state, caster.id);
if (castBlock) return err(castBlock); if (castBlock) return err(castBlock);
@@ -2290,6 +2313,22 @@ function doCounteract(prev: GameState, playerId: PlayerId, instanceId: string):
return { ok: true, state, events }; 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 // SHIELDSTONE: "use a NUMBER card as a counteraction against point- or
// duration-based spells, reducing effects by [its] value." // duration-based spells, reducing effects by [its] value."
if (isNumberCard(card.cardId)) { if (isNumberCard(card.cardId)) {
@@ -2411,6 +2450,12 @@ function resolveStack(state: GameState, events: GameEvent[]): void {
pipe.duration = Math.max(0, pipe.duration - v); pipe.duration = Math.max(0, pipe.duration - v);
continue; 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]; const ce = CARD_EFFECTS[counter.card.cardId];
if (ce && ce.kind === "counter") ce.apply(pipe); if (ce && ce.kind === "counter") ce.apply(pipe);
} }
+17 -8
View File
@@ -297,14 +297,23 @@ describe("control effects", () => {
type: "cast", instanceId: mist.instanceId, numberInstanceIds: ["number-3#N"], type: "cast", instanceId: mist.instanceId, numberInstanceIds: ["number-3#N"],
}); });
// Misted wizard walks through a wall if one is adjacent. // Mist passes through locked DOORS but NOT the maze's stone walls.
const view = boardView(state); {
const d = state.players.find((p) => p.id === defender)!; const view = boardView(state);
for (const side of SIDES) { const d = state.players.find((p) => p.id === defender)!;
const k = edgeKey(d.position, side); const doorEntry = Object.entries(view.edges).find(([, st]) => st === "door")!;
if (view.edges[k] === "wall" && view.cells[cellKey(neighbor(d.position, side))]) { const [kind, coords] = doorEntry[0].split(":") as [string, string];
state = must(state, defender, { type: "move", direction: side }); const [dx, dy] = coords.split(",").map(Number) as [number, number];
break; d.position = { x: dx, y: dy };
state = must(state, defender, { type: "move", direction: kind === "V" ? "E" : "S" });
for (const side of SIDES) {
const k = edgeKey(state.players.find((p) => p.id === defender)!.position, side);
const view2 = boardView(state);
if (view2.edges[k] === "wall" &&
view2.cells[cellKey(neighbor(state.players.find((p) => p.id === defender)!.position, side))]) {
expect(applyCommand(state, defender, { type: "move", direction: side }).ok).toBe(false);
break;
}
} }
} }
state = must(state, defender, { type: "endTurn", draw: 0 }); state = must(state, defender, { type: "endTurn", draw: 0 });
+92
View File
@@ -293,3 +293,95 @@ describe("sector manipulation", () => {
expect(refused.ok).toBe(false); expect(refused.ok).toBe(false);
}); });
}); });
describe("6e card-face corrections", () => {
it("no spell does not block pick lock — 'This is not a spell'", () => {
let { state } = newGame();
state = toRound2(state);
const attacker = activePlayer(state);
const defender = state.players.find((p) => p.id !== attacker.id)!;
defender.position = { ...attacker.position };
const ns = giveCard(state, attacker.id, "no-spell");
giveCard(state, attacker.id, "number-3", "N", 1);
state = must(state, attacker.id, {
type: "cast", instanceId: ns.instanceId, numberInstanceIds: ["number-3#N"],
target: { kind: "player", playerId: defender.id },
});
state = must(state, defender.id, { type: "pass" });
state = must(state, attacker.id, { type: "endTurn", draw: 0 });
// The silenced defender cannot cast a spell...
const fb = giveCard(state, defender.id, "fireball", "F", 0);
expect(applyCommand(state, defender.id, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: attacker.id },
}).ok).toBe(false);
// ...but picking a lock is a physical action, not a spell.
const view = boardView(state);
const doorEntry = Object.entries(view.edges).find(([, st]) => st === "door")!;
const [kind, coords] = doorEntry[0].split(":") as [string, string];
const [dx, dy] = coords.split(",").map(Number) as [number, number];
const d = state.players.find((p) => p.id === defender.id)!;
d.position = { x: dx, y: dy };
const pl = giveCard(state, defender.id, "pick-lock", "PL", 1);
const result = applyCommand(state, defender.id, {
type: "cast", instanceId: pl.instanceId,
target: { kind: "edge", cell: { x: dx, y: dy }, side: kind === "V" ? "E" : "S" },
});
expect(result.ok).toBe(true);
});
it("blinded punches flail on the die — 'engage in combat'", () => {
let hits = 0, misses = 0;
for (let seed = 1; seed <= 10; seed++) {
let { state } = newGame(seed);
state = toRound2(state);
const attacker = activePlayer(state);
const defender = state.players.find((p) => p.id !== attacker.id)!;
defender.position = { ...attacker.position };
state.sustained.push({
id: "fx-test", cardId: "blind", casterId: defender.id,
targetId: attacker.id, remainingTurns: 3, data: {},
});
const result = applyCommand(state, attacker.id, { type: "punch", targetId: defender.id });
expect(result.ok).toBe(true);
if (!result.ok) continue;
if (result.state.stack) hits++;
else {
misses++;
expect(result.state.turn.attackUsed).toBe(true); // the flail spends the attack
}
}
expect(hits + misses).toBe(10);
expect(misses).toBeGreaterThan(0);
});
it("wall of fire, as a counteraction, stops a waterbolt cold", () => {
let { state } = newGame();
state = toRound2(state);
const attacker = activePlayer(state);
const defender = state.players.find((p) => p.id !== attacker.id)!;
defender.position = { ...attacker.position };
const wb = giveCard(state, attacker.id, "waterbolt");
giveCard(state, attacker.id, "number-4", "N", 1);
giveCard(state, defender.id, "wall-of-fire", "WOF", 0);
state = must(state, attacker.id, {
type: "cast", instanceId: wb.instanceId, numberInstanceIds: ["number-4#N"],
target: { kind: "player", playerId: defender.id },
params: { damage: 4, knockback: 0 },
});
state = must(state, defender.id, { type: "counteract", instanceId: "wall-of-fire#WOF" });
state = must(state, attacker.id, { type: "pass" });
state = must(state, defender.id, { type: "pass" });
expect(state.players.find((p) => p.id === defender.id)!.life).toBe(15);
// But it cannot counter a fireball.
state = must(state, attacker.id, { type: "endTurn", draw: 0 });
state = must(state, defender.id, { type: "endTurn", draw: 0 });
const fb = giveCard(state, attacker.id, "fireball", "F", 0);
giveCard(state, defender.id, "wall-of-fire", "WOF2", 0);
state = must(state, attacker.id, {
type: "cast", instanceId: fb.instanceId, target: { kind: "player", playerId: defender.id },
});
expect(applyCommand(state, defender.id, { type: "counteract", instanceId: "wall-of-fire#WOF2" }).ok).toBe(false);
});
});
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 MiB