feat: shelf micro-relief + drainage incision (terrain-water task 10)
Two paired detail passes on the locked v5 skeleton, output-heights only (classify path untouched): PASS A — shelf micro-relief: +-ShelfReliefAmp (default 3 m) Simplex skin (seed resolved+7409, ~40 undulations/island ~ 200 m features), weighted by shelf-ness (full mid-shelf, feathered to zero 30% of a band half-width into the risers) — both shelves get their rolling texture back; risers and peaks untouched. PASS B — drainage incision: D8 steepest-descent routing + height- ordered flow accumulation over the curved+relieved land (TerrainDetailPass — pure array machinery, named C++ candidate per D-035); depth = K*accum^p*slope (K=0.78, p=0.45 concave, cap 30 m), masked full on risers / 30% on shelves / zero on the toe, above the plateau top, and within 1.2x CraterRadius (feathered to 1.4x); hard clamp: carved height >= sea + 1 m. Ordering: curve -> relief -> incision -> crater carve (the carve stays the final authority; carve moved to its own pass 2c, bit-identical expression on both maps). The pass prints its own MEASURED depth distribution. Gate TerrainDetail "off"|"v1" (default v1; both passes one judged unit; no-op without the curve) + ShelfReliefAmp dial. New TDTL section (38 B: version + relief/incision params + seed offset) across writer/parser/harness — blueprints stay self-describing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c71026fd94
commit
45fe2ce034
7 changed files with 354 additions and 13 deletions
|
|
@ -33,6 +33,7 @@ namespace IslaApocalypse.Core
|
|||
public const uint TAG_WATER_BODY_TABLE = 0x42544257; // "WBTB"
|
||||
public const uint TAG_WATER_SURFACE = 0x46525357; // "WSRF"
|
||||
public const uint TAG_TERRAIN_CURVE = 0x56524354; // "TCRV"
|
||||
public const uint TAG_TERRAIN_DETAIL = 0x4C544454; // "TDTL"
|
||||
|
||||
// WSRF quantization: u16, 0 reserved as the no-water sentinel. A real level L
|
||||
// (raw blueprint height units) encodes as 1 + round(L × 32768), so a genuine
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ namespace IslaApocalypse.Core
|
|||
WriteSection(writer, BlueprintFormat.TAG_PARAMS, w => WriteParams(w, bp));
|
||||
if (bp.TerrainCurve != null)
|
||||
WriteSection(writer, BlueprintFormat.TAG_TERRAIN_CURVE, w => WriteTerrainCurve(w, bp.TerrainCurve));
|
||||
if (bp.TerrainDetail != null)
|
||||
WriteSection(writer, BlueprintFormat.TAG_TERRAIN_DETAIL, w => WriteTerrainDetail(w, bp.TerrainDetail));
|
||||
WriteSection(writer, BlueprintFormat.TAG_HEIGHTS, w => WriteHeights(w, bp));
|
||||
WriteSection(writer, BlueprintFormat.TAG_BIOMES, w => WriteBiomes(w, bp));
|
||||
|
||||
|
|
@ -151,6 +153,16 @@ namespace IslaApocalypse.Core
|
|||
}
|
||||
}
|
||||
|
||||
private static void WriteTerrainDetail(BinaryWriter writer, TerrainDetailInfo d)
|
||||
{
|
||||
writer.Write(d.Version);
|
||||
writer.Write(d.ReliefAmpM); writer.Write(d.ReliefFreqIslands);
|
||||
writer.Write(d.IncK); writer.Write(d.IncP); writer.Write(d.IncCapM);
|
||||
writer.Write(d.SeaClampRaw); writer.Write(d.CraterExclFactor);
|
||||
writer.Write(d.ShelfIncWeight);
|
||||
writer.Write(d.ReliefSeedOffset);
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
{
|
||||
int n = bp.MapSize;
|
||||
|
|
|
|||
|
|
@ -33,6 +33,13 @@ 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 + drainage
|
||||
// incision as one judged unit (requires the curve; no-op when it is off);
|
||||
// "off" disables both. ShelfReliefAmp is the micro-relief amplitude in
|
||||
// metres. Defaults: v1, 3 m.
|
||||
public static string TerrainDetail = "v1";
|
||||
public static float ShelfReliefAmp = 3.0f;
|
||||
|
||||
public static void LoadConfig()
|
||||
{
|
||||
string path = "res://ServerConfig.json";
|
||||
|
|
@ -118,6 +125,20 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
GD.PrintErr($"[ConfigManager] Unknown TerrainCurve '{curve}'. Keeping '{TerrainCurve}'.");
|
||||
}
|
||||
|
||||
// Extract the terrain-detail gate + relief amplitude
|
||||
if (data.ContainsKey("TerrainDetail"))
|
||||
{
|
||||
string detail = (string)data["TerrainDetail"];
|
||||
if (detail == "off" || detail == "v1")
|
||||
TerrainDetail = detail;
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown TerrainDetail '{detail}'. Keeping '{TerrainDetail}'.");
|
||||
}
|
||||
if (data.ContainsKey("ShelfReliefAmp"))
|
||||
{
|
||||
ShelfReliefAmp = (float)data["ShelfReliefAmp"];
|
||||
}
|
||||
|
||||
switch (profile)
|
||||
{
|
||||
case "4K":
|
||||
|
|
|
|||
|
|
@ -87,6 +87,20 @@ namespace IslaApocalypse.Core
|
|||
public float K5, K6;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The terrain detail passes that shaped this blueprint's HGTS (v2 TDTL section,
|
||||
/// terrain-water task 10): shelf micro-relief + drainage incision parameters.
|
||||
/// Null when detail was off. Metadata only — heights are already detailed.
|
||||
/// </summary>
|
||||
public class TerrainDetailInfo
|
||||
{
|
||||
public ushort Version;
|
||||
public float ReliefAmpM, ReliefFreqIslands;
|
||||
public float IncK, IncP, IncCapM;
|
||||
public float SeaClampRaw, CraterExclFactor, ShelfIncWeight;
|
||||
public int ReliefSeedOffset;
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
{
|
||||
public int MapSize;
|
||||
|
|
@ -114,6 +128,9 @@ namespace IslaApocalypse.Core
|
|||
|
||||
// The curve that shaped HeightMap (v2 TCRV section); null = raw legacy profile.
|
||||
public TerrainCurveInfo TerrainCurve;
|
||||
|
||||
// The detail passes that shaped HeightMap (TDTL section); null = no detail.
|
||||
public TerrainDetailInfo TerrainDetail;
|
||||
}
|
||||
|
||||
// 2. The Parser Utility
|
||||
|
|
@ -237,6 +254,7 @@ namespace IslaApocalypse.Core
|
|||
else if (tag == BlueprintFormat.TAG_WATER_SURFACE) sectionOk = ParseWaterGrid(reader, blueprint, payloadLength, isSurface: true);
|
||||
else if (tag == BlueprintFormat.TAG_WATER_BODY_TABLE) sectionOk = ParseWaterBodyTable(reader, blueprint);
|
||||
else if (tag == BlueprintFormat.TAG_TERRAIN_CURVE) sectionOk = ParseTerrainCurve(reader, blueprint);
|
||||
else if (tag == BlueprintFormat.TAG_TERRAIN_DETAIL) sectionOk = ParseTerrainDetail(reader, blueprint);
|
||||
else
|
||||
{
|
||||
// The property the redesign exists to buy: future sections (water,
|
||||
|
|
@ -430,6 +448,19 @@ namespace IslaApocalypse.Core
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool ParseTerrainDetail(BinaryReader reader, WorldBlueprint blueprint)
|
||||
{
|
||||
var d = new TerrainDetailInfo();
|
||||
d.Version = reader.ReadUInt16();
|
||||
d.ReliefAmpM = reader.ReadSingle(); d.ReliefFreqIslands = reader.ReadSingle();
|
||||
d.IncK = reader.ReadSingle(); d.IncP = reader.ReadSingle(); d.IncCapM = reader.ReadSingle();
|
||||
d.SeaClampRaw = reader.ReadSingle(); d.CraterExclFactor = reader.ReadSingle();
|
||||
d.ShelfIncWeight = reader.ReadSingle();
|
||||
d.ReliefSeedOffset = reader.ReadInt32();
|
||||
blueprint.TerrainDetail = d;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static bool ParseRoadTier(BinaryReader reader, List<Vector2[]> into)
|
||||
{
|
||||
int pathCount = reader.ReadInt32();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue