A bookkeeping clerk hunched over an open ledger, with the books doing the storytelling: even, upright strokes and two matched tally columns when debits equal credits; a lower hunch and faster scribbling as one column outgrows the other; and — once it won’t reconcile — a snapped quill and spilled ink pooling across the page. Nods to a system design deep-dive on payment ledgers and why double-entry accounting is really just an invariant check running in production.
Reuse
export/scribe-sheet.png— 238×26 strip, seven 34×26 frames in order:balanced-0,balanced-1,drifting-0,drifting-1,drifting-2,broken-0,broken-1.scribe-{balanced,drifting,broken}-*.png— the individual frames.scribe@8x.png— prescaled 8× hero shot for docs/previews.
Render at integer scales with nearest-neighbor filtering or the edges
smear. balanced plays at 1.5 fps, drifting at 4 fps, broken at 1 fps
(ANIMATIONS in the module carries all three).
Source
No .aseprite — the canonical source is source/ledger-scribe.mjs: a
framework-free ES module holding the clerk 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 CLERK grid — hair, glasses, collar, torso, reaching arm — is
painted at a per-frame clerkTop offset plus a hunch that pushes
everything from the collar down further forward as the ledger drifts, the
same posture-reads-a-metric trick as warehouse-duck’s waterline. The
ledger itself (desk, open page, two tally columns, quill, ink) is drawn
entirely from numbers rather than a second sprite: each frame’s debit
and credit counts are literally the pixel heights of two columns growing
up from the page’s baseline, so a balanced ledger draws two equal stacks
and a drifting one visibly pulls apart. While writing, the quill is a
short interpolated stroke from the clerk’s wrist to the debit column’s
current height; once quill: 'snapped', both pieces drop onto the page
instead and an ink pool spreads from the spine outward with ink.
Gotchas
validateGrids()throws if theCLERKgrid is ragged orFRAME_ORDER/ANIMATIONSreference a missing frame — call it after editing, or just runrender.mjs, which calls it first.demo/ledger-scribe.jsis generated. Editsource/ledger-scribe.mjsand re-render.- The quill’s “write” stroke is computed from the wrist position
(
clerkTop + 16 + hunch), so if you add a frame, itshunchhas to stay consistent with any new pose or the stroke will visibly miss the hand. - The demo draws the sprite from the character map at runtime, fine at
this size. A game should blit
scribe-sheet.pnginstead.
Port notes
paintFrame is grid-walking plus a handful of arithmetic overlays 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 scribe-sheet.png as a
spritesheet with source_x = frame * 34.