From 0a0108c48c962305186943cf38a43c4c4d3bb5c9 Mon Sep 17 00:00:00 2001 From: Eric Wagoner Date: Wed, 19 Aug 2026 22:00:29 -0400 Subject: [PATCH] The dread wraps: fear's diamond continues from the opposite rim MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The maze is a torus — a wizard walking through walls off one edge arrives at the other, so the crow flies around the wrap too. dreadDistance measures toroidal manhattan; the aura's wash wraps with it. No rev-32 game has cast fear yet, so the measure corrects in place; older vintages replay flat as they were played. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_0138A8CjeQRpvzKxuMfz1Bqc --- packages/engine/src/game.ts | 19 ++++++++++++++----- packages/web/src/Board.svelte | 11 ++++++++--- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/packages/engine/src/game.ts b/packages/engine/src/game.ts index 86ab12f..87a5ea4 100644 --- a/packages/engine/src/game.ts +++ b/packages/engine/src/game.ts @@ -3722,15 +3722,24 @@ function takeFromHand(p: PlayerState, instanceId: string): CardInstance | null { * monsters (rules rev 32 widened it beyond walking wizards; forced movement * such as knockback or a wave is not willing and passes). */ +/** + * FEAR's measure — FAQ: "as a Wizard walks (if he could walk through + * walls), not diagonally." Orthogonal steps, walls ignored — and the maze + * wraps, so stepping off one rim continues from the opposite one. + */ +function dreadDistance(board: AssembledBoard, a: Cell, b: Cell): number { + const dx = Math.abs(a.x - b.x); + const dy = Math.abs(a.y - b.y); + return Math.min(dx, board.width - dx) + Math.min(dy, board.height - dy); +} + function fearRepels(state: GameState, moverId: PlayerId | null, from: Cell, to: Cell): boolean { + const board = boardView(state); for (const other of state.players) { if (!other.alive || other.id === moverId) continue; if (sustainedOn(state, other.id, "fear").length === 0) continue; - // FAQ: "The 3 spaces away is measured as a Wizard walks (if he could - // walk through walls), not diagonally" — orthogonal steps, walls - // ignored. Warps do not carry the dread; walls do not shield from it. - const d = Math.abs(other.position.x - to.x) + Math.abs(other.position.y - to.y); - const dBefore = Math.abs(other.position.x - from.x) + Math.abs(other.position.y - from.y); + const d = dreadDistance(board, other.position, to); + const dBefore = dreadDistance(board, other.position, from); if (d <= 3 && d < dBefore) return true; } return false; diff --git a/packages/web/src/Board.svelte b/packages/web/src/Board.svelte index e08cb5e..e2f73a4 100644 --- a/packages/web/src/Board.svelte +++ b/packages/web/src/Board.svelte @@ -181,16 +181,21 @@ }); // FEAR's dread: three spaces in every direction, straight through walls - // ("even if walls separate you"), never diagonally — a manhattan diamond, - // the same measure the engine's refusal takes. + // ("even if walls separate you"), never diagonally — and the maze wraps, + // so the diamond continues from the opposite rim. Same measure the + // engine's refusal takes. const fearCells = $derived.by(() => { const out = new Set(); + const W = view.board.width; + const H = view.board.height; for (const fp of view.players) { if (!fp.alive) continue; if (!view.sustained.some((e) => e.cardId === "fear" && e.targetId === fp.id)) continue; for (let dx = -3; dx <= 3; dx++) { for (let dy = -3 + Math.abs(dx); dy <= 3 - Math.abs(dx); dy++) { - const k = `${fp.position.x + dx},${fp.position.y + dy}`; + const wx = ((fp.position.x + dx) % W + W) % W; + const wy = ((fp.position.y + dy) % H + H) % H; + const k = `${wx},${wy}`; if (view.board.cells[k]) out.add(k); } }