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
`[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
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
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²`. |
| `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. |
| `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. |
| `RDRG` / `0x47524452` | same layout | Rugged tier. |
| `RDTL` / `0x4C544452` | same layout | Trail tier. |
@ -85,9 +88,20 @@ the file, not the original generation.
### Size
`total = 8 (header) + Σ per section (12 + payload)`. The pixel grid dominates: `5·MapSize²` bytes
(4 height + 1 biome) ≈ **320 MiB at 8K**, vs v1's `8·MapSize²` ≈ 512 MiB — the u8 biome section
saves ~192 MiB at 8K.
`total = 8 (header) + Σ per section (12 + payload)`. The pixel grids dominate: without water
sections `5·MapSize²` bytes (4 height + 1 biome) ≈ **320 MiB at 8K**, vs v1's `8·MapSize²`
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)

View file

@ -39,10 +39,11 @@ rendering.
- **`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.
- **`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
flag). Dispatches on the first byte: v2 tagged-section files get validation (version gate, size
towns, four road tiers, and — v2 only — the embedded generation params, per-town highway-node
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
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
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

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
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).
- **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
mountain hub, and county roads daisy-chained outward with Prim's algorithm. Paths are decimated
(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.
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.
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.
6. **Roads** — see below.
7. **Export** — writes the `.dat` blueprint (dual-write: the v2 tagged container under the primary
7. **Roads** — see below. Skipped entirely when the `SkipRoads` config toggle is on (the ~25-min
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
`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).
@ -55,8 +63,10 @@ Road colours on the snapshot, useful for identifying a road: **red** = Highway,
**dark brown** = Rugged, **light brown** = Trail.
### Outputs
`MapData_Seed_<seed>.dat` (v2), `MapData_Seed_<seed>_v1.dat` (legacy dual-write), and
`Map_Seed_<seed>.png`, all to `user://`.
`MapData_Seed_<seed>.dat` (v2), `MapData_Seed_<seed>_v1.dat` (legacy dual-write), and the staged
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`