# Grow Terrain from Local Height Rules # Book: Cellular Automata From First Principles # Chapter URL: https://programmer.ie/books/cellular-automata-from-first-principles/16-chapter/ # Prose fingerprint (sha256, normalized): 6df7ff3e1ed269d4243c28630caaad44a08c4dc6ada49b91713c28651efdbc2e # Snapshot generated at build time by Hugo. This is the complete rendered prose # of the chapter; it is not a transcript of the source file. 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. 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 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: Component Role Models Does not model height field + smoothing organic spatial texture correlated terrain variation real erosion physics uplift + clipping persistent elevation highlands that survive smoothing tectonic process moisture diffusion second interacting field inland falloff for biomes hydrology semantic classes game-facing interpretation water/plains/hills/mountains any dynamics at all global constraints design guarantees connected continents, spawn rules emergence 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