"Procedurally generated" is often taken to mean "random", and when it does mean that, the results are usually bad. Genuine randomness produces impossible jumps, unfair hazard clusters and long stretches of nothing at all.

Good procedural generation is something more specific: a library of hand-authored pieces, assembled under constraints that make an unfair result structurally impossible. The creativity is in the pieces and the constraints. Only the ordering is generated.

This guide covers the four generation systems running on this site and the guarantees each one provides.

System one: chunk-based track generation

Every runner on this site builds its track from chunks — self-contained sections of a fixed width, each authored by hand, each tagged with a difficulty value.

The library

ChunkDemandContents
Rest0.0Flat ground and a five-item food trail
Single hurdle0.2One crate with an arc over it
Overhead0.4A hanging pot requiring a duck, food at floor level
Double hurdle0.45Two obstacles with a zig-zag between them
Low flame0.5A burner flame with a high arc over it
Gap0.6A pit with an arc across it
Staircase0.66Three ascending platforms with food on each
Moving blade0.72A whisk on a vertical path over a floor trail
Gap plus crate0.78A pit followed immediately by an obstacle
Bonus risk0.85Two flames with a 240-point token between them

Ten chunks is enough. Because they are assembled in varying orders at varying speeds with varying food pools, ten pieces produce a track that does not feel repetitive for a long time — and every one of those pieces has been played and verified by a person.

Windowed selection

The generator does not sample the library uniformly. It filters to chunks whose demand is at most the current intensity plus about 0.22, sorts what remains by demand descending, and picks from the top portion of that list, weighted by intensity.

That produces content consistently near the edge of what the player can currently handle without ever going far past it. The 0.22 window is deliberate — it keeps the game slightly ahead of the player, which is where the interesting part of any difficulty curve lives.

Forced rest

Windowing alone is not sufficient. Four maximum-demand chunks in sequence will defeat almost anyone, and random selection will eventually produce exactly that.

So the generator counts consecutive demanding chunks — those above 0.3 demand — and forces a rest chunk after four. The rest chunk has no hazards, flat ground and a food trail.

Rest chunks are not filler

A rest section is where a player rebuilds a combo chain, spends a saved power-up or repositions after a near miss.

Without them a long run becomes uniformly tense, and uniform tension stops registering as tension at all. The rest section is what makes the hard sections feel hard.

Reachability

Chunks containing gaps need to know how far the character can jump. The generator computes the projectile range — speed × 2 × jumpSpeed / gravity — every time the speed changes, and sizes gaps at 62% of it.

That 38% margin covers imperfect timing, the coyote-time window and the fact that a player under pressure does not jump at the theoretical optimum. It is the difference between a gap that is technically clearable and one that is actually clearable.

System two: maze carving

Food Factory generates a new maze floor every time you clear one, using a randomised depth-first search.

The algorithm starts with a grid entirely of walls, picks a cell, and repeatedly moves two cells in a random unvisited direction, knocking out both the destination and the wall between. When no unvisited neighbours remain it backtracks. The result is a perfect maze: every cell is reachable from every other, and there is exactly one path between any two points.

That guarantee is what makes the level fair. Any token placed on any open cell is reachable, with no validation pass required — it is a property of the algorithm rather than something to check afterwards.

Adding loops

A perfect maze has a problem for an arcade game: every branch is a dead end. Being chased into one is a guaranteed loss with no counterplay, which is not a challenge so much as a slow-motion failure.

So after carving, the generator knocks out an additional 2% of walls at random. That converts the tree into a graph with cycles, which means you can run a loop around a block to escape a pursuer. It is a single line of code and it transforms the game.

Placement within the maze

  • The player starts on the first open cell found.
  • Tokens are placed on about 62% of open cells, drawn at 60% size so a corridor can hold several without clutter.
  • Golden stars go in the three cells furthest from the start, which gives the maze a reason to be fully explored.
  • Hazards start at cells far from the player, spaced along the open-cell list.
  • One power-up is placed at the midpoint of the open-cell list — roughly central.

System three: scatter placement

Arena games place items with a rejection-sampling scatter, which is simple and produces good results under two constraints.

  1. Minimum spacing. No two items closer than 52 units, so nothing overlaps into an illegible pile.
  2. Exclusion zones. A clear radius — typically 80 units, up to 140 in challenge rounds — around the player's position, so a wave never materialises on top of you.

The algorithm generates a candidate position, tests it against both constraints, and either accepts or retries. It gives up after forty attempts per item, which means a very crowded field produces slightly fewer items rather than hanging.

That bounded-retry detail matters. A rejection sampler without an attempt limit can loop indefinitely when the constraints cannot be satisfied, which in a game means a frozen frame.

Try it yourself: Food Factory

Clear every ingredient token from a generated factory floor before the shift bell rings.

Play Food Factory

System four: corridor generation

The flying games generate gates — paired walls with a gap between them — and the constraint that makes them fair is about the relationship between consecutive gates, not about any single one.

Gap height starts between 135 and 250 units and narrows as intensity rises. Gate spacing starts at 300–330 units and tightens by up to 18%. Neither of those is the fairness constraint.

The constraint is this: the vertical shift between one gate's gap centre and the next is capped at the lesser of 30% of screen height or 55% of the gate spacing. That guarantees the character can physically travel from one gap to the next at its maximum climb and fall rates.

Without that cap, the generator would occasionally place consecutive gaps at opposite extremes of the screen, which is unsurvivable regardless of skill.

Food Tunnel adds a squeeze that narrows every gap over time, and it needs a second guarantee: a hard floor of 96 units on the gap, which is more than twice the rocket's height. The tunnel becomes brutal; it never becomes geometrically impossible.

Seeded versus unseeded randomness

Two kinds of randomness appear in these systems, and the distinction matters.

Unseeded randomness — plain Math.random() — is used for chunk selection, hazard spawning and item placement. Every run should be different, so unrepeatability is the point.

Seeded randomness — a small deterministic generator — is used where a layout must be stable within a run. The star field in the night theme is generated from a fixed seed so the stars do not dance between frames, and maze generation can be seeded for testing so a specific floor can be reproduced and examined.

The seeded generator on this site is mulberry32: a few lines of integer arithmetic producing a well-distributed sequence from a 32-bit seed. It is not cryptographic and does not need to be.

What procedural generation cannot do

Worth being clear about, because the technique is often oversold.

  • It cannot invent good content. Every chunk in the runner library was authored and tested by a person. The generator only orders them.
  • It cannot replace balance work. Demand tags are judgements, and a mis-tagged chunk produces an unfair sequence no constraint will catch.
  • It cannot create memorable moments. Generated content is consistent rather than surprising. Authored moments — a specific risk placement, a specific hazard combination — are what players remember.
  • It cannot be tested exhaustively. You can test the pieces and the constraints, and verify that the constraints hold, but you cannot play every possible sequence.

The right way to think about it is as a distribution system rather than a creative one. It takes authored content and delivers it in an order that stays appropriate to the player's current state — which is a genuinely valuable thing to automate, and not the same as generating a game.

Key takeaways

  • Good procedural generation is a library of hand-authored pieces assembled under constraints, not randomness.
  • Tag every chunk with a demand value and filter by a window above current intensity before sampling.
  • Force a rest section after a run of demanding chunks, or random selection will eventually produce an unsurvivable sequence.
  • Depth-first maze carving guarantees full connectivity by construction — then knock out 2% more walls so the maze has loops rather than dead ends.
  • Scatter placement needs a minimum spacing, an exclusion radius around the player, and a bounded retry count so it cannot hang.
  • In corridor games the fairness constraint is the shift between consecutive gates, not the size of any single gap.
  • Procedural generation distributes authored content. It does not create it, balance it or produce memorable moments.

Frequently Asked Questions

Is procedural generation just randomness?

No, and when it is, the results are bad. Good generation is a library of hand-authored pieces assembled under constraints that make an unfair result structurally impossible — only the ordering is generated.

How does a generator avoid creating impossible jumps?

By computing the character's actual projectile range from its jump speed, gravity and current horizontal speed, then sizing gaps at a fraction of it. This site uses 62%, leaving margin for imperfect timing.

What is a chunk?

A self-contained section of track of fixed width, authored by hand, containing obstacles, platforms, food and gaps, and tagged with a demand value from 0 to 1 describing how much it asks of the player.

Why do generated mazes have extra passages knocked out?

A perfect maze is a tree, so every branch is a dead end — and being chased into one is a guaranteed loss with no counterplay. Removing an extra 2% of walls creates loops, which gives the player an escape route.

When should randomness be seeded?

When a layout must stay stable within a run — a star field that would otherwise flicker — or when you need to reproduce a specific generated level for testing.

What can procedural generation not do?

It cannot invent good content, replace balance work, or produce memorable moments. It is a distribution system for authored content, not a creative one.