islaApocalypse-v2/Tools/Scripts/SouthernStretch.cs
beezm 537bd3e048 chat2/08: southern stretch, EXPLORATION — the fragmentation knob space, diagnosed then laddered
Diagnostic first (SouthernStretchTool, ISLA_DIAG_ONLY; scratch/southern_diagnosis.md). Along
the central south profile the mask blend climbs 0.018 per 0.01 N, edge noise adds 0.03-0.06,
and the southern sinker is zero until 0.75 N then adds 0.024 per 0.01 N before the 2.5 power;
the coast sits at 0.76-0.82 N, ~0.06 N north of where the blend alone would end. The stretch
compresses the southward distance the mask sees inside a fixed feathered latitude band,
y' = yB + (y - yB)/(1 + s*ramp), texture (base noise, edge noise, latitude) untouched, so the
extended mass keeps the elevation of the rows it came from. With the sinker reading the REAL y
it cancels the stretch outright: median coast saturates at 0.83-0.84 from s = 1 to 16 and the
south stays 1-10 tiny nubs on both seeds. The lever is therefore two things - extend reach AND
hold the sinker back - and this pass couples them 1:1 (StretchSinker = true; the real-y mode is
kept as the config's other setting). Ladder chosen from the sweep, non-linear: 0.5, 1, 2, 3, 5
(peninsula / onset / few big pieces / big pieces consolidating / over-stretched sheet); 8 and 16
are indistinguishable from 5.

Topography: cells above the band take the untouched code path (fy == y), so the classify field
north of the band is bit-identical by construction. SouthernStretch.cs holds the band constants
(start 0.70 N, feather 0.05 N - fixed for the batch) and the ramp. TerrainGenConfig: SouthStretch
(the swept axis, 0 = off), SouthBandStartFrac, SouthBandFeatherFrac, StretchSinker.

Oracle, all passing: a1/a3 stretch OFF bit-identical; a3b stretch 5 north of the band
bit-identical to the task-03 dump (2.93 M cells), a4b the same at 8192 against the
terrain-curve-v1 gallery dump (46.97 M cells north of the band, 20.1 M differ below - the
relaxation); per field north bit-locked classify and render vs stretch 0 (p, p2), northern
island set invariant (q), centre is land, tag/coastline, classify == raw; ids and heights
deterministic. Batch: BatchRoot(8, "southern_stretch_explore") - 5 levels x 2 seeds
(1063685222, 999999937 - the two 07 seeds with the most mainland south of the band), lean render
(regions overlay + relief + .f32), the hemisphere-split count/size table. Region labeling on,
offshore / shelf / speck revert off: the pure fragmentation signal.

The read at 4096: s 0.5 peninsula (coast +0.04 N); s 1 onset (999999937 6 -> 12 southern islands,
11 substantial); s 2 few big pieces (35k / 86k-cell fragments); s 3-5 a solid sheet to the trench
whose southern edge is the trench wall - a ruler-straight coast - and whose island count falls as
pieces re-merge. Gravel never dominates. Graduation held, per the task.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013EY3ZTF6NwzF8ukBHQXSK7
2026-08-22 05:54:12 -04:00

57 lines
2.9 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using System;
namespace IslaApocalypse.Tools
{
/// <summary>
/// ⭐ THE SOUTHERN STRETCH (chat2/08, EXPLORATION) — the one deliberate relaxation of sea identity,
/// confined to a FIXED feathered latitude band.
///
/// ═══ THE MECHANISM (read from pass 1, chat2/08 diagnostic) ═══
///
/// Pass 1's mask is falloff = ½·ellipse + ½·squircle (+ edge noise · squircle), then the SOUTHERN
/// SINKER adds 0.6 · (y 0.75N)/(0.25N) for y &gt; 0.75N, BEFORE the 2.5 power; the coast sits
/// where rawBase falloff^2.5 crosses sea, i.e. near falloff ≈ 0.66. Every term that grows with y
/// is a function of the SOUTHWARD DISTANCE. The stretch compresses that distance inside the band:
///
/// y' = yB + (y yB) / (1 + stretch · ramp(y)) ramp = smoothstep over the feather
///
/// so a cell at y takes the mask geometry of the row y' north of it — the mass reaches further
/// south AND keeps the elevation profile of the rows it came from (the base noise, edge noise and
/// latitude field keep the real y — texture stays, only the mask's geometry stretches). Where the
/// stretched thin edge drops below sea it fragments organically. Nothing is stamped.
///
/// ⚠ North of the band (y ≤ yB) the caller takes the untouched code path: bit-identical by
/// construction, asserted by the oracle. The band line and feather are constants for a batch;
/// <c>TerrainGenConfig.SouthStretch</c> is the only swept axis.
///
/// The SINKER: <c>TerrainGenConfig.StretchSinker</c> decides whether it rides y' (pushed out with
/// the geometry — held back inside the band) or the real y (keeps pulling the extended mass down
/// where it always did). The chat2/08 diagnostic measured both — see the report.
/// </summary>
public static class SouthernStretch
{
/// <summary>The band's fixed latitude line, fraction of the map. Chosen by the chat2/08 diagnostic (see the report).</summary>
public const float DefaultBandStartFrac = 0.70f;
/// <summary>The feather width, fraction of the map.</summary>
public const float DefaultBandFeatherFrac = 0.05f;
/// <summary>Whether the sinker rides the stretched distance by default. Set by the chat2/08 diagnostic.</summary>
public const bool DefaultStretchSinker = true;
/// <summary>The smoothstep ramp across the feather: 0 at the band line, 1 a feather-width below it.</summary>
public static float Ramp(float y, float bandStart, float bandFeather)
{
float t = Math.Clamp((y - bandStart) / bandFeather, 0f, 1f);
return t * t * (3f - 2f * t);
}
/// <summary>The y the mask geometry sees. For y ≤ bandStart returns y unchanged.</summary>
public static float StretchedY(float y, float bandStart, float bandFeather, float stretch)
{
if (y <= bandStart || stretch <= 0f) return y;
float r = Ramp(y, bandStart, bandFeather);
return bandStart + (y - bandStart) / (1f + stretch * r);
}
}
}