Seeds and Deterministic Generation
How Pseudorandom Number Generators Work
Computers cannot generate truly random numbers from software alone. Instead, they use pseudorandom number generators (PRNGs), which are mathematical functions that take a current state and produce both an output value and a new state. The output appears random (passing statistical randomness tests), but the sequence is entirely determined by the initial state. That initial state is the seed.
A PRNG initialized with seed 42 will produce the exact same sequence of values whether it runs on a desktop PC, a mobile phone, or a game console. The fifth number in the sequence will always be the same fifth number. The thousandth will always be the same thousandth. This determinism is what makes seeds powerful: an entire procedurally generated world, potentially containing millions of rooms, terrain features, and items, is fully encoded by a single integer.
Common PRNGs used in game development include the Mersenne Twister (the default in many languages and engines), xorshift variants (fast and compact), SplitMix (excellent statistical quality with minimal state), and PCG (permuted congruential generator, designed specifically for game and simulation applications). Each offers different tradeoffs between speed, state size, period length, and statistical quality. For most game applications, any of these produces randomness that is visually indistinguishable from true randomness.
The period of a PRNG is the number of values it produces before the sequence repeats. A 32-bit PRNG has a period of at most 2^32 (about 4.3 billion) values. A 64-bit PRNG has a period of up to 2^64 values. For game applications, even a 32-bit period is more than sufficient, as no game will consume billions of random numbers in a single generation pass.
Why Determinism Matters for Games
Deterministic generation enables features that are impossible with non-deterministic randomness.
Seed sharing lets players exchange world codes. Minecraft made this concept mainstream: a player discovers an incredible island spawn, posts the seed online, and thousands of other players can generate the exact same island on their own machines. This social feature emerged naturally from the deterministic generation system and became one of the most recognizable aspects of the game's culture.
Multiplayer synchronization uses shared seeds to avoid transmitting world data over the network. When all clients generate terrain from the same seed using the same algorithm, they produce identical worlds without the server sending any geometry data. This reduces bandwidth requirements to just the seed value (a single integer) plus any player-made modifications (blocks placed or removed, for example). For games with massive procedural worlds, this approach makes multiplayer feasible where transmitting the full world state would be impractical.
Speedrunning benefits from seeded generation in two ways. Seeded speedruns challenge players to complete a fixed, known layout as fast as possible, testing pure execution skill. Random seed speedruns challenge players to adapt to whatever the generator produces, testing strategic flexibility. Both formats are supported by the same deterministic generation system, and the speedrunning community for games like Spelunky, Minecraft, and Slay the Spire actively uses both formats.
Daily challenges use the current date as a seed (or a hash of the date) to generate a level that is identical for every player on that day. This creates a shared competitive experience where all players face the same challenges and can compare their performance on an even playing field. Games like Spelunky 2, Dead Cells, and Crypt of the NecroDancer use daily seed challenges to build community engagement around their procedural generation.
Bug reproduction is dramatically simplified by seeds. When a tester encounters a broken level, they record the seed that produced it. The developer enters that seed into the generator and sees the exact same level, including the exact bug. Without seeds, reproducing procedural generation bugs would require storing the entire generated output, which might be megabytes or gigabytes of data instead of a single number.
Seed Splitting and Independent Subsystems
A naive implementation uses a single PRNG for all generation, drawing random numbers sequentially for every subsystem: terrain, vegetation, enemies, items, decorations. This creates a fragile dependency where adding a single random call to one subsystem shifts the entire sequence for every subsequent subsystem, potentially changing the terrain layout just because the item generator now draws one extra random number.
Seed splitting solves this by deriving independent PRNGs for each subsystem from the master seed. A common approach hashes the master seed with a unique identifier for each subsystem: hash(master_seed, "terrain") initializes the terrain PRNG, hash(master_seed, "enemies") initializes the enemy PRNG, and so on. Each subsystem draws from its own independent sequence, so changes to one subsystem never affect any other.
For chunk-based generation, seed splitting extends to the spatial domain. Each chunk derives its own PRNG from the master seed and the chunk coordinates: hash(master_seed, chunk_x, chunk_y). This means chunks can be generated in any order, in parallel, or on demand without affecting each other's output. A chunk generated in isolation produces exactly the same content as it would if surrounded by fully generated neighbors.
Hierarchical seed derivation creates multiple levels of independence. The master seed derives per-floor seeds. Each per-floor seed derives per-room seeds. Each per-room seed derives per-subsystem seeds within that room. This hierarchy ensures that adding a room to floor 3 never changes anything on floor 7, and adding an enemy to room 12 never changes the items in room 5.
Position-Based Hashing vs Sequential PRNGs
Traditional PRNGs are sequential: you call next() to get each value, and the sequence depends on the order of calls. Position-based hashing is stateless: you pass coordinates (and a seed) to a hash function and get a random value back. There is no internal state, no call order dependency, and no need to consume values sequentially.
Hash functions like MurmurHash3, xxHash, and Wang hash are designed to produce well-distributed output from arbitrary integer inputs. Given a seed and a position (x, y), the hash returns a value that appears random, varies unpredictably with small changes in position, and is perfectly deterministic. The same seed and position always return the same value, regardless of what other positions have been queried.
Position-based hashing is ideal for terrain generation because terrain is inherently spatial. When generating a heightmap, each grid cell's value depends only on its coordinates and the seed, not on which other cells have been computed. This makes the generation trivially parallelizable (each cell is independent), cache-friendly (cells can be computed in any order), and compatible with chunk-based streaming (a new chunk never needs to know what neighboring chunks contain).
The tradeoff is that hash functions are slightly slower per call than optimized sequential PRNGs, and they do not provide the long-period guarantees of dedicated PRNG algorithms. For applications that need millions of sequential random values (like particle simulations or Monte Carlo sampling), a sequential PRNG is more appropriate. For spatial generation where each position needs an independent random value, hashing is simpler and more robust.
Common Pitfalls and Best Practices
Several common mistakes break determinism in procedural generation systems that are otherwise correctly designed.
Floating-point non-determinism across platforms is a frequent source of bugs. IEEE 754 floating-point arithmetic does not guarantee identical results across different CPUs, compilers, or optimization levels. Operations like sin(), cos(), and sqrt() can return slightly different values on AMD versus Intel processors, or on x86 versus ARM architectures. For cross-platform determinism, use integer-only PRNG implementations and convert to floating point only at the final output stage, or use fixed-point arithmetic throughout the generation pipeline.
Order-dependent generation breaks when the execution order changes. If chunks are generated by multiple threads, the order in which threads complete is non-deterministic. If the generator uses a shared PRNG across threads, the interleaving of random number consumption is unpredictable. The solution is seed splitting: each chunk (or thread) gets its own independent PRNG derived from the master seed and the chunk identifier.
Library version changes can silently alter PRNG output. If a game engine updates its built-in random number generator to a newer algorithm, every seed produces different output. For games where seed compatibility across versions matters (multiplayer, saved worlds, speedrun records), the PRNG implementation should be owned by the game rather than delegated to the engine or standard library.
Seed quality matters for certain PRNGs. Some generators produce poor-quality output for seeds near zero, seeds with many repeated bits, or seeds that differ by only one bit. Passing user-provided seeds through a hash function before using them as PRNG initialization ensures good statistical quality regardless of what the user enters. This seed conditioning step is cheap (a single hash call) and prevents subtle quality issues with player-chosen seed values.
Seeds transform procedural generation from a source of uncontrolled randomness into a deterministic, reproducible, and shareable system. Proper seed management through seed splitting, position-based hashing, and cross-platform care ensures that the same seed always produces the same world, enabling multiplayer synchronization, seed sharing, speedrunning, daily challenges, and reliable debugging.