fix: shrink erosion's crater exclusion to the strike core; add FULL/FEATHER modes (terrain-water task 19)
Task 17's hard 1.2 x CraterRadius cutoff left a visible un-eroded disc with a hard edge. Measured (radius dump, seed 1280587109): the carve writes only inside 0.80 x (640 px) and its displacement is EXACTLY 0 beyond that, so the 640-960 px annulus was 620,811 LAND cells of ordinary terrain held smooth for no geometric reason. That annulus is now eroded — 98% of those cells are touched. The protected core is the carve's own extent (CraterErosionCore, 0.80 x). CraterErosionMode selects the transition: "full" applies full strength at the core boundary (older-crater look), "feather" ramps 0->full out to CraterErosionFeather (1.05 x, mirroring the detail pass) for a younger-crater look with no seam. Implemented as a WEIGHT that scales carve and deposit amounts, not a skip, which is what makes feather a one-liner. Default "feather" pending the gate. Core = the carve radius is load-bearing, not tidy: the carve runs AFTER erosion and scales height toward the sea target, so it amplifies any erosion delta inside its radius. Measured at a 0.50 core: 79 cells newly below the rendered sea and 113,310 below-sea cells disturbed — the in-pass flood guard cannot see this because it measures before the carve. At 0.80 the two passes touch disjoint cells and the guarantee is exact again (0/0/0). A narrower core also reclaims nothing extra, since the over-protected annulus lies entirely outside the carve. MapGenerator now owns CRATER_CARVE_FACTOR as the single source of that 0.80 and warns loudly if the configured core is narrower. Verified both modes, seed 1280587109: 1_biomes/0_water md5-identical to erosion-OFF, BIOM/WBID bitwise equal, 0 newly below/above sea, 0 below-sea cells modified, 0 cells modified inside the core, island top 457.65 m unchanged, isotropy 1.005/1.007. Bay-to-ocean connection demonstrated explicitly by flood fill through rendered water to the map's north border (2,122,935 cells), not merely argued from the sea clamp. EROS body version -> 3: craterExclFactor replaced by craterCoreFactor + craterFeatherFactor + craterMode. Round-trip harness PASS with a real v3 payload. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
85e7bceb72
commit
f810ff44dc
7 changed files with 141 additions and 24 deletions
|
|
@ -49,7 +49,9 @@ namespace IslaApocalypse.Core
|
|||
// current) inserts the DEPOSIT CAP governor after the carve cap — deposition is
|
||||
// now brush-spread and per-cell bounded. Same reader rule as TDTL: an unknown
|
||||
// body version is skipped whole rather than misread into plausible nonsense.
|
||||
public const ushort EROS_VERSION = 2;
|
||||
// 3 (task 19, current) replaces the single crater exclusion factor with the
|
||||
// protected-core factor, a feather-band factor, and the crater mode byte.
|
||||
public const ushort EROS_VERSION = 3;
|
||||
|
||||
// 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
|
||||
|
|
|
|||
|
|
@ -176,7 +176,9 @@ namespace IslaApocalypse.Core
|
|||
writer.Write(e.MinSlopeM); // f32
|
||||
writer.Write(e.ErodeRate); writer.Write(e.DepositRate); // 2 × f32
|
||||
writer.Write(e.Evaporation); writer.Write(e.Gravity); // 2 × f32
|
||||
writer.Write(e.CraterExclFactor); // f32
|
||||
writer.Write(e.CraterCoreFactor); // f32
|
||||
writer.Write(e.CraterFeatherFactor); // f32
|
||||
writer.Write(e.CraterMode); // u8
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
|
|
|
|||
|
|
@ -79,6 +79,32 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
public static float ErosionEvaporation = 0.004f;
|
||||
public static float ErosionGravity = 4.0f;
|
||||
|
||||
// How erosion treats the crater surrounds (task 19). The task-17 hard
|
||||
// 1.2 × CraterRadius cutoff left a visible un-eroded disc: measured, the carve
|
||||
// writes only inside 0.80 × and its displacement is exactly 0 beyond that, so
|
||||
// 620 811 LAND cells of ordinary terrain were being held smooth for no
|
||||
// geometric reason. Now only the deep strike core is protected.
|
||||
// "full" — full-strength erosion right up to the core boundary. The
|
||||
// crater formed after the terrain and has weathered since.
|
||||
// "feather" — erosion ramps 0→full across CraterErosionCore →
|
||||
// CraterErosionFeather (the detail pass's shape), so the crater
|
||||
// reads as younger, less-weathered, and there is no seam at all.
|
||||
// Radii are FACTORS of CraterRadius. The flooded bay and its sea connection do
|
||||
// NOT depend on these: below-sea cells are read-only in both directions.
|
||||
public static string CraterErosionMode = "feather";
|
||||
public static float CraterErosionCore = CRATER_EROSION_CORE_DEFAULT;
|
||||
public static float CraterErosionFeather = CRATER_EROSION_FEATHER_DEFAULT;
|
||||
// 0.80 = the carve's OWN extent (MapGenerator's physicalCraterRadius). Keeping the
|
||||
// core at least this wide is what makes erosion and the carve touch DISJOINT cells,
|
||||
// which is what keeps the post-carve flood guard exactly zero: the carve runs after
|
||||
// erosion and scales height toward the sea target, so it AMPLIFIES any erosion delta
|
||||
// inside its radius and can push a hair-above-sea cell across the waterline. Measured
|
||||
// at a 0.50 core: 79 cells newly below the rendered sea, 113 310 below-sea cells
|
||||
// disturbed. A smaller core reclaims nothing extra either — the over-protected annulus
|
||||
// is 0.80x-1.2x, entirely outside the carve.
|
||||
public const float CRATER_EROSION_CORE_DEFAULT = 0.80f;
|
||||
public const float CRATER_EROSION_FEATHER_DEFAULT = 1.05f;
|
||||
|
||||
// Island falloff shaping (task 11).
|
||||
//
|
||||
// CoastProfile: "wide" adds the submarine shelf — the height curve is identity
|
||||
|
|
@ -246,6 +272,29 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
ErosionCarveCap = Mathf.Clamp(ErosionCarveCap, 0f, 60f);
|
||||
if (rawCount != ErosionDropletCount || rawLife != ErosionDropletLifetime || rawCap != ErosionCarveCap)
|
||||
GD.PrintErr($"[ConfigManager] Erosion governor out of bounds — clamped: count {rawCount}->{ErosionDropletCount}, lifetime {rawLife}->{ErosionDropletLifetime}, cap {rawCap}->{ErosionCarveCap} m.");
|
||||
// Crater erosion treatment (task 19)
|
||||
if (data.ContainsKey("CraterErosionMode"))
|
||||
{
|
||||
string cm = (string)data["CraterErosionMode"];
|
||||
if (cm == "full" || cm == "feather")
|
||||
CraterErosionMode = cm;
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown CraterErosionMode '{cm}'. Keeping '{CraterErosionMode}'.");
|
||||
}
|
||||
if (data.ContainsKey("CraterErosionCore")) CraterErosionCore = (float)data["CraterErosionCore"];
|
||||
if (data.ContainsKey("CraterErosionFeather")) CraterErosionFeather = (float)data["CraterErosionFeather"];
|
||||
CraterErosionCore = Mathf.Clamp(CraterErosionCore, 0f, 3f);
|
||||
CraterErosionFeather = Mathf.Clamp(CraterErosionFeather, 0f, 4f);
|
||||
// A feather band that does not extend past the core is not a band; say so
|
||||
// rather than silently behaving like "full".
|
||||
if (CraterErosionMode == "feather" && CraterErosionFeather <= CraterErosionCore)
|
||||
{
|
||||
GD.PrintErr($"[ConfigManager] CraterErosionFeather {CraterErosionFeather:F2} must exceed " +
|
||||
$"CraterErosionCore {CraterErosionCore:F2} — the ramp would have zero width. " +
|
||||
$"Restoring {CRATER_EROSION_FEATHER_DEFAULT:F2}.");
|
||||
CraterErosionFeather = CRATER_EROSION_FEATHER_DEFAULT;
|
||||
}
|
||||
|
||||
// Negative is meaningless; 0 is the documented "unbounded" escape hatch.
|
||||
ErosionDepositCap = Mathf.Clamp(ErosionDepositCap, 0f, 60f);
|
||||
ErosionSeaMargin = Mathf.Clamp(ErosionSeaMargin, 0f, 5f);
|
||||
|
|
|
|||
|
|
@ -118,7 +118,8 @@ namespace IslaApocalypse.Core
|
|||
public float CarveCapM, DepositCapM, SeaMarginM;
|
||||
public float Inertia, CapacityFactor, MinSlopeM;
|
||||
public float ErodeRate, DepositRate, Evaporation, Gravity;
|
||||
public float CraterExclFactor;
|
||||
public float CraterCoreFactor, CraterFeatherFactor;
|
||||
public byte CraterMode; // 0 = full, 1 = feather
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
|
|
@ -515,7 +516,8 @@ namespace IslaApocalypse.Core
|
|||
e.MinSlopeM = reader.ReadSingle();
|
||||
e.ErodeRate = reader.ReadSingle(); e.DepositRate = reader.ReadSingle();
|
||||
e.Evaporation = reader.ReadSingle(); e.Gravity = reader.ReadSingle();
|
||||
e.CraterExclFactor = reader.ReadSingle();
|
||||
e.CraterCoreFactor = reader.ReadSingle(); e.CraterFeatherFactor = reader.ReadSingle();
|
||||
e.CraterMode = reader.ReadByte();
|
||||
blueprint.Erosion = e;
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue