feat: droplet hydraulic erosion — carve+deposit, three governors, sea clamp (terrain-water task 17, Phase C0)
Erosion: "off"|"v1" gate (default off, pending the developer's gate). Output-only: applied to the render map after detail, before the crater carve; the classify path reads pre-erosion heights, so biomes/water stay byte-identical (verified: OFF run bit-identical to the task-14 reference; ON run BIOM/WBID identical to OFF). Governors: droplet count / lifetime / per-cell carve cap (ledger-enforced, thrown on violation). Sea clamp: below-sea cells read-only, land never carved below sea+margin — rendered coastline provably fixed (asserted per generation). Crater excluded at 1.2×R. Deterministic from resolvedSeed+9271 (PCG32). New EROS blueprint section (writer/parser/harness/format doc). Supersedes the reverted D8 approach with the organic droplet model. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
34b0e1c9e3
commit
e1e1fe3227
8 changed files with 611 additions and 22 deletions
|
|
@ -57,6 +57,7 @@ Every section: `[u32 tag][u64 payload-length in bytes][payload]`.
|
|||
| `TOWN` / `0x4E574F54` | `i32 count`, then per town: `f32 X` · `f32 Y` · `u8 tier` · `u8 isHighwayNode` (0/1) | Tier ordinals from `Enums.cs::TownTier`, append-only, range-checked on read. The highway-node flag is what the generator's road topology was built from (v1 dropped it); carried and exposed on the parsed blueprint, consumed by nothing server-side yet. |
|
||||
| `TCRV` / `0x56524354` | 50 B base: `u16 curveVersion` · `f32 knot1..knot4` · `f32 spikeMax` · `f32 sea` · `f32 orangeCeil` · `f32 redCeil` · `f32 benchLo` · `f32 benchHi` · `f32 peakCap` · `f32 tailSlope`. **When `curveVersion ≥ 4`, a 36 B modulation extension follows:** `f32 benchAmp` · `f32 plateauAmp` · `f32 shelfSpanMin` · `f32 shelfSpanMax` · `f32 elevFreqIslands` · `f32 strengthFreqIslands` · `i32 benchSeedOffset` · `i32 plateauSeedOffset` · `i32 strengthSeedOffset`. **When `curveVersion ≥ 5`, a 9 B preset extension follows:** `u8 presetId` (1 = compact, 2 = balanced) · `f32 k5` · `f32 k6` — with the four base knot slots this makes the effective curve unambiguous from the record alone (record total 95 B; the length-framed section + version byte keep every layout change safe) | Optional — present iff the height-redistribution curve shaped this blueprint's `HGTS` (config `TerrainCurve: "v3"`); absent = raw legacy profile. **The `u16 curveVersion` selects the field semantics and the unserialized anchor set:** v1 — knots t1..t4, spikeMax = pooled calibration max, benchLo/Hi = the 50 m plateau's lo/hi. v2 — as v1 but spikeMax = the seed's effective raw pre-curve maximum (per-seed spike normalizer). v3 — knots = K1..K4 of six (K5 = 0.930304, K6 = 1.050720 are version constants, not serialized), spikeMax per-seed, benchLo/benchHi = the fixed 100 m / 220 m shelves. **v4 (current)** — as v3, but benchLo/benchHi are the shelf **BASE** anchors and the extension record carries the spatial-modulation parameters: shelf elevations vary ±benchAmp/±plateauAmp and shelf strength blends the output span across `[shelfSpanMin, shelfSpanMax]`, via Simplex fields seeded `resolvedWorldSeed + seedOffset` at `freq/MapSize` (frequencies stated in undulations per island width). Because spikeMax is per-seed (v2+), blueprints are not reproducible from curve constants alone — that is why it is recorded. **Metadata only:** exported heights are already curved; nothing re-applies the map. Calibration provenance: `HeightCurve.cs` header + the task-05/06/07 reports. |
|
||||
| `TDTL` / `0x4C544454` | 30 B: `u16 detailVersion` · `f32 reliefAmpM` · `f32 reliefFreqIslands` · `i32 reliefSeedOffset` · `f32 edgeAmpM` · `f32 edgeFreqIslands` · `i32 edgeSeedOffset` · `f32 edgeMaxShiftM` | Optional — present iff the terrain detail passes shaped this blueprint's `HGTS` (config `TerrainDetail: "v1"`, requires the curve). **`detailVersion` selects the body layout and a reader that does not recognise it SKIPS the section** (leaving detail metadata null) rather than misreading a differently shaped payload — the one section whose body is versioned rather than extended, because v1's layout was retired rather than grown. **v1 (retired, never shipped)** — shelf micro-relief + D8 drainage incision, 38 B; the incision produced grid-aligned artifacts and was reverted whole, so the only v1 payloads that exist are in that batch's own tree. **v2 (current)** — shelf micro-relief (±`reliefAmpM` output metres, shelf-ness weighted) + shelf-edge variation: a per-column shift of the curve's shelf/riser knot block K3/K4/K5, drawn from a Simplex field seeded `resolvedWorldSeed + edgeSeedOffset` at `edgeFreqIslands/MapSize`, amplitude ±`edgeAmpM` **metres of INPUT height** — a displacement of the shelf boundary contour, not an elevation change. `edgeAmpM` is recorded **as applied**, after the clamp to `edgeMaxShiftM` (the preset's band-squeeze bound), so the record always describes the terrain rather than the request. **Metadata only** — heights are already detailed. Machinery: `Tools/Scripts/TerrainDetailPass.cs`. |
|
||||
| `EROS` / `0x534F5245` | 58 B: `u16 erosionVersion` · `i32 dropletCount` · `i32 lifetime` · `i32 brushRadius` · `i32 seedOffset` · `f32 carveCapM` · `f32 seaMarginM` · `f32 inertia` · `f32 capacityFactor` · `f32 minSlopeM` · `f32 erodeRate` · `f32 depositRate` · `f32 evaporation` · `f32 gravity` · `f32 craterExclFactor` | Optional — present iff the droplet hydraulic-erosion pass shaped this blueprint's `HGTS` (config `Erosion: "v1"`, terrain-water task 17). **`erosionVersion` selects the body layout; an unrecognised version is SKIPPED whole** (erosion metadata left null), same rule as `TDTL`. **v1 (current)** — the carve-and-deposit droplet model: `dropletCount`/`lifetime`/`carveCapM` are the three hard governors (total droplets, max steps each, max cumulative erosion depth per cell in metres); `seaMarginM` is the flood-guard clamp (no cell carved below sea + margin; below-sea cells untouched entirely, so the rendered coastline cannot move); droplets are deterministic from `resolvedWorldSeed + seedOffset` (PCG32); no cell within `craterExclFactor × CraterRadius` of the impact centre is modified. Strength constants (`inertia`, `capacityFactor`, `minSlopeM`, `erodeRate`, `depositRate`, `evaporation`, `gravity`) are the standard droplet-model dials; slopes/amounts in metres (1 raw = 251 m). All values recorded **as applied** (post config clamping). **Metadata only** — heights are already eroded, and the classify-side sections (`BIOM`/`WBID`/…) never saw the pass by design. Machinery: `Tools/Scripts/HydraulicErosion.cs`. |
|
||||
| `WBID` / `0x44494257` | `MapSize²` × `u16` water-body id, same pixel order as `HGTS` | Optional (absent = no water data, e.g. a legacy re-encode). `0` = no water, `1` = **the** ocean body, `2..N` = lakes. Ids assigned in deterministic scan order (X outer / Y inner, first-encountered pixel), lakes labeled with the **same 4-connectivity as `CalculateTrueOcean`**. Membership is exactly the generator's water classification — the biome grid's Ocean/Lake pixels and this grid's nonzero pixels are the same set **by construction** (shared predicates). Length must equal `2·MapSize²`. |
|
||||
| `WBTB` / `0x42544257` | `i32 count`, then per body (20 B): `u16 id` · `u8 type` (0 ocean, 1 lake) · `u8 salinity` (0 fresh, 1 salt) · `f32 surfaceLevel` · `i32 pixelCount` · `f32 centroidX` · `f32 centroidY` | Optional, paired with `WBID`. **`surfaceLevel` is a documented TRANSITIONAL rule:** one flat level per body — `GetSeaLevel` at the body's pixel centroid (ocean: at the map centre) under the still-live latitude field; superseded by the flat-scalar sea model (minted, lands with the coast change set). The field's per-pixel slope is deliberately NOT baked into any section. **Salinity is a provisional default** (ocean salt, lake fresh) — a placeholder for the future fresh/salt irrigation mechanic, not a mechanic. |
|
||||
| `WSRF` / `0x46525357` | `MapSize²` × `u16` quantized water-surface elevation, same pixel order | Optional, paired with `WBID`. `0` is the reserved **no-water sentinel**; a real level `L` (raw height units) encodes as `1 + round(L × 32768)` so it can never encode to 0; decode `(q − 1)/32768` (`BlueprintFormat.EncodeWaterLevel`/`DecodeWaterLevel`). Covers `[0 … ~1.99997]` raw at `1/32768` raw ≈ **7.7 mm** of world height (1 raw = 251 m) — far finer than the 1 m voxel. Nonzero exactly where `WBID` is nonzero; the value is the pixel's body level. |
|
||||
|
|
@ -121,6 +122,15 @@ body).
|
|||
construction (it classifies the retained uncurved heights); town positions and everything 3D
|
||||
follow the curved terrain. When on, `TCRV` records the effective parameters including the
|
||||
per-seed `spikeMax`.
|
||||
- **`Erosion`** (`"v1"` | `"off"`, default `"off"` — opt-in until the developer's gate approves
|
||||
it) — the task-17 droplet hydraulic-erosion pass: carve-and-deposit drainage detailing of
|
||||
`HGTS` after the detail passes and before the crater carve. Governor dials
|
||||
(`ErosionDropletCount` / `ErosionDropletLifetime` / `ErosionCarveCap`, defaults 400 000 / 48 /
|
||||
8 m; bounds clamped loudly at load) plus the sea-clamp margin and strength constants — the
|
||||
full dial list and semantics live in the `EROS` row above and `ConfigManager.cs`. Output-height
|
||||
only: the classify path reads pre-erosion heights, so `BIOM` and every water section stay
|
||||
bit-identical with erosion on or off, and the sea clamp keeps even the RENDERED coastline
|
||||
fixed. When on, `EROS` records the parameters as applied.
|
||||
|
||||
- **`CoastProfile`** (`"wide"` | `"steep"`, default `"wide"`) — the submarine shelf. The height
|
||||
curve is identity at and below sea level, so it never reshaped the seabed; `"wide"` compresses
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ namespace IslaApocalypse.Core
|
|||
public const uint TAG_WATER_SURFACE = 0x46525357; // "WSRF"
|
||||
public const uint TAG_TERRAIN_CURVE = 0x56524354; // "TCRV"
|
||||
public const uint TAG_TERRAIN_DETAIL = 0x4C544454; // "TDTL"
|
||||
public const uint TAG_EROSION = 0x534F5245; // "EROS"
|
||||
|
||||
// TDTL body version. 1 = shelf micro-relief + D8 drainage incision (the
|
||||
// task-10 draft; the incision was reverted, and the only v1 payloads in
|
||||
|
|
@ -42,6 +43,12 @@ namespace IslaApocalypse.Core
|
|||
// differently shaped payload into plausible-looking nonsense.
|
||||
public const ushort TDTL_VERSION = 2;
|
||||
|
||||
// EROS body version (terrain-water task 17). 1 = the droplet hydraulic-
|
||||
// erosion params block: governors (count/lifetime/carve cap), sea clamp,
|
||||
// brush, strength constants, RNG seed offset, crater exclusion factor.
|
||||
// Same reader rule as TDTL: an unknown body version is skipped whole.
|
||||
public const ushort EROS_VERSION = 1;
|
||||
|
||||
// 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
|
||||
// level can never encode to 0. Decodes back via (q − 1) / 32768. Covers
|
||||
|
|
|
|||
|
|
@ -35,6 +35,8 @@ namespace IslaApocalypse.Core
|
|||
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));
|
||||
if (bp.Erosion != null)
|
||||
WriteSection(writer, BlueprintFormat.TAG_EROSION, w => WriteErosion(w, bp.Erosion));
|
||||
WriteSection(writer, BlueprintFormat.TAG_HEIGHTS, w => WriteHeights(w, bp));
|
||||
WriteSection(writer, BlueprintFormat.TAG_BIOMES, w => WriteBiomes(w, bp));
|
||||
|
||||
|
|
@ -163,6 +165,19 @@ namespace IslaApocalypse.Core
|
|||
writer.Write(d.EdgeMaxShiftM); // f32
|
||||
}
|
||||
|
||||
private static void WriteErosion(BinaryWriter writer, ErosionInfo e)
|
||||
{
|
||||
writer.Write(e.Version); // u16
|
||||
writer.Write(e.DropletCount); writer.Write(e.Lifetime); // 2 × i32
|
||||
writer.Write(e.BrushRadius); writer.Write(e.SeedOffset); // 2 × i32
|
||||
writer.Write(e.CarveCapM); writer.Write(e.SeaMarginM); // 2 × f32
|
||||
writer.Write(e.Inertia); writer.Write(e.CapacityFactor); // 2 × f32
|
||||
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
|
||||
}
|
||||
|
||||
private static void WriteWaterBodyIds(BinaryWriter writer, WorldBlueprint bp)
|
||||
{
|
||||
int n = bp.MapSize;
|
||||
|
|
|
|||
|
|
@ -44,6 +44,31 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
public static float ShelfReliefAmp = 3.0f;
|
||||
public static float ShelfEdgeVariation = 12.0f;
|
||||
|
||||
// Hydraulic erosion (task 17, Phase C0): droplet-based carve-and-deposit on
|
||||
// the RENDER height map only — the classify path (biomes/water) never sees
|
||||
// it. "off" until the developer's gate approves it; the batch that turns it
|
||||
// on does so explicitly. The three GOVERNORS hard-bound the pass:
|
||||
// DropletCount (cost/detail), DropletLifetime (max steps per droplet),
|
||||
// CarveCap (max erosion depth per cell, metres — the runaway-trench guard
|
||||
// and what keeps erosion a detailing pass). ErosionSeaMargin is the flood
|
||||
// guard: no cell is ever carved below sea + margin, and below-sea cells are
|
||||
// never touched at all, so the rendered coastline cannot move. The remaining
|
||||
// dials are the standard droplet-model strength constants; slopes/amounts
|
||||
// are in METRES (1 raw height unit = 251 m).
|
||||
public static string Erosion = "off";
|
||||
public static int ErosionDropletCount = 400000;
|
||||
public static int ErosionDropletLifetime = 48;
|
||||
public static float ErosionCarveCap = 8.0f; // m per cell
|
||||
public static float ErosionSeaMargin = 0.5f; // m above sea, carve floor
|
||||
public static int ErosionBrushRadius = 2; // px
|
||||
public static float ErosionInertia = 0.05f;
|
||||
public static float ErosionCapacity = 4.0f;
|
||||
public static float ErosionMinSlope = 0.01f; // m per px, capacity floor
|
||||
public static float ErosionErodeRate = 0.3f;
|
||||
public static float ErosionDepositRate = 0.3f;
|
||||
public static float ErosionEvaporation = 0.02f;
|
||||
public static float ErosionGravity = 4.0f;
|
||||
|
||||
// Island falloff shaping (task 11).
|
||||
//
|
||||
// CoastProfile: "wide" adds the submarine shelf — the height curve is identity
|
||||
|
|
@ -178,6 +203,48 @@ namespace IslaApocalypse.Core // Change this if your namespace is different
|
|||
ShelfEdgeVariation = (float)data["ShelfEdgeVariation"];
|
||||
}
|
||||
|
||||
// Extract the erosion gate + dials (task 17)
|
||||
if (data.ContainsKey("Erosion"))
|
||||
{
|
||||
string erosion = (string)data["Erosion"];
|
||||
if (erosion == "off" || erosion == "v1")
|
||||
Erosion = erosion;
|
||||
else
|
||||
GD.PrintErr($"[ConfigManager] Unknown Erosion '{erosion}'. Keeping '{Erosion}'.");
|
||||
}
|
||||
if (data.ContainsKey("ErosionDropletCount")) ErosionDropletCount = (int)data["ErosionDropletCount"];
|
||||
if (data.ContainsKey("ErosionDropletLifetime")) ErosionDropletLifetime = (int)data["ErosionDropletLifetime"];
|
||||
if (data.ContainsKey("ErosionCarveCap")) ErosionCarveCap = (float)data["ErosionCarveCap"];
|
||||
if (data.ContainsKey("ErosionSeaMargin")) ErosionSeaMargin = (float)data["ErosionSeaMargin"];
|
||||
if (data.ContainsKey("ErosionBrushRadius")) ErosionBrushRadius = (int)data["ErosionBrushRadius"];
|
||||
if (data.ContainsKey("ErosionInertia")) ErosionInertia = (float)data["ErosionInertia"];
|
||||
if (data.ContainsKey("ErosionCapacity")) ErosionCapacity = (float)data["ErosionCapacity"];
|
||||
if (data.ContainsKey("ErosionMinSlope")) ErosionMinSlope = (float)data["ErosionMinSlope"];
|
||||
if (data.ContainsKey("ErosionErodeRate")) ErosionErodeRate = (float)data["ErosionErodeRate"];
|
||||
if (data.ContainsKey("ErosionDepositRate")) ErosionDepositRate = (float)data["ErosionDepositRate"];
|
||||
if (data.ContainsKey("ErosionEvaporation")) ErosionEvaporation = (float)data["ErosionEvaporation"];
|
||||
if (data.ContainsKey("ErosionGravity")) ErosionGravity = (float)data["ErosionGravity"];
|
||||
|
||||
// Governor bounds are enforced HERE, loudly, so a bad dial is a refused
|
||||
// dial rather than a silently absurd generation. The clamps are wide —
|
||||
// they exist to catch typos (an extra zero), not to tune.
|
||||
int rawCount = ErosionDropletCount; int rawLife = ErosionDropletLifetime;
|
||||
float rawCap = ErosionCarveCap;
|
||||
ErosionDropletCount = Mathf.Clamp(ErosionDropletCount, 0, 50_000_000);
|
||||
ErosionDropletLifetime = Mathf.Clamp(ErosionDropletLifetime, 1, 4096);
|
||||
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.");
|
||||
ErosionSeaMargin = Mathf.Clamp(ErosionSeaMargin, 0f, 5f);
|
||||
ErosionBrushRadius = Mathf.Clamp(ErosionBrushRadius, 0, 8);
|
||||
ErosionInertia = Mathf.Clamp(ErosionInertia, 0f, 0.99f);
|
||||
ErosionCapacity = Mathf.Max(ErosionCapacity, 0f);
|
||||
ErosionMinSlope = Mathf.Max(ErosionMinSlope, 0f);
|
||||
ErosionErodeRate = Mathf.Clamp(ErosionErodeRate, 0f, 1f);
|
||||
ErosionDepositRate = Mathf.Clamp(ErosionDepositRate, 0f, 1f);
|
||||
ErosionEvaporation = Mathf.Clamp(ErosionEvaporation, 0f, 0.5f);
|
||||
ErosionGravity = Mathf.Max(ErosionGravity, 0f);
|
||||
|
||||
// Extract the island-falloff dials (task 11)
|
||||
if (data.ContainsKey("CoastProfile"))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -104,6 +104,23 @@ namespace IslaApocalypse.Core
|
|||
public float EdgeMaxShiftM;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The hydraulic-erosion pass that detailed this blueprint's HGTS (v2 EROS
|
||||
/// section, terrain-water task 17): droplet-model governors and strength
|
||||
/// constants, as APPLIED (post config clamping). Null when erosion was off.
|
||||
/// Metadata only — heights are already eroded; the classify-side data (biomes,
|
||||
/// water) never saw the pass by design.
|
||||
/// </summary>
|
||||
public class ErosionInfo
|
||||
{
|
||||
public ushort Version;
|
||||
public int DropletCount, Lifetime, BrushRadius, SeedOffset;
|
||||
public float CarveCapM, SeaMarginM;
|
||||
public float Inertia, CapacityFactor, MinSlopeM;
|
||||
public float ErodeRate, DepositRate, Evaporation, Gravity;
|
||||
public float CraterExclFactor;
|
||||
}
|
||||
|
||||
public class WorldBlueprint
|
||||
{
|
||||
public int MapSize;
|
||||
|
|
@ -134,6 +151,9 @@ namespace IslaApocalypse.Core
|
|||
|
||||
// The detail passes that shaped HeightMap (TDTL section); null = no detail.
|
||||
public TerrainDetailInfo TerrainDetail;
|
||||
|
||||
// The erosion pass that detailed HeightMap (EROS section); null = no erosion.
|
||||
public ErosionInfo Erosion;
|
||||
}
|
||||
|
||||
// 2. The Parser Utility
|
||||
|
|
@ -258,6 +278,7 @@ namespace IslaApocalypse.Core
|
|||
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, payloadLength);
|
||||
else if (tag == BlueprintFormat.TAG_EROSION) sectionOk = ParseErosion(reader, blueprint, payloadLength);
|
||||
else
|
||||
{
|
||||
// The property the redesign exists to buy: future sections (water,
|
||||
|
|
@ -474,6 +495,30 @@ namespace IslaApocalypse.Core
|
|||
return true;
|
||||
}
|
||||
|
||||
private static bool ParseErosion(BinaryReader reader, WorldBlueprint blueprint, ulong payloadLength)
|
||||
{
|
||||
var e = new ErosionInfo();
|
||||
e.Version = reader.ReadUInt16();
|
||||
if (e.Version != BlueprintFormat.EROS_VERSION)
|
||||
{
|
||||
// Same rule as TDTL: an unrecognised body version is skipped whole
|
||||
// rather than misread into plausible-looking nonsense.
|
||||
GD.PrintErr($"[MapDataParser] ⚠ EROS version {e.Version} is not the current {BlueprintFormat.EROS_VERSION} — section skipped, erosion metadata unavailable.");
|
||||
reader.BaseStream.Seek((long)payloadLength - 2L, SeekOrigin.Current);
|
||||
return true;
|
||||
}
|
||||
e.DropletCount = reader.ReadInt32(); e.Lifetime = reader.ReadInt32();
|
||||
e.BrushRadius = reader.ReadInt32(); e.SeedOffset = reader.ReadInt32();
|
||||
e.CarveCapM = reader.ReadSingle(); e.SeaMarginM = reader.ReadSingle();
|
||||
e.Inertia = reader.ReadSingle(); e.CapacityFactor = reader.ReadSingle();
|
||||
e.MinSlopeM = reader.ReadSingle();
|
||||
e.ErodeRate = reader.ReadSingle(); e.DepositRate = reader.ReadSingle();
|
||||
e.Evaporation = reader.ReadSingle(); e.Gravity = reader.ReadSingle();
|
||||
e.CraterExclFactor = reader.ReadSingle();
|
||||
blueprint.Erosion = e;
|
||||
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