# Procedurally Generated Puzzles: How a Seed Writes a Manual

- Author: Bomb Appétit (https://bombappetit.com)
- Published: 2026-10-20
- Category: Rule seeds
- Canonical: https://bombappetit.com/blog/procedurally-generated-puzzles-seed-writes-manual

> How procedurally generated puzzles work here: one number feeds every module's generator, and the same seed always prints the same bomb manual.
Procedurally generated puzzles are built by code from a starting number instead of being written by hand. In Bomb Appétit, that number is the rule seed. It feeds a separate generator for each of the 14 modules, each generator proposes rules until a test accepts them, and the same seed always prints the same bomb manual.

## What makes procedurally generated puzzles different from hand-made ones?

A hand-made puzzle has one author who chose every clue. A procedurally generated puzzle has an author who wrote the rules for making clues, then let a program choose. The designer controls the grammar, the limits and the tests. The program controls which particular combination comes out for a given number.

That split changes what a designer has to worry about. With a hand-written rulebook, the worry is quality: is this clue fair, is this table too long, does this branch ever matter? With generated rules, the same worries apply to every possible output at once. The designer cannot read all of them, so the checks have to be written as code.

It also changes what a player gets. A hand-made manual is one manual. Once a group has learned it, the puzzles turn into recall. A generated manual is one of many. Bomb Appétit accepts any whole number from 1 to 999,999 as a rule seed, and each one produces its own set of tables, word lists, mazes and conditions. The page layout and the module names stay put; the content inside them is drawn fresh.

There is a cost. A writer can make a clue clever on purpose. A generator only produces what its grammar allows, so its rules tend to be regular in shape: a list of if-lines, a lookup table, a set of mazes. Bomb Appétit leans into that. Each module's manual page is laid out the same way under every seed, which lets an Expert who has read one seed find their way around another.

Finally, a generated puzzle has to be reproducible. If two people type the same number, they must see the same rules, or the game falls apart. That is why the randomness in the engine is seeded rather than taken from the clock. For the general idea behind that, the guide on [how a game seed makes randomness repeatable](/blog/what-is-a-seed-in-games-same-number-same-rules#how-a-game-seed-makes-randomness-repeatable) covers seeds outside this game too.

## From one rule seed to fourteen module generators

The rule seed becomes a manual in four steps. The engine turns the number into a keyed random stream. It forks that stream once per module, by the module's name. Each module's generator draws a candidate rule set from its fork. A test plays the candidate, and the first one that passes becomes that module's page.

The engine code for this is short. It creates a random number generator keyed to the text `rules:` followed by the seed, then calls `fork` with each module's id. A fork is a new stream whose key is the parent key plus a label. It does not depend on how many numbers the parent has already drawn, so one module's draws never shift another's.

Here is a worked example that traces seed 42 through the pipeline for the Wires module. The keys are how the engine names its streams. The outcomes in steps 4 and 5 are invented for illustration, not read from the real seed 42 manual.

1. **The seed.** A player opens manual 42. The engine starts a stream keyed `rules:42`.
2. **The per-module fork.** Wires gets the stream `rules:42/wires`. Big Button gets `rules:42/big-button`, Passcode gets `rules:42/passcode`, and so on for all 14 modules.
3. **The generator's proposal.** Wires forks again for its first try, keyed `rules:42/wires/try0`. From that stream it builds one list each for three, four, five and six wires. Each list gets three or four if-lines and an otherwise line. A line might check the serial number's last digit, the color of the last wire, or how many wires of one color there are.
4. **The acceptance check.** The candidate goes through a filter and then the solvability test. In this invented example, try 0 fails because one line names the same color twice in its conditions, which the Wires filter rejects. Try 1, keyed `rules:42/wires/try1`, passes the filter and then plays 1,000 random Wires modules by following its own hints without a strike.
5. **The cached manual page.** The accepted rule set from try 1 is stored in the rule book for seed 42, next to the other 13 modules' accepted sets. The manual page for Wires renders from that data and nothing else.

A generator gets up to 200 tries. Each try has its own named fork, so whichever try passes, the result is still fixed by the seed. If all 200 failed, the engine would raise an error rather than print a page.

The test in step 4 has its own guide. Read [how the 1,000-bomb solvability test plays a manual](/blog/randomized-puzzle-game-solvability-test#how-the-1-000-bomb-solvability-test-plays-a-manual) for what one sample bomb goes through and why five modules play 200 samples instead of 1,000. The broader post on [the randomized puzzle game solvability test](/blog/randomized-puzzle-game-solvability-test) also covers what happens when a candidate fails.

Not every module has the same kind of generator. Passcode samples a word list from a pool. Labyrinth carves a set of mazes and places marker cells. Discharge Lever has no rules to generate at all. The module-by-module table in [what each module redraws under a new seed](/blog/games-with-changing-rules-what-each-module-redraws) lists them all.

## Why a generated manual is cached once per seed

Generating a manual is the slow part, because each candidate rule set has to play hundreds or a thousand sample bombs before it is accepted. So the engine builds the whole rule book for a seed the first time it is asked, keeps it in memory, and hands back the same object on every later request.

The cache is a plain map from seed number to rule book inside the engine. The function that builds a rule book checks the map first. If the seed is there, it returns the stored book. If not, it runs every module's generator, assembles the 14 results and stores them under that seed.

Caching is safe only because the generators are deterministic. If the same seed could give two different manuals, a stored copy might disagree with a fresh one, and an Expert could read rules the bomb does not use. Because every draw comes from a keyed stream, a rebuild would produce the same book, so keeping one copy changes speed and nothing else.

The website adds a second layer for the standard manual. The page for [manual 1](/manual/1) is built ahead of time, when the site is built. Any other seed's page is generated the first time someone opens it. A group trying a seed nobody has opened yet may wait a moment longer on that first load. After that, the page and its rule book are ready.

Seed 1 also skips the generators entirely. Its rules are stored as data files shipped with the game, one per module, and the engine reads those instead of running the generators. That is a separate guarantee from the cache: the cache saves time, while the stored files keep seed 1 from changing when a generator is edited. The [games with random rules guide](/blog/games-with-random-rules-rule-seed-manual) explains why seed 1 is the standard manual.

## Can a procedurally generated puzzle surprise its own designer?

Yes. A generator can combine its parts in ways nobody wrote down, and some of those combinations are bad puzzles even if they are technically solvable. Bomb Appétit's code shows the designer expected this: several modules carry an extra filter that rejects dull or pointless rule sets before the solvability test even runs.

The filters read like a list of surprises the designer planned for. Big Button rejects a manual where every line gives the same answer, or where two lines repeat a condition. Pressure Vent rejects a set of prompts that answers nearly everything the same way, because that would need no Expert. Glyph Keypad requires each column of symbols to share several glyphs with the other columns, so one familiar glyph never gives the column away. Word Grid rejects rules that never send the Defuser to some button position.

Recall has a filter of its own. Each stage needs at least three different instructions, and every stage after the first must sometimes refer back to an earlier one. Without that, a generated Recall page could ask nothing of the Defuser's memory, which is the point of the module.

The solvability test catches a different kind of surprise: a rule set that looks fine on paper but leaves some bombs with no correct move. That can happen when a generator's choices interact with random edgework in a way nobody tried. The test plays sample bombs with random serial numbers, batteries, indicators and ports, so a gap in the rules shows up as a failed sample.

The project also checks its generators after the fact. The engine's rule tests sweep seeds 2 to 500 in continuous integration, playing every module under every one of those seeds on bombs drawn from a different sample stream than the generator used. A generator that could produce a broken manual for some seed would fail that sweep. The same determinism is what lets the server replay a submitted run on the bomb it issued; [how a defusal gets ranked](/blog/games-with-leaderboards-how-a-defusal-gets-ranked#verified-runs-only-what-the-server-checks-before-listing) covers that replay.

So surprise is expected, but it is fenced. The generator is free to choose any combination its grammar allows, and the filters and the test decide which of those choices a player ever reads.

## The generator edge case: a seed past 999,999

A rule seed above 999,999 does not reach the generators at all. The manual's address accepts one to six digits with no leading zero and a value of at most 999,999. Anything else, such as `/manual/1000000`, `/manual/0` or `/manual/042`, shows a not-found page instead of a manual.

The limit lives in one engine constant, the maximum rule seed, and every place that takes a seed respects it. Each one handles an out-of-range number a little differently, which is worth knowing before a group types a long number from memory.

| Where the seed is entered               | What happens with 1,000,000 or more                               |
| --------------------------------------- | ----------------------------------------------------------------- |
| The manual's address bar                | A not-found page, because the address does not match a valid seed |
| The Rule seed box in the manual sidebar | Pressing Open does nothing, so the current manual stays on screen |
| The "Manual (rule seed)" freeplay field | The value is pulled back to 999,999                               |
| A room's freeplay setup                 | The same field, so the same pull back to 999,999                  |

The freeplay behaviour is the one that can catch people out. If someone types 1,234,567 into the freeplay field, the bomb is set up on seed 999,999. An Expert who types the same long number into the manual sidebar sees nothing happen and may stay on the old manual. The two sides now disagree.

The fix is simple. Pick seeds of six digits or fewer, and read the seed back from the bomb side before the round: the freeplay screen has a button labelled "Open manual" followed by the seed it will use. If that number is not the one you meant, change it before anyone arms the bomb.

Zero, negative numbers and blanks follow the same pattern. The manual address rejects them, the sidebar ignores them, and the freeplay field turns them into 1, the standard manual. That last case is easy to miss, because an Expert who meant to try a new seed may end up on the most familiar one without noticing. To try a new seed now, open [Play](/play) and set the freeplay field first.
