revert: the D8 drainage incision, whole (terrain-water task 10)
The task-10 draft's PASS B shipped and produced the canonical grid
artifact: thousands of straight, disconnected, pooling scratches. Per-
cell steepest descent on a regular grid can only route along its eight
neighbour headings, so at map scale the "channels" read as hatching,
not drainage. Reverted entirely rather than tuned -- no K, p or mask
setting fixes a directional basis. Rivers and erosion move to Phase C
as a hydraulic-erosion pass over FINAL terrain.
Removed: FlowAccumulation / IncisionWeight / EdgeBlend and the INC_*,
SEA_CLAMP, SHELF_INC_WEIGHT and CRATER_* constants; RunIncisionPass and
the pass-2b call; the six incision fields from TDTL (writer, parser,
harness). KEPT, untouched and developer-approved: pass A, the shelf
micro-relief skin, and its ShelfReliefAmp dial.
The crater carve folds back into the single pass-2 loop it came from,
carving two LOCALS written once each. That is what the code did before
the draft split it out, and it makes the aliased double-carve that the
split introduced (fix 1b98fb5) structurally impossible rather than
merely fixed -- there is no longer a read-modify-write to get wrong.
TDTL bodies are now versioned (BlueprintFormat.TDTL_VERSION = 2) and the
parser skips a body version it does not know instead of misreading the
longer v1 layout into plausible nonsense. The only v1 payloads that
exist are in the reverted batch's own tree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
e3f7b508e7
commit
3b6d166458
7 changed files with 56 additions and 224 deletions
|
|
@ -1,43 +1,34 @@
|
|||
using Godot;
|
||||
using System;
|
||||
|
||||
/// <summary>
|
||||
/// The terrain DETAIL passes (terrain-water task 10) — pure numeric array machinery
|
||||
/// 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 — drainage incision: D8 steepest-descent flow routing + accumulation
|
||||
/// over the curved terrain; depth = K · accum^p · localSlope (capped), masked to
|
||||
/// the risers (feathered ~30 % onto shelves, zero on the toe and above the
|
||||
/// plateau top, zero near the crater), clamped so carved terrain never drops
|
||||
/// below sea + 1 m. The channels double as the future river routes (Phase C).
|
||||
/// Output-height only; the classify map never sees it.
|
||||
///
|
||||
/// Ordering (enforced by the caller): curve → micro-relief → incision → crater
|
||||
/// carve. The classify map never sees any of it.
|
||||
/// NOTE — what this file deliberately no longer contains: the task-10 draft's D8
|
||||
/// steepest-descent flow routing, accumulation and drainage incision. It shipped,
|
||||
/// and it produced the canonical grid artifact — thousands of straight,
|
||||
/// disconnected, pooling scratches running along the eight D8 neighbour
|
||||
/// directions, because per-cell steepest descent on a regular grid can only ever
|
||||
/// route along those eight headings. It is reverted whole. Rivers and erosion are
|
||||
/// Phase C work: a hydraulic-erosion pass over FINAL terrain, not a routing carve
|
||||
/// on a grid mid-pipeline.
|
||||
/// </summary>
|
||||
public static class TerrainDetailPass
|
||||
{
|
||||
public const ushort VERSION = 1;
|
||||
// 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 — incision. K/p tuned against the depth targets (gullies 8–15 m,
|
||||
// trunks ~25 m, cap 30 m); the tuning run's achieved distribution is in the
|
||||
// task-10 report.
|
||||
public const float INC_K = 1.40f;
|
||||
public const float INC_P = 0.45f; // concave: many fingers, few deep trunks
|
||||
public const float INC_CAP_M = 30f; // IncisionMax
|
||||
public const float SEA_CLAMP = 0.15f + 1f / 251f; // carved height ≥ sea + 1 m
|
||||
public const float SHELF_INC_WEIGHT = 0.3f; // shelves get washes, not gorges
|
||||
public const float CRATER_EXCL_FACTOR = 1.2f; // zero incision inside this × CraterRadius
|
||||
public const float CRATER_FEATHER_FACTOR = 1.4f; // ...feathering to full by this × CraterRadius
|
||||
|
||||
/// <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
|
||||
|
|
@ -55,89 +46,4 @@ public static class TerrainDetailPass
|
|||
// full inside 60 % of the band, linear feather to zero at 130 %
|
||||
return Mathf.Clamp(1f - (t - 0.6f) / 0.7f, 0f, 1f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Incision mask from the RAW input height: 0 below the red-ceiling input (K2)
|
||||
/// and above the plateau top (K6); 1 on the riser bands; SHELF_INC_WEIGHT on the
|
||||
/// shelf bands; smooth feathers (15 % of the local band width) at every boundary.
|
||||
/// </summary>
|
||||
public static float IncisionWeight(float raw, CurveKnots k)
|
||||
{
|
||||
if (raw <= k.K2 || raw >= k.K6) return 0f;
|
||||
if (raw < k.K3) // foothill riser: feather in from K2, feather toward shelf weight at K3
|
||||
return EdgeBlend(raw, k.K2, k.K3, 0f, 1f, SHELF_INC_WEIGHT);
|
||||
if (raw < k.K4) // bench
|
||||
return SHELF_INC_WEIGHT;
|
||||
if (raw < k.K5) // mid riser
|
||||
return EdgeBlend(raw, k.K4, k.K5, SHELF_INC_WEIGHT, 1f, SHELF_INC_WEIGHT);
|
||||
// plateau band: shelf weight, feathering to zero at K6
|
||||
float w = (k.K6 - raw) / ((k.K6 - k.K5) * 0.15f);
|
||||
return Mathf.Min(SHELF_INC_WEIGHT, Mathf.Clamp(w, 0f, 1f) * SHELF_INC_WEIGHT);
|
||||
}
|
||||
|
||||
private static float EdgeBlend(float h, float lo, float hi, float wIn, float wMid, float wOut)
|
||||
{
|
||||
float f = (hi - lo) * 0.15f;
|
||||
if (h < lo + f) return Mathf.Lerp(wIn, wMid, (h - lo) / f);
|
||||
if (h > hi - f) return Mathf.Lerp(wMid, wOut, (h - (hi - f)) / f);
|
||||
return wMid;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// D8 flow accumulation over a height field (row-major idx = x·n + y).
|
||||
/// Steepest-descent routing (drop / distance, diagonals ÷√2), deterministic
|
||||
/// tie-break (fixed neighbour order, first winner). Cells with no lower
|
||||
/// neighbour are pits/outlets (no outflow). accum = upslope contributing cells
|
||||
/// including self; steepestDrop = drop per pixel toward the chosen neighbour.
|
||||
/// </summary>
|
||||
public static int[] FlowAccumulation(float[] h, int n, out float[] steepestDrop)
|
||||
{
|
||||
int total = n * n;
|
||||
int[] downstream = new int[total];
|
||||
steepestDrop = new float[total];
|
||||
int[] dx = { 1, -1, 0, 0, 1, 1, -1, -1 };
|
||||
int[] dy = { 0, 0, 1, -1, 1, -1, 1, -1 };
|
||||
float[] invDist = { 1f, 1f, 1f, 1f, 0.7071068f, 0.7071068f, 0.7071068f, 0.7071068f };
|
||||
|
||||
for (int x = 0; x < n; x++)
|
||||
{
|
||||
for (int y = 0; y < n; y++)
|
||||
{
|
||||
int i = x * n + y;
|
||||
float hc = h[i];
|
||||
float best = 0f;
|
||||
int bestIdx = -1;
|
||||
for (int d = 0; d < 8; d++)
|
||||
{
|
||||
int nx = x + dx[d], ny = y + dy[d];
|
||||
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||
int ni = nx * n + ny;
|
||||
float grade = (hc - h[ni]) * invDist[d];
|
||||
if (grade > best)
|
||||
{
|
||||
best = grade;
|
||||
bestIdx = ni;
|
||||
}
|
||||
}
|
||||
downstream[i] = bestIdx;
|
||||
steepestDrop[i] = best;
|
||||
}
|
||||
}
|
||||
|
||||
// Height-descending order: each cell pushes its accumulated count downstream.
|
||||
float[] keys = (float[])h.Clone();
|
||||
int[] order = new int[total];
|
||||
for (int i = 0; i < total; i++) order[i] = i;
|
||||
Array.Sort(keys, order); // ascending
|
||||
|
||||
int[] accum = new int[total];
|
||||
for (int i = 0; i < total; i++) accum[i] = 1;
|
||||
for (int i = total - 1; i >= 0; i--)
|
||||
{
|
||||
int c = order[i];
|
||||
int d = downstream[c];
|
||||
if (d >= 0) accum[d] += accum[c];
|
||||
}
|
||||
return accum;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue