A dog stationed at a badge reader. calm is a single scan: ears up, steady
green lamp, one tick. alert is the same badge replayed: the lamp still
reads valid — that’s the whole problem with a stolen session credential, it
passes the check every time — but the reader’s tick counter climbs fast, the
scan-rate gauge pegs high, and the dog’s ears pin back, hackles rise, and
teeth show. Nothing here fails a badge. The dog reads the rhythm of the
scans, because rhythm is the only tell a replayed credential leaves. Nods to
a week of infostealer campaigns replaying stolen session cookies straight
past password and MFA — the tell in those incidents was usage draining in
ticks nobody was there to make, same shape as this reader’s counter.
Reuse
export/hound-sheet.png— 170×24 strip, five 34×24 frames in order:calm-0,calm-1,alert-0,alert-1,alert-2.hound-{calm,alert}-*.png— the individual frames.hound@8x.png— prescaled 8× hero shot for docs/previews.
Render at integer scales with nearest-neighbor filtering or the edges
smear. calm plays at 1.5 fps, alert at 5 fps (ANIMATIONS in the module
carries both).
Source
No .aseprite — the canonical source is source/gate-hound.mjs: a
framework-free ES module. Edit the numbers 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
Same trick as night-shift’s owl: the dog’s round body, ears, and hackle
ruffle are rasterized from an ellipse formula and a handful of per-frame
numbers instead of a hand-typed character grid, and the reader beside it is
drawn from a literal readout — ticks stacks one short stroke per scan,
gauge is a pixel-width fill bar for scan rate, and lampOn toggles the
status light (including one deliberately-off flicker frame in alert). The
“posture reads a metric” idea, applied twice in one sprite: once to the
dog’s own silhouette, once to the prop beside it.
Gotchas
validateGrids()only checks thatFRAME_ORDER/ANIMATIONSreference real frames — there’s no character grid to catch a ragged row, since there isn’t one. Sanity-check new frames by rendering and looking.demo/gate-hound.jsis generated. Editsource/gate-hound.mjsand re-render.- The hackle spikes are placed by a fixed
dxlist against the ellipse’s current top arc, so ifDOG_RYorDOG_RXchanges, recheck that the spikes still land on the silhouette rather than floating off it. - The demo draws the sprite by calling
paintFrameat runtime, fine at this size. A game should blithound-sheet.pnginstead.
Port notes
paintFrame is arithmetic plus fillRect calls with no DOM dependency
beyond a 2D context — same shape as warehouse-duck’s and night-shift’s —
so a DragonRuby port translates directly: reimplement the ellipse/gauge math
against args.outputs.solids, or just consume hound-sheet.png as a
spritesheet with source_x = frame * 34.