using System.Collections.Generic; namespace IslaApocalypse.Tools { /// /// ⭐⭐ 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 (DrainageAnalysis.Params.TrunkCount / GiantCount, /// 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 — is retained /// per river because downstream routing branches on it, and Trunk / Giant are left /// exactly as ported. /// /// ═══ ⚠ THE METRIC IS THE SAME UNIT ON BOTH SIDES, AND THAT IS LOAD-BEARING ═══ /// /// is a COUNT OF CONTRIBUTING LAND CELLS in both cases, computed on the /// same D8 field in the same pass: /// /// SEA Plan.Acc at the outlet — every non-ocean cell is seeded 1 and accumulated /// along Plan.Dir, so the outlet's value is the count of cells whose flow path /// passes through it. /// ENDORHEIC Plan.BasinInflow[BasinId] — the memoised downstream walk over the SAME /// Dir, counting cells whose flow TERMINATES in that basin. /// /// Every land cell has exactly one destination, so the two populations are disjoint and exhaustive: /// Σ sea-outlet Acc + Σ BasinInflow + UnroutedCells == LandCells. `RiverPromotionTool` /// ASSERTS that identity per seed — it is the mechanical proof that one ranking over both is sound. /// public sealed class RiverCandidate { /// ⭐ The terminus. True = the outlet touches RegionLabeling.OceanMask; false = it pools in a terminal basin. Never a bare h < sea test. public bool IsSea; /// Row-major cell: the sea outlet, or the terminal basin's MINIMUM (its deepest cell). public int Cell; public int X, Y; /// /// ⭐ Where the RIVER actually ends — the point its main stem pools at, i.e. the first point of /// . Defaults to / 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. /// public int TermX, TermY; /// ⭐ THE RANKING METRIC — contributing land cells. Same unit for both termini (see the class note). public long DrainagePx; /// Terminal-basin id (endorheic only; 0 for sea). The stable key for binding a candidate to its Giant. public int BasinId; /// Endorheic only: the basin's max fill depth, metres. public float BasinDepthM; /// Endorheic only: the basin's area in cells. public long BasinAreaPx; /// /// ⚠ Sea only. True when this outlet was DROPPED by the MinOutletSeparationPx 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. /// public bool SuppressedBySeparation; /// /// The REAL upland stem — the max-accumulation traced course from DrainageAnalysis's own /// TraceStem, 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 Giant.ProvisionalRoute — 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. /// public List<(float x, float y)> Course; /// 1-based rank in the unified descending ranking. 0 until ranked. public int Rank; /// /// ⭐ rivers/03 — THE ANALYSIS'S OWN routed/lake-ender verdict, copied from Giant.Kind at /// bind time. Endorheic only ("" for sea candidates). /// /// ⚠⚠ READ THE TEST BEFORE TRUSTING THE NAME. `DrainageAnalysis` assigns this as /// (basinHasLake[id] && !SouthernCandidate) ? "lake-ender" : "routed" — 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. /// public string AnalysisKind = ""; /// Endorheic only: the analysis found classify water in this terminal basin. public bool TerminalInClassifyWater; public string TerminusName => IsSea ? "sea" : "endorheic"; } }