The unofficial eighth member of the Straphanger / Stand Clear / nyc-conductor / Subway Rat / Platform Pigeon / Subway Cockroach platform: a sooty black cat sitting on its haunches, front paw tucked, tail wrapped around toward the front — the one who’s seen everything and clocks out at the end of the shift like everyone else. Two animation loops — an idle (still body, a slow tail-tip lift, blink) and a grooming wash-up (four-frame paw raise: chest, cheek, ear, back down) — plus amber eyes and tiny pink toe beans, because even a soot-black transit cat gets one bright detail.
Reuse
export/cat-sheet.png— 192×22 strip, six 32×22 frames in order:idle-0,idle-1,groom-0,groom-1,groom-2,groom-3.cat-{idle,groom}-*.png— the individual frames.cat@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, groom at 6 fps (ANIMATIONS in the module carries
both). The cat is drawn near-black — pick a mid-to-light background behind
it (the demo uses a warm platform gray) or it disappears the way it would
on an actual subway platform at 2am.
Source
No .aseprite — the canonical source is source/platform-cat.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 up to three layers composited on the 32×22 canvas, back to
front: TAIL (a full-width layer, fixed anchor row — the rump silhouette
already covers everything above it, so the tail only needs to fill the open
gap underneath: drop below the body, sweep right toward the front paws,
curl the tip up into the pocket between rump and paw), BODY (the sitting
silhouette — head, ears, torso, tucked front leg and paw, all baked into
one static grid, drawn at the frame’s bob offset — the only layer that
moves vertically), then PAW (a small local grid placed at a per-frame
{left, top}, present only in groom frames — the raised mitt that sells
the wash-up, same “small piece travels alone” trick platform-pigeon uses
for its head-bob strut). Idle frames carry no paw entry at all — that
absence is “not grooming right now,” not a fourth empty-pattern lookup.
Gotchas
validateGrids()throws if any row isn’t exactly the right width, a frame references a missing tail pattern or eye color, or apawoffset would push the mitt off the canvas — call it after editing, or just runrender.mjs, which calls it first.demo/platform-cat.jsis generated. Editsource/platform-cat.mjsand re-render.- The tail lives entirely in the gap between the rump and the front paw at bottom of frame — route any redesign through that same pocket, or the body silhouette (drawn after the tail) will just paint over it.
- The demo draws the sprite from the character maps at runtime, fine at
this size. A game should blit
cat-sheet.pnginstead.
Port notes
paintFrame is ~25 lines of grid walking with no DOM dependency beyond a
2D context — same shape as platform-pigeon’s (fixed body + a small
traveling local grid) — so a DragonRuby port translates directly: keep the
grids as strings, map cells to args.outputs.solids, or just consume
cat-sheet.png as a spritesheet with source_x = frame * 32.