# /Tools/Scripts — generation logic C# backend for the offline developer tools. Decoupled from the live server and client. ## `MapGenerator.cs` Generates the entire 2D blueprint. Roughly in order: 1. **Config** — reads `ServerConfig.json`, which sets `MapSize` (8192 by default) and the crater radius. The `MapSize = 4096` field initialiser is a fallback that is immediately overwritten. 2. **Topography** — FastNoiseLite base height plus a mountain spine, minus a squircle distance falloff, giving a guaranteed island. Noise frequency is divided by `scaleFactor` (`MapSize / 1024f`) so terrain features stay the same real-world size at any map profile. The island's proportions are the `IslandAxisX`/`IslandAxisY` dials (`1.30`/`0.78`; the pre-task-11 shape was `1.15`/`0.90`). **`IslandAxisX` is a weak lever** — the island is already Trench-clamped at ~90 % of the map width, so aspect responds almost entirely to `IslandAxisY`, which trades against land area. These move the coastline, so they move biomes. The mountain spine shares `IslandAxisX` for its width, and its crest is rounded (`IslandFalloff.SmoothAbs`) — `1 - |x - centre|` used to peak with a slope discontinuity that was, measured, the largest slope step on the map outside the Trench walls. **The spine's AXIS is still a straight line down the map centre; that is known, deferred, and its own task.** With `CoastProfile: "wide"` (the default) the *seabed* leaving the shoreline is shelved (`IslandFalloff.CoastShelf`): the height curve is identity at and below sea level, so it never reached the water, and the shoreline used to shelve gently on land then drop 6.7× steeper the moment it went under. The shelf cannot move the waterline — it is strictly positive for positive depth — so biomes and water are bit-identical with it on or off. `OffshoreIslandDensity` (`0.02`, `0` disables) seeds sparse discoverable islets in the open ocean; they are held off the mainland by a depth moat and out of the Trench by a distance mask, both by construction. When `TerrainCurve: "v5"` (the default — the task-09 gate's BALANCED winner), the calibrated height-redistribution curve (`HeightCurve.cs` — the terraced ascent with spatially modulated shelves: flat farmable lowlands, a walkable foothill bench at 100±12 m, the white mountain-town plateau at 220±20 m — shelf heights and strength drift across the island so no two flanks wear the same ring — and a per-seed-normalized summit spike to the 420 m cap) reshapes above-sea terrain after noise/falloff/Trench and before the crater carve; biome classification reads a retained uncurved map, so biomes are identical either way. With `TerrainDetail: "v1"` (the default) two detail passes ride along with the curve (`TerrainDetailPass.cs`): shelf micro-relief (`ShelfReliefAmp`, ±3 m rolling skin on the benches/plateaus) and shelf-edge variation (`ShelfEdgeVariation`, 12 m) — a per-column shift of the shelf/riser knot block that makes the shelf edge scallop into notches, coves and peninsulas instead of tracing a clean height contour. Both touch exported heights only, and neither can reach below the red ceiling or above the 420 m cap: the curve's K2 and K6 knots do not move, so a warped column is bit-identical to an unwarped one outside the shelf/riser stack. Drops the `0_height` hillshade snapshot (hypsometric bands × NW hillshade) in both modes. **Erosion (tasks 17–18, Phase C0)** — with `Erosion: "v1"` (default **"off"**, opt-in until the developer's gate approves it) a droplet-based hydraulic erosion pass (`HydraulicErosion.cs`, standalone numeric, deterministic from the resolved seed) details the curved+detailed render map after the detail passes and before the crater carve: droplets walk downhill with inertia, eroding steep fast stretches and depositing where the ground flattens — both spread over the same cone brush, so neither carving nor dumping can spike a single cell. **Four** hard governors bound it (`ErosionDropletCount`/`ErosionDropletLifetime`/`ErosionCarveCap`/ `ErosionDepositCap`; both caps are per-cell metres against one net-displacement ledger and are asserted on exit), a sea clamp forbids carving below sea + margin and leaves below-sea cells untouched in both directions (the rendered coastline cannot move — asserted every generation), and the crater is handled by `CraterErosionMode` (task 19): nothing inside the protected strike core (`CraterErosionCore`, **0.80 ×** CraterRadius — the carve's own extent) is modified, and outside it either `"full"` applies full strength at once or `"feather"` ramps 0→full out to `CraterErosionFeather` (1.05 ×). Task 17's hard 1.2 × cutoff was replaced because it held **620 811 land cells** of ordinary terrain smooth for no geometric reason — measured, the carve's displacement is exactly 0 beyond 0.80 × — which read as an un-eroded disc with a hard edge. Keeping the core at the carve radius is what makes erosion and the carve touch **disjoint** cells; a narrower core lets the carve, which runs afterwards and scales height toward the sea target, amplify erosion's deltas across the waterline (measured at a 0.50 core: 79 rendered waterline crossings). The flooded bay and its sea connection never depend on the crater mode — below-sea cells are read-only in both directions, so the bay can be neither carved open nor silted shut. Output-height only: biome/water classification reads the retained pre-erosion map, so `1_biomes`/`0_water` are bit-identical with erosion on or off. The task-18 defaults tune for a drainage **hierarchy** — fine rills everywhere feeding a set of clearly deeper convergent channels — by letting droplets live long enough (384 steps at inertia 0.35, low evaporation) that their paths overlap and deepen shared low lines, and by raising the carve cap to 15 m so trunks separate from rills instead of both piling against one ceiling. Measured on seed 1280587109: ~1.9 M cells carved past 0.5 m, 42 k past 5 m, 1.4 k past 10 m, and 177 connected channel systems of 200+ cells at the 3 m threshold. Erosion concentrates ~9× on the curve's shelf risers, because that is where sustained slope exists; the flat shelves and lowlands are barely touched, so **the lowland continuation of a trunk is not erosion's to cut** — that is river promotion's job. **No rivers yet** — the carved channels are the designated future river routes (Phase C0b promotes them by flow accumulation). The predecessor D8 drainage-incision pass was written and reverted in task 10 — per-cell steepest descent on a regular grid can only route along eight headings, and at map scale that reads as straight hatching, not drainage; the droplet model is the working replacement (its carve field measures isotropic to within 1.5 % across the folded 45° grid period). **River plan (task 21, C0b part 1)** — `DrainageAnalysis.cs` + the headless `RiverPlanTool.tscn` produce a river PLAN from an erosion-ON blueprint: priority-flood with a one-ulp epsilon resolves the ~15k erosion pits for ROUTING ONLY (terrain untouched), deep+large depressions survive as terminal basins, D8 flow directions + Kahn accumulation build the drainage network, and the top ~3 TRUE-ocean outlets (the ocean body, WBID 1 — enclosed lagoons do not count as "the sea") become trunk candidates with lean tributaries, mountain-exit handoff points, and lean endorheic terminals. Task 21b extends the plan to the developer's MIXED promotion: the ocean trunks PLUS the top endorheic giants (`RIVERPLAN_GIANT_N`, default 3), each classified — a giant whose terminal basin holds a classify lake stays a **lake-ender** (rivers ending in lakes are real geography); a dry-pan giant, and always the **SOUTHERN CANDIDATE** (the giant pooling nearest the southernmost town), gets a **PROVISIONAL route** to the ocean: steepest descent on the full (no-terminal) fill, so the basin overtops at its spill and the walk follows the terrain's own drainage to the sea — drawn dashed for the gate, never carved. Output is a JSON *plan* sidecar + console report — deliberately NOT a blueprint section, so a plan can never masquerade as realized water. Part 2 (task 22) carves and waters the gated plan. Pure analysis: the source blueprint is never written. **River carving (task 22, C0b part 2a)** — with `Rivers: "v1"` (default **"off"**, pending the routing-style gate) the frozen task-21b plan is executed as real terrain (`RiverCarvePass.cs`): the drainage analysis reruns in-pipeline (deterministic), each routed giant gets a lowland route to the nearest ocean by deterministic Dijkstra — `RiverRoutingStyle: "short"` (direct, uphill-penalised) or `"lowground"` (cost ≈ elevation; follows the lowest ground and meanders; the provisional default) — and every promoted course is carved as a parabolic channel with a smoothstep shoulder, width/depth growing downstream with drainage (`RiverWidthScale`/ `RiverDepthScale`), bed made monotone non-increasing toward the outlet and clamped to sea + `RiverSeaMargin` everywhere (the erosion flood-guard discipline: below-sea cells read-only, zero new below-sea cells, coastline provably fixed; asserted per generation). Slots AFTER towns (town placement reads the render map and must not shift) and BEFORE roads. Crater core excluded. **NO WATER yet** — part 2b waters the gated routing style. 3. **Sea level and water** — sea level per the configured model (`SeaLevelModel`: `"flat"` scalar — the default, `SeaLevelValue` 0.15 — or the legacy `"field"` latitude Lerp); flood fill separates true ocean from inland lakes; a mainland fill guarantees one contiguous landmass. 4. **The crater** — placed along the northern coast and carved to below sea level, but only out to **80 % of its radius**, which guarantees a landbridge rather than severing the island. 5. **Water bodies** — promotes the classified water into explicit data: the ocean as body 1, each lake connected-component labeled (same 4-connectivity as the true-ocean fill), one transitional surface level per body. Classification comes from the shared `IsOceanPixel`/`IsLakePixel` predicates that the biome stage also uses, so the two can never disagree. A priority-flood pit-fill runs here as validated diagnostics (basin statistics to console; serializes nothing). Drops the `0_water` snapshot. 6. **Biomes and towns** — biome zoning by height and temperature; tiered town placement (Capitol, Hubs, Villages, Outposts, POIs) filtered by slope, water proximity and spacing. 7. **Roads** — see below. Skipped entirely when the `SkipRoads` config toggle is on (the ~25-min A\* pass is the bottleneck; a skip run exports a road-less iteration blueprint in ~80 s with a loud console banner). 8. **Export** — writes the `.dat` blueprint (dual-write: the v2 tagged container under the primary seed name, plus the legacy v1 format beside it as `_v1.dat` — see `Core/Scripts/BLUEPRINT_FORMAT.md`), then renders the PNG snapshot. The v2 file embeds the resolved generation params (seed, MapSize, crater radius, density, impact centre, provenance). ### The road network `AStarGrid2D` over the whole map at one node per pixel. Mountains are made expensive rather than impassable (`1 + elevation³ × 400`), water and the crater are marked solid. - **Continental loop** — highway nodes sorted by radial angle and connected in a ring. - **Mountain branch** — a spur from the loop to the snow hub. - **County roads** — Prim's algorithm daisy-chains remaining towns onto the network. Villages become Rugged roads, everything else Trails. - **Abandon protocol** — a town further than 8 % of the map from the network is skipped rather than pathed to, so one unreachable outpost cannot hang generation. - **Smoothing** — every path is decimated with Ramer–Douglas–Peucker (tolerance 4.0) then smoothed with 4 Chaikin passes. ⚠ **This is the project's dominant performance problem.** At 8K the grid is 67 million nodes, and the weight-setup pass touches every one before the first path is requested. The `await` yields between stages cannot interrupt a single engine-side `GetPointPath` call, so a long path still blocks. Full regenerations frequently get abandoned. ### The PNG snapshot `SaveMapSnapshot()` builds an offscreen `SubViewport` in code rather than relying on the scene's layout, because Godot's UI layout engine will otherwise crush a 4096+ render down to the editor window size. A `MapDrawProxy` control re-issues the `_Draw()` calls into that viewport — `GetImage()` captures only the base texture and would otherwise miss the roads and towns entirely — and the code awaits two `RenderingServer.FramePostDraw` signals so the GPU has actually painted before the image is read back. Road colours on the snapshot, useful for identifying a road: **red** = Highway, **black** = Branch, **dark brown** = Rugged, **light brown** = Trail. ### Outputs `MapData_Seed_.dat` (v2), `MapData_Seed__v1.dat` (legacy dual-write), and the staged snapshots `Map_Seed__{0_height,0_water,1_biomes,2_towns,3_roads}.png`, all to `user://`. (`0_height` is the hypsometric hillshade of the curved terrain; `0_water` is painted from the water stage's own outputs — ocean deep blue, lakes lighter blue, land neutral; `3_roads` is absent on a SkipRoads run.) ## `RoundTripHarness.cs` The blueprint format regression test (scene: `Tools/Scenes/RoundTripHarness.tscn`). Loads a known-good blueprint through the real parser, re-writes it as v2 through the real writer, re-loads it, and asserts semantic equality (heights bitwise, biomes, towns, every road point). Headless, seconds per cycle, no generation. Exit code 0 = pass. ## Rules 1. **No magic numbers.** Distances, radii and thresholds derive from `MapSize` or `scaleFactor`, so the generator behaves identically at 4K and 10K. 2. **Respect the GPU.** Anything exporting visual data must `await` the appropriate frame signals before reading pixels back.