docs: water sections (WBID/WBTB/WSRF), no-BSIN rationale, SkipRoads + water stage in READMEs (terrain-water task 03)

Byte-accurate entries for the three water sections (transitional
per-body level rule, provisional salinity, WSRF sentinel/quantization),
updated size math (~576 MiB at 8K with water), the on-the-record
rationale for NOT serializing basins, and README updates for the new
pipeline stage, the 0_water snapshot, and the SkipRoads toggle.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Stewart Howe 2026-08-07 01:40:48 -04:00
parent 3bf822dac6
commit 1f957b3430
5 changed files with 44 additions and 13 deletions

View file

@ -17,7 +17,9 @@ dispatches on the file's first byte:
- **v2 (primary)** — raw `ISLA` magic, u32 version gate, then tagged sections - **v2 (primary)** — raw `ISLA` magic, u32 version gate, then tagged sections
`[u32 tag][u64 length][payload]`. Unknown tags are skipped by length, so future sections are `[u32 tag][u64 length][payload]`. Unknown tags are skipped by length, so future sections are
invisible to older readers. Validated on read: version, MapSize bounds, section lengths against invisible to older readers. Validated on read: version, MapSize bounds, section lengths against
the file, biome/tier ordinal ranges. the file, biome/tier ordinal ranges. Since the water-bodies stage, v2 files also carry the
optional `WBID`/`WBTB`/`WSRF` water sections (per-pixel body ids, the body table, quantized
surface levels) — parsed into `WorldBlueprint` and consumed by nothing at runtime yet.
- **v1 (legacy)** — the positional `"ISLA_V1"` format, still written beside v2 as `_v1.dat` and - **v1 (legacy)** — the positional `"ISLA_V1"` format, still written beside v2 as `_v1.dat` and
still loadable (with a deprecation warning) until a future removal task. still loadable (with a deprecation warning) until a future removal task.

View file

@ -55,7 +55,10 @@ Every section: `[u32 tag][u64 payload-length in bytes][payload]`.
| `HGTS` / `0x53544748` | `MapSize²` × `f32` height, **X outer / Y inner** | Mandatory. The second index is the map's north/south axis; the server consumes it as world **Z**. (This axis convention was never written down for v1 — it is now normative.) Length must equal `4·MapSize²`. | | `HGTS` / `0x53544748` | `MapSize²` × `f32` height, **X outer / Y inner** | Mandatory. The second index is the map's north/south axis; the server consumes it as world **Z**. (This axis convention was never written down for v1 — it is now normative.) Length must equal `4·MapSize²`. |
| `BIOM` / `0x4D4F4942` | `MapSize²` × `u8` biome ordinal, same pixel order | Mandatory. Ordinals from `Enums.cs::Biome`**append-only, never reorder** (the ordinal IS the wire value). Writer refuses ordinals > 255; reader rejects ordinals ≥ the known biome count (parse-time validation; the palette's runtime Dirt fallback for in-memory values is unchanged). Length must equal `MapSize²`. | | `BIOM` / `0x4D4F4942` | `MapSize²` × `u8` biome ordinal, same pixel order | Mandatory. Ordinals from `Enums.cs::Biome`**append-only, never reorder** (the ordinal IS the wire value). Writer refuses ordinals > 255; reader rejects ordinals ≥ the known biome count (parse-time validation; the palette's runtime Dirt fallback for in-memory values is unchanged). Length must equal `MapSize²`. |
| `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. | | `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. |
| `RDHW` / `0x57484452` | `i32 pathCount`, then per path: `i32 pointCount` + `pointCount` × (`f32 X` · `f32 Y`) | Highway tier. **Tags, not file position, identify the tier** — the v1 order-fragility is gone. | | `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. |
| `RDHW` / `0x57484452` | `i32 pathCount`, then per path: `i32 pointCount` + `pointCount` × (`f32 X` · `f32 Y`) | Highway tier. **Tags, not file position, identify the tier** — the v1 order-fragility is gone. Road sections may legitimately be **present but empty** (zero paths): the `SkipRoads` config toggle exports a road-less iteration blueprint. |
| `RDBR` / `0x52424452` | same layout | Branch tier. | | `RDBR` / `0x52424452` | same layout | Branch tier. |
| `RDRG` / `0x47524452` | same layout | Rugged tier. | | `RDRG` / `0x47524452` | same layout | Rugged tier. |
| `RDTL` / `0x4C544452` | same layout | Trail tier. | | `RDTL` / `0x4C544452` | same layout | Trail tier. |
@ -85,9 +88,20 @@ the file, not the original generation.
### Size ### Size
`total = 8 (header) + Σ per section (12 + payload)`. The pixel grid dominates: `5·MapSize²` bytes `total = 8 (header) + Σ per section (12 + payload)`. The pixel grids dominate: without water
(4 height + 1 biome) ≈ **320 MiB at 8K**, vs v1's `8·MapSize²` ≈ 512 MiB — the u8 biome section sections `5·MapSize²` bytes (4 height + 1 biome) ≈ **320 MiB at 8K**, vs v1's `8·MapSize²`
saves ~192 MiB at 8K. 512 MiB — the u8 biome section saves ~192 MiB. With the water sections the per-pixel cost is
`9·MapSize²` (adds 2 B `WBID` + 2 B `WSRF`) ≈ **576 MiB at 8K** plus a trivial `WBTB` (20 B per
body).
### Deliberately NOT a section: basins (`BSIN`)
The priority-flood pit-fill that runs in the water-bodies stage is **diagnostics only** and
serializes nothing. Rationale, on the record so nobody adds a basin section later without meeting
the argument: basin geometry is a pure function of the heightmap, and it goes stale the moment the
coast-smoothing change set (or any terrain change) touches heights — a serialized `BSIN` would be
a cache with a silent invalidation hazard. The rivers stage recomputes basins fresh at generation
time (measured ~12 s at 8K — cheap enough to never cache).
### Adding a new section (the intended extension path) ### Adding a new section (the intended extension path)

View file

@ -39,10 +39,11 @@ rendering.
- **`BlueprintWriter.cs`** — writes a `WorldBlueprint` as a v2 file. Blueprint-typed on purpose: - **`BlueprintWriter.cs`** — writes a `WorldBlueprint` as a v2 file. Blueprint-typed on purpose:
the map generator and the round-trip harness are both just callers. the map generator and the round-trip harness are both just callers.
- **`MapDataParser.cs`** — decodes a `.dat` blueprint into a `WorldBlueprint` (heightmap, biome map, - **`MapDataParser.cs`** — decodes a `.dat` blueprint into a `WorldBlueprint` (heightmap, biome map,
towns, four road tiers, and — v2 only — the embedded generation params and per-town highway-node towns, four road tiers, and — v2 only — the embedded generation params, per-town highway-node
flag). Dispatches on the first byte: v2 tagged-section files get validation (version gate, size flag, and the optional water-bodies data: `WaterBodyIds`, `WaterBodies` table, `WaterSurfaceQ`).
Dispatches on the first byte: v2 tagged-section files get validation (version gate, size
bounds, section-length and ordinal range checks); legacy v1 files still load, intact, with a bounds, section-length and ordinal range checks); legacy v1 files still load, intact, with a
deprecation warning. deprecation warning. Nothing at runtime consumes the water data yet.
- **`ChunkData.cs`** — one chunk's density and block-ID fields, `+1` padded on every axis so the - **`ChunkData.cs`** — one chunk's density and block-ID fields, `+1` padded on every axis so the
mesher can reach into the neighbouring chunk, plus the per-column data the renderer needs. mesher can reach into the neighbouring chunk, plus the per-column data the renderer needs.
- **`Constants.cs`** — chunk dimensions, `ISO_LEVEL`, `VOXEL_SCALE`, and the visual/road tunables - **`Constants.cs`** — chunk dimensions, `ISO_LEVEL`, `VOXEL_SCALE`, and the visual/road tunables

View file

@ -17,6 +17,10 @@ Builds the topographical and logistical foundation of the island before any 3D e
- **Topography:** FastNoiseLite plus distance-falloff maths for a guaranteed mainland island, a - **Topography:** FastNoiseLite plus distance-falloff maths for a guaranteed mainland island, a
mountain spine, dynamic temperature-driven sea level, and an impact crater pushed into the northern mountain spine, dynamic temperature-driven sea level, and an impact crater pushed into the northern
coast (carved at 80 % of its radius so a landbridge always survives). coast (carved at 80 % of its radius so a landbridge always survives).
- **Water bodies:** the classified water promoted into explicit blueprint data (ocean + labeled
lakes with transitional per-body levels — `WBID`/`WBTB`/`WSRF` sections), plus priority-flood
basin diagnostics. The `SkipRoads` config toggle turns a full ~26-min generation into a ~80 s
road-less iteration cycle.
- **Logistics:** `AStarGrid2D` pathfinding producing a looping continental highway, a branch to the - **Logistics:** `AStarGrid2D` pathfinding producing a looping continental highway, a branch to the
mountain hub, and county roads daisy-chained outward with Prim's algorithm. Paths are decimated mountain hub, and county roads daisy-chained outward with Prim's algorithm. Paths are decimated
(RamerDouglasPeucker) then smoothed (4 Chaikin passes). (RamerDouglasPeucker) then smoothed (4 Chaikin passes).

View file

@ -15,10 +15,18 @@ Generates the entire 2D blueprint. Roughly in order:
inland lakes; a mainland fill guarantees one contiguous landmass. inland lakes; a mainland fill guarantees one contiguous landmass.
4. **The crater** — placed along the northern coast and carved to below sea level, but only out to 4. **The crater** — placed along the northern coast and carved to below sea level, but only out to
**80 % of its radius**, which guarantees a landbridge rather than severing the island. **80 % of its radius**, which guarantees a landbridge rather than severing the island.
5. **Biomes and towns** — biome zoning by height and temperature; tiered town placement (Capitol, 5. **Water bodies** — promotes the classified water into explicit data: the ocean as body 1, each
lake connected-component labeled (same 4-connectivity as the true-ocean fill), one transitional
surface level per body. Classification comes from the shared `IsOceanPixel`/`IsLakePixel`
predicates that the biome stage also uses, so the two can never disagree. A priority-flood
pit-fill runs here as validated diagnostics (basin statistics to console; serializes nothing).
Drops the `0_water` snapshot.
6. **Biomes and towns** — biome zoning by height and temperature; tiered town placement (Capitol,
Hubs, Villages, Outposts, POIs) filtered by slope, water proximity and spacing. Hubs, Villages, Outposts, POIs) filtered by slope, water proximity and spacing.
6. **Roads** — see below. 7. **Roads** — see below. Skipped entirely when the `SkipRoads` config toggle is on (the ~25-min
7. **Export** — writes the `.dat` blueprint (dual-write: the v2 tagged container under the primary A\* pass is the bottleneck; a skip run exports a road-less iteration blueprint in ~80 s with a
loud console banner).
8. **Export** — writes the `.dat` blueprint (dual-write: the v2 tagged container under the primary
seed name, plus the legacy v1 format beside it as `_v1.dat` — see seed name, plus the legacy v1 format beside it as `_v1.dat` — see
`Core/Scripts/BLUEPRINT_FORMAT.md`), then renders the PNG snapshot. The v2 file embeds the `Core/Scripts/BLUEPRINT_FORMAT.md`), then renders the PNG snapshot. The v2 file embeds the
resolved generation params (seed, MapSize, crater radius, density, impact centre, provenance). resolved generation params (seed, MapSize, crater radius, density, impact centre, provenance).
@ -55,8 +63,10 @@ Road colours on the snapshot, useful for identifying a road: **red** = Highway,
**dark brown** = Rugged, **light brown** = Trail. **dark brown** = Rugged, **light brown** = Trail.
### Outputs ### Outputs
`MapData_Seed_<seed>.dat` (v2), `MapData_Seed_<seed>_v1.dat` (legacy dual-write), and `MapData_Seed_<seed>.dat` (v2), `MapData_Seed_<seed>_v1.dat` (legacy dual-write), and the staged
`Map_Seed_<seed>.png`, all to `user://`. snapshots `Map_Seed_<seed>_{0_water,1_biomes,2_towns,3_roads}.png`, all to `user://`. (`0_water`
is painted from the water stage's own outputs — ocean deep blue, lakes lighter blue, land neutral;
`3_roads` is absent on a SkipRoads run.)
## `RoundTripHarness.cs` ## `RoundTripHarness.cs`