← Back to work

Cascade


This is a small thing I’ve been poking at: a browser juggling game. You’re a stick figure, you throw balls between two hands, and you try to keep a rhythm going while the game keeps handing you more of them. It’s early and I’m still tuning the feel, but it’s playable, so here it is.

Open Cascade in its own tab → for a bit more room.

Click the game first, then use the arrow keys. Hold a hand to wind up, release to throw — hold longer for a higher, slower throw. Hold up while you release to cross to the other hand. Keep it steady and you’ll build a flow multiplier; keep it steady long enough and another ball drops in.

The constraint

I’ve been aiming at the js13kGames budget — the whole thing, code and all, has to fit in 13KB zipped. That rules out a game engine, so it’s just TypeScript and a 2D canvas, no dependencies at runtime. TypeScript’s types compile away to nothing, so they cost zero bytes in the build while still keeping the code honest. As of writing, the minified bundle is about 6.7KB (~3KB gzipped), so there’s plenty of room left.

How it’s put together

The part I care about is the separation. There’s a pure simulation core that knows nothing about the browser — no canvas, no DOM, no wall clock. The whole game advances through one function:

step(state, inputs, dt) -> newState

Time comes in as dt and any randomness is seeded, which means the game is fully deterministic: the same inputs always produce the same run. Around that core sit thin shells — one that turns key events into intents, one that draws the state to the canvas, and a fixed-timestep loop that wires them together. The shells depend on the core; the core never depends on them.

The reason for all that discipline is testing. Because the core is pure and deterministic, I can script a whole juggling sequence at a fixed timestep and assert exactly where every ball lands — no flaky “open a canvas and sleep” tests. Coverage ratchets upward and a regression fails the build, so it can’t quietly rot.

A bit I liked

Scoring rewards timing, not mashing. Rather than counting catches, it watches how steady you are: the variation in the gap between catches (your rhythm) and the variation in your throw power. Steady on both and a “flow” multiplier climbs; get erratic and it falls off. I prototyped it as a throwaway first — scripted a tight run and a sloppy one through the sim and checked the numbers actually separated — before committing to it. They did, cleanly, which was a nice little confirmation that the deterministic-core idea pays for itself.

There’s more I want to do — one-handed patterns, tricks and combos, maybe a finale — but that’s for later. For now it’s a stick figure keeping a few balls in the air.