diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index c28f0b1..34ca4b2 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -1937,9 +1937,15 @@ const CARD_EFFECTS: Record if (cmd.target?.kind === "edge") { const { cell, side } = cmd.target; const key = edgeKey(cell, side); - if (view.edges[key] !== "wall") return "that is not a stone wall"; + // Doors are "small entryways in a stone wall" — stone melts as + // stone (rev 38; older ledgers had refused doors and replay so). + const current = view.edges[key]; + const meltable = current === "wall" || + ((state.config.deckRev ?? 1) >= 38 && current === "door"); + if (!meltable) return "that is not a stone wall"; if (!losToEdge(view, caster.position, cell, side)) return "no line of sight"; state.edgeOverrides[key] = "open"; + delete state.doorStates[key]; delete state.createdEdges[key]; events.push({ type: "stoneTurnedToWater", caster: caster.id, at: null, edge: { cell, side } }); // "Wall turns into a WATERWALL with a range and damage of 2." diff --git a/packages/engine/test/expansion-terrain.test.ts b/packages/engine/test/expansion-terrain.test.ts index 133d815..8b6299e 100644 --- a/packages/engine/test/expansion-terrain.test.ts +++ b/packages/engine/test/expansion-terrain.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { applyCommand, activePlayer, boardView, createGame, gameLos } from "../src/game"; -import { cellKey, edgeKey, SIDES, stepTarget, type Cell } from "../src/board"; +import { cellKey, edgeKey, SIDES, stepTarget, type Cell, type Side } from "../src/board"; import type { CardInstance } from "../src/cards"; import { eligibleCellsFor, sightedCellsFor, viewFor } from "../src/view"; import { newExpansionGame as newGame, must, giveCard, emptyNeighborCell } from "./helpers"; @@ -161,6 +161,40 @@ describe("expansion terrain", () => { expect(state.squareContents[cellKey(spot.cell)]).toBeUndefined(); // Wave side effects vary by geometry; the melt itself is the pinned behavior. }); + + it("stone to water melts a door from rev 38 — a small entryway in a stone wall", () => { + for (const deckRev of [37, 38]) { + const { state } = createGame({ + playerIds: ["alice", "bob"], seed: 42, sets: ["basic", "expansion1"], deckRev, + }); + const me = activePlayer(state); + const board = boardView(state); + // Stand the caster at some door and melt it point-blank. + let doorAt: { cell: Cell; side: Side } | null = null; + outer: for (const k of Object.keys(board.cells)) { + const [x, y] = k.split(",").map(Number) as [number, number]; + for (const side of SIDES) { + if (board.edges[edgeKey({ x, y }, side)] === "door") { + doorAt = { cell: { x, y }, side }; + break outer; + } + } + } + if (!doorAt) throw new Error("setup: no door on this board"); + me.position = { ...doorAt.cell }; + const stw = giveCard(state, me.id, "stone-to-water", "SW", 0); + const r = applyCommand(state, me.id, { + type: "cast", instanceId: stw.instanceId, + target: { kind: "edge", cell: doorAt.cell, side: doorAt.side }, + }); + expect(r.ok).toBe(deckRev >= 38); + if (r.ok) { + const key = edgeKey(doorAt.cell, doorAt.side); + expect(boardView(r.state).edges[key] ?? "open").toBe("open"); + expect(r.state.doorStates[key]).toBeUndefined(); + } + } + }); }); describe("eligibility dimming mirrors the engine", () => { diff --git a/packages/server/src/rooms.ts b/packages/server/src/rooms.ts index b61357f..165fa95 100644 --- a/packages/server/src/rooms.ts +++ b/packages/server/src/rooms.ts @@ -54,7 +54,7 @@ export interface Room { const rooms = new Map(); /** Rules revision new games are dealt under (stored games keep their own). */ -const RULES_REV = 37; +const RULES_REV = 38; const ROOM_CODE_ALPHABET = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789"; diff --git a/packages/web/src/App.svelte b/packages/web/src/App.svelte index 57fc018..291d877 100644 --- a/packages/web/src/App.svelte +++ b/packages/web/src/App.svelte @@ -282,7 +282,7 @@ "remove-lock": "click a door to strip its lock for good", "create-door": "click a wall for the new door", "warp-wand": "click a wall to warp it open", - "stone-to-water": "click a stone wall, or a stone-filled square", + "stone-to-water": "click a stone wall or door, or a stone-filled square", "thumb-of-god": "aim the die at a square in sight — it may drift on impact", "dispel-creation": "click the creation — a square, a creature, or a conjured wall", };