A small owl working the graveyard shift at a terminal, with the screen doing half the storytelling: a mostly-empty buffer and a slow blink at idle; widened eyes, puffed-up feathers, and a scrolling screen as the buffer fills; and — once the gauge tops out — a hard squint through one whiteout frame as it compacts back down to empty. Built by, and about, exactly this kind of unattended overnight run: an agent’s context window filling up and getting summarized away mid-task, which is the same thing happening to this very routine as it works.
Reuse
export/owl-sheet.png— 238×24 strip, seven 34×24 frames in order:idle-0,idle-1,filling-0,filling-1,filling-2,flush-0,flush-1.owl-{idle,filling,flush}-*.png— the individual frames.owl@8x.png— prescaled 8× hero shot for docs/previews.
Render at integer scales with nearest-neighbor filtering or the edges
smear. idle plays at 1.5 fps, filling at 4 fps, flush at 1 fps
(ANIMATIONS in the module carries all three).
Source
No .aseprite — the canonical source is source/night-shift.mjs: a
framework-free ES module. Edit the numbers and regenerate everything with:
node source/render.mjs
(needs site/node_modules installed — the script resolves Playwright from
there.) That one command rewrites export/, media/, thumb.png, and
the demo’s bundled copy of the module, so nothing drifts.
How the frames are built
Unlike cat-stretch’s hand-typed character grid, the owl’s round body,
feather ruffle, and eyes are rasterized from an ellipse formula and a
handful of per-frame numbers — the same “posture reads a metric” trick as
warehouse-duck’s waterline and ledger-scribe’s tally columns, just
applied to the sprite’s own silhouette instead of a prop next to it. A
fixed set of ruffle-spike offsets gets added along the top arc, scaled by
each frame’s ruffle value, to read as puffed feathers without a second
body grid. The terminal beside it is drawn the same way: lines sets how
many glowing rows are stacked on the screen, gauge is a literal
pixel-width fill bar, and flash swaps the whole screen to a single
whiteout color for the compaction frame.
Gotchas
validateGrids()only checks thatFRAME_ORDER/ANIMATIONSreference real frames — there’s no character grid to catch a ragged row, since there isn’t one. Sanity-check new frames by rendering and looking.demo/night-shift.jsis generated. Editsource/night-shift.mjsand re-render.- The ruffle spikes are placed by a fixed
dxlist against the ellipse’s current top arc, so ifOWL_RYorOWL_RXchanges, recheck that the spikes still land on the silhouette rather than floating off it. - The demo draws the sprite by calling
paintFrameat runtime, fine at this size. A game should blitowl-sheet.pnginstead.
Port notes
paintFrame is arithmetic plus fillRect calls with no DOM dependency
beyond a 2D context — same shape as warehouse-duck’s — so a DragonRuby
port translates directly: reimplement the ellipse/gauge math against
args.outputs.solids, or just consume owl-sheet.png as a spritesheet
with source_x = frame * 34.