Stands up the CODE_REPO the rewrite is written into (D-049, D-058). Godot 4.7.2 /
.NET 8 / Godot.NET.Sdk 4.7.2. Nothing is generated, meshed, or ported — this is the
shell and the two data contracts.
Four layers, each with its boundary stated in a directory README:
Core/ math + data only, and engine-free — depends on nothing above it
Server/ authoritative logic (empty this phase)
Client/ rendering (empty this phase)
Tools/ the offline generator — may NOT reference Client (Phase 1 fills it)
Contracts (compiling stubs, no algorithms):
- Column model (D-053): a 2D grid of columns, each a stack of (material, thickness)
runs, any material at any depth. Air is the TOP RUN — there is no air-vs-solid
height branch, and no surface-height accessor exists to reintroduce one. Water is
not a band; it stays an overlay.
- Material schemas (D-054): terrain and building as two append-only registries,
bridged by a recipe seam that is deliberately EMPTY. Identity is a registry key,
never a serialized ordinal. Mesh style is per-ORIGIN and is not a material field.
Rails, so Phase 1 inherits them rather than rediscovering them:
- GenerationScale: MapSize is a parameter, everything derives from it, nothing uses
a raw pixel number. Tightening over the reference — decorrelation offsets are
declared in map widths, not pixels (chat1/00 §4.2).
- ToolingPaths: config/blueprint/output paths all env-overridable, resolved in one
place, so a batch cannot touch the developer's live files.
- FileSafety: the permanent no-deletion rules as throws rather than sentences.
user:// isolation: project name and use_custom_user_dir both pin the runtime dir to
~/.local/share/islaApocalypse-v2/, away from the old prototype's preserved seeds and
batches. Tools/Scenes/UserDirProbe.tscn confirms it rather than assuming it.
114 lines
5.5 KiB
C#
114 lines
5.5 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 directory: <c>batches/NN_<name>/<seed>_<variant>/</c>.
|
|
/// </summary>
|
|
public static string BatchDir(int batchNumber, string batchName, long seed, string variant)
|
|
=> Path.Combine(BatchesRoot, $"{batchNumber:D2}_{batchName}", $"{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}]" : "";
|
|
}
|
|
}
|