Two idle-bot loops cured; visionstone reaches the walls
Game eleven's silent turns, diagnosed from the ledger. Automaton III proposed DESTROY WALL at a wall its visionstone could see — but the engine's edge-target casts never honored the stone, despite the declared policy that it is the bearer's sight wherever sight is asked. castSightEdge now dissolves one wall for the look, and the three raw losToEdge gates (create-wall, destroy-wall, attacks on walls) route through it, gaining visionstone and bent-corner sight alike. Automaton II cast REMOVE CURSE with no patient and no ailment named; the rung now writes the full prescription. Refusal-fallback loops read as idle bots — both replays march again. Ledgers verified. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015RCWSTnb1KYTPyL4GmhGnF
This commit is contained in:
co-authored by
Claude Fable 5
parent
b99c22f14a
commit
7585a5e88d
@@ -1071,11 +1071,19 @@ function selfCare(view: GameView, style: AutomatonStyle, tier: TierTraits): Comm
|
||||
}
|
||||
if (!tier.buffs) return null; // the apprentice's book ends at the stones
|
||||
// A curse on the clockwork gets scrubbed off.
|
||||
const cursed = view.sustained.some(
|
||||
// The engine wants the whole prescription — patient and ailment both —
|
||||
// or the cast is refused and the fallback reads as an idle bot.
|
||||
const myCurse = view.sustained.find(
|
||||
(e) => e.targetId === view.you && e.casterId !== view.you && AFFLICTIONS[e.cardId] != null,
|
||||
);
|
||||
const cleanse = inHand(view, "remove-curse");
|
||||
if (cursed && cleanse) return { type: "cast", instanceId: cleanse.instanceId };
|
||||
if (myCurse && cleanse) {
|
||||
return {
|
||||
type: "cast", instanceId: cleanse.instanceId,
|
||||
target: { kind: "player", playerId: view.you },
|
||||
params: { cardId: myCurse.cardId },
|
||||
};
|
||||
}
|
||||
// Three or more wizards: LIFESAVER takes elimination off the table.
|
||||
const lifesaver = inHand(view, "lifesaver");
|
||||
if (lifesaver && view.players.length > 2) return { type: "cast", instanceId: lifesaver.instanceId };
|
||||
|
||||
@@ -521,7 +521,9 @@ function castSight(
|
||||
return !!cmd.aroundCornerInstanceId && bentLos(state, caster, caster.position, to);
|
||||
}
|
||||
|
||||
/** Sight for an edge-aimed cast (walls, doors), bend-aware like castSight. */
|
||||
/** Sight for an edge-aimed cast (walls, doors), bend-aware like castSight.
|
||||
* VISIONSTONE is the bearer's sight here too — one wall or door, any one,
|
||||
* falls away for the look, exactly as it does for square-aimed casts. */
|
||||
function castSightEdge(
|
||||
state: GameState,
|
||||
caster: PlayerState,
|
||||
@@ -531,6 +533,14 @@ function castSightEdge(
|
||||
side: Side,
|
||||
): boolean {
|
||||
if (losToEdge(board, caster.position, cell, side)) return true;
|
||||
if (displays(caster, "visionstone")) {
|
||||
for (const [key, edge] of Object.entries(board.edges)) {
|
||||
if (edge === "open") continue;
|
||||
const edges = { ...board.edges };
|
||||
delete edges[key];
|
||||
if (losToEdge({ ...board, edges }, caster.position, cell, side)) return true;
|
||||
}
|
||||
}
|
||||
if (!cmd.aroundCornerInstanceId) return false;
|
||||
for (const key of Object.keys(board.cells)) {
|
||||
const [mx, my] = key.split(",").map(Number) as [number, number];
|
||||
@@ -1211,7 +1221,7 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
}
|
||||
const key = edgeKey(cell, side);
|
||||
if ((view.edges[key] ?? "open") !== "open") return "there is already something in that wall line";
|
||||
if (!losToEdge(view, caster.position, cell, side)) return "no line of sight to the wall line";
|
||||
if (!castSightEdge(state, caster, cmd, view, cell, side)) return "no line of sight to the wall line";
|
||||
state.edgeOverrides[key] = "wall";
|
||||
state.createdEdges[key] = true;
|
||||
events.push({ type: "wallCreated", caster: caster.id, edge: { cell, side } });
|
||||
@@ -1235,7 +1245,7 @@ const CARD_EFFECTS: Record<string, AttackEffect | NeutralEffect | CounterEffect>
|
||||
const key = edgeKey(cell, side);
|
||||
const current = view.edges[key] ?? "open";
|
||||
if (current === "open") return "there is no wall there";
|
||||
if (!losToEdge(view, caster.position, cell, side)) return "no line of sight to the wall";
|
||||
if (!castSightEdge(state, caster, cmd, view, cell, side)) return "no line of sight to the wall";
|
||||
state.edgeOverrides[key] = "open";
|
||||
delete state.doorStates[key];
|
||||
delete state.createdEdges[key];
|
||||
@@ -5122,7 +5132,7 @@ function doCast(prev: GameState, cmd: Extract<Command, { type: "cast" }>): Comma
|
||||
if (effect.sameSquare && !touchesEdge(caster.position, cell, side)) {
|
||||
return err("you must stand beside the wall");
|
||||
}
|
||||
if (effect.requiresLos && !losToEdge(view, caster.position, cell, side)) {
|
||||
if (effect.requiresLos && !castSightEdge(state, caster, cmd, view, cell, side)) {
|
||||
return err("no line of sight to the wall");
|
||||
}
|
||||
const dmg =
|
||||
|
||||
Reference in New Issue
Block a user