feat: mixed river plan with provisional routes (terrain-water task 21b, C0b part 1 revised)
Still PURE ANALYSIS — zero terrain change, zero water; the source blueprint is never written (md5-verified). Extends task 21 per the developer's b+c decision: promote ~5-6 rivers, the natural ocean trunks PLUS the top endorheic giants. Giants (top GiantCount terminal basins by per-basin total inflow) get their main stem anchored on the STRONGEST FEEDER into the basin, not the basin's deepest cell — on a flat basin floor the deepest cell sees only local trickles (the task-21 lesson applied to stems). Classification is by what the terminal BASIN holds, not the stem's single pooling cell (a stem can pool on dry ground a few hundred px short of its lagoon and still be a lagoon river): basin holds a classify lake -> LAKE-ENDER; dry pan -> ROUTED; the giant pooling nearest the southernmost town is the SOUTHERN CANDIDATE and always ROUTED (shown, not forced). Routed giants carry a PROVISIONAL route: steepest descent on the FULL (no-terminal) epsilon fill, so the basin overtops at its spill and the walk follows the terrain's own drainage to the ocean — the technique part 2 carves with, here only drawn and flagged provisionalRoute_NOT_WATER in the JSON. Seed 1280587109 result (defaults, 6 rivers): 3 ocean trunks (unchanged from task 21) + GIANT 1 [ROUTED - SOUTHERN CANDIDATE] 2.27M px pooling in the SE lagoon, 932-px route via spill (6598,5866) to the ocean — the island's biggest river serving the south; GIANT 2 [LAKE-ENDER] 1.82M px ending at the E lagoon; GIANT 3 [ROUTED] 1.76M px SW dry-pan system, 1716-px route to the SW coast. All provisional routes reach the ocean. Analysis 22 s. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c60510beb7
commit
2de9502e4f
2 changed files with 262 additions and 4 deletions
|
|
@ -53,6 +53,7 @@ public static class DrainageAnalysis
|
||||||
public int EndorheicMaxCount = 3;
|
public int EndorheicMaxCount = 3;
|
||||||
|
|
||||||
public int TrunkCount = 3; // ~3 sea-reaching trunks (developer)
|
public int TrunkCount = 3; // ~3 sea-reaching trunks (developer)
|
||||||
|
public int GiantCount = 3; // 21b: top endorheic giants promoted
|
||||||
public int MinOutletSeparationPx = 400; // don't pick 3 mouths of one delta
|
public int MinOutletSeparationPx = 400; // don't pick 3 mouths of one delta
|
||||||
|
|
||||||
public int StemMinAccPx = 1000; // stem tracing stops below this
|
public int StemMinAccPx = 1000; // stem tracing stops below this
|
||||||
|
|
@ -83,6 +84,32 @@ public static class DrainageAnalysis
|
||||||
public List<Stream> Tributaries = new();
|
public List<Stream> Tributaries = new();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A promoted endorheic giant (task 21b): one of the island's biggest drainage
|
||||||
|
/// systems, which pools inland because erosion could not cross the flats.
|
||||||
|
/// Kind "routed" carries a PROVISIONAL route across the flats to the ocean —
|
||||||
|
/// the path part 2 would carve, drawn for the gate, not water. Kind
|
||||||
|
/// "lake-ender" keeps its lake/lagoon terminal (real geography, developer's
|
||||||
|
/// call). 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.
|
||||||
|
/// </summary>
|
||||||
|
public class Giant : Stream
|
||||||
|
{
|
||||||
|
public (float x, float y) Terminal;
|
||||||
|
public (float x, float y) Spill; // where the basin overtops
|
||||||
|
public float BasinDepthM;
|
||||||
|
public long BasinAreaPx;
|
||||||
|
public string Kind = "routed"; // "routed" | "lake-ender"
|
||||||
|
public bool SouthernCandidate;
|
||||||
|
public bool TerminalInClassifyWater;
|
||||||
|
public List<(float x, float y)> ProvisionalRoute; // null for lake-enders
|
||||||
|
public bool RouteReachedOcean;
|
||||||
|
public (float x, float y) MountainExit;
|
||||||
|
public float MountainExitElevM;
|
||||||
|
public bool ExitFound;
|
||||||
|
public List<Stream> Tributaries = new();
|
||||||
|
}
|
||||||
|
|
||||||
public class EndorheicTerminal
|
public class EndorheicTerminal
|
||||||
{
|
{
|
||||||
public (float x, float y) Terminal; // basin minimum
|
public (float x, float y) Terminal; // basin minimum
|
||||||
|
|
@ -95,6 +122,7 @@ public static class DrainageAnalysis
|
||||||
{
|
{
|
||||||
public List<Trunk> Trunks = new();
|
public List<Trunk> Trunks = new();
|
||||||
public List<EndorheicTerminal> Endorheics = new();
|
public List<EndorheicTerminal> Endorheics = new();
|
||||||
|
public List<Giant> Giants = new(); // 21b: the promoted giants
|
||||||
public int TerminalBasinCount; // basins that qualified as sinks
|
public int TerminalBasinCount; // basins that qualified as sinks
|
||||||
public long PitsFilledCount; // depressions filled through
|
public long PitsFilledCount; // depressions filled through
|
||||||
public long LandCells, SeaReachingCells, EndorheicCells, UnroutedCells;
|
public long LandCells, SeaReachingCells, EndorheicCells, UnroutedCells;
|
||||||
|
|
@ -110,7 +138,14 @@ public static class DrainageAnalysis
|
||||||
/// such) or fill and spill onward to the true sea. Without this mask the first
|
/// such) or fill and spill onward to the true sea. Without this mask the first
|
||||||
/// draft called two of its three "sea-reaching" trunks done at enclosed
|
/// draft called two of its three "sea-reaching" trunks done at enclosed
|
||||||
/// lagoons, which is exactly the overclaim the gate must not inherit.</param>
|
/// lagoons, which is exactly the overclaim the gate must not inherit.</param>
|
||||||
public static Plan Run(float[,] height, int mapSize, bool[] isOcean, Params p)
|
/// <param name="isClassifyWater">Row-major mask of ANY classify water (WBID != 0):
|
||||||
|
/// a giant whose main stem pools inside classify water is a natural lake-ender;
|
||||||
|
/// one pooling on dry ground is a route-to-sea candidate.</param>
|
||||||
|
/// <param name="southX">Southernmost-town position (or -1 for none): the giant
|
||||||
|
/// whose terminal lies closest is flagged the SOUTHERN CANDIDATE and always
|
||||||
|
/// routed provisionally, per the 21b design — shown, not forced.</param>
|
||||||
|
public static Plan Run(float[,] height, int mapSize, bool[] isOcean,
|
||||||
|
bool[] isClassifyWater, float southX, float southY, Params p)
|
||||||
{
|
{
|
||||||
int n = mapSize;
|
int n = mapSize;
|
||||||
int total = n * n;
|
int total = n * n;
|
||||||
|
|
@ -122,6 +157,8 @@ public static class DrainageAnalysis
|
||||||
for (int y = 0; y < n; y++)
|
for (int y = 0; y < n; y++)
|
||||||
original[x * n + y] = height[x, y];
|
original[x * n + y] = height[x, y];
|
||||||
|
|
||||||
|
float[] plan_fullFilled = null; // set inside step 2, used by 21b routing
|
||||||
|
|
||||||
// --- 1. Priority-flood with one-ulp epsilon (routing surface only) ---
|
// --- 1. Priority-flood with one-ulp epsilon (routing surface only) ---
|
||||||
float[] filled = (float[])original.Clone();
|
float[] filled = (float[])original.Clone();
|
||||||
{
|
{
|
||||||
|
|
@ -197,6 +234,11 @@ public static class DrainageAnalysis
|
||||||
basinDepthM.Add(depth); basinAreaPx.Add(area); basinMinCell.Add(minCell);
|
basinDepthM.Add(depth); basinAreaPx.Add(area); basinMinCell.Add(minCell);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 21b: the FULL fill (before terminal reversion) is the provisional-
|
||||||
|
// routing surface — on it, every basin overtops at its spill and drains
|
||||||
|
// to the border, which is exactly "where the water would continue".
|
||||||
|
plan_fullFilled = (float[])filled.Clone();
|
||||||
|
|
||||||
bool[] terminal = new bool[nextId];
|
bool[] terminal = new bool[nextId];
|
||||||
for (int id = 1; id < nextId; id++)
|
for (int id = 1; id < nextId; id++)
|
||||||
{
|
{
|
||||||
|
|
@ -270,8 +312,8 @@ public static class DrainageAnalysis
|
||||||
// badly when the basin floor is flat (a lagoon bed scatters inflow across
|
// badly when the basin floor is flat (a lagoon bed scatters inflow across
|
||||||
// many sub-minima — measured: a 500k-px lagoon system reported under 50k).
|
// many sub-minima — measured: a 500k-px lagoon system reported under 50k).
|
||||||
long[] basinInflow = new long[basinMinCell.Count];
|
long[] basinInflow = new long[basinMinCell.Count];
|
||||||
{
|
|
||||||
int[] dest = new int[total]; // 0 unknown, -1 sea, -2 stuck, >0 basin id
|
int[] dest = new int[total]; // 0 unknown, -1 sea, -2 stuck, >0 basin id
|
||||||
|
{
|
||||||
var path = new List<int>(4096);
|
var path = new List<int>(4096);
|
||||||
for (int i = 0; i < total; i++)
|
for (int i = 0; i < total; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -455,6 +497,161 @@ public static class DrainageAnalysis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- 5d. The promoted GIANTS (21b): mixed set, provisional routes ---
|
||||||
|
// Top GiantCount terminal basins by TOTAL inflow. Their upland stems are the
|
||||||
|
// island's real big rivers; whether each continues to the sea is the gate's
|
||||||
|
// decision, previewed here.
|
||||||
|
{
|
||||||
|
var giantsRanked = new List<(int id, long inflow)>();
|
||||||
|
for (int id = 1; id < basinMinCell.Count; id++)
|
||||||
|
{
|
||||||
|
int mc = basinMinCell[id];
|
||||||
|
if (mc < 0 || basinId[mc] != id) continue;
|
||||||
|
if (basinInflow[id] >= p.EndorheicMinInflowPx) giantsRanked.Add((id, basinInflow[id]));
|
||||||
|
}
|
||||||
|
giantsRanked.Sort((a, b) => b.inflow.CompareTo(a.inflow));
|
||||||
|
|
||||||
|
// Does a terminal basin HOLD classify water? The lake-ender test must look
|
||||||
|
// at the whole pool, not the stem's single pooling cell — a stem can pool on
|
||||||
|
// dry ground a few hundred px short of its lagoon and still be a lagoon river.
|
||||||
|
bool[] basinHasLake = new bool[basinMinCell.Count];
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
if (basinId[i] != 0 && isClassifyWater[i] && !isOcean[i])
|
||||||
|
basinHasLake[basinId[i]] = true;
|
||||||
|
|
||||||
|
// The main stem's ENTRY into the basin: the highest-accumulation cell
|
||||||
|
// whose flow terminates in this basin. On a flat basin floor the deepest
|
||||||
|
// cell sees only local trickles (the task-21 lesson), so the stem is
|
||||||
|
// anchored on the strongest feeder instead.
|
||||||
|
var bestEntry = new Dictionary<int, int>();
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
if (dest[i] <= 0) continue;
|
||||||
|
if (!bestEntry.TryGetValue(dest[i], out int cur) || acc[i] > acc[cur])
|
||||||
|
bestEntry[dest[i]] = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The giant whose pooling point sits closest to the southernmost town is
|
||||||
|
// the SOUTHERN CANDIDATE — always routed provisionally (shown, not forced).
|
||||||
|
int southernPick = -1;
|
||||||
|
if (southX >= 0f)
|
||||||
|
{
|
||||||
|
float bestD = float.MaxValue;
|
||||||
|
foreach (var (id, _) in giantsRanked.GetRange(0, Math.Min(p.GiantCount, giantsRanked.Count)))
|
||||||
|
{
|
||||||
|
int mc = basinMinCell[id];
|
||||||
|
float ddx = mc / n - southX, ddy = mc % n - southY;
|
||||||
|
float d2 = ddx * ddx + ddy * ddy;
|
||||||
|
if (d2 < bestD) { bestD = d2; southernPick = id; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var (id, inflow) in giantsRanked.GetRange(0, Math.Min(p.GiantCount, giantsRanked.Count)))
|
||||||
|
{
|
||||||
|
var g = new Giant { DrainageAreaPx = inflow, BasinDepthM = basinDepthM[id], BasinAreaPx = basinAreaPx[id] };
|
||||||
|
if (!bestEntry.TryGetValue(id, out int entry)) entry = basinMinCell[id];
|
||||||
|
|
||||||
|
// Downstream from the strongest feeder to where it actually pools…
|
||||||
|
int t2 = entry;
|
||||||
|
var down = new List<int> { t2 };
|
||||||
|
while (dir[t2] >= 0) { t2 = Target(t2); down.Add(t2); }
|
||||||
|
g.Terminal = (t2 / n, t2 % n);
|
||||||
|
// …then the full main stem, traced upstream from that pooling point.
|
||||||
|
var stem = TraceStem(t2, p.StemMinAccPx);
|
||||||
|
g.Course = Decimate(stem);
|
||||||
|
g.Head = (stem[^1] / n, stem[^1] % n);
|
||||||
|
g.TerminalInClassifyWater = isClassifyWater[t2];
|
||||||
|
|
||||||
|
for (int i = 0; i + p.ExitWindowPx < stem.Count; i++)
|
||||||
|
{
|
||||||
|
float rise = (original[stem[i + p.ExitWindowPx]] - original[stem[i]]) * M_PER_UNIT;
|
||||||
|
if (rise / p.ExitWindowPx >= p.ExitGradeMin)
|
||||||
|
{
|
||||||
|
g.ExitFound = true;
|
||||||
|
g.MountainExit = (stem[i] / n, stem[i] % n);
|
||||||
|
g.MountainExitElevM = original[stem[i]] * M_PER_UNIT;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lean tributaries on the giant's stem, same junction rule as trunks.
|
||||||
|
var stemSet = new HashSet<int>(stem);
|
||||||
|
var cands = new List<(int cell, long acc)>();
|
||||||
|
foreach (int sc in stem)
|
||||||
|
{
|
||||||
|
int cx = sc / n, cy = sc % n;
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
{
|
||||||
|
int nx = cx + DX[k], ny = cy + DY[k];
|
||||||
|
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||||
|
int ni = nx * n + ny;
|
||||||
|
if (stemSet.Contains(ni)) continue;
|
||||||
|
if (dir[ni] >= 0 && Target(ni) == sc && acc[ni] >= p.TributaryMinAccPx)
|
||||||
|
cands.Add((ni, acc[ni]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cands.Sort((a, b) => b.acc.CompareTo(a.acc));
|
||||||
|
var takenT = new List<int>();
|
||||||
|
foreach (var (cell, _) in cands)
|
||||||
|
{
|
||||||
|
if (takenT.Count >= p.TributaryMaxPerTrunk) break;
|
||||||
|
int cx2 = cell / n, cy2 = cell % n;
|
||||||
|
bool dup = false;
|
||||||
|
foreach (int tc in takenT)
|
||||||
|
{
|
||||||
|
float ddx = cx2 - tc / n, ddy = cy2 - tc % n;
|
||||||
|
if (ddx * ddx + ddy * ddy < 30f * 30f) { dup = true; break; }
|
||||||
|
}
|
||||||
|
if (!dup) takenT.Add(cell);
|
||||||
|
}
|
||||||
|
foreach (int cell in takenT)
|
||||||
|
{
|
||||||
|
var trib = new Stream { DrainageAreaPx = acc[cell] };
|
||||||
|
var ts = TraceStem(cell, Math.Max(p.StemMinAccPx, (int)(acc[cell] / 20)));
|
||||||
|
trib.Course = Decimate(ts);
|
||||||
|
trib.Head = (ts[^1] / n, ts[^1] % n);
|
||||||
|
g.Tributaries.Add(trib);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Kind: the terminal BASIN holds a classify lake → natural lake-ender;
|
||||||
|
// dry pan → route to sea; the southern candidate is always routed.
|
||||||
|
g.SouthernCandidate = id == southernPick;
|
||||||
|
g.TerminalInClassifyWater = g.TerminalInClassifyWater || basinHasLake[id];
|
||||||
|
g.Kind = (basinHasLake[id] && !g.SouthernCandidate) ? "lake-ender" : "routed";
|
||||||
|
|
||||||
|
// PROVISIONAL route (routed giants): walk steepest descent on the FULL
|
||||||
|
// fill from the pooling point — the basin overtops at its spill and
|
||||||
|
// the walk continues along the terrain's own drainage to the ocean.
|
||||||
|
// DRAWN, not carved; part 2 carves along a route like this one.
|
||||||
|
if (g.Kind == "routed")
|
||||||
|
{
|
||||||
|
var route = new List<int>();
|
||||||
|
int c = t2;
|
||||||
|
bool spillRecorded = false;
|
||||||
|
for (int guard = 0; guard < 4 * n; guard++)
|
||||||
|
{
|
||||||
|
route.Add(c);
|
||||||
|
if (isOcean[c]) { g.RouteReachedOcean = true; break; }
|
||||||
|
if (!spillRecorded && basinId[c] != id)
|
||||||
|
{ g.Spill = (c / n, c % n); spillRecorded = true; }
|
||||||
|
int cx = c / n, cy = c % n;
|
||||||
|
float best = float.MaxValue; int bestN = -1;
|
||||||
|
for (int k = 0; k < 8; k++)
|
||||||
|
{
|
||||||
|
int nx = cx + DX[k], ny = cy + DY[k];
|
||||||
|
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||||
|
int ni = nx * n + ny;
|
||||||
|
if (plan_fullFilled[ni] < best) { best = plan_fullFilled[ni]; bestN = ni; }
|
||||||
|
}
|
||||||
|
if (bestN < 0 || plan_fullFilled[bestN] >= plan_fullFilled[c]) break; // stuck (report via flag)
|
||||||
|
c = bestN;
|
||||||
|
}
|
||||||
|
g.ProvisionalRoute = Decimate(route);
|
||||||
|
}
|
||||||
|
plan.Giants.Add(g);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return plan;
|
return plan;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,8 @@ public partial class RiverPlanTool : Node
|
||||||
p.EndorheicMinInflowPx = EnvI("RIVERPLAN_ENDO_MIN_INFLOW_PX", p.EndorheicMinInflowPx);
|
p.EndorheicMinInflowPx = EnvI("RIVERPLAN_ENDO_MIN_INFLOW_PX", p.EndorheicMinInflowPx);
|
||||||
p.EndorheicMaxCount = EnvI("RIVERPLAN_ENDO_MAX_COUNT", p.EndorheicMaxCount);
|
p.EndorheicMaxCount = EnvI("RIVERPLAN_ENDO_MAX_COUNT", p.EndorheicMaxCount);
|
||||||
p.TrunkCount = EnvI("RIVERPLAN_TRUNK_COUNT", p.TrunkCount);
|
p.TrunkCount = EnvI("RIVERPLAN_TRUNK_COUNT", p.TrunkCount);
|
||||||
|
p.TrunkCount = EnvI("RIVERPLAN_OCEAN_N", p.TrunkCount); // 21b alias
|
||||||
|
p.GiantCount = EnvI("RIVERPLAN_GIANT_N", p.GiantCount);
|
||||||
p.MinOutletSeparationPx = EnvI("RIVERPLAN_OUTLET_SEPARATION_PX", p.MinOutletSeparationPx);
|
p.MinOutletSeparationPx = EnvI("RIVERPLAN_OUTLET_SEPARATION_PX", p.MinOutletSeparationPx);
|
||||||
p.StemMinAccPx = EnvI("RIVERPLAN_STEM_MIN_ACC_PX", p.StemMinAccPx);
|
p.StemMinAccPx = EnvI("RIVERPLAN_STEM_MIN_ACC_PX", p.StemMinAccPx);
|
||||||
p.TributaryMinAccPx = EnvI("RIVERPLAN_TRIB_MIN_ACC_PX", p.TributaryMinAccPx);
|
p.TributaryMinAccPx = EnvI("RIVERPLAN_TRIB_MIN_ACC_PX", p.TributaryMinAccPx);
|
||||||
|
|
@ -81,12 +83,22 @@ public partial class RiverPlanTool : Node
|
||||||
// destinations that make a trunk "sea-reaching".
|
// destinations that make a trunk "sea-reaching".
|
||||||
int nn = bp.MapSize;
|
int nn = bp.MapSize;
|
||||||
bool[] isOcean = new bool[nn * nn];
|
bool[] isOcean = new bool[nn * nn];
|
||||||
|
bool[] isClassifyWater = new bool[nn * nn];
|
||||||
for (int x = 0; x < nn; x++)
|
for (int x = 0; x < nn; x++)
|
||||||
for (int y = 0; y < nn; y++)
|
for (int y = 0; y < nn; y++)
|
||||||
isOcean[x * nn + y] = bp.WaterBodyIds[x, y] == 1;
|
{
|
||||||
|
ushort wb = bp.WaterBodyIds[x, y];
|
||||||
|
isOcean[x * nn + y] = wb == 1;
|
||||||
|
isClassifyWater[x * nn + y] = wb != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Southernmost town — the 21b SOUTHERN CANDIDATE anchor (shown, not forced).
|
||||||
|
float southX = -1f, southY = -1f;
|
||||||
|
foreach (var t in bp.Towns)
|
||||||
|
if (t.Position.Y > southY) { southX = t.Position.X; southY = t.Position.Y; }
|
||||||
|
|
||||||
var p = ReadParams();
|
var p = ReadParams();
|
||||||
var plan = DrainageAnalysis.Run(bp.HeightMap, bp.MapSize, isOcean, p);
|
var plan = DrainageAnalysis.Run(bp.HeightMap, bp.MapSize, isOcean, isClassifyWater, southX, southY, p);
|
||||||
ulong t2 = Time.GetTicksMsec();
|
ulong t2 = Time.GetTicksMsec();
|
||||||
GD.Print($"[RiverPlan] analysis in {(t2 - t1) / 1000.0:F1}s.");
|
GD.Print($"[RiverPlan] analysis in {(t2 - t1) / 1000.0:F1}s.");
|
||||||
|
|
||||||
|
|
@ -123,6 +135,22 @@ public partial class RiverPlanTool : Node
|
||||||
(wb > 1 ? $" — terminates IN classify lake/lagoon WBID {wb} (river-feeds-lake)" : " — dry closed basin") + ".");
|
(wb > 1 ? $" — terminates IN classify lake/lagoon WBID {wb} (river-feeds-lake)" : " — dry closed basin") + ".");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- 21b: the promoted giants ----
|
||||||
|
int gi = 0;
|
||||||
|
foreach (var g in plan.Giants)
|
||||||
|
{
|
||||||
|
gi++;
|
||||||
|
GD.Print($"[RiverPlan] GIANT {gi} [{g.Kind.ToUpper()}{(g.SouthernCandidate ? " — SOUTHERN CANDIDATE" : "")}]: " +
|
||||||
|
$"drainage {g.DrainageAreaPx} px, pools at ({g.Terminal.x:F0},{g.Terminal.y:F0}) " +
|
||||||
|
$"({(g.TerminalInClassifyWater ? "in classify water" : "dry pan")}, basin {g.BasinDepthM:F1} m / {g.BasinAreaPx} px), " +
|
||||||
|
(g.ExitFound ? $"mountain-exit ({g.MountainExit.x:F0},{g.MountainExit.y:F0}) at {g.MountainExitElevM:F0} m, " : "") +
|
||||||
|
$"{g.Tributaries.Count} tributaries" +
|
||||||
|
(g.ProvisionalRoute != null
|
||||||
|
? $"; PROVISIONAL route {g.ProvisionalRoute.Count * 4} px via spill ({g.Spill.x:F0},{g.Spill.y:F0}) " +
|
||||||
|
(g.RouteReachedOcean ? "-> reaches the OCEAN" : "-> DID NOT reach the ocean (walk stuck — report)")
|
||||||
|
: "; ends at its lake") + ".");
|
||||||
|
}
|
||||||
|
|
||||||
// ---- the southern-town report (filed fact, not a constraint) ----
|
// ---- the southern-town report (filed fact, not a constraint) ----
|
||||||
if (bp.Towns.Count > 0)
|
if (bp.Towns.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -209,6 +237,39 @@ public partial class RiverPlanTool : Node
|
||||||
if (i < plan.Trunks.Count - 1) sb.Append(',');
|
if (i < plan.Trunks.Count - 1) sb.Append(',');
|
||||||
sb.Append('\n');
|
sb.Append('\n');
|
||||||
}
|
}
|
||||||
|
sb.Append("],\n\"giants\": [\n");
|
||||||
|
for (int i = 0; i < plan.Giants.Count; i++)
|
||||||
|
{
|
||||||
|
var g = plan.Giants[i];
|
||||||
|
sb.Append(" {\"kind\": \"").Append(g.Kind).Append("\", ");
|
||||||
|
sb.Append($"\"southernCandidate\": {(g.SouthernCandidate ? "true" : "false")}, ");
|
||||||
|
sb.Append($"\"drainageAreaPx\": {g.DrainageAreaPx}, ");
|
||||||
|
sb.Append("\"terminal\": "); Pt(sb, g.Terminal);
|
||||||
|
sb.Append($", \"terminalInClassifyWater\": {(g.TerminalInClassifyWater ? "true" : "false")}, ");
|
||||||
|
sb.Append($"\"basinDepthM\": {g.BasinDepthM.ToString("F2", ci)}, \"basinAreaPx\": {g.BasinAreaPx}, ");
|
||||||
|
sb.Append($"\"exitFound\": {(g.ExitFound ? "true" : "false")}, \"mountainExit\": "); Pt(sb, g.MountainExit);
|
||||||
|
sb.Append($", \"mountainExitElevM\": {g.MountainExitElevM.ToString("F1", ci)},\n \"course\": ");
|
||||||
|
Course(g.Course);
|
||||||
|
if (g.ProvisionalRoute != null)
|
||||||
|
{
|
||||||
|
sb.Append(",\n \"spill\": "); Pt(sb, g.Spill);
|
||||||
|
sb.Append($", \"routeReachedOcean\": {(g.RouteReachedOcean ? "true" : "false")}");
|
||||||
|
sb.Append(",\n \"provisionalRoute_NOT_WATER\": ");
|
||||||
|
Course(g.ProvisionalRoute);
|
||||||
|
}
|
||||||
|
sb.Append(",\n \"tributaries\": [");
|
||||||
|
for (int j = 0; j < g.Tributaries.Count; j++)
|
||||||
|
{
|
||||||
|
var tr = g.Tributaries[j];
|
||||||
|
if (j > 0) sb.Append(',');
|
||||||
|
sb.Append($"\n {{\"drainageAreaPx\": {tr.DrainageAreaPx}, \"course\": ");
|
||||||
|
Course(tr.Course);
|
||||||
|
sb.Append('}');
|
||||||
|
}
|
||||||
|
sb.Append("]\n }");
|
||||||
|
if (i < plan.Giants.Count - 1) sb.Append(',');
|
||||||
|
sb.Append('\n');
|
||||||
|
}
|
||||||
sb.Append("],\n\"endorheics\": [");
|
sb.Append("],\n\"endorheics\": [");
|
||||||
for (int i = 0; i < plan.Endorheics.Count; i++)
|
for (int i = 0; i < plan.Endorheics.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue