islaApocalypse/Core/Scripts/ConfigManager.cs
beezm 3b6d166458 revert: the D8 drainage incision, whole (terrain-water task 10)
The task-10 draft's PASS B shipped and produced the canonical grid
artifact: thousands of straight, disconnected, pooling scratches. Per-
cell steepest descent on a regular grid can only route along its eight
neighbour headings, so at map scale the "channels" read as hatching,
not drainage. Reverted entirely rather than tuned -- no K, p or mask
setting fixes a directional basis. Rivers and erosion move to Phase C
as a hydraulic-erosion pass over FINAL terrain.

Removed: FlowAccumulation / IncisionWeight / EdgeBlend and the INC_*,
SEA_CLAMP, SHELF_INC_WEIGHT and CRATER_* constants; RunIncisionPass and
the pass-2b call; the six incision fields from TDTL (writer, parser,
harness). KEPT, untouched and developer-approved: pass A, the shelf
micro-relief skin, and its ShelfReliefAmp dial.

The crater carve folds back into the single pass-2 loop it came from,
carving two LOCALS written once each. That is what the code did before
the draft split it out, and it makes the aliased double-carve that the
split introduced (fix 1b98fb5) structurally impossible rather than
merely fixed -- there is no longer a read-modify-write to get wrong.

TDTL bodies are now versioned (BlueprintFormat.TDTL_VERSION = 2) and the
parser skips a body version it does not know instead of misreading the
longer v1 layout into plausible nonsense. The only v1 payloads that
exist are in the reverted batch's own tree.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 20:30:44 -04:00

186 lines
6 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Godot;
using Godot.Collections; // Required for Godot's built-in JSON parser
namespace IslaApocalypse.Core // Change this if your namespace is different
{
public static class ConfigManager
{
// Default Fallbacks
// public static int WorldSeed = 1063685222;
public static int WorldSeed = (int)GD.Randi(); // if 0 also randomizes
public static int MapSize = 8192;
public static float CraterRadius = 400f;
public static float DensityMultiplier = 1.0f; // <-- Replaces TownCount
public static int ChunkRadius = 24; // Default chunk size, can be overridden by config
// Iteration toggle: skip the ~25-min A* road pass entirely. The blueprint is
// still exported (road sections present but empty) — an iteration artifact,
// not a shippable world. Default false.
public static bool SkipRoads = false;
// Sea-level model (D-033, terrain-water task 04): "flat" = one scalar sea
// level everywhere (SeaLevelValue); "field" = the legacy latitude Lerp
// (0.26 north .. 0.15 south). Generator-only — the runtime never computes
// sea level. Default: flat 0.15.
public static string SeaLevelModel = "flat";
public static float SeaLevelValue = 0.15f;
// Height-redistribution curve (tasks 0510, graduation M-7): "v5" is the
// task-09 gate's winner — the BALANCED terraced ascent with corner easing and
// modulated shelves; "off" is the raw legacy profile. "v1""v4" and the
// taste-batch tri-state ("v5-compact" lost, "v5-balanced" became plain "v5")
// are retired. Default: v5.
public static string TerrainCurve = "v5";
// Terrain detail passes (task 10): "v1" = shelf micro-relief (requires the
// curve; no-op when it is off), "off" disables it. ShelfReliefAmp is the
// micro-relief amplitude in metres. Defaults: v1, 3 m.
public static string TerrainDetail = "v1";
public static float ShelfReliefAmp = 3.0f;
public static void LoadConfig()
{
string path = "res://ServerConfig.json";
if (!FileAccess.FileExists(path))
{
GD.PrintErr("[ConfigManager] ServerConfig.json not found! Defaulting to 8K.");
return;
}
// Read the file
using var file = FileAccess.Open(path, FileAccess.ModeFlags.Read);
string content = file.GetAsText();
// Parse the JSON
var json = new Json();
var error = json.Parse(content);
if (error != Error.Ok)
{
GD.PrintErr($"[ConfigManager] JSON Parse Error: {json.GetErrorMessage()}");
return;
}
var data = (Dictionary)json.Data;
// Extract the Seed
if (data.ContainsKey("WorldSeed"))
{
WorldSeed = (int)data["WorldSeed"];
}
// Extract the MapProfile and run your Switch/Case logic!
string profile = "8K";
if (data.ContainsKey("MapProfile"))
{
profile = (string)data["MapProfile"];
}
// Extract the TownDensity and run your Switch/Case logic!
string townDensity = "Normal";
if (data.ContainsKey("TownDensity"))
{
townDensity = (string)data["TownDensity"];
}
// Extract the ChunkRadius
if (data.ContainsKey("ChunkRadius")) {
ChunkRadius = (int)data["ChunkRadius"];
}
// Extract the SkipRoads iteration toggle
if (data.ContainsKey("SkipRoads"))
{
SkipRoads = (bool)data["SkipRoads"];
}
// Extract the sea-level model
if (data.ContainsKey("SeaLevelModel"))
{
string model = (string)data["SeaLevelModel"];
if (model == "flat" || model == "field")
SeaLevelModel = model;
else
GD.PrintErr($"[ConfigManager] Unknown SeaLevelModel '{model}'. Keeping '{SeaLevelModel}'.");
}
if (data.ContainsKey("SeaLevelValue"))
{
SeaLevelValue = (float)data["SeaLevelValue"];
}
// Extract the terrain-curve gate
if (data.ContainsKey("TerrainCurve"))
{
string curve = (string)data["TerrainCurve"];
if (curve == "off" || curve == "v5")
TerrainCurve = curve;
else if (curve == "v5-balanced")
GD.PrintErr($"[ConfigManager] TerrainCurve 'v5-balanced' won the task-09 gate and is now plain \"v5\". Keeping '{TerrainCurve}'.");
else if (curve == "v5-compact")
GD.PrintErr($"[ConfigManager] TerrainCurve 'v5-compact' was retired by the task-09 verdict (BALANCED won). Keeping '{TerrainCurve}' — use \"v5\" or \"off\".");
else if (curve == "v1" || curve == "v2" || curve == "v3" || curve == "v4")
GD.PrintErr($"[ConfigManager] TerrainCurve '{curve}' was retired by a later recalibration (v5, tasks 09/10). Keeping '{TerrainCurve}' — use \"v5\" or \"off\".");
else
GD.PrintErr($"[ConfigManager] Unknown TerrainCurve '{curve}'. Keeping '{TerrainCurve}'.");
}
// Extract the terrain-detail gate + relief amplitude
if (data.ContainsKey("TerrainDetail"))
{
string detail = (string)data["TerrainDetail"];
if (detail == "off" || detail == "v1")
TerrainDetail = detail;
else
GD.PrintErr($"[ConfigManager] Unknown TerrainDetail '{detail}'. Keeping '{TerrainDetail}'.");
}
if (data.ContainsKey("ShelfReliefAmp"))
{
ShelfReliefAmp = (float)data["ShelfReliefAmp"];
}
switch (profile)
{
case "4K":
MapSize = 4096;
CraterRadius = 400f; // Scaled down crater
break;
case "6K":
MapSize = 6144;
CraterRadius = 600f;
break;
case "8K":
MapSize = 8192;
CraterRadius = 800f; // Your original crater size spread over an 8K map
break;
case "10K":
MapSize = 10240;
CraterRadius = 1000f;
break;
default:
GD.PrintErr($"[ConfigManager] Unknown MapProfile '{profile}'. Defaulting to 8K.");
MapSize = 8192;
CraterRadius = 800f;
break;
}
switch (townDensity)
{
case "Sparse":
DensityMultiplier = 0.5f;
break;
case "Normal":
DensityMultiplier = 1.0f;
break;
case "Dense":
DensityMultiplier = 2.0f;
break;
default:
GD.PrintErr($"[ConfigManager] Unknown TownDensity '{townDensity}'. Defaulting to Normal.");
DensityMultiplier = 1.0f;
break;
}
GD.Print($"[ConfigManager] Successfully loaded {profile} Profile. MapSize set to {MapSize}.");
}
}
}