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
116 lines
6.2 KiB
C#
116 lines
6.2 KiB
C#
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")}";
|
|
}
|
|
}
|
|
}
|