Credibility pass: the code reads as one hand's work. Dead plumbing and copies removed, four claims the source does not support corrected, and inherited kit text re-read for this game

Engine: the facade re-exports the board in one block; describe() and the
dead letters preference, void ply, the King-taken line, the identity maps
and the unused imports are gone; SIDE_NAMES is the one spelling of a side;
the drake's hunter comment no longer claims only four kinds reach the air.
Prose: no shatranj and no sitting duck, which Gygax never wrote; the Thief
statistic and the Basilisk's "one trick" are gone; lessons say what the
board shows instead of repeating the panel; a Thief's leap counts four.
Browser: one keyboard walk, in BoardArtwork; the lesson board and page share
one game and one inspected piece; the chronicle's possessive is told, not
patched by a regex; the chronicle colours and the reading layout are global
rules instead of three copies; the guide and the about panel say what the
code and the article actually do.
Server and docs: the room server states that it is the kit's, kept whole;
the skills, the deploy README and the share card name files that exist.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CCLa2PaEeCj8njQZSWGqHx
This commit is contained in:
Eric WagonerandClaude Fable 5.1 committed 2026-10-26 22:38:00 -04:00
1 parent 944e4e8306
commit 0be89a1dfe
28 files changed
+227 -340

No files matched your search

+1 -1
View File
@@ -31,7 +31,7 @@
<h2>The hall</h2>
<figure><img src="/guide/hall.png" alt="The hall, with a name field, the two table choices, the button to play the drake, and the opening position drawn beside them" loading="lazy" /></figure>
<p>Write your name and choose your company. <strong>Play now against a drake</strong> seats you across from the house player and begins at once. <strong>Open a table</strong> lays a table and hands you a link and a four-letter code to send to a friend. <strong>Join one</strong> takes a code someone sent you.</p>
<p>Two choices sit above the buttons. <em>You play</em> picks your side, and Gold moves first. <em>The Sylphs</em> picks where the six Sylphs start. As Gygax printed it, Gold's Dragon can take an undefended Sylph on the very first move, which a reader of the Chess Variant Pages took for a misprint. The shifted setup moves the Sylphs one file over, out of reach. Beneath the buttons, <em>your games</em> lists every table this browser holds a seat at and whose move it is.</p>
<p>Two choices sit above the buttons. <em>You play</em> picks your side, and Gold moves first. <em>The Sylphs</em> picks where the six Sylphs start. As Gygax printed it, Gold's Dragon can take an undefended Sylph on the very first move, which a reader of the Chess Variant Pages took for a misprint. The shifted setup moves the Sylphs one file over, out of reach. <em>Your games</em>, which comes first once you have one, lists every table this browser holds a seat at and whose move it is.</p>
</section>
<section id="table">
+7 -30
View File
@@ -6,8 +6,7 @@
import PiecePanel from '$lib/components/PiecePanel.svelte';
import { SCENES, sceneCells } from '$lib/game/lessons';
import { tell } from '$lib/game/chronicle';
import { createGame, playOn, square, squareName, type Cell, type Move } from '$lib/game';
import { kindOf, inCheck, hasLegalMove, other } from '$lib/game/board';
import { createGame, playOn, square, kindOf, inCheck, hasLegalMove, other, SIDE_NAMES, type Cell, type Move, type Side } from '$lib/game';
const room = $derived((page.url.searchParams.get('room') ?? '').toUpperCase());
const withRoom = (path: string) => (room ? `${path}?room=${room}` : path);
@@ -31,17 +30,17 @@
const right = scene.moves.some(([a, b]) => square(a) === from && square(b) === to);
const played = playOn(cells, from, to);
const move: Move = { by: scene.side, from, to, kind: kindOf(cells[from]), captured: played.captured, afar: played.afar, promoted: played.promoted, check: inCheck(played.cells, other(scene.side)) };
const name = (s: typeof scene.side) => (s === scene.side ? 'You' : 'Scarlet');
let told = tell(cells, move, name).replace(/You’s/g, 'Your');
const owner = (s: Side) => (s === scene.side ? 'Your' : `${SIDE_NAMES[s]}’s`);
let told = tell(cells, move, owner);
const mated = move.check && !hasLegalMove(played.cells, other(scene.side), 'printed');
if (mated) told += ' Checkmate.';
outcome = { told, text: right ? scene.after : (scene.aside ?? scene.after), right };
cells = played.cells;
}
/** The panel reads a game; this one is the scene's. */
/** The board and the panel read a game; this one is the scene's, with the reader on the scene's side. */
const g = $derived.by(() => {
const s = createGame({ A: 'You', B: 'Scarlet' });
const s = createGame({ A: scene.side === 'gold' ? 'You' : SIDE_NAMES.gold, B: scene.side === 'scarlet' ? 'You' : SIDE_NAMES.scarlet });
s.cells = cells;
s.toMove = scene.side;
return s;
@@ -52,7 +51,7 @@
<title>Wyrm: learn the pieces</title>
</svelte:head>
<main class="lesson">
<main class="reading wide">
<header>
{#if room}<a class="back" href={`/join/${room}`}>Back to your game</a>{' · '}{/if}<a class="back" href="/">Back to the hall</a> · <a class="back" href={withRoom('/guide')}>How to play</a> · <a class="back" href={withRoom('/rules')}>The rules</a>
<h1>Learn the pieces</h1>
@@ -67,7 +66,7 @@
<section class="stage">
<div class="boards">
<LessonBoard {cells} side={scene.side} levels={scene.levels} locked={outcome !== null} onMove={moved} onInspect={(at) => (inspected = at)} />
<LessonBoard {g} levels={scene.levels} locked={outcome !== null} onMove={moved} bind:inspected />
</div>
<div class="words">
<p class="eyebrow">Scene {n + 1} of {SCENES.length}</p>
@@ -97,28 +96,6 @@
</main>
<style>
.lesson {
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem var(--gutter) 4rem;
}
.lesson a.back {
color: var(--frost);
font-size: 0.95rem;
}
h1 {
font-size: 2.4rem;
font-weight: 300;
margin: 0.5rem 0 0.3rem;
}
.lede {
color: var(--bone-dim);
max-width: 44em;
}
.scenes {
display: flex;
flex-wrap: wrap;
+5 -59
View File
@@ -5,7 +5,7 @@
import PiecePanel from '$lib/components/PiecePanel.svelte';
import { CALLOUTS, TOLD_MOVES, toldStates } from '$lib/game/told';
import { narrate } from '$lib/game/chronicle';
import { notation, kingSquare, inCheck, movesFrom, legalMovesFrom, levelOf, fileOf, rankOf, idx, FILES, RANKS, LEVELS } from '$lib/game';
import { notation, kingSquare, inCheck, movesFrom, legalMovesFrom, sideOf } from '$lib/game';
import { prefs, reducedMotion } from '$lib/prefs.svelte';
const states = toldStates();
@@ -27,8 +27,7 @@
let inspected = $state<number | null>(null);
const reach = $derived.by(() => {
if (inspected === null || g.cells[inspected] === '' || g.over) return [];
const side = g.cells[inspected][0] === 'g' ? 'gold' : 'scarlet';
return side === g.toMove ? legalMovesFrom(g.cells, inspected, g.setup) : movesFrom(g.cells, inspected, g.setup);
return sideOf(g.cells[inspected]) === g.toMove ? legalMovesFrom(g.cells, inspected, g.setup) : movesFrom(g.cells, inspected, g.setup);
});
function go(n: number) {
@@ -38,21 +37,6 @@
function tap(i: number) {
inspected = g.cells[i] !== '' && inspected !== i ? i : null;
}
function squareKey(e: KeyboardEvent, i: number) {
const step: Record<string, [number, number, number]> = { ArrowLeft: [-1, 0, 0], ArrowRight: [1, 0, 0], ArrowUp: [0, 1, 0], ArrowDown: [0, -1, 0], PageUp: [0, 0, 1], PageDown: [0, 0, -1] };
if (e.key in step) {
e.preventDefault();
e.stopPropagation();
const [df, dr, dl] = step[e.key];
const nf = fileOf(i) + df;
const nr = rankOf(i) + dr;
const nl = levelOf(i) + dl;
if (nf >= 0 && nf < FILES && nr >= 0 && nr < RANKS && nl >= 1 && nl <= LEVELS) document.querySelector<SVGRectElement>(`rect.square[data-i="${idx(nl, nf, nr)}"]`)?.focus();
} else if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
tap(i);
} else if (e.key === 'Escape') inspected = null;
}
/** Left and right step the game, unless the keyboard is on a square, where the arrows walk the board. */
function key(e: KeyboardEvent) {
if ((e.target as HTMLElement | null)?.tagName === 'rect') return;
@@ -69,7 +53,7 @@
<svelte:window onkeydown={key} />
<main class="lesson">
<main class="reading wide">
<header>
<a class="back" href="/">Back to the hall</a> · <a class="back" href="/learn">Learn the pieces</a> · <a class="back" href="/guide">How to play</a> · <a class="back" href="/rules">The rules</a>
<h1>A game told</h1>
@@ -78,12 +62,12 @@
<section class="stage">
<div class="boards">
<BoardArtwork {g} {showCoords} {shownSquares} {shownCaptured} {checkSquare} selected={inspected} {reach} {calm} yourMove={true} {tap} key={squareKey} />
<BoardArtwork {g} {showCoords} {shownSquares} {shownCaptured} {checkSquare} selected={inspected} {reach} {calm} {tap} escape={() => (inspected = null)} />
</div>
<div class="words">
<p class="eyebrow">{ply === 0 ? 'Before the first move' : `Move ${ply} of ${TOLD_MOVES.length}`}{#if move}&nbsp;· {notation(move)}{/if}</p>
{#if telling}
<ul class="told">
<ul class="told telling">
{#each telling.lines as l, i (i)}<li class={l.kind}>{l.text}</li>{/each}
</ul>
{/if}
@@ -107,26 +91,7 @@
</main>
<style>
.lesson {
max-width: 1280px;
margin: 0 auto;
padding: 1.5rem var(--gutter) 4rem;
}
.lesson a.back {
color: var(--frost);
font-size: 0.95rem;
}
h1 {
font-size: 2.4rem;
font-weight: 300;
margin: 0.5rem 0 0.3rem;
}
.lede {
color: var(--bone-dim);
max-width: 44em;
margin-bottom: 1.2rem;
}
@@ -167,25 +132,6 @@
font-size: 0.95rem;
}
.told li.capture {
color: var(--ember);
}
.told li.check,
.told li.freeze,
.told li.promotion {
color: var(--frost);
}
.told li.note {
color: var(--bone-dim);
font-style: italic;
}
.told li.end {
color: var(--blood);
}
.callout {
margin-top: 0.8rem;
color: var(--bone);