PASS B, replacing the reverted incision. The developer's sketch asked for
the shelf/riser BOUNDARY to be organic, not for water: notches, coves and
small peninsulas where a flat shelf meets its riser, instead of the clean
oval contour the curve produces.
Mechanism (the task's preferred "boundary warp", in its most
monotonic-safe form): every shelf/riser boundary is the contour where the
raw height crosses K3, K4 or K5, so the boundary is warped by SLIDING
THOSE THREE KNOTS per column -- edgeShift = simplex(resolvedSeed + 7507,
12 per island width) x ShelfEdgeVariation. The contours then wander in and
out of the terrain instead of tracing an iso-height line. Noise-warped by
construction, so there is no grid direction for an artifact to line up on
-- the failure mode of the thing this replaces.
Why slide knots rather than perturb a weight or the input height:
monotonicity becomes structural instead of conditional. The curve is
strictly monotonic for ANY ordered knot set, so no derivative bound, no
amplitude-vs-feather-width tuning, no way for a dial to invert a column.
MaxEdgeShift keeps the set ordered (half the smallest margin to a fixed
knot = 12.3 m of input height for the v5 knots); the config dial is
clamped to it, loudly. AssertMonotonic now sweeps 24 corners -- the 8
modulation extremes x {-max, 0, +max} shift -- and checks the bound first.
K1, K2 and K6 never move, which buys the guarantees exactly rather than
statistically: below K2 and above K6 a warped column is bit-identical to
an unwarped one, so the red ceiling still floors every shelf edge (storm
ladder safe), the 420 m cap still caps, and the toe and summit spikes are
untouched. The block slides rigidly, so the bench and mid-riser keep their
exact widths -- shelf interiors stay flat, riser interiors keep their
profile, and only the foothill riser and plateau stretch to absorb it.
The micro-relief mask takes the same shift, so pass A's skin follows the
shelf wherever pass B moved its edge.
Dial ShelfEdgeVariation (default 5 m of INPUT height -- a boundary
displacement, not an elevation change) under the existing TerrainDetail
gate; both passes stay one judged unit. TDTL v2 body records relief and
edge amp/frequency/seed-offset plus the applied clamp bound; writer,
parser and harness follow. No blueprint was written between the revert
and this commit, so v2 only ever means relief + edge warp on disk.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
76 lines
3.8 KiB
C#
76 lines
3.8 KiB
C#
using Godot;
|
||
|
||
/// <summary>
|
||
/// The terrain DETAIL passes (terrain-water task 10) — pure numeric functions
|
||
/// (D-035; a named future C++ candidate, kept standalone):
|
||
///
|
||
/// PASS A — shelf micro-relief: a medium-frequency noise skin (±ShelfReliefAmp,
|
||
/// default 3 m) weighted by shelf-ness, so the compressed shelves get their
|
||
/// rolling texture back while risers and peaks stay untouched.
|
||
///
|
||
/// PASS B — shelf-edge variation: a per-column shift of the shelf/riser KNOT
|
||
/// BLOCK (K3/K4/K5) by a low-frequency noise field, so the boundary where a
|
||
/// shelf meets its riser wanders in and out instead of tracing a clean height
|
||
/// contour — organic notches, coves and peninsulas at the shelf edge.
|
||
///
|
||
/// Both are output-height only; the classify map never sees either of them.
|
||
///
|
||
/// NOTE — what this file deliberately does NOT contain: the task-10 draft's D8
|
||
/// flow routing / accumulation / drainage incision. It shipped, produced the
|
||
/// canonical grid artifact (thousands of straight, disconnected, pooling
|
||
/// scratches along the D8 neighbour directions) and was reverted whole. Rivers
|
||
/// and erosion are Phase C work — a hydraulic-erosion pass over FINAL terrain,
|
||
/// not a per-cell steepest-descent carve on a grid.
|
||
/// </summary>
|
||
public static class TerrainDetailPass
|
||
{
|
||
// The TDTL body version is owned by the format (Core); the pass just stamps it.
|
||
public const ushort VERSION = IslaApocalypse.Core.BlueprintFormat.TDTL_VERSION;
|
||
|
||
// Pass A — micro-relief.
|
||
public const float RELIEF_AMP_DEFAULT_M = 3f; // config dial: ShelfReliefAmp (metres)
|
||
public const float RELIEF_FREQ_ISLANDS = 40f; // ~40 undulations per island width (~200 m features)
|
||
public const int RELIEF_SEED_OFFSET = 7409;
|
||
|
||
// Pass B — shelf-edge variation. The amplitude is stated in metres of INPUT
|
||
// height (raw × 251): it is how far, in raw-height terms, a shelf boundary
|
||
// contour is displaced — not an output elevation change. Lateral wander on
|
||
// the map is that displacement divided by the local raw gradient.
|
||
public const float EDGE_AMP_DEFAULT_M = 5f; // config dial: ShelfEdgeVariation (metres of input height)
|
||
public const float EDGE_FREQ_ISLANDS = 12f; // ~12 undulations per island width — a handful of notches
|
||
public const int EDGE_SEED_OFFSET = 7507; // per shelf perimeter, not hundreds of teeth
|
||
public const float EDGE_SAFETY_FRACTION = 0.5f; // shift ≤ half the smallest margin to a fixed knot
|
||
|
||
/// <summary>
|
||
/// The largest per-column knot shift this preset can take while keeping the
|
||
/// knot set strictly ordered (K2 < K3+d, K5+d < K6) with margin to spare.
|
||
/// K1/K2/K6 never move, so the toe, the orange/red bands and the summit spike
|
||
/// are bit-identical whatever the warp does — which is what makes the
|
||
/// red-ceiling and peak-height guarantees exact rather than statistical.
|
||
/// </summary>
|
||
public static float MaxEdgeShift(CurveKnots k)
|
||
{
|
||
return EDGE_SAFETY_FRACTION * Mathf.Min(k.K3 - k.K2, k.K6 - k.K5);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Shelf-ness weight from the RAW input height: 1 mid-shelf, feathering to 0
|
||
/// through the risers (feather extends 30 % of the band half-width past each
|
||
/// shelf edge). Covers both shelves. <paramref name="edgeShift"/> is the same
|
||
/// per-column warp the curve is evaluated with, so the micro-relief skin
|
||
/// follows the shelf wherever pass B has moved its boundary.
|
||
/// </summary>
|
||
public static float ShelfWeight(float raw, CurveKnots k, float edgeShift)
|
||
{
|
||
return Mathf.Max(BandBump(raw, k.K3 + edgeShift, k.K4 + edgeShift),
|
||
BandBump(raw, k.K5 + edgeShift, k.K6));
|
||
}
|
||
|
||
private static float BandBump(float h, float lo, float hi)
|
||
{
|
||
float half = (hi - lo) * 0.5f;
|
||
float t = Mathf.Abs(h - (lo + half)) / half; // 0 centre, 1 at band edge
|
||
// full inside 60 % of the band, linear feather to zero at 130 %
|
||
return Mathf.Clamp(1f - (t - 0.6f) / 0.7f, 0f, 1f);
|
||
}
|
||
}
|