Rev 19: a dust cloud blinds whoever stands in it (CPU7)

The automaton stood in Kestrel's DUST CLOUD and filled a square with
stone three rows away. The cloud blocked only sight lines passing
through it; the square a line began or ended in was never asked. The
FAQ is plain: no LOS spell may be cast by a wizard in a cloud, nor at
one, though a spell upon oneself still works, and VISIONSTONE does
not see through dust. From rev 19 the engine's sight and the view's —
the targeting aid the client and the automaton both read — refuse a
line with dust at either end. Older games keep their sight.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Jm2auWk6RP71CjaAb4FMoG
This commit is contained in:
Eric Wagoner
2026-09-05 23:30:17 -04:00
co-authored by Claude Fable 5.1
parent 3d4bf1fa6d
commit f859d90af8
3 changed files with 55 additions and 1 deletions
+19
View File
@@ -220,8 +220,14 @@ export function sightedCellsFor(view: GameView): Set<string> {
const me = view.players.find((p) => p.id === view.you);
if (!me) return out;
const { board, blockers } = sightBasis(view);
// In a dust cloud a wizard sees only their own square (rev 19).
if (dustAtEnd(view, me.position, me.position, true)) {
out.add(`${me.position.x},${me.position.y}`);
return out;
}
for (const key of Object.keys(board.cells)) {
const [x, y] = key.split(",").map(Number) as [number, number];
if (dustAtEnd(view, me.position, { x, y })) continue;
if (sightBetween(board, me.position, { x, y }, blockers)) out.add(key);
}
// VISIONSTONE lets its bearer see through exactly one wall or door —
@@ -314,16 +320,29 @@ function sightBasis(view: GameView): { board: GameView["board"]; blockers: Recor
* exists — the renderer's material for drawing the line an attack traveled.
*/
export function traceSightFor(view: GameView, from: Cell, to: Cell): SightTrace | null {
if (dustAtEnd(view, from, to)) return null;
const { board, blockers } = sightBasis(view);
return traceSight(board, from, to, blockers);
}
/** Rev 19: DUST CLOUD blinds its occupant and hides them from LOS spells;
* a square sees itself still. `standing` asks only whether the viewer's
* own square is dust. */
function dustAtEnd(view: GameView, from: Cell, to: Cell, standing = false): boolean {
if (view.deckRev < 19) return false;
const dust = (c: Cell) => view.squareContents[`${c.x},${c.y}`]?.kind === "dust";
if (standing) return dust(from);
if (from.x === to.x && from.y === to.y) return false;
return dust(from) || dust(to);
}
/**
* AROUND THE CORNER's bent sight, from this viewer's knowledge: the caster
* sees a middle square which sees the target — mirroring the engine's
* bentLos so a client can predict whether the modifier will land.
*/
export function bentSightFor(view: GameView, from: Cell, to: Cell): boolean {
if (dustAtEnd(view, from, to)) return false;
const { board, blockers } = sightBasis(view);
if (sightBetween(board, from, to, blockers)) return true;
for (const key of Object.keys(board.cells)) {