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
@@ -297,14 +297,23 @@ describe("control effects", () => {
|
||||
type: "cast", instanceId: mist.instanceId, numberInstanceIds: ["number-3#N"],
|
||||
});
|
||||
|
||||
// Misted wizard walks through a wall if one is adjacent.
|
||||
const view = boardView(state);
|
||||
const d = state.players.find((p) => p.id === defender)!;
|
||||
for (const side of SIDES) {
|
||||
const k = edgeKey(d.position, side);
|
||||
if (view.edges[k] === "wall" && view.cells[cellKey(neighbor(d.position, side))]) {
|
||||
state = must(state, defender, { type: "move", direction: side });
|
||||
break;
|
||||
// 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 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];
|
||||
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 });
|
||||
|
||||
@@ -293,3 +293,95 @@ describe("sector manipulation", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user