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.
120 lines
6.5 KiB
C#
120 lines
6.5 KiB
C#
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 < 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] && !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";
|
||
}
|
||
}
|