diff --git a/server/src/atlas/body_world_state.rs b/server/src/atlas/body_world_state.rs index b3d17dcff..8a0e8be32 100644 --- a/server/src/atlas/body_world_state.rs +++ b/server/src/atlas/body_world_state.rs @@ -54,8 +54,57 @@ pub struct RiverNetwork { /// empty, never a decode error (the additive T-1124 §1 pattern). #[serde(default)] pub river_class: Vec, + /// Per-`river_cells`-entry D8 downstream pointer (T-1170 Ruling 2a) — same + /// index, same length as `river_cells`/`river_class`. Each river cell has + /// exactly one downstream direction, so this is the exact same shape as + /// `river_class`, captured in `extract_river_network` where `fdir[i]` is + /// already in scope (one `.map()`, no new grid pass). + /// + /// **Values 0–7:** an index into `drainage::D8` — the downstream neighbor + /// direction, i.e. "this river cell flows toward `D8[value]`". + /// + /// **Sentinel values above 7 (Ruling 2c):** + /// - [`RIVER_DOWNSTREAM_MOUTH`] — flow reaches a raw-sea cell (the river's + /// mouth in the D8 sense; T-1170's course inventor walks stations from + /// here to the invented-coast terminus, Ruling 3e). + /// - [`RIVER_DOWNSTREAM_EDGE_DRAIN`] — flow exits the grid's top/bottom + /// edge. This is a **grid artifact, not a mouth** (Ruling 3f) — pole-edge + /// exits are deliberately excluded from `mouths` at extraction (see + /// `extract_river_network`'s doc). + /// - [`RIVER_DOWNSTREAM_TERMINAL`] — **reserved, unused in round 1.** Flow + /// ends in an interior sink (future endorheic basin / inland delta, + /// Ruling 7b). Reserving the value now keeps that future path additive + /// (no wire migration) rather than requiring a new sentinel later. + /// + /// Why persist rather than reconstruct from adjacency alone (Ruling 2b): + /// at a 3-river-neighbor confluence, adjacency cannot distinguish inflow + /// from outflow, and re-deriving direction from elevation is re-running D8 + /// badly — the true answer (`fdir[i]`) is already computed and in scope at + /// extraction time; discarding it and re-deriving later is strictly worse + /// than keeping the ~1 byte/river-cell projection. The former D-203 + /// memory-frugality rationale for discarding covered the 131 KB *full* + /// `fdir` grid, not this per-river-cell projection. + /// + /// `#[serde(default)]` — same additive pattern as `river_class`: an absent + /// array (pre-T-1170 payload/fixture) decodes to empty, never an error. + #[serde(default)] + pub river_downstream: Vec, } +/// [`RiverNetwork::river_downstream`] sentinel: this river cell's D8 flow +/// reaches a raw-sea cell (T-1170 Ruling 2c). Values 0–7 are real D8 direction +/// indices, so sentinels start at 8. +pub const RIVER_DOWNSTREAM_MOUTH: u8 = 8; +/// [`RiverNetwork::river_downstream`] sentinel: this river cell's flow exits +/// the grid's top/bottom (polar) edge — a grid artifact, never a mouth +/// (T-1170 Ruling 2c/3f). +pub const RIVER_DOWNSTREAM_EDGE_DRAIN: u8 = 9; +/// [`RiverNetwork::river_downstream`] sentinel: **reserved, unused in round +/// 1.** Flow terminates in an interior sink (future endorheic basin / inland +/// delta, T-1170 Ruling 2c/7b). Reserving this value now is what makes that +/// future extension additive rather than a wire migration. +pub const RIVER_DOWNSTREAM_TERMINAL: u8 = 10; + /// One drainage basin / province derived from watershed analysis (D-205). /// Stub — boundary polyline data comes from atlas_province_boundaries. #[derive(Debug, Clone, Serialize, Deserialize)] diff --git a/server/src/atlas/drainage.rs b/server/src/atlas/drainage.rs index 16fc278e5..837906baa 100644 --- a/server/src/atlas/drainage.rs +++ b/server/src/atlas/drainage.rs @@ -19,7 +19,9 @@ use std::collections::VecDeque; -use crate::atlas::body_world_state::{DrainageBasin, RiverNetwork}; +use crate::atlas::body_world_state::{ + DrainageBasin, RiverNetwork, RIVER_DOWNSTREAM_EDGE_DRAIN, RIVER_DOWNSTREAM_MOUTH, +}; use crate::simulation::generator::TerritorialStatus; /// A cell is a river cell when its flow accumulation exceeds this threshold (D-208). @@ -316,28 +318,53 @@ fn extract_river_network( .map(|i| ((i / w) as u16, (i % w) as u16)) .collect(); - // Mouths: river cells that flow to a sea cell or to the polar edge. - let mouths: Vec<(u16, u16)> = (0..n) - .filter(|&i| { - if !is_river[i] { - return false; - } + // Mouths + river_downstream (T-1170 Ruling 2a/2c/3f): a single pass over + // `river_cells`, in the SAME order, computing both the D8-downstream + // sentinel/pointer AND whether this cell is a mouth. `fdir[i]` is already + // in scope here — capturing it as `river_downstream` costs one extra push + // per river cell, no new grid pass (Ruling 2a's binding requirement). + // + // **Pole-edge exits are NOT mouths (Ruling 3f, binding).** A river cell + // with no outflow because its D8 walk ran off the grid's top/bottom row + // is a grid artifact — the equirectangular projection simply stops there, + // there is no sea. The former code classified this the same as a real + // sea-adjacent mouth, which rendered double-ring mouth markers in polar + // ice with no sea in sight (Jeroen's capture question, T-1170 ticket). + // `RIVER_DOWNSTREAM_EDGE_DRAIN` cells are excluded from `mouths` here; + // T-1170's course inventor (Ruling 3f) ends their course geometry at the + // last in-grid station with no mouth flag. + // + // A flat-peak interior cell (no outflow, but NOT at a pole row) is D8's + // other `k < 0` case — vanishingly rare for a cell that also cleared + // `RIVER_THRESHOLD`, but handled the same as `EDGE_DRAIN` (no downstream + // neighbor to point at, not a sea mouth) rather than crashing the + // pointer's "always points somewhere real" contract. + let mut mouths: Vec<(u16, u16)> = Vec::new(); + let river_downstream: Vec = (0..n) + .filter(|&i| is_river[i]) + .map(|i| { let r = i / w; let c = i % w; let k = fdir[i]; if k < 0 { - return true; // no outflow — edge + // No outflow at all — edge/flat-peak. Not a mouth (Ruling 3f). + return RIVER_DOWNSTREAM_EDGE_DRAIN; } let (dr, dc) = D8[k as usize]; let nr = r as i32 + dr; let nc = (c as i32 + dc).rem_euclid(w as i32) as usize; if nr < 0 || nr >= h as i32 { - return true; // polar edge + // Flow direction points off the polar edge — a grid artifact, + // not a mouth (Ruling 3f, the pole-edge-drain fix). + return RIVER_DOWNSTREAM_EDGE_DRAIN; } - // Flows into a sub-sea-level cell = mouth - elevation[nr as usize * w + nc] < sea_level + if elevation[nr as usize * w + nc] < sea_level { + // Flows into a sub-sea-level cell — a real mouth. + mouths.push((r as u16, c as u16)); + return RIVER_DOWNSTREAM_MOUTH; + } + k as u8 }) - .map(|i| ((i / w) as u16, (i % w) as u16)) .collect(); RiverNetwork { @@ -345,6 +372,7 @@ fn extract_river_network( confluences, mouths, river_class, + river_downstream, } } @@ -1040,4 +1068,121 @@ mod tests { "the body's max river-cell accumulation should be trunk" ); } + + // ----------------------------------------------------------------------- + // river_downstream (T-1170 Ruling 2a/2c/3f) + // ----------------------------------------------------------------------- + + #[test] + fn river_downstream_parallel_to_river_cells() { + let elev = slope_grid(512, 256); + let result = analyze(&elev, 512, 256, 0.3); + let rn = &result.river_network; + assert_eq!( + rn.river_cells.len(), + rn.river_downstream.len(), + "river_downstream must be parallel/aligned with river_cells" + ); + assert!(!rn.river_cells.is_empty()); + } + + #[test] + fn river_downstream_values_are_direction_or_sentinel() { + // Every entry is either a real D8 index (0-7) or one of the T-1170 + // sentinels (MOUTH=8, EDGE_DRAIN=9); TERMINAL=10 is reserved/unused. + let elev = slope_grid(512, 256); + let result = analyze(&elev, 512, 256, 0.3); + for &v in &result.river_network.river_downstream { + assert!( + v <= RIVER_DOWNSTREAM_EDGE_DRAIN, + "unexpected river_downstream value {v} (round-1 only emits 0-7, MOUTH=8, \ + EDGE_DRAIN=9 — TERMINAL=10 is reserved and unused)" + ); + } + } + + #[test] + fn river_downstream_mouth_sentinel_matches_mouths_list() { + // Every river cell whose river_downstream is MOUTH must appear in + // `mouths`, and every entry of `mouths` must have MOUTH as its + // river_downstream — the two are the same underlying classification, + // captured in the same pass (Ruling 2a/3f). + use crate::atlas::heightmap::load_heightmap_png; + let src = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")) + .join("../wiki/star-systems/GJ-1/bodies/GJ1c/heightmap.png"); + let heightmap = + load_heightmap_png(&src, "GJ1c", 0.3).expect("decode committed GJ1c heightmap"); + let small = heightmap.downsample(256, 128); + let result = analyze(&small.data, small.width, small.height, small.sea_level); + let rn = &result.river_network; + assert!(!rn.mouths.is_empty(), "GJ1c should have real sea mouths"); + + let mouth_set: std::collections::BTreeSet<(u16, u16)> = + rn.mouths.iter().copied().collect(); + for (i, &pos) in rn.river_cells.iter().enumerate() { + let is_mouth_sentinel = rn.river_downstream[i] == RIVER_DOWNSTREAM_MOUTH; + let is_in_mouths_list = mouth_set.contains(&pos); + assert_eq!( + is_mouth_sentinel, is_in_mouths_list, + "cell {pos:?}: MOUTH sentinel ({is_mouth_sentinel}) must agree with \ + mouths-list membership ({is_in_mouths_list})" + ); + } + } + + #[test] + fn pole_edge_drains_are_not_mouths() { + // Ruling 3f, binding: a river that flows off the grid's polar edge is + // a grid artifact, not a river-meets-sea event. Build a tiny grid that + // slopes toward the north pole (row 0) with no ocean anywhere, so any + // river cell reaching row 0 must exit via EDGE_DRAIN, never MOUTH — + // and must never land in `mouths`. + let (w, h) = (16usize, 32usize); + let n = w * h; + // Slope: elevation decreases toward row 0 (the north pole), giving a + // clean, deterministic downhill flow off the top edge. sea_level below + // everything so no cell is ever ocean. + let elev: Vec = (0..n) + .map(|i| { + let r = i / w; + 0.2 + (r as f32 / h as f32) * 0.7 + }) + .collect(); + let result = analyze(&elev, w as u32, h as u32, 0.0); + let rn = &result.river_network; + if rn.river_cells.is_empty() { + // Too small a grid to clear RIVER_THRESHOLD — nothing to assert, + // but not a test failure (the threshold is a fixed constant this + // synthetic tiny grid isn't guaranteed to reach). + return; + } + assert!( + rn.mouths.is_empty(), + "an all-land, pole-draining world must have zero mouths — got {:?}", + rn.mouths + ); + assert!( + rn.river_downstream + .iter() + .any(|&v| v == RIVER_DOWNSTREAM_EDGE_DRAIN), + "expected at least one EDGE_DRAIN-sentinel river cell on a pole-draining world" + ); + assert!( + !rn.river_downstream + .iter() + .any(|&v| v == RIVER_DOWNSTREAM_MOUTH), + "an all-land world must never emit a MOUTH sentinel" + ); + } + + #[test] + fn river_downstream_deterministic() { + let elev = slope_grid(64, 32); + let r1 = analyze(&elev, 64, 32, 0.3); + let r2 = analyze(&elev, 64, 32, 0.3); + assert_eq!( + r1.river_network.river_downstream, r2.river_network.river_downstream, + "river_downstream must be deterministic (D-010/D-208)" + ); + } } diff --git a/server/tests/cascade_golden.rs b/server/tests/cascade_golden.rs index 271acf8c6..acd7c32e0 100644 --- a/server/tests/cascade_golden.rs +++ b/server/tests/cascade_golden.rs @@ -18,6 +18,18 @@ //! Golden captured on x86_64. The downsample and sub-biome cost use f32, so a //! different architecture could in principle round differently — regenerate //! per-arch if CI ever moves off x86_64. +//! +//! **Deliberate re-pin (T-1170 Ruling 2a/2c/3f, A1):** `RiverNetwork` gained +//! an additive `river_downstream: Vec` field (per-`river_cells`-entry D8 +//! downstream pointer + MOUTH/EDGE_DRAIN sentinel), and `extract_river_network` +//! stopped classifying pole-edge D8 exits (flow running off the grid's +//! top/bottom row) as `mouths` — they are grid artifacts, not river-meets-sea +//! events (Ruling 3f, Jeroen's capture question). On this fixture (GJ1c, +//! 256×128 downsample) that drops `mouths` from 19 to 3: 16 of the 19 were +//! pole-edge exits (now `RIVER_DOWNSTREAM_EDGE_DRAIN`), leaving the 3 real +//! sea-adjacent mouths (`RIVER_DOWNSTREAM_MOUTH`). `river_cells`/`attractors` +//! counts are unchanged (93/256) — this is a pure re-classification + one new +//! additive field, not a drainage-algorithm change. use std::path::PathBuf; diff --git a/server/tests/golden/cascade_layer1.json b/server/tests/golden/cascade_layer1.json index 434b7e1a1..cdfcc0e37 100644 --- a/server/tests/golden/cascade_layer1.json +++ b/server/tests/golden/cascade_layer1.json @@ -5,138 +5,6 @@ ], "layer1": { "attractors": [ - { - "attractor_type": "RiverMouth", - "position": [ - 0, - 100 - ], - "strength": 20, - "sub_biome": "Tundra", - "terrain_modification_cost": 162, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 0, - 142 - ], - "strength": 14, - "sub_biome": "Tundra", - "terrain_modification_cost": 165, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 0, - 197 - ], - "strength": 13, - "sub_biome": "Tundra", - "terrain_modification_cost": 166, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 0, - 241 - ], - "strength": 23, - "sub_biome": "Tundra", - "terrain_modification_cost": 166, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 11, - 65 - ], - "strength": 12, - "sub_biome": "Alpine", - "terrain_modification_cost": 384, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 12, - 232 - ], - "strength": 15, - "sub_biome": "Tundra", - "terrain_modification_cost": 166, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 12, - 238 - ], - "strength": 23, - "sub_biome": "Tundra", - "terrain_modification_cost": 163, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 15, - 141 - ], - "strength": 22, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 150, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 20, - 85 - ], - "strength": 24, - "sub_biome": "BorealForest", - "terrain_modification_cost": 158, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 25, - 100 - ], - "strength": 22, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 146, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 28, - 14 - ], - "strength": 34, - "sub_biome": "TemperateForest", - "terrain_modification_cost": 150, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 28, - 181 - ], - "strength": 20, - "sub_biome": "Wetland", - "terrain_modification_cost": 327, - "water_bearing": 360 - }, { "attractor_type": "RiverMouth", "position": [ @@ -159,50 +27,6 @@ "terrain_modification_cost": 348, "water_bearing": 360 }, - { - "attractor_type": "RiverMouth", - "position": [ - 50, - 226 - ], - "strength": 13, - "sub_biome": "Wetland", - "terrain_modification_cost": 325, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 50, - 254 - ], - "strength": 16, - "sub_biome": "Wetland", - "terrain_modification_cost": 324, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 51, - 230 - ], - "strength": 25, - "sub_biome": "Wetland", - "terrain_modification_cost": 322, - "water_bearing": 360 - }, - { - "attractor_type": "RiverMouth", - "position": [ - 88, - 217 - ], - "strength": 13, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 142, - "water_bearing": 360 - }, { "attractor_type": "RiverMouth", "position": [ @@ -487,7 +311,7 @@ "strength": 90, "sub_biome": "Wetland", "terrain_modification_cost": 325, - "water_bearing": 0 + "water_bearing": 180 }, { "attractor_type": "CoastalAccess", @@ -908,67 +732,78 @@ "water_bearing": 0 }, { - "attractor_type": "ValleyFloor", + "attractor_type": "CoastalAccess", "position": [ - 0, - 101 + 111, + 100 ], - "strength": 87, - "sub_biome": "Tundra", - "terrain_modification_cost": 162, - "water_bearing": 270 + "strength": 90, + "sub_biome": "Wetland", + "terrain_modification_cost": 327, + "water_bearing": 0 + }, + { + "attractor_type": "CoastalAccess", + "position": [ + 113, + 238 + ], + "strength": 90, + "sub_biome": "Wetland", + "terrain_modification_cost": 331, + "water_bearing": 0 + }, + { + "attractor_type": "CoastalAccess", + "position": [ + 115, + 51 + ], + "strength": 90, + "sub_biome": "Wetland", + "terrain_modification_cost": 366, + "water_bearing": 0 + }, + { + "attractor_type": "CoastalAccess", + "position": [ + 115, + 67 + ], + "strength": 90, + "sub_biome": "Wetland", + "terrain_modification_cost": 326, + "water_bearing": 0 + }, + { + "attractor_type": "CoastalAccess", + "position": [ + 116, + 79 + ], + "strength": 90, + "sub_biome": "Wetland", + "terrain_modification_cost": 323, + "water_bearing": 315 }, { "attractor_type": "ValleyFloor", "position": [ - 0, - 141 - ], - "strength": 89, - "sub_biome": "Tundra", - "terrain_modification_cost": 164, - "water_bearing": 90 - }, - { - "attractor_type": "ValleyFloor", - "position": [ - 0, - 198 + 16, + 143 ], "strength": 86, - "sub_biome": "Tundra", - "terrain_modification_cost": 164, - "water_bearing": 270 - }, - { - "attractor_type": "ValleyFloor", - "position": [ - 15, - 140 - ], - "strength": 94, "sub_biome": "BorealForest", - "terrain_modification_cost": 159, - "water_bearing": 90 + "terrain_modification_cost": 157, + "water_bearing": 180 }, { "attractor_type": "ValleyFloor", "position": [ - 20, - 86 + 18, + 109 ], - "strength": 91, - "sub_biome": "BorealForest", - "terrain_modification_cost": 154, - "water_bearing": 270 - }, - { - "attractor_type": "ValleyFloor", - "position": [ - 23, - 182 - ], - "strength": 88, + "strength": 82, "sub_biome": "BorealForest", "terrain_modification_cost": 158, "water_bearing": 180 @@ -976,13 +811,35 @@ { "attractor_type": "ValleyFloor", "position": [ - 24, - 101 + 20, + 86 ], - "strength": 98, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 146, - "water_bearing": 225 + "strength": 80, + "sub_biome": "BorealForest", + "terrain_modification_cost": 154, + "water_bearing": 90 + }, + { + "attractor_type": "ValleyFloor", + "position": [ + 23, + 168 + ], + "strength": 76, + "sub_biome": "BorealForest", + "terrain_modification_cost": 171, + "water_bearing": 135 + }, + { + "attractor_type": "ValleyFloor", + "position": [ + 23, + 182 + ], + "strength": 80, + "sub_biome": "BorealForest", + "terrain_modification_cost": 158, + "water_bearing": 180 }, { "attractor_type": "ValleyFloor", @@ -1001,10 +858,10 @@ 27, 16 ], - "strength": 89, + "strength": 87, "sub_biome": "TemperateForest", "terrain_modification_cost": 135, - "water_bearing": 270 + "water_bearing": 180 }, { "attractor_type": "ValleyFloor", @@ -1017,6 +874,28 @@ "terrain_modification_cost": 143, "water_bearing": 270 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 30, + 37 + ], + "strength": 83, + "sub_biome": "TemperateForest", + "terrain_modification_cost": 138, + "water_bearing": 90 + }, + { + "attractor_type": "ValleyFloor", + "position": [ + 30, + 98 + ], + "strength": 95, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 148, + "water_bearing": 180 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1072,17 +951,6 @@ "terrain_modification_cost": 155, "water_bearing": 180 }, - { - "attractor_type": "ValleyFloor", - "position": [ - 36, - 94 - ], - "strength": 85, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 146, - "water_bearing": 90 - }, { "attractor_type": "ValleyFloor", "position": [ @@ -1149,16 +1017,27 @@ "terrain_modification_cost": 153, "water_bearing": 90 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 45, + 229 + ], + "strength": 83, + "sub_biome": "TemperateForest", + "terrain_modification_cost": 145, + "water_bearing": 270 + }, { "attractor_type": "ValleyFloor", "position": [ 45, 253 ], - "strength": 94, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 148, - "water_bearing": 180 + "strength": 92, + "sub_biome": "TemperateForest", + "terrain_modification_cost": 138, + "water_bearing": 90 }, { "attractor_type": "ValleyFloor", @@ -1171,17 +1050,6 @@ "terrain_modification_cost": 147, "water_bearing": 135 }, - { - "attractor_type": "ValleyFloor", - "position": [ - 46, - 231 - ], - "strength": 92, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 157, - "water_bearing": 180 - }, { "attractor_type": "ValleyFloor", "position": [ @@ -1204,6 +1072,17 @@ "terrain_modification_cost": 204, "water_bearing": 225 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 48, + 241 + ], + "strength": 88, + "sub_biome": "TropicalWet", + "terrain_modification_cost": 213, + "water_bearing": 90 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1240,13 +1119,24 @@ { "attractor_type": "ValleyFloor", "position": [ - 58, - 228 + 52, + 191 ], - "strength": 94, + "strength": 84, + "sub_biome": "Wetland", + "terrain_modification_cost": 325, + "water_bearing": 180 + }, + { + "attractor_type": "ValleyFloor", + "position": [ + 58, + 227 + ], + "strength": 90, "sub_biome": "TropicalWet", - "terrain_modification_cost": 203, - "water_bearing": 0 + "terrain_modification_cost": 205, + "water_bearing": 270 }, { "attractor_type": "ValleyFloor", @@ -1270,6 +1160,17 @@ "terrain_modification_cost": 144, "water_bearing": 135 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 62, + 121 + ], + "strength": 80, + "sub_biome": "TropicalWet", + "terrain_modification_cost": 221, + "water_bearing": 90 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1369,6 +1270,17 @@ "terrain_modification_cost": 158, "water_bearing": 270 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 77, + 205 + ], + "strength": 80, + "sub_biome": "TropicalWet", + "terrain_modification_cost": 236, + "water_bearing": 270 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1383,34 +1295,45 @@ { "attractor_type": "ValleyFloor", "position": [ - 86, - 203 + 88, + 148 ], - "strength": 90, + "strength": 84, "sub_biome": "Wetland", - "terrain_modification_cost": 328, - "water_bearing": 225 + "terrain_modification_cost": 332, + "water_bearing": 45 }, { "attractor_type": "ValleyFloor", "position": [ - 86, - 215 + 89, + 207 ], - "strength": 98, + "strength": 96, "sub_biome": "CoastalLowland", - "terrain_modification_cost": 144, - "water_bearing": 135 + "terrain_modification_cost": 145, + "water_bearing": 270 }, { "attractor_type": "ValleyFloor", "position": [ 92, - 227 + 225 ], - "strength": 86, - "sub_biome": "Wetland", - "terrain_modification_cost": 347, + "strength": 94, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 152, + "water_bearing": 90 + }, + { + "attractor_type": "ValleyFloor", + "position": [ + 99, + 87 + ], + "strength": 84, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 142, "water_bearing": 180 }, { @@ -1534,6 +1457,17 @@ "terrain_modification_cost": 143, "water_bearing": 0 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 127, + 13 + ], + "strength": 83, + "sub_biome": "Wetland", + "terrain_modification_cost": 336, + "water_bearing": 0 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1567,6 +1501,17 @@ "terrain_modification_cost": 145, "water_bearing": 315 }, + { + "attractor_type": "ValleyFloor", + "position": [ + 127, + 212 + ], + "strength": 84, + "sub_biome": "Tundra", + "terrain_modification_cost": 164, + "water_bearing": 270 + }, { "attractor_type": "ValleyFloor", "position": [ @@ -1598,7 +1543,7 @@ "strength": 46, "sub_biome": "Tundra", "terrain_modification_cost": 162, - "water_bearing": 0 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1609,7 +1554,7 @@ "strength": 49, "sub_biome": "Tundra", "terrain_modification_cost": 163, - "water_bearing": 90 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1620,7 +1565,7 @@ "strength": 1, "sub_biome": "Alpine", "terrain_modification_cost": 389, - "water_bearing": 90 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1631,7 +1576,7 @@ "strength": 34, "sub_biome": "Tundra", "terrain_modification_cost": 165, - "water_bearing": 0 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1642,7 +1587,7 @@ "strength": 13, "sub_biome": "Alpine", "terrain_modification_cost": 382, - "water_bearing": 270 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1653,7 +1598,7 @@ "strength": 29, "sub_biome": "Tundra", "terrain_modification_cost": 162, - "water_bearing": 0 + "water_bearing": 90 }, { "attractor_type": "PassEntrance", @@ -1664,7 +1609,7 @@ "strength": 35, "sub_biome": "Tundra", "terrain_modification_cost": 163, - "water_bearing": 90 + "water_bearing": 135 }, { "attractor_type": "PassEntrance", @@ -1686,7 +1631,7 @@ "strength": 6, "sub_biome": "Alpine", "terrain_modification_cost": 389, - "water_bearing": 225 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1730,7 +1675,7 @@ "strength": 42, "sub_biome": "BorealForest", "terrain_modification_cost": 155, - "water_bearing": 90 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1752,7 +1697,7 @@ "strength": 18, "sub_biome": "Alpine", "terrain_modification_cost": 387, - "water_bearing": 90 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1774,7 +1719,7 @@ "strength": 34, "sub_biome": "TemperateForest", "terrain_modification_cost": 134, - "water_bearing": 0 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1796,7 +1741,7 @@ "strength": 14, "sub_biome": "Alpine", "terrain_modification_cost": 386, - "water_bearing": 0 + "water_bearing": 225 }, { "attractor_type": "PassEntrance", @@ -1807,7 +1752,7 @@ "strength": 49, "sub_biome": "TemperateForest", "terrain_modification_cost": 164, - "water_bearing": 315 + "water_bearing": 180 }, { "attractor_type": "PassEntrance", @@ -1862,7 +1807,7 @@ "strength": 36, "sub_biome": "TemperateForest", "terrain_modification_cost": 135, - "water_bearing": 225 + "water_bearing": 90 }, { "attractor_type": "PassEntrance", @@ -2139,92 +2084,37 @@ "terrain_modification_cost": 332, "water_bearing": 225 }, - { - "attractor_type": "PlainCenter", - "position": [ - 0, - 99 - ], - "strength": 35, - "sub_biome": "Tundra", - "terrain_modification_cost": 162, - "water_bearing": 90 - }, - { - "attractor_type": "PlainCenter", - "position": [ - 0, - 143 - ], - "strength": 35, - "sub_biome": "Tundra", - "terrain_modification_cost": 167, - "water_bearing": 270 - }, - { - "attractor_type": "PlainCenter", - "position": [ - 0, - 196 - ], - "strength": 34, - "sub_biome": "Tundra", - "terrain_modification_cost": 165, - "water_bearing": 90 - }, - { - "attractor_type": "PlainCenter", - "position": [ - 4, - 131 - ], - "strength": 33, - "sub_biome": "Tundra", - "terrain_modification_cost": 162, - "water_bearing": 180 - }, { "attractor_type": "PlainCenter", "position": [ 16, 142 ], - "strength": 38, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 150, - "water_bearing": 315 - }, - { - "attractor_type": "PlainCenter", - "position": [ - 18, - 182 - ], - "strength": 34, + "strength": 35, "sub_biome": "BorealForest", - "terrain_modification_cost": 155, - "water_bearing": 180 + "terrain_modification_cost": 160, + "water_bearing": 225 }, { "attractor_type": "PlainCenter", "position": [ - 21, - 85 + 20, + 87 ], - "strength": 36, + "strength": 32, "sub_biome": "BorealForest", "terrain_modification_cost": 157, - "water_bearing": 0 + "water_bearing": 135 }, { "attractor_type": "PlainCenter", "position": [ - 24, - 100 + 23, + 181 ], - "strength": 39, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 146, + "strength": 32, + "sub_biome": "BorealForest", + "terrain_modification_cost": 160, "water_bearing": 180 }, { @@ -2244,10 +2134,21 @@ 27, 17 ], - "strength": 36, + "strength": 35, "sub_biome": "TemperateForest", "terrain_modification_cost": 133, - "water_bearing": 270 + "water_bearing": 180 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 29, + 100 + ], + "strength": 38, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 146, + "water_bearing": 180 }, { "attractor_type": "PlainCenter", @@ -2271,17 +2172,6 @@ "terrain_modification_cost": 138, "water_bearing": 90 }, - { - "attractor_type": "PlainCenter", - "position": [ - 31, - 182 - ], - "strength": 38, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 151, - "water_bearing": 0 - }, { "attractor_type": "PlainCenter", "position": [ @@ -2330,12 +2220,23 @@ "attractor_type": "PlainCenter", "position": [ 35, - 197 + 187 ], - "strength": 37, + "strength": 38, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 153, + "water_bearing": 180 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 35, + 199 + ], + "strength": 36, "sub_biome": "CoastalLowland", "terrain_modification_cost": 156, - "water_bearing": 135 + "water_bearing": 180 }, { "attractor_type": "PlainCenter", @@ -2398,21 +2299,10 @@ 44, 253 ], - "strength": 37, + "strength": 36, "sub_biome": "TemperateForest", "terrain_modification_cost": 137, - "water_bearing": 180 - }, - { - "attractor_type": "PlainCenter", - "position": [ - 45, - 229 - ], - "strength": 35, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 155, - "water_bearing": 180 + "water_bearing": 90 }, { "attractor_type": "PlainCenter", @@ -2447,6 +2337,50 @@ "terrain_modification_cost": 147, "water_bearing": 180 }, + { + "attractor_type": "PlainCenter", + "position": [ + 47, + 167 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 330, + "water_bearing": 180 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 47, + 180 + ], + "strength": 32, + "sub_biome": "Wetland", + "terrain_modification_cost": 331, + "water_bearing": 180 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 48, + 96 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 322, + "water_bearing": 45 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 48, + 238 + ], + "strength": 34, + "sub_biome": "TropicalWet", + "terrain_modification_cost": 211, + "water_bearing": 90 + }, { "attractor_type": "PlainCenter", "position": [ @@ -2480,6 +2414,17 @@ "terrain_modification_cost": 155, "water_bearing": 225 }, + { + "attractor_type": "PlainCenter", + "position": [ + 52, + 192 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 324, + "water_bearing": 180 + }, { "attractor_type": "PlainCenter", "position": [ @@ -2494,13 +2439,13 @@ { "attractor_type": "PlainCenter", "position": [ - 57, - 228 + 58, + 222 ], - "strength": 37, + "strength": 36, "sub_biome": "TropicalWet", - "terrain_modification_cost": 203, - "water_bearing": 0 + "terrain_modification_cost": 216, + "water_bearing": 270 }, { "attractor_type": "PlainCenter", @@ -2524,6 +2469,17 @@ "terrain_modification_cost": 145, "water_bearing": 180 }, + { + "attractor_type": "PlainCenter", + "position": [ + 62, + 234 + ], + "strength": 36, + "sub_biome": "TropicalWet", + "terrain_modification_cost": 205, + "water_bearing": 135 + }, { "attractor_type": "PlainCenter", "position": [ @@ -2615,13 +2571,13 @@ { "attractor_type": "PlainCenter", "position": [ - 86, - 216 + 87, + 147 ], - "strength": 39, - "sub_biome": "CoastalLowland", - "terrain_modification_cost": 144, - "water_bearing": 180 + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 331, + "water_bearing": 225 }, { "attractor_type": "PlainCenter", @@ -2634,6 +2590,39 @@ "terrain_modification_cost": 149, "water_bearing": 180 }, + { + "attractor_type": "PlainCenter", + "position": [ + 92, + 216 + ], + "strength": 38, + "sub_biome": "CoastalLowland", + "terrain_modification_cost": 143, + "water_bearing": 90 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 96, + 98 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 323, + "water_bearing": 45 + }, + { + "attractor_type": "PlainCenter", + "position": [ + 99, + 85 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 323, + "water_bearing": 225 + }, { "attractor_type": "PlainCenter", "position": [ @@ -2766,6 +2755,17 @@ "terrain_modification_cost": 141, "water_bearing": 0 }, + { + "attractor_type": "PlainCenter", + "position": [ + 127, + 12 + ], + "strength": 33, + "sub_biome": "Wetland", + "terrain_modification_cost": 336, + "water_bearing": 0 + }, { "attractor_type": "PlainCenter", "position": [ @@ -7766,54 +7766,6 @@ "river_network": { "confluences": [], "mouths": [ - [ - 0, - 100 - ], - [ - 0, - 142 - ], - [ - 0, - 197 - ], - [ - 0, - 241 - ], - [ - 11, - 65 - ], - [ - 12, - 232 - ], - [ - 12, - 238 - ], - [ - 15, - 141 - ], - [ - 20, - 85 - ], - [ - 25, - 100 - ], - [ - 28, - 14 - ], - [ - 28, - 181 - ], [ 38, 47 @@ -7822,22 +7774,6 @@ 38, 98 ], - [ - 50, - 226 - ], - [ - 50, - 254 - ], - [ - 51, - 230 - ], - [ - 88, - 217 - ], [ 124, 239 @@ -8311,6 +8247,101 @@ 0, 0, 0 + ], + "river_downstream": [ + 3, + 3, + 3, + 3, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 9, + 9, + 9, + 9, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 9, + 7, + 3, + 3, + 3, + 3, + 9, + 9, + 3, + 3, + 9, + 5, + 2, + 9, + 2, + 4, + 9, + 7, + 9, + 3, + 3, + 9, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 3, + 6, + 2, + 2, + 6, + 2, + 2, + 2, + 2, + 2, + 4, + 8, + 8, + 9, + 9, + 9, + 3, + 3, + 5, + 3, + 3, + 3, + 5, + 5, + 9, + 2, + 8 ] } },