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
+17 -6
View File
@@ -1,6 +1,6 @@
// Websocket client + reactive session state (Svelte 5 runes).
import type { Command, GameEvent, GameView } from "@wizwar/engine";
import type { Command, GameEvent, GameView, Side } from "@wizwar/engine";
import { cardDef } from "@wizwar/engine";
const SERVER_URL =
@@ -23,6 +23,9 @@ export function spellName(cardId: string): string {
}
}
/** The compass, for captions and buttons. */
export const SIDE_NAMES: Record<Side, string> = { N: "North", E: "East", S: "South", W: "West" };
export function humanize(e: GameEvent): string | null {
switch (e.type) {
case "gameStarted": {
@@ -66,7 +69,7 @@ export function humanize(e: GameEvent): string | null {
case "extraTurnGranted": return `${e.player} speeds up — extra turn banked.`;
case "wardWindow": return `The grab hangs in the air — ${e.owner} clutches something…`;
case "pushWindow": return `${e.giant} bears down on ${e.pushee} at a fork — which way will they go?`;
case "pushChosen": return `${e.pushee} breaks ${e.direction === "N" ? "north" : e.direction === "S" ? "south" : e.direction === "E" ? "east" : "west"}.`;
case "pushChosen": return `${e.pushee} breaks ${SIDE_NAMES[e.direction].toLowerCase()}.`;
case "treasureTorn": return e.toFloor
? `${e.attacker} TEARS the treasure from ${e.defender}'s arms — it tumbles to the floor!`
: `${e.attacker} TEARS the treasure from ${e.defender}'s arms!`;
@@ -246,9 +249,11 @@ const ACTING = new Set([
"climbedFromPit", "spellTrapped",
]);
function actorOf(e: GameEvent, turnOwner: string | null): string | undefined {
const any = e as unknown as Record<string, unknown>;
for (const k of ["caster", "attacker", "by", "owner"]) if (typeof any[k] === "string") return any[k] as string;
if (ACTING.has(e.type) && typeof any.player === "string") return any.player as string;
if ("caster" in e && typeof e.caster === "string") return e.caster;
if ("attacker" in e && typeof e.attacker === "string") return e.attacker;
if ("by" in e && typeof e.by === "string") return e.by;
if ("owner" in e && typeof e.owner === "string") return e.owner;
if (ACTING.has(e.type) && "player" in e && typeof e.player === "string") return e.player;
return turnOwner ?? undefined;
}
@@ -620,12 +625,18 @@ class Net {
setTimeout(() => { if (this.error === msg.message) this.error = null; }, 5000);
break;
}
}
}
private send(message: unknown): void {
if (this.ws?.readyState === WebSocket.OPEN) this.ws.send(JSON.stringify(message));
}
/** A toast the table shows for a moment, as it shows the server's refusals. */
flash(message: string): void {
this.error = message;
setTimeout(() => { if (this.error === message) this.error = null; }, 5000);
}
create(name: string): void {
this.you = name;
this.spectating = false;