Presentation only. No generation file is touched — Topography, TerrainNoise, IslandFalloff, Pass1Result, TerrainGenConfig and GenerationScale are all unchanged. Batch naming, fixed in code. The prefix is the AUTHORING TASK number, not a running counter: 04_review means "the batch task 04 authored", not "the fifth batch". It had already drifted — tasks 02 and 03 produced 00_smoke through 05_trophy_10240 across two tasks, so no folder name said which task made what. Now the task number is an explicit argument to ToolingPaths.BatchRoot(taskNumber, descriptor), which composes the prefix itself and REFUSES a descriptor that carries its own. All three tools take it as ISLA_TASK. Verified: ISLA_BATCH=05_foo is refused with a stated reason and exit 2, and creates no folder. Also fixed: an exception out of _Ready does not stop Godot — it logs and the process sits there with no main loop, so a misconfigured run HUNG rather than failing. A hang looks like slow work, which is worse than a crash. The batch tools now catch, print what was refused, and exit non-zero. Grayscale mode: normalize a field to its own [min,max]. This is the one place per-image normalization is correct — everywhere else anchors are fixed so images compare, but here the point is to see one field at full contrast. The range is printed and indexed so a shade reads back to a height. You cannot judge noise through a palette: a ramp bends the distribution, a hillshade adds shape the data does not have. The wide Costa-Rica palette, and the reframing the developer asked for: the pretty map is the WIDE GRADIENT RENDERED FLAT, and relief is no longer the hero. On raw pass-1 noise a hillshade has nothing coherent to shade, so it renders fine fractal bumpiness as fuzz that actively hides the elevation the colour is showing. Strong hillshade is retained as diagnostic_relief and labelled a dev view — its bumpiness is exaggerated slope, not extra terrain. Subtle relief is kept for comparison, with the honest note that it still fuzzes until Phase 2 carves coherent landforms. Legend: a colour bar with ticks drawn from the palette's own ramp, so it cannot drift from the map beside it. Ticks are RELATIVE height and the image says NOT METRES — the conversion is Phase 2's, and labelling it "m" would invent a fact in the artefact a reader most trusts. Text comes from a 5x7 bitmap font written for this, specifically so a legend does not drag in the SubViewport capture path.
156 lines
7.6 KiB
C#
156 lines
7.6 KiB
C#
using System;
|
|
using System.IO;
|
|
|
|
namespace IslaApocalypse.Core
|
|
{
|
|
/// <summary>
|
|
/// ⭐ EVERY PATH THE TOOLING READS OR WRITES, RESOLVED IN ONE PLACE, OVERRIDABLE BY ENVIRONMENT.
|
|
/// → `Design - Tooling - Iteration and Batching.md` § "Tooling must be safe BY CODE, not by care".
|
|
///
|
|
/// ═══ WHY THIS TYPE EXISTS ═══
|
|
///
|
|
/// So a batch run CANNOT read over or write to the developer's live config, staged blueprints,
|
|
/// or output directory. Not "should not" — cannot, because there is no other way to obtain a
|
|
/// path, and each one has an environment override that a batch script sets before it starts.
|
|
///
|
|
/// > ⭐ THE POINT IS THAT IT IS ENFORCED BY CODE, NOT BY CARE.
|
|
/// > A rule that depends on an executor remembering it will eventually meet an executor who
|
|
/// > does not.
|
|
///
|
|
/// ⚠ This is not hypothetical. These rules were born from a real incident: an executor admitted
|
|
/// it had been deleting the developer's staged test blueprint. It was owned and fixed IN CODE.
|
|
/// That is why these are rules and not guidance. → <see cref="FileSafety"/>.
|
|
///
|
|
/// ═══ THE OVERRIDES ═══
|
|
///
|
|
/// ISLA_CONFIG_PATH the generation config file default: user://config.json
|
|
/// ISLA_BLUEPRINT_PATH the blueprint read/written default: user://blueprints
|
|
/// ISLA_OUTPUT_DIR generation output (maps, batches) default: user://output
|
|
///
|
|
/// ⚠ user:// RESOLUTION. Defaults sit under the project's own user data directory, which this
|
|
/// project pins away from the old prototype's — see project.godot's user:// isolation block.
|
|
/// Core is engine-free by design, so the caller supplies the resolved user directory (from
|
|
/// Godot's OS.GetUserDataDir()) via <see cref="Configure"/>. Until it does, the defaults resolve
|
|
/// under the process working directory, which is wrong for a real run and loud enough to notice.
|
|
///
|
|
/// No generator exists this phase. The rails are laid before it so it inherits them.
|
|
/// </summary>
|
|
public static class ToolingPaths
|
|
{
|
|
public const string ConfigPathVar = "ISLA_CONFIG_PATH";
|
|
public const string BlueprintPathVar = "ISLA_BLUEPRINT_PATH";
|
|
public const string OutputDirVar = "ISLA_OUTPUT_DIR";
|
|
|
|
private static string _userDataDir;
|
|
|
|
/// <summary>
|
|
/// Hand Core the engine-resolved user data directory. Called once at startup by whichever
|
|
/// layer owns the engine (Tools or Server); Core never asks Godot for it itself.
|
|
/// </summary>
|
|
public static void Configure(string userDataDir)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(userDataDir))
|
|
throw new ArgumentException("A user data directory is required.", nameof(userDataDir));
|
|
_userDataDir = userDataDir;
|
|
}
|
|
|
|
/// <summary>The resolved user data directory, or the working directory if none was configured.</summary>
|
|
public static string UserDataDir => _userDataDir ?? Directory.GetCurrentDirectory();
|
|
|
|
/// <summary>Whether <see cref="Configure"/> has been called. Tooling should assert this before a run.</summary>
|
|
public static bool IsConfigured => _userDataDir != null;
|
|
|
|
/// <summary>The generation config file. Override: ISLA_CONFIG_PATH.</summary>
|
|
public static string ConfigPath =>
|
|
Override(ConfigPathVar) ?? Path.Combine(UserDataDir, "config.json");
|
|
|
|
/// <summary>Where blueprints are read from and written to. Override: ISLA_BLUEPRINT_PATH.</summary>
|
|
public static string BlueprintPath =>
|
|
Override(BlueprintPathVar) ?? Path.Combine(UserDataDir, "blueprints");
|
|
|
|
/// <summary>Where a generation run writes its output. Override: ISLA_OUTPUT_DIR.</summary>
|
|
public static string OutputDir =>
|
|
Override(OutputDirVar) ?? Path.Combine(UserDataDir, "output");
|
|
|
|
/// <summary>
|
|
/// The batches root. → `Design - Tooling - Iteration and Batching.md`:
|
|
/// <c>batches/NN_<name>/<seed>_<variant>/</c>, each batch carrying an
|
|
/// INDEX.md and a persistent scratch/. A/B comparisons are browsed by a human, and a flat
|
|
/// directory of same-named PNGs is not browsable.
|
|
///
|
|
/// ⚠ PROTECTED FROM DELETION. → <see cref="FileSafety"/>.
|
|
/// </summary>
|
|
public static string BatchesRoot => Path.Combine(OutputDir, "batches");
|
|
|
|
/// <summary>
|
|
/// The scratch subfolder of a batch. INTERMEDIATES PERSIST HERE AND ARE NEVER CLEANED —
|
|
/// the whole point is that a run's intermediates survive it, so a surprising result can be
|
|
/// investigated instead of regenerated.
|
|
/// </summary>
|
|
public static string BatchScratch(string batchDir) => Path.Combine(batchDir, "scratch");
|
|
|
|
/// <summary>
|
|
/// ⭐ A BATCH ROOT: <c>batches/<task>_<descriptor>/</c>.
|
|
///
|
|
/// ═══ ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER. ═══
|
|
///
|
|
/// <c>04_review</c> means "the batch task 04 authored". It does NOT mean "the fifth batch".
|
|
/// A task that produces six batches produces six <c>04_*</c> folders, not <c>04_</c> through
|
|
/// <c>09_</c>.
|
|
///
|
|
/// This is enforced here, in code, because it already drifted once: tasks 02 and 03 used the
|
|
/// prefix as a global running counter and produced <c>00_smoke</c> … <c>05_trophy_10240</c>
|
|
/// across two tasks, so nothing in the folder name said which task made what. Passing the
|
|
/// task number as an explicit argument — rather than letting a caller compose a free-form
|
|
/// string — is what makes the convention unbreakable rather than remembered.
|
|
/// → `Design - Tooling - Iteration and Batching.md`.
|
|
///
|
|
/// The descriptor must NOT carry its own numeric prefix; that is the mistake this method
|
|
/// exists to prevent, so it is refused rather than silently accepted.
|
|
/// </summary>
|
|
public static string BatchRoot(int taskNumber, string descriptor)
|
|
{
|
|
if (taskNumber < 0)
|
|
throw new ArgumentOutOfRangeException(nameof(taskNumber), taskNumber,
|
|
"A batch is named for the task that authored it; there is no negative task.");
|
|
if (string.IsNullOrWhiteSpace(descriptor))
|
|
throw new ArgumentException("A batch needs a descriptor — '04_' alone is not browsable.", nameof(descriptor));
|
|
|
|
string d = descriptor.Trim();
|
|
|
|
// Refuse "04_review", "4_review", "05_foo" — the caller is re-adding a prefix, which is
|
|
// exactly how the counter drifted. The task number is this method's job, not theirs.
|
|
int us = d.IndexOf('_');
|
|
if (us > 0 && int.TryParse(d.Substring(0, us), out _))
|
|
throw new ArgumentException(
|
|
$"Descriptor '{d}' starts with its own numeric prefix. Pass the task number as " +
|
|
$"taskNumber and the descriptor WITHOUT one (e.g. \"review\", not \"04_review\") — " +
|
|
"the prefix is composed here so it cannot drift.", nameof(descriptor));
|
|
|
|
return Path.Combine(BatchesRoot, $"{taskNumber:D2}_{d}");
|
|
}
|
|
|
|
/// <summary>
|
|
/// A variant directory inside a batch:
|
|
/// <c>batches/<task>_<descriptor>/<seed>_<variant>/</c>.
|
|
/// </summary>
|
|
public static string BatchDir(int taskNumber, string descriptor, long seed, string variant)
|
|
=> Path.Combine(BatchRoot(taskNumber, descriptor), $"{seed}_{variant}");
|
|
|
|
private static string Override(string variable)
|
|
{
|
|
string v = Environment.GetEnvironmentVariable(variable);
|
|
return string.IsNullOrWhiteSpace(v) ? null : v;
|
|
}
|
|
|
|
/// <summary>Every resolved path, for a run report's header. Print this; it is cheap and it has caught things.</summary>
|
|
public static string Describe() =>
|
|
$"user data : {UserDataDir}{(IsConfigured ? "" : " ⚠ NOT CONFIGURED — falling back to CWD")}\n" +
|
|
$"config : {ConfigPath}{Marker(ConfigPathVar)}\n" +
|
|
$"blueprints: {BlueprintPath}{Marker(BlueprintPathVar)}\n" +
|
|
$"output : {OutputDir}{Marker(OutputDirVar)}\n" +
|
|
$"batches : {BatchesRoot}";
|
|
|
|
private static string Marker(string variable) => Override(variable) != null ? $" [{variable}]" : "";
|
|
}
|
|
}
|