The unofficial tenth member of the Straphanger / Stand Clear / nyc-conductor / Subway Rat / Platform Pigeon / Subway Cockroach / Platform Cat / Subway Raccoon platform: the one that only shows up on the elevated stretches near the water (Coney Island, the Rockaways) — a white-headed, gray-backed gull with an orange beak and legs, planted on the platform edge. Unlike the rest of the crew this one doesn’t walk: two animation loops — an idle sit (gentle bob, an eye that blinks shut on the second frame) and a wing stretch (four-frame flap: wing sweeps down and back, levels out wide, throws all the way up over the head, then levels back down), the thing gulls actually do while standing still rather than a generic flying loop.
Reuse
export/seagull-sheet.png— 192×24 strip, six 32×24 frames in order:idle-0,idle-1,flap-0,flap-1,flap-2,flap-3.seagull-{idle,flap}-*.png— the individual frames.seagull@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, flap at 8 fps (ANIMATIONS in the module carries both).
Source
No .aseprite — the canonical source is source/subway-seagull.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 four layers composited on the 32×24 canvas, back to front: TAIL
(fixed anchor, tucks under the rump), LEGS (fixed anchor, planted — this
one stands rather than walks, so there’s a single stand pattern instead of
a gait), BODY (head, beak and torso, at the frame’s bob offset), then
WING on top so it reads over the back when folded and over everything when
raised. The wing poses aren’t all the same size — folded is a compact
7-row drape, up is a 12-row spread that reaches above the head — so instead
of one shared anchor like TAIL/LEGS, each frame carries its own
wingTop placing that pose’s grid. Getting this wrong is exactly the
failure mode to watch for: an early draft put the mid pose’s wingTop too
close to the head and the wide top edge of that grid blanketed the whole
face — the fix was dropping wingTop back down near the shoulder line
(14, the same row folded and down use) rather than trying to slide the
wing upward to suggest motion.
Gotchas
validateGrids()throws if any row isn’t exactly 32 (24 forWING, 11 forTAIL) chars, a frame references a missing wing/tail pattern or eye color, or a frame’swingTopwould push that pose’s grid past the bottom of the canvas — call it after editing, or just runrender.mjs, which calls it first.demo/subway-seagull.jsis generated. Editsource/subway-seagull.mjsand re-render.- If you widen a
WINGpose, check where its top-left rows land relative toBODY’s head (rows 0–4 at bob 0) before picking awingTop— a pose wide enough to reach past local column ~18 will cover the eye if placed higher than the shoulder row. - The demo draws the sprite from the character maps at runtime, fine at this
size. A game should blit
seagull-sheet.pnginstead.
Port notes
paintFrame is grid-walking with no DOM dependency beyond a 2D context —
same shape as the rest of the platform crew’s — so a DragonRuby port
translates directly: keep the grids as strings, map cells to
args.outputs.solids, or just consume seagull-sheet.png as a spritesheet
with source_x = frame * 32. The one wrinkle versus subway-raccoon/
subway-rat is the per-frame wingTop instead of a fixed anchor — carry
that value over rather than assuming every layer shares one offset.