Visionstone shots draw their ray and mark the pierced wall
A stone-bearing automaton fired Blind through a warp mouth and one wall, and the table saw nothing but the attack modal — the sight trace only knew plain and bent-corner lines, so a legal shot read as a cheat. When the attacker displays VISIONSTONE and no plain line exists, stackSightTrace now finds the single edge whose removal opens the line (warp legs included), draws the ray through it, and the keymap pulses the dissolved wall so the answer to "how can he even see me?" is on the board. Verified against the live LZ5R blind moment: warp mouths at (0,2)W and (4,7)E, pierced wall V:3,8. 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
358633022c
commit
773f0a7d21
@@ -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.
|
||||
|
||||
@@ -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}
|
||||
<SightTraceOverlay from={sightTrace.from} to={sightTrace.to} trace={sightTrace.trace} />
|
||||
{/if}
|
||||
{#if sightTrace.pierced}
|
||||
<!-- the one wall VISIONSTONE dissolved for this shot -->
|
||||
{@const seg = piercedSegment(sightTrace.pierced)}
|
||||
<line x1={seg.x1} y1={seg.y1} x2={seg.x2} y2={seg.y2} class="sight-pierced" />
|
||||
{/if}
|
||||
{/if}
|
||||
<!-- spell flourishes: one sprite component per effect (src/fx-sprites/) -->
|
||||
<g class="fx-layer" aria-hidden="true">
|
||||
@@ -804,6 +818,13 @@
|
||||
stroke-width: 2;
|
||||
animation: sight-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
.sight-pierced {
|
||||
stroke: #c9a72a;
|
||||
stroke-width: 5;
|
||||
stroke-linecap: round;
|
||||
stroke-dasharray: 2 5;
|
||||
animation: sight-pulse 1.6s ease-in-out infinite;
|
||||
}
|
||||
@keyframes sight-pulse {
|
||||
0%, 100% { opacity: 0.9; }
|
||||
50% { opacity: 0.35; }
|
||||
|
||||
Reference in New Issue
Block a user