feat: tributaries wet full length; water fills the channel; banks flare as shores (terrain-water task 25)

The gate's two river notes.

FILL THE TRIBUTARIES. RiverTribWaterMinFlow defaults to 0, so every promoted
tributary carries water its whole length (800 of 1574 reaches are now on
tributaries, 65,364 of 268,863 wet px). The graceful tip survives: the fade zone
is now always TaperPx long starting wherever water begins, so with a threshold it
spans (WetFrom-Taper -> WetFrom) and with full watering it spans the first
TaperPx from the HEAD. Fill is about LENGTH, taper is about the END.

FILL THE CHANNEL, NOT THE FLOOR. The water surface is now RiverFillFraction (80%)
of the LOCAL bed depth rather than a fixed height, floored by RiverWaterDepthM as
an absolute minimum. A fixed height is wrong at both ends — a trickle in a 13 m
mouth and over the rim at a 1.5 m head — where a fraction is right at every
scale and cannot spill onto the plain.

BANKS AS SHORES. The shoulder flares RiverBankFlare (3.2, was a hard 2x)
half-widths with a smootherstep instead of smoothstep, so the rim leaves nearly
tangent to the water plane. That alone cut too much where a course crosses a
ridge (cells deeper than 20 m went 339 -> 1917, max 26 -> 37 m), so the SHOULDER
may not lower a cell more than RiverBankMaxCutM (3 m) — measured against the
PRE-PASS surface, because a per-write cap let overlapping stamps each take
another 3 m and changed the carved volume by 1 m3 out of 1.29 M. Gentle ground
still flares into a shore; a ridge crossing keeps steep walls, which is what a
gorge looks like. Net result is better than BOTH predecessors: max cut 16.8 m
(task 24: 26.0), zero cells past 20 m (task 24: 339), carve p95 5.26 m.

Guards unchanged: BIOME oracle md5-identical to the task-22 baseline, 0
newly-below-sea, 0 below-sea cells modified, lowest carved cell exactly
sea+margin, island top 457.65 m exact, crater core excluded.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stewart Howe 2026-08-11 21:11:41 -04:00
parent 3fd14c0389
commit c1f62f6899
2 changed files with 66 additions and 10 deletions

View file

@ -133,9 +133,19 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
// water (0 = water them end to end); above it the water TAPERS to dry over
// RiverTribTaperPx so a stream head fades instead of ending in a wall.
// RiverLakeMinTargetPx is the smallest water body a lake-ender may target.
// Task 25: RiverFillFraction fills the channel to a fraction of its LOCAL bed
// depth (a fixed height was a trickle at deep mouths and overtopped shallow
// heads); RiverWaterDepthM stays as the absolute minimum. RiverBankFlare
// widens the bank shoulder (half-widths beyond the channel) and it now uses a
// smootherstep, so water meets land as a shore instead of a wall.
// RiverTribWaterMinFlow defaults to 0 — tributaries carry water their full
// promoted length (the gate's preference), still fading at the tip.
public static float RiverStepDropM = 0.6f;
public static float RiverWaterDepthM = 2.2f;
public static int RiverTribWaterMinFlow = 40000;
public static float RiverWaterDepthM = 1.0f;
public static float RiverFillFraction = 0.80f;
public static float RiverBankFlare = 3.2f;
public static float RiverBankMaxCutM = 3.0f; // shoulder-only cut cap (task 25)
public static int RiverTribWaterMinFlow = 0;
public static int RiverTribTaperPx = 120;
public static int RiverLakeMinTargetPx = 20000;
public static float RiverDepthScale = 1.5f; // deepened at the task-24 gate's ask
@ -351,6 +361,12 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
if (data.ContainsKey("RiverTribWaterMinFlow")) RiverTribWaterMinFlow = (int)data["RiverTribWaterMinFlow"];
if (data.ContainsKey("RiverTribTaperPx")) RiverTribTaperPx = (int)data["RiverTribTaperPx"];
if (data.ContainsKey("RiverLakeMinTargetPx")) RiverLakeMinTargetPx = (int)data["RiverLakeMinTargetPx"];
if (data.ContainsKey("RiverFillFraction")) RiverFillFraction = (float)data["RiverFillFraction"];
if (data.ContainsKey("RiverBankFlare")) RiverBankFlare = (float)data["RiverBankFlare"];
RiverFillFraction = Mathf.Clamp(RiverFillFraction, 0.1f, 1f);
if (data.ContainsKey("RiverBankMaxCutM")) RiverBankMaxCutM = (float)data["RiverBankMaxCutM"];
RiverBankFlare = Mathf.Clamp(RiverBankFlare, 1.2f, 8f);
RiverBankMaxCutM = Mathf.Clamp(RiverBankMaxCutM, 0f, 30f);
RiverTribWaterMinFlow = Mathf.Max(RiverTribWaterMinFlow, 0);
RiverTribTaperPx = Mathf.Clamp(RiverTribTaperPx, 0, 2000);
RiverLakeMinTargetPx = Mathf.Max(RiverLakeMinTargetPx, 0);