This commit is contained in:
Rainer Leit
2026-09-25 17:02:24 +03:00
parent cc43ed8dc8
commit 9597629951
2149 changed files with 460234 additions and 1770 deletions
+61
View File
@@ -0,0 +1,61 @@
{
"_comment": "The world map's art: which planet-wide images become the layers of the in-game and in-editor map view, and how big they are when they get there. Read by Tools/MapArt (which downsamples and renders them into this folder) and by Scripts/Authoring/create_world_map.py (which imports the results and writes the definition asset). Adding a layer is a line here and a rerun of Scripts/Authoring/build_world_map.sh.",
"_comment_registration": "Every source here must be a whole cylinder of the SAME planet as the heightmap Region.json cuts L_World from, at any resolution. They register because L_World imports the entire export with no crop - Region.json's window is the full 8192x4096 - so normalised u,v on any of these images is normalised u,v on the world. A crop, a different planet or a different seed would misregister silently: there is nothing inside a PNG that says which world it is. `mapart check` is the guard, and it is a land/sea agreement against the heightmap rather than a hash, because the layers are different renders of one planet and no two of them are alike pixel for pixel.",
"source_dir": "RawContent/World/Orogen Gens",
"output_dir": "RawContent/World/MapArt",
"region": "RawContent/World/Region.json",
"_comment_wraps_x": "Does the map's left edge join its right? Only when Region.json's window is the whole width of the source, which is the case here - the export is imported entire. null works it out by comparing the window with the source's own width, which only the mapart report knows, because a PNG's size belongs in no manifest. Set it true or false to decide by hand. Getting it wrong is visible: a map that wraps when it should not lets a pan run off one edge and arrive at the other, and one that does not wrap when it should puts a hard edge down the middle of an ocean.",
"wraps_x": null,
"_comment_output": "4096 x 2048 is 17.4 m a pixel over the 71.40 km world: a quarter of the source's 8192 and still far finer than the couple of hundred metres the Orogen mesh actually resolves, so no real detail is lost. Raising it is a number here and a rerun; the cost is LFS, about 11 MB a layer at this size and four times that at full resolution.",
"output": { "width": 4096, "height": 2048 },
"_comment_package": "Where the imported textures and the definition asset land. The definition is what the game and the editor tab both read, and it carries the projection copied out of Region.json, so nothing at runtime needs RawContent to exist.",
"package": "/Game/World/Maps",
"definition": "DA_WorldMap_L_World",
"level": "/Game/Maps/L_World",
"_comment_layers": "`render` says how a source becomes a layer. `copy` downsamples the image as it is, averaging in linear light, which is right for anything already coloured. `relief` is the one derived layer: it reads a 16-bit heightmap, shades it from a light in the north-west and tints it by altitude, which is what shows the landform a player will walk over - a colormap shows biome, not shape. `default` is the layer the map opens on.",
"layers": [
{
"id": "relief",
"name": "Relief",
"file": "orogen-heightmap-7945.png",
"render": "relief",
"default": true,
"note": "Shaded relief and a hypsometric tint off the same heightmap L_World's geometry came from, so the map and the ground are the same shape by construction rather than by agreement."
},
{
"id": "colour",
"name": "Colour",
"file": "orogen-colormap-7945.png",
"render": "copy",
"note": "Orogen's own planet render. Was orogen-colormap-14733759.png until the 2026-09-21 re-export, which produced one carrying the planet's own number - the same picture of the same world, with a filename that no longer invites the question. Orogen numbers each export, not each planet, which is why the old one looked like a different world and was not."
},
{
"id": "satellite",
"name": "Satellite",
"file": "orogen-satellite-7945.png",
"render": "copy"
},
{
"id": "climate",
"name": "Climate",
"file": "orogen-climate-7945.png",
"render": "copy",
"note": "Biome bands. Nothing in the project reads climate yet; it is here because it costs one line and it is the layer that answers 'why would there be forest here'."
}
],
"_comment_relief": "The relief render's knobs. The light is the cartographic convention, north-west, so ridges read as ridges rather than as trenches - a light from the south-east inverts the relief for most people looking at it. `exaggeration` multiplies the slope before shading, because a few hundred metres of relief spread over 17.4 m pixels is nearly flat and an honest hillshade of it is featureless grey. `land_top_m` is where the hypsometric ramp tops out, in metres on Region.json's elevation contract; null measures the 99.5th percentile of the land instead, which is what Orogen's export normalises to anyway.",
"relief": {
"light_azimuth_deg": 315,
"light_altitude_deg": 45,
"exaggeration": 4.0,
"land_top_m": null,
"shade_strength": 0.75
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e49829b06ad7710629c557bcfbd4da573454a3e22e2fffe1ebe8cc6254d1e490
size 1002889
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1e2cdeff872d6894efb9ff99d431c804a2c7a908adc64458ed67a421ef314266
size 4243569
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:255b02e32fa12cda0a3c69f24bcf4aa14a54d385878d3e1132a9253ee11f1018
size 5573941
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:00c90ff98bfbc66a760d87a863a68f6e72a61274f74804b165dc173fa722d16c
size 25318019
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:ebb2fae628a7bc74948e92a62c638c574cc544abfef0b7c4d101abd8056df31b
size 18476308
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5e2882f0ee4f3ab5a63e7752c78583ae452332da23dee1d4d7883eb67b2a62d5
size 317942
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9441c24d71c9709528e674379cff325cff54a9882bdcf68948c687aaf4cf1be9
size 866718
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:6310261284b7cc897a703236171f7c1dcfb4ee10519c8e3642c82dce5d622bd0
size 403856
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c5b5b1a66890da834db74c2db115c373847b3f5eceba33b3d96fc30b7c6830e4
size 4399845
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:34e7f2f9e7ce506c7aa7d82d80d29aadd498192312708d39c94fd8493c36faa6
size 560816
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3900b1c9b0d492913091c877a79a72ba044a66ddc1ed9da4de3e9905c80954d6
size 553179
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:17808ae6f9233e64207190f7ed76abfd04dc8f682f0aebf5bbe5d90c1e357e13
size 2155585
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:9fbed3c0cc5e102891733f9c34256299b4f7cbf92935b8ab436399f310045c80
size 2387319
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:689cf6b23359bc5916628c45f88a69fff695cb7485db794ff564405dacdf1909
size 5372560
+57
View File
@@ -0,0 +1,57 @@
{
"_comment": "A painted planet: Templates/Map3.jpg plus its legend, turned into terrain by the Go generator in Tools/Terrain. Read by `terrain plan` and `terrain bake` only - World.json next to this file is the square 14 km canvas the numpy pipeline and create_world.py still build, and the two do not share anything but their defaults. See Docs/Terrain.md.",
"level": "/Game/Maps/L_Planet",
"quad_cm": 200,
"sea_level_m": 0,
"_comment_elevation": "Wider than D-48's -512..1536, which is the square canvas's range and is tied to an exact Unreal Z scale of 400. A planet is not one Landscape, so that constraint does not bind here, and the trunk profiles are several times longer than the 14 km world's, which is where relief comes from. Span 3072 m over 16 bits is 4.7 cm a step, invisible at an 8 m cell. The clip fraction per region is the check: above 0.1% it is a failed run, not a rounded one.",
"elevation_m": { "min": -1024, "max": 2048 },
"source": { "kind": "template", "seed": 7945 },
"planet": {
"overlay": "Templates/Map5.overlay.png",
"template": "Templates/Map5.png",
"legend": "Templates/Map5.legend.json",
"_comment_overlay": "The annotation layer (D-57): a second painting the same size as the template, and a legend of marks saying what its colours stand for. It is where everything that is *placed on* the world rather than solved goes - forests, settlements, roads - and it carries exactly one instruction the generator reads, `coast_jitter`, which is how a coastline drawn by hand survives the waterline roughening below. Leave both keys out and there is no overlay, which is what every planet had before it. The image is created the first time it is saved from `terrain studio`, and versioned after that the same way the template is.",
"overlay_legend": "Templates/Map5.overlay.json",
"_comment_palette": "How the preview is drawn, and nothing else - no height changes, so two bakes under two palettes are the same terrain. Swap the file to restyle the map. Leave the key out and the generator's own numbers are used, which is what this file holds; `terrain palette <path>` writes a fresh copy to start from.",
"palette": "Templates/default.palette.json",
"_comment_scale": "100 km round at a 2 m detail quad and geology_factor 4 is 12500 x 6076 geology cells of 8 m. The 8 m cell is fixed by D-48 and must not be varied: stream power applied down to a single cell makes every divide steepen as the cell shrinks, so the same painting solved at two cell sizes would be two different worlds.",
"circumference_km": 100,
"_comment_massif": "How big the upland fabric's blocks are - the thing a class's massif block cuts to decide where it is plain and where it stands up. One field for the whole planet, so a highland belt and the hills in the lowland beside it are parts of one structure. 7 km against landmasses of 20 to 45 km puts several blocks across every continent; at 12.5 the right-hand island came out entirely above the cut, which is the defect this exists to fix, only smaller. Rounded to a whole number of lattice cells in the noise period - `terrain plan` prints what it got.",
"massif_wavelength_km": 7,
"_comment_reroll": "The two fields the seed re-rolls that the painting does not fix (D-58). Lithology cuts one planet-wide rock pattern into pipeline.lithology's types and multiplies each class's own k_mult by it, so a range has provinces in it rather than being one rock; the wavelength is how big a province is. The fault grain is only how the traces are *aimed* - which classes are faulted, and how hard, is the legend's `faults` block. Both are rounded to a whole number of lattice cells in the noise period, and `terrain plan` prints what they came to. Set either to 0 to switch that field off.",
"lithology_wavelength_km": 8,
"fault_grain_km": 45,
"_comment_plates": "The painted tectonic layer. A colour is a plate and the legend says how it moves; where two of them meet, whether that is a collision, a transform or a rift is worked out from the two motions. The faults block is the deformation zone around every margin - how wide, and how densely broken.",
"plates": {
"layer": "Templates/Map5.plates.png",
"legend": "Templates/Map5.plates.json",
"faults": { "zone_km": 6, "per_1000km2": 90, "throw_m": [80, 420], "length_km": [4, 16] }
},
"ocean_margin_km": 0.5,
"min_land_cells": 16,
"uplift_variation": 0.30,
"_comment_coast": "A drawn shore is a smooth curve and a coastline is not - that is the Richardson paradox, and projected straight the painting's own smoothness survives all the way to the heightmap. The waterline is roughened before projection by adding fractal noise to the signed distance from it: land juts out where the noise is positive and the sea reaches in where it is negative, so bays come out about wavelength wide and up to amplitude deep. Both are in template pixels, which are 12.92 m here. The amplitude is capped per cell at two thirds of the widest land within reach, or a wavelength far larger than an islet would take the whole islet at once and an archipelago would vanish between two runs. Tune it in `terrain studio`; --coast-jitter, --coast-wavelength, --coast-octaves and --coast-gain override it for one run.",
"coast_jitter_px": 68,
"coast_jitter_wavelength_px": 384,
"coast_jitter_octaves": 5,
"coast_jitter_gain": 0.65
},
"pipeline": {
"geology_factor": 4,
"_comment_lithology": "The rock types the lithology field is cut into, equal shares of the planet each. The generator's own defaults are [0.5, 1.0, 3.0], a six-fold span, which was chosen for the square canvas where lithology was the *only* thing varying K; here it multiplies a class's k_mult on top of everything else the legend says, so a narrower span is what reads as one range made of several rocks rather than as two different worlds meeting inside it.",
"lithology": { "types": 3, "k_multipliers": [0.6, 1.0, 1.8] }
}
}
+216 -8
View File
@@ -1,11 +1,21 @@
# The world's terrain source
> **`L_World` is the planet-map region (D-72).** Two manifests live in this folder and they build different
> levels. `Region.json` builds `L_World`, the world the game uses — skip to
> [The region](#the-region-900-km-of-land-from-a-planet-map) for it. `World.json`, described first below,
> builds `L_Canvas_Proto`: the numpy pipeline's square 14.28 km canvas, legacy by D-47 and kept only because
> it is still the one path that carries the erosion pass's flow, wear and deposit maps into Unreal.
>
> The names are apart on purpose. `create_world.py` **empties whatever level it is handed** before rebuilding
> it, so a manifest still pointing at `L_World` would replace 98 landscapes with a 14 km square on a single
> run, with no prompt and no warning.
> This document describes the pipeline as it is built today. Where it is going — a Go core, stream-power
> erosion, plates and faults, a `Generated` edit layer, and a canvas of 7141 vertices at 200 cm — is settled in
> [`../../Docs/Terrain.md`](../../Docs/Terrain.md) (D-47). Nothing here is wrong yet; several things in it are
> scheduled to be replaced, and that document says which and by what.
`L_World` is a product of three inputs, none of them hand-edited: this folder's `World.json` (the manifest),
`L_Canvas_Proto` is a product of three inputs, none of them hand-edited: this folder's `World.json` (the manifest),
the PNGs in `Heightmaps/` that `Scripts/Authoring/generate_heightmap.py` writes from it, and
`Scripts/Authoring/create_world.py`, which imports them into the level and dresses it with Elite_RockyMeadows'
kit. Change an input, rerun the two scripts, and the level is rebuilt from scratch.
@@ -19,8 +29,8 @@ The first needs numpy in `Scripts/Authoring/.pylib` (`bootstrap-pylib.sh`, once
five minutes at 4081, most of it erosion (40 s without). The second takes a minute or two (the landscape's
textures are built through the derived-data cache)
and must not be killed part way: run it detached, not under a tool with a timeout. It can run while the editor
is open, as long as the editor does not have `L_World` loaded at that moment. An existing level is loaded and
emptied rather than deleted, because an editor that has had `L_World` open keeps its two HLOD layer assets
is open, as long as the editor does not have `L_Canvas_Proto` loaded at that moment. An existing level is
loaded and emptied rather than deleted, because an editor that has had it open keeps its two HLOD layer assets
locked and a recreation would fail to save; the previous build's proxy packages are swept after the save.
## The manifest
@@ -58,8 +68,8 @@ path as a rill one cell wide, which reads as brush strokes across the lowlands.
own cell, so a pit fills to the brim and the droplets move on; spread through the brush, a pit's rim rises
faster than its floor and every droplet draining into it adds to a mound.
The pass also writes four derivative maps next to the weightmaps: `L_World_Flow.png` (water passed, log scaled),
`L_World_Wear.png` (bedrock scraped), `L_World_Deposit.png` (sediment laid down) and `L_World_Curvature.png`
The pass also writes four derivative maps next to the weightmaps: `L_Canvas_Proto_Flow.png` (water passed, log scaled),
`L_Canvas_Proto_Wear.png` (bedrock scraped), `L_Canvas_Proto_Deposit.png` (sediment laid down) and `L_Canvas_Proto_Curvature.png`
(128 flat, brighter convex, darker concave). The layer rules use them: scraped bedrock and convex ridges read as
rock, sediment fans and basins read as meadow. Nothing in the landscape material samples them yet; they are
there for the material that will.
@@ -109,8 +119,206 @@ resolution, pick `255 x N + 1` (or `127 x N + 1`) with N at most 32.
The pack contributes the landscape material (`M_Landscape_Main_Inst_RockyMeadows02`) and its three layer infos,
which is why the weightmaps carry the pack's names. Those names mislead: its `Base_Layer` samples the rock
textures, `Layer_02` the grass, `Layer_03` the high rock, so the meadow weightmap is `L_World_Layer_02.png`; the
textures, `Layer_02` the grass, `Layer_03` the high rock, so the meadow weightmap is `L_Canvas_Proto_Layer_02.png`; the
sun with the pack's cloud-shadow light function; its skybox dome and sky light; its height fog and post-process
grade. The numbers are copied from the pack's `Rocky_Meadows_01` demo map as `Scripts/Authoring/dump_level.py`
read them, and live at the top of `create_world.py`. The sea is a plane with the engine's water material,
`World_Sea_Proto`, until a water body replaces it.
read them, and live at the top of `create_world.py`. The sea is a plane, `World_Sea_Proto`, until a water
body replaces it.
It wears a **placeholder grey** (`/Game/World/M_Sea_Proto`, opaque and default-lit) rather than the engine's
single-layer water. The water material is a lake shader stretched over a whole planet here and reads at every
scale as something it is not; a plane that is honestly a placeholder is worth more while the ground is being
looked at than one pretending to be an ocean. `rocky_meadows.SEA_GREY` is the switch — set it `False` and the
water material comes back, unchanged and still the first thing tried. The material is authored on demand
rather than picked out of `/Engine`, because nothing there is the right value: `BasicShapeMaterial` is the
near-white that once read as an ice sheet to the horizon, and `WorldGridMaterial` puts a metre grid on a plane
seventy kilometres across.
`ensure_dressing` only spawns a sea when the level has none — correct, or a rerun would leave a second sun —
so changing the switch cannot by itself reach a world that already exists. That is what
`fix_sea_material.py` is for: it repaints the sea in a finished level and saves it, without rebuilding
anything.
```bash
D:/UE_5.8/Engine/Binaries/Win64/UnrealEditor-Cmd.exe <abs>/Salty.uproject -run=pythonscript \
-script="<abs>/Scripts/Authoring/fix_sea_material.py --level /Game/Maps/L_World" \
-AllowCommandletRendering -unattended -nopause -abslog=<abs>/Saved/Logs/sea.log
```
Pass `--level` more than once for several worlds. It probes the `.umap` first and refuses when an editor holds
it, for the same reason the region builder does.
## The region: 900 km² of land from a planet map
`L_Canvas_Proto` above is the numpy pipeline's square 14.28 km canvas. **`L_World` is this**, built from a
different manifest: a **window cut out of a finished planet heightmap** and laid out as a grid of Unreal
landscapes, so there is ground at the scale the game wants long before the Go generator's detail passes reach
Unreal. Its contract is `Region.json`; every key in it is explained in `Scripts/Authoring/region_manifest.py`.
`Region.json`'s `level` decides which level is built **and what the tile files are called** — `tile_name`
is the level's last segment plus the tile's coordinates, so `L_World` means `L_World_x0_y0_Height.png`.
Change `level` and every tile in `RegionTiles/` looks missing; rename the PNGs to match or the generator
rebuilds all ninety-eight of them. `build_region.sh` reads the level out of the manifest for the same reason
it reads the grid from there: a name written into the script goes stale the moment the manifest changes.
```bash
cd Tools/MapArt && go run . biomes # the biome masks, if any paint layer reads one
D:/UE_5.8/Engine/Binaries/ThirdParty/Python3/Win64/python.exe Scripts/Authoring/generate_region_tiles.py --scout
D:/UE_5.8/Engine/Binaries/ThirdParty/Python3/Win64/python.exe Scripts/Authoring/generate_region_tiles.py
D:/UE_5.8/Engine/Binaries/Win64/UnrealEditor-Cmd.exe <abs>/Salty.uproject -run=pythonscript \
-script=<abs>/Scripts/Authoring/create_region_world.py -AllowCommandletRendering -abslog=<abs>/region.log
```
**Do not leave the editor sitting on `L_World` while a build runs.** It holds a write lock on the `.umap`,
and the save is the *last* thing a batch does while `--rebuild` is the first: the run aborts on
`MoveFile … (Error Code 32)` having already emptied the level. That happened on 2026-09-20 and left twelve
of ninety-eight tiles, all of row y=0 — the polar strip, which on this planet is nearly all ocean — so the
level opened on 71 km of sea and read as a corrupted world. Both `build_region.sh` and
`create_region_world.py` now probe the file before anything is destroyed and refuse with a message naming
the cause. Load another level in the editor (or close it) and rerun; `--append` keeps whatever survived.
`--scout` measures the window and prints what it holds without writing anything: it is the cheap way to try a
scale or a position. The tiles take about two minutes and 208 MB (untracked; they are a product of the manifest
and the source). The level takes about ten minutes and must be run detached, with absolute paths and its own
`-abslog`: with the editor open and a relative project path the commandlet exits silently having done nothing.
### What it is today
| | |
| --- | --- |
| Source | `Orogen Gens/orogen-heightmap-7945.png`, 8192x4096, the painted planet exported from the browser twin. The **absolute** heightmap (-5..6 km), not `orogen-land-heightmap-*.png`, which Orogen's "Export All" confusingly labels "Heightmap" and which has every ocean pixel at 0 m |
| Window | **the whole export**, 8192 x 4096 at (0, 0). Not a crop: every pixel of the planet is imported |
| Scale | 8.7158203125 m a source pixel, resampled to 2 m quads: a 4.36x upsample |
| Grid | 14 x 7 landscapes of 2551 vertices, 10 x 10 components of 255 quads each: **9800 components** |
| Extent | 71.40 x 35.70 km, 2549 km² of map holding about **925 km² of land**, elevation -1024..6144 m |
14:7 is exactly the export's own 2:1, so the *aspect* is undistorted and both axes come out at the same metres
per pixel, which is what `--scout` checks.
### Three things about it that are not obvious
> These three were written when the window was a 1355 px square at latitude -24 — 6 x 6 tiles, 30.60 km a
> side, 22.583 m a pixel. The window is the whole planet now and two of the numbers below moved with it, but
> the reasoning is unchanged and is why the manifest looks the way it does. **The second one got worse, not
> better:** a 1355 px window at latitude -24 stretched a uniform 9.6%, whereas reading the *entire* cylinder
> flat stretches by 1/cos(latitude) at every row, which is unbounded at the poles. The polar strips of
> `L_World` are therefore smeared east-west, and that is the price of importing the whole map rather than a
> patch of it. It is not a defect to fix here; it is the reason a window at middle latitudes was the original
> shape, and the reason Orogen's own export (below) cosine-corrects at the centre latitude.
**The source carries no scale, so the scale is a choice.** Orogen's heightmap export is a cylindrical
projection with a fixed -5000..6000 m ramp and nothing saying how wide the planet is. `Planet.json` says 100 km
round, and at 100 km the planet is 31.8 km *across*: a flat 30 km square is bigger than the planet and there is
no window to cut. `metres_per_pixel` is therefore a manifest number rather than something derived, and it is
the number that decides how much land a window can hold. 22.583 m a pixel is a 185 km circumference, chosen as
the finest reading whose best window still clears 900 km² of land. Coarser buys more land and blunter ground.
**The map is read flat, not unprojected.** A cylindrical map read flat stretches east-west by 1/cos(latitude);
this window sits at latitude -24 so its ground is 9.6% wider east-west than Orogen drew it. That is the price
of not projecting, and it is much cheaper than the alternatives: cos-correcting the crop stretches the window's
own edges by ±35% across the latitudes it spans, and a proper azimuthal projection of a patch two thirds the
width of the planet distorts more still. Keep a window at middle latitudes and the flat reading is a few
per cent. `--scout` prints the stretch it would cause.
**The heights are Orogen's, not the generator's.** Orogen normalises land so its 99.5th percentile stands at
the import page's peak setting, so these are a browser preview's metres. `terrain bake` makes this same
continent a plain about 116 m tall; the export makes it 2972 m. Treat the relief as art. `sea_scale` is the one
correction applied, and only to the sea: the export's abyss is 3 km down on a whole-planet ramp, which over a
30 km window is either a clipped plateau with a cliff at every shore, or an elevation range so wide the land
loses its precision.
### The other way to get the tiles: straight out of Orogen
`generate_region_tiles.py` cuts the tiles out of a whole-planet PNG. World Orogen can now write them
itself, which skips the PNG and, more usefully, **skips inventing the scale**. Open `Tools/Orogen` in a
browser, press **Export Map** then **Unreal Landscape…**, and point the folder picker at
`RawContent/World/`. It writes `RegionTiles/` and a `Region.json` beside it, in exactly the shape
`region_manifest.py` reads, so `create_region_world.py` and `build_region.sh` are unchanged.
What that buys, and what it does not:
- **The scale stops being a guess.** You give the planet's circumference (this project's is in
`Planet.json`: 100 km) and the export records `metres_per_pixel`, the window in degrees and the
projection into the manifest. `metres_per_pixel` in the hand-written manifest above is a number somebody
chose; here it is a consequence.
- **The window is a window**, not a crop of a planet-wide raster, so the sampling resolution is spent on
the ground you are cutting, and the heights come back as float rather than through a 16-bit ramp.
- **The projection is cosine-corrected** at the window's centre latitude, so the east-west stretch is split
between the north and south edges instead of landing entirely on one. The panel prints it.
- **It does not make the ground finer.** The mesh still resolves about 200 m. That is the next section.
- **It will not overwrite this `Region.json`.** Most of that file is the reasoning behind its numbers, so
when one is already there the export writes `Region.generated.json` beside it and says so; rename it over
the old one once you have read the difference. The tiles in `RegionTiles/` *are* overwritten.
- **The cutter cannot run against that manifest**, and says so. Its `source.kind` is `orogen_render` and it
names no file, because there is no PNG to re-cut from — the tiles came out of the browser. If a tile file
goes missing, `create_region_world.py` notices and reaches for `generate_region_tiles.py`, which now
stops with a message telling you to re-export from Orogen rather than a `KeyError` three frames down.
Everything else `region_manifest.py` exposes works unchanged, `metres_per_pixel()` included.
It also puts a hard number on something this document only implies. **A 100 km circumference is a 3183 km²
planet.** The 936 km² window above is 29% of its entire surface, which is why it comes out as a rectangle
110° on a side with 74.7% of east-west stretch at its edge. Nothing is wrong with the tiles that produces —
they are what a flat reading of most of a small globe looks like — but if you want a window that a sphere
this size can hold flat, it is a few hundred km², not nine hundred. The panel's default, 4 × 2 tiles, is
20.4 × 10.2 km and 208 km² at 5.4% stretch.
Requires Chrome or Edge on desktop (the File System Access API); the panel says so if the browser lacks it.
### What is missing, and where it comes from
The source resolves about 200 m — Orogen solves on a 204 K-region sphere mesh — so below that the ground is
smooth, and an 11x upsample cannot invent what is not there. There is no erosion pass here and therefore no
wear, flow or deposit map, which is why the paint layers are slope and altitude alone rather than L_World's
richer rules. None of that is a defect to be fixed here: the detail is the Go generator's job, and
`terrain tiles` already writes 5 km tiles of 2 m samples over a bake. When those tiles replace the window as
the source, `generate_region_tiles.py` is what changes and nothing downstream of it does.
### Seams
Neighbouring tiles share their edge vertices and every vertex is sampled from its **global** position in the
window, so a shared column is computed twice from the same source coordinates and comes out bit-identical;
nothing blends or stitches. The paint-layer break-up noise goes through `fbm_at` at global coordinates for the
same reason. The one thing that did not follow from this was slope: `np.gradient` takes a one-sided difference
at an array edge, which is not what the neighbour computes for that vertex, and every tile boundary came out as
a one-vertex line of different paint. Tiles are therefore sampled with one vertex of margin on each side, the
layers derived over the lot, and the margin cropped off.
### The overlay: reserved, not built
`Region.json` reserves an `overlay` block for the annotation layer (D-57), and `region_manifest.py` has the
place a tile's mark map would go (`marks_path`). **Neither is implemented.** No mark map is written, and
nothing here reads a mark: no forest, no road and no settlement is placed from one, which is deliberate — the
overlay is a feature this pipeline carries a slot for, not an input to any outcome in it.
It is left unbuilt rather than written blind because there is no overlay to run it against yet, and a carry
that has never carried anything is a guess about a file format. When there is one, this is the shape it should
take, and it is the shape `terrain tiles` already uses: an 8-bit mark index beside every tile, registered to
the same window and cut on the same global coordinates as the height, plus the features in world metres, with
the legend from `RawContent/World/Templates/*.overlay.json`. Until then the key is documentation of intent.
### Why the level can look empty, and where the terrain actually is
`ChangeGridSize` splits every landscape into world-partition streaming proxies: at
`streaming_grid_components` 5 over a tile's 10 x 10 components that is four proxies a tile, **144 over the
window**, and they hold *all* 3600 components. The thirty-six `Landscape` actors left behind are always-loaded
and carry **none**. So a freshly opened level shows only what is always loaded - the sun, the sky dome, the fog
and the sea plane - and the sea plane at Z 0, lit through the pack's cloud-shadow light function, looks
convincingly like soft terrain from above. It is not. `trace_world` straight down at the origin hitting Z 0
is the quick way to tell.
Nothing is lost when this happens: World Partition holds all 190 actor descriptors and reports the right world
bounds. Three ways to see the ground:
- **Load a region.** Window > World Partition, drag a box, right-click > Load Region. No rebuild, and it is
the intended editor workflow.
- **Build HLODs.** The World Partition window's Build HLODs button, or the
`WorldPartitionHLODsBuilder` commandlet. Unloaded ground then draws as proxy meshes, so the whole 936 km²
is visible from the air. This is the right answer at this size and `L_World` needs it too.
- **Stop splitting.** `streaming_grid_components: 0` skips `ChangeGridSize` entirely and leaves the components
on the always-loaded landscape - measured, 100 a tile instead of 0. The level then just opens showing
everything, at the cost of loading 3600 components at once and giving up streaming.
A related trap, and the reason the player starts were once 180 m underground: **do not trace for the ground in
a commandlet.** The landscape's collision is not reliably present there, the sea plane's is, and a trace that
hits the sea returns `0.0` rather than failing. Read the height out of the heightmap instead, as
`create_region_world.pad_height_cm` does.
+79
View File
@@ -0,0 +1,79 @@
{
"_comment": "The whole Orogen planet export, scaled so it holds about 900 km2 of land, cut into Unreal landscapes. Read by Scripts/Authoring/generate_region_tiles.py and create_region_world.py through region_manifest.py, which is where every key is explained. World.json beside this file is the square 14.28 km canvas the numpy pipeline builds and shares nothing with this but the height contract.",
"_comment_level": "L_World: there is one world level and this builds it. The square 14.28 km canvas that used to own this name is the numpy pipeline's and is legacy (World.json beside this file now points at L_Canvas_Proto). The tiles are named after the level's last segment, so they are L_World_x0_y0_Height.png and the like.",
"level": "/Game/Maps/L_World",
"_comment_tiles": "14 x 7 landscapes of 2551 vertices. 2550 quads is 10 x 255, so the engine gives each tile 10 x 10 components of 255 quads: 100 a tile, 9800 over the window. Neighbours share their edge vertices, so the grid is 35701 x 17851 vertices, 71.40 x 35.70 km, 2549 km2 of map holding about 925 km2 of land. 14:7 is exactly the export's own 2:1, so the ground is not stretched. 637 M vertices is not an import any single Landscape actor will take; ninety-eight of 6.5 M each is the same work in ninety-eight resumable pieces.",
"tiles": { "columns": 14, "rows": 7, "vertices": 2551 },
"quad_cm": 200,
"sea_level_m": 0,
"spawn_pad_m": 150,
"_comment_streaming": "Components per world-partition streaming proxy, per side. 5 over a tile's 10 x 10 gives four proxies a tile, 2.55 km each.",
"streaming_grid_components": 5,
"_comment_elevation": "The whole export spans -5000..5988 m on Orogen's fixed ramp; after sea_scale that is -850..5988. -1024..6144 spans 7168 m, an exact Z scale of 1400, and 10.9 cm a step over 16 bits. Nothing clips at either end.",
"elevation_m": { "min": -1024, "max": 6144 },
"source": {
"_comment": "A cylindrical (2:1 equirectangular) planet heightmap, read flat. Orogen writes 16-bit greyscale on a fixed ramp: 0 is -5000 m and 65535 is +6000 m, whatever the planet, because the ramp is absolute so the same shade always means the same height. Its land is normalised before that, scaled so the 99.5th percentile stands at the import page's peak setting, so these metres are Orogen's and not the Go generator's: the same painting baked by `terrain bake` makes its largest continent a plain about 116 m tall. Treat the heights as art.",
"kind": "planet_map",
"path": "RawContent/World/Orogen Gens/orogen-heightmap-7945.png",
"elevation_m": { "min": -5000, "max": 6000 },
"_comment_scale": "What one source pixel is worth on the ground. The file carries no scale of its own, so this is a choice, and it is the choice that decides how much land the world holds. 8.7158 m a pixel puts the whole 8192 x 4096 export on 71.40 x 35.70 km: 2549 km2 of map and, at the export's 36.3% land, about 925 km2 of land. Both axes come out at the same metres per pixel, which is what `--scout` checks: the export is 2:1 and so is a 14 x 7 grid of square tiles, so nothing is stretched.",
"metres_per_pixel": 8.7158203125,
"_comment_window": "The rectangle of source pixels the world is cut from. This is the whole export - every pixel of the planet is imported, not a crop of it.",
"window": { "x": 0, "y": 0, "width": 8192, "height": 4096 },
"_comment_sea_scale": "Multiplies everything below sea level. Orogen's abyss is 5 km down on a ramp built for a whole planet, and 64% of this map is ocean; 0.17 brings the deepest water to -850 m, which fits the elevation range with nothing clipped and leaves the shore a slope instead of the cliff a clamp would cut. Land is untouched.",
"sea_scale": 0.17
},
"_comment_overlay": "Reserved for the annotation layer (D-57) and NOT IMPLEMENTED. There is a place for a per-tile mark map in region_manifest.py and nothing writes one; no pass here reads a mark, and no forest, road or settlement is placed from one. That is deliberate - the overlay is a feature this pipeline keeps a slot for, not an input to any outcome in it - and it is left unbuilt rather than written blind because there is no overlay to run it against yet. The README next to this file says the shape it should take when there is one.",
"_comment_layers": "The paint-layer rules. Three of them read the height alone - rock by slope (rise over run), high rock by altitude in metres, and a noise break-up so no boundary is a contour line - because there is no erosion pass on this route and therefore no wear, flow or deposit map to read (D-74). The rest read the *biome*, which is a different kind of input and is described under `biomes` below.",
"layers": {
"rock_slope_start": 0.55,
"rock_slope_full": 1.05,
"high_altitude_start_m": 1400,
"high_altitude_full_m": 2000,
"breakup_m": 18,
"breakup_cells": 24,
"breakup_seed": 7,
"_comment_beach": "The shore layer, from height and slope alone. `above_sea_m` is full strength up to that height, fading out by `fade_m`, and only where the ground is flatter than `max_slope` so a cliff foot is rock and not sand. This is the one biome that is an approximation rather than a reading: the bake's coast pass knows where beaches actually are (it reports beach_km2 and planed_km2) and this route does not carry that, which D-74 accepted as the price of dressing Route A now.",
"beach": { "above_sea_m": 12, "fade_m": 25, "max_slope": 0.18 },
"_comment_biomes": "Where the biome layers come from. Two categorical maps of the whole planet, both read by identity in normalised u,v - the same coordinates the height is sampled at, so a biome and the ground under it cannot drift apart. That identity is measured, not assumed: the painting is 7738x3761 and the heightmap 8192x4096, and the two candidate registrations were tested against each other on land/sea agreement, where identity scored 98.09% against 96.14% and won in every latitude band including the polar ones.",
"_comment_class_source": "The *painting*, not Orogen's class render. A painted map is made of its legend's own colours and nothing else - measured, worst nearest-colour distance 0.0 - whereas the render is those colours double-encoded to sRGB, which puts exported `desert` nearer to legend `ice` than to legend `desert`. One is data; the other is a picture of data.",
"_comment_climate_source": "Koppen classes, read from Orogen's climate export against the palette in Tools/Orogen/js/koppen.js. The palette is parsed out of the JavaScript rather than copied beside it, because that is where it is used and two copies is how one goes stale. Encoding the table's linear colours to sRGB reproduces every observed colour to within 1.4/255.",
"biomes": {
"class_image": "RawContent/World/Templates/Map5.png",
"class_legend": "RawContent/World/Templates/Map5.legend.json",
"climate_image": "RawContent/World/Orogen Gens/orogen-climate-7945.png",
"koppen_js": "Tools/Orogen/js/koppen.js",
"masks_dir": "RawContent/World/Biomes",
"_comment_blend_m": "How wide a biome boundary is on the ground. Blurred once, globally, in the source's own pixels by `mapart biomes`, so a tile reads an already-smooth field and two tiles agree at a shared vertex without either carrying a margin the width of the blend.",
"blend_m": 400
},
"_comment_paint": "The landscape's paint layers, in order. `name` is what the material blends and what the weightmap file is called. Eight is the budget and the budget is the design (D-74): Unreal packs four layers per weightmap texture per component and there are 9800 components, so the fourth layer is free and the fifth doubles the weightmap memory. `enabled` false is written by no one and imported by no one - the five biome layers stay off until Phase 2 lands a substance and a layer info for each, so today's build is unchanged. `mapart biomes` still writes their masks either way, which is what makes them inspectable before anything is imported.",
"_comment_rules": "slope: rock, by gradient. altitude: high rock, by metres. beach: the shore rule above. class: the painted classes named. climate: the Koppen codes named. remainder: whatever is left, which exactly one layer must claim.",
"paint": [
{ "name": "Base_Layer", "rule": "slope", "enabled": true, "note": "the rock. The pack's name; it is not a base." },
{ "name": "Layer_03", "rule": "altitude", "enabled": true, "note": "high rock." },
{ "name": "Layer_02", "rule": "remainder", "enabled": true, "note": "the meadow grass, and whatever no other layer claimed." },
{ "name": "Beach", "rule": "beach", "enabled": true, "note": "Thai_Beach_Sand. The one biome that is an approximation rather than a reading; see _comment_beach." },
{ "name": "Sand", "rule": "class", "enabled": true, "classes": ["desert"] },
{ "name": "Ice", "rule": "class", "enabled": true, "classes": ["ice"], "note": "Snow, not blue ice: an ice cap's surface is snow. Its scan is 0.30 m where every other substance is 2 m, so it tiles nearly seven times more densely and the material must not use one tiling number for every layer." },
{ "name": "Regolith", "rule": "class", "enabled": true, "classes": ["crater"] }
],
"_comment_no_jungle": "There was a Jungle layer here reading Koppen Af+Am+Aw, and it was dropped rather than dressed wrongly (D-76): the nearest substance available is a mossy rocky ground, which is temperate and damp where a tropical forest floor is leaf litter, and the wrong green over the whole equator is more misleading than no biome at all. The equator wears the remainder layer - meadow grass - which is generic but not wrong. Everything needed to bring it back is still here: `mapart biomes` still knows the rule, layers.biomes still names the climate source and the Koppen table, and reinstating it is this entry restored plus a substance."
}
}
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:60ddbf49da40e2ab2e1b237d2059716e7c3570723046b98b24f1e332b9824a4f
size 2015822
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f55009607fcb2a5ea782300de4ea81d34ea2ef3d955d5529434b8e85f59122fd
size 537370
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:4c567c9c46586f7276441bffeff9f367f2e91cb3c86a0dfe6fe7f8fc4c386b81
size 2263576
@@ -0,0 +1,46 @@
{
"_comment": "What each painted colour in Map3.jpg means. See README.md next to this file. Rates are rock uplift in mm/yr, and the number to think in is not the rate but the hillslope angle it fixes: at an 8 m cell and K 5e-5, tan(angle) = 2.5 x the rate, so 0.08 is 11 degrees on every divide and 0.25 is 32. Do not read internal/stats' \"plain below 0.1 mm/yr\" as terrain - it is a reporting bucket, and 0.1 mm/yr is hill country. `terrain plan` prints the angle and what it reads as for every class here.",
"image": "Map3.jpg",
"warn_distance": 60,
"classes": [
{ "name": "ocean", "rgb": [ 91, 175, 185], "sea": true, "depth_m": 512 },
{ "name": "deep", "rgb": [ 65, 165, 180], "sea": true, "depth_m": 512 },
{ "name": "shelf", "rgb": [153, 204, 221], "sea": true, "depth_m": 120 },
{ "name": "surf", "rgb": [221, 238, 238], "sea": true, "depth_m": 20 },
{ "_comment_ice": "snow is display and material only - it changes no height and no pass reads it, it just stops the polar caps rendering as meadow. What makes the cap read as ice in the terrain is the detail block: an ice sheet has meltwater channels but not a dendritic bedrock gully network, so it gets almost no droplets. Uplift is low and the cap is still solved like any other land; a proper ice dome would be a stamp after the solve, the way the crater is.",
"name": "ice", "derived": true, "rgb": [250, 250, 250], "uplift_mm_yr": 0.05, "k_mult": 1.0,
"snow": true, "lithology_mix": 0,
"detail": { "droplets_per_cell": 0.02, "strata_contrast": 0.15, "amplitude_m": [1, 3] } },
{ "_comment_plain": "coastal_plain_km puts the range inland. For n=1 the uplift rate alone fixes the hillslope angle, so a uniformly painted island sits at the angle of repose right down to the water; this ramps the rate up from coastal_floor_mm_yr over the first few km so there is a plain in front of the range. It is the opt-in, explicit version of the taper D-52 removed - that one was hidden and went to zero at the waterline.",
"_comment_massif": "The massif is what makes this a landscape rather than a landmass. 0.08 mm/yr is an 11 degree hillslope, and before this block it was 11 degrees on every divide from the waterline to the summit, so the whole continent came out as continuous hill country with no flat ground anywhere on it. Now 0.08 is the rate a *hill mass* reaches and 0.012 - 1.7 degrees, a plain - is the ground between them, with a sixth of it standing up. That is the North European Plain with the Harz and the Ardennes in it, which is what a lowland is.",
"name": "lowland", "rgb": [153, 204, 102], "uplift_mm_yr": 0.08, "k_mult": 1.0,
"massif": { "floor_mm_yr": 0.012, "fraction": 0.16 },
"coastal_plain_km": 1.0, "coastal_floor_mm_yr": 0.012 },
{ "_comment_faults": "A fault is a difference in uplift rate across a line - steep on one side, gentle on the other - which erosion then carves into a scarp. It is what puts an escarpment and a straight fifteen-kilometre valley inside a range, and neither is something the painting can draw. throw_m is the whole step across the fault over the run - how much higher the upthrown side would stand than the downthrown one if nothing eroded either, which since D-62 is the step and not a full throw on each flank, so a number written before that asks for half what it used to get. A fault is a range front several kilometres wide, not a line: the rate crosses over about a kilometre and the upthrown flank reaches six, which is what lets erosion cut valleys into it instead of printing the profile on the surface. Where several of them overlap - which at that width is most of a faulted class, and they are sub-parallel by design - the total saturates at 1.6 times the strongest single fault rather than adding up (D-63), so raising per_1000km2 past the overlap point buys texture and not height; per_1000km2 is a density over *this class*, which on this template is 549 km2 of highland, so 25 is about fourteen traces before the long ones step into segments. Drop the block to have none, which is what a plain should have.",
"name": "highland", "rgb": [ 68, 170, 102], "uplift_mm_yr": 0.25, "k_mult": 1.0,
"faults": { "per_1000km2": 25, "throw_m": [120, 400], "length_km": [6, 18] },
"_comment_clamped": "0.90 mm/yr is 66 degrees at a divide against a 35 degree angle of repose, so the repose clamp shapes this class rather than erosion does and the ground bakes out as flat polygonal facets. The ceiling is U = tan(talus)*K*cell = 0.280 mm/yr at an 8 m cell; `terrain plan` prints the angle for every class. Dropping to about 0.25 gives erosion-shaped mountains at roughly 350 m on a 20 km island instead of 695 m of talus - relief and steepness are the same knob at a fixed cell, so that is the trade.",
"_comment_massif": "A range is a belt, not a dome. Painted solid, this class used to raise every cell of an island to 32 degrees, which is why the first planet had two islands that were nothing but mountain. 0.25 is now the rate the belt reaches and 0.045 - 6.4 degrees, rolling - is its foreland, with three tenths of it standing up. It cuts the same fabric as the lowland beside it, so the hills in the neighbouring lowland are that orogen's outliers rather than an unrelated noise.",
"massif": { "floor_mm_yr": 0.045, "fraction": 0.30 },
"coastal_plain_km": 4.0, "coastal_floor_mm_yr": 0.03 },
{ "_comment_desert": "A desert is not a low uplift rate - a wet lowland has one of those too - so at the geology grid it is only k_mult, which being below 1 means less water doing less work and therefore steeper, more angular ground held further from being worn down. What makes it read as desert is the detail block, at two metres: a fifth of the running water, so the dendritic gully network thins out to isolated wadis; twice the strata contrast, so mesas and ledges keep their edges because nothing is rounding them off; and several times the noise amplitude, which on flat ground is dune.",
"_comment_massif": "k_mult 0.5 doubles the angle a rate makes - tan(angle) = 2.5 x rate / k_mult - so 0.10 here is 27 degrees, not 14, and painted solid it made a plateau of mountain. 0.10 is the rate a mesa block reaches and 0.015 - 4.3 degrees - is the sand sea between them.",
"name": "desert", "rgb": [238, 221, 153], "uplift_mm_yr": 0.10, "k_mult": 0.5,
"faults": { "per_1000km2": 10, "throw_m": [60, 220], "length_km": [5, 14] },
"massif": { "floor_mm_yr": 0.015, "fraction": 0.14 },
"coastal_plain_km": 1.5, "coastal_floor_mm_yr": 0.015,
"detail": { "droplets_per_cell": 0.035, "strata_contrast": 0.92, "amplitude_m": [7, 14] } },
{ "_comment_crater": "An impact is an event, not a rate, so it is stamped onto the finished terrain after the solve rather than painted as uplift - a closed basin built out of negative uplift would be filled in by the priority-flood within a hundred steps. The shape comes from the painted blob: distance in from its own shoreline, normalised by its widest point, so rim_at and wall_at describe every crater whatever size it was drawn. The uplift rate here is only what the solve does to the flanks before the stamp lands, so it is low.",
"name": "crater", "rgb": [124, 117, 111], "uplift_mm_yr": 0.15, "k_mult": 1.5,
"lithology_mix": 0,
"crater": { "rim_m": 340, "floor_m": 60, "rim_at": 0.30, "wall_at": 0.62 } },
{ "_comment": "White is painted twice: the polar caps and the outline stroke around every island. The stroke owns the colour, because it is the one that has to be recognised wherever it appears; a white region touching the top or bottom row of the map is a cap and becomes ice instead. Everything else white dissolves into whichever real class is nearest.",
"name": "stroke", "rgb": [238, 238, 238], "stroke": true, "edge_class": "ice" }
]
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:f76becfa11e4e6282caa01521a18802e6564f5311cde2c0cf98597d6deab4887
size 187973
@@ -0,0 +1,56 @@
{
"_comment": "A painted tectonic layer: one colour per plate, and how that plate is moving. heading_deg is a compass bearing over the map - 0 points at the top of the image, 90 to the right, 180 to the bottom. speed_cm_yr is drift; what happens at a margin is the difference between the two plates either side of it, so two plates drifting the same way are a boundary doing nothing. spin_deg_myr turns a plate about its own centre, positive clockwise, and it is worth setting on at least one: without it every margin is the same all the way along, and with it one end collides while the other slides. Paint the plates, not the mountains - where two of these meet, the collision, the belt and its faults are worked out from the motions. Repaint the blobs freely; only the colours have to keep matching this file.",
"image": "Map3.plates.png",
"warn_distance": 60,
"plates": [
{
"name": "plate_0",
"rgb": [219, 83, 83],
"speed_cm_yr": 3,
"heading_deg": 341,
"spin_deg_myr": -22.91
},
{
"name": "plate_1",
"rgb": [83, 123, 219],
"speed_cm_yr": 2.1,
"heading_deg": 207,
"spin_deg_myr": 15.62
},
{
"name": "plate_2",
"rgb": [163, 219, 83],
"speed_cm_yr": 5.8,
"heading_deg": 192,
"spin_deg_myr": 44.51
},
{
"name": "plate_3",
"rgb": [219, 83, 202],
"speed_cm_yr": 5.9,
"heading_deg": 212,
"spin_deg_myr": -44.77
},
{
"name": "plate_4",
"rgb": [83, 219, 197],
"speed_cm_yr": 4.4,
"heading_deg": 285,
"spin_deg_myr": -33.44
},
{
"name": "plate_5",
"rgb": [219, 157, 83],
"speed_cm_yr": 3.5,
"heading_deg": 321,
"spin_deg_myr": -26.46
},
{
"name": "plate_6",
"rgb": [117, 83, 219],
"speed_cm_yr": 1.6,
"heading_deg": 281,
"spin_deg_myr": 12.3
}
]
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:36abeaf5151710e163008efacd9d2b17bfcb0371cefba5eb040425290d5428ae
size 19083
@@ -0,0 +1,46 @@
{
"_comment": "What each painted colour in Map3.jpg means. See README.md next to this file. Rates are rock uplift in mm/yr, and the number to think in is not the rate but the hillslope angle it fixes: at an 8 m cell and K 5e-5, tan(angle) = 2.5 x the rate, so 0.08 is 11 degrees on every divide and 0.25 is 32. Do not read internal/stats' \"plain below 0.1 mm/yr\" as terrain - it is a reporting bucket, and 0.1 mm/yr is hill country. `terrain plan` prints the angle and what it reads as for every class here.",
"image": "Map3.jpg",
"warn_distance": 60,
"classes": [
{ "name": "ocean", "rgb": [ 91, 175, 185], "sea": true, "depth_m": 512 },
{ "name": "deep", "rgb": [ 65, 165, 180], "sea": true, "depth_m": 512 },
{ "name": "shelf", "rgb": [153, 204, 221], "sea": true, "depth_m": 120 },
{ "name": "surf", "rgb": [221, 238, 238], "sea": true, "depth_m": 20 },
{ "_comment_ice": "snow is display and material only - it changes no height and no pass reads it, it just stops the polar caps rendering as meadow. What makes the cap read as ice in the terrain is the detail block: an ice sheet has meltwater channels but not a dendritic bedrock gully network, so it gets almost no droplets. Uplift is low and the cap is still solved like any other land; a proper ice dome would be a stamp after the solve, the way the crater is.",
"name": "ice", "derived": true, "rgb": [250, 250, 250], "uplift_mm_yr": 0.05, "k_mult": 1.0,
"snow": true, "lithology_mix": 0,
"detail": { "droplets_per_cell": 0.02, "strata_contrast": 0.15, "amplitude_m": [1, 3] } },
{ "_comment_plain": "coastal_plain_km puts the range inland. For n=1 the uplift rate alone fixes the hillslope angle, so a uniformly painted island sits at the angle of repose right down to the water; this ramps the rate up from coastal_floor_mm_yr over the first few km so there is a plain in front of the range. It is the opt-in, explicit version of the taper D-52 removed - that one was hidden and went to zero at the waterline.",
"_comment_massif": "The massif is what makes this a landscape rather than a landmass. 0.08 mm/yr is an 11 degree hillslope, and before this block it was 11 degrees on every divide from the waterline to the summit, so the whole continent came out as continuous hill country with no flat ground anywhere on it. Now 0.08 is the rate a *hill mass* reaches and 0.012 - 1.7 degrees, a plain - is the ground between them, with a sixth of it standing up. That is the North European Plain with the Harz and the Ardennes in it, which is what a lowland is.",
"name": "lowland", "rgb": [153, 204, 102], "uplift_mm_yr": 0.08, "k_mult": 1.0,
"massif": { "floor_mm_yr": 0.012, "fraction": 0.16 },
"coastal_plain_km": 1.0, "coastal_floor_mm_yr": 0.012 },
{ "_comment_faults": "A fault is a difference in uplift rate across a line - steep on one side, gentle on the other - which erosion then carves into a scarp. It is what puts an escarpment and a straight fifteen-kilometre valley inside a range, and neither is something the painting can draw. throw_m is the total displacement over the whole run, so it is the height of the scarp the fault would build if nothing eroded it; per_1000km2 is a density over *this class*, which on this template is 549 km2 of highland, so 25 is about fourteen traces before the long ones step into segments. Drop the block to have none, which is what a plain should have.",
"name": "highland", "rgb": [ 68, 170, 102], "uplift_mm_yr": 0.25, "k_mult": 1.0,
"faults": { "per_1000km2": 25, "throw_m": [120, 400], "length_km": [6, 18] },
"_comment_clamped": "0.90 mm/yr is 66 degrees at a divide against a 35 degree angle of repose, so the repose clamp shapes this class rather than erosion does and the ground bakes out as flat polygonal facets. The ceiling is U = tan(talus)*K*cell = 0.280 mm/yr at an 8 m cell; `terrain plan` prints the angle for every class. Dropping to about 0.25 gives erosion-shaped mountains at roughly 350 m on a 20 km island instead of 695 m of talus - relief and steepness are the same knob at a fixed cell, so that is the trade.",
"_comment_massif": "A range is a belt, not a dome. Painted solid, this class used to raise every cell of an island to 32 degrees, which is why the first planet had two islands that were nothing but mountain. 0.25 is now the rate the belt reaches and 0.045 - 6.4 degrees, rolling - is its foreland, with three tenths of it standing up. It cuts the same fabric as the lowland beside it, so the hills in the neighbouring lowland are that orogen's outliers rather than an unrelated noise.",
"massif": { "floor_mm_yr": 0.045, "fraction": 0.30 },
"coastal_plain_km": 4.0, "coastal_floor_mm_yr": 0.03 },
{ "_comment_desert": "A desert is not a low uplift rate - a wet lowland has one of those too - so at the geology grid it is only k_mult, which being below 1 means less water doing less work and therefore steeper, more angular ground held further from being worn down. What makes it read as desert is the detail block, at two metres: a fifth of the running water, so the dendritic gully network thins out to isolated wadis; twice the strata contrast, so mesas and ledges keep their edges because nothing is rounding them off; and several times the noise amplitude, which on flat ground is dune.",
"_comment_massif": "k_mult 0.5 doubles the angle a rate makes - tan(angle) = 2.5 x rate / k_mult - so 0.10 here is 27 degrees, not 14, and painted solid it made a plateau of mountain. 0.10 is the rate a mesa block reaches and 0.015 - 4.3 degrees - is the sand sea between them.",
"name": "desert", "rgb": [238, 221, 153], "uplift_mm_yr": 0.10, "k_mult": 0.5,
"faults": { "per_1000km2": 10, "throw_m": [60, 220], "length_km": [5, 14] },
"massif": { "floor_mm_yr": 0.015, "fraction": 0.14 },
"coastal_plain_km": 0, "coastal_floor_mm_yr": 0.1,
"detail": { "droplets_per_cell": 0.035, "strata_contrast": 0.92, "amplitude_m": [7, 14] } },
{ "_comment_crater": "An impact is an event, not a rate, so it is stamped onto the finished terrain after the solve rather than painted as uplift - a closed basin built out of negative uplift would be filled in by the priority-flood within a hundred steps. The shape comes from the painted blob: distance in from its own shoreline, normalised by its widest point, so rim_at and wall_at describe every crater whatever size it was drawn. The uplift rate here is only what the solve does to the flanks before the stamp lands, so it is low.",
"name": "crater", "rgb": [124, 117, 111], "uplift_mm_yr": 0.15, "k_mult": 1.5,
"lithology_mix": 0,
"crater": { "rim_m": 340, "floor_m": 60, "rim_at": 0.30, "wall_at": 0.62 } },
{ "_comment": "White is painted twice: the polar caps and the outline stroke around every island. The stroke owns the colour, because it is the one that has to be recognised wherever it appears; a white region touching the top or bottom row of the map is a cap and becomes ice instead. Everything else white dissolves into whichever real class is nearest.",
"name": "stroke", "rgb": [238, 238, 238], "stroke": true, "edge_class": "ice" }
]
}
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:d76d7c0e968f8ec52eff15d07ff5c227368203d7e754f6bef6288333042cb423
size 1681659
@@ -0,0 +1,48 @@
{
"_comment": "What each painted colour in Map5 means. See README.md next to this file. Rates are rock uplift in mm/yr, and the number to think in is not the rate but the hillslope angle it fixes: at an 8 m cell and K 5e-5, tan(angle) = 2.5 x rate / k_mult, so 0.08 is 11 degrees on every divide and 0.25 is 32. Read the *typical* column `terrain plan` prints, not the divide one - a divide is the steepest ground a rate can make and almost none of a map is divide, so the median comes out at about a third of it in tangent. Reading the divide angle as the landscape is how a legend gets set two or three times too hot.",
"_comment_colours": "Every rgb below is the *measured modal colour* of Map5.jpg, not a guess at it: the painting was sampled, its colours clustered, and the mode of each cluster taken. Map5.png beside it is the same painting with every pixel snapped to exactly these seven colours, which is what the classifier would do anyway and which removes the JPEG halo along every painted edge for good. The manifest points at the PNG; the JPEG is kept as the original.",
"image": "Map5.png",
"warn_distance": 60,
"classes": [
{ "_comment_ocean": "The open sea. depth_m is the painted sea floor, which internal/coast reads per cell as the abyss it builds a shelf and a continental slope down to; the shelf break itself is pipeline.coast.break_m and defaults to 130 m on a planet (D-64), so it is never deeper than the water it is a break in.",
"name": "ocean", "rgb": [ 91, 175, 185], "sea": true, "depth_m": 512 },
{ "_comment_deep": "The second, darker blue the author painted - the big offshore lobe on the right, the band through the north-west islands, and the channels between the island groups. Taken as deeper water rather than as a shallow shelf, because it is darker than the open ocean and not lighter. If a shelf was meant instead, set depth_m to about 120 and rename it; nothing else in the legend has to change. It is 9% of the world, so it will show.",
"name": "deep", "rgb": [ 66, 165, 180], "sea": true, "depth_m": 900 },
{ "_comment_ice": "The polar caps, painted white at the top and bottom of the map and nowhere else - so unlike Map3's white this is a class in its own right rather than an outline stroke rescued at the poles, and it needs no stroke/edge_class pair. snow is display and material only: it changes no height and no pass reads it, it just stops the caps rendering as meadow. What makes a cap read as ice in the terrain is the detail block - an ice sheet has meltwater channels but not a dendritic bedrock gully network, so it gets almost no droplets.",
"name": "ice", "rgb": [250, 250, 250], "uplift_mm_yr": 0.05, "k_mult": 1.0,
"snow": true, "lithology_mix": 0,
"detail": { "droplets_per_cell": 0.02, "strata_contrast": 0.15, "amplitude_m": [1, 3] } },
{ "_comment_plain": "coastal_plain_km puts the range inland. For n=1 the uplift rate alone fixes the hillslope angle, so a uniformly painted island sits at the same angle right down to the water; this ramps the rate up from coastal_floor_mm_yr over the first kilometre so there is a plain in front of the ground behind it.",
"_comment_massif": "The massif block is what makes this a landscape rather than a landmass. 0.08 mm/yr is an 11 degree divide, and without this block it would be 11 degrees from the waterline to the summit, so the whole continent would come out as continuous hill country with no flat ground anywhere on it. 0.08 is the rate a *hill mass* reaches and 0.012 - 1.7 degrees at a divide, a plain - is the ground between them, with a sixth of it standing up. That is the North European Plain with the Harz in it, which is what a lowland is. The threshold is a quantile of the planet, never of this class, so the hills here and the ones in the highland next door are parts of one structure.",
"name": "lowland", "rgb": [150, 200, 105], "uplift_mm_yr": 0.08, "k_mult": 1.0,
"massif": { "floor_mm_yr": 0.012, "fraction": 0.16 },
"coastal_plain_km": 1.0, "coastal_floor_mm_yr": 0.012 },
{ "_comment_faults": "A fault is a difference in uplift rate across a line - steep one side, gentle the other - which erosion then carves into a scarp. It is what puts an escarpment and a straight fifteen-kilometre valley inside a range, and neither is a shape a brush can draw. throw_m is the *step across* the fault over the run (D-62), not a full throw on each flank. A fault is a range front several kilometres wide rather than a line: the rate crosses over about a kilometre and the upthrown flank reaches six, which is what lets erosion cut valleys into it instead of printing the profile on the surface. Where several overlap - which at that width is most of a faulted class - the total saturates at 1.6x the strongest single fault rather than adding up (D-63), so raising per_1000km2 past the overlap point buys texture and not height. per_1000km2 is a density over *this class*: Map5 has about 554 km2 of highland, so 25 is roughly fourteen traces before the long ones step into segments.",
"_comment_massif": "A range is a belt, not a dome. Painted solid, 0.25 would raise every cell of these continents to a 32 degree divide, which is how a planet ends up with islands that are nothing but mountain. 0.25 is the rate the belt reaches and 0.045 - 6.4 degrees, rolling - is its foreland, with three tenths standing up.",
"_comment_ceiling": "The repose clamp takes over above U = tan(35 deg) x K x cell = 0.280 mm/yr at an 8 m cell, and past it the ground bakes out as flat polygonal facets rather than as erosion-shaped mountains. 0.25 sits just under it deliberately. Relief and steepness are the same knob at a fixed cell, so going higher buys facets, not mountains.",
"name": "highland", "rgb": [ 71, 175, 100], "uplift_mm_yr": 0.25, "k_mult": 1.0,
"faults": { "per_1000km2": 25, "throw_m": [120, 400], "length_km": [6, 18] },
"massif": { "floor_mm_yr": 0.045, "fraction": 0.30 },
"coastal_plain_km": 4.0, "coastal_floor_mm_yr": 0.03 },
{ "_comment_desert": "A desert is not a low uplift rate - a wet lowland has one of those too - so at the geology grid it is only k_mult, which being below 1 means less water doing less work and therefore steeper, more angular ground held further from being worn down. What makes it read as desert is the detail block at two metres: a fifth of the running water, so the gully network thins to isolated wadis; twice the strata contrast, so mesas and ledges keep their edges; and several times the noise amplitude, which on flat ground is dune.",
"_comment_massif": "k_mult 0.5 doubles the angle a rate makes - tan(angle) = 2.5 x rate / k_mult - so 0.10 here is a 27 degree divide, not 14, and painted solid it would be a plateau of mountain. 0.10 is the rate a mesa block reaches and 0.015 - 4.3 degrees - is the sand sea between them.",
"name": "desert", "rgb": [226, 215, 145], "uplift_mm_yr": 0.10, "k_mult": 0.5,
"faults": { "per_1000km2": 10, "throw_m": [60, 220], "length_km": [5, 14] },
"massif": { "floor_mm_yr": 0.015, "fraction": 0.14 },
"coastal_plain_km": 1.5, "coastal_floor_mm_yr": 0.015,
"detail": { "droplets_per_cell": 0.035, "strata_contrast": 0.92, "amplitude_m": [7, 14] } },
{ "_comment_crater": "The two grey blobs on the seam, at the far left and far right of the painting - one crater, drawn across the meridian, which the generator treats as a single feature because X wraps. An impact is an event and not a rate, so it is stamped onto the finished terrain after the solve rather than painted as uplift: a closed basin built out of negative uplift would be filled in by the priority-flood within a hundred steps. The shape comes from the painted blob - distance in from its own shoreline, normalised by its widest point - so rim_at and wall_at describe a crater of any size. The uplift rate here is only what the solve does to the flanks before the stamp lands, so it is low.",
"name": "crater", "rgb": [124, 116, 111], "uplift_mm_yr": 0.15, "k_mult": 1.5,
"lithology_mix": 0,
"crater": { "rim_m": 340, "floor_m": 60, "rim_at": 0.30, "wall_at": 0.62 } }
]
}
@@ -0,0 +1,48 @@
{
"_comment": "The annotation layer for Map5: a second painting, the same size as the template and registered to it, saying what is *placed on* the finished world rather than what the rock is doing. Paint it in `terrain studio` under the `overlay` tab (or press o), or let `terrain overlay` propose a first draft from a bake. See README.md next to this file, and Docs/Terrain.md.",
"_comment_blank": "Most of this sheet is nothing, and nothing is alpha rather than a colour: an unpainted pixel is transparent, so no colour has to be spent on the background and an export with a white matte behind it does not turn the world into whatever mark white is nearest. An opaque pixel further than match_distance from every mark below is dropped and counted - `terrain plan` says how many, which is the only way a colour the legend forgot ever shows.",
"_comment_image": "There is no image yet, and that is the normal way to start: the legend says what the marks mean and the sheet stays empty until the first save from the studio, or until `terrain overlay` writes one. The manifest's planet.overlay is what points at it once it exists.",
"image": "Map5.overlay.png",
"match_distance": 40,
"min_area_px": 24,
"marks": [
{ "_comment": "The one thing on this layer the generator reads. The waterline roughening exists because a drawn shore is a smooth curve and a real coast is fractal - which is true of a coast nobody thought about and false of one traced off a map on purpose. Paint over the shore you drew by hand and it stays exactly where you put it while the rest of the world is still roughened. Either side of the waterline is enough; a brush stroke along it covers both.",
"name": "drawn_coast", "rgb": [255, 0, 255], "coast_jitter": 0,
"note": "Coastline as painted. No jitter." },
{ "_comment": "The same knob pointed the other way. A fjord or a ria coast wants more than the planet's own amplitude, not less; at 2.5 the bays are two and a half times as deep and just as wide, which is what makes them fjords rather than scallops.",
"_comment_generate": "This mark can also be generated - add \"generate\": { \"kind\": \"coast\", \"coast_km\": 2.5 } and `terrain overlay` will band every shore with it. It is left off deliberately: coast_jitter is the one overlay property a pass reads, so generating this mark changes the next bake everywhere, and that is a decision to take on purpose rather than to inherit from a default.",
"name": "wild_coast", "rgb": [255, 128, 0], "coast_jitter": 2.5,
"note": "Chew this shore harder than the rest of the world." },
{ "_comment_inert": "Everything below is inert. No pass reads it, no height changes, and two bakes with and without it are the same terrain to the bit. What they do is travel: each one comes out as an index in the per-tile mask beside every heightmap, and as a feature in world metres in overlay.json - a centre, an area, an extent for an area; an ordered polyline for a path. That is what the engine places things from.",
"_comment_generate": "The generate block lets `terrain overlay` propose this mark from a baked world. not_classes keeps it off ground painted as ice or desert, which matters more than it sounds: height and slope cannot tell an ice cap from a meadow, and without it woodland grows across both poles. The treeline is derived from the land's own heights, because a number in metres means nothing until the world is baked.",
"name": "forest", "rgb": [0, 128, 0],
"note": "Where trees are scattered. The mask is the volume bound.",
"generate": { "kind": "forest", "cover": 0.45, "wavelength_km": 6, "max_slope_deg": 22,
"not_classes": ["ice", "desert"] } },
{ "_comment_settlements": "Three tiers, listed largest first, which is the order the generator places them in. They share one spacing rule - the largest min_spacing_km any of them sets - so a village never lands inside a city. Sites are scored on the three things the terrain actually knows: drainage, flat ground and distance to the sea. Everything else about where a town is belongs to you, which is why these are proposals in a sheet you edit.",
"name": "city", "rgb": [220, 30, 30], "min_area_px": 400,
"note": "One settlement per painted blob. overlay.json gives its centre and radius.",
"generate": { "kind": "settlement", "count": 4, "min_spacing_km": 9, "not_classes": ["ice"] } },
{ "name": "town", "rgb": [255, 200, 0], "min_area_px": 120,
"generate": { "kind": "settlement", "count": 12, "not_classes": ["ice"] } },
{ "name": "village", "rgb": [150, 90, 200], "min_area_px": 24,
"generate": { "kind": "settlement", "count": 30, "not_classes": ["ice"] } },
{ "_comment_path": "A path is a stroke whose width is not the point: it is thinned to its centreline and comes out as an ordered polyline, because the thing built from it on the other side is a spline. width_m is what the road really is on the ground and is carried rather than used. One stroke is one path - a fork reports its two longest arms as one line and drops the third - so paint each run separately and check the piece count in `terrain plan`.",
"_comment_generate": "Generated roads are a minimum spanning tree over the settlements along least-cost paths, not every pair, so there is exactly enough road to reach everywhere. Water is impassable, so each landmass gets its own network and a bridge stays a deliberate act.",
"name": "road", "rgb": [90, 60, 30], "kind": "path", "width_m": 8,
"generate": { "kind": "road", "max_slope_deg": 20 } },
{ "name": "track", "rgb": [200, 160, 110], "kind": "path", "width_m": 3 }
]
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:92464c0fb278db9b33e5a1ca06f5b41db23826fdb1004572377a72d061da15a9
size 205450
@@ -0,0 +1,56 @@
{
"_comment": "A painted tectonic layer: one colour per plate, and how that plate is moving. heading_deg is a compass bearing over the map - 0 points at the top of the image, 90 to the right, 180 to the bottom. speed_cm_yr is drift; what happens at a margin is the difference between the two plates either side of it, so two plates drifting the same way are a boundary doing nothing. spin_deg_myr turns a plate about its own centre, positive clockwise, and it is worth setting on at least one: without it every margin is the same all the way along, and with it one end collides while the other slides. Paint the plates, not the mountains - where two of these meet, the collision, the belt and its faults are worked out from the motions. Repaint the blobs freely; only the colours have to keep matching this file.",
"image": "Map5.plates.png",
"warn_distance": 60,
"plates": [
{
"name": "plate_0",
"rgb": [219, 83, 83],
"speed_cm_yr": 3,
"heading_deg": 38,
"spin_deg_myr": -22.76
},
{
"name": "plate_1",
"rgb": [83, 123, 219],
"speed_cm_yr": 5.3,
"heading_deg": 108,
"spin_deg_myr": -40.21
},
{
"name": "plate_2",
"rgb": [163, 219, 83],
"speed_cm_yr": 2.8,
"heading_deg": 333,
"spin_deg_myr": 21.11
},
{
"name": "plate_3",
"rgb": [219, 83, 202],
"speed_cm_yr": 2.7,
"heading_deg": 7,
"spin_deg_myr": -20.61
},
{
"name": "plate_4",
"rgb": [83, 219, 197],
"speed_cm_yr": 5.4,
"heading_deg": 142,
"spin_deg_myr": -40.89
},
{
"name": "plate_5",
"rgb": [219, 157, 83],
"speed_cm_yr": 1.5,
"heading_deg": 152,
"spin_deg_myr": 11.35
},
{
"name": "plate_6",
"rgb": [117, 83, 219],
"speed_cm_yr": 4.3,
"heading_deg": 207,
"spin_deg_myr": -32.87
}
]
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5ee213080c00e00a7e0c93e757d41e6b9ef559c25c480d028657013e4111f0d1
size 14509
+3
View File
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0cdaa91f191731bbdbf19fedcaae57e21e5a2d02e5d44cbbaad07744d0b4abc3
size 198624
+937
View File
@@ -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.
@@ -0,0 +1,31 @@
{
"_comment": "How a preview is drawn. Nothing here changes a height - two bakes of the same world under two palettes are the same terrain. Point a planet manifest at this file with \"palette\": \"<path relative to the manifest>\"; leave it out and these numbers are used anyway. Keys beginning with an underscore are comments.",
"_comment_land_stops": "The hypsometric ramp: sea level at t 0 to the top of the land at t 1. The top is a percentile rather than the maximum, so one high summit cannot push a whole continent into the bottom of the ramp.",
"land_stops": [
{ "t": 0, "rgb": [72, 106, 68] },
{ "t": 0.08, "rgb": [104, 132, 74] },
{ "t": 0.2, "rgb": [142, 152, 88] },
{ "t": 0.38, "rgb": [164, 148, 104] },
{ "t": 0.58, "rgb": [150, 128, 106] },
{ "t": 0.75, "rgb": [138, 130, 128] },
{ "t": 0.88, "rgb": [176, 174, 174] },
{ "t": 1, "rgb": [246, 246, 250] }
],
"sea_shallow": [56, 104, 136],
"sea_deep": [18, 40, 72],
"river": [70, 132, 180],
"_comment_ice": "Drawn wherever a class is marked snow, whatever height the ground stands at. Not pure white: white has nowhere left to go under the hillshade, so an ice sheet comes out as a flat cut-out with no shape in it at all.",
"ice": [232, 238, 245],
"_comment_top": "Where the top of the hypsometric ramp sits. The percentile is relative to the world being drawn, which is the only way a low continent is legible at all and is also a picture that lies about scale - measured on this planet's central landmass, a 47 m plain whose median slope is 0.6 degrees comes out with the same bare rock and white caps a 2800 m range would, because 40 m is the top of its own ramp. Set land_top_m to a height in metres for an absolute ramp instead; 0 keeps the percentile, and either way the run summary says which ceiling the preview was drawn against.",
"land_top_percentile": 99.5,
"land_top_m": 0,
"_comment_light": "Azimuth is degrees clockwise from north and altitude degrees above the horizon; north-west at 45 is what every DEM hillshade uses. Ambient is how lit the shaded side is - at zero a shadow is a hole - and gain how much the lit side brightens.",
"sun_azimuth_deg": 315,
"sun_altitude_deg": 45,
"ambient": 0.45,
"gain": 0.75
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:3d3b0f70b6932b36974a8423bf6007e5e48020a46c6a2e9cc92ec7bf578f8b26
size 2433944
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e6996dd53fc49134b7f557579da0207eaf6896ebb426782dac66d31e976c5658
size 81648
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:69215c65621f0a9d1fb30e5e5bf89e55bc6d11ca732c33e90fc5b0b7b3caa2b4
size 40881
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2671e9e5a603e400db6b37fa5041e42ff6bf4683128265fe743dbbb16a2a9135
size 3219685
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a1f87aa648e33fdcc18b7bdae4ac3641af0b9a807986f8fdde8f60d7c5648fc2
size 61095
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3cb1bfc9994ca075e63928c50ecfff3ac33c45e4e2870c396433c4eb636ac3f
size 2927709
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5677f8e3ea797425ad61d5b21be046c41d4c6131ec366779c06a898207836efd
size 41778
+542
View File
@@ -0,0 +1,542 @@
{
"craters": [
{
"Class": "crater",
"Blobs": 1,
"Cells": 153256,
"DeepestM": 0.31284677390522947,
"HighestM": 339.99997081616704
}
],
"elapsed": "1h38m22s",
"manifest": "D:\\GitDrive\\gitlab\\UnrealEnginePrototyping\\RawContent\\World\\Planet.json",
"plan": {
"manifest": "D:\\GitDrive\\gitlab\\UnrealEnginePrototyping\\RawContent\\World\\Planet.json",
"template": "Templates/Map3.jpg",
"legend": "Templates/Map3.legend.json",
"when": "2026-09-17T18:42:52Z",
"paint_w": 7738,
"paint_h": 3761,
"circumference_km": 100,
"height_km": 48.608,
"area_km2": 4860.8,
"cell_m": 8,
"grid_w": 12500,
"grid_h": 6076,
"pad_rows": 63,
"margin_cells": 63,
"elevation_min_m": -1024,
"elevation_max_m": 2048,
"seed": 7,
"match_far_px": 1021,
"match_worst_distance": 203.43549346168678,
"match_worst_at": [
0,
2043
],
"stroke_rescued_at_poles_px": 2084442,
"stroke_dissolved_px": 0,
"wrap_rows": 3761,
"wrap_differ": 355,
"wrap_land_against_sea": 261,
"wrap_far_edge_px": 48,
"classes": [
{
"name": "ocean",
"sea": true,
"cells": 35656741,
"share": 0.4694765108624095,
"depth_m": 512
},
{
"name": "deep",
"sea": true,
"cells": 6796744,
"share": 0.08948971691902567,
"depth_m": 512
},
{
"name": "shelf",
"sea": true,
"cells": 2880150,
"share": 0.03792165898617512,
"depth_m": 120
},
{
"name": "surf",
"sea": true,
"cells": 2949114,
"share": 0.038829677419354836,
"depth_m": 20
},
{
"name": "ice",
"sea": false,
"cells": 5437480,
"share": 0.0715928900592495,
"uplift_mm_yr": 0.05,
"k_mult": 1
},
{
"name": "lowland",
"sea": false,
"cells": 11042633,
"share": 0.14539345622119815,
"uplift_mm_yr": 0.08,
"k_mult": 1
},
{
"name": "highland",
"sea": false,
"cells": 8650568,
"share": 0.11389819618169848,
"uplift_mm_yr": 0.9,
"k_mult": 1
},
{
"name": "desert",
"sea": false,
"cells": 2383314,
"share": 0.03138003949967084,
"uplift_mm_yr": 0.1,
"k_mult": 0.5
},
{
"name": "crater",
"sea": false,
"cells": 153256,
"share": 0.0020178538512179065,
"uplift_mm_yr": 0.15,
"k_mult": 1.5
}
],
"regions": [
{
"id": 11,
"x0": 3077,
"y0": 2638,
"w": 5720,
"h": 2451,
"width_km": 45.76,
"height_km": 19.608,
"cells": 14019720,
"land_cells": 8977362,
"seam": false,
"estimate_minutes": 34.58264265335235,
"estimate_gb": 0.8225835487246513
},
{
"id": 2,
"x0": 3510,
"y0": 834,
"w": 4916,
"h": 1798,
"width_km": 39.328,
"height_km": 14.384,
"cells": 8838968,
"land_cells": 4802782,
"seam": false,
"estimate_minutes": 21.80320803613885,
"estimate_gb": 0.5186116173863411
},
{
"id": 13,
"x0": 8825,
"y0": 3281,
"w": 2694,
"h": 2317,
"width_km": 21.552,
"height_km": 18.536,
"cells": 6241998,
"land_cells": 3847825,
"seam": false,
"estimate_minutes": 15.397225213980029,
"estimate_gb": 0.36623875983059406
},
{
"id": 0,
"x0": 1732,
"y0": 0,
"w": 8692,
"h": 708,
"width_km": 69.536,
"height_km": 5.664,
"cells": 6153936,
"land_cells": 3441318,
"seam": false,
"estimate_minutes": 15.180001426533524,
"estimate_gb": 0.3610718697309494
},
{
"id": 10,
"x0": 871,
"y0": 2509,
"w": 2085,
"h": 1911,
"width_km": 16.68,
"height_km": 15.288,
"cells": 3984435,
"land_cells": 2377317,
"seam": false,
"estimate_minutes": 9.828462464336662,
"estimate_gb": 0.23378003854304552
},
{
"id": 17,
"x0": 2305,
"y0": 5709,
"w": 7866,
"h": 493,
"width_km": 62.928,
"height_km": 3.944,
"cells": 3877938,
"land_cells": 1996162,
"seam": false,
"estimate_minutes": 9.565764800285306,
"estimate_gb": 0.22753150574862957
},
{
"id": 9,
"x0": 11697,
"y0": 2477,
"w": 1042,
"h": 1370,
"width_km": 8.336,
"height_km": 10.96,
"cells": 1427540,
"land_cells": 462269,
"seam": true,
"estimate_minutes": 3.52133321445554,
"estimate_gb": 0.08375851437449455
},
{
"id": 16,
"x0": 7100,
"y0": 4973,
"w": 1197,
"h": 728,
"width_km": 9.576,
"height_km": 5.824,
"cells": 871416,
"land_cells": 333829,
"seam": false,
"estimate_minutes": 2.149534236804565,
"estimate_gb": 0.051128871738910675
},
{
"id": 6,
"x0": 9150,
"y0": 1785,
"w": 962,
"h": 835,
"width_km": 7.696,
"height_km": 6.68,
"cells": 803270,
"land_cells": 329748,
"seam": false,
"estimate_minutes": 1.9814375297194486,
"estimate_gb": 0.04713051952421665
},
{
"id": 4,
"x0": 1014,
"y0": 1150,
"w": 813,
"h": 902,
"width_km": 6.504,
"height_km": 7.216,
"cells": 733326,
"land_cells": 143754,
"seam": false,
"estimate_minutes": 1.8089056704707562,
"estimate_gb": 0.043026672676205635
},
{
"id": 3,
"x0": 2706,
"y0": 974,
"w": 804,
"h": 763,
"width_km": 6.432,
"height_km": 6.104,
"cells": 613452,
"land_cells": 212052,
"seam": false,
"estimate_minutes": 1.5132107703281028,
"estimate_gb": 0.035993266850709915
},
{
"id": 1,
"x0": 1817,
"y0": 717,
"w": 520,
"h": 1046,
"width_km": 4.16,
"height_km": 8.368,
"cells": 543920,
"land_cells": 233291,
"seam": false,
"estimate_minutes": 1.3416951973371376,
"estimate_gb": 0.03191359341144562
},
{
"id": 5,
"x0": 9192,
"y0": 1418,
"w": 887,
"h": 411,
"width_km": 7.096,
"height_km": 3.288,
"cells": 364557,
"land_cells": 137004,
"seam": false,
"estimate_minutes": 0.8992579350927248,
"estimate_gb": 0.021389770321547985
},
{
"id": 14,
"x0": 223,
"y0": 3320,
"w": 580,
"h": 509,
"width_km": 4.64,
"height_km": 4.072,
"cells": 295220,
"land_cells": 127719,
"seam": false,
"estimate_minutes": 0.7282233713742273,
"estimate_gb": 0.01732153818011284
},
{
"id": 12,
"x0": 262,
"y0": 2803,
"w": 534,
"h": 501,
"width_km": 4.272,
"height_km": 4.008,
"cells": 267534,
"land_cells": 110226,
"seam": false,
"estimate_minutes": 0.6599299215406563,
"estimate_gb": 0.015697108581662178
},
{
"id": 8,
"x0": 76,
"y0": 2320,
"w": 447,
"h": 441,
"width_km": 3.576,
"height_km": 3.528,
"cells": 197127,
"land_cells": 69076,
"seam": false,
"estimate_minutes": 0.48625597360912987,
"estimate_gb": 0.011566095985472202
},
{
"id": 15,
"x0": 12298,
"y0": 3669,
"w": 365,
"h": 373,
"width_km": 2.92,
"height_km": 2.984,
"cells": 136145,
"land_cells": 38655,
"seam": true,
"estimate_minutes": 0.3358308071802188,
"estimate_gb": 0.007988079451024532
},
{
"id": 7,
"x0": 12350,
"y0": 2095,
"w": 257,
"h": 459,
"width_km": 2.056,
"height_km": 3.672,
"cells": 117963,
"land_cells": 26862,
"seam": true,
"estimate_minutes": 0.2909810092724679,
"estimate_gb": 0.006921281106770039
}
],
"land_cells": 27667251,
"solve_cells": 49488465,
"dropped_regions": 0,
"dropped_cells": 0,
"estimate_minutes": 122.07390023181169,
"estimate_peak_gb": 0.8225835487246513,
"prepare_seconds": 6.6104118
},
"regions": [
{
"ID": 0,
"Cells": 6153936,
"LandCells": 3441318,
"Seconds": 2168.1264624,
"MinM": 0.03509208559989929,
"MaxM": 29.555843353271484,
"ClipFrac": 0
},
{
"ID": 1,
"Cells": 543920,
"LandCells": 233291,
"Seconds": 107.3814386,
"MinM": 0.0709940567612648,
"MaxM": 36.735618591308594,
"ClipFrac": 0
},
{
"ID": 2,
"Cells": 8838968,
"LandCells": 4802782,
"Seconds": 3409.2016243,
"MinM": 0.05836598575115204,
"MaxM": 572.6420288085938,
"ClipFrac": 0
},
{
"ID": 3,
"Cells": 613452,
"LandCells": 212052,
"Seconds": 111.1352802,
"MinM": 0.04569526016712189,
"MaxM": 36.36088562011719,
"ClipFrac": 0
},
{
"ID": 4,
"Cells": 733326,
"LandCells": 143754,
"Seconds": 106.6721776,
"MinM": 0.025150394067168236,
"MaxM": 22.520099639892578,
"ClipFrac": 0
},
{
"ID": 5,
"Cells": 364557,
"LandCells": 137004,
"Seconds": 66.16925,
"MinM": 0.09793587028980255,
"MaxM": 33.592628479003906,
"ClipFrac": 0
},
{
"ID": 6,
"Cells": 803270,
"LandCells": 329748,
"Seconds": 156.8608063,
"MinM": 0.07160612940788269,
"MaxM": 42.542781829833984,
"ClipFrac": 0
},
{
"ID": 7,
"Cells": 117963,
"LandCells": 26862,
"Seconds": 17.5039889,
"MinM": 0.07459286600351334,
"MaxM": 17.186336517333984,
"ClipFrac": 0
},
{
"ID": 8,
"Cells": 197127,
"LandCells": 69076,
"Seconds": 33.7803033,
"MinM": 0.032177094370126724,
"MaxM": 21.997482299804688,
"ClipFrac": 0
},
{
"ID": 9,
"Cells": 1427540,
"LandCells": 462269,
"Seconds": 265.9503376,
"MinM": 0.029805969446897507,
"MaxM": 43.477603912353516,
"ClipFrac": 0
},
{
"ID": 10,
"Cells": 3984435,
"LandCells": 2377317,
"Seconds": 1305.9213298,
"MinM": 0.10007207840681076,
"MaxM": 199.90245056152344,
"ClipFrac": 0
},
{
"ID": 11,
"Cells": 14019720,
"LandCells": 8977362,
"Seconds": 5896.7940218,
"MinM": 0.03755355626344681,
"MaxM": 71.30496215820312,
"ClipFrac": 0
},
{
"ID": 12,
"Cells": 267534,
"LandCells": 110226,
"Seconds": 51.4490666,
"MinM": 0.03166285157203674,
"MaxM": 33.41362762451172,
"ClipFrac": 0
},
{
"ID": 13,
"Cells": 6241998,
"LandCells": 3847825,
"Seconds": 2534.8766657,
"MinM": 0.05683371424674988,
"MaxM": 694.6005859375,
"ClipFrac": 0
},
{
"ID": 14,
"Cells": 295220,
"LandCells": 127719,
"Seconds": 56.6610819,
"MinM": 0.07922995835542679,
"MaxM": 31.147418975830078,
"ClipFrac": 0
},
{
"ID": 15,
"Cells": 136145,
"LandCells": 38655,
"Seconds": 20.9616778,
"MinM": 0.053121235221624374,
"MaxM": 16.977237701416016,
"ClipFrac": 0
},
{
"ID": 16,
"Cells": 871416,
"LandCells": 333829,
"Seconds": 160.3109476,
"MinM": 0.05054319649934769,
"MaxM": 43.582515716552734,
"ClipFrac": 0
},
{
"ID": 17,
"Cells": 3877938,
"LandCells": 1996162,
"Seconds": 1012.7862956,
"MinM": 0.03253816440701485,
"MaxM": 26.481033325195312,
"ClipFrac": 0
}
],
"seed": 7,
"steps": 1000,
"when": "2026-09-17T18:42:52Z"
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:74f478102d490ad1805a136af6d31cb9decb095cffddd12e70b1160e38f3bce1
size 30275678
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:24da3429e5cffcaa4f9000cfa0f96672bf77552de4851dc3064bb86a5b29d59a
size 868575
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:fb6a83c9645bfc9dc8a2a6ff821caf172efaf89524de1e5fcb120502c26b565c
size 4146746
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:decbe8b6e01fa3a771c8f59fa4a0e19abbf47598d1391c70030d6045313fe62d
size 2138774
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:838a133e43a708b2dd1db62a814cdd832ff75905572e86246f37d53167816717
size 36775
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8117063e0dfc3192318d40686b9a1c714b5a644d8b625e2bd149efdef00218a7
size 17687
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5badc4e5500fa1b0e3e51a8339fb1ede626045a29af04e6a2c728884e44d88aa
size 196229
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8d0e4111906b865a9d2d58414102276b49c8ccb109a9415f3eab79a9b0c7435c
size 27870
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:1f474cdd6429d95a3e266f70bb0ced5cac83da32a97da656f5e10088b8b6246e
size 170724
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b71158dae1ff3cfba07dab5cd0a9b715a4b387898b5ce2ec5bc78f9938a582f4
size 17885
+398
View File
@@ -0,0 +1,398 @@
{
"craters": [
{
"Class": "crater",
"Blobs": 1,
"Cells": 153256,
"FloorCells": 25268,
"RadiusM": 1513.692174783235,
"RimM": 339.99997081616704,
"FloorM": 60
}
],
"elapsed": "17m41s",
"manifest": "D:\\GitDrive\\gitlab\\UnrealEnginePrototyping\\RawContent\\World\\Planet.json",
"plan": {
"manifest": "D:\\GitDrive\\gitlab\\UnrealEnginePrototyping\\RawContent\\World\\Planet.json",
"template": "Templates/Map3.jpg",
"legend": "Templates/Map3.legend.json",
"when": "2026-09-17T19:26:12Z",
"paint_w": 7738,
"paint_h": 3761,
"circumference_km": 100,
"height_km": 48.608,
"area_km2": 4860.8,
"cell_m": 8,
"grid_w": 12500,
"grid_h": 6076,
"pad_rows": 63,
"margin_cells": 63,
"talus_deg": 35,
"clamp_ceiling_mm_yr": 0.2800830152838839,
"elevation_min_m": -1024,
"elevation_max_m": 2048,
"seed": 7,
"match_far_px": 1021,
"match_worst_distance": 203.43549346168678,
"match_worst_at": [
0,
2043
],
"stroke_rescued_at_poles_px": 2084442,
"stroke_dissolved_px": 0,
"wrap_rows": 3761,
"wrap_differ": 355,
"wrap_land_against_sea": 261,
"wrap_far_edge_px": 48,
"classes": [
{
"name": "ocean",
"sea": true,
"cells": 35656741,
"share": 0.4694765108624095,
"depth_m": 512
},
{
"name": "deep",
"sea": true,
"cells": 6796744,
"share": 0.08948971691902567,
"depth_m": 512
},
{
"name": "shelf",
"sea": true,
"cells": 2880150,
"share": 0.03792165898617512,
"depth_m": 120
},
{
"name": "surf",
"sea": true,
"cells": 2949114,
"share": 0.038829677419354836,
"depth_m": 20
},
{
"name": "ice",
"sea": false,
"cells": 5437480,
"share": 0.0715928900592495,
"uplift_mm_yr": 0.05,
"k_mult": 1,
"divide_deg": 7.125016348901798
},
{
"name": "lowland",
"sea": false,
"cells": 11042633,
"share": 0.14539345622119815,
"uplift_mm_yr": 0.08,
"k_mult": 1,
"divide_deg": 11.309932474020213
},
{
"name": "highland",
"sea": false,
"cells": 8650568,
"share": 0.11389819618169848,
"uplift_mm_yr": 0.25,
"k_mult": 1,
"divide_deg": 32.005383208083494
},
{
"name": "desert",
"sea": false,
"cells": 2383314,
"share": 0.03138003949967084,
"uplift_mm_yr": 0.1,
"k_mult": 0.5,
"divide_deg": 26.56505117707799
},
{
"name": "crater",
"sea": false,
"cells": 153256,
"share": 0.0020178538512179065,
"uplift_mm_yr": 0.15,
"k_mult": 1.5,
"divide_deg": 14.036243467926477
}
],
"regions": [
{
"id": 11,
"x0": 3077,
"y0": 2638,
"w": 5720,
"h": 2451,
"width_km": 45.76,
"height_km": 19.608,
"cells": 14019720,
"land_cells": 8977362,
"seam": false,
"estimate_minutes": 34.58264265335235,
"estimate_gb": 0.8225835487246513
},
{
"id": 2,
"x0": 3510,
"y0": 834,
"w": 4916,
"h": 1798,
"width_km": 39.328,
"height_km": 14.384,
"cells": 8838968,
"land_cells": 4802782,
"seam": false,
"estimate_minutes": 21.80320803613885,
"estimate_gb": 0.5186116173863411
},
{
"id": 13,
"x0": 8825,
"y0": 3281,
"w": 2694,
"h": 2317,
"width_km": 21.552,
"height_km": 18.536,
"cells": 6241998,
"land_cells": 3847825,
"seam": false,
"estimate_minutes": 15.397225213980029,
"estimate_gb": 0.36623875983059406
},
{
"id": 0,
"x0": 1732,
"y0": 0,
"w": 8692,
"h": 708,
"width_km": 69.536,
"height_km": 5.664,
"cells": 6153936,
"land_cells": 3441318,
"seam": false,
"estimate_minutes": 15.180001426533524,
"estimate_gb": 0.3610718697309494
},
{
"id": 10,
"x0": 871,
"y0": 2509,
"w": 2085,
"h": 1911,
"width_km": 16.68,
"height_km": 15.288,
"cells": 3984435,
"land_cells": 2377317,
"seam": false,
"estimate_minutes": 9.828462464336662,
"estimate_gb": 0.23378003854304552
},
{
"id": 17,
"x0": 2305,
"y0": 5709,
"w": 7866,
"h": 493,
"width_km": 62.928,
"height_km": 3.944,
"cells": 3877938,
"land_cells": 1996162,
"seam": false,
"estimate_minutes": 9.565764800285306,
"estimate_gb": 0.22753150574862957
},
{
"id": 9,
"x0": 11697,
"y0": 2477,
"w": 1042,
"h": 1370,
"width_km": 8.336,
"height_km": 10.96,
"cells": 1427540,
"land_cells": 462269,
"seam": true,
"estimate_minutes": 3.52133321445554,
"estimate_gb": 0.08375851437449455
},
{
"id": 16,
"x0": 7100,
"y0": 4973,
"w": 1197,
"h": 728,
"width_km": 9.576,
"height_km": 5.824,
"cells": 871416,
"land_cells": 333829,
"seam": false,
"estimate_minutes": 2.149534236804565,
"estimate_gb": 0.051128871738910675
},
{
"id": 6,
"x0": 9150,
"y0": 1785,
"w": 962,
"h": 835,
"width_km": 7.696,
"height_km": 6.68,
"cells": 803270,
"land_cells": 329748,
"seam": false,
"estimate_minutes": 1.9814375297194486,
"estimate_gb": 0.04713051952421665
},
{
"id": 4,
"x0": 1014,
"y0": 1150,
"w": 813,
"h": 902,
"width_km": 6.504,
"height_km": 7.216,
"cells": 733326,
"land_cells": 143754,
"seam": false,
"estimate_minutes": 1.8089056704707562,
"estimate_gb": 0.043026672676205635
},
{
"id": 3,
"x0": 2706,
"y0": 974,
"w": 804,
"h": 763,
"width_km": 6.432,
"height_km": 6.104,
"cells": 613452,
"land_cells": 212052,
"seam": false,
"estimate_minutes": 1.5132107703281028,
"estimate_gb": 0.035993266850709915
},
{
"id": 1,
"x0": 1817,
"y0": 717,
"w": 520,
"h": 1046,
"width_km": 4.16,
"height_km": 8.368,
"cells": 543920,
"land_cells": 233291,
"seam": false,
"estimate_minutes": 1.3416951973371376,
"estimate_gb": 0.03191359341144562
},
{
"id": 5,
"x0": 9192,
"y0": 1418,
"w": 887,
"h": 411,
"width_km": 7.096,
"height_km": 3.288,
"cells": 364557,
"land_cells": 137004,
"seam": false,
"estimate_minutes": 0.8992579350927248,
"estimate_gb": 0.021389770321547985
},
{
"id": 14,
"x0": 223,
"y0": 3320,
"w": 580,
"h": 509,
"width_km": 4.64,
"height_km": 4.072,
"cells": 295220,
"land_cells": 127719,
"seam": false,
"estimate_minutes": 0.7282233713742273,
"estimate_gb": 0.01732153818011284
},
{
"id": 12,
"x0": 262,
"y0": 2803,
"w": 534,
"h": 501,
"width_km": 4.272,
"height_km": 4.008,
"cells": 267534,
"land_cells": 110226,
"seam": false,
"estimate_minutes": 0.6599299215406563,
"estimate_gb": 0.015697108581662178
},
{
"id": 8,
"x0": 76,
"y0": 2320,
"w": 447,
"h": 441,
"width_km": 3.576,
"height_km": 3.528,
"cells": 197127,
"land_cells": 69076,
"seam": false,
"estimate_minutes": 0.48625597360912987,
"estimate_gb": 0.011566095985472202
},
{
"id": 15,
"x0": 12298,
"y0": 3669,
"w": 365,
"h": 373,
"width_km": 2.92,
"height_km": 2.984,
"cells": 136145,
"land_cells": 38655,
"seam": true,
"estimate_minutes": 0.3358308071802188,
"estimate_gb": 0.007988079451024532
},
{
"id": 7,
"x0": 12350,
"y0": 2095,
"w": 257,
"h": 459,
"width_km": 2.056,
"height_km": 3.672,
"cells": 117963,
"land_cells": 26862,
"seam": true,
"estimate_minutes": 0.2909810092724679,
"estimate_gb": 0.006921281106770039
}
],
"land_cells": 27667251,
"solve_cells": 49488465,
"dropped_regions": 0,
"dropped_cells": 0,
"estimate_minutes": 122.07390023181169,
"estimate_peak_gb": 0.8225835487246513,
"prepare_seconds": 6.7940063
},
"regions": [
{
"ID": 13,
"Cells": 6241998,
"LandCells": 3847825,
"Seconds": 1059.355318,
"MinM": 0.049020953476428986,
"MaxM": 183.12913513183594,
"ClipFrac": 0
}
],
"seed": 7,
"steps": 1000,
"when": "2026-09-17T19:26:12Z"
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:b798259d14715b97433b7c5a8613f4f7eba54020a13896f73f15f5916f447ca0
size 14062330
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:a13fb9daaf8b27819d0d39f4dd59b66a538c5e25366c6ddd025de9f4868b4f8f
size 538861
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:2e692a72f1a1b87b8d9ae0692b9644653a05559b37d9debf2c6cb8e5cc18c5ee
size 2274333
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:8cea3687b7736a4fea914e2cf332015b8a6df27b7755cea5137d5f8a14c2d269
size 270736
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:838a133e43a708b2dd1db62a814cdd832ff75905572e86246f37d53167816717
size 36775

Some files were not shown because too many files have changed in this diff Show More