islaApocalypse-v2/Tools/Scripts/RiverCandidate.cs
beezm 4e4be6a83e rivers/03: lowland routing — the routed MIX on the pure N=12, courses only
Ports the ROUTING PORTION of the reference's RiverCarvePass (RouteToOcean, the
routed/lake-ender sort, SmoothCourse). NOT CarveRiver (bed stamp) and NOT
AddSteppedWater (water bodies) — those are later tasks.

RED LINE: no height mutated, no water filled, nothing carved. Asserted per seed
by an FNV digest of both height fields before/after routing.

- RiverRouting: deterministic LOWGROUND Dijkstra, uphill penalised so a route may
  cross the basin rim, empty-list-on-no-path. Effective == declared constants
  (verified: private const, no ConfigManager key, no [Export] in the reference).
- The sort is the REFERENCE's — Kind = basinHasLake ? lake-ender : routed. The
  task's stated "a path exists -> routed" cannot discriminate: on an 8-connected
  grid a path to the ocean always exists, confirmed empirically (43/43 probes
  reached). The ocean route is probed for every giant anyway, so the missing
  affordability threshold is reported as a number rather than guessed.
- RegionLabeling.SignificantWaterMask: interim substitute for v2's missing
  water-bodies table — 8-connected classify-water components >= 20,000 px.
- RiverCandidates: the candidate enumeration extracted out of RiverPromotionTool
  so routing ranks the identical set the count gate was judged on. Behaviour
  neutral — rivers/02b's twelve plates are byte-identical across the extraction.
- DrainageRenderer.RoutedMix: three classes, with each routed river's added
  lowland reach and the rim it crossed drawn distinctly from its natural stem.

Taste gate: no count, no K, no style, no default set.
2026-08-24 04:54:30 -04:00

120 lines
6.5 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System.Collections.Generic;
namespace IslaApocalypse.Tools
{
/// <summary>
/// ⭐⭐ ONE CANDIDATE MAJOR DRAINAGE — the unit the river count is chosen over (rivers/02).
///
/// ═══ WHY THIS TYPE EXISTS: ONE LIST, NOT TWO ═══
///
/// The reference promoted rivers from TWO separate lists with TWO separate quotas — N sea-reaching
/// trunks and N endorheic giants (<c>DrainageAnalysis.Params.TrunkCount</c> / <c>GiantCount</c>,
/// both 3). That structure encodes an assumption this terrain does not satisfy: that reaching the
/// sea is what makes a drainage a river, and inland ones are a second category to be quota'd
/// separately.
///
/// **On the reshaped terrain ~68 % of land drains INLAND** (measured: 67.6 % on the primary seed,
/// 116 terminal basins). A separate quota would fight that — it would promote small coastal
/// drainages over far larger inland ones purely because of where they end.
///
/// > ### So selection is UNIFIED: rank every major drainage by contributing area, promote the top N,
/// > and let the sea-vs-endorheic split FALL OUT of which promoted rivers happen to reach the ocean.
/// > **An endorheic terminus is a PASS, not a fallback** — a river ending in a significant lake is
/// > as real as one reaching the coast, and is never forced to the coast.
///
/// ⚠ This is a DELIBERATE DEPARTURE from the reference's two-list structure (approved in chat;
/// D-050 port-discipline noted). Only the SELECTION is unified — <see cref="IsSea"/> is retained
/// per river because downstream routing branches on it, and <c>Trunk</c> / <c>Giant</c> are left
/// exactly as ported.
///
/// ═══ ⚠ THE METRIC IS THE SAME UNIT ON BOTH SIDES, AND THAT IS LOAD-BEARING ═══
///
/// <see cref="DrainagePx"/> is a COUNT OF CONTRIBUTING LAND CELLS in both cases, computed on the
/// same D8 field in the same pass:
///
/// SEA <c>Plan.Acc</c> at the outlet — every non-ocean cell is seeded 1 and accumulated
/// along <c>Plan.Dir</c>, so the outlet's value is the count of cells whose flow path
/// passes through it.
/// ENDORHEIC <c>Plan.BasinInflow[BasinId]</c> — the memoised downstream walk over the SAME
/// <c>Dir</c>, counting cells whose flow TERMINATES in that basin.
///
/// Every land cell has exactly one destination, so the two populations are disjoint and exhaustive:
/// <c>Σ sea-outlet Acc + Σ BasinInflow + UnroutedCells == LandCells</c>. `RiverPromotionTool`
/// ASSERTS that identity per seed — it is the mechanical proof that one ranking over both is sound.
/// </summary>
public sealed class RiverCandidate
{
/// <summary>⭐ The terminus. True = the outlet touches <c>RegionLabeling.OceanMask</c>; false = it pools in a terminal basin. Never a bare <c>h &lt; sea</c> test.</summary>
public bool IsSea;
/// <summary>Row-major cell: the sea outlet, or the terminal basin's MINIMUM (its deepest cell).</summary>
public int Cell;
public int X, Y;
/// <summary>
/// ⭐ Where the RIVER actually ends — the point its main stem pools at, i.e. the first point of
/// <see cref="Course"/>. Defaults to <see cref="X"/>/<see cref="Y"/> until bound.
///
/// ⚠⚠ FOR AN ENDORHEIC RIVER THIS IS NOT THE BASIN'S DEEPEST CELL, and the difference is
/// visible on a map. `DrainageAnalysis` is explicit about why: *"Terminal is where the MAIN
/// STEM actually pools (its sub-minimum), which on a flat basin floor is more truthful than the
/// basin's deepest cell."* On a wide flat lagoon bed those two points can sit far apart.
///
/// Both are real and both are kept: the basin minimum is the BASIN's identity (and is what the
/// CSV records), this is the RIVER's terminus (and is what the plates mark). Marking a river at
/// its basin's deepest cell draws the stem visibly detached from its own endpoint — which reads
/// as a broken river and would corrupt a count judgment.
/// </summary>
public int TermX, TermY;
/// <summary>⭐ THE RANKING METRIC — contributing land cells. Same unit for both termini (see the class note).</summary>
public long DrainagePx;
/// <summary>Terminal-basin id (endorheic only; 0 for sea). The stable key for binding a candidate to its <c>Giant</c>.</summary>
public int BasinId;
/// <summary>Endorheic only: the basin's max fill depth, metres.</summary>
public float BasinDepthM;
/// <summary>Endorheic only: the basin's area in cells.</summary>
public long BasinAreaPx;
/// <summary>
/// ⚠ Sea only. True when this outlet was DROPPED by the <c>MinOutletSeparationPx</c> rule
/// because a larger outlet sits within that radius. Kept in the distribution (it is a real
/// drainage) but excluded from ranking — see the tool's note on what separation discards.
/// </summary>
public bool SuppressedBySeparation;
/// <summary>
/// The REAL upland stem — the max-accumulation traced course from <c>DrainageAnalysis</c>'s own
/// <c>TraceStem</c>, bound after selection. Null for candidates outside the promoted set.
/// ⚠ Downstream-first and decimated ×4, as the analysis produces it.
/// ⚠⚠ This is the erosion-carved course, NOT <c>Giant.ProvisionalRoute</c> — the steepest-descent
/// placeholder ("the comb") is deliberately never drawn here; replacing it is rivers/03's job,
/// and drawing it would mislead a count judgment.
/// </summary>
public List<(float x, float y)> Course;
/// <summary>1-based rank in the unified descending ranking. 0 until ranked.</summary>
public int Rank;
/// <summary>
/// ⭐ rivers/03 — THE ANALYSIS'S OWN routed/lake-ender verdict, copied from <c>Giant.Kind</c> at
/// bind time. Endorheic only ("" for sea candidates).
///
/// ⚠⚠ READ THE TEST BEFORE TRUSTING THE NAME. `DrainageAnalysis` assigns this as
/// <c>(basinHasLake[id] &amp;&amp; !SouthernCandidate) ? "lake-ender" : "routed"</c> — i.e. purely on
/// **whether the terminal basin holds classify water**. It is NOT a path test: "routed" means
/// "this basin is a dry pan, so it SHOULD be routed", not "a route to the sea exists". Whether
/// one actually does is what `RiverRouting.RouteToOcean` decides, and the two CAN disagree.
/// rivers/03 reports both per river rather than silently picking one.
/// </summary>
public string AnalysisKind = "";
/// <summary>Endorheic only: the analysis found classify water in this terminal basin.</summary>
public bool TerminalInClassifyWater;
public string TerminusName => IsSea ? "sea" : "endorheic";
}
}