qproj eye icon qproj design docs

Quell hex terrain, round 11: the generator as it stands

design · current · Procedural generation · edited 2026-11-04

Status: Round 11, 2026-10-13. Python experiments settled. Next: re-plan the Rust port against this.

This document consolidates rounds 1–10 into the current algorithm:

Where they disagree, this document wins.

Code: misc/hex-lab/src/hexlab/ (terrain.py, erosion.py, layout.py). Every experiment is a tagged hex-lab commit (exp/NN-name), with a summary in misc/hex-lab/experiments/ and figures in .docs/wiki/static/media/pcg/experiments/NN-name/. The current algorithm is the arm creeks2-trunk at tag exp/12-crossings, with the fixes in §7 (exps 13–14, arm trunk-sight). §8 is woodland, the next open problem.

Goals (unchanged)

Scale and units

Exp 06.

1. Terrain: regional erosion, northeast-Kansas character

Reference: Atchison County, Kansas. Glacial-drift uplands are finely carved by dendritic ravines and creeks set 2–5 m into the ground; there are no lakes.

Simulate a region, then crop the board.

  1. Region: 100 × 100 hexes (about 5 km square), one cell per hex edge.

    • It drains off every side (“rivers all round”), so there’s no global direction to read.
    • It starts nearly level: 0.2% regional fall, 4 m rolling rises about 1.5 km across, and 1 m white-noise roughness. The roughness breaks grid-straight channels.
  2. 250 erosion passes, each:

    1. lift the land uplift_m against fixed outlets;
    2. priority-flood so every cell drains downhill (pits get a tiny slope, a breach, never a pond);
    3. route steepest descent;
    4. cut channels by stream power, erodibility × area^0.5 × slope, where drainage passes channel_ha (2 ha);
    5. let slopes creep.

    Lifting against fixed outlets grows dendritic networks headward. A tilted plane gives parallel gullies instead, and a single outlet only carves about 1.5 km inland (exps 07–08).

  3. Choose the window by play features (exp 09). Score 60 random windows (any position far enough from the region’s edges, random rotation) on:

    • creek tiles in the playable area;
    • ford sites: creek tiles with banks within 3 m, spread 4 hexes apart;
    • natural tease spots: a viewer and target 3–6 hexes apart, the target at least 12 m lower, with a clear sight line;
    • a penalty for rivers (over 600 ha);
    • a weak bonus for 15–60 m of relief.

    Keep the best. Flat bottomlands aren’t punished.

  4. Heights are sampled cubically into the window raster at 2 cells per edge.

Cost: about 20 s per region in Python. Regions are cached per (settings, seed).

Creeks run on the dual lattice (tile centre to tile centre). The region’s channels at 40 ha or more are traced tile by tile: each channel cell maps to its nearest tile, and a creek tile’s downstream is the next tile down the channel. Gaps where a channel skips a tile near a hex corner are filled in, so every creek link joins neighbouring tiles (exp 11).

Colours in figures are absolute height, 0–120 m above the region’s outlets, and deliberately false-colour.

2. Layers (“liquid geometry”)

The displayed terrain is heightmap + model layer + trail layer:

Later, fine detail (smaller noise octaves, deep creek channels) goes in display layers too, kept small enough not to change routing.

3. Nodes (typed at placement)

NodePlaced byNotes
Spawnthe board’s middle tile, moved off a creekBecomes the player’s tile on regeneration.
Crossingfirst; see §4Bridges and fords. Not a POI or bench, no card. Never moves.
POI (6)suitability^spread (2) × up to 2× near water (e^(−hexes/2))Deck: key, NPC, Fear on random POIs; the rest are empty. The deck never changes.
Bench (3)bench scoreNot a spur by preference. The “benches together” rule is dropped and kept only as a statistic.

Exps 03, 10.

4. Crossings

Trails cross a creek only at crossing nodes. The hex side between two consecutive creek tiles is closed to trails; a crossing’s pad perimeter is the ford or bridge. crossings defaults to 2 and is a slider.

Placement (exp 12):

  1. On creek tiles only.
  2. First, any crossings needed to join parts of the board the creeks cut off, lowest banks first.
  3. Then the rest go on the main creek (at least half the biggest flow on the board), each as far as possible from the others. Two crossings land near opposite ends, so the creek is a long way round rather than a wall.
  4. Both banks: after the trail passes, any bank of a crossing pad without a trail gets one to the nearest trail or pad on that bank.

Creeks and crossings are terrain-stable, so they’re the fixed landmarks through a Fear.

5. Trails

Unchanged from rounds 5–8, minus escarpments and noise:

6. Regeneration

layout(ground, pins, seed) is one pure function; the first layout is the case with nothing pinned.

Pins:

Crossings are always fixed.

Partial regeneration: everything within 4 hexes of the player is pinned. Pinned trails cut by the radius leave ports, open ends that must join the new network. No dead ends were left in any exemplar.

A Fear: only the player and the crossings are pinned. Every other node moves and the trails regenerate.

Seeds: the terrain seed gives the region, the window and the first layout. Each regeneration uses (seed, count). Exps 04, 11, 12.

7. Fixes after round 11 (exps 13–14)

From a robustness audit of exps 10–12:

8. Woodland: oak–hickory forest

The board is oak–hickory forest, the deciduous forest of the eastern Great Plains’ edge, so sight lines run through and over trees. Wikipedia gives the species but no structure:

Working assumptions (no stats; adjust by feel):

Exp 14 result: with a solid 15 m canopy over 16–26 m of relief, no tease is visible: all 58 node pairs within sight range on seeds 2, 3 and 7 are hidden. Window ranking finds no natural tease spots.

Sight model (exp 15). The forest’s look in the game is unknown, so the generator models only how far you can see:

density is the knob to play with. Fully wooded, a line reaches about 170 m at density 1, 85 m at 3, and 43 m at 7. Teases mostly survive at 1 and almost never at 3 or more. None of this changes how the game draws the world: there’s no fog.

Teases in woodland (direction, not yet tried):

Knobs

KnobDefaultMeaning
columns, rows20, 20Playable board (hexes).
Hex edge33 mScale.
Region size100 hexesSimulated area.
uplift_m0.2Relief (0.6 gives about 2× the relief).
channel_ha2Ravine density.
Creek threshold40 haWhat counts as a creek.
Window candidates60Windows scored.
Window weightssee WindowWeightsCreek, fords, teases, river, relief.
pois, benches6, 3Node counts.
spread2POI pull to buildable ground.
view0.7Bench preference: quiet (0) to overlook (1).
bench_sharpness4Only the extremes score as benches.
water_boost1POI pull to water.
crossings2Creek crossings (slider).
teases, tease_detour2, 3As before.
Pin radius4 hexesPartial regeneration.
Displacement2–4 hexesHow far movable nodes move.

Effect on the Rust port

Done on feat/hex-paths, and carries over:

Changes in Rust:

Re-plan .docs/plans/done/quell-hex-terrain-plan.md from here.

Open questions

  1. Getting lost: forks or connectors (the Clinton Lake “ladder”), now on creek terrain.
  2. Creek-bed walking as a stable shortcut corridor.
  3. Finer ravines: a second erosion pass inside the window, or display-only detail.
  4. What stays during a regeneration (deferred): the pin radius versus “only a Fear reshuffles fully”.
  5. Very long teases across a creek (×17 on exp 12, seed 3): fine, or should they be capped?
  6. Woodland teases (§8): try vistas and creek corridors, with tall landmarks for key locations.