Credibility pass: sweep the workshop floor
Scoped to everything since the last pass (695307d). The residue of
fast iteration, removed: a reduced-motion media query that had
swallowed a full copy of the .faq-seal rules; doc comments orphaned
from their functions by inserted methods; the FAQ scrape's seams
(section headings run into ruling bodies under the wrong topics, a
next-page heading shipped as a ruling, an amputated "h", and rulings
filed under alphabetically-nearest strangers — Large Rock/Dagger now
lives on those cards; Book of Spells and Torquemada describe no card
in this set and are gone); the write-only aisle flag left behind by
the reverted rev 7; a linter-silenced dead destructure; a duplicated
median lookup; dead casts; and the fallback path that ignored the
apprentice's one-card draw.
Tests now typecheck (tsconfig includes test/), which surfaced the
missing type imports and a drifted creature literal hiding under an
as-cast. Also: a no-op self-assignment, mid-file imports hoisted, a
dynamic-import habit made static, a hedge comment replaced with a
loud setup failure, the fallback-retries-itself dead rung removed
from both bot drivers, and the twin peek scrims merged.
Deliberately kept: the TIERS lookup guard (ledger JSON is untrusted),
the wand-cost stanzas (they differ on the power-attack trade — a
rules question, not a dedup), and rollDie's coexistence with rollD4
(migrating changes visible logs; future work).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
73150a89b5
commit
3308850bb6
@@ -465,12 +465,11 @@ function selfCare(view: GameView, style: AutomatonStyle, tier: TierTraits): Comm
|
||||
return { type: "cast", instanceId: safe.instanceId, target: { kind: "cell", cell: myFloorTreasure.position! } };
|
||||
}
|
||||
const glue = inHand(view, "glue");
|
||||
const midN = numbers[Math.floor(numbers.length / 2)];
|
||||
if (glue && midN) {
|
||||
if (glue && mid) {
|
||||
return {
|
||||
type: "cast", instanceId: glue.instanceId,
|
||||
target: { kind: "cell", cell: myFloorTreasure.position! },
|
||||
numberInstanceIds: [midN.instanceId],
|
||||
numberInstanceIds: [mid.instanceId],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -504,7 +503,7 @@ export function automatonCommand(
|
||||
style: AutomatonStyle = "hunter",
|
||||
tierName: AutomatonTier = "archmage",
|
||||
): Command | null {
|
||||
const you = view.you as PlayerId;
|
||||
const you = view.you;
|
||||
const tier = TIERS[tierName] ?? TIERS.archmage;
|
||||
if (view.phase !== "playing") return null;
|
||||
|
||||
@@ -653,11 +652,11 @@ export function automatonCommand(
|
||||
}
|
||||
|
||||
/** The safe fallback when the automaton's choice was refused. */
|
||||
export function automatonFallback(view: GameView): Command {
|
||||
const you = view.you as PlayerId;
|
||||
export function automatonFallback(view: GameView, tierName: AutomatonTier = "archmage"): Command {
|
||||
const you = view.you;
|
||||
if (view.pendingDiscard === you) {
|
||||
return { type: "discard", instanceIds: worstCards(view, Math.max(1, overLimit(view))) };
|
||||
}
|
||||
if (view.stack || view.chaosPending || view.outOfTurnWindow) return { type: "pass" };
|
||||
return { type: "endTurn", draw: 2 };
|
||||
return { type: "endTurn", draw: (TIERS[tierName] ?? TIERS.archmage).draw };
|
||||
}
|
||||
|
||||
@@ -27,8 +27,6 @@ export interface Warp {
|
||||
from: { cell: Cell; side: Side };
|
||||
/** ...you arrive at this cell (entering through `side`). */
|
||||
to: { cell: Cell; side: Side };
|
||||
/** An aisle (AUTO) warp joins two edges that meet at a corner. */
|
||||
aisle?: boolean;
|
||||
}
|
||||
|
||||
export interface AssembledBoard {
|
||||
@@ -121,7 +119,7 @@ export interface AssemblyOptions {
|
||||
* openings pair straight across the map — correct for the 4p square; the
|
||||
* standard 2p column crosses its side openings and must pass pairs.
|
||||
*/
|
||||
warpPairs?: ([OpeningRef, OpeningRef] | [OpeningRef, OpeningRef, "aisle"])[];
|
||||
warpPairs?: [OpeningRef, OpeningRef][];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -206,12 +204,11 @@ export function assembleBoard(
|
||||
};
|
||||
|
||||
if (options.warpPairs) {
|
||||
for (const [a, b, kind] of options.warpPairs) {
|
||||
for (const [a, b] of options.warpPairs) {
|
||||
const ca = openingCell(a.sector, a.side);
|
||||
const cb = openingCell(b.sector, b.side);
|
||||
const aisle = kind === "aisle" ? { aisle: true as const } : {};
|
||||
warps.push({ from: { cell: ca, side: a.side }, to: { cell: cb, side: b.side }, ...aisle });
|
||||
warps.push({ from: { cell: cb, side: b.side }, to: { cell: ca, side: a.side }, ...aisle });
|
||||
warps.push({ from: { cell: ca, side: a.side }, to: { cell: cb, side: b.side } });
|
||||
warps.push({ from: { cell: cb, side: b.side }, to: { cell: ca, side: a.side } });
|
||||
}
|
||||
} else {
|
||||
for (let i = 0; i < placements.length; i++) {
|
||||
|
||||
@@ -218,9 +218,8 @@ export interface GameConfig {
|
||||
* BIG MAN pushes occupants ahead, steps over floor hazards for 2 points,
|
||||
* and bars monsters from his square. Rev 6: ANTI-ANTI cannot pin a
|
||||
* teleport escape ("does not work against escape" — the card face).
|
||||
* Rev 7 briefly restricted warp sight to aisle corners; the table's
|
||||
* reading prevailed and all board openings carry sight in every revision.
|
||||
* DIMENSIONAL WARP's tokens never do ("There is no L.O.S. through the
|
||||
* Rev 7: no behavioral change — every board opening carries warp sight
|
||||
* in every revision. DIMENSIONAL WARP's tokens never do ("There is no L.O.S. through the
|
||||
* warp." — the card face). Rev 8: nothing can be created on a warp token,
|
||||
* and a SPEED bonus turn burns a turn of durations on the hastened wizard.
|
||||
*/
|
||||
@@ -4219,7 +4218,7 @@ function doCast(prev: GameState, cmd: Extract<Command, { type: "cast" }>): Comma
|
||||
const view = boardView(state);
|
||||
const current = view.edges[edgeKey(cell, side)] ?? "open";
|
||||
if (current !== "wall" && current !== "door") {
|
||||
return err("only walls, doors, and thornbushes can be attacked");
|
||||
return err("there is no wall or door on that edge to attack");
|
||||
}
|
||||
if (effect.sameSquare && !touchesEdge(caster.position, cell, side)) {
|
||||
return err("you must stand beside the wall");
|
||||
|
||||
@@ -68,7 +68,7 @@ export function setupBoard(playerCount: number, rng: RngState): SetupResult {
|
||||
];
|
||||
const board = assembleBoard(placements, {
|
||||
warpPairs: [
|
||||
[{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // AUTO WARP corner
|
||||
[{ sector: 0, side: "W" }, { sector: 1, side: "N" }], // AUTO WARP corner
|
||||
[{ sector: 0, side: "N" }, { sector: 2, side: "S" }],
|
||||
[{ sector: 1, side: "W" }, { sector: 2, side: "E" }],
|
||||
[{ sector: 0, side: "E" }, { sector: 1, side: "S" }],
|
||||
@@ -106,10 +106,10 @@ export function setupBoard(playerCount: number, rng: RngState): SetupResult {
|
||||
warpPairs: [
|
||||
[{ sector: 0, side: "N" }, { sector: 4, side: "S" }], // tip to tip
|
||||
[{ sector: 1, side: "W" }, { sector: 3, side: "E" }],
|
||||
[{ sector: 0, side: "W" }, { sector: 1, side: "N" }, "aisle"], // aisle-warp corners
|
||||
[{ sector: 0, side: "E" }, { sector: 3, side: "N" }, "aisle"],
|
||||
[{ sector: 4, side: "W" }, { sector: 1, side: "S" }, "aisle"],
|
||||
[{ sector: 4, side: "E" }, { sector: 3, side: "S" }, "aisle"],
|
||||
[{ sector: 0, side: "W" }, { sector: 1, side: "N" }], // aisle-warp corners
|
||||
[{ sector: 0, side: "E" }, { sector: 3, side: "N" }],
|
||||
[{ sector: 4, side: "W" }, { sector: 1, side: "S" }],
|
||||
[{ sector: 4, side: "E" }, { sector: 3, side: "S" }],
|
||||
],
|
||||
});
|
||||
return { board, rng: rngN };
|
||||
|
||||
@@ -212,7 +212,6 @@ export function eligibleCellsFor(view: GameView, cardId: string): Set<string> |
|
||||
const out = new Set<string>();
|
||||
for (const k of cells) {
|
||||
if (view.squareContents[k]) continue;
|
||||
const [x, y] = k.split(",").map(Number) as [number, number];
|
||||
if (view.board.homes.some((h) => key(h.x, h.y) === k)) continue;
|
||||
if (view.players.some((p) => p.alive && key(p.position.x, p.position.y) === k)) continue;
|
||||
if (view.treasures.some((t) => t.position && key(t.position.x, t.position.y) === k)) continue;
|
||||
@@ -220,7 +219,6 @@ export function eligibleCellsFor(view: GameView, cardId: string): Set<string> |
|
||||
if (view.dimWarps.some((w) => key(w.a.x, w.a.y) === k || key(w.b.x, w.b.y) === k)) continue;
|
||||
if (!sighted.has(k)) continue;
|
||||
out.add(k);
|
||||
void x; void y;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user