On this page
One engine, thirty games
Every game on this site runs on the same engine, written in plain JavaScript with no framework and no third-party game library. The engine owns the canvas, the game loop, input, collision, particles, audio, the state machine, personal bests and touch controls. An individual game is a configuration object plus two functions: one that advances the world by a time step, and one that draws it.
That architecture is the reason the library feels consistent. Pause behaves identically in all thirty games. So does the sound toggle, the restart key, the automatic pause when you switch tabs, and the way a personal best is written. A player who learns one game has learned most of the controls of all of them.
It also means a fix is a fix everywhere. When we improved how jump buffering works, every platformer and every runner got better in the same afternoon.
The loop
At the centre of everything is a loop driven by requestAnimationFrame, which lets the browser schedule frames when it is actually going to paint them. Each frame does four things in order: measure elapsed time, update the world, draw the world, clear the one-frame input flags.
The elapsed time — delta time, or dt — is the number everything else is multiplied by. A creature moving at 200 pixels per second moves 200 * dt pixels this frame, which means it covers the same ground on a 60 Hz laptop and a 120 Hz phone.
Why dt is clamped
If the tab is backgrounded, or the machine stalls, the next frame might arrive half a second later. Multiplying by a dt of 0.5 would teleport everything half a second into the future, straight through walls. So dt is capped at one twentieth of a second: a long stall produces one slightly large step rather than a catastrophe.
This is why games here pause automatically when you switch tabs rather than trying to catch up. Catching up is either unfair or impossible, and pausing is honest.
The state machine
Every game moves through the same seven states, and each one owns a piece of interface:
| State | What is happening | What you see |
|---|---|---|
| Loading | Scripts parsing, canvas sizing | A static frame of the scene |
| Ready | Waiting for you | Title, your best, a Start button |
| Countdown | Used where a run needs a beat to settle | A brief counter |
| Playing | The loop is running | The game |
| Paused | Loop stopped, input released | Resume and Restart buttons |
| Game Over | Run committed to storage | Your stats and why you lost |
| Victory | A win state was reached | Your stats and a play-again button |
The overlays are real HTML rather than text drawn on the canvas. That matters for accessibility: a button drawn on a canvas is invisible to a screen reader and cannot be reached by keyboard, whereas a real <button> is both.
Pausing stops the loop entirely rather than setting a flag. A paused game uses no CPU at all, which is why leaving a tab open costs nothing.
How difficulty is calculated
Almost every difficulty curve in the library is the same shape: asymptotic. The value rises quickly at first, then more slowly, approaching a ceiling it never reaches. In code it is a single expression — the elapsed time divided by the elapsed time plus a half-life constant.
The alternative, a linear ramp, is worse in a specific way. It eventually produces a speed at which no human could react, which makes the late game a formality rather than a challenge. With a curve, a skilled run ends because the player made a mistake, not because physics ran out.
The constraint that actually matters
Speed is the obvious dial and it is not the interesting one. The interesting one is spacing. In every runner here the generator calculates how long the player is committed after handling an obstacle — a jump takes about 0.55 seconds before you can act again — and refuses to place the next obstacle inside that window plus a reaction allowance.
That allowance shrinks over a run: about 420 milliseconds early, down to a floor of 260. The floor is where the difficulty comes from, and it is a floor rather than a slope because below roughly a quarter of a second the game stops measuring skill.
The practical consequence is the promise we can make about every procedural game on the site: the generator is allowed to be as nasty as it likes about what it places, and never about when.
Why every enemy telegraphs
Before any creature on this site does something that can hurt you, it does something visible that cannot. A charger crouches. A lunger rears back. A spitter glows. Those windows run between 0.25 and 0.5 seconds — long enough for a human to act, short enough to demand attention.
This is a contract rather than a decoration. If an attack could land without warning, the optimal strategy would be to move randomly forever and the game would have no skill ceiling. Because every attack announces itself, the optimal strategy is to watch, and watching is a skill that improves.
Boss fights add one more rule: the first cycle of every new phase telegraphs about twenty per cent longer than normal. The first time you see an attack you get extra time to read it; thereafter you do not. It is a teaching mechanism hidden inside a fight.
It also gives us a clean bug definition. If a hit felt unavoidable, either the telegraph was too short or it was somewhere you had no reason to look. Both are defects, and both are fixable.
Scoring, combos and personal bests
Score in most of these games is not a count of things collected. It is a count multiplied by a combo, and the combo is where the design lives.
Consecutive successful actions raise a multiplier in steps up to a cap. Taking damage resets it. So does a gap — every chain has a timeout, which is the mechanic new players most often miss. You can lose a ×4 chain by playing too safely, not just by getting hit.
The consequence, stated as plainly as we can: protecting a chain is usually worth more than extending a haul. If reaching an item has any real chance of breaking your multiplier, skipping it is the higher-scoring play. Most people learn this backwards, chase everything, and finish with more items and fewer points.
Personal bests are written to your browser's localStorage when a run ends. There is no server here, which is why there is no global leaderboard: a submitted score could not be verified, and an unverifiable leaderboard is worse than none.
Where the numbers in our guides come from
Every figure quoted in a guide on this site — a 180-millisecond timing window, a two-second combo timeout, a 92% velocity retention on ice — is a constant in the game source, checked before the article was published.
That is the main thing our guides offer over writing about these games from the outside, and it only stays true if the checking is real. When a game is rebalanced, every article that quoted the changed value is revised at the same time and both dates change. The process is described in our fact-checking policy.
If you ever find a guide describing a game as it no longer behaves, that is a genuine failure of our process and we would like to hear about it.
What happens in a single frame
A frame is about sixteen milliseconds, and the same six things happen in the same order in every game on the site. Knowing the order explains several behaviours that otherwise look arbitrary.
- Read input. The input layer reports which actions are held and which were newly pressed since the last frame. Nothing in the game reads the keyboard directly.
- Advance time. Delta time is measured and clamped to a maximum of one twentieth of a second, so a browser stall produces a slow frame rather than a teleport.
- Update the player. Movement, gravity, jump state, ability cooldowns.
- Update the world. Creatures, projectiles, platforms, hazards, collectibles and the difficulty curve, in that order.
- Resolve collisions. Everything has already moved; this step asks what now overlaps what, and applies the consequences.
- Draw. Background, scenery, collectibles, enemies, player, effects, HUD.
The clamp in step two is the one worth understanding as a player. Without it, a frame that took 400 ms because the browser was busy would advance the simulation 400 ms in a single step — and your creature would pass straight through a wall it never had a chance to collide with. The clamp trades a moment of slow motion for never dying to a stutter.
The order of steps three and four matters too. Because the player moves before creatures do, a creature can never move onto a position you have already left, which removes an entire class of unfair-feeling hit.
Why nothing is allocated during play
Every pool in the engine — particles, projectiles, obstacles, platforms, collectibles, creatures — exists before the run starts and is reused for its duration.
The reason is garbage collection. JavaScript reclaims memory automatically, and it does so by pausing everything else briefly. A pause of twenty milliseconds is a dropped frame, and a dropped frame at the wrong moment is a missed jump you had timed correctly.
Pooling makes those pauses essentially disappear, because nothing is being discarded to reclaim. A particle that has expired is marked inactive and waits; the next burst takes it back rather than creating a new one. The cost is a small amount of memory held permanently and a discipline about never writing new inside the loop.
The visible effect is consistency. A game that runs at sixty frames per second with occasional hitches feels substantially worse than one that runs slightly slower and never hitches, because your timing calibrates to the average and then gets betrayed by the exception.
Why the loop pauses when you look away
Switch to another browser tab during a run and the game stops entirely rather than continuing without you.
The mechanism is the Page Visibility API: when the document becomes hidden, the run is paused and the animation frame loop is cancelled. When you return, the game is waiting on a paused screen rather than on a game-over one.
This solves two problems at once. The obvious one is fairness — a run that continued while you answered a message would end for reasons that had nothing to do with playing it. The less obvious one is that browsers throttle background tabs heavily, so a loop left running would receive enormous delta times, get clamped, and produce a slow-motion simulation burning battery to no purpose.
The same logic covers window blur and, on mobile, the app being backgrounded. In every case the rule is the same: if you cannot see the game, the game is not running.
Key takeaways
- All thirty games share one engine, which is why pause, sound, restart and best scores behave identically everywhere.
- Delta time is clamped so a stalled frame produces one large step rather than teleporting everything through walls.
- Difficulty curves are asymptotic: they approach a ceiling rather than rising forever, so a skilled run ends from a mistake.
- Obstacle spacing, not speed, is the real fairness constraint — the generator may be nasty about what it places, never about when.
- Every attack telegraphs, and a boss's first cycle in a new phase telegraphs 20% longer so the pattern is learnable.
Frequently asked questions
Why do the games pause when I switch tabs?
Because catching up on missed time is either unfair or impossible. Pausing stops the loop entirely, which also means a backgrounded game uses no CPU.
Is the difficulty random?
No. Which obstacle appears is random within limits; when it appears is strictly constrained by how long the previous one committed you for, plus a reaction allowance.
Why is there no global leaderboard?
The site is static with no server, so a submitted score could not be verified. We show your own local best instead and label it as local.
How do you decide a game's difficulty rating?
The label describes execution pressure rather than depth. See monster game difficulty explained for the full method.