PCG: noise and dungeons
The mechanism: smooth randomness and baked-in constraints
Perlin/Simplex noise
A plain random() gives you "snow" — neighboring values are independent. Terrain needs smooth randomness: nearby points have nearby heights. Perlin noise: random gradient vectors at the lattice nodes; for a point you take dot products of the gradients with the offset vectors to the nodes and interpolate smoothly → a value in . One octave gives smooth hills; detail comes from octaves (fBm) — a sum of copies of the noise with rising frequency and falling amplitude:
( is lacunarity, the frequency multiplier; is persistence, the amplitude multiplier, usually , ). The large octaves set the mountains, the small ones the rocks and ripples: exactly the fractal terrain of Minecraft. Fast (O(1) per sample, precomputable), parameterizable, comprehensible. Simplex (Perlin, 2001) is faster and free of directional artifacts; its 3D+ patent expired on Jan 8, 2022, and OpenSimplex (Kurt Spencer, 2014) is a clean-room patent-free alternative.
L-systems, BSP, grammars
- L-systems — string rewriting rules: axiom
F, ruleF → F[+F]F[-F]F, iterate, interpret as turtle graphics (F— forward,[]— position stack,±— turn). Recursion in the rules produces tree-like branching — procedural vegetation. - BSP (Binary Space Partition) — recursively split a rectangle, rooms in the leaves, corridors between neighbors. It guarantees connectivity (a corridor exists), has a natural hierarchy, O(n). The dungeons of Rogue/Diablo/roguelikes.
- Level grammars —
Level → Entrance Room+ Boss Exit: you sample from the grammar and check the constraints (the exit is reachable, difficulty is balanced).
The main insight: constraints baked into generation (Spelunky)
Spelunky (2008) shipped with procedural levels that feel hand-made. The secret is constraint satisfaction during generation, not after: the generator guarantees a path to the exit, rising difficulty with depth, no dead ends — because the constraints are baked into the process rather than checked afterwards. "Generate a level, then check whether it's valid" loses to "generate in a way where validity cannot be broken". Hence the definition: PCG isn't "random", it's constrained randomness, where the constraints are what deliver the quality.
And GANs? A frontier that usually loses
ML-augmented PCG exists: train a GAN on levels (the Mario-level papers, Volz et al.) — it learns the distribution of "good" levels and can interpolate. But in practice, for shippable content, it loses to the classics: the output is often invalid (mismatched tiles, impassable), hard constraints are hard to impose (a level must be traversable — and a GAN gives no guarantees), and it's slower. ML PCG is useful in spots (style blending, augmentation, textures), but structural, constraint-bound content is almost always cheaper and more reliable to generate classically — it's the same story as with RL agents, only about content.
🕹 What to play — and what to notice
The Minecraft world is Perlin/Simplex noise over height (+ caves from 3D noise); NMS generates planets with the same machinery. Large forms + small detail = fBm octaves.
🎮 Notice: in Minecraft, fly up high and look at the hierarchy of scales: big hills (a low octave) with bumps and ripples laid over them (high octaves). That is literally the sum of octaves from the formula. And the whole world isn't stored, it's computed from a seed (store the function, not the output — as in hardware).
Every level is unique and yet always traversable: the path to the exit is guaranteed by generation, difficulty grows with depth. It feels hand-made even though it's procedural.
🎮 Notice: play a few Spelunky levels and confirm it — the exit is always reachable, there are no trap dead ends. That isn't the generator getting lucky, it's a baked-in constraint. Compare with how a "random" level with no constraints would feel (an impassable mess). That's the difference between "constrained randomness" and "just random".
Classic roguelikes (and Diablo) build dungeons with BSP: recursive splitting → rooms in the leaves → corridors between neighbors. Always connected, cheap, "looks good".
🎮 Notice: in a roguelike, look at the dungeon map: rooms grouped hierarchically, joined by corridors, no isolated regions. That's the fingerprint of a BSP tree. A simple deterministic algorithm gives you a connected, playable level with no learning at all.
Deep end · value vs gradient noise, octaves, Simplex/OpenSimplexskippable
Value noise vs gradient noise
Value noise: random values at the lattice nodes + smooth interpolation (smoothstep). Simple, but it shows visible lattice "blockiness". Gradient noise (Perlin): random gradients at the nodes, the value = an interpolation of dot products gradient·(point−node); it's zero at the nodes with smooth extrema in between → visually richer, fewer axis artifacts. fBm on top of either: — octaves add detail at shrinking scales; for the series converges and the spectrum follows a power law (hence "fractal"/natural). Variations: ridged (|noise|, sharp ridges), domain warping (noise of coordinates that were themselves distorted by noise — rivers/veins).
Simplex, the patent and OpenSimplex
Classical Perlin on a cubic lattice needs corners in D (8 in 3D, 16 in 4D) and produces directional artifacts along the axes. Simplex (Perlin, 2001) puts the nodes on a simplex lattice → corners (4 in 3D), fewer multiplications, better scaling into high dimensions, no axis artifacts. The catch: 3D+ use of Simplex was patented (US 6,867,776), so many engines stuck with Perlin for years; the patent expired on Jan 8, 2022. OpenSimplex (Kurt Spencer, 2014) is a clean-room free alternative, also gradient noise, without Perlin's artifacts. In practice: take Simplex/OpenSimplex for speed and isotropy, Perlin if it's already good enough.
Deep end · why the classics beat GANs, and the link to constrained decodingskippable
Validity as a hard constraint
Game content almost always has hard invariants: a level must be traversable, tiles must fit together, a dungeon must be connected. Classical PCG bakes them into generation (BSP guarantees connectivity by construction; Spelunky guarantees the path to the exit; WFC guarantees neighbor compatibility). A GAN learns a soft distribution of "looks like a good level" and easily violates hard invariants — and after-the-fact checking + resampling is expensive with no guarantee of convergence. Plus control: noise/grammars have knobs (frequency, rules, seed), a GAN has a latent that's hard to connect to "make the passage wider". So for structural content the hand-written generator + constraints wins; ML enters where the distribution is too rich for hand-written rules (textures, natural images, dialogue) and there are few hard invariants.
The same trick in LLMs: constrained decoding
"Generate-with-constraints > generate-then-check" is literally structured/constrained generation in LLMs: grammar-constrained decoding, JSON-schema/regex enforcement, masking inadmissible tokens at every step guarantee valid output by construction, instead of "generate freely → parse → reject → repeat". That's exactly the Spelunky insight carried over to token generation: bake the invariant into the process. And fBm/octaves ↔ the role of structural priors and multiscale/frequency decompositions in ML (Fourier features, positional encodings, multi-resolution). The meta-lesson is general: a cheap algorithmic generator with constraints often beats an expensive trained one when the domain has hard validity rules.
ML / AI (your domain): PCG-vs-GAN is the content side of the whole thesis: for structural generation with hard invariants, hand-written rules + constraint satisfaction beat a trained generator (validity, control, cost). The direct bridge is constrained/structured decoding in LLMs: grammar/JSON-schema/regex enforcement and token masking guarantee valid output by construction — exactly "generate-with-constraints > generate-then-check-and-retry" (the Spelunky insight at the token level). The general pattern is a generative model + hard constraints (diffusion with guidance, program synthesis with types, constrained sampling). fBm/octaves ↔ structural priors and multiscale/frequency decompositions (Fourier features, positional encodings). "Store the function, not the output" (a world from a seed) ↔ implicit representations/INRs and compression as generation. And the meta-lesson — an algorithmic generator with constraints is often better than a trained one when the domain is structural — is the same "when ML isn't the answer" discipline as in RL agents, only about generation.
Design/content: constrained randomness = controlled variety; knobs (frequency, rules, seed) matter more than "magic" — reproducibility and editability.
Systems: "store the function, not the output" — generating on the fly saves memory/bandwidth (a world from a seed), with deterministic reproducibility by seed.
Principle: for structural content take algorithm + constraints by default; ML where the distribution is too rich for rules and there are few hard invariants; bake validity into the process rather than checking it afterwards.
Perlin, Simplex, OpenSimplex — which one do you take?
Why is PCG called "not random"?
random() gives garbage for content: incoherent terrain, impassable levels, blob trees. PCG that works is constrained randomness: the randomness is there, but it runs in channels set by structure and constraints. The smoothness of noise (nearby points → nearby heights) is a constraint on the "randomness" of terrain. BSP connectivity (there is always a corridor between neighbors) is a hard invariant. Spelunky's traversability is a baked-in rule. Neighbor compatibility in WFC is a constraint. It's the constraints that turn "noise" into "a world that feels made". Hence the practical shift in thinking: not "I'll generate randomly and see", but "which invariants must hold, and how do I build them into generation so they cannot be broken?". Randomness gives variety; constraints give quality; PCG is their product.Do GANs really lose to the classics for levels?
How does "generate-with-constraints" carry over to LLMs?
- Ken Perlin — "Improving Noise" (2002, Simplex); Stefan Gustavson — "Simplex noise demystified".
- "The Book of Shaders" (the noise chapter) + Red Blob Games — interactive explanations of noise/fBm.
- Przemyslaw Prusinkiewicz — "The Algorithmic Beauty of Plants" (L-systems).
- Derek Yu — the Spelunky generation postmortem (constrained randomness); Volz et al. — "Evolving Mario Levels in the Latent Space of a GAN" (2018).
- Module 11, §4 "Procedural Content Generation" (
11-ai-ml-in-games.md).