Sight travels through the wraparound openings

"If casting a spell, or checking line of sight through the AUTO WARP,
treat it as a straight line, and the two connected boards as though
they were adjacent" — and the lettered openings reconnect edges the
same way. hasWarpLineOfSight models it: the line must run straight
down the corridor, out one mouth, and in through the paired mouth,
with both corridor legs clear and neither mouth filled solid. The new
sightBetween (direct or through-a-warp) is now the game's LOS check
everywhere — spells, ambush triggers, Around The Corner, the
visionstone sweep, and the targeting dim on the client.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-16 12:43:57 -04:00
co-authored by Claude Fable 5
parent fce3adc340
commit 64454ffc3e
4 changed files with 107 additions and 9 deletions
+5 -4
View File
@@ -21,6 +21,7 @@ import {
cellKey,
edgeKey,
hasLineOfSight,
sightBetween,
neighbor,
stepTarget,
} from "./board";
@@ -290,7 +291,7 @@ export function losBlockers(state: GameState): Record<string, true> {
/** LOS including square-filling blockers. */
export function gameLos(state: GameState, from: Cell, to: Cell): boolean {
return hasLineOfSight(boardView(state), from, to, losBlockers(state));
return sightBetween(boardView(state), from, to, losBlockers(state));
}
/** Parse an edge key back into its north/west cell and side. */
@@ -368,13 +369,13 @@ function casterLos(
): boolean {
const board = perceivedBoard(state, events, caster.id, { from, to });
const blockers = losBlockers(state);
if (hasLineOfSight(board, from, to, blockers)) return true;
if (sightBetween(board, from, to, blockers)) return true;
if (!displays(caster, "visionstone")) return false;
for (const key of Object.keys(board.edges)) {
if ((board.edges[key] ?? "open") === "open") continue;
const edges = { ...board.edges };
delete edges[key];
if (hasLineOfSight({ ...board, edges }, from, to, blockers)) return true;
if (sightBetween({ ...board, edges }, from, to, blockers)) return true;
}
return false;
}
@@ -2752,7 +2753,7 @@ function dragToward(state: GameState, target: PlayerState, dest: Cell): void {
function losToEdge(board: AssembledBoard, from: Cell, cell: Cell, side: Side): boolean {
const n = neighbor(cell, side);
return hasLineOfSight(board, from, cell) || hasLineOfSight(board, from, n);
return sightBetween(board, from, cell) || sightBetween(board, from, n);
}
function isAdjacentToEdge(pos: Cell, cell: Cell, side: Side): boolean {