The Terrain Seed
When you generate a game map procedurally, the first thing you need is not an algorithm. It is a seed.
The Seed Contract
A seed is a promise: given the same seed, the same world will appear. Every time. On every machine. After every code change that does not touch the generation logic.
This sounds trivial. Store a 32-bit integer, pass it to your random number generator, done. But the implications run deep.
Why Determinism Matters
Debugging: A player reports that their map has no oil deposits. You ask for the seed. You regenerate the map locally. You see exactly what they see. No screenshots needed, no save file transfers, no “it works on my machine.”
Testing: You can write a test that generates map seed 42 and asserts that cell (15, 23) is a forest tile. If generation changes, the test catches it. Not “the map looks different” — “cell (15, 23) is now grass instead of forest.”
Fairness: In competitive or speedrun contexts, everyone can play the same map. The seed is the shared starting point.
The Layering Problem
Procedural generation works best when you layer terrain in a deliberate order:
- Fill everything with grass (the default)
- Place water bodies
- Scatter forest clusters
- Add rock outcrops
- Place rare resource deposits on valid terrain
Each layer only modifies cells that have not already been claimed by a higher-priority terrain, or intentionally overwrites. The order matters because water placed after forest will carve through trees. Forest placed after water will create impossible floating groves.
/* Water first — lakes carve through anything */
scatter_clusters (map, rng, TERRAIN_WATER, num_lakes, 2, 5);
/* Forest next — fills available grass */
scatter_clusters (map, rng, TERRAIN_FOREST, num_forests, 2, 5);
/* Rock on top — smaller, denser */
scatter_clusters (map, rng, TERRAIN_ROCK, num_outcrops, 1, 3);
/* Oil last — only on remaining grass, very rare */
place_deposits (map, rng, TERRAIN_OIL, num_deposits);
The Noise Trick
Perfectly circular clusters look artificial. The fix is cheap: near the edges of a cluster, randomly skip cells. A 30% skip rate near the boundary turns circles into organic blobs. The randomness comes from the same seeded RNG, so it is still deterministic.
dist_sq = dx * dx + dy * dy;
if (dist_sq > (radius * radius) / 2) {
if (g_rand_int_range (rng, 0, 100) < 30)
continue;
}
Three lines. The difference between “computer-generated” and “almost natural.”
The Takeaway
Procedural generation is not about making random things. It is about making reproducible things that look random. The seed is your anchor. Everything else — the cluster sizes, the skip rates, the layering order — is just knobs you can tune.
Save the seed. Document the generation order. Everything else follows.