Credibility pass: comments say what the code can't, and the seams are gone

Engine: pit-rim exits live in one helper (pitRimExits) shared by the
resolver and the automaton; chargeReach names the six-stride cap; dust
sight is inDust + dustAtEnd; PushPending is exported once; the force-field
counter builds its events in one list; drawUnderSlowDeath says what it
draws under. Test groups are named for the rule they pin, not the
revision that introduced it.

Client: one smoothstep; one edgeScar per battle-scarred edge; one
board-zone rule (which also seats the caption strip on phones); the
compass names live in SIDE_NAMES; net.flash() is the one toast; MediaQuery
replaces two hand-rolled matchMedia listeners; sprites carry their sizes
as plain numbers and their CSS in one rule each; canvas helpers are
formatted like the rest of the tree. Comments that told how a cel or a
fallback used to look now say what it draws. The actions-over notice no
longer blames a pickup when slime ended the turn.

Server and ops: dataRoot() is the one data directory; headlineOf builds
its deeds without a cast; the rollup and skills read the same way the
code behaves.

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-16 00:21:34 -04:00
co-authored by Claude Fable 5.1
parent 85690f0e23
commit 6e49fc9020
40 changed files with 331 additions and 293 deletions
+11 -10
View File
@@ -9,6 +9,7 @@ import {
LOS_BLOCKING_CONTENT,
type AmbushState,
type CastStack,
type PushPending,
type CreatureState,
type GameState,
type PlayerId,
@@ -93,7 +94,7 @@ export interface GameView {
chaosPending: { casterId: PlayerId; queue: PlayerId[] } | null;
/** A grab hangs while the treasure's owner decides their Ward. */
wardPending: { ownerId: PlayerId; takerId: PlayerId } | null;
pushPending: GameState["pushPending"];
pushPending: PushPending | null;
slowDeathPending: { playerId: PlayerId; points: number } | null;
/** YOUR armed ambushes. Other players' ambushes are invisible. */
yourAmbushes: AmbushState[];
@@ -229,7 +230,7 @@ export function sightedCellsFor(view: GameView, from?: Cell): Set<string> {
const eye = from ?? me.position;
const { board, blockers } = sightBasis(view);
// In a dust cloud a wizard sees only their own square (rev 19).
if (dustAtEnd(view, eye, eye, true)) {
if (inDust(view, eye)) {
out.add(`${eye.x},${eye.y}`);
return out;
}
@@ -333,15 +334,15 @@ export function traceSightFor(view: GameView, from: Cell, to: Cell): SightTrace
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);
/** Rev 19: DUST CLOUD blinds its occupant. Older games see through it. */
function inDust(view: GameView, cell: Cell): boolean {
return view.deckRev >= 19 && view.squareContents[`${cell.x},${cell.y}`]?.kind === "dust";
}
/** Dust at either end kills a sight line; a square sees itself still. */
function dustAtEnd(view: GameView, from: Cell, to: Cell): boolean {
if (from.x === to.x && from.y === to.y) return false;
return dust(from) || dust(to);
return inDust(view, from) || inDust(view, to);
}
/**