feat: shelf-edge variation -- the red-line scalloping (terrain-water task 10)
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>
This commit is contained in:
parent
3b6d166458
commit
492b56a87c
7 changed files with 182 additions and 69 deletions
|
|
@ -158,6 +158,9 @@ namespace IslaApocalypse.Core
|
|||
writer.Write(d.Version); // u16
|
||||
writer.Write(d.ReliefAmpM); writer.Write(d.ReliefFreqIslands); // 2 × f32
|
||||
writer.Write(d.ReliefSeedOffset); // i32
|
||||
writer.Write(d.EdgeAmpM); writer.Write(d.EdgeFreqIslands); // 2 × f32
|
||||
writer.Write(d.EdgeSeedOffset); // i32
|
||||
writer.Write(d.EdgeMaxShiftM); // f32
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
|
|
|
|||
|
|
@ -33,11 +33,16 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
// are retired. Default: v5.
|
||||
public static string TerrainCurve = "v5";
|
||||
|
||||
// Terrain detail passes (task 10): "v1" = shelf micro-relief (requires the
|
||||
// curve; no-op when it is off), "off" disables it. ShelfReliefAmp is the
|
||||
// micro-relief amplitude in metres. Defaults: v1, 3 m.
|
||||
// Terrain detail passes (task 10): "v1" = shelf micro-relief + shelf-edge
|
||||
// variation as one judged unit (requires the curve; no-op when it is off);
|
||||
// "off" disables both. ShelfReliefAmp is the micro-relief amplitude in metres
|
||||
// of OUTPUT height. ShelfEdgeVariation is the shelf-edge warp amplitude in
|
||||
// metres of INPUT height — how far the shelf/riser boundary contour is
|
||||
// displaced, not an elevation change; it is clamped at load time to the
|
||||
// largest shift that keeps the curve's knots ordered. Defaults: v1, 3 m, 5 m.
|
||||
public static string TerrainDetail = "v1";
|
||||
public static float ShelfReliefAmp = 3.0f;
|
||||
public static float ShelfEdgeVariation = 5.0f;
|
||||
|
||||
public static void LoadConfig()
|
||||
{
|
||||
|
|
@ -137,6 +142,10 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
{
|
||||
ShelfReliefAmp = (float)data["ShelfReliefAmp"];
|
||||
}
|
||||
if (data.ContainsKey("ShelfEdgeVariation"))
|
||||
{
|
||||
ShelfEdgeVariation = (float)data["ShelfEdgeVariation"];
|
||||
}
|
||||
|
||||
switch (profile)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -89,16 +89,19 @@ namespace IslaApocalypse.Core
|
|||
|
||||
/// <summary>
|
||||
/// The terrain detail passes that shaped this blueprint's HGTS (v2 TDTL section,
|
||||
/// terrain-water task 10): the shelf micro-relief parameters. Null when detail
|
||||
/// was off. Metadata only — heights are already detailed. Body version 1 (the
|
||||
/// reverted relief + D8-incision layout) is longer and differently shaped; the
|
||||
/// parser refuses it rather than misreading it.
|
||||
/// terrain-water task 10): shelf micro-relief + shelf-edge variation parameters.
|
||||
/// Null when detail was off. Metadata only — heights are already detailed.
|
||||
/// Version 1 (relief + the reverted D8 incision) never left the task-10 batch
|
||||
/// tree; the parser rejects it rather than misreading its longer payload.
|
||||
/// </summary>
|
||||
public class TerrainDetailInfo
|
||||
{
|
||||
public ushort Version;
|
||||
public float ReliefAmpM, ReliefFreqIslands;
|
||||
public int ReliefSeedOffset;
|
||||
public float EdgeAmpM, EdgeFreqIslands;
|
||||
public int EdgeSeedOffset;
|
||||
public float EdgeMaxShiftM;
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
|
|
@ -454,14 +457,19 @@ namespace IslaApocalypse.Core
|
|||
d.Version = reader.ReadUInt16();
|
||||
if (d.Version != BlueprintFormat.TDTL_VERSION)
|
||||
{
|
||||
// Skip the body and leave TerrainDetail null. The heights are still
|
||||
// whatever they are — we simply refuse to describe them wrongly.
|
||||
GD.PrintErr($"[MapDataParser] ⚠ TDTL body version {d.Version} is not the current {BlueprintFormat.TDTL_VERSION} — section skipped, detail metadata unavailable.");
|
||||
// A TDTL v1 payload (the reverted relief+incision layout) is LONGER and
|
||||
// laid out differently; reading it as v2 would silently mint plausible
|
||||
// nonsense. Skip the body and leave TerrainDetail null — the heights are
|
||||
// still whatever they are, we just refuse to describe them wrongly.
|
||||
GD.PrintErr($"[MapDataParser] ⚠ TDTL version {d.Version} is not the current {BlueprintFormat.TDTL_VERSION} — section skipped, detail metadata unavailable.");
|
||||
reader.BaseStream.Seek((long)payloadLength - 2L, SeekOrigin.Current);
|
||||
return true;
|
||||
}
|
||||
d.ReliefAmpM = reader.ReadSingle(); d.ReliefFreqIslands = reader.ReadSingle();
|
||||
d.ReliefSeedOffset = reader.ReadInt32();
|
||||
d.EdgeAmpM = reader.ReadSingle(); d.EdgeFreqIslands = reader.ReadSingle();
|
||||
d.EdgeSeedOffset = reader.ReadInt32();
|
||||
d.EdgeMaxShiftM = reader.ReadSingle();
|
||||
blueprint.TerrainDetail = d;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue