Tooling
This commit is contained in:
@@ -0,0 +1,937 @@
|
||||
# Painted templates
|
||||
|
||||
A template is a picture of a world and a legend saying what its colours mean. The generator turns the two
|
||||
into terrain: `Tools/Terrain/bin/terrain.exe plan` reads them and says what a bake would involve, and
|
||||
`terrain bake` solves it. The contract between them is [`../Planet.json`](../Planet.json).
|
||||
|
||||
`World.json` next to it is a different world — the square 14.28 km canvas the numpy pipeline and
|
||||
`create_world.py` still build. The two share nothing but their defaults, and nothing here touches that path.
|
||||
|
||||
```bash
|
||||
# a minute: read the painting, cut the planet into regions, solve nothing
|
||||
Tools/Terrain/bin/terrain.exe plan
|
||||
|
||||
# an hour or two: solve every region and composite the world. Run it detached.
|
||||
# The output goes into the next free RawContent/World/Bake_NNN, so a re-bake never
|
||||
# destroys the one before it - the interesting question is almost always "what did
|
||||
# that change", and answering it needs both.
|
||||
Tools/Terrain/bin/terrain.exe bake
|
||||
|
||||
# one landmass, short, for tuning the legend
|
||||
Tools/Terrain/bin/terrain.exe bake --only 11 --steps 200 --out /tmp/try
|
||||
|
||||
# the same painting as a different world: massifs, rock, faults and coastline detail all re-rolled
|
||||
Tools/Terrain/bin/terrain.exe plan --seed 9342
|
||||
|
||||
# the upland fabric at another size, without editing the manifest
|
||||
Tools/Terrain/bin/terrain.exe plan --massif-km 5
|
||||
|
||||
# the painting tool: brushes that carry the legend's numbers, and Plan as a button
|
||||
Tools/Terrain/bin/terrain.exe studio # http://127.0.0.1:8099
|
||||
```
|
||||
|
||||
## The studio
|
||||
|
||||
`terrain studio` is the tool to reach for first. It exists because the two halves of a painted world used to
|
||||
live in different programs: the shapes in an image editor that knows nothing about uplift rates, the meanings
|
||||
in this legend, which cannot show you where they land.
|
||||
|
||||
The brushes **are** the classes. Picking `highland` is picking 0.25 mm/yr, and the panel tells you that is
|
||||
11.7° of ground and reads as hill country while you are painting it — with the 32° divide angle beside it in
|
||||
grey, because that is where the number comes from and what the repose clamp eventually binds against. So the
|
||||
table in the section above stops being something to look up. Change a number and every swatch re-reads
|
||||
instantly; nothing is written until you press a Save button.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| paint | left-drag. `[` and `]` change the brush size |
|
||||
| pan | shift-drag, middle-drag or right-drag |
|
||||
| zoom | wheel |
|
||||
| Plan | runs the plan **against what is on screen**, not what is on disk, and prints the class table, the seam check and the region cuts |
|
||||
| the map buttons | draw `class`, `uplift`, `regions`, `erodibility` or `overlay` over the canvas, each with a key under it. Press again, or Escape, to go back to painting |
|
||||
| layers | the two tabs at the top, or press **o**. `classes` is the geology; `overlay` is the annotation layer, where the brushes are its marks and the last one is an eraser. See **The overlay** |
|
||||
|
||||
**Plan is seven seconds the first time and about a third of a second after that**, as long as all you changed
|
||||
was a number. That is the tuning loop, and it is worth knowing why it works: the classification, the despeckle,
|
||||
the coast mask, the projection and the region cuts depend on the painting and on the class *colours* only — an
|
||||
uplift rate decides how the maps are coloured in and nothing else — so a plan that differs from the last one
|
||||
only in the legend's numbers reuses the whole of it. Touch the painting, or any of the coast or margin
|
||||
settings, and the next plan is a full one again. The report says which you got.
|
||||
|
||||
Rendering the maps smaller does **not** make it quicker, which is worth stating because it is the obvious
|
||||
guess. Measured on the 100 km template, prepare is flat at about 6.5 s from a 400 px map to a 2400 px one, and
|
||||
it breaks down as classify 0.09 s, dissolve strokes 0.93, despeckle 1.52, the coast mask 1.62, project 0.05
|
||||
and `region.Build` **2.68** — and the last one works on the 76-million-cell planet grid, which the size of the
|
||||
painting and the size of the maps both have nothing to do with.
|
||||
|
||||
Three things about it are deliberate:
|
||||
|
||||
- **The brush is hard-edged and writes exact colours.** No antialiasing and no soft edges, because a blended
|
||||
pixel is not a colour between two classes — it is a pixel that classifies as whichever *third* class happens
|
||||
to sit near the midpoint. See the note on the coastline below; that defect is real and this is where not to
|
||||
create it.
|
||||
- **The canvas wraps.** Paint off the left edge and it arrives at the right, because the map is a cylinder.
|
||||
The first template disagrees with itself on 9.4 % of its rows and this is how that gets fixed.
|
||||
- **There are two sheets, and only one of them is geology.** The class painting is what the solve reads; the
|
||||
overlay rides on top of it and, `coast_jitter` aside, changes no height anywhere. It is dimmed while the
|
||||
brush is on the classes so that a road can never be mistaken for something the solve will act on.
|
||||
- **It never overwrites anything, and the base map least of all.** Saves go to the next free `Map3_001.png`,
|
||||
`Map3_002.png`, ... beside the template, and the manifest is repointed at the new one — the same convention
|
||||
as `Bake_001`, for the same reason: the interesting question is almost always "what did that change", and
|
||||
answering it needs both. Rolling back is pointing `planet.template` at an earlier one. A painting is
|
||||
something you made by hand and there is no undo for it outside the studio, and writing back over a *JPEG*
|
||||
would additionally re-create the blended boundary pixels the despeckle pass exists to remove, compounding
|
||||
them on every save.
|
||||
|
||||
Legend and manifest saves **patch the text** rather than rewriting the file, so the commentary in both
|
||||
survives and a change still shows up as a small diff.
|
||||
|
||||
### Baking from the studio
|
||||
|
||||
The **Bake** panel runs the real solve on the painting as it is on screen, and you can watch it. Because the
|
||||
geology is decomposed per landmass, each one comes out whole — so the preview redraws every time a region
|
||||
lands and the world fills in a continent at a time. The first one out tells you whether the numbers are right;
|
||||
the other seventeen do not have to finish for you to know.
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| regions | blank bakes everything (~2 h on the 100 km template). A few ids — `11,13` — is the quick version, and the ids are the ones in the regions key |
|
||||
| steps | 0 is the manifest's full count. A few hundred is under-eroded but enough to see the shape |
|
||||
| jobs | how many landmasses solve at once. The wall time is set by the largest single region, which runs at about one core, so this helps least on exactly the template that needs it most |
|
||||
| Cancel | stops it. Regions in flight stop at the end of their current step, so the wait is one step of the biggest one |
|
||||
|
||||
A finished bake goes to the next `Bake_NNN`. A **cancelled** one goes to `Partial_NNN` instead — the regions
|
||||
that finished are complete and worth keeping, everything else in it is still at sea level, and the separate
|
||||
name is so that nothing downstream (`tiles --bake` takes the newest `Bake_NNN`) can mistake one for the other.
|
||||
|
||||
## The picture
|
||||
|
||||
Flat cylindrical: **X wraps** — the left and right edges are the same meridian, so a landmass may straddle
|
||||
them and comes out whole — and **Y does not**: the top and bottom rows are the poles. Any aspect works; the
|
||||
paint is never stretched to 2:1.
|
||||
|
||||
**The painting has to wrap too, and this is the thing to check first.** The left and right edges are the same
|
||||
meridian, so anything that does not continue from one to the other is a real discontinuity down one line of
|
||||
the world — and it is the one defect you cannot see by looking at the picture, because the two edges are as
|
||||
far apart on screen as they can be. `terrain plan` measures it:
|
||||
|
||||
```
|
||||
wrap the left and right edges are the same meridian: they disagree on 355 of 3761 rows (9.4%),
|
||||
261 of those land against water, and 48 px in the outermost columns match no class.
|
||||
THAT IS A VISIBLE SEAM. The generator wraps; the painting has to as well.
|
||||
```
|
||||
|
||||
That is `Map3.jpg` as it stands. The crater island crosses the seam perfectly — sampled at the geology grid,
|
||||
the heights run continuously from the last column into the first — but the islets near the left edge were
|
||||
drawn touching `x = 0` with nothing to meet them at `x = W-1`, so the world has a 400 m cliff down the seam
|
||||
wherever that happens. Painting round the edge, or nudging those islets inwards, fixes it.
|
||||
|
||||
PNG is better than JPEG, and the seam is where it shows. JPEG bleeds colour across every class boundary, and
|
||||
two legend colours closer together than the bleed will swap pixels where they meet — survivable in the middle
|
||||
of the map, but on the outermost columns a lossy encoder leaves a halo, and a halo on the seam is a stripe of
|
||||
the wrong class down the one line of the world that cannot hide it. `Map3.jpg`'s first two columns are
|
||||
lighter than the ocean behind them, which the classifier reads as shelf: a 400 m ledge two pixels wide,
|
||||
running the height of the map.
|
||||
|
||||
The paint does not have to match the grid. `Map3.jpg` is 7738 px across a 100 km world, so a pixel is 12.9 m
|
||||
against an 8 m geology cell: the painting is slightly *coarser* than the simulation, which is the right way
|
||||
round. The rule from `Docs/Terrain-Next.md` §3.2 is that the painted map owns the wavelengths above its pixel
|
||||
size and noise owns those below.
|
||||
|
||||
## Paint the uplift, never the height
|
||||
|
||||
This is the one thing to understand before painting anything. A class does not say how high the ground is; it
|
||||
says **how fast the rock is rising** and **how easily it erodes**. The simulation then produces the terrain.
|
||||
|
||||
Painting a heightmap instead does not work and the reason is worth knowing: a stream-power solve handed a
|
||||
painted surface erodes it into something else within a few hundred steps, and what comes out has no
|
||||
relationship to what was drawn — while the drainage network, which is the entire reason this generator exists,
|
||||
is thrown away. Paint intent; get rivers.
|
||||
|
||||
Rivers cannot be painted either. A river is an *output* of the drainage solve. What does work is biasing:
|
||||
raise `k_mult` along a line so the water finds the soft rock, and the solve chooses to put a river there for
|
||||
its own reasons — and the result is still a coherent network.
|
||||
|
||||
## The legend
|
||||
|
||||
One JSON file beside the image. Keys beginning with an underscore are comments; any other unknown key is an
|
||||
error, because a misspelt key silently ignored is a class quietly running on the default.
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"image": "Map3.jpg",
|
||||
"warn_distance": 60, // how far in RGB a pixel may sit from every class before the run says so
|
||||
"classes": [
|
||||
{ "name": "ocean", "rgb": [91, 175, 185], "sea": true, "depth_m": 512 },
|
||||
{ "name": "lowland", "rgb": [153, 204, 102], "uplift_mm_yr": 0.08, "k_mult": 1.0 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
| key | meaning |
|
||||
| --- | --- |
|
||||
| `name` | what it is called in the reports and the maps |
|
||||
| `rgb` | the painted colour. Every pixel takes the nearest one, so there is no unclassified pixel |
|
||||
| `sea` | water. The solve holds every sea cell at base level for its whole run |
|
||||
| `depth_m` | sea only: how deep the open water is, in metres below sea level, positive |
|
||||
| `uplift_mm_yr` | land only: rock uplift, and **the hillslope angle**. See below: `tan(divide) = 2.5 × rate ÷ k_mult` at an 8 m cell, and the *ground* is a third of that in tangent — so 0.08 is 3.8° of median hillslope with 11.3° at its divides, and 0.25 is 11.7° with 32° |
|
||||
| `k_mult` | land only: the multiplier on stream-power erodibility. Soft rock above 1, hard below |
|
||||
| `stroke` | decoration rather than data — dissolved into whichever real class is nearest |
|
||||
| `edge_class` | a stroke touching the top or bottom row of the map is not a stroke; it becomes this class |
|
||||
| `derived` | never painted: no colour matching, exists only as something else's `edge_class`. Its `rgb`, if any, is for the diagnostic maps |
|
||||
| `coastal_plain_km` | land only: how far inland the rate ramps up to its full value, so the range sits behind a plain |
|
||||
| `coastal_floor_mm_yr` | land only: the rate at the waterline. Defaults to 0.02, never raised above the class rate |
|
||||
| `massif` | land only: `{floor_mm_yr, fraction}` — the class breaks into plain and upland instead of holding one rate everywhere. **Read the next section before writing a legend without one** |
|
||||
| `faults` | land only: `{per_1000km2, throw_m, length_km}` — traces placed in this class's ground. Absent means none, which is right for a plain. See **One painting, many worlds** |
|
||||
| `lithology_mix` | land only: 0 to 1, how much of the planet's rock field shows through here. Default 1; 0 is uniform rock, which is what an ice cap or a crater floor should be |
|
||||
| `crater` | land only: `{rim_m, floor_m, rim_at, wall_at}`, stamped on the finished terrain |
|
||||
| `snow` | land only: permanently under ice. **Display and material only** — no height moves, no pass reads it, and it is drawn with the palette's `ice` colour. What makes a polar cap *terrain* is its uplift rate and its `detail` block like any other class |
|
||||
| `detail` | land only: `{droplets_per_cell, strata_contrast, amplitude_m}`, what the detail passes do differently on this ground |
|
||||
|
||||
### A rate is an angle, and one rate is one landscape
|
||||
|
||||
This is the second thing to understand, and it is the one that caught the first painted planet out.
|
||||
|
||||
For `n = 1` the steady-state slope is `S = U / (K·A^m)`, and with `critical_area_m2` at 0 that law reaches down
|
||||
to a single cell — so at a drainage divide `A` is one cell and the uplift rate, on its own, fixes the hillslope
|
||||
angle. At the 8 m geology cell and `K` 5e-5 it works out at:
|
||||
|
||||
```
|
||||
tan(angle) = 2.5 × uplift_mm_yr ÷ k_mult
|
||||
```
|
||||
|
||||
**But a divide is the steepest place in a catchment, and almost none of a map is divide.** `A` is smallest at
|
||||
the top of a hillslope and grows all the way down, so the divide angle is the *ceiling* on a class and not its
|
||||
landscape. Measured on a 600² grid of 8 m cells at 1000 steps with the manifest's own constants, the median
|
||||
slope comes out at a third of it in tangent, and the ratio barely moves over a factor of twenty in rate
|
||||
(0.34, 0.33, 0.33, 0.32). So there are two columns, and **the one to steer by is the second**:
|
||||
|
||||
| `uplift_mm_yr` | divide | typical ground | reads as |
|
||||
| --- | --- | --- | --- |
|
||||
| 0.012 | 1.7° | 0.6° | plain |
|
||||
| 0.02 | 2.9° | 0.9° | plain |
|
||||
| 0.04 | 5.7° | 1.9° | plain |
|
||||
| 0.08 | 11.3° | 3.8° | rolling |
|
||||
| 0.10 | 14.0° | 4.7° | rolling |
|
||||
| 0.25 | 32.0° | 11.7° | hill country |
|
||||
| 0.28 | 35° | 12.9° | the angle of repose at a divide — above this the clamp shapes the ground, not erosion |
|
||||
|
||||
`terrain plan` prints both for every class in the legend, along with the word in the right-hand column, and so
|
||||
does the studio panel as you type. Read it before starting a bake; it is four seconds against two hours.
|
||||
|
||||
**Reading the divide column as the landscape is how a legend ends up two or three times too hot everywhere**,
|
||||
and it is the same mistake as the one in the paragraph below, one level up.
|
||||
|
||||
**Do not read `internal/stats`' "plain below 0.1 mm/yr" as a description of terrain.** It is a reporting bucket
|
||||
calibrated for the old procedural path, and 0.1 mm/yr is a fourteen-degree hillslope: hill country wherever it
|
||||
is painted. That sentence is how the first planet's `lowland` ended up ten times too hot.
|
||||
|
||||
### Flat ground, which needs two rates and not one
|
||||
|
||||
Because a class was one rate over every cell an author painted with it, a landmass painted one colour came out
|
||||
**uniformly** dissected — from the waterline to the summit, at whatever angle the rate named, with no flat
|
||||
ground anywhere on it. That is not what a continent looks like. Europe away from the Alps is a plain at a
|
||||
fraction of a degree with isolated massifs standing out of it, and what separates the two is not the rate, it
|
||||
is that the rate is not the same everywhere.
|
||||
|
||||
So a class carries a `massif` block: the class rate is re-read as the rate a *massif* reaches, `floor_mm_yr` is
|
||||
the plain between them, and `fraction` is how much of the class stands above the midpoint of the two.
|
||||
|
||||
```jsonc
|
||||
{ "name": "lowland", "rgb": [153,204,102], "uplift_mm_yr": 0.08,
|
||||
"massif": { "floor_mm_yr": 0.012, "fraction": 0.16 } }
|
||||
```
|
||||
|
||||
A sixth of it is rolling ground at 3.8° (11.3° at its divides); the rest is a 0.6° plain that tops out about
|
||||
30 m above the sea over a whole continent. Half the fraction again — a twelfth — reaches 0.08 outright, and
|
||||
half again above that is off the plain at all.
|
||||
|
||||
**So "the lowlands come out too hilly" is almost always one of two numbers, and neither of them is the floor.**
|
||||
It is `fraction`, because the ramp opens at `1 - 1.5 × fraction` in probability, so 0.16 leaves about a
|
||||
*quarter* of the class off the plain rather than a sixth; or it is `uplift_mm_yr`, which is what those raised
|
||||
parts climb to. The floor itself has been measured baking out at 0.58° median with 4 % of it over three
|
||||
degrees, which is a plain by any reading.
|
||||
|
||||
Three things worth knowing about it:
|
||||
|
||||
- **The floor wants to be about a tenth of the rate.** It is the number that decides whether the class has flat
|
||||
ground at all. If a bake comes out too busy, though, drop `fraction` first: the floor is already a plain and
|
||||
lowering it further only flattens ground that was flat.
|
||||
- **`fraction` is a share of the planet's surface, so it is only the *expected* share of any one island.** A
|
||||
small island may get all of a massif or none of it, exactly as it would if it were a real island that
|
||||
happened to sit on or off an orogen. That variance is the point; normalising it per landmass would hand
|
||||
every island its quota of hills, which is the thing this exists to stop.
|
||||
- **There is one fabric for the whole planet**, `planet.massif_wavelength_km` in `Planet.json`, and every class
|
||||
cuts the same one at a different level. So a highland belt and the hills in the lowland beside it come out as
|
||||
the high and low parts of one structure — a foreland and its outliers — rather than as two unrelated noises
|
||||
meeting at a painted edge. Set the wavelength well below the size of a landmass: at 12.5 km against islands
|
||||
of 20–45 km one island came out entirely above the cut, which is the original defect over again, only
|
||||
smaller. 7 km puts several blocks across every continent.
|
||||
|
||||
A class with no `massif` block is one rate all over, which is what every class was before this existed. That is
|
||||
still the right answer for a small class, for a crater's flanks, and for anything an author would rather paint
|
||||
by hand.
|
||||
|
||||
### Coastlines, which are not drawn lines
|
||||
|
||||
A shore drawn with a bezier tool is smooth, and a real coast is not: it has bays inside bays inside bays, and
|
||||
how long it is depends on the ruler you measure it with. Projected straight, the painting's own smoothness
|
||||
survives all the way to the heightmap and the result reads as what it is — a shape somebody drew.
|
||||
|
||||
So the waterline is roughened before the painting is projected. Fractal noise is added to the signed distance
|
||||
from the shore and the sign is read again, so land juts out where the noise is positive and the sea reaches in
|
||||
where it is negative. Nothing away from the shore moves.
|
||||
|
||||
```jsonc
|
||||
// in Planet.json, not the legend: it is about the world, not about one colour
|
||||
"coast_jitter_px": 48, // how far the shore may move, template px
|
||||
"coast_jitter_wavelength_px": 384, // how wide the biggest bay is
|
||||
"coast_jitter_octaves": 5,
|
||||
"coast_jitter_gain": 0.55 // near 0.5: each scale as prominent as the last
|
||||
```
|
||||
|
||||
At 12.92 m a pixel that is bays about 5 km wide and up to 620 m deep. **The ratio of the two is the knob that
|
||||
matters** — amplitude far below wavelength gives a rough line, amplitude approaching half the wavelength gives
|
||||
a fjord coast. Tune it in the studio, which prints both in kilometres as you type. `0` projects the painting
|
||||
exactly as drawn.
|
||||
|
||||
Small islands are safe: the amplitude is capped per cell at two thirds of the widest land within reach, so an
|
||||
islet gets a ragged edge instead of being swallowed by a wavelength ten times its size.
|
||||
|
||||
**And a coastline you drew on purpose is exempt.** The argument above is true of a shore nobody thought about
|
||||
and false of one traced off a real map, so the amplitude is a *field* rather than a number: paint a
|
||||
`coast_jitter` mark from the overlay over the stretch you want kept and it stays exactly where you put it
|
||||
while the rest of the world is still roughened. Zero pins it; above one chews it harder, which is how a fjord
|
||||
coast is made without turning the whole planet into one. See **The overlay** below.
|
||||
|
||||
### A note on JPEG, which is not a style preference
|
||||
|
||||
The README says export PNG. Here is what it costs not to. Classification gives every pixel its nearest class
|
||||
in RGB, and a lossy codec blends across every boundary. On this template the blend of `surf` (221,238,238) and
|
||||
`lowland` (153,204,102) comes out at about (186,219,174) — whose distance to `desert` (238,221,153) is **53.8**
|
||||
against **77.9** to either of the colours it was actually mixed from. So the antialiased edge between the sea
|
||||
and the shore classifies as *desert*, and the map gains a one-pixel ribbon of it along every temperate coast:
|
||||
1607 pixels on this one.
|
||||
|
||||
One pixel wide that is invisible. It stopped being invisible the moment the coast mask arrived, because a
|
||||
stray pixel is still **land** — so it became the nearest land to a stretch of open water, and every cell the
|
||||
mask turned into shore inherited its class. An eleven-pixel band of desert appeared along a green continent.
|
||||
|
||||
A despeckle pass now removes any class that holds a one-pixel line with two others either side of it, which
|
||||
costs 0.068 % of the map and takes that count to zero. It is a safety net and not a licence: paint in the
|
||||
studio or export PNG, and none of this happens.
|
||||
|
||||
## The overlay
|
||||
|
||||
A second painting, the same size as the template and registered to it, for everything that is *placed on* the
|
||||
finished world rather than solved: where the forest is, where the village is, what a road follows, and which
|
||||
coastlines to leave alone.
|
||||
|
||||
It is a separate image rather than more colours on the template because the two answer different questions.
|
||||
Every colour on the template is geology — an uplift rate the solve answers for — and there is no uplift rate
|
||||
for a town. A mark has to be able to sit on top of any class without changing it.
|
||||
|
||||
```jsonc
|
||||
// Planet.json
|
||||
"overlay_legend": "Templates/Map3.overlay.json",
|
||||
"overlay": "Templates/Map3.overlay_001.png", // written by the studio; leave it out to begin
|
||||
```
|
||||
|
||||
```jsonc
|
||||
// Map3.overlay.json
|
||||
{
|
||||
"image": "Map3.overlay.png",
|
||||
"match_distance": 40, // an opaque pixel further than this from every mark is dropped
|
||||
"min_area_px": 24, // below this a blob is a speck and is not reported
|
||||
"marks": [
|
||||
{ "name": "drawn_coast", "rgb": [255, 0, 255], "coast_jitter": 0 },
|
||||
{ "name": "wild_coast", "rgb": [255, 128, 0], "coast_jitter": 2.5 },
|
||||
{ "name": "forest", "rgb": [ 0, 128, 0] },
|
||||
{ "name": "city", "rgb": [220, 30, 30], "min_area_px": 400 },
|
||||
{ "name": "road", "rgb": [ 90, 60, 30], "kind": "path", "width_m": 8 }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Nothing is a colour.** An unpainted pixel is transparent, not a background colour, so no colour has to be
|
||||
spent on emptiness and an export with a white matte behind it does not turn the world into whatever mark white
|
||||
is nearest. An opaque pixel that matches no mark inside `match_distance` is dropped and counted — `terrain
|
||||
plan` says how many, which is the only way a colour the legend forgot ever shows.
|
||||
|
||||
### What the generator reads, which is one thing
|
||||
|
||||
`coast_jitter` and nothing else. It scales how far the waterline roughening may move the shore inside the
|
||||
mark: `0` pins it exactly as painted, `2.5` chews it two and a half times as hard as the rest of the world.
|
||||
Painting either side of the waterline is enough — the roughening already knows, for every cell it might move,
|
||||
which cell on the far side it would take its class from — so a brush stroke drawn along the shore covers both
|
||||
and nothing has to be traced precisely.
|
||||
|
||||
The check is exact: an overlay painted `coast_jitter: 0` over every pixel produces a class raster byte for
|
||||
byte identical to a run with `coast_jitter_px` at zero.
|
||||
|
||||
### What travels, which is everything else
|
||||
|
||||
No pass reads a `forest` or a `city` or a `road`. Two bakes with and without them are the same terrain to the
|
||||
bit. What those marks do is come out the other end in two shapes:
|
||||
|
||||
- **A mask per tile.** `Planet_x11_y07_overlay.png` beside every heightmap: 8-bit, one mark index a detail
|
||||
cell, `0` for nothing. Marks cannot overlap — the overlay is one painting and a pixel is one colour — so
|
||||
254 of them fit in the file one would have taken.
|
||||
- **Features in world metres.** `overlay.json` beside `tiles.json`, and beside the maps of every plan and
|
||||
bake. An **area** mark gives a centre, an area, a radius and an extent per connected blob; a **path** mark
|
||||
is thinned to its centreline and gives an ordered polyline and a length, because the thing built from it on
|
||||
the other side is a spline. Both are in metres east of the seam and metres south of the top painted row.
|
||||
|
||||
A blob that crosses the seam is one feature, not two, and its centre lands on the blob rather than on the far
|
||||
side of the world. A road across the seam comes out as one polyline whose X runs past the circumference,
|
||||
which a consumer can wrap back knowing how wide the world is.
|
||||
|
||||
One stroke is one path. A fork reports its two longest arms as a single line and drops the third, so paint
|
||||
each run separately — `terrain plan` prints how many pieces each mark has, which is where a fork shows.
|
||||
|
||||
### Generating a first draft
|
||||
|
||||
The overlay starts blank, and the three things most worth putting on it - woodland, settlements and the roads
|
||||
between them - all depend on ground you cannot see while painting: the classes are painted before the solve
|
||||
exists, and once it does exist it is a seventy-six-million-pixel heightmap. So there is a command that reads a
|
||||
finished bake and proposes them.
|
||||
|
||||
```bash
|
||||
terrain overlay # the newest Bake_NNN, writes the next Map3_NNN.overlay.png
|
||||
terrain overlay --no-save # say what it would place and write nothing
|
||||
terrain overlay --bake RawContent/World/Bake_022
|
||||
terrain overlay --replace # start from a blank sheet instead of filling in around what is painted
|
||||
```
|
||||
|
||||
**In the studio it is a button.** Switch to the `overlay` tab and press **Generate marks**. That is the loop
|
||||
this is really for: press it, look at where the towns landed, press it again, keep the third one. Two things
|
||||
differ from the command, and both follow from a button being pressed repeatedly rather than once.
|
||||
|
||||
- **Every press is a new seed**, so it is a re-roll by construction. The painting fixes where the land is;
|
||||
the seed decides everything it does not - which patch of eligible ground becomes woodland, and which of
|
||||
several equally good sites gets the town.
|
||||
- **Each press replaces the last draft** instead of piling on top of it. The studio remembers exactly which
|
||||
pixels the previous generation put down and clears those, and only those, first. Anything you painted is
|
||||
never in that set.
|
||||
|
||||
The sheet is replaced wholesale rather than stroked, so **ctrl+z will not walk back over a generation** and
|
||||
the undo history is dropped when you press it. The status line says so. Nothing you painted is lost either
|
||||
way, because generation only fills blanks.
|
||||
|
||||
**It works before the first bake**, on the painting alone, and says which of the two ran. That matters: with
|
||||
no solve there are no rivers to sit on and no slope for a road to bend around, so a draft made that way is a
|
||||
sketch that respects land, sea and classes and nothing else. Bake, then press again for one that reads the
|
||||
terrain. A bake made from a *different painting* is ignored rather than used - two paintings of the same
|
||||
planet encode their heightmaps identically, so nothing else would catch it and the marks would be placed
|
||||
against terrain from another world.
|
||||
|
||||
**It never touches a pixel you painted.** The sheet on disk is loaded first and generation fills around it, so
|
||||
running this against a half-painted overlay adds to it, and running it twice is safe. That is what makes the
|
||||
round trip work in both directions: generate a draft, move the towns where you want them in the studio,
|
||||
regenerate the roads around your edits. `--replace` is the explicit way to throw a generation away.
|
||||
|
||||
**It is opt-in per mark.** A mark generates only if it carries a `generate` block, so a legend written before
|
||||
this existed produces the blank sheet it always did, and a mark you want to own completely simply says
|
||||
nothing.
|
||||
|
||||
```jsonc
|
||||
{ "name": "forest", "rgb": [0,128,0],
|
||||
"generate": { "kind": "forest", "cover": 0.45, "wavelength_km": 6,
|
||||
"max_slope_deg": 22, "not_classes": ["ice", "desert"] } },
|
||||
|
||||
{ "name": "city", "rgb": [220,30,30], "min_area_px": 400,
|
||||
"generate": { "kind": "settlement", "count": 3, "min_spacing_km": 9, "not_classes": ["ice"] } },
|
||||
|
||||
{ "name": "road", "rgb": [90,60,30], "kind": "path", "width_m": 8,
|
||||
"generate": { "kind": "road", "max_slope_deg": 20 } }
|
||||
```
|
||||
|
||||
Four kinds:
|
||||
|
||||
- **`forest`** fills ground that is shallow enough and below the treeline, broken up by a noise field so it
|
||||
reads as woodland rather than as a contour band. `cover` is the share of eligible ground it takes and
|
||||
`wavelength_km` how big the patches are. The treeline defaults to a quantile of the land's own heights,
|
||||
because a number in metres means nothing until the world is baked - the same legend has to put trees on a
|
||||
47 m plain and on a 2800 m range.
|
||||
- **`settlement`** scores the land on three things the terrain actually knows - drainage, flat ground and
|
||||
distance to the sea - and takes the best sites with a minimum spacing. Several marks may use it and they
|
||||
share one spacing rule, largest first, so a village never lands inside a city. Everything else about where
|
||||
a town is - trade, history, who won a war - is yours, which is why these are proposals in a sheet you edit.
|
||||
- **`road`** joins the settlements along least-cost paths, as a minimum spanning tree rather than every pair,
|
||||
so there is exactly enough road to reach everywhere. **Water is impassable**, so each landmass gets its own
|
||||
network and a bridge or a ferry stays a deliberate act.
|
||||
- **`coast`** bands the waterline. **It is not enabled in the shipped legend on purpose**: `coast_jitter` is
|
||||
the one overlay property a pass reads, so generating that mark changes the next bake's coastline
|
||||
everywhere, and that is a decision to take deliberately rather than to inherit from a default.
|
||||
|
||||
**`only_classes` and `not_classes`** keep a mark off ground you painted a particular way. They matter more
|
||||
than they sound: height and slope cannot tell an ice cap from a meadow, and the first run of this generator
|
||||
grew woodland across both polar caps, which are flat, below the treeline, and no business of anybody's. A
|
||||
class name that is not in the class legend is an error rather than an empty filter.
|
||||
|
||||
**Read the run's report.** It says what it kept, what it painted, and - the useful one - when it placed fewer
|
||||
settlements than you asked for, which means the spacing or the flat ground ran out. On this template asking
|
||||
for 26 villages at a 9 km spacing gets 17.
|
||||
|
||||
### Painting it
|
||||
|
||||
Press **o** in the studio, or use the `overlay` tab. The brushes become the legend's marks, the last one is an
|
||||
eraser, and the sheet rides over the class painting — full strength while you are on it, dimmed while you are
|
||||
painting classes, because a mark means nothing except against the coastline it was drawn along. **Save
|
||||
overlay** writes the next numbered PNG and points the manifest at it, exactly as the class painting does.
|
||||
|
||||
A planet with `overlay_legend` set and no image yet is the normal way to start: the legend says what the marks
|
||||
mean and the sheet is empty until the first save.
|
||||
|
||||
## One painting, many worlds
|
||||
|
||||
A painting is a **composition**: where the continents are, where the ranges run, which coast you drew on
|
||||
purpose. It is not the whole world. Everything inside it that nobody drew comes from the seed, and changing
|
||||
the seed re-rolls all of it while the painting stays exactly as painted.
|
||||
|
||||
```bash
|
||||
Tools/Terrain/bin/terrain.exe plan --seed 9342 # four seconds; the maps say what moved
|
||||
Tools/Terrain/bin/terrain.exe bake --seed 9342 --out RawContent/World/Bake
|
||||
Tools/Terrain/bin/terrain.exe tiles --seed 9342 --bake RawContent/World/Bake
|
||||
```
|
||||
|
||||
Or in the studio: the **seed** box and the **Re-roll** button, then Save planet and Plan.
|
||||
|
||||
`--seed` is on `tiles` as well, and that is not a convenience: the detail passes hash the seed into every
|
||||
droplet, so a tile run has to be told the seed its heightmap was baked under. `CheckBake` refuses a mismatch
|
||||
rather than giving you gullies from a different world.
|
||||
|
||||
### What moves
|
||||
|
||||
| | |
|
||||
| --- | --- |
|
||||
| the upland fabric | where the massifs stand inside every class that has one |
|
||||
| the rock | which lithology province is where, and so which flank of a range is gullied and which is benched |
|
||||
| the faults | where every trace is, which way it runs, and how much it throws |
|
||||
| the regional swell | the long-wavelength warp that puts divides on a plain |
|
||||
| the initial relief | the small symmetry-breaking the solve starts from |
|
||||
| the coastline detail | the bays and headlands the waterline roughening cuts, except where the overlay pins it |
|
||||
|
||||
Measured on this template, seed 7 against 9342: **13.8 %** of `map_uplift.png` and **24.9 %** of
|
||||
`map_erodibility.png` change, against 2.1 % of `map_class.png` — and that 2.1 % is only the coastline.
|
||||
|
||||
### Rock
|
||||
|
||||
One low-frequency pattern over the whole planet, cut into `pipeline.lithology`'s types at **quantiles of the
|
||||
planet** so every region agrees, multiplying each class's own `k_mult`:
|
||||
|
||||
```jsonc
|
||||
// Planet.json
|
||||
"lithology_wavelength_km": 9, // how big a province is. 0 switches it off
|
||||
"pipeline": { "lithology": { "types": 3, "k_multipliers": [0.6, 1.0, 1.8] } }
|
||||
```
|
||||
|
||||
Set the wavelength well *below* a range or a range is one rock and you have gained nothing: 9 km against
|
||||
belts of 15–25 km puts two or three provinces across each of them. The bands are equal-area, so every type
|
||||
appears whatever the seed did — a seed that produced no hard rock anywhere would be a seed that quietly
|
||||
removed a process.
|
||||
|
||||
Boundaries are graded rather than blurred, and pointwise: the width of a transition on the ground follows the
|
||||
field's own gradient, sharp where the rock changes fast. That is not a style choice. A blur is a neighbourhood
|
||||
operation, and one near a region's edge would read cells a different decomposition of the planet would not
|
||||
have given it — the same rule that makes the cut a planet quantile rather than a regional percentile.
|
||||
|
||||
### Faults
|
||||
|
||||
A fault is **a difference in uplift rate across a line**, steep on one side and gentle on the other, which
|
||||
erosion carves into a scarp. That is why it is not something to paint: painted as terrain the solve erodes it
|
||||
away, applied as a rate it maintains itself. It is what puts an escarpment and a straight fifteen-kilometre
|
||||
valley inside a range, and neither is a shape a brush can draw.
|
||||
|
||||
```jsonc
|
||||
{ "name": "highland", "uplift_mm_yr": 0.25,
|
||||
"faults": { "per_1000km2": 25, "throw_m": [120, 400], "length_km": [6, 18] } }
|
||||
```
|
||||
|
||||
- **`per_1000km2` is a density over this class**, not a count, because a class covers whatever you painted
|
||||
with it. This template has 549 km² of highland, so 25 is about fourteen traces before the long ones step.
|
||||
- **`throw_m` is the whole step across the fault over the run**, not a rate — how much higher the upthrown
|
||||
side would stand than the downthrown one if nothing eroded either, which is a number you can picture. It
|
||||
is the *step*, not the height of each side: before D-62 the profile put a full throw on each flank and so
|
||||
built two, and a legend written against that number is now asking for half what it used to get.
|
||||
- **Put the block on the classes that are orogens.** A plain should have none. The *influence* is not
|
||||
restricted to the class, because a range-front fault runs along the edge of a range by definition and its
|
||||
scarp faces the lowland.
|
||||
|
||||
`planet.fault_grain_km` is only how they are **aimed**: a grain field, so traces near each other come out
|
||||
sub-parallel and the set swings gradually across the world rather than running as corduroy.
|
||||
|
||||
**A fault is kilometres wide, not a line.** The rate crosses from the downthrown side to the upthrown one
|
||||
over about a kilometre and the upthrown flank reaches six, so what a fault makes is a *range front* — a belt
|
||||
of ground with its own valleys cut into it, its crest a kilometre or so back from the trace. It used to be a
|
||||
step across one cell inside a 600 m welt, which erosion could not touch, so it came out as a smooth ruled
|
||||
ridge; that is D-62. Two consequences for you: a fault influences ground several kilometres from where you
|
||||
would draw the line, and it needs that much room, so faulting a class painted in strips two kilometres wide
|
||||
will not give you what the picture in your head has.
|
||||
|
||||
**And a set of them saturates rather than stacking** (D-63). Faults near each other are sub-parallel on
|
||||
purpose — that is what `fault_grain_km` is for — so once each one is kilometres wide they overlap, and
|
||||
overlapping faults that all push the same way used to add. Where several reach the same ground the total is
|
||||
now bent onto at most 1.6 times whatever the strongest single fault there asked for. One fault is untouched
|
||||
by that bound, so `throw_m` still means exactly what it says; what it stops is five parallel faults
|
||||
delivering five throws. The practical consequence for you: **raising `per_1000km2` past the point where
|
||||
faults overlap buys texture, not height.** If you want a belt to stand higher, raise `throw_m`.
|
||||
|
||||
Watch `fault_grain_km` against the size of your landmasses. It is the wavelength of the field the strikes
|
||||
are drawn from, so a landmass much smaller than it sits inside one grain cell and *every* fault on it comes
|
||||
out near-parallel. The shipped 45 km against a 22 km landmass is exactly that case.
|
||||
|
||||
Traces over twelve kilometres are drawn as two or three overlapping en-echelon segments, the throw rises and
|
||||
falls along strike as a bell rather than holding flat through the middle, and the only ceiling is the repose
|
||||
angle — or whatever your own numbers asked for, whichever is higher. A bake's per-region line says what share of the land that bound
|
||||
touched, and says nothing when it touched none; a large share means the throws are big for the class they are
|
||||
sitting in.
|
||||
|
||||
**Expect erosion to take most of the throw.** Measured on this template, region 15 baked whole: five traces
|
||||
with throws of 139 to 399 m left scarps of 2.7 to 50 m, all five facing the side the fault raises. That is
|
||||
the point of applying a fault as a *rate* — a painted 400 m step would have been eroded to nothing in a
|
||||
thousand steps, while a rate difference keeps rebuilding the scarp as fast as the rivers cut it down. (Those
|
||||
five were measured 600 m either side of the trace and under the pre-D-62 profile, so they straddle the old
|
||||
welt rather than spanning a range front; treat them as the right order of magnitude and not as a table to
|
||||
tune against.)
|
||||
|
||||
They are drawn on `map_uplift.png` in cyan — the line rather than the rate it contributes, because a scarp is
|
||||
a couple of hundred metres wide and that map is a hundred kilometres across. The geometry is in the bake's
|
||||
`meta.json`, so "which fault made that valley" is answerable afterwards.
|
||||
|
||||
### Making a desert
|
||||
|
||||
A desert is not a low uplift rate — a wet lowland has one of those too. At the geology grid the only lever is
|
||||
`k_mult`, and below 1 it means less water doing less work, so the ground comes out steeper and more angular
|
||||
and further from being worn down. That is right, and it is nowhere near enough: at eight metres a desert and
|
||||
a meadow are the same picture.
|
||||
|
||||
The difference is at two metres, in the `detail` block:
|
||||
|
||||
```jsonc
|
||||
{ "name": "desert", "rgb": [238,221,153], "uplift_mm_yr": 0.10, "k_mult": 0.5,
|
||||
"detail": { "droplets_per_cell": 0.035, "strata_contrast": 0.92, "amplitude_m": [7, 14] } }
|
||||
```
|
||||
|
||||
- **`droplets_per_cell`** is the load-bearing one. A fifth of the running water turns a dendritic gully
|
||||
network into a few isolated wadis, which is most of what "arid" looks like from the air.
|
||||
- **`strata_contrast`** near 1 keeps the ledges sharp: mesas and benches survive because nothing is rounding
|
||||
them off.
|
||||
- **`amplitude_m`** raised on flat ground is dune. Two metres on a plain is a plain; ten is a sand sea.
|
||||
|
||||
Anything left out keeps the manifest's own number, and a legend that overrides nothing costs nothing — the
|
||||
class raster is not even carried through the detail passes.
|
||||
|
||||
### Putting the range inland
|
||||
|
||||
A uniformly painted island comes out at the angle of repose right down to the water, and that is arithmetic
|
||||
rather than a tuning miss: for `n = 1` the uplift rate alone fixes the hillslope angle, so the rivers cut down
|
||||
to sea level but the ground between them has no idea how far from the coast it is. Fjords, end to end.
|
||||
|
||||
`coastal_plain_km` ramps the rate up from the waterline, so there is a plain in front of the range:
|
||||
|
||||
```jsonc
|
||||
{ "name": "highland", "rgb": [68,170,102], "uplift_mm_yr": 0.25,
|
||||
"massif": { "floor_mm_yr": 0.045, "fraction": 0.30 },
|
||||
"coastal_plain_km": 4.0, "coastal_floor_mm_yr": 0.03 }
|
||||
```
|
||||
|
||||
```
|
||||
distance inland 0km 1km 2km 3km 4km 5km+
|
||||
uplift mm/yr 0.03 0.06 0.13 0.20 0.24 0.25
|
||||
at a divide 4° 9° 18° 27° 31° 32°
|
||||
typical ground 1° 3° 6° 9° 11° 12°
|
||||
```
|
||||
|
||||
(on a massif cell — the ramp runs up to whatever rate the massif field left there, and it only ever ramps
|
||||
*down* towards the sea, so a cell of plain between two massifs keeps its floor rather than being raised into a
|
||||
rim of hills round the edge of the continent.)
|
||||
|
||||
It never goes to zero at the water, and that matters: an earlier version of the generator tapered the uplift
|
||||
to nothing at the shore as a hidden side effect, which flattened the hundred-metre strip the surf works in and
|
||||
moved every cliff inland (D-52). This is you saying where the range starts.
|
||||
|
||||
### Craters
|
||||
|
||||
An impact is an event, not a rate, and it cannot be painted as one — a closed basin built from negative uplift
|
||||
is filled in by the priority-flood within a hundred steps, because the flood raises every depression to its
|
||||
spill level on every one of them. So a crater class is **stamped onto the finished terrain after the solve**,
|
||||
and its shape comes from the blob you painted: distance inward from its own shoreline, normalised by its
|
||||
widest point, so the same four numbers describe a small crater and a large one.
|
||||
|
||||
```jsonc
|
||||
{ "name": "crater", "rgb": [124,117,111], "uplift_mm_yr": 0.15,
|
||||
"crater": { "rim_m": 340, "floor_m": 60, "rim_at": 0.30, "wall_at": 0.62 } }
|
||||
```
|
||||
|
||||
```
|
||||
sea ~~~~\ /‾‾‾\_________/‾‾‾\ /~~~~ sea
|
||||
\ / rim rim \ /
|
||||
\____/ \____/
|
||||
t: 0 0.30 0.62 .. 1 floor
|
||||
```
|
||||
|
||||
`rim_at` is where the crest sits and `wall_at` where the inner wall reaches the floor, both as a fraction of
|
||||
the way in from the shore. `floor_m` above zero keeps the basin dry; the floor is a closed basin, so expect it
|
||||
to pond in the detail passes unless there is a gap in the rim. A crater that straddles the seam is one crater.
|
||||
|
||||
The class's `uplift_mm_yr` still matters a little — it is what the solve does to the flanks before the stamp
|
||||
lands — so keep it low. It is not what makes the crater.
|
||||
|
||||
**Uplift is the number that matters and the one to turn first.** Steady-state slope is `U/(K·A^m)`, applied
|
||||
down to a single cell, so at an 8 m cell and `K` 5e-5 a rate of 0.03 mm/yr is about a 4° hillslope, 0.08 about
|
||||
8°, and anything past roughly 0.45 is above the angle of repose and shaped by landsliding rather than by
|
||||
rivers. Raising the rate makes the summits *higher*; it does not make the ground steeper past that point.
|
||||
|
||||
### The white problem
|
||||
|
||||
Every hand-painted world map draws white twice: the polar caps, and an outline stroke around each island.
|
||||
Exactly one class can own that colour, and it has to be the stroke, because the stroke is the one that must be
|
||||
recognised wherever it appears. What the caps become is then a `derived` class:
|
||||
|
||||
```jsonc
|
||||
{ "name": "ice", "derived": true, "rgb": [250, 250, 250], "uplift_mm_yr": 0.05 },
|
||||
{ "name": "stroke", "rgb": [238, 238, 238], "stroke": true, "edge_class": "ice" }
|
||||
```
|
||||
|
||||
A white region touching the top or bottom row is a cap and becomes `ice`; every other white dissolves into
|
||||
whichever real class is nearest, split down its middle rather than given wholly to one side — which is the
|
||||
only answer that does not move the coastline by the width of the artist's brush.
|
||||
|
||||
The plan's two counters are how you check it landed: `Map3.jpg` reports **2,084,442 px rescued at the poles
|
||||
and 0 dissolved**, which is the evidence that on this template white is only ever the caps.
|
||||
|
||||
## The palette
|
||||
|
||||
How a preview is *drawn* is a separate file from what the painting *means*, and deliberately so: the legend is
|
||||
about the world, the palette is about the picture. Nothing in a palette changes a height — two bakes of the
|
||||
same world under two palettes are the same terrain — so swapping one is cheap and reversible.
|
||||
|
||||
```bash
|
||||
# write the defaults out to start from
|
||||
Tools/Terrain/bin/terrain.exe palette RawContent/World/Templates/mine.palette.json
|
||||
```
|
||||
|
||||
Then point the planet manifest at it with `"palette": "Templates/mine.palette.json"`. Leaving the key out
|
||||
uses the same numbers, so the file is a starting point rather than a requirement.
|
||||
|
||||
It holds the hypsometric ramp (`land_stops`, sea level at `t` 0 to the top of the land at `t` 1), the water
|
||||
(`sea_shallow`, `sea_deep`), the rivers, the ice, where the ramp's top is taken from
|
||||
(`land_top_percentile` — a percentile rather than the maximum, so one high summit cannot push a whole
|
||||
continent into the bottom of the ramp), and the light (`sun_azimuth_deg`, `sun_altitude_deg`, `ambient`,
|
||||
`gain`). Anything left out keeps the default; a misspelt key is an error rather than a setting that silently
|
||||
does nothing. Keys beginning with an underscore are comments.
|
||||
|
||||
## Reading a plan
|
||||
|
||||
`terrain plan` writes the maps and `plan.json` into `RawContent/World/Plan/`, in about four seconds. In
|
||||
order of what they answer:
|
||||
|
||||
- **`map_class.png`** — did the legend read the painting? If this is not the picture you drew, nothing
|
||||
downstream can be. The match report beside it counts pixels no class was near.
|
||||
- **`map_regions.png`** — how the planet was cut up. Each region is one hue, its land saturated and the ocean
|
||||
margin it carries the same hue dimmed. Check that a landmass straddling the seam is one colour and not two,
|
||||
and that nothing has swallowed the ocean.
|
||||
- **`map_uplift.png`** — the field everything else is a consequence of, the massif fabric included, so a class
|
||||
with a `massif` block shows as plain with upland in it rather than as one flat colour across the landmass.
|
||||
The one to read when the ground comes out the same everywhere.
|
||||
- **`map_erodibility.png`** — where texture inside a range will come from.
|
||||
- **`map_overlay.png`** — the annotation layer over a dimmed class map, when there is one. A mark means
|
||||
nothing on its own and everything against the coastline or the range it was drawn along.
|
||||
|
||||
And the class table above them, which is the four seconds best spent. It prints every class's uplift rate and
|
||||
then **two** angles, and which of the two you read is the difference between a legend that is right and one
|
||||
that is three times too hot:
|
||||
|
||||
```
|
||||
class share cells uplift mm/yr K depth m divide typical
|
||||
lowland 14.4% 10967451 0.080 1.00 - 11.3 deg 3.8 deg rolling
|
||||
massif over 16% of it; the other 84% is 0.012 mm/yr, 1.7 deg at a divide and 0.6 typical - plain
|
||||
```
|
||||
|
||||
The same two angles are in World Orogen's class table, beside the inputs that set them (D-67), which is the
|
||||
place to read them while you are still typing the numbers.
|
||||
|
||||
`divide` is the steepest ground the rate can make: steady state is `S = U/(K·A^m)`, `A` is smallest at the top
|
||||
of a catchment, and that is where the number comes from. `typical` is the median slope over the class, and it
|
||||
is about **a third** of the divide angle in tangent — measured on a 600² grid of 8 m cells over a factor of
|
||||
twenty in rate, where the ratio came out 0.34, 0.33, 0.33 and 0.32. Almost none of a map is divide. Read the
|
||||
second column; it is the ground somebody stands on, and `reads as` is taken from it.
|
||||
|
||||
Then the block that says what a re-roll would move: the rock provinces and the fault set, with a trace count
|
||||
per class. And if there is an overlay, how much of the world each mark covers, how many pieces it is in, and
|
||||
what it asked of the coastline.
|
||||
|
||||
Then the region table, which is where the bake time goes. **The margin is the lever.** It decides which
|
||||
landmasses are solved together: two within twice the margin share a box. At 0.5 km this template gives 18
|
||||
regions; scattered islets on some other template would each cost a whole region, and `--margin-km` is how to
|
||||
find out before spending the afternoon.
|
||||
|
||||
**Read the time estimate as a floor.** It is calibrated on lowland, and mountains cost about five times as
|
||||
much per cell — steep ground drives the hillslope law to its full sub-step budget every step while a plain
|
||||
leaves it idle. Measured on this template: a 14.0 M cell lowland region took 1014 s and a 4.0 M cell highland
|
||||
region took 1394 s. So raising an `uplift_mm_yr` does not only change the terrain, it changes the bake time.
|
||||
|
||||
## The detail bake
|
||||
|
||||
`terrain tiles` runs the detail passes over a geology bake and writes the ground somebody can stand on. It
|
||||
reads the bake's heightmap off disk rather than solving anything, which is what makes it batchable: the
|
||||
geology is hours, a 5 km tile is about twelve seconds, and the islands you care about can be baked first.
|
||||
|
||||
```bash
|
||||
# every tile, about forty minutes for a 100 km world. --bake defaults to the newest Bake_NNN.
|
||||
Tools/Terrain/bin/terrain.exe tiles
|
||||
|
||||
# a rectangle of them, by tile index, from a particular bake
|
||||
Tools/Terrain/bin/terrain.exe tiles --bake RawContent/World/Bake_001 --only 11,7,13,8
|
||||
|
||||
# the geology upsampled and nothing else: is what you are looking at the solve or the detail passes?
|
||||
Tools/Terrain/bin/terrain.exe tiles --only 16,6,16,6 --no-detail
|
||||
```
|
||||
|
||||
Each tile gets `Planet_x11_y07.png` (16-bit height, 2500 x 2500 at 2 m), `_shade.png` (a hillshade at the same
|
||||
resolution, because a 16-bit grey PNG of a hundred metres of relief is a flat grey rectangle to look at), and
|
||||
`_flow`, `_wear` and `_deposit` as 8-bit maps of what the droplets did. `tiles.json` beside them carries the
|
||||
grid, the cell size, each tile's world origin and its elevation range.
|
||||
|
||||
If the planet has an overlay, `_overlay.png` comes too — 8-bit, one mark index a detail cell — with
|
||||
`overlay.json` beside `tiles.json` as its key and the feature list. The mask is sampled through world metres
|
||||
rather than through a tile-local index, so a cell reads the same mark whichever tile reaches it.
|
||||
|
||||
The tiles are seamless and that is measured, not asserted: a tile carries a margin of three droplet lifetimes
|
||||
which is discarded afterwards, and every hash and noise lattice in the passes is keyed on absolute world
|
||||
position, so a cell in one tile's interior comes out as it would have in one whole-world run. The margin costs
|
||||
about five per cent of a tile on each side.
|
||||
|
||||
**What is not there yet:** the shore, at *detail* resolution. The geology bake has a shelf, a surf-cut
|
||||
platform and a beach as of D-60, but the detail passes do not refine any of it: a tile's coastline is the
|
||||
8 m coastal pass upsampled, with no sand, no boulder field and no plan-view crenulation at 2 m. Everything
|
||||
inland is finished.
|
||||
|
||||
## What a bake writes
|
||||
|
||||
### `preview.png` is relative, and that matters more than it sounds
|
||||
|
||||
The hypsometric ramp's top is `land_top_percentile` of **the land in the world being drawn**, not a fixed
|
||||
height. So bare rock and snow on a preview mean "the highest ground here" and say nothing at all about scale.
|
||||
|
||||
Measured on this template's central lowland: it bakes to **0..47 m** with a median hillslope of **0.61°**,
|
||||
4.4 % of it over three degrees and nothing anywhere over eight — a plain by any reading. Drawn against its own
|
||||
32 m ceiling it comes out with tan uplands and white caps on its 40 m hills, which is exactly what a 2800 m
|
||||
range looks like, and it is the single most misleading thing the generator produces. Redrawn at a fixed 400 m
|
||||
ceiling the same heightmap is flat green with four pale massifs on it.
|
||||
|
||||
Every run prints which ceiling its preview used, so read that line before reading the picture:
|
||||
|
||||
```
|
||||
preview the hypsometric ramp tops out at 32 m - the 99.5% percentile of *this* world's land, so rock and
|
||||
snow mean "the highest ground here" and nothing about scale. Set palette.land_top_m for an
|
||||
absolute ramp
|
||||
```
|
||||
|
||||
`palette.land_top_m` is a height in metres; `0` keeps the percentile. The percentile stays the default on
|
||||
purpose — an absolute ramp over a world with no mountains is a flat green shape with nothing legible on it,
|
||||
and "is there drainage here" is a question the contrast has to answer. When what you want to know is *how
|
||||
steep is this ground*, read `map_slope.png` and the plan's `typical` column, not the tint.
|
||||
|
||||
`planet_height.png` at the geology grid (12500 × 6076 at 8 m for this world), plus quarter and tenth
|
||||
overviews; `preview.png` with hypsometric tint, hillshade and rivers; the plan's maps again, now over real
|
||||
terrain; `map_slope.png` and `map_flow.png`; and `meta.json` with the resolved manifest, the legend, the
|
||||
match report and every region's statistics.
|
||||
|
||||
And **the two coastal maps**, which are the whole of what the pass did:
|
||||
|
||||
- **`map_coast.png`** — what it moved, cool where the surf cut and warm where the sediment landed, on a
|
||||
±30 m scale. The deep sea floor is masked out of it on purpose: the ocean drops five hundred metres in
|
||||
this one pass and would otherwise set the scale for the few metres the shore processes move, which is the
|
||||
thing the map exists to show.
|
||||
- **`map_exposure.png`** — how open each stretch of shore is to the sea, drawn only within a kilometre of
|
||||
the water. Not tidiness: exposure is measured *on* the waterline and carried inland by "the nearest stretch
|
||||
of shore to you", so past a few hundred metres it stops being a coastal quantity and becomes a picture of
|
||||
the continent's medial axis.
|
||||
|
||||
The pass is one line in the log and one block in `meta.json`, and it runs **once over the whole cylinder**
|
||||
rather than per region — fetch crosses straits, and a sediment budget split in two is not a budget:
|
||||
|
||||
```
|
||||
coast: 971 km of shoreline, 65% sea, shelf 60% of it; surf planed 3.5 km2 and cut 34.18 Mm3,
|
||||
6 river mouths delivered 1.15 Mm3, 35.32 Mm3 laid (0% unplaced) as 4.14 km2 of new beach
|
||||
```
|
||||
|
||||
`0% unplaced` is the line to read: it is the sediment the budget could not find a home for, and a figure that
|
||||
is not near zero means the drift ran out of shore before it ran out of sand. On a `--only` run the backshore
|
||||
figures read 0 m, correctly — most of the planet's land is unsolved and sitting at sea level.
|
||||
|
||||
It is also the memory peak of a bake, at about **8.3 GB** of working set against the solve's 3.6, and it is
|
||||
worth knowing that before starting one on a smaller machine.
|
||||
|
||||
And **the verdict block**, which a bake prints for the first time as of D-59: the slope distribution, the
|
||||
slope-area fit, the hypsometric integral, the drainage density, and the per-uplift-class breakdown that is
|
||||
the one to read. It is pooled from the regions rather than computed on a grid that never exists - a histogram
|
||||
adds, so summing the pieces and taking a quantile of the sum is exactly what one pass over the whole world
|
||||
would have given.
|
||||
|
||||
A run with `--only` marks itself **PARTIAL**: its extent is the whole cylinder while its ground statistics
|
||||
cover just the landmasses that were solved, and the two are not comparable.
|
||||
|
||||
```
|
||||
field -543..340 m; land 0..9 m (relief 9 m), 36% land, 0.00% clipped
|
||||
PARTIAL: the line above is the whole world; everything below is the 3% of its land that
|
||||
was actually solved (842682 of 27455176 cells). The two are not comparable.
|
||||
slopes: 100% under 15 deg, 100% under 30, 0.0% over 50, median 0.5 deg
|
||||
...
|
||||
by uplift class:
|
||||
plain 0.00..0.10 mm/yr 100% of land slope 0.5 deg median, 0.8 P90 relief 3 m/500 m ...
|
||||
```
|
||||
|
||||
The polar pad never appears in any of them. It is a few rows of synthetic ocean above and below the painted
|
||||
map, added so that a cap touching the top row has a shore to drain into — without it the solver would treat
|
||||
the map edge as an outlet and freeze the ice at its initial relief.
|
||||
|
||||
## Limits worth knowing
|
||||
|
||||
- **A landmass cannot ring the planet.** A region is flattened into a rectangle with water on both sides,
|
||||
because a grid edge is an outlet; one that goes all the way round has no such edges. The run refuses it
|
||||
rather than silently freezing the coast. Break it with a strait.
|
||||
- **The seed alone no longer names a world.** The margin and the minimum landmass size change how the planet
|
||||
is cut up, and the priority-flood's traversal across a flat depends on the box it is flooding. All three are
|
||||
in `Planet.json` and all three are recorded in `meta.json`.
|
||||
- **The elevation range is a hard clip.** A run whose clip fraction is above 0.1 % is a failed run, not a
|
||||
rounded one, and painted uplift makes it easy to ask for more relief than the range holds. `U/K` is the one
|
||||
relief knob.
|
||||
|
||||
## The same painting in World Orogen
|
||||
|
||||
`Tools/Orogen/` (D-66) reads exactly these files. Serve the folder with any static server and open the
|
||||
import page's **Painted Map** source:
|
||||
|
||||
```bash
|
||||
npx serve Tools/Orogen # then http://localhost:3000/import
|
||||
```
|
||||
|
||||
Choose `Map3_002.png` as the painting and `Map3.legend.json` as the legend, press **Solve Terrain**, and in
|
||||
about half a minute the painting is a globe with rivers, climate and a satellite view, plus class, uplift,
|
||||
erodibility, drainage, slope and basin layers to inspect and export. The table under the legend edits the
|
||||
classes' numbers and **Download legend JSON** writes them back into a copy of this file with the commentary
|
||||
intact, so a legend tuned there runs here unchanged.
|
||||
|
||||
What it is for is the thirty-second question - does this painting make the rivers I meant, is that lowland
|
||||
a plain with massifs in it or a slab - before the two-hour bake answers the eight-metre one. What it is not:
|
||||
the globe is Earth-sized (legend distances are scaled by the ratio of circumferences), the cells are ~44 km,
|
||||
and faults, craters, the plates, the repose clamp and the detail passes are not carried because nothing at
|
||||
that cell size could show them.
|
||||
|
||||
**It reads this planet's own files now, and the studio serves them.** Point it at `Planet.json` and the
|
||||
planet block, the lithology multipliers, the seed and the geology grid's constants travel without being
|
||||
retyped; it outranks a legend's own `planet` block, because it is the file the bake reads. The **overlay**
|
||||
is carried too - as a texture over the globe and the map rather than voted onto the mesh, since a road is
|
||||
a few pixels wide and a cell there is tens of kilometres - and its `coast_jitter` marks pin or roughen the
|
||||
shore on the sphere exactly as they do here.
|
||||
|
||||
Easiest of all, while `terrain studio` is running, its **From terrain studio** button loads the painting,
|
||||
both legends and the manifest in one step, including strokes made since the last save, because the studio
|
||||
holds the painting in memory. The studio shares reads and only reads: its CORS header is on GET alone and it
|
||||
answers no preflight, so a browser tab can look at the planet and can never start a bake.
|
||||
|
||||
**What it still cannot tell you** is the ground at eight metres. The bake owns the geology grid, the coast
|
||||
pass, the faults, the craters and the detail tiles, and only they produce ground a player can stand on.
|
||||
Reference in New Issue
Block a user