A small saucer-hulled scout drone: antenna, twin stub fins, a ringed lens for an eye, and a hover-light bar underneath that keeps it grounded while the hull bobs above it. Two animation loops — an idle hover (amber lens, gentle bob) and a sensor scan (lens flips cyan, the light bar sweeps left-right-left).
Reuse
export/scout-sheet.png— 126×14 strip, six 21×14 frames in order:idle-0,idle-1,scan-0,scan-1,scan-2,scan-3.scout-{idle,scan}-*.png— the individual frames.scout@8x.png— prescaled 8× hero shot for docs/previews.
Render at integer scales with nearest-neighbor filtering or the edges smear.
Idle plays at 2 fps, scan at 6 fps (ANIMATIONS in the module carries both).
Source
No .aseprite — the canonical source is source/scout-drone.mjs: a
framework-free ES module holding the pixel grids as character maps (one char
per pixel, palette at the top). Edit a 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
A frame is two layers composited on the 21×14 canvas: the GLOW bar (drawn
first, anchored at a fixed GLOW_Y — the hover-light stays on the ground),
then the DOME drawn over it at the frame’s bob offset. The lens ring shape
never moves — only its core color (E cells, remapped per frame via
frame.core) and the glow bar’s bright/dim pattern (frame.glow) change, so
six frames come from one hull grid and four small glow-bar variants instead of
six full redraws.
Gotchas
validateGrids()throws if any row isn’t exactly 21 chars, or a frame references a missing glow pattern or core color — call it after editing, or just runrender.mjs, which calls it first.demo/scout-drone.jsis generated. Editsource/scout-drone.mjsand re-render.- The demo draws the sprite from the character maps at runtime, fine at this
size. A game should blit
scout-sheet.pnginstead.
Port notes
paintFrame is ~20 lines of grid walking with no DOM dependency beyond a 2D
context — same shape as mech-strider’s — so a DragonRuby port translates
directly: keep the grids as strings, map cells to args.outputs.solids, or
just consume scout-sheet.png as a spritesheet with source_x = frame * 21.