diff --git a/Core/Scripts/MapDataParser.cs b/Core/Scripts/MapDataParser.cs
index b91f73a..8f05503 100644
--- a/Core/Scripts/MapDataParser.cs
+++ b/Core/Scripts/MapDataParser.cs
@@ -163,6 +163,24 @@ namespace IslaApocalypse.Core
{
public static WorldBlueprint LoadMapData(string seedStr)
{
+ // ISLA_BLUEPRINT_PATH (task 25, FILE SAFETY): load an explicit blueprint
+ // FILE instead of the seed-named one in the runtime root. Capture and A/B
+ // tooling points this straight at a batch folder, so it never has to copy
+ // a blueprint over — or delete — the file the developer has staged at the
+ // root to view a world in 3D. Loud, so a forgotten env var cannot be
+ // mistaken for the root blueprint.
+ string envPath = OS.GetEnvironment("ISLA_BLUEPRINT_PATH");
+ if (!string.IsNullOrEmpty(envPath))
+ {
+ if (File.Exists(envPath))
+ {
+ GD.Print($"[MapDataParser] ⚠ ISLA_BLUEPRINT_PATH override: loading '{envPath}' " +
+ "(NOT the runtime-root blueprint).");
+ return LoadMapDataFromPath(envPath);
+ }
+ GD.PrintErr($"[MapDataParser] ISLA_BLUEPRINT_PATH set but '{envPath}' does not exist — " +
+ "falling back to the runtime-root blueprint.");
+ }
string filePath = ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}.dat");
return LoadMapDataFromPath(filePath);
}
diff --git a/Tools/Scripts/MapGenerator.cs b/Tools/Scripts/MapGenerator.cs
index af9f073..eaa8b7c 100644
--- a/Tools/Scripts/MapGenerator.cs
+++ b/Tools/Scripts/MapGenerator.cs
@@ -335,12 +335,12 @@ public partial class MapGenerator : TextureRect
// 6. Pull the image from the invisible monitor and save it!
Image capture = offscreenVP.GetTexture().GetImage();
string seedStr = _noise.Seed.ToString();
- string fileName = $"user://Map_Seed_{seedStr}_{label}.png";
+ string fileName = System.IO.Path.Combine(OutputDir(), $"Map_Seed_{seedStr}_{label}.png");
Error saveResult = capture.SavePng(fileName);
if (saveResult == Error.Ok)
- GD.Print($"{T()} Map saved: {ProjectSettings.GlobalizePath(fileName)}");
+ GD.Print($"{T()} Map saved: {fileName}");
else
GD.PrintErr($"{T()} Failed to save map. Godot Error code: {saveResult}");
@@ -348,18 +348,40 @@ public partial class MapGenerator : TextureRect
offscreenVP.QueueFree();
}
+ ///
+ /// Where generated artifacts go. Normally the runtime root (`user://`), but
+ /// ISLA_EXPORT_DIR redirects EVERYTHING this run writes — blueprints and stage
+ /// snapshots — into a task folder instead (task 25, FILE SAFETY). Batch and A/B
+ /// tooling sets it so a generation can never overwrite the blueprint the
+ /// developer has staged at the root to view a world in 3D, and so no cleanup
+ /// move/delete is needed afterwards. Loud on use.
+ ///
+ private string OutputDir()
+ {
+ string dir = OS.GetEnvironment("ISLA_EXPORT_DIR");
+ if (!string.IsNullOrEmpty(dir))
+ {
+ if (System.IO.Directory.Exists(dir)) return dir;
+ GD.PrintErr($"[MapGenerator] ISLA_EXPORT_DIR '{dir}' does not exist — writing to the runtime root instead.");
+ }
+ return ProjectSettings.GlobalizePath("user://");
+ }
+
private void ExportMapData()
{
string seedStr = _noise.Seed.ToString();
+ string outDir = OutputDir();
+ if (outDir != ProjectSettings.GlobalizePath("user://"))
+ GD.Print($"{T()} ⚠ ISLA_EXPORT_DIR override: writing artifacts to '{outDir}' (NOT the runtime root).");
// PRIMARY: the v2 tagged-section container (Core/Scripts/BLUEPRINT_FORMAT.md),
// under the name the server looks for.
- string v2Path = ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}.dat");
+ string v2Path = System.IO.Path.Combine(outDir, $"MapData_Seed_{seedStr}.dat");
BlueprintWriter.WriteV2(v2Path, BuildBlueprint());
// SAFETY NET: the legacy v1 format beside it, until the developer has lived
// with v2 across several regenerations. Removal is a future task.
- ExportMapDataV1(ProjectSettings.GlobalizePath($"user://MapData_Seed_{seedStr}_v1.dat"));
+ ExportMapDataV1(System.IO.Path.Combine(outDir, $"MapData_Seed_{seedStr}_v1.dat"));
}
///
@@ -869,6 +891,9 @@ public partial class MapGenerator : TextureRect
SeaMarginM = ConfigManager.RiverSeaMargin,
TribWaterMinFlowPx = ConfigManager.RiverTribWaterMinFlow,
TribTaperPx = ConfigManager.RiverTribTaperPx,
+ FillFraction = ConfigManager.RiverFillFraction,
+ BankFlare = ConfigManager.RiverBankFlare,
+ BankMaxCutM = ConfigManager.RiverBankMaxCutM,
LakeMinTargetPx = ConfigManager.RiverLakeMinTargetPx
};
var st = RiverCarvePass.Apply(_heightMap, MapSize, isOcean, isClassifyWater,
@@ -898,7 +923,8 @@ public partial class MapGenerator : TextureRect
nextBodyId, st.Carved, seaMap, seaFlat,
_impactCenter.X, _impactCenter.Y,
_impactRadius * ConfigManager.CraterErosionCore,
- ConfigManager.RiverStepDropM, ConfigManager.RiverWaterDepthM);
+ ConfigManager.RiverStepDropM, ConfigManager.RiverWaterDepthM,
+ ConfigManager.RiverFillFraction);
long riverWetPx = 0;
foreach (var reach in reaches)
{
@@ -921,7 +947,8 @@ public partial class MapGenerator : TextureRect
GD.Print($"{T()} [Rivers] water: {reaches.Count} stepped reaches ({tribReaches} on tributaries) " +
$"across {mainCarved} mains + {tribCarved} tributaries, {riverWetPx} wet px " +
$"({tribWet} tributary), step drop {ConfigManager.RiverStepDropM:F2} m, depth {ConfigManager.RiverWaterDepthM:F1} m, " +
- $"trib flow threshold {ConfigManager.RiverTribWaterMinFlow} px taper {ConfigManager.RiverTribTaperPx} px.");
+ $"trib flow threshold {ConfigManager.RiverTribWaterMinFlow} px taper {ConfigManager.RiverTribTaperPx} px, " +
+ $"fill {ConfigManager.RiverFillFraction:P0} of bed depth (min {ConfigManager.RiverWaterDepthM:F1} m), bank flare {ConfigManager.RiverBankFlare:F1}x (shoulder cut cap {ConfigManager.RiverBankMaxCutM:F1} m).");
GD.Print($"{T()} [Rivers] v1 '{ConfigManager.RiverRoutingStyle}': plan {st.AnalysisSeconds:F1}s, " +
$"routing {st.RoutingSeconds:F1}s, carve {st.CarveSeconds:F1}s " +