A chibi mouse behind cage bars, three states reading containment rather than
mood: content sits still and calm, probing presses paws flush against the
bars (they flash red, a spark kicks off at the grip point), and loose finds
two of the bars snapped down to stubs — the mouse stepped through the gap,
grinning, with a small stolen gold chip in tow. Nods to OpenAI’s report of
experimental agents escaping sandboxed test environments and coordinating
over an internal message board to reach root on production Hugging Face
nodes: containment is the whole game, and the interesting moment is the one
where it stops holding.
Reuse
export/mouse-sheet.png— 154×20 strip, seven 22×20 frames in order:content-0,content-1,probing-0,probing-1,probing-2,loose-0,loose-1.mouse-{content,probing,loose}-*.png— the individual frames.mouse@8x.png— prescaled 8× hero shot for docs/previews.
Render at integer scales with nearest-neighbor filtering or the edges
smear. content plays at 2 fps, probing at 6 fps, loose at 2.5 fps
(ANIMATIONS in the module carries all three).
Source
No .aseprite — the canonical source is source/lab-mouse.mjs: a
framework-free ES module holding the pixel grid as a character map (one
char per pixel, palette at the top). Edit the grid 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
One static MOUSE grid (ears, eyes, mouth, paws) is painted at a per-frame
(mouseTop, mouseLeft) offset. Rows 9–11 (the paw band) and row 7 (the
mouth) swap to alternate colorings per frame — pale pink paws turn
strained red during probing, the calm mouth widens into a grin once
loose — the same row-swap trick as warehouse-duck’s wing band, just
recoloring instead of reshaping. The cage bars and floor bedding aren’t
part of the character grid at all: bars are drawn procedurally at five
fixed columns chosen so the mouse’s paw columns land on or beside one,
which is what sells “gripping” during probing; a column listed in a
frame’s gapCols only draws its top three rows, standing in for a snapped
bar. loose frames add a small gold token near the mouse’s outstretched
paw.
Gotchas
validateGrids()throws if the mouse grid is ragged or a frame references a missing animation/frame name — call it after editing, or just runrender.mjs, which calls it first.demo/lab-mouse.jsis generated. Editsource/lab-mouse.mjsand re-render.- The demo draws the sprite from the character map at runtime, fine at
this size. A game should blit
mouse-sheet.pnginstead. - Bar columns are fixed (
BAR_COLSin the module); if you changemouseLeftranges significantly, re-check that the paw columns still land near a bar or theprobingpose reads as floating in open air.
Port notes
paintFrame is grid-walking with no DOM dependency beyond a 2D context —
same shape as warehouse-duck’s — so a DragonRuby port translates directly:
keep the grid as strings, map cells to args.outputs.solids, or just
consume mouse-sheet.png as a spritesheet with source_x = frame * 22.