Added: Initial world generation tool
This commit is contained in:
@@ -7,6 +7,30 @@ say so.
|
||||
Status: `Decided` · `Supersedes` (contradicts an earlier decision, names it) · `Deferred` (deliberately not now,
|
||||
with a trigger)
|
||||
|
||||
## 2026-09-17 — Off the ladder, the terrain generator leaves numpy and gains a drainage network
|
||||
|
||||
| # | Decision | Where |
|
||||
| --- | --- | --- |
|
||||
| D-52 | **The continent mask is thresholded, not multiplied, into the uplift field: where the land ends does not decide how fast it is rising.** The rate was `r * l` with `l` the smoothstep continent mask, so uplift tapered to zero across the shore and every coastline on the map was the lowest-uplift ground on the map by construction. Steady state is `S = U/(K*A^m)`, so ground with no uplift grades to no slope: the surf had a flattened strip to work in wherever it looked. The mask now answers only "is this cell sea", which is the yes-or-no the solve needs for its base level, and a range that runs out to the water rises at range rates right up to the waterline. Measured, seed 7 at 1400 with everything else held: surf cut 23.32 → 38.87 Mm³, planed 3.0 → 3.9 km², new beach 3.41 → 5.59 km²; seed 9342 the same in direction, 22.42 → 30.35 Mm³. `Corrects a measurement, not only the code`: the "mean cliff 2 m" that motivated this was a broken metric — it measured the drop from a cell to its seaward neighbour, which is a gradient, and at the angle of repose one cell of a 10 m grid is 7 m, so it could never have exceeded 7 whatever the coast did. Replaced by backshore height, the land's elevation one to two surf reaches inland, median and P90; on that metric the *old* build already read 88 m at P90, so the taper was never the difference between cliffs and no cliffs — it flattened the ~100 m strip the surf works in, so the cliff began a hundred metres inland instead of at the water. The slope–area fit moved −0.480 → −0.312 on seed 7 and −0.698 → −0.720 on seed 9342, in opposite directions and both inside a seed-to-seed spread that is several times larger, so it is not a regression; it is a warning that the fit at five or six bins cannot judge a single change on a single seed. `Decided`. | [Terrain.md](Terrain.md) |
|
||||
| D-51 | **The coastline becomes a pass of its own, run after the fluvial solve, and the continent outline gains the octaves that make it a coast rather than a blob.** The coast was a line in a mask: the sea floor dropped to a flat plane at −180 m in one step, the land met the water at whatever angle the last erosion step left, and no process knew the shoreline was there. `internal/coast` adds three, each derived rather than drawn. A shelf, from an exact signed distance transform: gentle to a break, then a continental slope to the abyss, its width read off the relief standing behind each stretch of shore, so a plain gets a wide shelf and a range that reaches the water gets a narrow one. A surf, which planes the land to a shore platform within a reach set by how open the water is — the cliff is the step where the reach ends, so its height is whatever the land behind it stands at. And a sediment budget: what the surf cuts is counted, carried a drift length along the shore, and laid in sheltered shallow water, with river mouths delivering their own load in proportion to what they drain, which is what makes a delta. It runs after the solve because two of the three need the finished terrain, and it owns the sea floor outright — `uplift.Result.Bathymetry` is gone, and ocean cells stay at sea level for the whole solve, which is the invariant that stopped rivers cutting to −180 m. Shelter is measured as effective fetch over the *seaward* half-space against fixed absolute anchors, not as a percentile of the map: casting in every direction counts the land behind the shore as shelter, and a percentile is a global statistic that two tiles would disagree about. Measured on seed 7: the outline's octave gain, swept with everything else held, gave 64 km of shoreline at 0.50, 81 at 0.58, 96 at 0.62 and 114 at 0.66, while the fetch's view of the median stretch of coast went 1.00, 0.98, 0.84, 0.51 — five octaves put the finest coastal feature at 450 m, which is a smooth blob with nothing to shelter anything from anything. 8 octaves at gain 0.62 is the setting that has islands, inlets and headlands without the outline breaking into speckle. The whole pass costs 83 ms at the manifest's geology grid. `Decided`. | [Terrain.md](Terrain.md) |
|
||||
| D-49 | **Plains are made by lowering the intraplate uplift rate an order of magnitude, not by tuning erosion; and the hillslope law becomes Roering nonlinear diffusion with the repose clamp demoted to a constraint the diffusion smooths over.** Steady state is `S = U/(K*A^m)`, and with `critical_area_m2` at 0 that holds down to a single cell, so at K 5e-5, m 0.5 and a ~10 m geology cell an intraplate rate of 0.25-0.9 mm/yr puts *every divide on the map* at 32 degrees or past the 35 degree repose angle. Measured on the old numbers: 81 % of the land in the >0.5 mm/yr class, the plain class 1 % of it and all sea cliff. The plains were never over-dissected; they were being uplifted at mountain rates, and for n = 1 the uplift rate alone fixes the hillslope angle. `intraplate_mm_yr` 0.25 -> 0.03 and `intraplate_swell_mm_yr` 0.9 -> 0.08 keeps the ~2.5-fold swell that puts divides on a plain while making the mountain-to-plain ratio 30-fold, which is what real ones are; `relief.amplitude_m` lo 50 -> 15 so the plains do not start as hills the run must erode away. Measured after: plains 42 % of land at 0.8 degrees median and 9 m of relief over 500 m. The repose clamp stays in the step loop but nonlinear diffusion runs *after* it every step, because the clamp cuts along the eight D8 directions and leaves grid-aligned pyramid faces; a symmetric five-point stencil rounds them off before the next step. Clamping once at the end instead was measured and is worse. `Supersedes` the three-fold mountain-to-plain ratio D-47's uplift notes settled on. `Decided`. | [Terrain.md](Terrain.md) |
|
||||
| D-50 | **The D8 router's flat-ground tie-breaks are jittered by a hash of the cell index, and `critical_area_m2` stays at 0.** Across a filled flat the only gradient is the priority-flood's millimetre of epsilon, applied in the order the flood happened to reach the cells, so the router draws the flood's own traversal geometry as ruler-straight diagonal rivers — the polygonal network that killed the first attempt at flat plains, and the dominant artefact once D-49 made the plains genuinely flat. The epsilon is scattered per cell by plus or minus half and the choice between two equally steep neighbours is broken by a hash of the cell and direction rather than by the fixed `dx8` order, both deterministic from the seed (cross-cutting rule 12). Measured: the slope-area fit went from R² 0.055 to 0.459 normalised, 0.039 to 0.664 raw, at no cost. A channelization threshold was re-measured after D-49 and still fails — at 1e4 the plains go back to 7.0 degrees and half the rolling class pins against the clamp — because the hillslope it creates must shed its uplift by diffusion and at D 0.02 it cannot. It stays at 0 until there is a transport law to pair it with. `Decided`. | [Terrain.md](Terrain.md) |
|
||||
| D-48 | **The world's canvas becomes 7141 vertices at 200 cm, elevation −512…1536 m, and it keeps its coastline.** 7140 = 255 × 28, so the engine's importer gives 28×28 components of 255 quads by its own rule (D-45) and the side stays 14 280 m, identical to today's, so nothing `create_world.py` places moves; cells go from 3.5 m to 2.0 m. A 2048 m elevation span *is* Z scale 400, so the incoming spec's canvas is expressed in the manifest's metres contract with no loss, at 3.125 cm precision. The ceiling drops from 2800 m to 1536 m deliberately: 2800 m over 14 km is a Himalayan gradient, and 800–1500 m of relief is what a fluvial landscape this size looks like. `Supersedes` the 2600 m crests the noise was tuned for. The coast stays and sea level is the fixed base level on every ocean cell, which is a better-posed boundary for the stream-power solve than one outlet edge and closes the spec's own open question. A full run holds today's five-minute bar, which sets `fill_every` at 50. `Decided`. | [Terrain.md](Terrain.md) |
|
||||
| D-47 | **The heightmap generator becomes a standalone Go CLI in `Tools/Terrain/`, and stream-power fluvial erosion replaces particle erosion as the thing that shapes the land.** Droplets carve the path a droplet takes; they do not make a drainage network, which is why the world reads as weathered noise rather than as terrain with a history. A Braun & Willett implicit solve over an uplift field on a coarse geology grid does, and plates, faults and lithology exist to feed it; particle erosion, thermal weathering and strata survive as detail passes at full resolution. The core is engine-free and deterministic from a seed (cross-cutting rule 12), and `World.json` stays the single contract, with its `erosion` block replaced by `pipeline`. Retires `heightmap_noise.py`, `heightmap_erosion.py`, `heightmap_io.py` and `generate_heightmap.py`, about 780 lines of tuned numpy whose lessons are carried into the port by name; keeps `create_world.py`, `dump_level.py`, `world_manifest.py` and `ULandscapeAuthoringLibrary`. `Supersedes` D-46 on the language and on which pass shapes the land. | [Terrain.md](Terrain.md) |
|
||||
|
||||
## 2026-09-17 — Off the ladder, the world's terrain is simulated, not only sampled
|
||||
|
||||
| # | Decision | Where |
|
||||
| --- | --- | --- |
|
||||
| D-46 | **The heightmap goes through geological passes after its source, in numpy, at the world's resolution.** Domain-warped ridged noise and cellular crest lines give the uplift; particle hydraulic erosion (a coarse pass for valleys, a fine pass for gullies), mass-conserving thermal weathering at an angle of repose and a strata hardness map shape it; flow, wear, deposition and curvature maps come out beside the weightmaps and drive the layer painting. All of it is engine-free so the source stays swappable, and `"enabled": false` in the manifest skips it for a real DEM. `Decided`. | [RawContent/World/README.md](../RawContent/World/README.md) |
|
||||
|
||||
## 2026-09-16 — Off the ladder, the world's terrain
|
||||
|
||||
| # | Decision | Where |
|
||||
| --- | --- | --- |
|
||||
| D-45 | **The world heightmap is 4081 vertices a side, not Epic's 4033.** The engine's importer picks the largest section size that divides the quad count, preferring one section per component; 4080 divides by 255 and gives 256 components, 4032 divides only by 63 and gives 4096. A resolution change stays on `255 x N + 1` or `127 x N + 1`. | [RawContent/World/README.md](../RawContent/World/README.md) |
|
||||
| D-44 | **`L_World` is a product of a manifest, generated PNGs and one script, never hand-edited.** `RawContent/World/World.json` fixes the size and what a 16-bit value means in metres (world Z 0 is sea level); the height comes from a named source, seeded noise now, a real heightmap by editing the source block; the paint layers are always derived from the height. The level is dressed as Elite_RockyMeadows dresses its own maps, with the numbers read from those maps rather than guessed. `Decided`. | [RawContent/World/README.md](../RawContent/World/README.md), [Worklog](Worklog.md) |
|
||||
|
||||
## 2026-09-16 — Step 2, the telemetry seam
|
||||
|
||||
| # | Decision | Where |
|
||||
|
||||
Reference in New Issue
Block a user