Files
2026-09-25 17:02:24 +03:00

16 KiB
Raw Permalink Blame History

The world pipeline: from a painted map to ground in Unreal

Read this when you want to know what order things happen in. It is the orientation document for everything between "an author paints a world map" and "a player stands on it", and it owns no decisions of its own — every one of them is argued somewhere else and linked from here.

If you want Read
Why the generator exists and how it works Terrain.md — the specification and the decision record
What is being worked on right now, what looks wrong Terrain-Next.md — the working brief
Every manifest key, explained RawContent/World/README.md and Scripts/Authoring/region_manifest.py
How to paint a world RawContent/World/Templates/README.md
Why a thing is the way it is Decisions.md — D-47 onwards is all world

All of this is off the ladder (D-47). No gameplay code reaches into it and nothing here blocks a step in Steps.md.


The shape of it

        RawContent/World/Templates/Map5.png  +  Map5.legend.json
        a painted flat cylindrical world map    what each colour means: uplift mm/yr, erodibility
                              |
                              |   terrain studio     paint it, 127.0.0.1:8099
                              |   terrain plan       4 s: cut the planet into regions, solve nothing
                              v
             +----------------+----------------+
             |                                 |
      terrain bake                      Tools/Orogen  (the browser twin)
      ~2 h, 8 m geology grid            ~12 s, 204K-region sphere mesh
             |                                 |
             v                                 v
      RawContent/World/Bake_NNN/         "Export Map"
      planet_height.png                  RawContent/World/Orogen Gens/
      map_flow / class / uplift ...      orogen-heightmap-7945.png  (8192 x 4096)
      overlay.json, meta.json            orogen-colormap / satellite / climate ...
             |                                 |
             |                    +------------+-------------+
             |                    |                          |
      terrain tiles        generate_region_tiles.py     Tools/MapArt  (Go)
      5 km tiles at 2 m    cuts the window into tiles   `biomes` -> biome masks
             |                    ^                     `build`  -> 4 map layers
             |                    |                          |
             |                    |                          |
             X  NOT WIRED         v                          v
             (this is the     RawContent/World/          RawContent/World/MapArt/
              future; see      RegionTiles/*.png          map_relief / colour / satellite / climate
              "What is not              |                          |
              wired yet")      create_region_world.py      create_world_map.py
                               via build_region.sh         via build_world_map.sh
                                        |                          |
                                        v                          v
                            Content/Maps/L_World          Content/World/Maps/
                            98 landscapes                 DA_WorldMap_L_World + 4 textures
                            71.40 x 35.70 km                        |
                                        \                          /
                                         \________________________/
                                                     |
                                       the map view, in game and in the editor

Two generators sit side by side and they are not interchangeable. The Go tool in Tools/Terrain/ is the one that makes ground a player can stand on: an 8 m geology grid, a coastal pass, faults, craters, detail tiles. World Orogen in Tools/Orogen/ is a browser twin that reads the same painting and the same legend and solves them on a sphere mesh in about twelve seconds — for looking at a painting's rivers on a globe and tuning the legend's numbers before committing two hours to a bake. A 44 km cell cannot show a fault or a crater, and it never will (D-66).

The ground in L_World today came from Orogen, not from the Go tool. That is a deliberate shortcut: it put 900 km² of ground in the engine long before the generator's detail passes reach Unreal, and the relief you see is therefore art rather than a solve. terrain bake makes that same continent a 116 m plain; the Orogen export makes it 2972 m (D-69).


The three routes

Only one of these is live. Knowing which is which saves an hour of confusion.

Route Status What it builds How the tiles are made
A. Orogen → Python cutter → Unreal live; this is what L_World is Content/Maps/L_World generate_region_tiles.py cuts a window out of a whole-planet PNG. Region.json's source.kind is planet_map and its metres_per_pixel is a number somebody chose
B. Orogen → direct tile export → Unreal built, not the one in use (D-71) the same level Orogen's Unreal Landscape… export renders the tiles itself and writes Region.generated.json. source.kind is orogen_render, and the scale is a consequence rather than a guess
C. terrain bake → terrain tiles → Unreal not wired the same level, eventually This is the point of the whole generator. terrain tiles already writes 5 km tiles at 2 m; nothing carries them into Unreal yet

Route B is strictly better than A on the one thing that matters most — the scale stops being invented — and the only reason A is what is in the level is that A came first. Route C is where this is going, and when it lands, generate_region_tiles.py is the only thing that changes.


Route A, step by step

This is the sequence that produced what is in the project now.

1. Paint the world

cd Tools/Terrain && go build -o bin/terrain.exe ./cmd/terrain
Tools/Terrain/bin/terrain.exe studio          # 127.0.0.1:8099

The brushes are the legend's classes. The panel prints the hillslope angle each uplift rate buys as you type it — read the typical column, not divide: almost none of a map is divide, and reading the divide angle as the landscape is how a legend gets set two or three times too hot (D-59). o switches to the overlay sheet, ctrl+z undoes a stroke, and Re-roll changes the seed, which moves the massifs, the rock, the faults and the coastline detail without touching a painted pixel (D-58).

Paint the uplift, never the height. A solve handed a painted surface erodes it into something else and throws the drainage network away, which is the reason the generator exists at all.

2. Check the painting before committing to a bake

Tools/Terrain/bin/terrain.exe plan            # four seconds, solves nothing

Plan/map_class.png and Plan/map_regions.png are the two pictures that decide whether a bake is worth starting. plan.json carries the class table.

3. Solve it — in the browser first

Serve the repo and open the import page:

npx serve Tools/Orogen                        # any static server
# then open /import, choose the Painted Map source, and Load from studio

Load from studio pulls the painting, the legend and Planet.json straight out of the running terrain studio in about four seconds, strokes included (D-67). The studio answers GET and HEAD only and no preflight, so no browser tab can ever paint, save, plan or bake.

Twelve seconds later there is a globe with rivers on it. Tune the legend's numbers in the table here, not after a two-hour bake.

4. Export the planet

Press Export Map. That writes the whole-planet PNGs into RawContent/World/Orogen Gens/ — the heightmap (8192 × 4096, a fixed −5000…6000 m ramp), plus colormap, satellite, climate, landmask and the painted-layer debug maps.

Do not use "Export All" for the heightmap. Its list is {biome: Satellite, koppen: Climate, landheightmap: Heightmap, landmask: Land Mask} — the entry labelled "Heightmap" is the land variant, whose landHeightmapColor returns black for elevation <= 0, so every ocean pixel is 0 m. Import that and the sea floor sits flat at exactly sea level: z-fighting with the sea plane across 64 % of the world, no shelf and no shore. The file Region.json reads is the absolute heightmap, −5 km to +6 km, which has to be exported as a single layer. Both are 8192 x 4096 greyscale and neither says which it is, so the tell is the filename: orogen-heightmap-*.png against orogen-land-heightmap-*.png.

Orogen also numbers each export, not each planet. orogen-colormap-14733759.png and orogen-heightmap-7945.png look like two different worlds and are one. Nothing inside a PNG says which planet it is, so if you are ever unsure, cd Tools/MapArt && go run . check measures land/sea agreement against the heightmap — 97.5 % is the same planet, 50 % is not.

5. Render the biome masks

The painting's classes and the Köppen climate become one blurred greyscale mask per biome, which is what the paint layers are built from. Go, because the painting is 29 megapixels of RGB and the engine's Python cannot decode it; blurred once globally so a tile can read it without carrying a margin the width of the blend.

cd Tools/MapArt && go run . biomes      # a few seconds, into RawContent/World/Biomes/

It prints how far the worst pixel was from any legend colour. On the painting that is 0.0 — a painted map is made of its legend's own colours. A number much above that means the image is a render of a classification rather than the classification itself, which is the difference between data and a picture of data, and is exactly why Orogen's own class export is not the source here.

6. Cut the window into landscape tiles

RawContent/World/Region.json is the contract: which level, how many tiles, what a heightmap value means in metres, and — the number that matters most — source.metres_per_pixel, because the export carries no scale of its own.

# measure a window and print what it holds, writing nothing. Try scales here, not by rebuilding.
D:/UE_5.8/.../python.exe Scripts/Authoring/generate_region_tiles.py --scout
D:/UE_5.8/.../python.exe Scripts/Authoring/generate_region_tiles.py     # ~2 min, 208 MB, untracked

7. Build the level

Scripts/Authoring/build_region.sh              # the whole grid, a few tiles per process
Scripts/Authoring/build_region.sh --append     # add whatever is still missing

One process per batch, because a landscape of a hundred components costs about a gigabyte the editor never gives back: thirty-six tiles in one process reached 14.7 GB by the ninth (D-69).

8. Build the map view

Scripts/Authoring/build_world_map.sh           # renders the art, imports it, writes the definition

Independent of the level — it reads the same Orogen Gens/ images and Region.json, and writes Content/World/Maps/. See Spec/UI.md for the map itself; M opens it in game, Window → World Map in the editor.


What each artefact is, and whether it is tracked

Path What In git?
RawContent/World/Templates/ The painting, its legend, its plates. The real source. tracked
RawContent/World/Planet.json The planet's manifest: circumference, cell size, pipeline constants tracked
RawContent/World/Orogen Gens/ Orogen's whole-planet exports tracked — they came out of a browser session and cannot be regenerated headlessly
RawContent/World/Plan/ terrain plan output ignored, 4 s to rebuild
RawContent/World/Bake_NNN/ terrain bake output: planet_height.png, the data maps, overlay.json, meta.json ignored, gigabytes, ~2 h to rebuild
RawContent/World/RegionTiles/ 98 tile sets: height + 3 weightmaps each ignored, 208 MB, 2 min to rebuild
RawContent/World/MapArt/ layers.json (tracked) and the rendered map layers (ignored, 6 s) mixed
Content/Maps/L_World The level: 98 landscapes and ~500 external actor packages tracked, through LFS
Content/World/Maps/ DA_WorldMap_L_World and four map textures tracked, through LFS

The rule is: an input is tracked, a product is not. The one exception is Orogen Gens/, because a browser session is not something a script can redo.


Traps

Every one of these has cost real time.

  • Never run a level build while the editor holds that level. --rebuild empties the level first and saves last, so a lock detected late is indistinguishable from data loss — that is exactly what happened on 2026-09-20, leaving twelve of ninety-eight tiles, all of them the polar ocean strip, so the level opened on 71 km of sea and read as corrupted. Both build_region.sh and create_region_world.py now probe the file before anything is destroyed (D-71a).
  • Run the long ones detached. create_world.py and terrain bake must never go under a tool timeout. A killed bake is two hours.
  • A script's arguments go inside the quoted -script= value. Anything after it is parsed by the engine and silently never reaches Python — which looks exactly like a script that ignored its arguments.
  • create_world.py empties whatever level it is handed. This is why the numpy canvas is named L_Canvas_Proto and not L_World: a manifest still pointing at the latter would replace 98 landscapes with a 14 km square on one run, with no prompt (D-72).
  • Tile files are named after the level. Change Region.json's level and all 98 tile sets look missing; rename the PNGs or 208 MB regenerates.
  • The heights out of Orogen are art. Fixed −5000…6000 ramp, land normalised to a browser preview's peak setting. Only sea_scale corrects any of it, and only the sea.
  • A freshly opened L_World can look empty. The landscapes are split into world-partition streaming proxies and none is loaded; what you see is the sky, the fog and the sea plane at Z 0, which from above looks convincingly like soft terrain. Load a region, or build HLODs.
  • Do not trace for the ground in a commandlet. Landscape collision is not reliably there, the sea plane's is, and a trace that hits the sea returns 0.0 rather than failing. Read the heightmap instead.

What is not wired yet

Stated plainly so nobody goes looking for it.

  • Route C. terrain tiles writes 5 km tiles of 2500 samples at 2 m with hillshade, flow, wear and deposit maps beside each one, and nothing carries them into Unreal. This is the path that ends with ground worth standing on; generate_region_tiles.py is the piece that changes.
  • The overlay in Unreal. terrain overlay proposes woodland, settlements and roads, the studio has a Generate marks button, and overlay.json carries every feature in world metres. Region.json reserves an overlay block and region_manifest.py has the slot for a per-tile mark map — neither is implemented. No forest, road or settlement is placed from one. It is left unbuilt rather than written blind because a carry that has never carried anything is a guess about a file format.
  • Overlay marks on the map view. They are in whole-cylinder normalised coordinates, exactly like the map art, so they transfer by the same u,v. This is the cheapest useful thing left in the list.
  • Erosion-derived paint layers in the region. Route A has no erosion pass and therefore no wear, flow or deposit map, which is why its paint rules are slope and altitude alone. L_Canvas_Proto — the legacy numpy pipeline — is still the only path that carries those maps into Unreal, and that is the only reason it is kept.
  • The coastal detail pass, which the shelf and shore platform have just unblocked. See Terrain-Next.md.