Mist shields its wizard from creatures; a glued square turns with its sector; Waterbolt splits the power it is cast with

Three of Ramses's reports from KFJL:
- Rev 28: MIST-BODY's "may not attack or be attacked" holds against
  creatures. A wraith's or democratic monster's touch, a fire imp's
  scorch, and an ordered troll/skeleton/shadow blow all pass the mist by;
  older games keep the strike their ledgers recorded. The automaton no
  longer orders its creatures at a misted wizard.
- ROTATE and RELOCATE SECTOR threw while GLUE was down: GLUE keeps its
  cell's key in the sustained effect's edge slot, and the remap read it
  as an edge. It maps as a cell now (the throw never reached a ledger,
  so this ships ungated).
- WATERBOLT's damage/knockback split was sized by the NUMBER's face; the
  engine weighs POWERSTONE's +1 and AMPLIFY's doubling, so a 5 behind a
  powerstone was refused for not totalling 6.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Eric WagonerandClaude Opus 5.5 committed 2026-10-21 19:54:43 -04:00
1 parent 3368a92cf5
commit 8dcdfd2415
5 files changed
+109 -5

No files matched your search

+3 -1
View File
@@ -1472,7 +1472,9 @@ export function automatonCommand(
// Command the menagerie: creatures march and maul before the wizard moves.
for (const c of view.creatures) {
if (c.controllerId !== you || c.justCreated) continue;
const enemyHere = livingEnemies(view).find((p) => cellKey(p.position) === cellKey(c.position));
const enemyHere = livingEnemies(view).find((p) => cellKey(p.position) === cellKey(c.position) &&
// Rev 28: creatures cannot strike the mist either.
!(view.deckRev >= 28 && view.sustained.some((s) => s.cardId === "mist-body" && s.targetId === p.id)));
if (enemyHere && !c.attackUsed &&
(c.kind === "troll" || c.kind === "skeleton" || c.kind === "shadow")) {
return { type: "creatureAttack", creatureId: c.id, targetId: enemyHere.id };
+13 -2
View File
@@ -247,7 +247,7 @@ export interface CastParams {
}
/** The revision new games are dealt under; GameConfig.deckRev pins it per game. */
export const CURRENT_RULES_REV = 27;
export const CURRENT_RULES_REV = 28;
/** Every rulings revision since the baseline, newest last — the entries a
* game's deckRev freezes it before or after. Shown to players as the house
@@ -279,6 +279,7 @@ export const RULES_REVISIONS: { rev: number; note: string }[] = [
{ rev: 25, note: "An attack on a bush or the ooze is an attack on an inanimate object like any wall: POWER ATTACK's bought points count, and a thrown DAGGER or LARGE ROCK lands at the foot of the bush instead of vanishing into the discard. Older games took neither." },
{ rev: 26, note: "BUTT-HEAD's ram is physical damage like any other: STRENGTH doubles it, AMPLIFY doubles it, WEAKNESS doubles it, POWER ATTACK adds to it, and ABSORB or BLUNT soak it. Older games dealt the bare distance, whatever rode the cast." },
{ rev: 27, note: "An ILLUSIONARY ATTACK is priced as the spell it fakes, so a BLUNT halves it, an ABSORB soaks it, and a shield stops it, exactly as against the real thing; the belief roll then decides whether the weighed blow lands at all. Older games dealt a believed illusion's full points past every counteraction." },
{ rev: 28, note: "MIST-BODY's \"may not be attacked\" holds against creatures too: a wraith's chill or a democratic monster's claw passes through a misted wizard, a fire imp's scorch finds nothing to burn, and no troll, skeleton or shadow may be ordered to strike one. Older games let creatures attack the mist." },
];
export interface GameConfig {
@@ -624,6 +625,12 @@ function isMisted(state: GameState, playerId: PlayerId): boolean {
return sustainedOn(state, playerId, "mist-body").length > 0;
}
/** Rev 28: MIST-BODY — "You may not attack or be attacked" — shields its
* wizard from creatures as from wizards. Older games let them strike. */
function mistShieldsFromCreatures(state: GameState, p: PlayerState): boolean {
return (state.config.deckRev ?? 1) >= 28 && isMisted(state, p.id);
}
function isLockedInPlace(state: GameState, playerId: PlayerId): boolean {
return sustainedOn(state, playerId, "lock-in-place").length > 0;
}
@@ -3188,6 +3195,7 @@ function impCheck(state: GameState, events: GameEvent[], onlyPlayer?: PlayerId):
if (onlyPlayer && p.id !== onlyPlayer) continue;
if (imp.justCreated && p.id === imp.controllerId) continue; // not its creator on creation turn
if (imp.scorchedThisTurn.includes(p.id)) continue;
if (mistShieldsFromCreatures(state, p)) continue;
// "Imp cannot see through a FIREWALL!" — gameLos already blocks on
// firewall edges and terrain.
if (!gameLos(state, imp.position, p.position)) continue;
@@ -3221,6 +3229,7 @@ function creatureTouchOnArrival(state: GameState, events: GameEvent[], creature:
for (const p of state.players) {
if (!p.alive || cellKey(p.position) !== cellKey(creature.position)) continue;
if (p.id === creature.controllerId && creature.kind !== "democratic-monster") continue; // won't hurt creator
if (mistShieldsFromCreatures(state, p)) continue;
if (creature.kind === "wraith" && !creature.attackUsed) {
creature.attackUsed = true;
events.push({ type: "creatureTouched", creatureId: creature.id, kind: creature.kind, player: p.id });
@@ -3432,6 +3441,7 @@ function doCreatureAttack(prev: GameState, creatureId: string, targetId: string)
return err("the creature must share its target's square");
}
if (targetPlayer && targetPlayer.id === creature.controllerId) return err("it will not hurt its creator");
if (targetPlayer && mistShieldsFromCreatures(state, targetPlayer)) return err("your target is mist and cannot be attacked");
creature.attackUsed = true;
let amount: number;
@@ -3571,7 +3581,8 @@ function remapState(
state.slimeTraps = remapRecord(state.slimeTraps, mapCellKey);
state.groundObjects = remapRecord(state.groundObjects, mapCellKey);
for (const fx of state.sustained) {
if (fx.edge) fx.edge = mapEdgeKey(fx.edge);
// GLUE keeps its glued cell's key where edge spells keep an edge's.
if (fx.edge) fx.edge = fx.cardId === "glue" ? mapCellKey(fx.edge) : mapEdgeKey(fx.edge);
}
for (const p of state.players) {
if (inSector(p.position)) p.position = mapCell(p.position);