BUGS
bugs/restless-thumbnails/record.md
sha256 4af71fd9110134e0 · 3033 bytes ·
original held in the private archive
---
title: The Restless Thumbnails
status: SEALED
repo: archive
observed: 2026-07-20
caught_by: git status dirty after a supposed no-op regeneration
one_line: Cold-rendered prototype thumbnails drifted a few bytes every run — and the first diagnosis blamed the wrong thing.
guard_location: museum/generate.mjs · screenshotPrototypes() · line ~1501
verified: fresh clone AND bare-derived clone both report "28 total, 28 cached, 0 to render", git status clean
exhibited: true
lesson: A wrong diagnosis (encoder jitter) would have "fixed" the innocent part; render once per content-hash and the whole class of drift becomes moot without solving it.
provenance:
- commit 43390e6 (this archive) — folded into the path-based-naming session
- Claude memory record museum-thumb-render-nondeterminism
---
## What happened
Cold-rendering the 28 Playwright prototype thumbnails produced **a different 11–13 of
them** with a few dozen-to-hundred differing bytes each — a *different* subset every run.
The first diagnosis, "PNG re-encode jitter," was **wrong**, and the correction is the
lesson: 27 of the 28 prototypes carry infinite CSS animations (`coreSpin 9s linear
infinite`, `corePulse 2.6s ease-in-out infinite`, a pulsing status lamp), and the capture
waited a flat 600 ms after load with no synchronisation to animation phase. Ordinary
process-scheduling jitter landed each screenshot at a different rotation angle / glow
intensity — **real pixel drift**, not lossless-encoder nondeterminism on identical
pixels. The other candidate fix the earlier pass had named, "force deterministic PNG
encoding," would not have touched the actual cause at all.
## How it was caught
`git status` showed modified PNGs after a regeneration that should have been a no-op, run
from a clean clone.
## The guard
`screenshotPrototypes()` now checks the already-committed
`museum/site/assets/thumbs/<sha>.png` (the previous successful build's tracked output)
**before** the local `.cache/thumbs` and before ever launching Playwright. A clone
regenerating an unchanged manifest finds every thumbnail already present and re-renders
nothing — so a prototype is rendered **once per content-hash, ever**, and the opportunity
for drift simply never recurs. The animation-timing question is left unsolved on purpose;
the guard removes the occasion for it rather than the cause.
## What the guard costs
An unchanged prototype can **never** get a fresh screenshot. If a thumbnail ever looks
stale, the only way to re-shoot it is to change the prototype's actual bytes.
## Containment
Fresh GitHub clone and bare-clone-derived clone (both with an empty gitignored cache)
each reported `prototypes: 28 total, 28 cached, 0 to render`, `git status` clean, stamp
`80bf9bb · f64fb7397980c9cb` matching the local run and each other.
## Provenance
Commit `43390e6` (folded into the path-based-naming + redirect-ledger session). Full
account, including the corrected diagnosis, in the Claude memory record
`museum-thumb-render-nondeterminism`.
STAMP · generated for RELEASE v2.8.5 commit 06e5180 (06e51801b38a) · archive input-tree hash c07fbfbdd2e1ddeb · 754 files · no wall-clock timestamp (regenerates identically when nothing changed).