Files
wizwar6e/packages/web/src/prefs.svelte.ts
T
Eric WagonerandClaude Fable 5.1 6e49fc9020 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
2026-09-16 00:21:34 -04:00

86 lines
3.4 KiB
TypeScript

// Player preferences: purely client-side taste, saved on this device.
// Nothing here touches the rules — a preference may change what you SEE
// or what routine commands the client sends on your behalf, never what
// is legal.
export interface Prefs {
/** Token art: the photographed cardboard, or the hand-drawn vectors. */
art: "photo" | "drawn";
/** Standing on lone grabbable gold at end of turn: pick it up first. */
autoGrab: boolean;
/** Spell flourishes (the animated effects layer). */
flourishes: boolean;
/** Pre-filled wizard name for creating and joining games. */
wizardName: string;
/** Preferred wizard color (0-5), claimed in lobbies when free. */
color: number | null;
/** "Are you sure?" prompts before easy-to-regret plays. */
cautions: boolean;
/** Default skill for automatons seated from the lobby. */
botTier: "apprentice" | "adept" | "archmage";
/** Instant replay: notable chronicle lines offer a first-person reel. */
instantReplay: boolean;
/** The live first-person pane above the board during play. */
liveFp: boolean;
/** The hand of cards stands in a column at the board's left instead of
* lying along the bottom (wide screens only). */
handLeft: boolean;
/** The hand's side was picked by hand; until then a wide, short screen
* stands the cards beside the board on its own. */
handLeftChosen: boolean;
/** The first-turn coaching slip has been read and dismissed. */
coached: boolean;
/** The automaton tier was picked by hand (so a finished game leaves it alone). */
tierChosen: boolean;
/** Games this browser has seen through to a winner. */
finishedGames: number;
/** Keep the step light on every turn: your square and the squares a
* stride reaches glow, the rest of the maze in shadow. A browser's
* first game lights its first turn on its own. */
stepLightAlways: boolean;
}
const KEY = "wizwar-prefs";
function load(): Prefs {
const fallback: Prefs = {
art: "photo", autoGrab: false, flourishes: true, wizardName: "", color: null,
cautions: true, botTier: "apprentice", instantReplay: true, liveFp: false, handLeft: false, handLeftChosen: false,
coached: false, tierChosen: false, finishedGames: 0, stepLightAlways: false,
};
try {
const raw = localStorage.getItem(KEY);
if (!raw) return fallback;
const p = JSON.parse(raw) as Partial<Prefs>;
return {
art: p.art === "drawn" ? "drawn" : "photo",
autoGrab: p.autoGrab === true,
flourishes: p.flourishes !== false,
wizardName: typeof p.wizardName === "string" ? p.wizardName.slice(0, 20) : "",
color: typeof p.color === "number" && p.color >= 0 && p.color <= 5 ? p.color : null,
cautions: p.cautions !== false,
botTier: p.botTier === "adept" || p.botTier === "archmage" ? p.botTier : "apprentice",
instantReplay: p.instantReplay !== false,
liveFp: p.liveFp === true,
handLeft: p.handLeft === true,
handLeftChosen: p.handLeftChosen === true || p.handLeft === true,
coached: p.coached === true,
tierChosen: p.tierChosen === true,
finishedGames: typeof p.finishedGames === "number" ? p.finishedGames : 0,
stepLightAlways: p.stepLightAlways === true,
};
} catch {
return fallback;
}
}
export const prefs = $state<Prefs>(load());
export function savePrefs(): void {
try {
localStorage.setItem(KEY, JSON.stringify(prefs));
} catch {
// A full or blocked localStorage loses persistence, not the session.
}
}