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
+9 -7
View File
@@ -86,8 +86,10 @@ function fileFor(roomId: string): string {
return join(DATA_DIR, `${roomId}.jsonl`);
}
/** Directory holding stats.json — the parent of the rooms dir. */
export function statsDir(): string {
/** The data root — the parent of the rooms dir — where everything that
* is not a room ledger lives: stats, feedback, stubs, the graveyard, the
* clip vault. */
export function dataRoot(): string {
return join(DATA_DIR, "..");
}
@@ -149,7 +151,7 @@ export function ledgerStat(roomId: string): { bytes: number; mtimeMs: number } |
// ledger only ever grows, so at boot a stub whose size still matches is
// the room's whole truth and the room stays asleep, unreplayed.
const stubDir = () => join(DATA_DIR, "..", "stubs");
const stubDir = () => join(dataRoot(), "stubs");
function stubFor(roomId: string): string {
if (!safeRoomId(roomId)) throw new Error(`unsafe room id: ${JSON.stringify(roomId)}`);
return join(stubDir(), `${roomId}.json`);
@@ -168,7 +170,7 @@ export function readStubFile<T>(roomId: string): { bytes: number; stub: T } | nu
}
}
export function writeStubFile(roomId: string, bytes: number, stub: unknown): void {
export function writeStubFile<T>(roomId: string, bytes: number, stub: T): void {
const file = stubFor(roomId);
mkdirSync(stubDir(), { recursive: true });
writeFileSync(file + ".tmp", JSON.stringify({ bytes, stub }), "utf8");
@@ -182,7 +184,7 @@ export function writeStubFile(roomId: string, bytes: number, stub: unknown): voi
* timestamp. A report line may carry fields (deckRev, player context)
* that only the operator's raw read uses; readFeedback keeps the
* player-facing subset. */
const feedbackFile = () => join(DATA_DIR, "..", "feedback.jsonl");
const feedbackFile = () => join(dataRoot(), "feedback.jsonl");
export function appendFeedback(entry: Record<string, unknown>): void {
ensureDataDir();
@@ -239,7 +241,7 @@ export function readFeedback(): FeedbackReport[] {
export function archiveRoomFile(roomId: string): void {
const src = fileFor(roomId);
if (!existsSync(src)) return;
const graveyard = join(DATA_DIR, "..", "rooms-abandoned");
const graveyard = join(dataRoot(), "rooms-abandoned");
mkdirSync(graveyard, { recursive: true });
renameSync(src, join(graveyard, `${roomId}.${Date.now()}.jsonl`));
}
@@ -260,7 +262,7 @@ export interface ClipMeta {
height?: number;
}
const clipsDir = () => join(DATA_DIR, "..", "clips");
const clipsDir = () => join(dataRoot(), "clips");
/** A clip's name, which is also its file stem: no separators, no dots,
* so a name can never name a path. */