diff --git a/packages/engine/src/view.ts b/packages/engine/src/view.ts index 88b28ca..667e1b0 100644 --- a/packages/engine/src/view.ts +++ b/packages/engine/src/view.ts @@ -322,7 +322,14 @@ export function bentSightFor(view: GameView, from: Cell, to: Cell): boolean { */ export function stackSightTrace( view: GameView, -): { from: Cell; to: Cell; trace: SightTrace; bend?: { mid: Cell; trace: SightTrace } } | null { +): { + from: Cell; + to: Cell; + trace: SightTrace; + bend?: { mid: Cell; trace: SightTrace }; + /** VISIONSTONE: the one wall or door the bearer's sight dissolved (edge key). */ + pierced?: string; +} | null { const stack = view.stack; if (!stack || stack.creatureId) return null; if (!stack.attackCard || cardDef(stack.attackCard.cardId).los !== true) return null; @@ -332,6 +339,19 @@ export function stackSightTrace( if (a.position.x === d.position.x && a.position.y === d.position.y) return null; const trace = traceSightFor(view, a.position, d.position); if (trace) return { from: a.position, to: d.position, trace }; + // VISIONSTONE: the bearer sees through exactly one wall or door. When no + // plain line exists, find the single edge whose removal opens one and draw + // the ray straight through it, marking the pierced wall for the table. + if (a.displayed.some((c) => c.cardId === "visionstone")) { + const { board, blockers } = sightBasis(view); + for (const [key, edge] of Object.entries(board.edges)) { + if (edge === "open") continue; + const edges = { ...board.edges }; + delete edges[key]; + const t = traceSight({ ...board, edges }, a.position, d.position, blockers); + if (t) return { from: a.position, to: d.position, trace: t, pierced: key }; + } + } // AROUND THE CORNER: no straight line exists — find a middle square both // ends can see and draw the sight leg by leg, so the table can audit the // needle instead of doubting it. diff --git a/packages/web/src/Board.svelte b/packages/web/src/Board.svelte index e75dfcd..f8ab810 100644 --- a/packages/web/src/Board.svelte +++ b/packages/web/src/Board.svelte @@ -75,9 +75,18 @@ effects?: BoardFx[] | null; /** The sight line an attack in progress traveled — proof against "how * can he even see me?", drawn leg by leg through any warp mouth. */ - sightTrace?: { from: { x: number; y: number }; to: { x: number; y: number }; trace: SightTrace; bend?: { mid: { x: number; y: number }; trace: SightTrace } } | null; + sightTrace?: { from: { x: number; y: number }; to: { x: number; y: number }; trace: SightTrace; bend?: { mid: { x: number; y: number }; trace: SightTrace }; pierced?: string } | null; } = $props(); + /** The wall segment a VISIONSTONE shot pierced, from its edge key. */ + function piercedSegment(key: string): { x1: number; y1: number; x2: number; y2: number } { + const [kind, xy] = key.split(":") as [string, string]; + const [x, y] = xy.split(",").map(Number) as [number, number]; + return kind === "V" + ? { x1: (x + 1) * CELL, y1: y * CELL, x2: (x + 1) * CELL, y2: (y + 1) * CELL } + : { x1: x * CELL, y1: (y + 1) * CELL, x2: (x + 1) * CELL, y2: (y + 1) * CELL }; + } + const SECTOR = 5; /** Ghost slots can lie beyond the assembled maze — on any side, including @@ -701,6 +710,11 @@ {:else} {/if} + {#if sightTrace.pierced} + + {@const seg = piercedSegment(sightTrace.pierced)} + + {/if} {/if}