132 lines
9.0 KiB
Markdown
132 lines
9.0 KiB
Markdown
# 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.
|