feat: tributary water (tapered), significant-lake targeting, finer steps + deeper beds (terrain-water task 24)

All three gate polish items — they interleave in the same files, so they land as
one buildable commit rather than three that do not compile independently.

1. TRIBUTARY WATER. Tributaries carved in task 22/23 but stayed dry: they were
   never registered with the water stage. They now are (as CarvedRiver entries
   named 'trib', kept OUT of the per-river console table so it stays the 6
   mains). RiverTribWaterMinFlow (40k px of along-course flow) sets where water
   starts; RiverTribTaperPx (120 px) makes the wet->dry transition a FADE, not a
   wall — across the taper the wet strip narrows (0.25x -> 1x half-width) AND its
   surface drops toward the bed, so a stream head thins out and vanishes.
   Along-course flow is modelled quadratically from headwater trickle to full
   drainage at the mouth (the plan records drainage per river, not per sample).
   RiverTribWaterMinFlow=0 waters them end to end — the documented fallback.

2. LAKE-ENDER TARGETING. The join routed to the nearest classify-water CELL,
   which a 322-px puddle satisfies; it stopped ~80 px short of the 197k-px
   lagoon beside it. It now routes to a SIGNIFICANT water mask (bodies of at
   least RiverLakeMinTargetPx = 20k cells, built from the water-body table),
   falling back to any classify water only if no significant body is reachable,
   so a seed with genuinely small ponds still connects. Proven by adjacency:
   task 23 had 63 river cells touching the puddle and 0 touching the lagoon;
   task 24 has 49 touching the lagoon and 0 touching the puddle.

3. FINER STEPS + DEEPER BEDS. RiverStepDropM 2.0 -> 0.6 and RiverWaterDepthM
   1.2 -> 2.2, RiverDepthScale 1.0 -> 1.5. Reaches 298 -> 1034; the level gap
   between adjacent reaches drops from median 0.571 m / p95 2.08 m to median
   0.124 m / p95 0.74 m — the pond-staircase reads as a graded descent. Reaches
   stay trivial bodies (median 44 px).

Guards all held: flood guard 0 newly-below-sea and 0 below-sea cells modified,
BIOME oracle md5-identical to the task-22 baseline (output-only unchanged),
island top 457.65 m exact, crater core excluded, lowest carved cell exactly
sea+margin (37.85 m). Cumulative max cut 25.98 m with 339 cells >20 m —
identical to task 23, so deepening the WATER did not deepen the worst cuts.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Stewart Howe 2026-08-11 20:24:07 -04:00
parent 645955d49c
commit 0e39538604
3 changed files with 113 additions and 15 deletions

View file

@ -127,9 +127,18 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
public static string Rivers = "off";
public static string RiverRoutingStyle = "lowground";
public static float RiverWidthScale = 1.75f; // widened at the task-23 gate's ask
public static float RiverStepDropM = 2.0f;
public static float RiverWaterDepthM = 1.2f;
public static float RiverDepthScale = 1.0f;
// Task 24 (the gate's polish): finer steps read as a descending river rather
// than a pond staircase; deeper water sits contained in its banks.
// RiverTribWaterMinFlow is the drainage a TRIBUTARY reach needs to carry
// 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.
public static float RiverStepDropM = 0.6f;
public static float RiverWaterDepthM = 2.2f;
public static int RiverTribWaterMinFlow = 40000;
public static int RiverTribTaperPx = 120;
public static int RiverLakeMinTargetPx = 20000;
public static float RiverDepthScale = 1.5f; // deepened at the task-24 gate's ask
public static float RiverSeaMargin = 0.2f; // m above sea, bed floor
// Island falloff shaping (task 11).
@ -339,6 +348,12 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
if (data.ContainsKey("RiverSeaMargin")) RiverSeaMargin = (float)data["RiverSeaMargin"];
if (data.ContainsKey("RiverStepDropM")) RiverStepDropM = (float)data["RiverStepDropM"];
if (data.ContainsKey("RiverWaterDepthM")) RiverWaterDepthM = (float)data["RiverWaterDepthM"];
if (data.ContainsKey("RiverTribWaterMinFlow")) RiverTribWaterMinFlow = (int)data["RiverTribWaterMinFlow"];
if (data.ContainsKey("RiverTribTaperPx")) RiverTribTaperPx = (int)data["RiverTribTaperPx"];
if (data.ContainsKey("RiverLakeMinTargetPx")) RiverLakeMinTargetPx = (int)data["RiverLakeMinTargetPx"];
RiverTribWaterMinFlow = Mathf.Max(RiverTribWaterMinFlow, 0);
RiverTribTaperPx = Mathf.Clamp(RiverTribTaperPx, 0, 2000);
RiverLakeMinTargetPx = Mathf.Max(RiverLakeMinTargetPx, 0);
RiverStepDropM = Mathf.Clamp(RiverStepDropM, 0.25f, 10f);
RiverWaterDepthM = Mathf.Clamp(RiverWaterDepthM, 0.2f, 5f);
RiverWidthScale = Mathf.Clamp(RiverWidthScale, 0.1f, 5f);