Credibility pass: the session seams sanded from the clips-and-camera batch

Two blind reviews of everything since the last pass (afa0e17), every
finding checked against the code, no behavior changed: all thirty
scene goldens match without a re-bless and the engine suite is
untouched.

Reel and renderer: the camera's look-down rule is stated once, beside
LOOK_DOWN, instead of twice in the effect; the empty aim branch that
stood where a cutaway used to be is gone (the guard it implied is now
explicit); the pit events and the punch are handled by their own
types, not through "in" casts; smoothstep is one export used by every
tween instead of eleven inline copies; the two floor rings share one
painter; project() takes a Billboard instead of a third hand-typed
copy of its fields; the strides-left figure and the web rim no longer
shadow the reel's steps and the pane's fx; the die card's verdict is
built from events, not by matching an emoji; the workshop asks for the
hover cue by name instead of passing an empty click handler.

Server and engine: one requestBase() for the origin, one slug pattern
in store.ts gating both the clip page and its files, one 404 for both;
LOOPBACK sits above its only caller; doCounteract names what a counter
is played against once; fearCells sits beside its own docblock rather
than between sightedCellsFor and its.

Deploy: chromiumExe, the private server, ffmpeg, and the reel rewind
live in deploy/lib/harness.mjs, shared by the gate, the recorder, and
the card cutter instead of pasted three times; the recorder drops its
duplicate frame counters and names its poster settle; the card uses the
gallery's exact gold; the one-time Sentry URL bootstrap leaves
deploy.sh; the backup comment states the rule rather than the incident.

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-03 11:55:57 -04:00
co-authored by Claude Fable 5.1
parent ddd2e347bd
commit e1a740119c
20 changed files with 238 additions and 263 deletions
+10 -6
View File
@@ -235,24 +235,28 @@ export interface ClipMeta {
const clipsDir = () => join(DATA_DIR, "..", "clips");
/** A clip's name, which is also its file stem: no separators, no dots,
* so a name can never name a path. */
const SLUG = "[a-z0-9-]{1,60}";
export const CLIP_SLUG = new RegExp(`^${SLUG}$`);
const CLIP_FILE = new RegExp(`^${SLUG}(?:(?:-fpv|-board)\\.mp4|(?:-card)?\\.jpg)$`);
export function readClips(): ClipMeta[] {
const file = join(clipsDir(), "clips.json");
if (!existsSync(file)) return [];
try {
const parsed = JSON.parse(readFileSync(file, "utf8")) as ClipMeta[];
return parsed.filter((c) => /^[a-z0-9-]{1,60}$/.test(c.name));
return parsed.filter((c) => CLIP_SLUG.test(c.name));
} catch {
return [];
}
}
/** Resolve a clip asset request to its path — or null for any name that
* is not exactly a published clip file shape. The gate IS the security:
* nothing outside <slug>(-fpv|-board).mp4 / <slug>(-card)?.jpg can be named. */
* is not exactly a published clip file: <slug>-fpv.mp4, <slug>-board.mp4,
* <slug>.jpg, <slug>-card.jpg. The pattern is the only path guard. */
export function clipAssetPath(file: string): string | null {
if (!/^[a-z0-9-]{1,60}(-fpv|-board)\.mp4$/.test(file) && !/^[a-z0-9-]{1,60}(-card)?\.jpg$/.test(file)) {
return null;
}
if (!CLIP_FILE.test(file)) return null;
const path = join(clipsDir(), file);
return existsSync(path) ? path : null;
}