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

+13 -21
View File
@@ -5,10 +5,8 @@
// replay of the moves, so the chronicle is a function of the game and
// never part of the ledger: rewording it changes no rules.
import {
type Cell, type Kind, type Side, CELLS, PER_LEVEL, NAMES, frozen, kindOf, levelOf, opening, other, playOn, rankOf, sideOf, squareName
} from './board';
import type { Move, State } from './index';
import { type Cell, type Kind, type Side, PER_LEVEL, NAMES, frozen, kindOf, levelOf, opening, other, playOn, rankOf, sideOf, squareName } from './board';
import { playerName, type Move, type State } from './index';
export type LineKind = 'move' | 'capture' | 'check' | 'freeze' | 'promotion' | 'end' | 'note';
@@ -48,7 +46,6 @@ function crossing(kind: Kind, from: number, to: number): string {
/** How a victim is told. */
function falls(victim: Kind, owner: string, where: string): string {
switch (victim) {
case 'K': return `${owner} King is taken`;
case 'R': return `${owner} Dragon falls from the sky at ${where}`;
case 'B': return `${owner} Basilisk is crushed at ${where}, and its gaze is broken`;
case 'S': return `${owner} Sylph is scattered at ${where}`;
@@ -57,8 +54,6 @@ function falls(victim: Kind, owner: string, where: string): string {
}
}
const possessive = (name: string) => `${name}’s`;
/** The pieces on the land held frozen, by square. */
function frozenSquares(cells: Cell[]): Set<number> {
const held = new Set<number>();
@@ -66,10 +61,13 @@ function frozenSquares(cells: Cell[]): Set<number> {
return held;
}
/** Whose a piece is, as a possessive: "Aud’s", "Scarlet’s", or "Your" at a lesson. */
export type Owner = (side: Side) => string;
/** Tell one move from the position it was made in. */
function tellOne(before: Cell[], move: Move, ply: number, name: (side: Side) => string): { lines: Line[]; after: Cell[] } {
const who = possessive(name(move.by));
const foe = possessive(name(other(move.by)));
function tellOne(before: Cell[], move: Move, owner: Owner): { lines: Line[]; after: Cell[] } {
const who = owner(move.by);
const foe = owner(other(move.by));
const piece = NAMES[move.kind];
const from = squareName(move.from);
const to = squareName(move.to);
@@ -95,7 +93,7 @@ function tellOne(before: Cell[], move: Move, ply: number, name: (side: Side) =>
const heldBefore = frozenSquares(before);
const heldAfter = frozenSquares(after);
for (const i of heldAfter) {
if (heldBefore.has(i) && !(move.afar === false && i === move.to)) continue;
if (heldBefore.has(i) && (move.afar || i !== move.to)) continue;
const owner = sideOf(after[i]) === move.by ? who : foe;
lines.push({ kind: 'freeze', text: `The Basilisk’s gaze falls on ${owner} ${NAMES[kindOf(after[i])]} at ${squareName(i)}, which cannot stir.` });
}
@@ -118,20 +116,16 @@ function tellOne(before: Cell[], move: Move, ply: number, name: (side: Side) =>
}
if (move.check) lines.push({ kind: 'check', text: `${foe} King is in check.` });
void ply;
return { lines, after };
}
/** The whole game told, one entry a move, from a replay of the moves. A game begun from the opening needs no `start`. */
export function narrate(state: State, start?: Cell[]): Telling[] {
const name = (side: Side) => {
const seat = state.seats.find((id) => state.players[id].side === side);
return seat ? state.players[seat].name : side === 'gold' ? 'Gold' : 'Scarlet';
};
const owner: Owner = (side) => `${playerName(state, side)}’s`;
let cells = start ?? opening(state.setup);
const out: Telling[] = [];
state.moves.forEach((move, n) => {
const { lines, after } = tellOne(cells, move, n + 1, name);
const { lines, after } = tellOne(cells, move, owner);
cells = after;
out.push({ ply: n + 1, move, lines });
});
@@ -140,8 +134,6 @@ export function narrate(state: State, start?: Cell[]): Telling[] {
}
/** One move told on its own, from the position it was made in. */
export function tell(before: Cell[], move: Move, name: (side: Side) => string): string {
return tellOne(before, move, 0, name).lines.map((l) => l.text).join(' ');
export function tell(before: Cell[], move: Move, owner: Owner): string {
return tellOne(before, move, owner).lines.map((l) => l.text).join(' ');
}
export { CELLS };