docs: erosion hierarchy defaults, EROS v2, and where erosion cannot reach (terrain-water task 18)

Records the four governors and the task-18 defaults, and states the measured
limit plainly: erosion concentrates ~9x on the curve's shelf risers because
that is where sustained slope is, so flat shelves and lowlands are barely
touched and the lowland continuation of a trunk is river promotion's job, not
erosion's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stewart Howe 2026-08-10 04:16:33 -04:00
parent cc6a24d2b9
commit 85e7bceb72
2 changed files with 36 additions and 21 deletions

View file

@ -57,7 +57,7 @@ Every section: `[u32 tag][u64 payload-length in bytes][payload]`.
| `TOWN` / `0x4E574F54` | `i32 count`, then per town: `f32 X` · `f32 Y` · `u8 tier` · `u8 isHighwayNode` (0/1) | Tier ordinals from `Enums.cs::TownTier`, append-only, range-checked on read. The highway-node flag is what the generator's road topology was built from (v1 dropped it); carried and exposed on the parsed blueprint, consumed by nothing server-side yet. |
| `TCRV` / `0x56524354` | 50 B base: `u16 curveVersion` · `f32 knot1..knot4` · `f32 spikeMax` · `f32 sea` · `f32 orangeCeil` · `f32 redCeil` · `f32 benchLo` · `f32 benchHi` · `f32 peakCap` · `f32 tailSlope`. **When `curveVersion ≥ 4`, a 36 B modulation extension follows:** `f32 benchAmp` · `f32 plateauAmp` · `f32 shelfSpanMin` · `f32 shelfSpanMax` · `f32 elevFreqIslands` · `f32 strengthFreqIslands` · `i32 benchSeedOffset` · `i32 plateauSeedOffset` · `i32 strengthSeedOffset`. **When `curveVersion ≥ 5`, a 9 B preset extension follows:** `u8 presetId` (1 = compact, 2 = balanced) · `f32 k5` · `f32 k6` — with the four base knot slots this makes the effective curve unambiguous from the record alone (record total 95 B; the length-framed section + version byte keep every layout change safe) | Optional — present iff the height-redistribution curve shaped this blueprint's `HGTS` (config `TerrainCurve: "v3"`); absent = raw legacy profile. **The `u16 curveVersion` selects the field semantics and the unserialized anchor set:** v1 — knots t1..t4, spikeMax = pooled calibration max, benchLo/Hi = the 50 m plateau's lo/hi. v2 — as v1 but spikeMax = the seed's effective raw pre-curve maximum (per-seed spike normalizer). v3 — knots = K1..K4 of six (K5 = 0.930304, K6 = 1.050720 are version constants, not serialized), spikeMax per-seed, benchLo/benchHi = the fixed 100 m / 220 m shelves. **v4 (current)** — as v3, but benchLo/benchHi are the shelf **BASE** anchors and the extension record carries the spatial-modulation parameters: shelf elevations vary ±benchAmp/±plateauAmp and shelf strength blends the output span across `[shelfSpanMin, shelfSpanMax]`, via Simplex fields seeded `resolvedWorldSeed + seedOffset` at `freq/MapSize` (frequencies stated in undulations per island width). Because spikeMax is per-seed (v2+), blueprints are not reproducible from curve constants alone — that is why it is recorded. **Metadata only:** exported heights are already curved; nothing re-applies the map. Calibration provenance: `HeightCurve.cs` header + the task-05/06/07 reports. |
| `TDTL` / `0x4C544454` | 30 B: `u16 detailVersion` · `f32 reliefAmpM` · `f32 reliefFreqIslands` · `i32 reliefSeedOffset` · `f32 edgeAmpM` · `f32 edgeFreqIslands` · `i32 edgeSeedOffset` · `f32 edgeMaxShiftM` | Optional — present iff the terrain detail passes shaped this blueprint's `HGTS` (config `TerrainDetail: "v1"`, requires the curve). **`detailVersion` selects the body layout and a reader that does not recognise it SKIPS the section** (leaving detail metadata null) rather than misreading a differently shaped payload — the one section whose body is versioned rather than extended, because v1's layout was retired rather than grown. **v1 (retired, never shipped)** — shelf micro-relief + D8 drainage incision, 38 B; the incision produced grid-aligned artifacts and was reverted whole, so the only v1 payloads that exist are in that batch's own tree. **v2 (current)** — shelf micro-relief (±`reliefAmpM` output metres, shelf-ness weighted) + shelf-edge variation: a per-column shift of the curve's shelf/riser knot block K3/K4/K5, drawn from a Simplex field seeded `resolvedWorldSeed + edgeSeedOffset` at `edgeFreqIslands/MapSize`, amplitude ±`edgeAmpM` **metres of INPUT height** — a displacement of the shelf boundary contour, not an elevation change. `edgeAmpM` is recorded **as applied**, after the clamp to `edgeMaxShiftM` (the preset's band-squeeze bound), so the record always describes the terrain rather than the request. **Metadata only** — heights are already detailed. Machinery: `Tools/Scripts/TerrainDetailPass.cs`. |
| `EROS` / `0x534F5245` | 58 B: `u16 erosionVersion` · `i32 dropletCount` · `i32 lifetime` · `i32 brushRadius` · `i32 seedOffset` · `f32 carveCapM` · `f32 seaMarginM` · `f32 inertia` · `f32 capacityFactor` · `f32 minSlopeM` · `f32 erodeRate` · `f32 depositRate` · `f32 evaporation` · `f32 gravity` · `f32 craterExclFactor` | Optional — present iff the droplet hydraulic-erosion pass shaped this blueprint's `HGTS` (config `Erosion: "v1"`, terrain-water task 17). **`erosionVersion` selects the body layout; an unrecognised version is SKIPPED whole** (erosion metadata left null), same rule as `TDTL`. **v1 (current)** — the carve-and-deposit droplet model: `dropletCount`/`lifetime`/`carveCapM` are the three hard governors (total droplets, max steps each, max cumulative erosion depth per cell in metres); `seaMarginM` is the flood-guard clamp (no cell carved below sea + margin; below-sea cells untouched entirely, so the rendered coastline cannot move); droplets are deterministic from `resolvedWorldSeed + seedOffset` (PCG32); no cell within `craterExclFactor × CraterRadius` of the impact centre is modified. Strength constants (`inertia`, `capacityFactor`, `minSlopeM`, `erodeRate`, `depositRate`, `evaporation`, `gravity`) are the standard droplet-model dials; slopes/amounts in metres (1 raw = 251 m). All values recorded **as applied** (post config clamping). **Metadata only** — heights are already eroded, and the classify-side sections (`BIOM`/`WBID`/…) never saw the pass by design. Machinery: `Tools/Scripts/HydraulicErosion.cs`. |
| `EROS` / `0x534F5245` | 62 B: `u16 erosionVersion` · `i32 dropletCount` · `i32 lifetime` · `i32 brushRadius` · `i32 seedOffset` · `f32 carveCapM` · `f32 depositCapM` · `f32 seaMarginM` · `f32 inertia` · `f32 capacityFactor` · `f32 minSlopeM` · `f32 erodeRate` · `f32 depositRate` · `f32 evaporation` · `f32 gravity` · `f32 craterExclFactor` | Optional — present iff the droplet hydraulic-erosion pass shaped this blueprint's `HGTS` (config `Erosion: "v1"`, terrain-water tasks 1718). **`erosionVersion` selects the body layout; an unrecognised version is SKIPPED whole** (erosion metadata left null), same rule as `TDTL`. **v1 (task 17, superseded)** — 58 B, no `depositCapM`; deposition was bilinear over 4 cells and unbounded, which built isolated cones (measured 15.5 m). The only v1 payloads in existence are in that task's batch tree. **v2 (task 18, current)** — deposition is spread over the same cone brush as carving and bounded per cell by `depositCapM` (`<= 0` = unbounded, the reference model). The FOUR governors are `dropletCount`/`lifetime`/`carveCapM`/`depositCapM`; both caps are enforced against one per-cell NET displacement ledger (positive = carved below the height the pass found, negative = built up above it) and asserted on exit. `seaMarginM` is the flood-guard clamp — no cell is carved below sea + margin, and below-sea cells are untouched in BOTH directions, so the rendered coastline cannot move. Droplets are deterministic from `resolvedWorldSeed + seedOffset` (PCG32); no cell within `craterExclFactor × CraterRadius` of the impact centre is modified. Remaining fields are the droplet-model strength dials; slopes/amounts in metres (1 raw = 251 m). All values recorded **as applied** (post config clamping). **Metadata only** — heights are already eroded, and the classify-side sections (`BIOM`/`WBID`/…) never saw the pass by design. Machinery: `Tools/Scripts/HydraulicErosion.cs`. |
| `WBID` / `0x44494257` | `MapSize²` × `u16` water-body id, same pixel order as `HGTS` | Optional (absent = no water data, e.g. a legacy re-encode). `0` = no water, `1` = **the** ocean body, `2..N` = lakes. Ids assigned in deterministic scan order (X outer / Y inner, first-encountered pixel), lakes labeled with the **same 4-connectivity as `CalculateTrueOcean`**. Membership is exactly the generator's water classification — the biome grid's Ocean/Lake pixels and this grid's nonzero pixels are the same set **by construction** (shared predicates). Length must equal `2·MapSize²`. |
| `WBTB` / `0x42544257` | `i32 count`, then per body (20 B): `u16 id` · `u8 type` (0 ocean, 1 lake) · `u8 salinity` (0 fresh, 1 salt) · `f32 surfaceLevel` · `i32 pixelCount` · `f32 centroidX` · `f32 centroidY` | Optional, paired with `WBID`. **`surfaceLevel` is a documented TRANSITIONAL rule:** one flat level per body — `GetSeaLevel` at the body's pixel centroid (ocean: at the map centre) under the still-live latitude field; superseded by the flat-scalar sea model (minted, lands with the coast change set). The field's per-pixel slope is deliberately NOT baked into any section. **Salinity is a provisional default** (ocean salt, lake fresh) — a placeholder for the future fresh/salt irrigation mechanic, not a mechanic. |
| `WSRF` / `0x46525357` | `MapSize²` × `u16` quantized water-surface elevation, same pixel order | Optional, paired with `WBID`. `0` is the reserved **no-water sentinel**; a real level `L` (raw height units) encodes as `1 + round(L × 32768)` so it can never encode to 0; decode `(q 1)/32768` (`BlueprintFormat.EncodeWaterLevel`/`DecodeWaterLevel`). Covers `[0 … ~1.99997]` raw at `1/32768` raw ≈ **7.7 mm** of world height (1 raw = 251 m) — far finer than the 1 m voxel. Nonzero exactly where `WBID` is nonzero; the value is the pixel's body level. |
@ -123,14 +123,18 @@ body).
follow the curved terrain. When on, `TCRV` records the effective parameters including the
per-seed `spikeMax`.
- **`Erosion`** (`"v1"` | `"off"`, default `"off"` — opt-in until the developer's gate approves
it) — the task-17 droplet hydraulic-erosion pass: carve-and-deposit drainage detailing of
`HGTS` after the detail passes and before the crater carve. Governor dials
(`ErosionDropletCount` / `ErosionDropletLifetime` / `ErosionCarveCap`, defaults 400 000 / 48 /
8 m; bounds clamped loudly at load) plus the sea-clamp margin and strength constants — the
full dial list and semantics live in the `EROS` row above and `ConfigManager.cs`. Output-height
only: the classify path reads pre-erosion heights, so `BIOM` and every water section stay
bit-identical with erosion on or off, and the sea clamp keeps even the RENDERED coastline
fixed. When on, `EROS` records the parameters as applied.
it) — the droplet hydraulic-erosion pass (tasks 1718): carve-and-deposit drainage detailing of
`HGTS` after the detail passes and before the crater carve. FOUR governor dials
(`ErosionDropletCount` / `ErosionDropletLifetime` / `ErosionCarveCap` / `ErosionDepositCap`,
defaults 250 000 / 384 / 15 m / 6 m; bounds clamped loudly at load) plus the sea-clamp margin
and strength constants — the full dial list and semantics live in the `EROS` row above and
`ConfigManager.cs`. The task-18 defaults tune for a drainage HIERARCHY: droplets live long
enough (384 steps at inertia 0.35) for their paths to overlap and deepen shared low lines into
trunk channels rather than dying as short independent scratches, and the raised carve cap lets
those trunks separate from the fine rills instead of both piling against one ceiling.
Output-height only: the classify path reads pre-erosion heights, so `BIOM` and every water
section stay bit-identical with erosion on or off, and the sea clamp keeps even the RENDERED
coastline fixed. When on, `EROS` records the parameters as applied.
- **`CoastProfile`** (`"wide"` | `"steep"`, default `"wide"`) — the submarine shelf. The height
curve is identity at and below sea level, so it never reshaped the seabed; `"wide"` compresses

View file

@ -43,22 +43,33 @@ Generates the entire 2D blueprint. Roughly in order:
bit-identical to an unwarped one outside the shelf/riser stack. Drops the `0_height` hillshade
snapshot (hypsometric bands × NW hillshade) in both modes.
**Erosion (task 17, Phase C0)** — with `Erosion: "v1"` (default **"off"**, opt-in until the
**Erosion (tasks 1718, Phase C0)** — with `Erosion: "v1"` (default **"off"**, opt-in until the
developer's gate approves it) a droplet-based hydraulic erosion pass (`HydraulicErosion.cs`,
standalone numeric, deterministic from the resolved seed) details the curved+detailed render
map after the detail passes and before the crater carve: droplets walk downhill with inertia,
eroding steep fast stretches over a small brush and depositing where the ground flattens —
organic, sinuous drainage, not grid hatching. Three hard governors bound it
(`ErosionDropletCount`/`ErosionDropletLifetime`/`ErosionCarveCap` — the cap is per-cell, in
metres, ledger-enforced), a sea clamp forbids carving below sea + margin and leaves below-sea
cells untouched entirely (the rendered coastline cannot move — asserted every generation), and
nothing within 1.2× the crater radius is modified. Output-height only: biome/water
eroding steep fast stretches and depositing where the ground flattens — both spread over the
same cone brush, so neither carving nor dumping can spike a single cell. **Four** hard
governors bound it (`ErosionDropletCount`/`ErosionDropletLifetime`/`ErosionCarveCap`/
`ErosionDepositCap`; both caps are per-cell metres against one net-displacement ledger and are
asserted on exit), a sea clamp forbids carving below sea + margin and leaves below-sea cells
untouched in both directions (the rendered coastline cannot move — asserted every generation),
and nothing within 1.2× the crater radius is modified. Output-height only: biome/water
classification reads the retained pre-erosion map, so `1_biomes`/`0_water` are bit-identical
with erosion on or off. **No rivers yet** — the carved channels are the designated future river
routes (Phase C0b promotes them). The predecessor D8 drainage-incision pass was written and
reverted in task 10 — per-cell steepest descent on a regular grid can only route along eight
headings, and at map scale that reads as straight hatching, not drainage; the droplet model is
the working replacement.
with erosion on or off.
The task-18 defaults tune for a drainage **hierarchy** — fine rills everywhere feeding a set of
clearly deeper convergent channels — by letting droplets live long enough (384 steps at inertia
0.35, low evaporation) that their paths overlap and deepen shared low lines, and by raising the
carve cap to 15 m so trunks separate from rills instead of both piling against one ceiling.
Measured on seed 1280587109: ~1.9 M cells carved past 0.5 m, 42 k past 5 m, 1.4 k past 10 m,
and 177 connected channel systems of 200+ cells at the 3 m threshold. Erosion concentrates
~9× on the curve's shelf risers, because that is where sustained slope exists; the flat
shelves and lowlands are barely touched, so **the lowland continuation of a trunk is not
erosion's to cut** — that is river promotion's job. **No rivers yet** — the carved channels are
the designated future river routes (Phase C0b promotes them by flow accumulation).
The predecessor D8 drainage-incision pass was written and reverted in task 10 — per-cell
steepest descent on a regular grid can only route along eight headings, and at map scale that
reads as straight hatching, not drainage; the droplet model is the working replacement (its
carve field measures isotropic to within 1.5 % across the folded 45° grid period).
3. **Sea level and water** — sea level per the configured model (`SeaLevelModel`: `"flat"` scalar
— the default, `SeaLevelValue` 0.15 — or the legacy `"field"` latitude Lerp); flood fill
separates true ocean from inland lakes; a mainland fill guarantees one contiguous landmass.