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:
co-authored by
Claude Fable 5
parent
1924114b6d
commit
5a0003f4ce
@@ -23,6 +23,8 @@ export interface CardDef {
|
||||
cardType: CardType | null;
|
||||
subtypes?: string[];
|
||||
los: boolean | null;
|
||||
/** Printed ADJACENT corner marking (Pick Lock, Master Key, Wizardblade). */
|
||||
adjacent?: boolean;
|
||||
text: string | null;
|
||||
quantity: number | null;
|
||||
value?: number; // number cards
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user