The unofficial sixth member of the Straphanger / Stand Clear / nyc-conductor / Subway Rat platform: a rock pigeon, dark head fading into an iridescent green-to-violet neck, light gray body with two black wing bars, orange-red eye and legs. Two animation loops — an idle sit (gentle bob, an eye that blinks shut on the second frame) and a strut (four-frame walk with the head snapping forward on the plant beat and pulling back on the lift beat — the actual mechanism behind a pigeon’s head-bob, not just a vertical wobble).
Reuse
export/pigeon-sheet.png— 180×20 strip, six 30×20 frames in order:idle-0,idle-1,strut-0,strut-1,strut-2,strut-3.pigeon-{idle,strut}-*.png— the individual frames.pigeon@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, strut at 6 fps (ANIMATIONS in the module carries both).
Source
No .aseprite — the canonical source is source/platform-pigeon.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 composites three layers, back to front: BODY (torso, tail, wing
bars and neck markings — all baked into one static grid, since none of it
moves), LEGS (a full-width layer, three foot-placement patterns for the
walk), and HEAD — a small local grid placed at a per-frame (headX, headY) offset instead of a fixed anchor. That’s the actual mechanism behind
the strut: the head isn’t just bobbing vertically, it’s traveling forward on
the plant beat and pulling back on the lift beat, same as a real pigeon
using its head as a fixed visual reference while the body moves underneath
it. Six frames come from one body grid, three leg patterns, and two head
positions — not six full redraws.
Gotchas
validateGrids()throws if any row isn’t the expected width, a frame references a missing leg pattern, or a head offset would draw outside the frame — call it after editing, or just runrender.mjs, which calls it first.demo/platform-pigeon.jsis generated. Editsource/platform-pigeon.mjsand re-render.- The demo draws the sprite from the character maps at runtime, fine at this
size. A game should blit
pigeon-sheet.pnginstead.
Port notes
paintFrame is grid-walking with no DOM dependency beyond a 2D context —
same shape as subway-rat’s and scout-drone’s — so a DragonRuby port
translates directly: keep the grids as strings, map cells to
args.outputs.solids, or just consume pigeon-sheet.png as a spritesheet
with source_x = frame * 30.