The dimming aid stops lying about boobytrap, glue, and safe
Three cards sat wrongly in the creation bucket of eligibleCellsFor, so the targeting dim demanded sighted empty squares the engine never asks for. Boobytrap tokens go anywhere on the board but solid stone — no sight required; a trap you can see coming is a poor trap. Glue and the safe were worse than restricted: inverted. Both seize a square that HOLDS a loose object or treasure, exactly the squares the empty- square rule dimmed out. Each now has its own branch mirroring the engine's actual checks, pinned by tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
1275ac9287
commit
ac53773fe4
@@ -185,7 +185,7 @@ export function sightedCellsFor(view: GameView): Set<string> {
|
|||||||
|
|
||||||
const CREATION_CARD_IDS = new Set([
|
const CREATION_CARD_IDS = new Set([
|
||||||
"fill-square-with-stone", "thornbush", "killer-ooze", "rosebush", "dust-cloud",
|
"fill-square-with-stone", "thornbush", "killer-ooze", "rosebush", "dust-cloud",
|
||||||
"fill-square-with-slime", "create-pit", "handful-of-tacks", "glue", "safe", "boobytrap",
|
"fill-square-with-slime", "create-pit", "handful-of-tacks",
|
||||||
]);
|
]);
|
||||||
const SUMMON_CARD_IDS = new Set([
|
const SUMMON_CARD_IDS = new Set([
|
||||||
"troll", "skeleton", "wraith", "fire-imp", "democratic-monster", "shadow",
|
"troll", "skeleton", "wraith", "fire-imp", "democratic-monster", "shadow",
|
||||||
@@ -256,6 +256,29 @@ export function eligibleCellsFor(view: GameView, cardId: string): Set<string> |
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (cardId === "boobytrap") {
|
||||||
|
// Tokens go anywhere on the board except solid stone — no sight needed;
|
||||||
|
// a trap you can see coming is a poor trap.
|
||||||
|
return new Set(cells.filter((k) => view.squareContents[k]?.kind !== "stone"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardId === "glue" || cardId === "safe") {
|
||||||
|
// Both seize a square that HOLDS something — a loose object or an
|
||||||
|
// unclaimed treasure — within the caster's sight. The safe additionally
|
||||||
|
// needs the square clear of terrain to build around its prize.
|
||||||
|
const out = new Set<string>();
|
||||||
|
for (const k of cells) {
|
||||||
|
if (cardId === "safe" && view.squareContents[k]) continue;
|
||||||
|
const hasObject =
|
||||||
|
(view.groundObjects[k] ?? []).length > 0 ||
|
||||||
|
view.treasures.some((t) => t.position && !t.carriedBy && key(t.position.x, t.position.y) === k);
|
||||||
|
if (!hasObject) continue;
|
||||||
|
if (!sighted.has(k)) continue;
|
||||||
|
out.add(k);
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
if (cardId === "stone-to-water") {
|
if (cardId === "stone-to-water") {
|
||||||
return new Set(cells.filter((k) => view.squareContents[k]?.kind === "stone"));
|
return new Set(cells.filter((k) => view.squareContents[k]?.kind === "stone"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -161,3 +161,35 @@ describe("expansion terrain", () => {
|
|||||||
// Wave side effects vary by geometry; the melt itself is the pinned behavior.
|
// Wave side effects vary by geometry; the melt itself is the pinned behavior.
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("eligibility dimming mirrors the engine", () => {
|
||||||
|
it("boobytrap tokens go anywhere but solid stone, sight be damned", async () => {
|
||||||
|
const { viewFor, eligibleCellsFor } = await import("../src/view");
|
||||||
|
let { state } = newGame();
|
||||||
|
const caster = activePlayer(state).id;
|
||||||
|
const view = viewFor(state, caster);
|
||||||
|
const lit = eligibleCellsFor(view, "boobytrap")!;
|
||||||
|
// Every board square is fair game (the fresh maze holds no solid stone),
|
||||||
|
// including squares far outside the caster's sight.
|
||||||
|
expect(lit.size).toBe(Object.keys(view.board.cells).length);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("glue lights only sighted squares that hold something", async () => {
|
||||||
|
const { viewFor, eligibleCellsFor } = await import("../src/view");
|
||||||
|
let { state } = newGame();
|
||||||
|
const caster = activePlayer(state);
|
||||||
|
// Drop a dagger at the caster's feet — the one guaranteed-sighted object.
|
||||||
|
const here = { ...caster.position };
|
||||||
|
state.groundObjects[cellKey(here)] = [{ instanceId: "dagger#G", cardId: "dagger" }];
|
||||||
|
const lit = eligibleCellsFor(viewFor(state, caster.id), "glue")!;
|
||||||
|
expect(lit.has(cellKey(here))).toBe(true);
|
||||||
|
// Empty squares stay dim — glue needs something to glue down.
|
||||||
|
for (const k of lit) {
|
||||||
|
const view = viewFor(state, caster.id);
|
||||||
|
const held =
|
||||||
|
(view.groundObjects[k] ?? []).length > 0 ||
|
||||||
|
view.treasures.some((t) => t.position && cellKey(t.position) === k);
|
||||||
|
expect(held).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user