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
+131
View File
@@ -0,0 +1,131 @@
# Product
<!-- impeccable:product-schema 1 -->
*Scope: this record covers **terrain studio**, the world-authoring tool in `Tools/Terrain/`. The Unreal Engine
project it feeds (`Salty`) is operating context, not the subject; `CLAUDE.md` at the repository root remains that
project's authority. Game UI is out of scope here and will need its own record.*
## Platform
web
## Users
The project's developers, the author of the generator among them. They can read the Go and the docs but are not
in the generator daily, so the tool has to be self-explaining without being a tutorial.
The situation is a desktop browser at `127.0.0.1:8099` with a terminal open beside it running the binary, mouse
and keyboard, one person per process. The job is to decide what a planet is — where the continents sit, what rate
each region is rising at, which stretches of coast were drawn deliberately — and to find out whether it made the
world they meant before committing hours of solve to it.
Confirmed as durable: terrain studio is the permanent authoring tool for this project's worlds, not a scaffold
waiting to be replaced by in-editor tooling.
## Product Purpose
terrain studio is the painting front end to the `terrain` generator. An author paints a flat cylindrical world
map whose colours are geology; a JSON legend beside it says what each colour means in uplift mm/yr and
erodibility; a Braun-Willett stream-power simulation makes the terrain from it. The studio exists so that the
painting, the legend and the planet manifest can be edited in one place, against the map they affect, with the
consequence of each number visible as it is typed.
Success is that an author can tell, in four seconds rather than two hours, whether the planet they have described
is the planet they wanted — and that when they commit to the bake, the result is the thing the four-second
preview promised.
## Positioning
**Paint the uplift, never the height.** A generator handed a painted surface erodes it into something else and
throws the drainage network away; this one is handed a rate field, and the simulation produces the terrain. Every
valley, divide, scarp and river mouth is a consequence of physics rather than a brush stroke. That is the reason
the generator exists, and it is the constraint the interface is built around.
Two consequences a neighbouring heightmap painter could not truthfully copy:
- **The brushes are the legend's classes.** Painting and parameterising are the same act; there is no export step
between a colour and what it means.
- **The panel answers in ground, not in input.** A rate typed in mm/yr is reported back as the hillslope angle it
buys, and specifically as the *typical* angle — the median over the class, a third of the divide angle in
tangent — because almost none of a map is divide and the divide figure is how a legend gets set two or three
times too hot.
## Operating Context
- Launched from a terminal: `terrain studio [path]`, `--addr` defaulting to `127.0.0.1:8099`. Local only; no
deployment, no accounts, no network beyond the loopback.
- The binary holds the painting in memory and answers `/api/*`. Opening `page.html` from disk is the one mistake
worth detecting: every fetch fails as a bare `NetworkError`, and the page says so instead of letting it.
- **Two registered sheets over one world.** The *class painting* is the geology — every colour is an uplift rate
the solve answers for. The *overlay* is annotation the simulation has no opinion about: forests, settlements,
roads, and the stretches of coast the author wants left exactly where they drew them. Tabs, or `o`, switch
between them; the overlay always rides on top, dimmed while the brush is on the classes.
- **The loop is plan-then-bake.** *Plan* is about four seconds: it cuts the planet into regions and returns a
report plus map layers (class, uplift, erodibility, overlay, regions), eroding nothing. *Bake* is about two
hours on the 100 km-round template; it runs in the server, survives a page reload, fills the preview in one
landmass at a time because the solve is decomposed per landmass, and can be cancelled. `terrain tiles` then
writes 5 km detail tiles at 2 m, about twelve seconds each.
- **Saving patches text.** Legend and manifest edits are written into the existing `*.legend.json` and
`Planet.json` as patches, so the author's commentary in those files survives a save.
- The same binary carries `generate`, `plan`, `bake`, `tiles` and `palette`. The studio is the only graphical
surface in the toolchain; everything else is a terminal report.
## Capabilities and Constraints
- **One binary, no build step, no dependencies.** `go.mod` declares zero third-party modules (Go 1.25, stdlib
only) and `page.html` is a single file embedded with `go:embed`. No npm, bundler, framework, CDN or web font.
The tool ships as one executable and must keep working offline.
- **Desktop, pointer and keyboard.** Wheel to zoom, middle/right/shift-drag to pan, `[` and `]` for brush size,
`o` to switch sheets, `Escape` to leave a map view. Not a touch or small-screen surface, and there is no reason
for it to become one.
- **The canvas wraps in X**, because the world is a cylinder. The seam is never an edge and a stroke across it is
continuous, both on screen and in the pixels.
- **Strokes are hard-edged discs written straight into `ImageData`.** Antialiasing is forbidden: a blended pixel
is not a colour between two classes, it is a pixel that classifies as whichever third class sits near the
midpoint.
- **Blank on the overlay is alpha, never a reserved colour**, and the eraser writes transparent black. An opaque
overlay pixel matching no mark is dropped and counted, not snapped to the nearest — the class legend's rule
deliberately inverted, because most of an overlay is nothing.
- **Every map view carries a key**, and the key is generated by the code that drew the map, so the two cannot
drift apart.
- **The seed is a composition control, not a debug field.** One painting is many worlds: the seed re-rolls the
massifs, the rock, the faults, the initial relief and the coastline detail while the painting stays exactly as
drawn. It sits next to a Re-roll button for that reason.
- Not present today, and not to be assumed: undo or edit history, concurrent editing (the server holds a single
painting in memory), any persistence beyond the two PNGs and the two JSON files, and any authentication.
## Evidence on Hand
- `Tools/Terrain/internal/studio/page.html` — the shipped interface, and `server.go` for the API it speaks to.
- `RawContent/World/Templates/` — the first painting and the README explaining how to paint another:
`Map3_001.png`, `Map3.legend.json`, `Map3.overlay.json`, `Map3_cliff.legend.json`, `default.palette.json`.
- `RawContent/World/Planet.json` — the planet manifest the studio patches.
- `Docs/Terrain-Next.md` — the working brief: how to run it, what each output map is for, what still looks wrong,
and what has already been measured and rejected. `Docs/Terrain.md` and `Docs/Decisions.md` (D-46 … D-60) hold
the decision record.
- Measured numbers exist and must not be re-guessed or rounded into slogans: plan ≈ 4 s; a 100 km-round bake
≈ 2 h; a 5 km detail tile ≈ 12 s; the coastal pass 7.9 s over 76 M cells at ≈ 8.3 GB peak against the solve's
3.6 GB; the sea-floor seam step 9.1 m → 0.32 m; seed 7 against 9342 moving 13.8 % of the uplift map and 24.9 %
of the erodibility map; a real 45.9 × 19.8 km lowland baking to 0–47 m at a 0.61° median slope.
- Observed in the product's own copy but **not** confirmed as binding: lowercase panel headings, and notes that
say *why* rather than *what* ("a mark you cannot see is one you will paint over"). Treat as evidence of an
incumbent voice, not as an approved commitment.
- There are no committed screenshots or visual-regression goldens of the interface, no usage data, no users
outside the team, and no published release. Future work must not fabricate any of them.
## Product Principles
1. **The author steers in consequences, not in inputs.** A number the panel accepts is reported back as the
ground it makes — degrees, metres, kilometres, minutes — at the moment it is typed. A control that shows only
its raw value has failed at the one job this tool has.
2. **Cheap answers before expensive ones.** Four seconds and two hours are the two costs in this product. Every
addition declares which side of that line it is on, and the four-second path must keep being able to answer
"is this the world I meant".
3. **The picture and its key come from the same place.** A map, report or preview is published with the legend
that describes it, generated by the code that drew it. A map with no key is a picture.
4. **Nothing silently reinterprets what the author drew.** Strokes are hard-edged, unmatched marks are dropped
and counted rather than snapped, saves patch text and keep commentary. Where the tool cannot honour a mark, it
says so in the report.
5. **One binary, no build.** Any capability needing a package manager, a bundler or a network at runtime is out of
scope here, however convenient it would be.