← Cellular Automata From First Principles

Grow Terrain from Local Height Rules

Binary caves ask:

wall or floor?

Terrain needs richer state.

Let each cell store a height:

0.0 = low
1.0 = high

Now local rules can smooth, raise, erode and classify terrain.

The challenge is not creating a pretty array.

It is creating a process whose output we can explain and control.


Start with a height field

import numpy as np


def random_heightmap(
    rows=120,
    cols=160,
    seed=42,
):
    rng = np.random.default_rng(seed)

    return rng.random(
        (rows, cols)
    )

Raw independent noise contains variation but little large-scale geography.


Add a local mean

def local_mean(grid):
    total = np.zeros_like(grid)

    for dy in (-1, 0, 1):
        for dx in (-1, 0, 1):
            total += np.roll(
                np.roll(
                    grid,
                    dy,
                    axis=0,
                ),
                dx,
                axis=1,
            )

    return total / 9.0

Blend toward the neighborhood:

def smooth_step(
    height,
    strength=0.35,
):
    mean = local_mean(height)

    return (
        (1 - strength) * height
        + strength * mean
    )

Repeated smoothing creates broad spatial regions.

But smoothing alone is not a terrain generator.

If we continue forever, it removes differences.


Add a competing process

Introduce persistent uplift:

uplift = np.zeros_like(height)

uplift[
    30:92,
    55:108,
] = 0.003

Then:

def terrain_step(
    height,
    uplift,
):
    height = smooth_step(
        height,
        strength=0.35,
    )

    height = height + uplift

    return np.clip(
        height,
        0.0,
        1.0,
    )

Now two influences compete:

smoothing
    -> reduces sharp local differences

uplift
    -> continually creates elevation

(Verified over 60 steps: values stay in range, and the uplifted region averages above the field mean — the competition is real, not notional.)

The renderer also applies a gentle radial edge falloff so the demonstration develops an island-like boundary.

Evolution of the terrain field

Smoothing-plus-uplift sits in a real lineage: local erosion simulators such as thermal erosion move material downhill wherever slopes exceed a stability angle, using the same neighborhood machinery with a physical criterion instead of a blend factor. This chapter’s version is the gentler cousin — controlled texture rather than physical simulation — and should not be mistaken for the erosive kind.


Inspect the final height field directly

Generated continuous terrain height field

This is important: the primary generated object is the height field.

Water, plains, hills and mountains are interpretations derived from it.


Derive semantic terrain classes

WATER = 0
PLAINS = 1
HILLS = 2
MOUNTAINS = 3


def classify_height(height):
    terrain = np.zeros_like(
        height,
        dtype=np.uint8,
    )

    terrain[
        (height >= 0.35)
        & (height < 0.55)
    ] = PLAINS

    terrain[
        (height >= 0.55)
        & (height < 0.75)
    ] = HILLS

    terrain[
        height >= 0.75
    ] = MOUNTAINS

    return terrain

This separates:

simulation / generation state:
continuous height

game-facing interpretation:
terrain class

That separation keeps the underlying process reusable.


Add a second continuous field

Height alone does not determine every world property.

Add moisture:

moisture = np.zeros_like(height)

moisture[:, :20] = 1.0

Diffuse it inland:

for _ in range(100):
    moisture = (
        moisture
        + 0.1 * laplacian(moisture)
    )

    moisture[:, :20] = 1.0

The diffusion rate 0.1 respects the stability limit from Chapter 12 (rate ≤ 1/4), and the ocean edge is re-imposed as a maintained source at every step.

Now each cell can be thought of as:

[height, moisture]

and biome classification can depend on both.

The grid is becoming a layered local state machine.


Global design goals should remain explicit

A game may require:

30-45% water
one large connected continent
flat spawn region
mountains away from spawn
river reaches ocean

Do not force a cellular smoothing rule to guarantee all of those.

Use:

local rules
    -> organic structure

measurement
    -> evaluate candidate

graph/search constraints
    -> guarantee global requirements

This is the same lesson we learned from cave generation.


Measure the world

def terrain_stats(
    height,
    water_level=0.35,
):
    return {
        "mean_height": float(
            height.mean()
        ),
        "height_std": float(
            height.std()
        ),
        "water_fraction": float(
            np.mean(
                height < water_level
            )
        ),
        "mountain_fraction": float(
            np.mean(
                height > 0.75
            )
        ),
    }

Now seeds and parameter sets can be searched instead of judged only by screenshots.


One idea to keep

Terrain generation becomes easier to reason about when each component has an explicit role and an explicit scope:

ComponentRoleModelsDoes not model
height field + smoothingorganic spatial texturecorrelated terrain variationreal erosion physics
uplift + clippingpersistent elevationhighlands that survive smoothingtectonic process
moisture diffusionsecond interacting fieldinland falloff for biomeshydrology
semantic classesgame-facing interpretationwater/plains/hills/mountainsany dynamics at all
global constraintsdesign guaranteesconnected continents, spawn rulesemergence

Local CA-style rules are excellent at producing spatial texture.

They do not need to carry every high-level requirement themselves.

In the next chapter we will use the same local machinery without pretending to simulate a world at all: we will deliberately treat cellular rules as visual texture generators.


Research

  • Beneš, B. — Parallel Implementation of Terrain Erosion Applied to the Surface of Mars (AFRIGRAPH 2001). The cellular-erosion lineage this chapter’s smoothing belongs beside: per-cell neighborhood operations that move material above a stability threshold, processed in parallel over the height field. Read it to see what turns local smoothing into a physical claim — and what this chapter deliberately leaves out. https://cs.purdue.edu/homes/bbenes/papers/Benes01AFRIGRAPH.pdf

  • Zenil, H. & Martinez, G. J. — Cellular Automata (Scholarpedia). Covers continuous-valued extensions beyond the finite-state definition and the reaction-diffusion frame — the definitional context for treating height and moisture fields as legitimate cellular-automaton state. http://www.scholarpedia.org/article/Cellular_automata