Straphanger’s and Stand Clear’s subway doors — “the aperture’s width is the door-open fraction” — pulled out into its own reusable mechanic: a gate cycles open → closing → closed → opening on a timer, and a probe (the player) either fits through the current opening or gets squeezed back out. The demo arcades it into an endless corridor run: doors ahead cycle on their own clocks, out of sync with each other, and you either rush a wide-open gap or thread dead center right as it’s about to shut. Get caught mid-close and you’re bounced back to wait for the next cycle — streak resets.
Reuse
The mechanic lives in src/closing-doors.js as a framework-free ES module:
createGate(opts)— returns a state object; callupdate(dt)each frame. Reads:phase(open/closing/closed/opening),aperture(0 shut..1 open),cycles.opts:openTime,closeTime,closedTime,openAnimTime— seconds in each phase.fitsGap(gate, gapCenter, gapHalfHeight, probeY, probeHalfHeight)— pure function, no state mutation: true iff the probe’s extent is wholly inside the gate’s current opening. Call it wherever you need the collision answer; the demo calls it once per gate per frame while the player’s x overlaps the gate’s band.
Both the cycle and the collision test are pure math with no DOM or canvas dependency — same trick the full subway sims use to stay headlessly testable. Reusable well beyond subways: elevator doors, a portcullis, a closing drawbridge, any hazard that’s dangerous mid-transition and safe at either extreme.
Port notes for DragonRuby: update() is straight state math — translate
directly into a tick method; fitsGap translates as-is since it takes plain
numbers, not DOM rects.
Gotchas
- Phase timing is time-based (
update(dt)), not frame-based — desync gates by callingupdate()once with a random offset at spawn (as the demo does), not by staggering spawn frames, or they drift back into sync at low frame rates. fitsGaptakes half-heights, not full sizes — passing a full player height where a half-height is expected makes every gate look half as open as it is.