feat: ISLA_BLUEPRINT_PATH + ISLA_EXPORT_DIR so tooling never touches the runtime root (terrain-water task 25, FILE SAFETY)
The task-25 file-safety rules make this structural rather than procedural. Two
env overrides, both loud on use:
ISLA_BLUEPRINT_PATH — MapDataParser.LoadMapData loads an explicit blueprint
FILE instead of the seed-named one in the runtime root, so capture tooling
reads straight from a batch folder.
ISLA_EXPORT_DIR — MapGenerator writes EVERYTHING a run produces (both
blueprints and every stage snapshot) into a task folder instead of the root.
Why this is needed and not merely tidy: generation ALWAYS wrote
user://MapData_Seed_<seed>.dat, and my task-22/23/24 capture scripts copied a
blueprint over that path and then rm'd it — which destroyed the blueprint the
developer had staged there to view a world in 3D. With these overrides a run
touches nothing in the root, so there is nothing to clean up afterwards and no
delete anywhere in the drivers. Verified this task: the developer's staged
MapData_Seed_1280587109.dat kept md5 dc2bc61b3eb7 across four generations and a
3D capture.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
3cbe4c7967
commit
3fd14c0389
2 changed files with 51 additions and 6 deletions
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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"));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -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 " +
|
||||
|
|
|
|||
Loading…
Reference in a new issue