# World map, approach 1: noise heightmap Open `index.html?seed=7&style=parchment` (styles: parchment, painted, hex, iso). ## How it works 1. **Elevation**: own 2D simplex noise, 6-octave fbm, domain-warped; ridged noise added where the land is already high, so mountain chains form; an elliptical island falloff pushes the edges under the sea. Grid 240x160 cells, 4 km per cell (960 x 640 km). 2. **Moisture**: a second fbm field, later raised along rivers and lakes. 3. **Water**: ocean = below sea level and connected to the map edge. A priority-flood fills every depression; filled cells deeper than a threshold become **lakes**. The same flood gives every land cell a downhill receiver, so the drainage tree comes for free. 4. **Rivers**: flow accumulation (rain = 0.3 + moisture) down the receiver tree; cells above a threshold are river, traced from each source down to the sea, merging where they meet; split where they cross a lake. Width grows with flow. 5. **Biomes**: Whittaker-style table on temperature (latitude and height) and moisture: desert, shrubland, grassland, forest, rainforest, swamp, taiga, tundra, hills, mountain, snow, beach. 6. **Places**: every land cell is scored (river +, coast +, lake +, fertile moisture +, slope -, height -, desert/tundra -), then picked greedily with a minimum spacing: 4 cities, 10 towns. 3 ruins and 3 dungeons go in wild biomes away from settlements. Natural features (mountain ranges, forests, lakes, deserts, swamps, hills) are the largest connected regions per biome group, labelled at their centre. 7. **Roads**: minimum spanning tree over the settlements plus a few extra links per city; each link is an A* path on the grid weighted by biome, slope and river crossings, with existing road cells 70% cheaper, so later roads merge onto earlier ones. Trails link each ruin/dungeon to the nearest settlement. Where a path crosses a river: a bridge (big river, road) or a ford. Settlements on different landmasses that are both ports get a sea route instead. Settlements on the same river get a `river` edge. 8. **Names**: a syllable generator (`shared/names.js`). Generation takes about 0.6 s in the browser; rendering 20-120 ms per style. ## Good - Looks like real geography: rivers start in the mountains and run to the sea, cities sit on rivers and coasts, roads follow valleys and bend round mountains. - Every number the game needs falls out: travel hours per road, bridges and fords, danger per place. - The heightfield is the single source of truth, so every style (parchment, painted, hex, iso, a future 3D one) is just a renderer. - Seeded and deterministic. ## Bad - Coastlines are noise-shaped: fine, but no specific "story" geography unless constrained (see below). - The grid shows at 4 km: a city sits on a cell; zooming in to a region needs the next level (site maps) anyway. - Some rivers end in a lake and start again; no deltas, no meanders. - Labels are placed with a simple collision check; on crowded seeds a feature label can still sit on a glyph. ## What it costs to make real - **Run it in the browser and post JSON** (recommended): the world-builder page generates, the player or DM rerolls until happy, then posts the JSON to Laravel, which stores it as locations rows. 1 evening to wire up, plus 1 for a "lock and save" UI. - **Port to PHP**: about 600 lines of straightforward array code; PHP 8 handles 38k cells fine (expect 1-3 s). 2-3 evenings, mainly the A* and the flood. Worth it only if the AI must generate worlds unattended on the server. - The renderers stay in the browser either way. ## How it fits the locations tree - The world is one `locations` row: `representation = world_map`, `gen_state = generated`, `representation_data = {kind:"heightfield", w, h, cell_km, sea_level, elevation:{u8-base64}, biome:{u8-base64, legend}, rivers:[{name, flow_max, pts}], crossings:[{x,y,kind}]}` (about 120 KB as JSON; could be gzipped or moved to a file). - Each city, town, ruin, dungeon and natural feature is a child: `representation = narrative`, `gen_state = stub`, `map_x/map_y` in heightfield cells, `danger_level` set. - Roads, trails, river and sea routes are `location_edges`: `path_kind`, `travel_cost` in hours on foot (or by boat), `bidirectional`, `curve` = polyline in cells. `sample.json` uses slugs (`from_slug`/`to_slug`) since ids come from the database. ## How the AI world-builder would use it - **AI names and describes, the generator places**: the generator decides where; the AI gets each stub with its context (coast, river, biome, neighbours, distance by road) and writes name and description, and later promotes a stub to a site map when the party goes there. - **AI gives constraints, the generator honours them**: "a mountain range in the north, a port city in the south-east, a big forest" become extra terms in the elevation (a ridge line, a raised blob), pins for a city (force a coastal cell in a region) and moisture boosts. These are cheap to add because every step is a field; the easiest is to bias the elevation before the pipeline runs. A demo is on the page: the selects "AI constraint: mountain range" (adds a ridge band in that region) and "port city" (strong placement bonus for coastal cells in that corner), also as URL parameters `mountains=north&port=se`. Side effect seen while testing: a ridge can dam a basin into a huge lake; lakes larger than 0.4% of the map are therefore kept as land, and the rivers then form a delta-like network across the basin.