feat: river bed carving with SHORT/LOWGROUND lowland routing A/B (terrain-water task 22, C0b part 2a)
Executes the frozen task-21b plan as real terrain — the first river pass to modify the render map. NO WATER (part 2b waters the gated routing style). RiverCarvePass (D-035 numeric): reruns DrainageAnalysis in-pipeline (deterministic — same seed, same eroded surface, same plan), routes each routed giant's lowland reach to the nearest ocean by deterministic Dijkstra under two config-gated cost models — 'short' (distance + uphill penalty; heads direct) vs 'lowground' (cost ~ elevation above sea; follows the lowest ground and meanders) — then carves every promoted course as a parabolic channel with a smoothstep shoulder. Width/depth grow downstream with sqrt(drainage); bed profile forced monotone non-increasing toward the outlet (water must flow) and clamped to sea + RiverSeaMargin EVERYWHERE, with below-sea cells read-only and the crater core excluded — the erosion flood-guard discipline, asserted per generation by the water-pixel A/B (throws on any change). Pipeline slot: AFTER GenerateTowns (towns read the render map for land/slope/ water checks — carving first would move towns and destabilise every A/B) and BEFORE roads (a full run's A* should see the beds). ISLA_SERVER_CONFIG env override added to ConfigManager: batch/A-B tooling loads an alternate config file and the developer's live ServerConfig.json is never written by tooling again (the task-19 restore near-miss class is retired). Measured, seed 1280587109 (both variants): oracle md5-identical to baseline (BIOM/WBID bitwise equal), 0 newly-below-sea cells, below-sea untouched, island top 457.65 m exact, lowest carved cell exactly sea+margin (37.85 m), and Rivers='off' is bitwise identical to the task-19 blueprint. All routed giants reach the ocean in both styles. Wander (polyline/straight): SHORT 1.02-1.06 vs LOWGROUND 1.24-1.38; SHORT carves 811k m3 (cuts through rises, max cumulative cut 14.6 m), LOWGROUND 481k m3 (goes around, 14.3 m). Pass cost ~25 s (plan 21 s, routing 1.4-2.5 s, carve 0.3 s). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
a53d4e8512
commit
6960c2a2e8
3 changed files with 507 additions and 0 deletions
|
|
@ -105,6 +105,24 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
public const float CRATER_EROSION_CORE_DEFAULT = 0.80f;
|
||||
public const float CRATER_EROSION_FEATHER_DEFAULT = 1.05f;
|
||||
|
||||
// Rivers (task 22, C0b part 2a): carve the frozen task-21b river plan's
|
||||
// beds into the RENDER map — ocean trunks, routed giants (lowland reach to
|
||||
// the sea), lake-enders. NO WATER yet (part 2b). "off" until the routing-
|
||||
// style gate; the A/B batch turns it on explicitly. RiverRoutingStyle picks
|
||||
// the lowland routing for routed giants: "short" heads direct (lightly
|
||||
// terrain-aware), "lowground" follows the lowest ground and wanders like a
|
||||
// real river — the task-22 gate decides which ships; "lowground" is the
|
||||
// provisional default pending that verdict. Width/depth scales are taste
|
||||
// dials on the flow-proportional bed profile. RiverSeaMargin is the bed's
|
||||
// absolute floor above sea — the erosion flood-guard discipline: no river
|
||||
// bed may create inland below-sea cells, so the rendered coastline cannot
|
||||
// move even with rivers carved.
|
||||
public static string Rivers = "off";
|
||||
public static string RiverRoutingStyle = "lowground";
|
||||
public static float RiverWidthScale = 1.0f;
|
||||
public static float RiverDepthScale = 1.0f;
|
||||
public static float RiverSeaMargin = 0.2f; // m above sea, bed floor
|
||||
|
||||
// Island falloff shaping (task 11).
|
||||
//
|
||||
// CoastProfile: "wide" adds the submarine shelf — the height curve is identity
|
||||
|
|
@ -140,6 +158,24 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
{
|
||||
string path = "res://ServerConfig.json";
|
||||
|
||||
// Iteration override (task 22): ISLA_SERVER_CONFIG names an alternate
|
||||
// config FILE to load instead of the repo's ServerConfig.json. Batch and
|
||||
// A/B runs point this at a scratch config, so the developer's live
|
||||
// ServerConfig.json is never written by tooling again — the whole
|
||||
// backup/restore dance (and its task-19 near-miss) goes away. Loud, so
|
||||
// a forgotten env var cannot silently masquerade as the repo config.
|
||||
string envPath = OS.GetEnvironment("ISLA_SERVER_CONFIG");
|
||||
if (!string.IsNullOrEmpty(envPath))
|
||||
{
|
||||
if (FileAccess.FileExists(envPath))
|
||||
{
|
||||
path = envPath;
|
||||
GD.Print($"[ConfigManager] ⚠ ISLA_SERVER_CONFIG override: loading '{envPath}' (NOT the repo ServerConfig.json).");
|
||||
}
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] ISLA_SERVER_CONFIG set but '{envPath}' does not exist — falling back to the repo config.");
|
||||
}
|
||||
|
||||
if (!FileAccess.FileExists(path))
|
||||
{
|
||||
GD.PrintErr("[ConfigManager] ServerConfig.json not found! Defaulting to 8K.");
|
||||
|
|
@ -272,6 +308,30 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
ErosionCarveCap = Mathf.Clamp(ErosionCarveCap, 0f, 60f);
|
||||
if (rawCount != ErosionDropletCount || rawLife != ErosionDropletLifetime || rawCap != ErosionCarveCap)
|
||||
GD.PrintErr($"[ConfigManager] Erosion governor out of bounds — clamped: count {rawCount}->{ErosionDropletCount}, lifetime {rawLife}->{ErosionDropletLifetime}, cap {rawCap}->{ErosionCarveCap} m.");
|
||||
// Rivers gate + dials (task 22)
|
||||
if (data.ContainsKey("Rivers"))
|
||||
{
|
||||
string rv = (string)data["Rivers"];
|
||||
if (rv == "off" || rv == "v1")
|
||||
Rivers = rv;
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown Rivers '{rv}'. Keeping '{Rivers}'.");
|
||||
}
|
||||
if (data.ContainsKey("RiverRoutingStyle"))
|
||||
{
|
||||
string st = (string)data["RiverRoutingStyle"];
|
||||
if (st == "short" || st == "lowground")
|
||||
RiverRoutingStyle = st;
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown RiverRoutingStyle '{st}'. Keeping '{RiverRoutingStyle}'.");
|
||||
}
|
||||
if (data.ContainsKey("RiverWidthScale")) RiverWidthScale = (float)data["RiverWidthScale"];
|
||||
if (data.ContainsKey("RiverDepthScale")) RiverDepthScale = (float)data["RiverDepthScale"];
|
||||
if (data.ContainsKey("RiverSeaMargin")) RiverSeaMargin = (float)data["RiverSeaMargin"];
|
||||
RiverWidthScale = Mathf.Clamp(RiverWidthScale, 0.1f, 5f);
|
||||
RiverDepthScale = Mathf.Clamp(RiverDepthScale, 0.1f, 5f);
|
||||
RiverSeaMargin = Mathf.Clamp(RiverSeaMargin, 0f, 5f);
|
||||
|
||||
// Crater erosion treatment (task 19)
|
||||
if (data.ContainsKey("CraterErosionMode"))
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue