Inspiration

I have two toy poodles. Milky is patient and floaty. Mongae is fast, bossy, and catches frisbees. Once a day, for no reason either of them will explain, they lose their minds and sprint in circles until they fall over. Dog people call these the zoomies. Behavioural literature calls them frenetic random activity periods, which is somehow funnier.

I wanted a game built on the above. So the zoomies aren't a power-up that keeps you safe. They are a button that makes the dog faster and harder to control in exchange for a bigger multiplier, and then asks when to stop. Every extra second grows the pot. Bank it and its yours. Crash and it was never yours.

What it does

One thumb, one screen. Tap to jump, hold for higher, swipe down to dive. Catch frisbees to fill a three-bar meter, set off the zoomies.

The two dogs are not skins. Milky has a higher jump and lighter gravity - 0.75 seconds of airtime and a gentle 3x/5x/8x ladder. Mongae is snappy and unforgiving - 0.51 seconds and a brutal 2x/8x/20x. The same hole is a different problem depending on who you brought.

Frisbees you bank across runs buy trails.

Nine trail and frisbee colours: plain to start with, five you earn by playing, and three in a $1.99 Supporter Pack. The pack is cosmetic, and that is the entire point of it. Nothing behind it touches how the game plays, and the shipped version has no ads, no timers, no energy, no lives and no continues. I would rather people chose to back the thing than felt squeezed by it.

It also asks you for nothing. No account, no sign-up, no email - there is not one text field in the whole app. It makes no network requests at all: no analytics, no telemetry, no tracking. The only things it writes to your device are your best score, whether you muted it, and which colours you own. Nothing is collected because nothing needs to be. Most of the data mishandling I have seen was of data nobody needed in the first place, and the simplest way not to mishandle it is not to have any.

How I built it

I had never written a line of code when I started this, and I still can't. Poodle Panic was built in conversation with Claude, over several weeks, mostly at night - and I would rather write that down plainly than have somebody work it out.

What that meant in practice: the model wrote every line, quickly and with enormous confidence. I decided what the game was, played it badly several hundred times, and said no a great deal. Almost everything below came out of refusing the first answer.

A single self-contained HTML file — canvas, no engine, all art and audio embedded, works offline.

The dog is a cut-out puppet. Eleven hand-drawn pieces per dog, moved entirely by code, the way Rayman Legends is built. The ears and tail run on spring-dampers driven by the body's actual vertical acceleration, so they lag and overshoot on a real landing.

The jump is solved. Given an apex height \( h \) and a time-to-apex \( t \), the launch velocity and gravity fall straight out:

$$ v_0 = \frac{2h}{t}, \qquad g = \frac{2h}{t^2} $$

Which means jump feel is authored in units a human can reason about — "118 pixels high, 0.345 seconds up" — rather than by nudging a gravity constant until it stops feeling wrong.

Following Canabalt's rule that constraints should come from the player's current state rather than constants, the width of a hole is computed at spawn from how far this dog can jump at this speed:

$$ w_{\text{gap}} = v_{\text{base}} \times T_{\text{air}} \times f $$

with \( f \) rising from 0.34 the first time you see one to 0.66 at full difficulty. Sized from base speed rather than burst speed, deliberately: if a hole were scaled during a zoomies burst and you then banked before reaching it, you'd arrive far slower at a hole built for a burst. Base speed only ever rises, so sizing from it is provably safe.

Challenges I ran into

I shipped an impossible obstacle, and I'd built it on purpose. One pattern put a low hazard and an overhead branch close together.

I stopped and looked at every pattern, every speed each one can actually occur at, both dogs, searching every jump, hold and dive at 1/240 of a second. It came back with a zero-millisecond window.

A branch kills you for being too high, a ground hazard forces you to be high, and together they make a 41-pixel corridor the dog has to fly along for 156 pixels of travel. But a jump is an arc, not a flat line, so at speed it crosses the corridor instead of following it. My original fix had been to widen the gap, which didn't work.

The real fix was a different rule: a branch has to arrive after the dog has landed, and that distance isn't a constant — it's this dog's airtime at this speed. Every pattern in the game now has a survivable line with at least a 158ms window.

Another problem, the legs never looked right, so I split each one into a thigh and a shank and solved them with two-bone IK so they could genuinely fold. It looked worse, and the reason ruled out the whole direction: a groomed poodle leg is a fluffy column with no visible joint. The art has no knee to expose. I reverted it and got the improvement from proportion instead — bigger head, shorter body, more bounce.

Accomplishments I'm proud of

Most games ship on the designer's instinct that a level is clearable. Mine has a solver that takes every obstacle pattern, at every speed each one can actually occur at, for both dogs, and searches every jump, hold and dive at 1/240 of a second. Every pattern has a survivable line with a window of at least 158 milliseconds.

A rotary gallop with a 0.33 duty factor, which leaves exactly two windows per stride with no foot on the ground. A short gathered suspension and a long extended one. The body arc and spine flex are phased onto those two windows. The ears and tail run on spring-dampers driven by the body's real vertical acceleration, so they lag and overshoot on an actual landing instead of replaying a canned one.

The two dogs change the level, not just the look. Milky's 0.75 seconds of airtime and Mongae's 0.51 aren't flavour text — hole widths are computed at spawn from whichever dog you picked. Choosing a character literally reshapes the geometry of the run.

I deleted my favourite thing in the game. The impossible pattern was one I'd built deliberately and left a proud comment on. I also spent a full day giving the legs real two-bone IK so they could fold properly, then threw it away when it looked worse — a groomed poodle leg is a fluffy column with no visible joint, so there was no knee to expose. Both were more valuable gone than kept.

What I learned

"Possible" and "fair" are different questions, and only one of them is answerable by playing the game. That sentence cost me the most time and taught me the most.

The rest was a genuine pleasure. I did not expect building this way to work as well as it did, and finding out that it does - that somebody who cannot code can sit down and come away with a finished game that people can actually play - was the most satisfying part of it.

I learned how animation works, which I had never once thought about. That a run cycle is a footfall pattern with a duty factor rather than a loop of drawings. That ears and tails read as alive when they are driven by the body's real acceleration and dead when they are driven by a sine wave. That a leg can be wrong for reasons to do with grooming rather than geometry.

And I learned that a lot of game design is applied psychology. Canabalt's rule - that constraints should come from the player's current state rather than from fixed constants - is a design idea before it is a technical one, and once you have seen it you cannot stop seeing it. The better decisions in this game all came from asking what the player knows at the moment they have to choose.

What's next for Poodle Panic

A 1.1 if there is appetite for it: more zones, a proper leaderboard, and Mongae getting the bandana she wears in real life.

Whether it goes further depends on whether anyone actually plays it. I built this because I wanted it to exist rather than because I had a market in mind, and I would rather find out there is an audience than assume one.

Built With

Share this project:

Updates

Submission history