Credibility pass: the seams get sanded

Scoped to everything since the last pass (3308850). Three blind
reviews, every finding verified before touching anything.

Confirmed and fixed: two identical comment-splitting insertions left
doc comments orphaned from their fields (net and local alike); a
51-line CSS fossil of the pre-extraction inline effects survived in
Board.svelte; the rev-13 miss-roll test asserted tautologies while
its comment claimed a check the code never made — it now proves the
die was consumed, and the skeleton is no longer returned as trollId;
the sprite registry's `as never` silently disabled the completeness
its annotation advertised (now a mapped type, one cast at the
dispatch seam); a dead ternary guarded a union that doesn't exist;
Bolt carried a duplicate .fork rule from a color iteration; fxTtl
contradicted three sprites' real animation lengths; the permanence
sentinel was reinvented as a magic 9000 (the engine now exports
isPermanentDuration); CELL was declared thrice (fx.ts now imports
it); App and Replay ran two divergent fx schedulers (one scheduleFx
now, cancellable — stale flourishes can no longer fire after leaving
a game); TokenArt retried missing files forever; the anti-anti
escape guards merge with the gate asymmetry explained; wall-of-fire's
rev-12 carve-out is marked; overLimit ignored a displayed BRAINSTONE
(bots over-discarded by two); botRemark's header mis-stated its own
branches; deliverGold fired on any drop, not a home-base delivery;
escape and win banter never fired from the steps that carry them.

Rejected: "as a human would" (house voice); FxGallery's dev-harness
framing (trimmed one plea, kept the facts).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Eric Wagoner
2026-08-17 12:12:03 -04:00
co-authored by Claude Fable 5
parent 4528c66057
commit d2b40ec6f8
16 changed files with 116 additions and 130 deletions
+24 -3
View File
@@ -9,12 +9,12 @@ type Cell = { x: number; y: number };
type Pt = { x: number; y: number };
type Side = "N" | "E" | "S" | "W";
const CELL = 48;
import { CELL } from "./fx-sprites/geom";
const cellMid = (c: Cell): Pt => ({ x: c.x * CELL + CELL / 2, y: c.y * CELL + CELL / 2 });
/** Where a wizard token's center sits in a cell (mirrors Board.svelte). */
const wizMid = (c: Cell): Pt => ({ x: c.x * CELL + CELL / 2, y: c.y * CELL + CELL * 0.36 });
type FxShape =
export type FxShape =
| { kind: "fireball" | "bolt" | "waterbolt" | "streak"; a: Pt; b: Pt }
| { kind: "burst" | "splash" | "shimmer" | "shield" | "sparkle" | "whiff" | "hit"
| "pow" | "claw" | "absorb" | "portal-cell" | "soul" | "fireworks" | "chaos-swirl"
@@ -134,7 +134,7 @@ export function fxForEvents(
break;
case "creatureAttacked": {
// The target may be a wizard or a fellow creature.
const at = posOf(typeof e.target === "string" ? e.target : null) ??
const at = posOf(e.target) ??
(() => {
const c = view.creatures.find((c) => c.id === e.target);
return c ? { ...c.position } : null;
@@ -306,3 +306,24 @@ export function fxForEvents(
}
return out;
}
/** Schedule a batch's effects into `add`, expiring each after its run.
* Returns a cancel that stops pending starts and sweeps what began. */
export function scheduleFx(
events: GameEvent[], view: GameView,
add: (fx: BoardFx) => void, remove: (id: number) => void,
): () => void {
const timers: ReturnType<typeof setTimeout>[] = [];
const started: number[] = [];
for (const { fx, delay } of fxForEvents(events, view)) {
timers.push(setTimeout(() => {
started.push(fx.id);
add(fx);
timers.push(setTimeout(() => remove(fx.id), fxTtl(fx.kind)));
}, delay));
}
return () => {
timers.forEach(clearTimeout);
started.forEach(remove);
};
}