rivers/01: re-baseline — bare defaults reproduce terrain-shape-v1 (calibration pinned family-off); retire stale oracles; namespace batch root by chat
The bare TerrainGenConfig defaults did NOT reproduce the terrain the developer
locked, so "run the default generator" was not "the terrain in the gallery" —
the single most expensive fact in the codebase, and the reason a fresh chat
would spend an afternoon chasing differences that were CONFIGURATION, not
regression. This is the deliberate task that ends that, before any river work.
A1 — the defaults ARE the locked shape now. Five fields actually move:
SpeckRevert false->true, MinLandComponentFrac 3e-5->2.5e-7 (120x smaller; the
config default would have eaten real islands, not specks), SouthStretch 0->2,
FragmentAmp 0->0.5, Erosion false->true. Seven more were already correct via
SouthernStretch.Default* / CoastalFragment.Default* and are now pinned as
literals, because TerrainShapeV1 used to do that pinning and this default set
inherits the job. CoastShelf stays OFF — the locked shape has no shelf, and
evaluating it (D-041) is its own later task once water renders. Offshore stays
Off permanently (D-063): islands are organic-only, made by the stretch +
fragmentation and identified by the region layer, never placed.
A2 — the preserve mechanism. The curve knots are percentiles of the FAMILY-OFF
land distribution; flipping the defaults would have moved the pool, the knots,
and with them the render field of every batch including terrain-shape-v1
itself. So the pool is pinned family-off (TerrainGenConfig.WithFamilyOff /
CalibrationPool) rather than the knots being baked: calibration stays live, its
INPUT distribution is held still. The pin was a no-op by construction — it sets
the values the defaults carried the instant before the flip — and re-measuring
after confirms it: pool, all six knots, per-seed spread, shaped max,
monotonicity spikeMax and all seven band shares identical.
Applied wider than "in CalibrateCurve": OffshoreIslandsTool,
RegionLabelingTool and SouthernStretchTool generate their own family-off
field for the Phase-1 anchor, so the pool pin alone would NOT have covered
them and their a1 would have failed for a configuration reason. TerrainGenTool
too — it AUTHORED 02_pass1_port and must stay able to regenerate its own
anchor.
Recorded as a judged-and-parked property: knots measured family-off, applied
family-on. Deliberate, not an oversight. Same disposition as the mid-slope
feather.
A4 — no oracle may pass against a superseded baseline. Six anchors retired
(01/03/04/06/08/09) with their checks and ISLA_T0x_SOURCE defaults; three kept
(chat1/02_pass1_port as the family-off pass-1 guard, chat2/10 and chat2/11 as
the shape and erosion acceptance anchors). Two invariants were RE-POINTED
rather than lost — the southern stretch's north-lock and the coastal-fragment
interior-lock now compare against SAME-RUN fields, which is scale-free and
cannot be invalidated by a moved dump. The retired dumps are kept, not deleted,
and marked superseded in their INDEX.md.
A missing anchor is now LOUD. The old pattern skipped silently, so a moved
anchor did not make its oracle fail — it made it not RUN, and a batch with a
skipped check prints an all-PASS table that reads like a clean one. That is
the INVERSE of the hazard the re-baseline guards against, and the migration
below is exactly the event that would have triggered it, on nine anchors at
once. ShapingOracle.LoadAnchor now separates the two cases: absent -> throw;
present at another size -> loud INCONCLUSIVE, which is a fail, never a pass.
TerrainShapeV1 inverted from PRESET to GUARD and moved to its own file.
Apply() is gone — stamping the values on top of the defaults would MASK a
drift instead of catching it. Its constants are now the assertion target, and
Assert() refuses a run whose defaults have drifted off the locked shape.
B/C — batches are namespaced by chat: batches/<chat>/NN_slug/. Task numbers
restart at 00 per chat, so a flat root collided the moment a second chat
existed — four colliding prefixes across 25 batches, separable only by slug.
ToolingPaths.ChatSlug is REQUIRED (throws if unset) and defaults per tool to
its authoring chat, so re-running reproduces a batch in place while ISLA_CHAT
redirects — which is also what stops an acceptance run from overwriting the
very anchor it checks against. Writes go through BatchRoot; historical READS
compose against BatchesRoot and so carry the prefix in their own source string
("chat1/02_pass1_port"). The 25 existing batches were migrated moves-only.
ACCEPTANCE — 16 of 16 byte-identical, 0 failed. All 8 gallery seeds at 8192
from the bare defaults are byte-identical to chat2/10_frag4_seed_gallery
(= terrain-shape-v1, a59e52f); all 8 erosion fields byte-identical to
chat2/11_erosion (= ea291ea). Every gallery table row and every erosion
statistic reproduces its recorded value exactly. DrainageTool's a11 passes
bit-identical over 67,108,864 cells, and its analysis reproduces batch 12
exactly — so the whole chain rivers depends on (shape -> erosion -> drainage)
is unchanged. All 12 edited tools re-run clean; both new guards negative-tested.
The baseline moved in DEFAULTS, not in TERRAIN.
-> XX_Human/output/rivers/01_rebaseline_and_batch_namespace.report.md
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WhkXBQh2tDmcWKpXYcj8vj
This commit is contained in:
parent
89e0f85c9f
commit
03fe75b378
20 changed files with 856 additions and 314 deletions
|
|
@ -26,6 +26,7 @@ namespace IslaApocalypse.Core
|
||||||
/// ISLA_CONFIG_PATH the generation config file default: user://config.json
|
/// ISLA_CONFIG_PATH the generation config file default: user://config.json
|
||||||
/// ISLA_BLUEPRINT_PATH the blueprint read/written default: user://blueprints
|
/// ISLA_BLUEPRINT_PATH the blueprint read/written default: user://blueprints
|
||||||
/// ISLA_OUTPUT_DIR generation output (maps, batches) default: user://output
|
/// ISLA_OUTPUT_DIR generation output (maps, batches) default: user://output
|
||||||
|
/// ISLA_CHAT the batch chat namespace default: the tool's authoring chat
|
||||||
///
|
///
|
||||||
/// ⚠ user:// RESOLUTION. Defaults sit under the project's own user data directory, which this
|
/// ⚠ 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.
|
/// project pins away from the old prototype's — see project.godot's user:// isolation block.
|
||||||
|
|
@ -60,6 +61,61 @@ namespace IslaApocalypse.Core
|
||||||
/// <summary>Whether <see cref="Configure"/> has been called. Tooling should assert this before a run.</summary>
|
/// <summary>Whether <see cref="Configure"/> has been called. Tooling should assert this before a run.</summary>
|
||||||
public static bool IsConfigured => _userDataDir != null;
|
public static bool IsConfigured => _userDataDir != null;
|
||||||
|
|
||||||
|
// ═══ ⭐⭐ THE CHAT NAMESPACE (rivers/01) ═══════════════════════════════════════════════════
|
||||||
|
//
|
||||||
|
// ═══ WHY BATCHES ARE NAMESPACED BY CHAT ═══
|
||||||
|
//
|
||||||
|
// The batch prefix is the AUTHORING TASK NUMBER (see <see cref="BatchRoot"/>), and task
|
||||||
|
// numbers restart at 00 in every new build chat. So a flat batches/ directory COLLIDES the
|
||||||
|
// moment a second chat exists: chat 1's `02_pass1_port` and chat 2's `02_curve_continuous`
|
||||||
|
// are both "batch 02", and nothing in either name says which chat made it. Measured on the
|
||||||
|
// real pile at rivers/01: 25 batches, FOUR colliding prefixes (02, 03, 04, 06), 13 folders
|
||||||
|
// belonging to chat 1 and 12 to chat 2 — separable only by SLUG, never by number.
|
||||||
|
//
|
||||||
|
// > ### ⚠ The slug is WHO IS RUNNING, not who authored.
|
||||||
|
// > A tool carries its authoring chat as its default so that re-running it reproduces its own
|
||||||
|
// > batch in place. A different chat re-running it for its own purposes sets ISLA_CHAT and
|
||||||
|
// > writes under its own namespace — which is also what stops an acceptance run from
|
||||||
|
// > OVERWRITING THE VERY ANCHOR IT IS CHECKING AGAINST.
|
||||||
|
//
|
||||||
|
// ⚠ REQUIRED, exactly like <see cref="Configure"/>: with no slug set, <see cref="BatchRoot"/>
|
||||||
|
// throws rather than quietly writing to the un-namespaced root and re-creating the collision
|
||||||
|
// this exists to end.
|
||||||
|
|
||||||
|
public const string ChatVar = "ISLA_CHAT";
|
||||||
|
|
||||||
|
private static string _chatSlug;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Set the chat namespace batches are written under. Called once at startup by every batch
|
||||||
|
/// tool, with its authoring chat as the fallback: <c>ConfigureChat(EnvStr(ChatVar, "chat2"))</c>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slug">
|
||||||
|
/// A short domain slug — `chat1`, `chat2`, `rivers`. ⚠ It becomes a single path SEGMENT, so
|
||||||
|
/// separators are refused rather than silently creating a nested tree nobody asked for.
|
||||||
|
/// </param>
|
||||||
|
public static void ConfigureChat(string slug)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(slug))
|
||||||
|
throw new ArgumentException("A chat slug is required — batches are namespaced by chat.", nameof(slug));
|
||||||
|
string t = slug.Trim();
|
||||||
|
if (t.IndexOf('/') >= 0 || t.IndexOf('\\') >= 0 || t.IndexOf(Path.DirectorySeparatorChar) >= 0
|
||||||
|
|| t == "." || t == "..")
|
||||||
|
throw new ArgumentException(
|
||||||
|
$"Chat slug '{t}' is not a single path segment. The slug is ONE folder under batches/ — " +
|
||||||
|
"pass \"rivers\", not \"a/b\" or \"..\".", nameof(slug));
|
||||||
|
_chatSlug = t;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>The chat namespace. ⚠ Throws if <see cref="ConfigureChat"/> has not been called.</summary>
|
||||||
|
public static string ChatSlug => _chatSlug ?? throw new InvalidOperationException(
|
||||||
|
"CHAT SLUG NOT SET. Batches are namespaced by chat (batches/<chat>/NN_slug/); a tool must call " +
|
||||||
|
"ToolingPaths.ConfigureChat(...) before composing a batch path. Writing to the un-namespaced root " +
|
||||||
|
$"is what collided task numbers across chats in the first place. (Override with {ChatVar}.) — rivers/01.");
|
||||||
|
|
||||||
|
/// <summary>Whether <see cref="ConfigureChat"/> has been called.</summary>
|
||||||
|
public static bool IsChatConfigured => _chatSlug != null;
|
||||||
|
|
||||||
/// <summary>The generation config file. Override: ISLA_CONFIG_PATH.</summary>
|
/// <summary>The generation config file. Override: ISLA_CONFIG_PATH.</summary>
|
||||||
public static string ConfigPath =>
|
public static string ConfigPath =>
|
||||||
Override(ConfigPathVar) ?? Path.Combine(UserDataDir, "config.json");
|
Override(ConfigPathVar) ?? Path.Combine(UserDataDir, "config.json");
|
||||||
|
|
@ -78,6 +134,13 @@ namespace IslaApocalypse.Core
|
||||||
/// INDEX.md and a persistent scratch/. A/B comparisons are browsed by a human, and a flat
|
/// 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.
|
/// directory of same-named PNGs is not browsable.
|
||||||
///
|
///
|
||||||
|
/// ⚠⚠ THIS IS THE ROOT, NOT A BATCH, AND THE DISTINCTION IS LOAD-BEARING. Batch WRITES go
|
||||||
|
/// through <see cref="BatchRoot"/>, which inserts the <see cref="ChatSlug"/> segment. Anchor
|
||||||
|
/// READS compose against THIS, so an anchor's source string must carry its own explicit
|
||||||
|
/// `chatN/` prefix (e.g. `"chat1/02_pass1_port"`). Changing only `BatchRoot` would namespace
|
||||||
|
/// every write and silently orphan every historical read — the exact trap rivers/01 had to
|
||||||
|
/// walk through, and why <c>ShapingOracle.LoadAnchor</c> now throws on a missing anchor.
|
||||||
|
///
|
||||||
/// ⚠ PROTECTED FROM DELETION. → <see cref="FileSafety"/>.
|
/// ⚠ PROTECTED FROM DELETION. → <see cref="FileSafety"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string BatchesRoot => Path.Combine(OutputDir, "batches");
|
public static string BatchesRoot => Path.Combine(OutputDir, "batches");
|
||||||
|
|
@ -90,7 +153,7 @@ namespace IslaApocalypse.Core
|
||||||
public static string BatchScratch(string batchDir) => Path.Combine(batchDir, "scratch");
|
public static string BatchScratch(string batchDir) => Path.Combine(batchDir, "scratch");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ⭐ A BATCH ROOT: <c>batches/<task>_<descriptor>/</c>.
|
/// ⭐ A BATCH ROOT: <c>batches/<chat>/<task>_<descriptor>/</c>.
|
||||||
///
|
///
|
||||||
/// ═══ ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER. ═══
|
/// ═══ ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER. ═══
|
||||||
///
|
///
|
||||||
|
|
@ -107,6 +170,14 @@ namespace IslaApocalypse.Core
|
||||||
///
|
///
|
||||||
/// The descriptor must NOT carry its own numeric prefix; that is the mistake this method
|
/// 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.
|
/// exists to prevent, so it is refused rather than silently accepted.
|
||||||
|
///
|
||||||
|
/// ═══ ⭐ THE <chat> SEGMENT (rivers/01) ═══
|
||||||
|
///
|
||||||
|
/// Prepended from <see cref="ChatSlug"/>, because the task-number prefix restarts at 00 in
|
||||||
|
/// every chat — see the note on <see cref="ConfigureChat"/>. It is a SEPARATE segment and is
|
||||||
|
/// never folded into the descriptor: the prefix guard below fires on a descriptor starting
|
||||||
|
/// with digits, so passing `"chat2/12_drainage"` as a descriptor would be a different kind
|
||||||
|
/// of wrong.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static string BatchRoot(int taskNumber, string descriptor)
|
public static string BatchRoot(int taskNumber, string descriptor)
|
||||||
{
|
{
|
||||||
|
|
@ -127,7 +198,7 @@ namespace IslaApocalypse.Core
|
||||||
$"taskNumber and the descriptor WITHOUT one (e.g. \"review\", not \"04_review\") — " +
|
$"taskNumber and the descriptor WITHOUT one (e.g. \"review\", not \"04_review\") — " +
|
||||||
"the prefix is composed here so it cannot drift.", nameof(descriptor));
|
"the prefix is composed here so it cannot drift.", nameof(descriptor));
|
||||||
|
|
||||||
return Path.Combine(BatchesRoot, $"{taskNumber:D2}_{d}");
|
return Path.Combine(BatchesRoot, ChatSlug, $"{taskNumber:D2}_{d}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -137,6 +208,13 @@ namespace IslaApocalypse.Core
|
||||||
public static string BatchDir(int taskNumber, string descriptor, long seed, string variant)
|
public static string BatchDir(int taskNumber, string descriptor, long seed, string variant)
|
||||||
=> Path.Combine(BatchRoot(taskNumber, descriptor), $"{seed}_{variant}");
|
=> Path.Combine(BatchRoot(taskNumber, descriptor), $"{seed}_{variant}");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Resolve a HISTORICAL batch by its namespaced name, e.g. <c>"chat1/02_pass1_port"</c> — the
|
||||||
|
/// form every `ISLA_*_SOURCE` anchor default takes since rivers/01. Kept beside
|
||||||
|
/// <see cref="BatchRoot"/> so a READ and a WRITE are visibly two different operations.
|
||||||
|
/// </summary>
|
||||||
|
public static string BatchSource(string namespacedName) => Path.Combine(BatchesRoot, namespacedName);
|
||||||
|
|
||||||
private static string Override(string variable)
|
private static string Override(string variable)
|
||||||
{
|
{
|
||||||
string v = Environment.GetEnvironmentVariable(variable);
|
string v = Environment.GetEnvironmentVariable(variable);
|
||||||
|
|
@ -149,7 +227,8 @@ namespace IslaApocalypse.Core
|
||||||
$"config : {ConfigPath}{Marker(ConfigPathVar)}\n" +
|
$"config : {ConfigPath}{Marker(ConfigPathVar)}\n" +
|
||||||
$"blueprints: {BlueprintPath}{Marker(BlueprintPathVar)}\n" +
|
$"blueprints: {BlueprintPath}{Marker(BlueprintPathVar)}\n" +
|
||||||
$"output : {OutputDir}{Marker(OutputDirVar)}\n" +
|
$"output : {OutputDir}{Marker(OutputDirVar)}\n" +
|
||||||
$"batches : {BatchesRoot}";
|
$"batches : {BatchesRoot}\n" +
|
||||||
|
$"chat : {(IsChatConfigured ? ChatSlug : "⚠ NOT SET")} → writes land under batches/{(IsChatConfigured ? ChatSlug : "<chat>")}/NN_slug/";
|
||||||
|
|
||||||
private static string Marker(string variable) => Override(variable) != null ? $" [{variable}]" : "";
|
private static string Marker(string variable) => Override(variable) != null ? $" [{variable}]" : "";
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -343,9 +343,15 @@ Enforced by `Core/Scripts/FileSafety.cs`, which throws rather than advises.
|
||||||
### 4. Batch layout
|
### 4. Batch layout
|
||||||
|
|
||||||
```
|
```
|
||||||
batches/<task>_<descriptor>/<seed>_<variant>/
|
batches/<chat>/<task>_<descriptor>/<seed>_<variant>/
|
||||||
batches/<task>_<descriptor>/INDEX.md
|
batches/<chat>/<task>_<descriptor>/INDEX.md
|
||||||
batches/<task>_<descriptor>/scratch/ ← persistent; never cleaned
|
batches/<chat>/<task>_<descriptor>/scratch/ ← persistent; never cleaned
|
||||||
|
|
||||||
|
⚠ <chat> is required (rivers/01): task numbers restart per chat, so a flat batches/
|
||||||
|
collided across chat1 and chat2. Set by ToolingPaths.ConfigureChat(), overridable
|
||||||
|
with ISLA_CHAT. Historical anchor READS carry the prefix in their own source string
|
||||||
|
(e.g. "chat1/02_pass1_port"), because they compose against BatchesRoot, not BatchRoot.
|
||||||
|
→ Tools/batches/README.md
|
||||||
```
|
```
|
||||||
|
|
||||||
> ### ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER.
|
> ### ⚠⚠ THE PREFIX IS THE AUTHORING TASK NUMBER. IT IS NOT A COUNTER.
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,6 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_SPECK_FRAC the speck-revert threshold, fraction of map area (default 2.5e-7 ≈ 4 cells at 4096)
|
/// ISLA_SPECK_FRAC the speck-revert threshold, fraction of map area (default 2.5e-7 ≈ 4 cells at 4096)
|
||||||
/// ISLA_PROBE=1 · ISLA_PROBE_FREQS · ISLA_PROBE_AMPS the probe sweep
|
/// ISLA_PROBE=1 · ISLA_PROBE_FREQS · ISLA_PROBE_AMPS the probe sweep
|
||||||
/// ISLA_FRAG_BITES=1 bites-only noise ([0,1]) instead of zero-mean ([-1,1])
|
/// ISLA_FRAG_BITES=1 bites-only noise ([0,1]) instead of zero-mean ([-1,1])
|
||||||
/// ISLA_SKIP_8K=1 (no 8192 check this batch — the 08 dump at 4096 is the baseline)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CoastalFragmentTool : Node
|
public partial class CoastalFragmentTool : Node
|
||||||
{
|
{
|
||||||
|
|
@ -86,6 +85,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 9);
|
int task = EnvInt("ISLA_TASK", 9);
|
||||||
string descr = EnvStr("ISLA_BATCH", "coastal_fragment");
|
string descr = EnvStr("ISLA_BATCH", "coastal_fragment");
|
||||||
|
|
@ -101,9 +104,7 @@ namespace IslaApocalypse.Tools
|
||||||
float[] probeFreqs = EnvFloats("ISLA_PROBE_FREQS", ProbeFreqs);
|
float[] probeFreqs = EnvFloats("ISLA_PROBE_FREQS", ProbeFreqs);
|
||||||
float[] probeAmps = EnvFloats("ISLA_PROBE_AMPS", ProbeAmps);
|
float[] probeAmps = EnvFloats("ISLA_PROBE_AMPS", ProbeAmps);
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t08Source = EnvStr("ISLA_T08_SOURCE", "08_southern_stretch_explore");
|
|
||||||
string t08Level = EnvStr("ISLA_T08_LEVEL", "stretch_3"); // the 08 rung with stretch 2
|
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
||||||
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
||||||
|
|
@ -132,16 +133,26 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
TerrainGenConfig Cfg(int size, int seed, string label, float amp, float fq, float st, bool revert)
|
TerrainGenConfig Cfg(int size, int seed, string label, float amp, float fq, float st, bool revert)
|
||||||
{
|
{
|
||||||
return new TerrainGenConfig
|
// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT:
|
||||||
|
// it was authored and judged before the shape family existed, and its regression checks
|
||||||
|
// hold pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the
|
||||||
|
// bare defaults family-ON, so without this pin every config here would silently acquire
|
||||||
|
// stretch + fragmentation and every anchor check would fail for a configuration reason.
|
||||||
|
// → TerrainGenConfig.WithFamilyOff().
|
||||||
|
var c = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = size, Seed = seed, VariantLabel = label,
|
MapSize = size, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
||||||
CoastShelf = false, Offshore = new OffshoreSettings(),
|
RegionLabeling = true,
|
||||||
RegionLabeling = true, SpeckRevert = revert, MinLandComponentFrac = speckFrac,
|
}.WithFamilyOff();
|
||||||
SouthStretch = st,
|
// …then this tool's swept axes, AFTER the pin. (These were already explicit before
|
||||||
FragmentAmp = amp, FragmentFreqPerMapWidth = fq, FragmentBitesOnly = bitesOnly,
|
// rivers/01; the pin makes the tool's independence from the defaults total rather than
|
||||||
};
|
// field-by-field, so a future default can never leak in through a field nobody listed.)
|
||||||
|
c.SpeckRevert = revert; c.MinLandComponentFrac = speckFrac;
|
||||||
|
c.SouthStretch = st;
|
||||||
|
c.FragmentAmp = amp; c.FragmentFreqPerMapWidth = fq; c.FragmentBitesOnly = bitesOnly;
|
||||||
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ PROBE ═══
|
// ═══ PROBE ═══
|
||||||
|
|
@ -186,21 +197,17 @@ namespace IslaApocalypse.Tools
|
||||||
var offCfg = Cfg(calibSize, seeds[0], "off", 0f, freq, 0f, false);
|
var offCfg = Cfg(calibSize, seeds[0], "off", 0f, freq, 0f, false);
|
||||||
Pass1Result p1 = Topography.Generate(offCfg);
|
Pass1Result p1 = Topography.Generate(offCfg);
|
||||||
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{seeds[0]}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{seeds[0]}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF, frag OFF == Phase-1 .f32 dump (the curve is untouched)", Shaping.Shape(p1, curveOff).Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF, frag OFF == Phase-1 .f32 dump (the curve is untouched)",
|
||||||
|
Shaping.Shape(p1, curveOff).Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump));
|
||||||
|
|
||||||
// ⭐ a8 — frag OFF at the fixed stretch, revert OFF == the task-08 stretch-2 field (its dump at the plate size).
|
// ⚑ RETIRED at rivers/01 — a8, the frag-OFF baseline == `08_southern_stretch_explore`.
|
||||||
foreach (int seed in seeds)
|
// An EXPLORATION ladder, and off-shape: the 08 batch's dump is at stretch 3, the locked
|
||||||
{
|
// shape is stretch 2. Nothing should be pinned to a rung of a ladder that was climbed to
|
||||||
string t08Dump = Path.Combine(ToolingPaths.BatchesRoot, t08Source, $"{seed}_{t08Level}", "height.f32");
|
// find a value, then superseded by the value it found.
|
||||||
if (File.Exists(t08Dump) && mapSize == 4096)
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
{
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
var c8 = Cfg(mapSize, seed, "t08", 0f, freq, stretch, false);
|
|
||||||
Pass2Result q8 = Shaping.Shape(Topography.Generate(c8), c8);
|
|
||||||
hard.Add(ShapingOracle.DumpRegression("a8", $"frag OFF, stretch {stretch:G3}, revert OFF == task-08 {t08Level} dump (the baseline) [{seed}]", q8.Height, HeightField.Load(t08Dump, mapSize), mapSize, t08Dump));
|
|
||||||
}
|
|
||||||
else GD.Print($" a8 [{seed}]: ⚠ skipped — {(mapSize != 4096 ? "map size is not 4096" : $"no 08 dump at {t08Dump}")}");
|
|
||||||
}
|
|
||||||
foreach (var c in hard) GD.Print(" " + c);
|
foreach (var c in hard) GD.Print(" " + c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -311,7 +318,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s));
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -324,11 +331,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_SHOWPIECE_SIZE larger confirmation profile (default 8192)
|
/// ISLA_SHOWPIECE_SIZE larger confirmation profile (default 8192)
|
||||||
/// ISLA_SHOWPIECE "0" to skip the big render
|
/// ISLA_SHOWPIECE "0" to skip the big render
|
||||||
/// ISLA_VARIANTS "0" to skip the per-seed variants (calibration-only probe)
|
/// ISLA_VARIANTS "0" to skip the per-seed variants (calibration-only probe)
|
||||||
/// ISLA_PHASE1_SOURCE batch holding Phase-1 .f32 (default "02_pass1_port")
|
/// ISLA_PHASE1_SOURCE batch holding Phase-1 .f32 (default "chat1/02_pass1_port")
|
||||||
/// ISLA_SKIP_RAW "1" to skip the .f32 dumps
|
/// ISLA_SKIP_RAW "1" to skip the .f32 dumps
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class CurveBaselineTool : Node
|
public partial class CurveBaselineTool : Node
|
||||||
|
|
@ -86,6 +86,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 1);
|
int task = EnvInt("ISLA_TASK", 1);
|
||||||
string descr = EnvStr("ISLA_BATCH", "curve_baseline");
|
string descr = EnvStr("ISLA_BATCH", "curve_baseline");
|
||||||
|
|
@ -94,7 +98,7 @@ namespace IslaApocalypse.Tools
|
||||||
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
||||||
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
||||||
bool variants = EnvStr("ISLA_VARIANTS", "1") == "1";
|
bool variants = EnvStr("ISLA_VARIANTS", "1") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
|
|
||||||
// ⚠ Composed by BatchRoot, never free-form — it refuses a descriptor carrying its own
|
// ⚠ Composed by BatchRoot, never free-form — it refuses a descriptor carrying its own
|
||||||
|
|
@ -131,7 +135,7 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
foreach (int seed in seeds)
|
foreach (int seed in seeds)
|
||||||
{
|
{
|
||||||
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seed };
|
var cfg = TerrainGenConfig.CalibrationPool(mapSize, seed);
|
||||||
Pass1Result p1 = Topography.Generate(cfg);
|
Pass1Result p1 = Topography.Generate(cfg);
|
||||||
pass1[seed] = p1;
|
pass1[seed] = p1;
|
||||||
rawPool.Accumulate(p1.Height, mapSize);
|
rawPool.Accumulate(p1.Height, mapSize);
|
||||||
|
|
@ -289,8 +293,16 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
// ---- configs --------------------------------------------------------
|
// ---- configs --------------------------------------------------------
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This is chat-2 CURVE development: authored
|
||||||
|
/// and judged before the shape family existed, on the family-off distribution the knots are
|
||||||
|
/// percentiles of. The re-baseline flipped the bare defaults family-ON, so the pin is what
|
||||||
|
/// keeps this tool measuring the thing it was written to measure.
|
||||||
|
/// → <see cref="TerrainGenConfig.WithFamilyOff"/>.
|
||||||
|
/// </remarks>
|
||||||
private static TerrainGenConfig OffConfig(int mapSize, int seed) =>
|
private static TerrainGenConfig OffConfig(int mapSize, int seed) =>
|
||||||
new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "curve_off", Curve = false, ShelfDetail = false };
|
new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "curve_off", Curve = false, ShelfDetail = false }
|
||||||
|
.WithFamilyOff();
|
||||||
|
|
||||||
private static TerrainGenConfig OnConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a) =>
|
private static TerrainGenConfig OnConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a) =>
|
||||||
new TerrainGenConfig
|
new TerrainGenConfig
|
||||||
|
|
@ -301,7 +313,7 @@ namespace IslaApocalypse.Tools
|
||||||
// bit-for-bit. chat2/02 moved the config DEFAULT to Continuous for its exploration;
|
// bit-for-bit. chat2/02 moved the config DEFAULT to Continuous for its exploration;
|
||||||
// a control batch must not move with a default. (chat2/02.)
|
// a control batch must not move with a default. (chat2/02.)
|
||||||
CurveMode = CurveModeKind.Staircase,
|
CurveMode = CurveModeKind.Staircase,
|
||||||
};
|
}.WithFamilyOff(); // ⭐ rivers/01 — see OffConfig: the staircase control is pre-family too
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fraction of land below the CostaRica palette's third stop (0.310 raw). A blunt
|
/// Fraction of land below the CostaRica palette's third stop (0.310 raw). A blunt
|
||||||
|
|
|
||||||
|
|
@ -48,8 +48,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_SEEDS variant seeds, comma-separated (default: the 2 pinned below)
|
/// ISLA_SEEDS variant seeds, comma-separated (default: the 2 pinned below)
|
||||||
/// ISLA_SHOWPIECE_SIZE the big confirmation render (default 8192)
|
/// ISLA_SHOWPIECE_SIZE the big confirmation render (default 8192)
|
||||||
/// ISLA_SHOWPIECE "0" to skip it
|
/// ISLA_SHOWPIECE "0" to skip it
|
||||||
/// ISLA_PHASE1_SOURCE batch holding Phase-1 .f32 (default "02_pass1_port")
|
/// ISLA_PHASE1_SOURCE batch holding Phase-1 .f32 (default "chat1/02_pass1_port")
|
||||||
/// ISLA_T01_SOURCE batch holding task-01 .f32 (default "01_curve_baseline")
|
|
||||||
/// ISLA_SKIP_RAW "1" to skip the .f32 dumps
|
/// ISLA_SKIP_RAW "1" to skip the .f32 dumps
|
||||||
/// ISLA_CEILING_M probe override: lowland ceiling, metres (default 30)
|
/// ISLA_CEILING_M probe override: lowland ceiling, metres (default 30)
|
||||||
/// ISLA_FEATHER probe override: climb feather, 0..1 (default 0.4)
|
/// ISLA_FEATHER probe override: climb feather, 0..1 (default 0.4)
|
||||||
|
|
@ -91,6 +90,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 2);
|
int task = EnvInt("ISLA_TASK", 2);
|
||||||
string descr = EnvStr("ISLA_BATCH", "curve_continuous");
|
string descr = EnvStr("ISLA_BATCH", "curve_continuous");
|
||||||
|
|
@ -98,8 +101,7 @@ namespace IslaApocalypse.Tools
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
||||||
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t01Source = EnvStr("ISLA_T01_SOURCE", "01_curve_baseline");
|
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr); // composed, never free-form
|
string batchRoot = ToolingPaths.BatchRoot(task, descr); // composed, never free-form
|
||||||
|
|
@ -128,7 +130,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int seed in CalibrationSeeds)
|
foreach (int seed in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = mapSize, Seed = seed });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(mapSize, seed));
|
||||||
pass1[seed] = p1;
|
pass1[seed] = p1;
|
||||||
rawPool.Accumulate(p1.Height, mapSize);
|
rawPool.Accumulate(p1.Height, mapSize);
|
||||||
GD.Print($" pooled seed {seed,-11} h[{p1.HMinSeed,7:F3} .. {p1.HMaxSeed,6:F3}] {p1.ElapsedMs,5} ms");
|
GD.Print($" pooled seed {seed,-11} h[{p1.HMinSeed,7:F3} .. {p1.HMaxSeed,6:F3}] {p1.ElapsedMs,5} ms");
|
||||||
|
|
@ -187,15 +189,20 @@ namespace IslaApocalypse.Tools
|
||||||
{
|
{
|
||||||
Pass1Result pp1 = pass1[primary];
|
Pass1Result pp1 = pass1[primary];
|
||||||
|
|
||||||
// (a1) curve off == Phase 1's own dump.
|
// (a1) curve off == Phase 1's own dump. ⭐ KEPT at rivers/01: the family-off pass-1 guard,
|
||||||
|
// the last link between today's generator and the Phase-1 port. The config is pinned
|
||||||
|
// family-off so it still means what it says. ⚠ A missing dump now THROWS.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF == Phase-1 .f32 dump",
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF == Phase-1 .f32 dump",
|
||||||
offs[primary].Height, HeightField.Load(p1Dump, mapSize), mapSize, p1Dump));
|
offs[primary].Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, mapSize), mapSize, p1Dump));
|
||||||
|
|
||||||
// (a2) staircase == task 01's own dump — the control is the control.
|
// ⚑ RETIRED at rivers/01 — a2, the staircase == `01_curve_baseline` control.
|
||||||
string t01Dump = Path.Combine(ToolingPaths.BatchesRoot, t01Source, $"{primary}_curve_on", "height.f32");
|
// The staircase curve is SUPERSEDED by the continuous grade (→ D-062). A control that
|
||||||
hard.Add(ShapingOracle.DumpRegression("a2", "staircase mode == task-01 curve_on .f32 dump",
|
// reproduces a curve nothing ships is scaffolding, and holding it green cost a
|
||||||
results[(primary, "staircase")].Height, HeightField.Load(t01Dump, mapSize), mapSize, t01Dump));
|
// 4-variant batch run to prove a mode no design doc describes any more.
|
||||||
|
// The dump is NOT deleted (file-safety; it is regenerable and it is the record of what
|
||||||
|
// was judged); its `INDEX.md` is marked superseded. The check is gone so nothing can
|
||||||
|
// pass against a superseded baseline. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
|
|
||||||
// (b) classify == raw, every seed × every variant.
|
// (b) classify == raw, every seed × every variant.
|
||||||
long bFail = 0;
|
long bFail = 0;
|
||||||
|
|
@ -340,7 +347,11 @@ namespace IslaApocalypse.Tools
|
||||||
LowlandCeilingM = EnvFloat("ISLA_CEILING_M", 30f),
|
LowlandCeilingM = EnvFloat("ISLA_CEILING_M", 30f),
|
||||||
ClimbFeather = EnvFloat("ISLA_FEATHER", 0.4f),
|
ClimbFeather = EnvFloat("ISLA_FEATHER", 0.4f),
|
||||||
SummitDrama = EnvFloat("ISLA_DRAMA", 2.5f),
|
SummitDrama = EnvFloat("ISLA_DRAMA", 2.5f),
|
||||||
};
|
}
|
||||||
|
// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. chat2/02 is CURVE development, measured on
|
||||||
|
// the family-off distribution the knots are percentiles of; the re-baseline flipped the bare
|
||||||
|
// defaults family-ON. → TerrainGenConfig.WithFamilyOff().
|
||||||
|
.WithFamilyOff();
|
||||||
|
|
||||||
// ---- output -----------------------------------------------------------
|
// ---- output -----------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 12);
|
int task = EnvInt("ISLA_TASK", 12);
|
||||||
string descr = EnvStr("ISLA_BATCH", "drainage_analysis");
|
string descr = EnvStr("ISLA_BATCH", "drainage_analysis");
|
||||||
|
|
@ -62,7 +66,7 @@ namespace IslaApocalypse.Tools
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
bool skipT11 = EnvStr("ISLA_SKIP_T11_CHECK", "0") == "1";
|
bool skipT11 = EnvStr("ISLA_SKIP_T11_CHECK", "0") == "1";
|
||||||
string t11Source = EnvStr("ISLA_T11_SOURCE", "11_erosion");
|
string t11Source = EnvStr("ISLA_T11_SOURCE", "chat2/11_erosion");
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
||||||
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
||||||
|
|
@ -77,7 +81,11 @@ namespace IslaApocalypse.Tools
|
||||||
GD.Print("==================================================================");
|
GD.Print("==================================================================");
|
||||||
GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize}");
|
GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize}");
|
||||||
GD.Print($"seeds : {string.Join(", ", seeds)}");
|
GD.Print($"seeds : {string.Join(", ", seeds)}");
|
||||||
GD.Print($"terrain : {TerrainShapeV1.Describe()} + erosion ON (faithful tune) — the task-11 erosion_on field");
|
// ⚠ rivers/01: the shape AND erosion now come from the defaults, so both are asserted before
|
||||||
|
// anything generates. A drift here would silently re-baseline every river measurement.
|
||||||
|
TerrainShapeV1.Assert("DrainageTool");
|
||||||
|
TerrainShapeV1.AssertErosionDefaultOn("DrainageTool");
|
||||||
|
GD.Print($"terrain : {TerrainShapeV1.Describe()} + erosion ON by default (faithful tune) — the task-11 erosion_on field");
|
||||||
GD.Print($"params : endorheic depth ≥ {dp.EndorheicMinDepthM} m, area ≥ {dp.EndorheicMinAreaPx}, inflow ≥ {dp.EndorheicMinInflowPx}, max {dp.EndorheicMaxCount} · trunks {dp.TrunkCount} sep {dp.MinOutletSeparationPx} px · giants {dp.GiantCount} · stem ≥ {dp.StemMinAccPx} · tributary ≥ {dp.TributaryMinAccPx} (max {dp.TributaryMaxPerTrunk}) · exit grade {dp.ExitGradeMin} m/px over {dp.ExitWindowPx} px");
|
GD.Print($"params : endorheic depth ≥ {dp.EndorheicMinDepthM} m, area ≥ {dp.EndorheicMinAreaPx}, inflow ≥ {dp.EndorheicMinInflowPx}, max {dp.EndorheicMaxCount} · trunks {dp.TrunkCount} sep {dp.MinOutletSeparationPx} px · giants {dp.GiantCount} · stem ≥ {dp.StemMinAccPx} · tributary ≥ {dp.TributaryMinAccPx} (max {dp.TributaryMaxPerTrunk}) · exit grade {dp.ExitGradeMin} m/px over {dp.ExitWindowPx} px");
|
||||||
GD.Print($"batch : {batchRoot}");
|
GD.Print($"batch : {batchRoot}");
|
||||||
GD.Print("==================================================================");
|
GD.Print("==================================================================");
|
||||||
|
|
@ -88,14 +96,15 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
TerrainGenConfig Cfg(int size, int seed)
|
TerrainGenConfig Cfg(int size, int seed)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: THE SHAPE AND EROSION COME FROM THE BARE DEFAULTS. `TerrainShapeV1.Apply(c)`
|
||||||
|
// and `c.Erosion = true` used to sit here; both are now what `new TerrainGenConfig()`
|
||||||
|
// carries. Only the CURVE (measured this run) is set.
|
||||||
var c = new TerrainGenConfig
|
var c = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = size, Seed = seed, VariantLabel = "drainage",
|
MapSize = size, Seed = seed, VariantLabel = "drainage",
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
||||||
};
|
};
|
||||||
TerrainShapeV1.Apply(c);
|
|
||||||
c.Erosion = true; // the faithful tune — the defaults
|
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,15 +123,16 @@ namespace IslaApocalypse.Tools
|
||||||
Pass2Result p2 = ero.Shaped;
|
Pass2Result p2 = ero.Shaped;
|
||||||
GD.Print($" terrain ready ({p1.ElapsedMs} ms pass 1, erosion {ero.Ms / 1000.0:F1} s)");
|
GD.Print($" terrain ready ({p1.ElapsedMs} ms pass 1, erosion {ero.Ms / 1000.0:F1} s)");
|
||||||
|
|
||||||
|
// ⭐ a11 — THE EROSION ACCEPTANCE ANCHOR (rivers/01 keeps this one). Since the re-baseline
|
||||||
|
// the whole chain — shape AND erosion — comes from the bare defaults, so this is the
|
||||||
|
// standing proof that the terrain every river measurement rests on has not moved.
|
||||||
|
// ⚠ A missing dump now THROWS (ShapingOracle.LoadAnchor) instead of skipping silently.
|
||||||
if (!skipT11)
|
if (!skipT11)
|
||||||
{
|
{
|
||||||
string dump = Path.Combine(ToolingPaths.BatchesRoot, t11Source, $"{seed}_erosion_on", "height.f32");
|
string dump = Path.Combine(ToolingPaths.BatchesRoot, t11Source, $"{seed}_erosion_on", "height.f32");
|
||||||
if (File.Exists(dump) && mapSize == 8192)
|
var a11 = ShapingOracle.DumpRegression("a11", $"the eroded render field from the BARE DEFAULTS == the task-11 erosion_on dump (the terrain the developer saw) [{seed}]",
|
||||||
{
|
p2.Height, ShapingOracle.LoadAnchor("a11", "ISLA_T11_SOURCE", dump, mapSize), mapSize, dump);
|
||||||
var a11 = ShapingOracle.DumpRegression("a11", $"the eroded render field == the task-11 erosion_on dump (the terrain the developer saw) [{seed}]", p2.Height, HeightField.Load(dump, mapSize), mapSize, dump);
|
hard.Add(a11); GD.Print(" " + a11);
|
||||||
hard.Add(a11); GD.Print(" " + a11);
|
|
||||||
}
|
|
||||||
else GD.Print($" a11 [{seed}]: ⚠ skipped — {(mapSize != 8192 ? "map size is not the 11 batch's 8192" : $"no dump at {dump}")}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ⭐ THE OCEAN IDENTITY — from the region layer, on the CLASSIFY field.
|
// ⭐ THE OCEAN IDENTITY — from the region layer, on the CLASSIFY field.
|
||||||
|
|
@ -284,7 +294,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s));
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -297,11 +307,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,6 @@ using IslaApocalypse.Core;
|
||||||
|
|
||||||
namespace IslaApocalypse.Tools
|
namespace IslaApocalypse.Tools
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// ⭐ THE LOCKED SHAPE — `terrain-shape-v1` (chat2/10 gallery-confirmed): the continuous curve + the
|
|
||||||
/// frag_4 organic islands. Every later pass (erosion, rivers, …) starts from exactly these values,
|
|
||||||
/// pinned here once so no tool re-types them.
|
|
||||||
/// </summary>
|
|
||||||
public static class TerrainShapeV1
|
|
||||||
{
|
|
||||||
public const float FragmentAmp = 0.5f, FragmentFreq = 12f, BandCentre = 0.66f, BandHalfWidth = 0.18f;
|
|
||||||
public const bool BitesOnly = false;
|
|
||||||
public const float Stretch = 2f, BandStart = 0.70f, BandFeather = 0.05f;
|
|
||||||
public const bool StretchSinker = true;
|
|
||||||
public const float SpeckFrac = 2.5e-7f;
|
|
||||||
|
|
||||||
/// <summary>Apply the locked shape to a config (curve settings are the caller's — they come from the calibration).</summary>
|
|
||||||
public static void Apply(TerrainGenConfig c)
|
|
||||||
{
|
|
||||||
c.CoastShelf = false; c.Offshore = new OffshoreSettings();
|
|
||||||
c.RegionLabeling = true; c.SpeckRevert = true; c.MinLandComponentFrac = SpeckFrac;
|
|
||||||
c.SouthStretch = Stretch; c.SouthBandStartFrac = BandStart; c.SouthBandFeatherFrac = BandFeather; c.StretchSinker = StretchSinker;
|
|
||||||
c.FragmentAmp = FragmentAmp; c.FragmentFreqPerMapWidth = FragmentFreq;
|
|
||||||
c.FragmentBandCentre = BandCentre; c.FragmentBandHalfWidth = BandHalfWidth; c.FragmentBitesOnly = BitesOnly;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Describe() =>
|
|
||||||
$"terrain-shape-v1: frag amp {FragmentAmp} freq {FragmentFreq} window {BandCentre}±{BandHalfWidth} · stretch {Stretch} (band {BandStart}/{BandFeather}, sinker stretched) · speck revert {SpeckFrac:G2} · offshore OFF · shelf OFF · labeling ON";
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ⭐ THE EROSION BATCH (chat2/11) — the faithful droplet erosion on the locked shape, judged across
|
/// ⭐ THE EROSION BATCH (chat2/11) — the faithful droplet erosion on the locked shape, judged across
|
||||||
/// seeds, erosion OFF vs ON. 4 seeds from the task-10 gallery × {off, on} = 8 fields at showpiece
|
/// seeds, erosion OFF vs ON. 4 seeds from the task-10 gallery × {off, on} = 8 fields at showpiece
|
||||||
|
|
@ -85,6 +58,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 11);
|
int task = EnvInt("ISLA_TASK", 11);
|
||||||
string descr = EnvStr("ISLA_BATCH", "erosion");
|
string descr = EnvStr("ISLA_BATCH", "erosion");
|
||||||
|
|
@ -93,7 +70,7 @@ namespace IslaApocalypse.Tools
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
bool skipTag = EnvStr("ISLA_SKIP_TAG_CHECK", "0") == "1";
|
bool skipTag = EnvStr("ISLA_SKIP_TAG_CHECK", "0") == "1";
|
||||||
string t10Source = EnvStr("ISLA_T10_SOURCE", "10_frag4_seed_gallery");
|
string t10Source = EnvStr("ISLA_T10_SOURCE", "chat2/10_frag4_seed_gallery");
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
||||||
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
||||||
|
|
@ -107,6 +84,7 @@ namespace IslaApocalypse.Tools
|
||||||
GD.Print("==================================================================");
|
GD.Print("==================================================================");
|
||||||
GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize}");
|
GD.Print($"MapSize : {mapSize} curve calibrated at {calibSize}");
|
||||||
GD.Print($"seeds : {string.Join(", ", seeds)}");
|
GD.Print($"seeds : {string.Join(", ", seeds)}");
|
||||||
|
TerrainShapeV1.Assert("ErosionTool"); // ⚠ rivers/01: refuse to render if the defaults drifted off the locked shape
|
||||||
GD.Print($"shape : {TerrainShapeV1.Describe()}");
|
GD.Print($"shape : {TerrainShapeV1.Describe()}");
|
||||||
GD.Print($"batch : {batchRoot}");
|
GD.Print($"batch : {batchRoot}");
|
||||||
GD.Print("==================================================================");
|
GD.Print("==================================================================");
|
||||||
|
|
@ -117,13 +95,17 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
TerrainGenConfig Cfg(int size, int seed, string label, bool erosion)
|
TerrainGenConfig Cfg(int size, int seed, string label, bool erosion)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: THE SHAPE COMES FROM THE BARE DEFAULTS. `TerrainShapeV1.Apply(c)` used to
|
||||||
|
// sit here; the locked shape is now what `new TerrainGenConfig()` produces, so stamping
|
||||||
|
// a preset on top would MASK a default drift instead of catching it. Only the CURVE
|
||||||
|
// (measured this run) and the per-variant erosion flag are set.
|
||||||
|
// → TerrainShapeV1.Assert(), called before any generation below.
|
||||||
var c = new TerrainGenConfig
|
var c = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = size, Seed = seed, VariantLabel = label,
|
MapSize = size, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
||||||
};
|
};
|
||||||
TerrainShapeV1.Apply(c);
|
|
||||||
c.Erosion = erosion;
|
c.Erosion = erosion;
|
||||||
c.ErosionDropletCount = EnvInt("ISLA_ERO_COUNT", c.ErosionDropletCount);
|
c.ErosionDropletCount = EnvInt("ISLA_ERO_COUNT", c.ErosionDropletCount);
|
||||||
c.ErosionDropletLifetime = EnvInt("ISLA_ERO_LIFETIME", c.ErosionDropletLifetime);
|
c.ErosionDropletLifetime = EnvInt("ISLA_ERO_LIFETIME", c.ErosionDropletLifetime);
|
||||||
|
|
@ -156,12 +138,15 @@ namespace IslaApocalypse.Tools
|
||||||
var cOff = Cfg(mapSize, seed, "erosion_off", false);
|
var cOff = Cfg(mapSize, seed, "erosion_off", false);
|
||||||
Pass1Result p1Off = Topography.Generate(cOff);
|
Pass1Result p1Off = Topography.Generate(cOff);
|
||||||
Pass2Result p2Off = Shaping.Shape(p1Off, cOff);
|
Pass2Result p2Off = Shaping.Shape(p1Off, cOff);
|
||||||
|
// ⭐ a10 — THE SHAPE ACCEPTANCE ANCHOR (rivers/01 keeps this one). Since the re-baseline
|
||||||
|
// `p2Off` is generated from the BARE DEFAULTS, so this check is now the standing proof
|
||||||
|
// that the defaults still reproduce `terrain-shape-v1`.
|
||||||
|
// ⚠ A missing dump now THROWS (ShapingOracle.LoadAnchor) instead of skipping silently.
|
||||||
if (!skipTag)
|
if (!skipTag)
|
||||||
{
|
{
|
||||||
string dump = Path.Combine(ToolingPaths.BatchesRoot, t10Source, $"{seed}", "height.f32");
|
string dump = Path.Combine(ToolingPaths.BatchesRoot, t10Source, $"{seed}", "height.f32");
|
||||||
if (File.Exists(dump) && mapSize == 8192)
|
hard.Add(ShapingOracle.DumpRegression("a10", $"erosion OFF from the BARE DEFAULTS == terrain-shape-v1 (the task-10 gallery dump) [{seed}]",
|
||||||
hard.Add(ShapingOracle.DumpRegression("a10", $"erosion OFF == terrain-shape-v1 (the task-10 gallery dump) [{seed}]", p2Off.Height, HeightField.Load(dump, mapSize), mapSize, dump));
|
p2Off.Height, ShapingOracle.LoadAnchor("a10", "ISLA_T10_SOURCE", dump, mapSize), mapSize, dump));
|
||||||
else GD.Print($" a10 [{seed}]: ⚠ skipped — {(mapSize != 8192 ? "map size is not the gallery's 8192" : $"no gallery dump at {dump}")}");
|
|
||||||
}
|
}
|
||||||
Image reliefOff = ReliefRenderer.Render(p2Off.Height, mapSize, look);
|
Image reliefOff = ReliefRenderer.Render(p2Off.Height, mapSize, look);
|
||||||
Image shadeOff = ShadeRenderer.Render(p2Off.Height, mapSize, sea, ShadeZ, look.LightAzimuth, look.LightAltitude);
|
Image shadeOff = ShadeRenderer.Render(p2Off.Height, mapSize, sea, ShadeZ, look.LightAzimuth, look.LightAltitude);
|
||||||
|
|
@ -281,7 +266,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s));
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -294,11 +279,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_MAPSIZE gallery size (default 8192)
|
/// ISLA_MAPSIZE gallery size (default 8192)
|
||||||
/// ISLA_CALIB_SIZE curve calibration size (default 2048)
|
/// ISLA_CALIB_SIZE curve calibration size (default 2048)
|
||||||
/// ISLA_SEEDS the gallery seeds (default: the 2 anchors + 6 fresh below)
|
/// ISLA_SEEDS the gallery seeds (default: the 2 anchors + 6 fresh below)
|
||||||
/// ISLA_SKIP_ANCHOR_CHECK=1 skip the 4096 bit-identity check against the 09 frag_4 dumps
|
/// ISLA_SKIP_ANCHOR_CHECK=1 skip the 4096 interior-locked invariant check
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class FragGalleryTool : Node
|
public partial class FragGalleryTool : Node
|
||||||
{
|
{
|
||||||
|
|
@ -35,17 +35,22 @@ namespace IslaApocalypse.Tools
|
||||||
/// <summary>⚠ Task 01's pool, verbatim — the curve's identity.</summary>
|
/// <summary>⚠ Task 01's pool, verbatim — the curve's identity.</summary>
|
||||||
private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 };
|
private static readonly int[] CalibrationSeeds = { 1063685222, 20260819, 777001, 424242, 90210, 31337 };
|
||||||
|
|
||||||
// ═══ THE FROZEN frag_4 SETTING — every value pinned explicitly (chat2/09 batch, level 4) ═══
|
// ═══ THE FROZEN frag_4 SETTING (chat2/09 batch, level 4) ═══
|
||||||
private const float FrozenFragmentAmp = 0.5f;
|
//
|
||||||
private const float FrozenFragmentFreq = 12f;
|
// ⭐ rivers/01: these were the ONLY home of the locked values. They now ALIAS
|
||||||
private const float FrozenBandCentre = 0.66f;
|
// `TerrainShapeV1`, which is itself the assertion target for `TerrainGenConfig`'s defaults —
|
||||||
private const float FrozenBandHalfWidth = 0.18f;
|
// so the chain is: bare defaults → asserted against TerrainShapeV1 → printed here. One value,
|
||||||
private const bool FrozenBitesOnly = false;
|
// one place, and a throw if the generator ever stops agreeing with it.
|
||||||
private const float FrozenStretch = 2f;
|
private const float FrozenFragmentAmp = TerrainShapeV1.FragmentAmp;
|
||||||
private const float FrozenBandStart = 0.70f;
|
private const float FrozenFragmentFreq = TerrainShapeV1.FragmentFreq;
|
||||||
private const float FrozenBandFeather = 0.05f;
|
private const float FrozenBandCentre = TerrainShapeV1.BandCentre;
|
||||||
private const bool FrozenStretchSinker = true;
|
private const float FrozenBandHalfWidth = TerrainShapeV1.BandHalfWidth;
|
||||||
private const float FrozenSpeckFrac = 2.5e-7f; // 09's low speck revert (≈ 4 cells at 4096, ≈ 17 at 8192)
|
private const bool FrozenBitesOnly = TerrainShapeV1.BitesOnly;
|
||||||
|
private const float FrozenStretch = TerrainShapeV1.Stretch;
|
||||||
|
private const float FrozenBandStart = TerrainShapeV1.BandStart;
|
||||||
|
private const float FrozenBandFeather = TerrainShapeV1.BandFeather;
|
||||||
|
private const bool FrozenStretchSinker = TerrainShapeV1.StretchSinker;
|
||||||
|
private const float FrozenSpeckFrac = TerrainShapeV1.SpeckFrac; // ≈ 4 cells at 4096, ≈ 17 at 8192
|
||||||
|
|
||||||
private const int DefaultMapSize = 8192;
|
private const int DefaultMapSize = 8192;
|
||||||
private const int DefaultCalibSize = 2048;
|
private const int DefaultCalibSize = 2048;
|
||||||
|
|
@ -79,6 +84,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 10);
|
int task = EnvInt("ISLA_TASK", 10);
|
||||||
string descr = EnvStr("ISLA_BATCH", "frag4_seed_gallery");
|
string descr = EnvStr("ISLA_BATCH", "frag4_seed_gallery");
|
||||||
|
|
@ -87,9 +96,7 @@ namespace IslaApocalypse.Tools
|
||||||
int[] seedsEnv = EnvSeeds("ISLA_SEEDS", null);
|
int[] seedsEnv = EnvSeeds("ISLA_SEEDS", null);
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
bool skipAnchor = EnvStr("ISLA_SKIP_ANCHOR_CHECK", "0") == "1";
|
bool skipAnchor = EnvStr("ISLA_SKIP_ANCHOR_CHECK", "0") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t08Source = EnvStr("ISLA_T08_SOURCE", "08_southern_stretch_explore");
|
|
||||||
string t09Source = EnvStr("ISLA_T09_SOURCE", "09_coastal_fragment");
|
|
||||||
|
|
||||||
var seeds = new List<int>(AnchorSeeds); if (seedsEnv == null) seeds.AddRange(FreshSeeds); else { seeds.Clear(); seeds.AddRange(seedsEnv); }
|
var seeds = new List<int>(AnchorSeeds); if (seedsEnv == null) seeds.AddRange(FreshSeeds); else { seeds.Clear(); seeds.AddRange(seedsEnv); }
|
||||||
var anchorSet = new HashSet<int>(AnchorSeeds);
|
var anchorSet = new HashSet<int>(AnchorSeeds);
|
||||||
|
|
@ -118,17 +125,40 @@ namespace IslaApocalypse.Tools
|
||||||
var (knots, calibration) = CalibrateCurve(calibSize, sea, anchors);
|
var (knots, calibration) = CalibrateCurve(calibSize, sea, anchors);
|
||||||
GD.Print($" {knots}");
|
GD.Print($" {knots}");
|
||||||
|
|
||||||
TerrainGenConfig Frozen(int size, int seed, string label, bool frag = true, bool revert = true, bool stretch = true) => new TerrainGenConfig
|
// ⭐⭐ rivers/01: THE FROZEN SETTING IS NOW THE BARE DEFAULT.
|
||||||
|
//
|
||||||
|
// Every `Frozen*` constant above was re-homed into `TerrainGenConfig`'s defaults by the
|
||||||
|
// re-baseline, so this helper no longer SETS the shape — it only ABLATES it, for the
|
||||||
|
// family-off halves of the regression checks. That is the whole point: this batch is the
|
||||||
|
// `terrain-shape-v1` acceptance, and it can only prove the defaults reproduce the locked
|
||||||
|
// shape if it reads them instead of re-stating them.
|
||||||
|
//
|
||||||
|
// ⚠ The `frag` / `revert` / `stretch` flags are ABLATIONS ONLY. All three true = the bare
|
||||||
|
// default = the locked shape; `TerrainShapeV1.Assert` below is what keeps that claim
|
||||||
|
// honest if a default ever drifts.
|
||||||
|
TerrainGenConfig Frozen(int size, int seed, string label, bool frag = true, bool revert = true, bool stretch = true)
|
||||||
{
|
{
|
||||||
MapSize = size, Seed = seed, VariantLabel = label,
|
var c = new TerrainGenConfig
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
{
|
||||||
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
MapSize = size, Seed = seed, VariantLabel = label,
|
||||||
CoastShelf = false, Offshore = new OffshoreSettings(),
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
RegionLabeling = true, SpeckRevert = revert, MinLandComponentFrac = FrozenSpeckFrac,
|
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
||||||
SouthStretch = stretch ? FrozenStretch : 0f, SouthBandStartFrac = FrozenBandStart, SouthBandFeatherFrac = FrozenBandFeather, StretchSinker = FrozenStretchSinker,
|
};
|
||||||
FragmentAmp = frag ? FrozenFragmentAmp : 0f, FragmentFreqPerMapWidth = FrozenFragmentFreq,
|
if (!stretch) c.SouthStretch = 0f;
|
||||||
FragmentBandCentre = FrozenBandCentre, FragmentBandHalfWidth = FrozenBandHalfWidth, FragmentBitesOnly = FrozenBitesOnly,
|
if (!frag) c.FragmentAmp = 0f;
|
||||||
};
|
if (!revert) c.SpeckRevert = false;
|
||||||
|
// This batch is render-only shape: erosion is a later pass and never ran here.
|
||||||
|
c.Erosion = false;
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ⚠⚠ THE DEFAULT-DRIFT GUARD (rivers/01). The gallery above stopped STATING the locked shape
|
||||||
|
// and started READING it. If a default ever moves, every render silently moves with it and
|
||||||
|
// the batch still "passes" — so the claim is asserted, loudly, before a pixel is drawn.
|
||||||
|
// The `Frozen*` constants below are unchanged in value; their ROLE flipped from source to
|
||||||
|
// assertion target. → Tools/Scripts/TerrainShapeV1.cs
|
||||||
|
TerrainShapeV1.Assert("FragGallery");
|
||||||
|
GD.Print($" defaults : ✅ {TerrainShapeV1.Describe()}");
|
||||||
|
|
||||||
// ═══ 1. THE ORACLE — no code change, same setting ═══
|
// ═══ 1. THE ORACLE — no code change, same setting ═══
|
||||||
GD.Print($"\n--- 1. ORACLE: the setting is the 09 frag_4 setting, and nothing upstream moved ---");
|
GD.Print($"\n--- 1. ORACLE: the setting is the 09 frag_4 setting, and nothing upstream moved ---");
|
||||||
|
|
@ -137,25 +167,32 @@ namespace IslaApocalypse.Tools
|
||||||
var offCfg = Frozen(calibSize, AnchorSeeds[0], "off", frag: false, revert: false, stretch: false);
|
var offCfg = Frozen(calibSize, AnchorSeeds[0], "off", frag: false, revert: false, stretch: false);
|
||||||
Pass1Result p1 = Topography.Generate(offCfg);
|
Pass1Result p1 = Topography.Generate(offCfg);
|
||||||
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{AnchorSeeds[0]}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{AnchorSeeds[0]}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF, frag OFF == Phase-1 .f32 dump (the curve is untouched)", Shaping.Shape(p1, curveOff).Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF, frag OFF == Phase-1 .f32 dump (the curve is untouched)",
|
||||||
|
Shaping.Shape(p1, curveOff).Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump));
|
||||||
|
|
||||||
|
// ⚑ RETIRED at rivers/01 — a8 (`08_southern_stretch_explore`) and a9 (`09_coastal_fragment`).
|
||||||
|
// Both are EXPLORATION ladders this gallery was built to CONCLUDE: chat2/09 climbed the
|
||||||
|
// fragmentation ladder, chat2/10 froze rung 4 across 8 seeds, and the developer tagged the
|
||||||
|
// result `terrain-shape-v1`. Since rivers/01 that frozen setting IS the bare default, and
|
||||||
|
// `TerrainShapeV1.Assert` + a10 assert it directly — asserting it a third time through the
|
||||||
|
// rungs it was chosen from is circular, and 08's dump is at stretch 3 (off-shape) besides.
|
||||||
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
|
|
||||||
if (!skipAnchor)
|
if (!skipAnchor)
|
||||||
{
|
{
|
||||||
foreach (int seed in AnchorSeeds)
|
foreach (int seed in AnchorSeeds)
|
||||||
{
|
{
|
||||||
// ⭐ a9 — the frozen setting at the 09 batch's size reproduces the 09 frag_4 field bit for bit.
|
// ⭐ r KEPT and SELF-ANCHORED — the interior-locked invariant is coastal fragmentation's
|
||||||
string t09Dump = Path.Combine(ToolingPaths.BatchesRoot, t09Source, $"{seed}_frag_4", "height.f32");
|
// load-bearing claim (it must touch the coastal window and NOTHING else), and it needs
|
||||||
|
// no external dump: both fields are generated here, from the bare defaults and from the
|
||||||
|
// same defaults with frag ablated off. That is what let 08 and 09 retire intact.
|
||||||
var c9 = Frozen(AnchorCheckSize, seed, "frag_4");
|
var c9 = Frozen(AnchorCheckSize, seed, "frag_4");
|
||||||
Pass1Result q9p = Topography.Generate(c9);
|
Pass1Result q9p = Topography.Generate(c9);
|
||||||
Pass2Result q9 = Shaping.Shape(q9p, c9);
|
|
||||||
hard.Add(ShapingOracle.DumpRegression("a9", $"frozen frag_4 at {AnchorCheckSize} == task-09 frag_4 dump [{seed}] (no code change, same setting)", q9.Height, HeightField.Load(t09Dump, AnchorCheckSize), AnchorCheckSize, t09Dump));
|
|
||||||
|
|
||||||
// a8 — the stretch-2, frag-off baseline still equals the 08 field, and the interior is still locked against it.
|
|
||||||
string t08Dump = Path.Combine(ToolingPaths.BatchesRoot, t08Source, $"{seed}_stretch_3", "height.f32");
|
|
||||||
var c8 = Frozen(AnchorCheckSize, seed, "t08", frag: false, revert: false);
|
var c8 = Frozen(AnchorCheckSize, seed, "t08", frag: false, revert: false);
|
||||||
Pass1Result q8p = Topography.Generate(c8);
|
Pass1Result q8p = Topography.Generate(c8);
|
||||||
hard.Add(ShapingOracle.DumpRegression("a8", $"frag OFF, stretch 2 at {AnchorCheckSize} == task-08 stretch_3 dump [{seed}]", Shaping.Shape(q8p, c8).Height, HeightField.Load(t08Dump, AnchorCheckSize), AnchorCheckSize, t08Dump));
|
|
||||||
var r = ShapingOracle.InteriorLocked(q8p, q9p, FrozenBandCentre, FrozenBandHalfWidth); r.Name += $" [{seed}, {AnchorCheckSize}]"; hard.Add(r);
|
var r = ShapingOracle.InteriorLocked(q8p, q9p, FrozenBandCentre, FrozenBandHalfWidth); r.Name += $" [{seed}, {AnchorCheckSize}]"; hard.Add(r);
|
||||||
}
|
}
|
||||||
// determinism at the check size
|
// determinism at the check size
|
||||||
|
|
@ -203,7 +240,7 @@ namespace IslaApocalypse.Tools
|
||||||
foreach (var c in perSeed) if (!c.Passed) GD.PrintErr(" " + c);
|
foreach (var c in perSeed) if (!c.Passed) GD.PrintErr(" " + c);
|
||||||
|
|
||||||
WriteTable(batchRoot, mapSize, rows, big, speckCells);
|
WriteTable(batchRoot, mapSize, rows, big, speckCells);
|
||||||
WriteIndex(batchRoot, mapSize, calibSize, rows, big, speckCells, hard, perSeed, allOk);
|
WriteIndex(batchRoot, task, mapSize, calibSize, rows, big, speckCells, hard, perSeed, allOk);
|
||||||
|
|
||||||
GD.Print("\n==================================================================");
|
GD.Print("\n==================================================================");
|
||||||
GD.Print($" DONE — {batchRoot}");
|
GD.Print($" DONE — {batchRoot}");
|
||||||
|
|
@ -262,7 +299,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s));
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -275,11 +312,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
@ -356,11 +396,11 @@ namespace IslaApocalypse.Tools
|
||||||
WriteText(Path.Combine(batchRoot, "count_size_table.csv"), csv.ToString());
|
WriteText(Path.Combine(batchRoot, "count_size_table.csv"), csv.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void WriteIndex(string batchRoot, int mapSize, int calibSize, List<Row> rows, long big, long speckCells,
|
private static void WriteIndex(string batchRoot, int task, int mapSize, int calibSize, List<Row> rows, long big, long speckCells,
|
||||||
List<ShapingOracle.Check> hard, List<ShapingOracle.Check> perSeed, bool allOk)
|
List<ShapingOracle.Check> hard, List<ShapingOracle.Check> perSeed, bool allOk)
|
||||||
{
|
{
|
||||||
var sb = new StringBuilder();
|
var sb = new StringBuilder();
|
||||||
sb.AppendLine($"# Batch 10 — frag_4 seed gallery: does the look generalize? (render-only, {mapSize})");
|
sb.AppendLine($"# Batch {task:D2} — frag_4 seed gallery: does the look generalize? (render-only, {mapSize})");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("**A contact sheet, not a tune.** Every plate is the SAME setting — chat2/09's `frag_4`, frozen — across the two");
|
sb.AppendLine("**A contact sheet, not a tune.** Every plate is the SAME setting — chat2/09's `frag_4`, frozen — across the two");
|
||||||
sb.AppendLine("seeds it was judged on (⭐ anchors) and six fresh seeds chosen before any render. The question: does a coherent");
|
sb.AppendLine("seeds it was judged on (⭐ anchors) and six fresh seeds chosen before any render. The question: does a coherent");
|
||||||
|
|
@ -370,7 +410,13 @@ namespace IslaApocalypse.Tools
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine($"`FragmentAmp {FrozenFragmentAmp}` · `FragmentFreqPerMapWidth {FrozenFragmentFreq}` · window `{FrozenBandCentre} ± {FrozenBandHalfWidth}` · bites-only `{FrozenBitesOnly}` · " +
|
sb.AppendLine($"`FragmentAmp {FrozenFragmentAmp}` · `FragmentFreqPerMapWidth {FrozenFragmentFreq}` · window `{FrozenBandCentre} ± {FrozenBandHalfWidth}` · bites-only `{FrozenBitesOnly}` · " +
|
||||||
$"`SouthStretch {FrozenStretch}` (band `{FrozenBandStart}` / feather `{FrozenBandFeather}`, sinker stretched `{FrozenStretchSinker}`) · speck revert < {speckCells} cells (`{FrozenSpeckFrac:G2}` of the map) · " +
|
$"`SouthStretch {FrozenStretch}` (band `{FrozenBandStart}` / feather `{FrozenBandFeather}`, sinker stretched `{FrozenStretchSinker}`) · speck revert < {speckCells} cells (`{FrozenSpeckFrac:G2}` of the map) · " +
|
||||||
"offshore OFF · shelf OFF · region labeling ON · the tagged curve (calibrated on task 01's pool at " + calibSize + "). Pinned explicitly in `FragGalleryTool` — nothing is left to a default.");
|
"offshore OFF · shelf OFF · region labeling ON · the tagged curve (calibrated on task 01's pool at " + calibSize + ").");
|
||||||
|
sb.AppendLine();
|
||||||
|
sb.AppendLine("> ### ⭐ Since rivers/01, this setting IS the bare `TerrainGenConfig` default — it is not stated here, it is READ.");
|
||||||
|
sb.AppendLine("> That is what makes this batch the acceptance for the re-baseline rather than a restatement of it: if a default");
|
||||||
|
sb.AppendLine("> ever drifts, `TerrainShapeV1.Assert` refuses the run instead of rendering a gallery that would look right and");
|
||||||
|
sb.AppendLine("> mean nothing. The curve calibration pool is pinned FAMILY-OFF (`TerrainGenConfig.WithFamilyOff`), which is what");
|
||||||
|
sb.AppendLine("> keeps the knots — and therefore these renders — bit-identical across the flip.");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
sb.AppendLine("## ⭐ The contact sheet");
|
sb.AppendLine("## ⭐ The contact sheet");
|
||||||
sb.AppendLine();
|
sb.AppendLine();
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/MountainRestoreTool.tscn
|
/// --path ~/celerNexus/islaApocalypse-v2 res://Tools/Scenes/MountainRestoreTool.tscn
|
||||||
///
|
///
|
||||||
/// ISLA_TASK / ISLA_BATCH / ISLA_MAPSIZE / ISLA_SEEDS / ISLA_SHOWPIECE_SIZE / ISLA_SHOWPIECE
|
/// ISLA_TASK / ISLA_BATCH / ISLA_MAPSIZE / ISLA_SEEDS / ISLA_SHOWPIECE_SIZE / ISLA_SHOWPIECE
|
||||||
/// ISLA_PHASE1_SOURCE (default "02_pass1_port") · ISLA_T01_SOURCE (default "01_curve_baseline")
|
/// ISLA_PHASE1_SOURCE (default "chat1/02_pass1_port")
|
||||||
/// ISLA_SKIP_RAW
|
/// ISLA_SKIP_RAW
|
||||||
/// ISLA_LIFT_BIG probe: the `continuous_bigger` lift (default 1.35)
|
/// ISLA_LIFT_BIG probe: the `continuous_bigger` lift (default 1.35)
|
||||||
/// ISLA_SHARP probe: the `continuous_sharper_peak` knob (default 2.5)
|
/// ISLA_SHARP probe: the `continuous_sharper_peak` knob (default 2.5)
|
||||||
|
|
@ -77,6 +77,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 3);
|
int task = EnvInt("ISLA_TASK", 3);
|
||||||
string descr = EnvStr("ISLA_BATCH", "mountain_restore");
|
string descr = EnvStr("ISLA_BATCH", "mountain_restore");
|
||||||
|
|
@ -84,8 +88,7 @@ namespace IslaApocalypse.Tools
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
int showSize = EnvInt("ISLA_SHOWPIECE_SIZE", DefaultShowpieceSize);
|
||||||
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
bool showpiece = EnvStr("ISLA_SHOWPIECE", "1") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t01Source = EnvStr("ISLA_T01_SOURCE", "01_curve_baseline");
|
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
float liftBig = EnvFloat("ISLA_LIFT_BIG", 1.35f);
|
float liftBig = EnvFloat("ISLA_LIFT_BIG", 1.35f);
|
||||||
float sharpKnob = EnvFloat("ISLA_SHARP", 2.5f);
|
float sharpKnob = EnvFloat("ISLA_SHARP", 2.5f);
|
||||||
|
|
@ -114,7 +117,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int seed in CalibrationSeeds)
|
foreach (int seed in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = mapSize, Seed = seed });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(mapSize, seed));
|
||||||
pass1[seed] = p1;
|
pass1[seed] = p1;
|
||||||
rawPool.Accumulate(p1.Height, mapSize);
|
rawPool.Accumulate(p1.Height, mapSize);
|
||||||
}
|
}
|
||||||
|
|
@ -219,12 +222,17 @@ namespace IslaApocalypse.Tools
|
||||||
var hard = new List<ShapingOracle.Check>();
|
var hard = new List<ShapingOracle.Check>();
|
||||||
var soft = new List<ShapingOracle.Check>();
|
var soft = new List<ShapingOracle.Check>();
|
||||||
|
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off).
|
||||||
|
// ⚠ A missing dump now THROWS instead of skipping silently.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{primary}_full", "height.f32");
|
||||||
string t01Dump = Path.Combine(ToolingPaths.BatchesRoot, t01Source, $"{primary}_curve_on", "height.f32");
|
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF == Phase-1 .f32 dump",
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF == Phase-1 .f32 dump",
|
||||||
offs[primary].Height, HeightField.Load(p1Dump, mapSize), mapSize, p1Dump));
|
offs[primary].Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, mapSize), mapSize, p1Dump));
|
||||||
hard.Add(ShapingOracle.DumpRegression("a2", "staircase == task-01 curve_on .f32 dump",
|
|
||||||
results[(primary, "staircase")].Height, HeightField.Load(t01Dump, mapSize), mapSize, t01Dump));
|
// ⚑ RETIRED at rivers/01 — a2, the staircase == `01_curve_baseline` control.
|
||||||
|
// Superseded by the continuous grade (→ D-062) — see CurveContinuousTool for the full note.
|
||||||
|
// The dump is NOT deleted (file-safety; it is regenerable and it is the record of what
|
||||||
|
// was judged); its `INDEX.md` is marked superseded. The check is gone so nothing can
|
||||||
|
// pass against a superseded baseline. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
|
|
||||||
long bFail = 0;
|
long bFail = 0;
|
||||||
foreach (int seed in seeds)
|
foreach (int seed in seeds)
|
||||||
|
|
@ -335,13 +343,20 @@ namespace IslaApocalypse.Tools
|
||||||
GetTree().Quit(hardOk ? 0 : 3);
|
GetTree().Quit(hardOk ? 0 : 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This is chat-2 CURVE development: authored
|
||||||
|
/// and judged before the shape family existed, on the family-off distribution the knots are
|
||||||
|
/// percentiles of. The re-baseline flipped the bare defaults family-ON, so the pin is what
|
||||||
|
/// keeps this tool measuring the thing it was written to measure.
|
||||||
|
/// → <see cref="TerrainGenConfig.WithFamilyOff"/>.
|
||||||
|
/// </remarks>
|
||||||
private static TerrainGenConfig MakeConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a, string label)
|
private static TerrainGenConfig MakeConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a, string label)
|
||||||
=> new TerrainGenConfig
|
=> new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, Knots = k, Anchors = a,
|
Curve = true, ShelfDetail = false, Knots = k, Anchors = a,
|
||||||
LowlandCeilingM = 30f,
|
LowlandCeilingM = 30f,
|
||||||
};
|
}.WithFamilyOff();
|
||||||
|
|
||||||
// ---- output ---------------------------------------------------------
|
// ---- output ---------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -57,8 +57,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_OFF_MINAREA / ISLA_OFF_MINSEP / ISLA_OFF_MAXAREA the guards (probe overrides)
|
/// ISLA_OFF_MINAREA / ISLA_OFF_MINSEP / ISLA_OFF_MAXAREA the guards (probe overrides)
|
||||||
/// ISLA_OFF_FREQ / ISLA_OFF_CORE / ISLA_OFF_SHARP / ISLA_OFF_CREST the shape (probe overrides)
|
/// ISLA_OFF_FREQ / ISLA_OFF_CORE / ISLA_OFF_SHARP / ISLA_OFF_CREST the shape (probe overrides)
|
||||||
/// ISLA_TABLE_ONLY=1 probe: diagnosis + count table only (no regressions, no plates)
|
/// ISLA_TABLE_ONLY=1 probe: diagnosis + count table only (no regressions, no plates)
|
||||||
/// ISLA_SKIP_8K=1 skip the 8192 regression against the 04 gallery dump (a4)
|
/// ISLA_PHASE1_SOURCE the Phase-1 regression dump's batch (default "chat1/02_pass1_port")
|
||||||
/// ISLA_PHASE1_SOURCE / ISLA_T03_SOURCE / ISLA_T04_SOURCE the regression dumps' batches
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class OffshoreIslandsTool : Node
|
public partial class OffshoreIslandsTool : Node
|
||||||
{
|
{
|
||||||
|
|
@ -77,7 +76,6 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
private const int DefaultMapSize = 4096;
|
private const int DefaultMapSize = 4096;
|
||||||
private const int DefaultCalibSize = 2048;
|
private const int DefaultCalibSize = 2048;
|
||||||
private const int GallerySize = 8192; // the 04 gallery's render size
|
|
||||||
|
|
||||||
/// <summary>The consistency targets the table is read against: "a couple north, a few south".</summary>
|
/// <summary>The consistency targets the table is read against: "a couple north, a few south".</summary>
|
||||||
private const int TargetNorth = 2, TargetSouth = 3;
|
private const int TargetNorth = 2, TargetSouth = 3;
|
||||||
|
|
@ -112,6 +110,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 6);
|
int task = EnvInt("ISLA_TASK", 6);
|
||||||
string descr = EnvStr("ISLA_BATCH", "offshore_organic_tune");
|
string descr = EnvStr("ISLA_BATCH", "offshore_organic_tune");
|
||||||
|
|
@ -121,12 +123,9 @@ namespace IslaApocalypse.Tools
|
||||||
int[] tableSeeds = EnvSeeds("ISLA_TABLE_SEEDS", DefaultTableSeeds);
|
int[] tableSeeds = EnvSeeds("ISLA_TABLE_SEEDS", DefaultTableSeeds);
|
||||||
int plateSeed = EnvInt("ISLA_PLATE_SEED", 1063685222);
|
int plateSeed = EnvInt("ISLA_PLATE_SEED", 1063685222);
|
||||||
int bulgeSeedEnv = EnvInt("ISLA_BULGE_SEED", 0);
|
int bulgeSeedEnv = EnvInt("ISLA_BULGE_SEED", 0);
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t03Source = EnvStr("ISLA_T03_SOURCE", "03_mountain_restore");
|
|
||||||
string t04Source = EnvStr("ISLA_T04_SOURCE", "04_seed_gallery");
|
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
bool tableOnly = EnvStr("ISLA_TABLE_ONLY", "0") == "1";
|
bool tableOnly = EnvStr("ISLA_TABLE_ONLY", "0") == "1";
|
||||||
bool skip8k = EnvStr("ISLA_SKIP_8K", "0") == "1";
|
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
||||||
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
||||||
|
|
@ -185,14 +184,17 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
||||||
Pass2Result pOff = Shaping.Shape(p1, curveOff);
|
Pass2Result pOff = Shaping.Shape(p1, curveOff);
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plateSeed}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plateSeed}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF == Phase-1 .f32 dump",
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF == Phase-1 .f32 dump",
|
||||||
pOff.Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
|
pOff.Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump));
|
||||||
|
|
||||||
Pass2Result pRest = Shaping.Shape(p1, offCfg);
|
// ⚑ RETIRED at rivers/01 — a3, `continuous_restored` == `03_mountain_restore`.
|
||||||
string t03Dump = Path.Combine(ToolingPaths.BatchesRoot, t03Source, $"{plateSeed}_continuous_restored", "height.f32");
|
// A curve-development intermediate: it proved the chat2/03 climb restoration against
|
||||||
hard.Add(ShapingOracle.DumpRegression("a3", "continuous_restored, offshore OFF == task-03 .f32 dump (lowlands + curve untouched)",
|
// chat2/02. Both are upstream of the locked shape, and `terrain-shape-v1` (a10) now
|
||||||
pRest.Height, HeightField.Load(t03Dump, calibSize), calibSize, t03Dump));
|
// asserts the whole chain end-to-end — subsuming it.
|
||||||
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
|
|
||||||
// Shelf ON, islets OFF: land must be bit-identical (the shelf touches only sea).
|
// Shelf ON, islets OFF: land must be bit-identical (the shelf touches only sea).
|
||||||
var shelfCfg = offCfg.Clone(); shelfCfg.CoastShelf = true; shelfCfg.VariantLabel = "shelf_only";
|
var shelfCfg = offCfg.Clone(); shelfCfg.CoastShelf = true; shelfCfg.VariantLabel = "shelf_only";
|
||||||
|
|
@ -205,22 +207,12 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
// ⭐ a4 — offshore OFF at the 04 gallery's size == the terrain-curve-v1 tag's OWN output.
|
// ⭐ a4 — offshore OFF at the 04 gallery's size == the terrain-curve-v1 tag's OWN output.
|
||||||
// The literal "offshore-off is bit-identical to terrain-curve-v1", at full size.
|
// The literal "offshore-off is bit-identical to terrain-curve-v1", at full size.
|
||||||
if (!skip8k)
|
// ⚑ RETIRED at rivers/01 — a4, offshore-OFF at 8192 == `04_seed_gallery` (`terrain-curve-v1`).
|
||||||
{
|
// The PRE-FAMILY committed curve. The locked shape is family-ON, so this dump is a
|
||||||
string t04Dump = Path.Combine(ToolingPaths.BatchesRoot, t04Source, $"{plateSeed}", "height.f32");
|
// baseline the generator is deliberately no longer on; `a10` replaced it as the 8192
|
||||||
if (File.Exists(t04Dump))
|
// acceptance. (It also cost an 8192 generation on every run of this tool.)
|
||||||
{
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
GD.Print($" a4: generating {plateSeed} at {GallerySize}, offshore OFF, against {t04Dump} …");
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
var gCfg = BaseConfig(GallerySize, plateSeed, knots, anchors, calibration, "off");
|
|
||||||
Pass2Result pG = Shaping.Shape(Topography.Generate(gCfg), gCfg);
|
|
||||||
var a4 = ShapingOracle.DumpRegression("a4", $"offshore OFF at {GallerySize} == terrain-curve-v1's 04 gallery .f32 dump",
|
|
||||||
pG.Height, HeightField.Load(t04Dump, GallerySize), GallerySize, t04Dump);
|
|
||||||
hard.Add(a4);
|
|
||||||
GD.Print(" " + a4);
|
|
||||||
}
|
|
||||||
else GD.Print($" a4: ⚠ skipped — no 04 gallery dump at {t04Dump}");
|
|
||||||
}
|
|
||||||
else GD.Print(" a4: skipped (ISLA_SKIP_8K)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ 2. THE DIAGNOSIS — measure the south before touching a knob ═══
|
// ═══ 2. THE DIAGNOSIS — measure the south before touching a knob ═══
|
||||||
|
|
@ -402,7 +394,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s }); // offshore OFF by default
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s)); // family-off PINNED (rivers/01), not defaulted
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -416,11 +408,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
@ -436,14 +431,21 @@ namespace IslaApocalypse.Tools
|
||||||
return (knots, cal, pass1);
|
return (knots, cal, pass1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT
|
||||||
|
/// (chat2/05–06): authored and judged before the shape family existed, and its regressions hold
|
||||||
|
/// pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the bare defaults
|
||||||
|
/// family-ON, so without <see cref="TerrainGenConfig.WithFamilyOff"/> every config here would
|
||||||
|
/// silently acquire stretch + fragmentation and the anchor checks would fail for a configuration
|
||||||
|
/// reason, not a regression. The variants re-enable offshore explicitly, after the pin.
|
||||||
|
/// </remarks>
|
||||||
private static TerrainGenConfig BaseConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a,
|
private static TerrainGenConfig BaseConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a,
|
||||||
ClimbCalibration cal, string label) => new TerrainGenConfig
|
ClimbCalibration cal, string label) => new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
Knots = k, Anchors = a, ClimbCalibration = cal, LowlandCeilingM = 30f,
|
Knots = k, Anchors = a, ClimbCalibration = cal, LowlandCeilingM = 30f,
|
||||||
CoastShelf = false, Offshore = new OffshoreSettings(), // OFF unless the variant turns it on
|
}.WithFamilyOff(); // ⭐ shelf / islets / speck / stretch / frag / erosion all OFF — pinned
|
||||||
};
|
|
||||||
|
|
||||||
// ---- output -----------------------------------------------------------
|
// ---- output -----------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,8 +42,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_SECOND_SEED the second plate seed (default 0 = auto: most natural islands)
|
/// ISLA_SECOND_SEED the second plate seed (default 0 = auto: most natural islands)
|
||||||
/// ISLA_THR_LOW / ISLA_THR_MID / ISLA_THR_HIGH thresholds, fraction of map area (probe overrides)
|
/// ISLA_THR_LOW / ISLA_THR_MID / ISLA_THR_HIGH thresholds, fraction of map area (probe overrides)
|
||||||
/// ISLA_TABLE_ONLY=1 probe: table only (no regressions, no plates)
|
/// ISLA_TABLE_ONLY=1 probe: table only (no regressions, no plates)
|
||||||
/// ISLA_SKIP_8K=1 skip the 8192 regression (a4)
|
/// ISLA_PHASE1_SOURCE the Phase-1 regression dump's batch (default "chat1/02_pass1_port")
|
||||||
/// ISLA_PHASE1_SOURCE / ISLA_T03_SOURCE / ISLA_T04_SOURCE / ISLA_T06_SOURCE the regression dumps' batches
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class RegionLabelingTool : Node
|
public partial class RegionLabelingTool : Node
|
||||||
{
|
{
|
||||||
|
|
@ -57,7 +56,6 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
private const int DefaultMapSize = 4096;
|
private const int DefaultMapSize = 4096;
|
||||||
private const int DefaultCalibSize = 2048;
|
private const int DefaultCalibSize = 2048;
|
||||||
private const int GallerySize = 8192;
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
|
@ -89,6 +87,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 7);
|
int task = EnvInt("ISLA_TASK", 7);
|
||||||
string descr = EnvStr("ISLA_BATCH", "region_labeling");
|
string descr = EnvStr("ISLA_BATCH", "region_labeling");
|
||||||
|
|
@ -98,13 +100,9 @@ namespace IslaApocalypse.Tools
|
||||||
int[] tableSeeds = EnvSeeds("ISLA_TABLE_SEEDS", DefaultTableSeeds);
|
int[] tableSeeds = EnvSeeds("ISLA_TABLE_SEEDS", DefaultTableSeeds);
|
||||||
int plateSeed = EnvInt("ISLA_PLATE_SEED", 1063685222);
|
int plateSeed = EnvInt("ISLA_PLATE_SEED", 1063685222);
|
||||||
int secondEnv = EnvInt("ISLA_SECOND_SEED", 0);
|
int secondEnv = EnvInt("ISLA_SECOND_SEED", 0);
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t03Source = EnvStr("ISLA_T03_SOURCE", "03_mountain_restore");
|
|
||||||
string t04Source = EnvStr("ISLA_T04_SOURCE", "04_seed_gallery");
|
|
||||||
string t06Source = EnvStr("ISLA_T06_SOURCE", "06_offshore_organic_tune");
|
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
bool tableOnly = EnvStr("ISLA_TABLE_ONLY", "0") == "1";
|
bool tableOnly = EnvStr("ISLA_TABLE_ONLY", "0") == "1";
|
||||||
bool skip8k = EnvStr("ISLA_SKIP_8K", "0") == "1";
|
|
||||||
|
|
||||||
var levels = new List<Level>
|
var levels = new List<Level>
|
||||||
{
|
{
|
||||||
|
|
@ -141,7 +139,14 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
TerrainGenConfig Cfg(int size, int seed, string label, bool offshoreOn, bool revertOn, float frac)
|
TerrainGenConfig Cfg(int size, int seed, string label, bool offshoreOn, bool revertOn, float frac)
|
||||||
{
|
{
|
||||||
var c = BaseConfig(size, seed, knots, anchors, calibration, label);
|
// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT:
|
||||||
|
// it was authored and judged before the shape family existed, and its regression checks
|
||||||
|
// hold pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the
|
||||||
|
// bare defaults family-ON, so without this pin every config here would silently acquire
|
||||||
|
// stretch + fragmentation and every anchor check would fail for a configuration reason.
|
||||||
|
// → TerrainGenConfig.WithFamilyOff().
|
||||||
|
var c = BaseConfig(size, seed, knots, anchors, calibration, label).WithFamilyOff();
|
||||||
|
// …then this tool's own axes, AFTER the pin (the pin would otherwise clear them).
|
||||||
if (offshoreOn) { c.CoastShelf = true; c.Offshore = OffshoreSettings.Organic(); }
|
if (offshoreOn) { c.CoastShelf = true; c.Offshore = OffshoreSettings.Organic(); }
|
||||||
c.RegionLabeling = true;
|
c.RegionLabeling = true;
|
||||||
c.SpeckRevert = revertOn;
|
c.SpeckRevert = revertOn;
|
||||||
|
|
@ -159,26 +164,17 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
||||||
Pass2Result pOff = Shaping.Shape(p1, curveOff);
|
Pass2Result pOff = Shaping.Shape(p1, curveOff);
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plateSeed}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plateSeed}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF, revert OFF (labeling on) == Phase-1 .f32 dump",
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, offshore OFF, revert OFF (labeling on) == Phase-1 .f32 dump",
|
||||||
pOff.Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
|
pOff.Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump));
|
||||||
|
|
||||||
Pass2Result pRest = Shaping.Shape(p1, offCfg);
|
// ⚑ RETIRED at rivers/01 — a3 and a3r, both against `03_mountain_restore`.
|
||||||
string t03Dump = Path.Combine(ToolingPaths.BatchesRoot, t03Source, $"{plateSeed}_continuous_restored", "height.f32");
|
// A curve-development intermediate, subsumed by the `terrain-shape-v1` acceptance (a10).
|
||||||
float[,] t03 = HeightField.Load(t03Dump, calibSize);
|
// a3r was informational only, and its subject — how many natural specks the revert takes —
|
||||||
hard.Add(ShapingOracle.DumpRegression("a3", "continuous_restored, offshore OFF, revert OFF (labeling on) == task-03 .f32 dump",
|
// is now reported by the region ledger every run, with the revert ON by default.
|
||||||
pRest.Height, t03, calibSize, t03Dump));
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
// Informational: offshore OFF, revert ON — how many NATURAL speck cells the revert removes
|
|
||||||
// from the bare field. Allowed to differ (the revert may change terrain); reported, not asserted.
|
|
||||||
var revCfg = Cfg(calibSize, plateSeed, "off_revert", offshoreOn: false, revertOn: true, mid.Frac);
|
|
||||||
Pass1Result p1Rev = Topography.Generate(revCfg);
|
|
||||||
Pass2Result pRev = Shaping.Shape(p1Rev, revCfg);
|
|
||||||
var info = ShapingOracle.DumpRegression("a3r", "(informational) offshore OFF, revert ON at threshold_mid vs task-03 dump — the natural specks removed", pRev.Height, t03, calibSize, t03Dump);
|
|
||||||
info.Detail = (info.Passed ? "no natural speck below the threshold on this seed — " : "") + info.Detail +
|
|
||||||
$" · reverted {p1Rev.RegionLedger.RevertedComponents} natural components / {p1Rev.RegionLedger.RevertedCells:N0} cells";
|
|
||||||
info.Passed = true;
|
|
||||||
hard.Add(info);
|
|
||||||
|
|
||||||
var shelfCfg = offCfg.Clone(); shelfCfg.CoastShelf = true; shelfCfg.VariantLabel = "shelf_only";
|
var shelfCfg = offCfg.Clone(); shelfCfg.CoastShelf = true; shelfCfg.VariantLabel = "shelf_only";
|
||||||
Pass1Result p1Shelf = Topography.Generate(shelfCfg);
|
Pass1Result p1Shelf = Topography.Generate(shelfCfg);
|
||||||
|
|
@ -188,34 +184,13 @@ namespace IslaApocalypse.Tools
|
||||||
hard.Add(ShapingOracle.CentreIsLand(p1));
|
hard.Add(ShapingOracle.CentreIsLand(p1));
|
||||||
foreach (var c in hard) GD.Print(" " + c);
|
foreach (var c in hard) GD.Print(" " + c);
|
||||||
|
|
||||||
if (!skip8k)
|
// ⚑ RETIRED at rivers/01 — a4 (`04_seed_gallery`) and a6 (`06_offshore_organic_tune`).
|
||||||
{
|
// a4 held the PRE-FAMILY committed curve; the locked shape is family-ON, and a10 is the
|
||||||
string t04Dump = Path.Combine(ToolingPaths.BatchesRoot, t04Source, $"{plateSeed}", "height.f32");
|
// 8192 acceptance now. a6 held the ORGANIC ISLET layer — the DROPPED mechanism (→ D-063):
|
||||||
if (File.Exists(t04Dump))
|
// islands are organic-only, made by the stretch + fragmentation and identified here, never
|
||||||
{
|
// placed. An oracle pinning islet output is an oracle defending a design that was reversed.
|
||||||
GD.Print($" a4: generating {plateSeed} at {GallerySize}, offshore OFF, revert OFF …");
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
var gCfg = Cfg(GallerySize, plateSeed, "off", offshoreOn: false, revertOn: false, mid.Frac);
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
Pass2Result pG = Shaping.Shape(Topography.Generate(gCfg), gCfg);
|
|
||||||
var a4 = ShapingOracle.DumpRegression("a4", $"offshore OFF, revert OFF at {GallerySize} == terrain-curve-v1's 04 gallery .f32 dump",
|
|
||||||
pG.Height, HeightField.Load(t04Dump, GallerySize), GallerySize, t04Dump);
|
|
||||||
hard.Add(a4); GD.Print(" " + a4);
|
|
||||||
}
|
|
||||||
else GD.Print($" a4: ⚠ skipped — no 04 gallery dump at {t04Dump}");
|
|
||||||
}
|
|
||||||
else GD.Print(" a4: skipped (ISLA_SKIP_8K)");
|
|
||||||
|
|
||||||
// ⭐ a6 — labeling ON, revert OFF, on the chat2/06 preset: bit-identical to the 06 batch's
|
|
||||||
// render field. Labeling is pure analysis; only the revert may change terrain.
|
|
||||||
string t06Dump = Path.Combine(ToolingPaths.BatchesRoot, t06Source, $"{plateSeed}_density_mid", "height.f32");
|
|
||||||
if (File.Exists(t06Dump) && mapSize == 4096)
|
|
||||||
{
|
|
||||||
var c6 = Cfg(mapSize, plateSeed, "density_mid", offshoreOn: true, revertOn: false, mid.Frac);
|
|
||||||
Pass2Result p6 = Shaping.Shape(Topography.Generate(c6), c6);
|
|
||||||
var a6 = ShapingOracle.DumpRegression("a6", "offshore density_mid ON, labeling ON, revert OFF == task-06 .f32 dump (labeling is pure analysis)",
|
|
||||||
p6.Height, HeightField.Load(t06Dump, mapSize), mapSize, t06Dump);
|
|
||||||
hard.Add(a6); GD.Print(" " + a6);
|
|
||||||
}
|
|
||||||
else GD.Print($" a6: ⚠ skipped — {(mapSize != 4096 ? "map size is not the 06 batch's 4096" : $"no 06 dump at {t06Dump}")}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ 2. DETERMINISM ═══
|
// ═══ 2. DETERMINISM ═══
|
||||||
|
|
@ -354,7 +329,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s }); // offshore OFF, revert OFF by default
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s)); // family-off PINNED (rivers/01), not defaulted
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -368,11 +343,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_LOOKS comma-separated look names (default: atlas,relief,dusk)
|
/// ISLA_LOOKS comma-separated look names (default: atlas,relief,dusk)
|
||||||
/// ISLA_TASK authoring task number (default 3)
|
/// ISLA_TASK authoring task number (default 3)
|
||||||
/// ISLA_BATCH descriptor, NO prefix (default "relief_taste")
|
/// ISLA_BATCH descriptor, NO prefix (default "relief_taste")
|
||||||
/// ISLA_SOURCE batch to read .f32 from (default 02_pass1_port)
|
/// ISLA_SOURCE batch to read .f32 from (default "chat1/02_pass1_port")
|
||||||
/// ISLA_DUMP_RAW "1" to also dump .f32 when a field had to be generated
|
/// ISLA_DUMP_RAW "1" to also dump .f32 when a field had to be generated
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class ReliefRenderTool : Node
|
public partial class ReliefRenderTool : Node
|
||||||
|
|
@ -36,6 +36,10 @@ namespace IslaApocalypse.Tools
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat1"));
|
||||||
|
|
||||||
int mapSize = EnvInt("ISLA_MAPSIZE", 2048);
|
int mapSize = EnvInt("ISLA_MAPSIZE", 2048);
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
|
|
@ -43,8 +47,8 @@ namespace IslaApocalypse.Tools
|
||||||
string batch = EnvStr("ISLA_BATCH", "relief_taste");
|
string batch = EnvStr("ISLA_BATCH", "relief_taste");
|
||||||
// ⚠ A FULL batch folder name, prefix included — it names an EXISTING folder rather than
|
// ⚠ A FULL batch folder name, prefix included — it names an EXISTING folder rather than
|
||||||
// composing a new one, so it is not run through BatchRoot. Tracks TerrainGenTool's
|
// composing a new one, so it is not run through BatchRoot. Tracks TerrainGenTool's
|
||||||
// default output: BatchRoot(task 2, "pass1_port") = 02_pass1_port.
|
// default output: BatchRoot(task 2, "pass1_port") = <chat>/02_pass1_port (rivers/01: chat-namespaced).
|
||||||
string source = EnvStr("ISLA_SOURCE", "02_pass1_port");
|
string source = EnvStr("ISLA_SOURCE", "chat1/02_pass1_port");
|
||||||
bool dumpRaw = EnvStr("ISLA_DUMP_RAW", "0") == "1";
|
bool dumpRaw = EnvStr("ISLA_DUMP_RAW", "0") == "1";
|
||||||
LookConfig[] looks = SelectLooks(EnvStr("ISLA_LOOKS", null));
|
LookConfig[] looks = SelectLooks(EnvStr("ISLA_LOOKS", null));
|
||||||
|
|
||||||
|
|
@ -83,7 +87,9 @@ namespace IslaApocalypse.Tools
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "full" };
|
// ⭐ rivers/01: family-off PINNED — this regenerates a PHASE-1 field to stand in for a
|
||||||
|
// missing `02_pass1_port` dump, so it must reproduce that dump, not the new default.
|
||||||
|
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "full" }.WithFamilyOff();
|
||||||
Pass1Result r = Topography.Generate(cfg);
|
Pass1Result r = Topography.Generate(cfg);
|
||||||
height = r.Height;
|
height = r.Height;
|
||||||
origin = "generated";
|
origin = "generated";
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace IslaApocalypse.Tools
|
||||||
///
|
///
|
||||||
/// ISLA_TASK authoring task number (default 4)
|
/// ISLA_TASK authoring task number (default 4)
|
||||||
/// ISLA_BATCH descriptor, NO prefix (default "review")
|
/// ISLA_BATCH descriptor, NO prefix (default "review")
|
||||||
/// ISLA_SOURCE batch holding the .f32 (default 02_pass1_port)
|
/// ISLA_SOURCE batch holding the .f32 (default "chat1/02_pass1_port")
|
||||||
/// ISLA_MAPSIZE side in columns (default 2048)
|
/// ISLA_MAPSIZE side in columns (default 2048)
|
||||||
/// ISLA_SEEDS comma-separated positive (default: the 4 pinned seeds)
|
/// ISLA_SEEDS comma-separated positive (default: the 4 pinned seeds)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
@ -59,16 +59,21 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat1"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 4);
|
int task = EnvInt("ISLA_TASK", 4);
|
||||||
string descr = EnvStr("ISLA_BATCH", "review");
|
string descr = EnvStr("ISLA_BATCH", "review");
|
||||||
// ⚠ A FULL batch folder name, prefix included — this NAMES AN EXISTING FOLDER rather than
|
// ⚠ A FULL batch folder name, prefix included — this NAMES AN EXISTING FOLDER rather than
|
||||||
// composing a new one, so it is not run through BatchRoot. The default tracks where
|
// composing a new one, so it is not run through BatchRoot. The default tracks where
|
||||||
// TerrainGenTool writes by default: BatchRoot(task 2, "pass1_port") = 02_pass1_port.
|
// TerrainGenTool writes by default: BatchRoot(task 2, "pass1_port") = chat1/02_pass1_port
|
||||||
|
// (rivers/01 namespaced batches by chat; the READ side carries the chatN/ prefix explicitly).
|
||||||
// It was "01_pass1_port" until chat1/05 renamed the folder to its authoring-task number;
|
// It was "01_pass1_port" until chat1/05 renamed the folder to its authoring-task number;
|
||||||
// a stale default here does not fail loudly, it just silently regenerates instead of
|
// a stale default here does not fail loudly, it just silently regenerates instead of
|
||||||
// loading — which is exactly the kind of quiet cost worth pinning to the real name.
|
// loading — which is exactly the kind of quiet cost worth pinning to the real name.
|
||||||
string source = EnvStr("ISLA_SOURCE", "02_pass1_port");
|
string source = EnvStr("ISLA_SOURCE", "chat1/02_pass1_port");
|
||||||
int mapSize = EnvInt("ISLA_MAPSIZE", 2048);
|
int mapSize = EnvInt("ISLA_MAPSIZE", 2048);
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
|
|
||||||
|
|
@ -103,7 +108,7 @@ namespace IslaApocalypse.Tools
|
||||||
if (h == null)
|
if (h == null)
|
||||||
{
|
{
|
||||||
GD.PrintErr($" ⚠ no full dump for {seed} at {mapSize} — generating deterministically.");
|
GD.PrintErr($" ⚠ no full dump for {seed} at {mapSize} — generating deterministically.");
|
||||||
h = Topography.Generate(new TerrainGenConfig { MapSize = mapSize, Seed = seed }).Height;
|
h = Topography.Generate(TerrainGenConfig.CalibrationPool(mapSize, seed)).Height;
|
||||||
}
|
}
|
||||||
|
|
||||||
notes.Add(Gray(h, mapSize, batchRoot, "2_height_grayscale", seed,
|
notes.Add(Gray(h, mapSize, batchRoot, "2_height_grayscale", seed,
|
||||||
|
|
|
||||||
|
|
@ -126,6 +126,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 4);
|
int task = EnvInt("ISLA_TASK", 4);
|
||||||
string descr = EnvStr("ISLA_BATCH", "seed_gallery");
|
string descr = EnvStr("ISLA_BATCH", "seed_gallery");
|
||||||
|
|
@ -180,7 +184,7 @@ namespace IslaApocalypse.Tools
|
||||||
var poolPass1 = new Dictionary<int, Pass1Result>();
|
var poolPass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s });
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s));
|
||||||
poolPass1[s] = p1;
|
poolPass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -288,13 +292,18 @@ namespace IslaApocalypse.Tools
|
||||||
public ulong ElapsedMs;
|
public ulong ElapsedMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <remarks>
|
||||||
|
/// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. chat2/04 is the PRE-FAMILY committed-curve
|
||||||
|
/// gallery (`terrain-curve-v1`); the re-baseline flipped the bare defaults family-ON, and this
|
||||||
|
/// tool must keep producing the curve gallery it was judged as. → TerrainGenConfig.WithFamilyOff().
|
||||||
|
/// </remarks>
|
||||||
private static TerrainGenConfig BaseConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a, string label)
|
private static TerrainGenConfig BaseConfig(int mapSize, int seed, CurveKnots k, CurveAnchors a, string label)
|
||||||
=> new TerrainGenConfig
|
=> new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
MapSize = mapSize, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, Knots = k, Anchors = a,
|
Curve = true, ShelfDetail = false, Knots = k, Anchors = a,
|
||||||
LowlandCeilingM = LowlandCeilingM,
|
LowlandCeilingM = LowlandCeilingM,
|
||||||
};
|
}.WithFamilyOff();
|
||||||
|
|
||||||
private static SeedMetrics WriteSeed(string batchRoot, Pass1Result p1, Pass2Result p2,
|
private static SeedMetrics WriteSeed(string batchRoot, Pass1Result p1, Pass2Result p2,
|
||||||
float sea, CurveAnchors anchors, bool skipRaw)
|
float sea, CurveAnchors anchors, bool skipRaw)
|
||||||
|
|
|
||||||
|
|
@ -675,6 +675,82 @@ namespace IslaApocalypse.Tools
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ═══ ⭐⭐ ANCHOR RESOLUTION — A MISSING ANCHOR IS LOUD (rivers/01) ═══════════════════════════
|
||||||
|
//
|
||||||
|
// ═══ THE FAILURE MODE THIS CLOSES ═══
|
||||||
|
//
|
||||||
|
// Every anchor check used to be written as:
|
||||||
|
//
|
||||||
|
// if (File.Exists(dump) && mapSize == 8192) hard.Add(DumpRegression(...));
|
||||||
|
// else GD.Print(" ⚠ skipped — no dump at …");
|
||||||
|
//
|
||||||
|
// so a moved, renamed or deleted anchor did not make the oracle FAIL. It made the oracle
|
||||||
|
// NOT RUN — and a batch with a silently-skipped check prints an all-PASS table and reads
|
||||||
|
// exactly like a clean one. The `INDEX.md` is then evidence for a claim nothing checked.
|
||||||
|
//
|
||||||
|
// > ### ⚠ This is the INVERSE of the hazard the re-baseline guards against.
|
||||||
|
// > The re-baseline stops an oracle PASSING FOR THE WRONG REASON. This stops one
|
||||||
|
// > DISAPPEARING FOR NO REASON. Both end with a green table and an unproven claim, and the
|
||||||
|
// > rivers/01 batch-root migration is exactly the event that would have triggered the second
|
||||||
|
// > one — nine anchors moved under `<chat>/` in a single commit.
|
||||||
|
//
|
||||||
|
// The two cases the old code conflated are now separated, in ONE place (`LoadAnchor`) so every
|
||||||
|
// anchor site behaves identically:
|
||||||
|
// MISSING FILE the anchor moved, was renamed, or was never written. THROWS — the check
|
||||||
|
// cannot run, and dropping it quietly is the failure this exists to close.
|
||||||
|
// WRONG SIZE the anchor exists, but only at the size it was captured. A legitimate case
|
||||||
|
// (a probe run at another size); reported loudly and recorded INCONCLUSIVE,
|
||||||
|
// which is a FAIL in the table. ⚠ Deliberately NOT a throw: a guard that fires
|
||||||
|
// on ordinary small-map probe work is a guard people learn to route around.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⭐ Load a regression anchor's `.f32`, separating the two failures the old code conflated.
|
||||||
|
///
|
||||||
|
/// FILE ABSENT → THROWS. The anchor moved, was renamed, or was never written.
|
||||||
|
/// This is the migration hazard, and it is not survivable: the
|
||||||
|
/// check cannot run and must not be quietly dropped.
|
||||||
|
/// PRESENT, WRONG SIZE → returns null, LOUDLY. A legitimate case — an anchor exists only
|
||||||
|
/// at the size it was captured, and a batch run at another size
|
||||||
|
/// genuinely cannot check against it. The caller's
|
||||||
|
/// <see cref="DumpRegression"/> records it INCONCLUSIVE, which is
|
||||||
|
/// a FAIL in the table, never a pass.
|
||||||
|
///
|
||||||
|
/// ⚠ The distinction matters because only ONE of them means something is broken. Throwing on a
|
||||||
|
/// size mismatch would make every small-map probe run refuse, and a guard that fires on ordinary
|
||||||
|
/// work is a guard people route around.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="checkId">The oracle check this anchor feeds, e.g. "a10" — named in the message.</param>
|
||||||
|
/// <param name="envVar">The env override that can re-point it, e.g. "ISLA_T10_SOURCE".</param>
|
||||||
|
/// <param name="dumpPath">The resolved absolute path.</param>
|
||||||
|
/// <param name="mapSize">The field size to read.</param>
|
||||||
|
public static float[,] LoadAnchor(string checkId, string envVar, string dumpPath, int mapSize)
|
||||||
|
{
|
||||||
|
if (!System.IO.File.Exists(dumpPath))
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"[Oracle] MISSING ANCHOR for check '{checkId}' — nothing at:\n" +
|
||||||
|
$" {dumpPath}\n" +
|
||||||
|
"An oracle whose anchor is absent does not fail, it does not RUN — and a batch with a " +
|
||||||
|
"silently-skipped check prints an all-PASS table that reads exactly like a clean one. " +
|
||||||
|
"Refusing to render evidence for a claim nothing checked.\n" +
|
||||||
|
$"→ Re-point it with {envVar}, or regenerate the anchor. If the anchor is genuinely " +
|
||||||
|
"retired, DELETE THE CHECK — never leave one aimed at nothing. (rivers/01.)");
|
||||||
|
|
||||||
|
long expected = (long)mapSize * mapSize * 4;
|
||||||
|
long actual = new System.IO.FileInfo(dumpPath).Length;
|
||||||
|
if (actual != expected)
|
||||||
|
{
|
||||||
|
int anchorSize = (int)System.Math.Round(System.Math.Sqrt(actual / 4.0));
|
||||||
|
Godot.GD.PrintErr(
|
||||||
|
$" ⚠⚠ {checkId}: NOT CHECKED — the anchor exists but was captured at {anchorSize}, " +
|
||||||
|
$"and this run is at {mapSize} ({actual:N0} bytes, expected {expected:N0}). This is a size " +
|
||||||
|
$"mismatch, NOT a missing file: the batch is simply not proven against it at this size. " +
|
||||||
|
$"Run at {anchorSize} to check it. The oracle records INCONCLUSIVE, which is a FAIL — never a pass.");
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return HeightField.Load(dumpPath, mapSize);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>Render the whole oracle as a markdown table for the INDEX and the report.</summary>
|
/// <summary>Render the whole oracle as a markdown table for the INDEX and the report.</summary>
|
||||||
public static string ToMarkdownTable(IEnumerable<Check> checks)
|
public static string ToMarkdownTable(IEnumerable<Check> checks)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,6 @@ namespace IslaApocalypse.Tools
|
||||||
/// ISLA_BAND_START / ISLA_BAND_FEATHER the fixed band (fractions of the map; constants for the batch)
|
/// ISLA_BAND_START / ISLA_BAND_FEATHER the fixed band (fractions of the map; constants for the batch)
|
||||||
/// ISLA_STRETCH_SINKER 1 = the sinker rides the stretched distance (default), 0 = real y
|
/// ISLA_STRETCH_SINKER 1 = the sinker rides the stretched distance (default), 0 = real y
|
||||||
/// ISLA_DIAG_ONLY=1 diagnostic only
|
/// ISLA_DIAG_ONLY=1 diagnostic only
|
||||||
/// ISLA_SKIP_8K=1 skip the 8192 band regression (a4b)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class SouthernStretchTool : Node
|
public partial class SouthernStretchTool : Node
|
||||||
{
|
{
|
||||||
|
|
@ -64,7 +63,6 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
private const int DefaultMapSize = 4096;
|
private const int DefaultMapSize = 4096;
|
||||||
private const int DefaultCalibSize = 2048;
|
private const int DefaultCalibSize = 2048;
|
||||||
private const int GallerySize = 8192;
|
|
||||||
|
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
|
|
@ -91,6 +89,10 @@ namespace IslaApocalypse.Tools
|
||||||
private void Run()
|
private void Run()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat2"));
|
||||||
|
|
||||||
int task = EnvInt("ISLA_TASK", 8);
|
int task = EnvInt("ISLA_TASK", 8);
|
||||||
string descr = EnvStr("ISLA_BATCH", "southern_stretch_explore");
|
string descr = EnvStr("ISLA_BATCH", "southern_stretch_explore");
|
||||||
|
|
@ -103,11 +105,8 @@ namespace IslaApocalypse.Tools
|
||||||
float bandFeather = EnvFloat("ISLA_BAND_FEATHER", SouthernStretch.DefaultBandFeatherFrac);
|
float bandFeather = EnvFloat("ISLA_BAND_FEATHER", SouthernStretch.DefaultBandFeatherFrac);
|
||||||
bool stretchSinker = EnvStr("ISLA_STRETCH_SINKER", SouthernStretch.DefaultStretchSinker ? "1" : "0") == "1";
|
bool stretchSinker = EnvStr("ISLA_STRETCH_SINKER", SouthernStretch.DefaultStretchSinker ? "1" : "0") == "1";
|
||||||
bool diagOnly = EnvStr("ISLA_DIAG_ONLY", "0") == "1";
|
bool diagOnly = EnvStr("ISLA_DIAG_ONLY", "0") == "1";
|
||||||
bool skip8k = EnvStr("ISLA_SKIP_8K", "0") == "1";
|
|
||||||
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
bool skipRaw = EnvStr("ISLA_SKIP_RAW", "0") == "1";
|
||||||
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "02_pass1_port");
|
string p1Source = EnvStr("ISLA_PHASE1_SOURCE", "chat1/02_pass1_port");
|
||||||
string t03Source = EnvStr("ISLA_T03_SOURCE", "03_mountain_restore");
|
|
||||||
string t04Source = EnvStr("ISLA_T04_SOURCE", "04_seed_gallery");
|
|
||||||
|
|
||||||
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
string batchRoot = ToolingPaths.BatchRoot(task, descr);
|
||||||
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
DirAccess.MakeDirRecursiveAbsolute(batchRoot);
|
||||||
|
|
@ -136,16 +135,26 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
TerrainGenConfig Cfg(int size, int seed, string label, float stretch, bool sinkerStretched, bool sinkerOn = true, bool edgeOn = true)
|
TerrainGenConfig Cfg(int size, int seed, string label, float stretch, bool sinkerStretched, bool sinkerOn = true, bool edgeOn = true)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01 — FAMILY-OFF PINNED, not defaulted. This tool is chat-2 shaping DEVELOPMENT:
|
||||||
|
// it was authored and judged before the shape family existed, and its regression checks
|
||||||
|
// hold pass 1 against the FAMILY-OFF `02_pass1_port` dump. The re-baseline flipped the
|
||||||
|
// bare defaults family-ON, so without this pin every config here would silently acquire
|
||||||
|
// stretch + fragmentation and every anchor check would fail for a configuration reason.
|
||||||
|
// → TerrainGenConfig.WithFamilyOff().
|
||||||
var c = new TerrainGenConfig
|
var c = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = size, Seed = seed, VariantLabel = label,
|
MapSize = size, Seed = seed, VariantLabel = label,
|
||||||
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
Curve = true, ShelfDetail = false, CurveMode = CurveModeKind.Continuous,
|
||||||
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
Knots = knots, Anchors = anchors, ClimbCalibration = calibration, LowlandCeilingM = 30f,
|
||||||
CoastShelf = false, Offshore = new OffshoreSettings(),
|
RegionLabeling = true,
|
||||||
RegionLabeling = true, SpeckRevert = false,
|
|
||||||
SouthStretch = stretch, SouthBandStartFrac = bandStart, SouthBandFeatherFrac = bandFeather, StretchSinker = sinkerStretched,
|
|
||||||
SouthernSinker = sinkerOn, EdgeNoise = edgeOn,
|
SouthernSinker = sinkerOn, EdgeNoise = edgeOn,
|
||||||
};
|
}.WithFamilyOff();
|
||||||
|
// …then the swept axis, AFTER the pin. Coastal fragmentation stays OFF here: chat2/08
|
||||||
|
// predates it, and this tool's ladder measures the stretch ALONE.
|
||||||
|
c.SouthStretch = stretch;
|
||||||
|
c.SouthBandStartFrac = bandStart;
|
||||||
|
c.SouthBandFeatherFrac = bandFeather;
|
||||||
|
c.StretchSinker = sinkerStretched;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -255,34 +264,31 @@ namespace IslaApocalypse.Tools
|
||||||
var offCfg = Cfg(calibSize, plate, "off", 0f, stretchSinker);
|
var offCfg = Cfg(calibSize, plate, "off", 0f, stretchSinker);
|
||||||
Pass1Result p1 = Topography.Generate(offCfg);
|
Pass1Result p1 = Topography.Generate(offCfg);
|
||||||
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
var curveOff = offCfg.Clone(); curveOff.Curve = false;
|
||||||
|
// ⭐ a1 KEPT at rivers/01 — the family-off pass-1 guard (config pinned family-off). ⚠ loud.
|
||||||
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plate}_full", "height.f32");
|
string p1Dump = Path.Combine(ToolingPaths.BatchesRoot, p1Source, $"{plate}_full", "height.f32");
|
||||||
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF == Phase-1 .f32 dump", Shaping.Shape(p1, curveOff).Height, HeightField.Load(p1Dump, calibSize), calibSize, p1Dump));
|
hard.Add(ShapingOracle.DumpRegression("a1", "curve OFF, stretch OFF == Phase-1 .f32 dump",
|
||||||
string t03Dump = Path.Combine(ToolingPaths.BatchesRoot, t03Source, $"{plate}_continuous_restored", "height.f32");
|
Shaping.Shape(p1, curveOff).Height, ShapingOracle.LoadAnchor("a1", "ISLA_PHASE1_SOURCE", p1Dump, calibSize), calibSize, p1Dump));
|
||||||
float[,] t03 = HeightField.Load(t03Dump, calibSize);
|
|
||||||
hard.Add(ShapingOracle.DumpRegression("a3", "continuous_restored, stretch OFF == task-03 .f32 dump", Shaping.Shape(p1, offCfg).Height, t03, calibSize, t03Dump));
|
|
||||||
|
|
||||||
// ⭐ a3b — stretch ON at the ladder's TOP: north of the band bit-identical to the tag's own dump.
|
// ⭐ a3c KEPT and RE-POINTED — the north-locked invariant is the load-bearing claim of the
|
||||||
|
// southern stretch (→ D-065) and it does NOT need an external anchor: the unstretched field
|
||||||
|
// is generated right here. Re-pointing it off `03_mountain_restore` is what let that dump
|
||||||
|
// retire without losing the guarantee.
|
||||||
var topCfg = Cfg(calibSize, plate, "top", maxStretch, stretchSinker);
|
var topCfg = Cfg(calibSize, plate, "top", maxStretch, stretchSinker);
|
||||||
Pass2Result pTop = Shaping.Shape(Topography.Generate(topCfg), topCfg);
|
Pass2Result pTop = Shaping.Shape(Topography.Generate(topCfg), topCfg);
|
||||||
if (t03 != null)
|
Pass2Result pUnstretched = Shaping.Shape(p1, offCfg);
|
||||||
hard.Add(ShapingOracle.NorthLocked("a3b", $"stretch {maxStretch:G3} ON: north of the band bit-identical to task-03 dump (terrain-curve-v1); changes only in/below the band", pTop.Height, t03, calibSize, bandRowC));
|
hard.Add(ShapingOracle.NorthLocked("a3c",
|
||||||
|
$"stretch {maxStretch:G3} ON: north of the band bit-identical to the SAME-RUN unstretched field; changes only in/below the band",
|
||||||
|
pTop.Height, pUnstretched.Height, calibSize, bandRowC));
|
||||||
foreach (var c in hard) GD.Print(" " + c);
|
foreach (var c in hard) GD.Print(" " + c);
|
||||||
|
|
||||||
if (!skip8k)
|
// ⚑ RETIRED at rivers/01 — a3 (`03_mountain_restore`) and a4b (`04_seed_gallery`).
|
||||||
{
|
// a3 was a curve-development intermediate, subsumed by the terrain-shape-v1 acceptance.
|
||||||
string t04Dump = Path.Combine(ToolingPaths.BatchesRoot, t04Source, $"{plate}", "height.f32");
|
// a4b asserted the north-lock against the PRE-FAMILY 8192 gallery — but the north-lock is
|
||||||
if (File.Exists(t04Dump))
|
// now proven scale-free against a same-run field (a3c above), so the external anchor bought
|
||||||
{
|
// nothing except an 8192 generation on every run.
|
||||||
GD.Print($" a4b: generating {plate} at {GallerySize}, stretch {maxStretch:G3} …");
|
// The dump is NOT deleted (file-safety; regenerable, and the record of what was judged);
|
||||||
var g = Cfg(GallerySize, plate, "top", maxStretch, stretchSinker);
|
// its `INDEX.md` is marked superseded. → XX_Human/output/rivers/01_*.report.md §A4.
|
||||||
Pass2Result pG = Shaping.Shape(Topography.Generate(g), g);
|
|
||||||
var a4b = ShapingOracle.NorthLocked("a4b", $"stretch {maxStretch:G3} ON at {GallerySize}: north of the band bit-identical to terrain-curve-v1's 04 gallery dump",
|
|
||||||
pG.Height, HeightField.Load(t04Dump, GallerySize), GallerySize, (int)(bandStart * GallerySize));
|
|
||||||
hard.Add(a4b); GD.Print(" " + a4b);
|
|
||||||
}
|
|
||||||
else GD.Print($" a4b: ⚠ skipped — no 04 gallery dump at {t04Dump}");
|
|
||||||
}
|
|
||||||
else GD.Print(" a4b: skipped (ISLA_SKIP_8K)");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ═══ 4. THE LADDER — 5 levels × 2 seeds ═══
|
// ═══ 4. THE LADDER — 5 levels × 2 seeds ═══
|
||||||
|
|
@ -420,7 +426,7 @@ namespace IslaApocalypse.Tools
|
||||||
var pass1 = new Dictionary<int, Pass1Result>();
|
var pass1 = new Dictionary<int, Pass1Result>();
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
var p1 = Topography.Generate(new TerrainGenConfig { MapSize = calibSize, Seed = s }); // bare default: offshore / revert / stretch OFF
|
var p1 = Topography.Generate(TerrainGenConfig.CalibrationPool(calibSize, s)); // family-off PINNED (rivers/01), not defaulted
|
||||||
pass1[s] = p1;
|
pass1[s] = p1;
|
||||||
rawPool.Accumulate(p1.Height, calibSize);
|
rawPool.Accumulate(p1.Height, calibSize);
|
||||||
}
|
}
|
||||||
|
|
@ -433,11 +439,14 @@ namespace IslaApocalypse.Tools
|
||||||
var outAbove = new LandHistogram(sea);
|
var outAbove = new LandHistogram(sea);
|
||||||
foreach (int s in CalibrationSeeds)
|
foreach (int s in CalibrationSeeds)
|
||||||
{
|
{
|
||||||
|
// ⭐ rivers/01: family-off PINNED, like the pool it shapes. (The family acts in pass 1 and
|
||||||
|
// `Shaping.Shape` never reads it, so this is inert today — pinned anyway so "the whole
|
||||||
|
// calibration is family-off" is a total claim rather than a field-by-field one.)
|
||||||
var scfg = new TerrainGenConfig
|
var scfg = new TerrainGenConfig
|
||||||
{
|
{
|
||||||
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
MapSize = calibSize, Seed = s, Curve = true, ShelfDetail = true,
|
||||||
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
CurveMode = CurveModeKind.Staircase, Knots = knots, Anchors = anchors, VariantLabel = "staircase",
|
||||||
};
|
}.WithFamilyOff();
|
||||||
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
Pass2Result st = Shaping.Shape(pass1[s], scfg);
|
||||||
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
rawAbove.AccumulateWhere(pass1[s].Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
outAbove.AccumulateWhere(st.Height, pass1[s].Height, calibSize, ceilingRaw);
|
||||||
|
|
|
||||||
|
|
@ -242,14 +242,20 @@ namespace IslaApocalypse.Tools
|
||||||
|
|
||||||
// ---- PASS 1b — the coast shelf + offshore islets (chat2/05) ----------
|
// ---- PASS 1b — the coast shelf + offshore islets (chat2/05) ----------
|
||||||
//
|
//
|
||||||
// ⚠⚠ BOTH DEFAULT OFF, DELIBERATELY — and that is a decision to revisit, not an oversight.
|
// ⚠⚠ BOTH STAY OFF AFTER THE rivers/01 RE-BASELINE — and each for its own reason.
|
||||||
//
|
//
|
||||||
// Every oracle in this phase holds pass 1 against Phase 1's `.f32` dumps (curve-off ==
|
// SHELF OFF because the LOCKED SHAPE has no shelf. `terrain-shape-v1` (a59e52f) was
|
||||||
// `02_pass1_port`), and the curve tools hold it against task 01/03's. The shelf changes every
|
// judged with `CoastShelf = false`, so turning it on here would produce terrain
|
||||||
// below-sea cell and the islets ADD LAND, so the moment either defaults ON, every one of
|
// the developer never approved and would break the bit-identity this default set
|
||||||
// those regression anchors goes stale at once. The batch tools that want them turn them on
|
// exists to guarantee. The shelf is below-sea only and invisible until water
|
||||||
// explicitly. FLIPPING THESE DEFAULTS IS THE ACT THAT RETIRES THE PHASE-1 REGRESSION DUMPS —
|
// renders — EVALUATING IT IS ITS OWN LATER TASK (→ D-041), once water renders.
|
||||||
// do it deliberately, in a task that re-baselines the oracles, not as a side effect here.
|
// (The rivers kickoff's "set shelf ON" was a mis-statement; corrected by the
|
||||||
|
// developer via master before rivers/01 ran.)
|
||||||
|
// ISLETS OFF because it is the DROPPED mechanism (→ D-063): islands are ORGANIC-ONLY,
|
||||||
|
// produced by the southern stretch + coastal fragmentation and then IDENTIFIED by
|
||||||
|
// the region layer — never placed. The pass is kept, not deleted, because
|
||||||
|
// `OffshoreSettings.Faithful` is a live port-fidelity control and `Pass1Result`
|
||||||
|
// carries the offshore seam. ⚠ NEVER RE-ENABLE IT IN THE BARE DEFAULT.
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The submarine coast shelf (<c>IslandFalloff.CoastShelf</c>). Below-sea only,
|
/// The submarine coast shelf (<c>IslandFalloff.CoastShelf</c>). Below-sea only,
|
||||||
|
|
@ -289,16 +295,27 @@ namespace IslaApocalypse.Tools
|
||||||
/// <see cref="MinLandComponentFrac"/> of the map to seabed. Origin-blind; lower-only and
|
/// <see cref="MinLandComponentFrac"/> of the map to seabed. Origin-blind; lower-only and
|
||||||
/// component-only, asserted; mainland never a candidate.
|
/// component-only, asserted; mainland never a candidate.
|
||||||
///
|
///
|
||||||
/// ⚠ DEFAULT OFF IN THE BARE CONFIG, for exactly the reason the shelf and the islets are: the
|
/// ⭐ ON BY DEFAULT since rivers/01 — it is part of the LOCKED SHAPE (`terrain-shape-v1`).
|
||||||
/// raw field has small natural nubs, so with this ON the calibration pool's land histogram, the
|
///
|
||||||
/// curve knots and every Phase-1 / task-03 / task-04 regression dump would move at once.
|
/// ⚠ It was default-OFF through chat 2 because turning it on moves the calibration pool's land
|
||||||
/// The region batch turns it on explicitly (its preset is ON); flipping the bare default is
|
/// histogram, the curve knots and every pre-family regression dump at once. rivers/01 is the
|
||||||
/// the act that re-baselines the oracles — own task, not a side effect.
|
/// task that owned that flip: the calibration pool is now pinned FAMILY-OFF
|
||||||
|
/// (<see cref="WithFamilyOff"/>), so the knots are unmoved and the flip is terrain-only.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool SpeckRevert = false;
|
public bool SpeckRevert = true;
|
||||||
|
|
||||||
/// <summary>The revert threshold, as a fraction of the map's AREA (scale-free). → <see cref="RegionPass.ThresholdMidFrac"/>.</summary>
|
/// <summary>
|
||||||
public float MinLandComponentFrac = RegionPass.ThresholdMidFrac;
|
/// The revert threshold, as a fraction of the map's AREA (scale-free).
|
||||||
|
///
|
||||||
|
/// ⚠⚠ THE LOCKED-SHAPE VALUE, PINNED AS A LITERAL — 2.5e-7 ≈ 4 cells at 4096, ≈ 17 at 8192.
|
||||||
|
/// It is deliberately NOT <see cref="RegionPass.ThresholdMidFrac"/> (3e-5, ~2,013 cells at
|
||||||
|
/// 8192), which was the pre-re-baseline default and is 120× larger: at that threshold the
|
||||||
|
/// revert eats real islands rather than specks. The three named `RegionPass.Threshold*Frac`
|
||||||
|
/// values are the chat2/07 exploration ladder; this is the value chat2/09–10 froze and the
|
||||||
|
/// developer judged. Changing it changes `terrain-shape-v1`. (rivers/01, from
|
||||||
|
/// `FragGalleryTool.FrozenSpeckFrac`.)
|
||||||
|
/// </summary>
|
||||||
|
public float MinLandComponentFrac = 2.5e-7f;
|
||||||
|
|
||||||
// ---- PASS 1 — THE SOUTHERN STRETCH (chat2/08, exploration) ----------------
|
// ---- PASS 1 — THE SOUTHERN STRETCH (chat2/08, exploration) ----------------
|
||||||
//
|
//
|
||||||
|
|
@ -311,14 +328,30 @@ namespace IslaApocalypse.Tools
|
||||||
// organically. Cells north of the band take the UNTOUCHED code path, so the classify field
|
// organically. Cells north of the band take the UNTOUCHED code path, so the classify field
|
||||||
// there is bit-identical by construction (asserted). Nothing is stamped.
|
// there is bit-identical by construction (asserted). Nothing is stamped.
|
||||||
|
|
||||||
/// <summary>⭐ THE SWEPT AXIS. 0 = off (bit-identical to the unstretched field everywhere). Stretch factor inside the band: 1 ⇒ the southward distance is halved, 3 ⇒ quartered.</summary>
|
/// <summary>
|
||||||
public float SouthStretch = 0f;
|
/// ⭐ THE SWEPT AXIS. 0 = off (bit-identical to the unstretched field everywhere). Stretch
|
||||||
|
/// factor inside the band: 1 ⇒ the southward distance is halved, 3 ⇒ quartered.
|
||||||
|
///
|
||||||
|
/// ⭐ 2 IS THE LOCKED-SHAPE VALUE since rivers/01 (→ D-065). With coastal fragmentation it is
|
||||||
|
/// one of the two mechanisms that MAKE the islands (→ D-063).
|
||||||
|
/// </summary>
|
||||||
|
public float SouthStretch = 2f;
|
||||||
|
|
||||||
/// <summary>The band's FIXED latitude line, fraction of the map (y runs south). Sea identity is hard above it. A constant for a whole batch.</summary>
|
/// <summary>
|
||||||
public float SouthBandStartFrac = SouthernStretch.DefaultBandStartFrac;
|
/// The band's FIXED latitude line, fraction of the map (y runs south). Sea identity is hard
|
||||||
|
/// above it. A constant for a whole batch.
|
||||||
|
/// ⚠ Pinned as a LITERAL at the locked-shape value (equals `SouthernStretch.DefaultBandStartFrac`
|
||||||
|
/// today). The literal is the pin: retuning that constant must not silently move
|
||||||
|
/// `terrain-shape-v1`. (rivers/01.)
|
||||||
|
/// </summary>
|
||||||
|
public float SouthBandStartFrac = 0.70f;
|
||||||
|
|
||||||
/// <summary>The feather width across which the stretch ramps 0 → 1 (smoothstep), fraction of the map. A constant for a whole batch.</summary>
|
/// <summary>
|
||||||
public float SouthBandFeatherFrac = SouthernStretch.DefaultBandFeatherFrac;
|
/// The feather width across which the stretch ramps 0 → 1 (smoothstep), fraction of the map.
|
||||||
|
/// ⚠ Pinned as a LITERAL at the locked-shape value (equals `SouthernStretch.DefaultBandFeatherFrac`
|
||||||
|
/// today) — same reason as <see cref="SouthBandStartFrac"/>. (rivers/01.)
|
||||||
|
/// </summary>
|
||||||
|
public float SouthBandFeatherFrac = 0.05f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Does the SOUTHERN SINKER ride the stretched distance (true — it is part of the southern
|
/// Does the SOUTHERN SINKER ride the stretched distance (true — it is part of the southern
|
||||||
|
|
@ -326,7 +359,8 @@ namespace IslaApocalypse.Tools
|
||||||
/// it keeps pulling the extended mass down where it always did)? The chat2/08 diagnostic
|
/// it keeps pulling the extended mass down where it always did)? The chat2/08 diagnostic
|
||||||
/// measured both; → <see cref="SouthernStretch"/>.
|
/// measured both; → <see cref="SouthernStretch"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool StretchSinker = SouthernStretch.DefaultStretchSinker;
|
/// <remarks>⚠ Pinned as a LITERAL at the locked-shape value (equals `SouthernStretch.DefaultStretchSinker` today). rivers/01.</remarks>
|
||||||
|
public bool StretchSinker = true;
|
||||||
|
|
||||||
// ---- PASS 1 — COASTAL FRAGMENTATION (chat2/09, exploration) ------------------
|
// ---- PASS 1 — COASTAL FRAGMENTATION (chat2/09, exploration) ------------------
|
||||||
//
|
//
|
||||||
|
|
@ -336,34 +370,59 @@ namespace IslaApocalypse.Tools
|
||||||
// into islands while the interior — window weight exactly zero — is bit-identical by
|
// into islands while the interior — window weight exactly zero — is bit-identical by
|
||||||
// construction. Nothing is detected, nothing is stamped. → CoastalFragment.
|
// construction. Nothing is detected, nothing is stamped. → CoastalFragment.
|
||||||
|
|
||||||
/// <summary>⭐ THE SWEPT AXIS. 0 = off (bit-identical everywhere). Peak |Δfalloff| (pre-power) at the window's centre.</summary>
|
/// <summary>
|
||||||
public float FragmentAmp = 0f;
|
/// ⭐ THE SWEPT AXIS. 0 = off (bit-identical everywhere). Peak |Δfalloff| (pre-power) at the
|
||||||
|
/// window's centre.
|
||||||
|
///
|
||||||
|
/// ⭐ 0.5 IS THE LOCKED-SHAPE VALUE since rivers/01 — chat2/09's `frag_4`, frozen by chat2/10
|
||||||
|
/// across an 8-seed gallery and tagged `terrain-shape-v1`. With the southern stretch it is one
|
||||||
|
/// of the two mechanisms that MAKE the islands (→ D-063).
|
||||||
|
/// </summary>
|
||||||
|
public float FragmentAmp = 0.5f;
|
||||||
|
|
||||||
/// <summary>The fragmentation noise's frequency, periods per map width — the neck/lobe scale. The secondary dial (fixed this round). → <see cref="CoastalFragment.DefaultFreqPerMapWidth"/>.</summary>
|
/// <summary>
|
||||||
public float FragmentFreqPerMapWidth = CoastalFragment.DefaultFreqPerMapWidth;
|
/// The fragmentation noise's frequency, periods per map width — the neck/lobe scale.
|
||||||
|
/// ⚠ Pinned as a LITERAL at the locked-shape value (equals `CoastalFragment.DefaultFreqPerMapWidth`
|
||||||
|
/// today); the literal is the pin. (rivers/01.)
|
||||||
|
/// </summary>
|
||||||
|
public float FragmentFreqPerMapWidth = 12f;
|
||||||
|
|
||||||
/// <summary>The coastal window's centre and half-width in PRE-power falloff units. Weight 1 at the centre, smooth to 0 at ± half-width; exactly 0 beyond.</summary>
|
/// <summary>
|
||||||
public float FragmentBandCentre = CoastalFragment.DefaultBandCentre;
|
/// The coastal window's centre and half-width in PRE-power falloff units. Weight 1 at the
|
||||||
public float FragmentBandHalfWidth = CoastalFragment.DefaultBandHalfWidth;
|
/// centre, smooth to 0 at ± half-width; exactly 0 beyond.
|
||||||
|
/// ⚠ Pinned as LITERALS at the locked-shape values (equal `CoastalFragment.DefaultBandCentre` /
|
||||||
|
/// `DefaultBandHalfWidth` today). (rivers/01.)
|
||||||
|
/// </summary>
|
||||||
|
public float FragmentBandCentre = 0.66f;
|
||||||
|
public float FragmentBandHalfWidth = 0.18f;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// false (default) ⇒ zero-mean noise: the margin is redrawn — bites AND builds (which can also
|
/// false (default) ⇒ zero-mean noise: the margin is redrawn — bites AND builds (which can also
|
||||||
/// bridge an island back onto the mainland). true ⇒ bites only ((noise+1)/2 ≥ 0): land can only
|
/// bridge an island back onto the mainland). true ⇒ bites only ((noise+1)/2 ≥ 0): land can only
|
||||||
/// recede, necks are cut, nothing is bridged, the coast net-recedes. → <see cref="CoastalFragment"/>.
|
/// recede, necks are cut, nothing is bridged, the coast net-recedes. → <see cref="CoastalFragment"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool FragmentBitesOnly = CoastalFragment.DefaultBitesOnly;
|
/// <remarks>⚠ Pinned as a LITERAL at the locked-shape value (equals `CoastalFragment.DefaultBitesOnly` today). rivers/01.</remarks>
|
||||||
|
public bool FragmentBitesOnly = false;
|
||||||
|
|
||||||
// ---- PASS 2b — HYDRAULIC EROSION (chat2/11) — RENDER MAP ONLY ------------------
|
// ---- PASS 2b — HYDRAULIC EROSION (chat2/11) — RENDER MAP ONLY ------------------
|
||||||
//
|
//
|
||||||
// The reference's droplet erosion, ported verbatim (Core.HydraulicErosion), run on the render
|
// The reference's droplet erosion, ported verbatim (Core.HydraulicErosion), run on the render
|
||||||
// field AFTER shaping (after detail, before the crater carve — which does not exist yet). The
|
// field AFTER shaping (after detail, before the crater carve — which does not exist yet). The
|
||||||
// classify field never sees it (D-046); the caller's flood guard proves no waterline moved.
|
// classify field never sees it (D-046); the caller's flood guard proves no waterline moved.
|
||||||
// ⚠ DEFAULT OFF in the bare config for the usual reason (regression anchors); the batch turns
|
// ⭐ DEFAULT ON since rivers/01 — the locked baseline the rivers epic routes on is the ERODED
|
||||||
// it on. The governors + physics are the reference ConfigManager's declared defaults, clamped
|
// render field. The governors + physics are the reference ConfigManager's declared defaults,
|
||||||
// as it clamped them (→ ErosionPass).
|
// clamped as it clamped them (→ ErosionPass).
|
||||||
|
//
|
||||||
|
// ⚠ This flag is INERT unless a caller explicitly runs `ErosionPass.Apply` — nothing in
|
||||||
|
// `Topography.Generate` or `Shaping.Shape` reads it. So flipping it moves no field on its own;
|
||||||
|
// it makes "erode by default" the answer for the callers that DO ask.
|
||||||
|
|
||||||
/// <summary>⭐ Erosion on/off. Render only. Default OFF (see above).</summary>
|
/// <summary>
|
||||||
public bool Erosion = false;
|
/// ⭐ Erosion on/off. RENDER MAP ONLY — the classify field never sees it (→ D-046), proven by
|
||||||
|
/// `ErosionPass`'s flood guard. Default ON since rivers/01 (see above); the erosion A/B sets
|
||||||
|
/// it to false explicitly for its OFF half.
|
||||||
|
/// </summary>
|
||||||
|
public bool Erosion = true;
|
||||||
|
|
||||||
/// <summary>Governor 1 — droplet count. Reference 250000, clamp [0, 50,000,000].</summary>
|
/// <summary>Governor 1 — droplet count. Reference 250000, clamp [0, 50,000,000].</summary>
|
||||||
public int ErosionDropletCount = 250000;
|
public int ErosionDropletCount = 250000;
|
||||||
|
|
@ -401,6 +460,75 @@ namespace IslaApocalypse.Tools
|
||||||
/// <summary>The scale object every distance and frequency in the generator derives from.</summary>
|
/// <summary>The scale object every distance and frequency in the generator derives from.</summary>
|
||||||
public GenerationScale Scale => new GenerationScale(MapSize);
|
public GenerationScale Scale => new GenerationScale(MapSize);
|
||||||
|
|
||||||
|
// ═══ ⭐⭐ THE FAMILY-OFF PIN (rivers/01) ═══════════════════════════════════════════════════
|
||||||
|
//
|
||||||
|
// ═══ WHY THIS EXISTS — the preserve mechanism for `terrain-shape-v1` ═══
|
||||||
|
//
|
||||||
|
// The locked shape's curve knots are PERCENTILES OF THE FAMILY-OFF LAND DISTRIBUTION, measured
|
||||||
|
// by chat2/01 over a 6-seed pool at 2048 and applied to FAMILY-ON generation. That was not a
|
||||||
|
// choice at the time — it was simply what the bare defaults produced, because the shape family
|
||||||
|
// defaulted off.
|
||||||
|
//
|
||||||
|
// rivers/01 flipped those defaults ON. Left alone, every calibration pool would have moved
|
||||||
|
// with them (fragmentation removes coastal land, the stretch adds southern land — both change
|
||||||
|
// the land CDF), the six knots would have moved, and with them the render field of EVERY
|
||||||
|
// batch, including `10_frag4_seed_gallery` (= `terrain-shape-v1`) and `11_erosion`. The
|
||||||
|
// developer's ruling was to PRESERVE the locked terrain bit-identically, so the pool is pinned
|
||||||
|
// here instead of the knots being baked: CALIBRATION STAYS LIVE, its INPUT DISTRIBUTION is
|
||||||
|
// what is held still.
|
||||||
|
//
|
||||||
|
// ⚠ At the moment it was introduced this was a NO-OP BY CONSTRUCTION: it sets exactly the
|
||||||
|
// values the bare defaults carried the instant before the flip. That is what made the flip
|
||||||
|
// provably terrain-only — and what the rivers/01 acceptance confirmed byte-for-byte at 8192².
|
||||||
|
//
|
||||||
|
// > ### ⚑ THE JUDGED-AND-PARKED PROPERTY (recorded for the vault, rivers/01)
|
||||||
|
// > The curve knots are percentiles of the FAMILY-OFF land distribution, applied to FAMILY-ON
|
||||||
|
// > terrain. That is a real asymmetry and it is DELIBERATE, not an oversight: re-pooling on
|
||||||
|
// > family-on land would move the locked shape the developer judged. Same disposition as the
|
||||||
|
// > mid-slope feather — documented, revisit only at the final palette / in the mesher if it
|
||||||
|
// > ever visibly bothers. → `Vision - Threads - Open Questions.md`.
|
||||||
|
//
|
||||||
|
// ═══ WHAT IT IS FOR, AND WHAT IT IS NOT FOR ═══
|
||||||
|
//
|
||||||
|
// USE IT for a config whose job is to REPRODUCE A PRE-FAMILY FIELD: a curve-calibration
|
||||||
|
// pool, or the "off" half of a regression check against a family-off `.f32` dump.
|
||||||
|
// DO NOT use it for generation — the locked shape IS the family, and the bare defaults now
|
||||||
|
// carry it.
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⭐ Pin the SHAPE FAMILY and erosion OFF on this config, independent of this class's
|
||||||
|
/// evolving defaults, and return it for chaining. → the block above for why.
|
||||||
|
///
|
||||||
|
/// Sets: <see cref="CoastShelf"/> false · <see cref="Offshore"/> Off ·
|
||||||
|
/// <see cref="SpeckRevert"/> false · <see cref="SouthStretch"/> 0 ·
|
||||||
|
/// <see cref="FragmentAmp"/> 0 · <see cref="Erosion"/> false.
|
||||||
|
///
|
||||||
|
/// ⚠ It deliberately does NOT touch <see cref="MinLandComponentFrac"/> or the band/window
|
||||||
|
/// shape dials: with the revert off and the amplitudes at zero those are unread, so pinning
|
||||||
|
/// them would assert an independence that does not exist. It also does not touch the CURVE
|
||||||
|
/// (knots, anchors, calibration, climb knobs) — the family and the curve are separate axes,
|
||||||
|
/// and a calibration pool is pass-1 only.
|
||||||
|
/// </summary>
|
||||||
|
public TerrainGenConfig WithFamilyOff()
|
||||||
|
{
|
||||||
|
CoastShelf = false;
|
||||||
|
Offshore = new OffshoreSettings(); // Mode = Off
|
||||||
|
SpeckRevert = false;
|
||||||
|
SouthStretch = 0f;
|
||||||
|
FragmentAmp = 0f;
|
||||||
|
Erosion = false;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⭐ A bare pass-1 config with the shape family pinned OFF — THE CALIBRATION POOL'S CONFIG.
|
||||||
|
/// Every curve-calibration pool in `Tools/` builds its fields through this, so there is one
|
||||||
|
/// place where "what distribution were the knots measured on?" is answered.
|
||||||
|
/// → <see cref="WithFamilyOff"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static TerrainGenConfig CalibrationPool(int mapSize, int seed) =>
|
||||||
|
new TerrainGenConfig { MapSize = mapSize, Seed = seed }.WithFamilyOff();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ⚠ DEEP on <see cref="Anchors"/>. <c>MemberwiseClone</c> is shallow, so two configs cloned
|
/// ⚠ DEEP on <see cref="Anchors"/>. <c>MemberwiseClone</c> is shallow, so two configs cloned
|
||||||
/// from one parent would share a single mutable anchor object and an A/B that edited one
|
/// from one parent would share a single mutable anchor object and an A/B that edited one
|
||||||
|
|
@ -421,6 +549,10 @@ namespace IslaApocalypse.Tools
|
||||||
$"[base={BaseNoise} falloff={IslandFalloff} edge={EdgeNoise} sinker={SouthernSinker} " +
|
$"[base={BaseNoise} falloff={IslandFalloff} edge={EdgeNoise} sinker={SouthernSinker} " +
|
||||||
$"trench={Trench} spine={MountainSpine}] " +
|
$"trench={Trench} spine={MountainSpine}] " +
|
||||||
$"[curve={Curve} detail={ShelfDetail} relief={ShelfReliefAmpM:F1}m edge={ShelfEdgeVariationM:F1}m " +
|
$"[curve={Curve} detail={ShelfDetail} relief={ShelfReliefAmpM:F1}m edge={ShelfEdgeVariationM:F1}m " +
|
||||||
$"knots={(Knots == null ? "-" : Knots.Name)}]";
|
$"knots={(Knots == null ? "-" : Knots.Name)}] " +
|
||||||
|
// rivers/01: the shape family is now a DEFAULT, so a run header must state it — otherwise
|
||||||
|
// "the defaults" stops being a readable claim the moment anyone asks which defaults.
|
||||||
|
$"[stretch={SouthStretch:G3} frag={FragmentAmp:G3} speck={(SpeckRevert ? $"{MinLandComponentFrac:G3}" : "off")} " +
|
||||||
|
$"shelf={CoastShelf} islets={Offshore?.Mode} erosion={Erosion}]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,10 @@ namespace IslaApocalypse.Tools
|
||||||
public override void _Ready()
|
public override void _Ready()
|
||||||
{
|
{
|
||||||
ToolingPaths.Configure(OS.GetUserDataDir());
|
ToolingPaths.Configure(OS.GetUserDataDir());
|
||||||
|
// ⭐ rivers/01: batches are namespaced by chat. The default is this tool's AUTHORING chat,
|
||||||
|
// so re-running it reproduces its own batch in place; ISLA_CHAT redirects a run to another
|
||||||
|
// chat's namespace — which is what keeps an acceptance run from overwriting its own anchor.
|
||||||
|
ToolingPaths.ConfigureChat(EnvStr(ToolingPaths.ChatVar, "chat1"));
|
||||||
|
|
||||||
int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize);
|
int mapSize = EnvInt("ISLA_MAPSIZE", DefaultMapSize);
|
||||||
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
int[] seeds = EnvSeeds("ISLA_SEEDS", DefaultSeeds);
|
||||||
|
|
@ -81,7 +85,11 @@ namespace IslaApocalypse.Tools
|
||||||
GD.Print("\n--- ABLATION LADDER (seed " + seeds[0] + ") ---");
|
GD.Print("\n--- ABLATION LADDER (seed " + seeds[0] + ") ---");
|
||||||
foreach (var (label, mutate) in Ladder())
|
foreach (var (label, mutate) in Ladder())
|
||||||
{
|
{
|
||||||
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seeds[0], VariantLabel = label };
|
// ⭐⭐ rivers/01 — FAMILY-OFF PINNED. This tool AUTHORED `02_pass1_port`, the Phase-1
|
||||||
|
// regression anchor that survived the re-baseline. If it picked up the family-on
|
||||||
|
// defaults it could no longer regenerate its own dump, and the last link between
|
||||||
|
// today's generator and the Phase-1 port would break silently.
|
||||||
|
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seeds[0], VariantLabel = label }.WithFamilyOff();
|
||||||
mutate(cfg);
|
mutate(cfg);
|
||||||
rows.Add(RunOne(cfg, batchRoot, skipRaw));
|
rows.Add(RunOne(cfg, batchRoot, skipRaw));
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +100,7 @@ namespace IslaApocalypse.Tools
|
||||||
GD.Print("\n--- SEED BATCH (full pass-1) ---");
|
GD.Print("\n--- SEED BATCH (full pass-1) ---");
|
||||||
foreach (int seed in seeds)
|
foreach (int seed in seeds)
|
||||||
{
|
{
|
||||||
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "full" };
|
var cfg = new TerrainGenConfig { MapSize = mapSize, Seed = seed, VariantLabel = "full" }.WithFamilyOff(); // ⭐ see the ladder above
|
||||||
rows.Add(RunOne(cfg, batchRoot, skipRaw));
|
rows.Add(RunOne(cfg, batchRoot, skipRaw));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
116
Tools/Scripts/TerrainShapeV1.cs
Normal file
116
Tools/Scripts/TerrainShapeV1.cs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
using System;
|
||||||
|
using IslaApocalypse.Core;
|
||||||
|
|
||||||
|
namespace IslaApocalypse.Tools
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// ⭐⭐ THE LOCKED SHAPE — `terrain-shape-v1` (commit <c>a59e52f</c>), AS AN ASSERTION.
|
||||||
|
///
|
||||||
|
/// ═══ ⚠⚠ THIS TYPE INVERTED AT rivers/01. READ THIS BEFORE USING IT. ═══
|
||||||
|
///
|
||||||
|
/// It used to be a PRESET: <c>Apply(cfg)</c> stamped the locked shape onto a bare config, because
|
||||||
|
/// the bare defaults did not reproduce the terrain the developer had judged. That was the single
|
||||||
|
/// most expensive fact in the codebase — *"run the default generator" ≠ "the terrain the developer
|
||||||
|
/// locked"* — and a fresh chat comparing bare-default output against the locked renders would see
|
||||||
|
/// differences that were CONFIGURATION, not regression.
|
||||||
|
///
|
||||||
|
/// **rivers/01 re-baselined the defaults so that `new TerrainGenConfig()` IS the locked shape.**
|
||||||
|
/// So <c>Apply</c> is GONE — there is nothing left to apply, and re-stamping the values on top of
|
||||||
|
/// the defaults would mask a default drift instead of catching it.
|
||||||
|
///
|
||||||
|
/// > ### What survives is the OPPOSITE job: these constants are now the ASSERTION TARGET.
|
||||||
|
/// > They are the values the developer judged, written down once, and <see cref="Assert"/> holds
|
||||||
|
/// > the live defaults against them. If a default is ever edited, the batch tools that claim to
|
||||||
|
/// > render the locked shape REFUSE TO RUN rather than quietly rendering something else.
|
||||||
|
///
|
||||||
|
/// ⚠ DO NOT "fix" a drift by editing these constants — they are the record of what was approved,
|
||||||
|
/// and `10_frag4_seed_gallery` / `11_erosion` are its pixels. A deliberate shape change moves the
|
||||||
|
/// defaults AND these constants AND re-runs the acceptance, in one task, as rivers/01 did.
|
||||||
|
///
|
||||||
|
/// ⚠ THE SHELF IS OFF, DELIBERATELY. The locked shape has no coast shelf; evaluating it (→ D-041)
|
||||||
|
/// is its own later task, once water renders. ⚠ THE ISLETS ARE OFF, PERMANENTLY (→ D-063): islands
|
||||||
|
/// are organic-only, made by the stretch + fragmentation and identified by the region layer.
|
||||||
|
///
|
||||||
|
/// ⚠ EROSION IS NOT PART OF THIS SHAPE. `terrain-shape-v1` is the pass-1/1c/2a field; erosion is
|
||||||
|
/// pass 2b, on top, render-only (`11_erosion` = `ea291ea`). It is asserted separately.
|
||||||
|
/// </summary>
|
||||||
|
public static class TerrainShapeV1
|
||||||
|
{
|
||||||
|
/// <summary>The tagged commit this shape is defined by. ⚠ Reference the COMMIT — the tag is annotated and local-only until the developer pushes it.</summary>
|
||||||
|
public const string Commit = "a59e52f";
|
||||||
|
|
||||||
|
public const float FragmentAmp = 0.5f, FragmentFreq = 12f, BandCentre = 0.66f, BandHalfWidth = 0.18f;
|
||||||
|
public const bool BitesOnly = false;
|
||||||
|
public const float Stretch = 2f, BandStart = 0.70f, BandFeather = 0.05f;
|
||||||
|
public const bool StretchSinker = true;
|
||||||
|
public const float SpeckFrac = 2.5e-7f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ⭐ THE DEFAULT-DRIFT GUARD. Throws unless a bare <see cref="TerrainGenConfig"/> carries the
|
||||||
|
/// locked shape exactly. Every batch tool that renders or asserts `terrain-shape-v1` calls this
|
||||||
|
/// before it generates anything.
|
||||||
|
///
|
||||||
|
/// ⚠ It is a THROW, not a warning, for the same reason <see cref="FileSafety"/> is: a batch
|
||||||
|
/// that renders the wrong terrain still produces beautiful, browsable, wrong PNGs, and a human
|
||||||
|
/// gate cannot see a default from a picture.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="who">The calling tool, for the message.</param>
|
||||||
|
public static void Assert(string who)
|
||||||
|
{
|
||||||
|
var d = new TerrainGenConfig();
|
||||||
|
string bad = null;
|
||||||
|
void Want(string name, object got, object want)
|
||||||
|
{
|
||||||
|
if (!Equals(got, want)) bad = (bad == null ? "" : bad + "; ") + $"{name} = {got}, expected {want}";
|
||||||
|
}
|
||||||
|
|
||||||
|
Want(nameof(d.SouthStretch), d.SouthStretch, Stretch);
|
||||||
|
Want(nameof(d.SouthBandStartFrac), d.SouthBandStartFrac, BandStart);
|
||||||
|
Want(nameof(d.SouthBandFeatherFrac), d.SouthBandFeatherFrac, BandFeather);
|
||||||
|
Want(nameof(d.StretchSinker), d.StretchSinker, StretchSinker);
|
||||||
|
Want(nameof(d.FragmentAmp), d.FragmentAmp, FragmentAmp);
|
||||||
|
Want(nameof(d.FragmentFreqPerMapWidth), d.FragmentFreqPerMapWidth, FragmentFreq);
|
||||||
|
Want(nameof(d.FragmentBandCentre), d.FragmentBandCentre, BandCentre);
|
||||||
|
Want(nameof(d.FragmentBandHalfWidth), d.FragmentBandHalfWidth, BandHalfWidth);
|
||||||
|
Want(nameof(d.FragmentBitesOnly), d.FragmentBitesOnly, BitesOnly);
|
||||||
|
Want(nameof(d.SpeckRevert), d.SpeckRevert, true);
|
||||||
|
Want(nameof(d.MinLandComponentFrac), d.MinLandComponentFrac, SpeckFrac);
|
||||||
|
Want(nameof(d.CoastShelf), d.CoastShelf, false);
|
||||||
|
Want(nameof(d.RegionLabeling), d.RegionLabeling, true);
|
||||||
|
Want("Offshore.Mode", d.Offshore.Mode, OffshoreMode.Off);
|
||||||
|
|
||||||
|
if (bad != null)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"[{who}] LOCKED-SHAPE DRIFT: the bare TerrainGenConfig no longer reproduces " +
|
||||||
|
$"terrain-shape-v1 ({Commit}) — {bad}. Refusing to run: this tool's output is only " +
|
||||||
|
"meaningful if the defaults ARE the locked shape. → Tools/Scripts/TerrainShapeV1.cs (rivers/01).");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The same guard for the EROSION default, kept separate because erosion is pass 2b and is not
|
||||||
|
/// part of the shape. Tools that render the erosion A/B set the flag per variant and call this
|
||||||
|
/// only if they rely on the default.
|
||||||
|
/// </summary>
|
||||||
|
public static void AssertErosionDefaultOn(string who)
|
||||||
|
{
|
||||||
|
if (!new TerrainGenConfig().Erosion)
|
||||||
|
throw new InvalidOperationException(
|
||||||
|
$"[{who}] EROSION DEFAULT DRIFT: bare TerrainGenConfig.Erosion is false; rivers/01 " +
|
||||||
|
"made it true (the rivers baseline routes on the eroded render field). Refusing to run.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One line for a run header, READ FROM THE LIVE DEFAULTS rather than from the constants —
|
||||||
|
/// so the header states what actually ran, and <see cref="Assert"/> states whether that is
|
||||||
|
/// still the locked shape.
|
||||||
|
/// </summary>
|
||||||
|
public static string Describe()
|
||||||
|
{
|
||||||
|
var d = new TerrainGenConfig();
|
||||||
|
return $"terrain-shape-v1 ({Commit}) from the BARE DEFAULTS: frag amp {d.FragmentAmp} freq {d.FragmentFreqPerMapWidth} " +
|
||||||
|
$"window {d.FragmentBandCentre}±{d.FragmentBandHalfWidth} · stretch {d.SouthStretch} " +
|
||||||
|
$"(band {d.SouthBandStartFrac}/{d.SouthBandFeatherFrac}, sinker {(d.StretchSinker ? "stretched" : "real-y")}) · " +
|
||||||
|
$"speck revert {d.MinLandComponentFrac:G2} · offshore {d.Offshore.Mode} · shelf {(d.CoastShelf ? "ON" : "OFF")} · labeling {(d.RegionLabeling ? "ON" : "OFF")}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -8,12 +8,33 @@ Real batch output is written under `ISLA_OUTPUT_DIR` (default `user://output/bat
|
||||||
## Layout
|
## Layout
|
||||||
|
|
||||||
```
|
```
|
||||||
batches/NN_<name>/ NN = the task number that ran it
|
batches/<chat>/NN_<name>/ <chat> = the chat namespace · NN = the task number that ran it
|
||||||
├── INDEX.md what varied, what to look at, what was concluded
|
├── INDEX.md what varied, what to look at, what was concluded
|
||||||
├── scratch/ intermediates — PERSISTENT, never cleaned
|
├── scratch/ intermediates — PERSISTENT, never cleaned
|
||||||
└── <seed>_<variant>/ one directory per generated world
|
└── <seed>_<variant>/ one directory per generated world
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### ⭐ The `<chat>` segment (rivers/01)
|
||||||
|
|
||||||
|
**`NN` is the AUTHORING TASK NUMBER, and task numbers restart at 00 in every build chat** — so a flat
|
||||||
|
`batches/` collides the moment a second chat exists. It did: chat 1's `02_pass1_port` and chat 2's
|
||||||
|
`02_curve_continuous` are both "batch 02", and nothing in either name says which chat made it. On the
|
||||||
|
real pile there were **four colliding prefixes (02, 03, 04, 06)** across 25 batches, separable only by
|
||||||
|
SLUG.
|
||||||
|
|
||||||
|
The slug is set by `ToolingPaths.ConfigureChat(...)` and is **required** — with none set, `BatchRoot`
|
||||||
|
throws rather than writing to the un-namespaced root. Each tool defaults to its own authoring chat, so
|
||||||
|
re-running it reproduces its batch in place; **`ISLA_CHAT` redirects a run to another namespace**, which
|
||||||
|
is what keeps an acceptance run from overwriting the very anchor it is checking against.
|
||||||
|
|
||||||
|
> ### ⚠ Writes are namespaced; historical READS carry the prefix themselves.
|
||||||
|
> `BatchRoot(task, descriptor)` inserts `<chat>`. `BatchesRoot` is the plain root, and every
|
||||||
|
> `ISLA_*_SOURCE` anchor composes against it — so an anchor default is written out in full, e.g.
|
||||||
|
> `"chat1/02_pass1_port"`. Namespacing only the writes would silently orphan every historical read.
|
||||||
|
> **That is why a missing anchor now THROWS** (`ShapingOracle.LoadAnchor`): a moved anchor used to make
|
||||||
|
> its oracle not RUN, and a batch with a silently-skipped check prints an all-PASS table that reads
|
||||||
|
> exactly like a clean one.
|
||||||
|
|
||||||
`INDEX.md` is not optional. A/B comparisons are browsed by a human, and a flat directory of
|
`INDEX.md` is not optional. A/B comparisons are browsed by a human, and a flat directory of
|
||||||
same-named PNGs is not browsable.
|
same-named PNGs is not browsable.
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue