feat: shared water predicates + water-bodies stage + priority-flood diagnostics (terrain-water task 03)
IsWaterPixel/IsOceanPixel/IsLakePixel are the single per-pixel water truth; AssignBiomesAndDraw's water branch now calls them (verbatim rules — behavior-preserving, proven by the bit-identical-biomes oracle at acceptance). New IdentifyWaterBodies stage between the masks and biomes: ocean = body 1, lakes labeled 2..N with CalculateTrueOcean's 4-connectivity in deterministic scan order; one transitional surface level per body (GetSeaLevel at the body centroid; ocean at map centre). 0_water snapshot painted from the stage's own outputs. Priority-flood pit-fill (heap + pit-queue variant) runs as validated diagnostics only — serializes nothing, asserts filled>=original and full-map non-ascending drainage (reverse BFS, no sampling), reports closed-basin statistics. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
81f7030140
commit
d296ef30c4
2 changed files with 333 additions and 3 deletions
|
|
@ -36,6 +36,28 @@ namespace IslaApocalypse.Core
|
||||||
public string GeneratorGitHash = ""; // short hash of the generator repo, "" if unknown
|
public string GeneratorGitHash = ""; // short hash of the generator repo, "" if unknown
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// One water body from the blueprint's WBTB section: the ocean (exactly one, id 1)
|
||||||
|
/// or a lake (ids 2..N). SurfaceLevel is the documented TRANSITIONAL rule — one flat
|
||||||
|
/// level per body, GetSeaLevel at the body's pixel centroid under the still-live
|
||||||
|
/// latitude field; superseded when the flat-scalar sea model lands. Salinity is a
|
||||||
|
/// provisional placeholder for the future fresh/salt mechanic (ocean salt, lake fresh).
|
||||||
|
/// </summary>
|
||||||
|
public class WaterBodyInfo
|
||||||
|
{
|
||||||
|
public const byte TYPE_OCEAN = 0;
|
||||||
|
public const byte TYPE_LAKE = 1;
|
||||||
|
public const byte SALINITY_FRESH = 0;
|
||||||
|
public const byte SALINITY_SALT = 1;
|
||||||
|
|
||||||
|
public ushort Id;
|
||||||
|
public byte Type;
|
||||||
|
public byte Salinity;
|
||||||
|
public float SurfaceLevel; // raw blueprint height units
|
||||||
|
public int PixelCount;
|
||||||
|
public Vector2 Centroid; // map pixels
|
||||||
|
}
|
||||||
|
|
||||||
public class WorldBlueprint
|
public class WorldBlueprint
|
||||||
{
|
{
|
||||||
public int MapSize;
|
public int MapSize;
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,15 @@ public partial class MapGenerator : TextureRect
|
||||||
private float[,] _tempMap;
|
private float[,] _tempMap;
|
||||||
private Biome[,] _biomeMap;
|
private Biome[,] _biomeMap;
|
||||||
private bool[,] _isTrueOcean;
|
private bool[,] _isTrueOcean;
|
||||||
private bool[,] _isMainland;
|
private bool[,] _isMainland;
|
||||||
internal List<TownData> _towns = new List<TownData>();
|
internal List<TownData> _towns = new List<TownData>();
|
||||||
|
|
||||||
|
// Water-bodies stage outputs (terrain-water task 03): 0 = no water, 1 = the
|
||||||
|
// ocean, 2..N = lakes. Filled by IdentifyWaterBodies, serialized by the v2
|
||||||
|
// writer, consumed by nothing at runtime yet.
|
||||||
|
internal ushort[,] _waterBodyIds;
|
||||||
|
internal List<WaterBodyInfo> _waterBodies;
|
||||||
|
|
||||||
internal List<Vector2[]> _highwayPaths = new List<Vector2[]>();
|
internal List<Vector2[]> _highwayPaths = new List<Vector2[]>();
|
||||||
internal List<Vector2[]> _branchPaths = new List<Vector2[]>();
|
internal List<Vector2[]> _branchPaths = new List<Vector2[]>();
|
||||||
internal List<Vector2[]> _ruggedPaths = new List<Vector2[]>();
|
internal List<Vector2[]> _ruggedPaths = new List<Vector2[]>();
|
||||||
|
|
@ -84,6 +90,10 @@ public partial class MapGenerator : TextureRect
|
||||||
CalculateMainland();
|
CalculateMainland();
|
||||||
GD.Print($"{T()} Ocean and mainland masks done.");
|
GD.Print($"{T()} Ocean and mainland masks done.");
|
||||||
|
|
||||||
|
IdentifyWaterBodies();
|
||||||
|
DrawWaterStageTexture();
|
||||||
|
await CaptureStage("0_water");
|
||||||
|
|
||||||
AssignBiomesAndDraw();
|
AssignBiomesAndDraw();
|
||||||
GD.Print($"{T()} Biomes done.");
|
GD.Print($"{T()} Biomes done.");
|
||||||
await CaptureStage("1_biomes");
|
await CaptureStage("1_biomes");
|
||||||
|
|
@ -441,6 +451,299 @@ public partial class MapGenerator : TextureRect
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =====================================================================
|
||||||
|
// SHARED WATER PREDICATES (terrain-water task 03)
|
||||||
|
// The single source of per-pixel water truth. AssignBiomesAndDraw and
|
||||||
|
// IdentifyWaterBodies both call these; correspondence between the biome
|
||||||
|
// grid and the water-body grid holds BY CONSTRUCTION, not by parallel
|
||||||
|
// implementations agreeing.
|
||||||
|
// =====================================================================
|
||||||
|
private bool IsWaterPixel(int x, int y) => _heightMap[x, y] < GetSeaLevel(_tempMap[x, y]);
|
||||||
|
private bool IsOceanPixel(int x, int y) => IsWaterPixel(x, y) && _isTrueOcean[x, y];
|
||||||
|
private bool IsLakePixel(int x, int y) => IsWaterPixel(x, y) && !_isTrueOcean[x, y];
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The water-bodies stage (terrain-water task 03). Promotes the world's EXISTING
|
||||||
|
/// water into explicit data: body 1 = the ocean (all IsOceanPixel pixels), bodies
|
||||||
|
/// 2..N = lakes, connected-component labeled with the SAME 4-connectivity as
|
||||||
|
/// CalculateTrueOcean's flood fill (Up/Down/Left/Right), in deterministic scan
|
||||||
|
/// order (X outer, Y inner; a body's id is fixed by its first-encountered pixel).
|
||||||
|
/// Membership comes only from the shared predicates — this stage groups pixels,
|
||||||
|
/// it never adds or removes any.
|
||||||
|
///
|
||||||
|
/// Each body carries ONE surface level: GetSeaLevel at the body's pixel centroid
|
||||||
|
/// (ocean: at the map centre). This is the documented TRANSITIONAL rule — see
|
||||||
|
/// BLUEPRINT_FORMAT.md (WBTB) — superseded when the flat-scalar sea model lands.
|
||||||
|
/// </summary>
|
||||||
|
private void IdentifyWaterBodies()
|
||||||
|
{
|
||||||
|
ulong t0 = Time.GetTicksMsec();
|
||||||
|
_waterBodyIds = new ushort[MapSize, MapSize];
|
||||||
|
_waterBodies = new List<WaterBodyInfo>();
|
||||||
|
|
||||||
|
// --- Body 1: the ocean, one body, first-class ---
|
||||||
|
long oceanCount = 0;
|
||||||
|
double oceanCx = 0, oceanCy = 0;
|
||||||
|
for (int x = 0; x < MapSize; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < MapSize; y++)
|
||||||
|
{
|
||||||
|
if (IsOceanPixel(x, y))
|
||||||
|
{
|
||||||
|
_waterBodyIds[x, y] = 1;
|
||||||
|
oceanCount++;
|
||||||
|
oceanCx += x; oceanCy += y;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Vector2 oceanCentroid = oceanCount > 0
|
||||||
|
? new Vector2((float)(oceanCx / oceanCount), (float)(oceanCy / oceanCount))
|
||||||
|
: Vector2.Zero;
|
||||||
|
_waterBodies.Add(new WaterBodyInfo
|
||||||
|
{
|
||||||
|
Id = 1,
|
||||||
|
Type = WaterBodyInfo.TYPE_OCEAN,
|
||||||
|
Salinity = WaterBodyInfo.SALINITY_SALT, // provisional default
|
||||||
|
SurfaceLevel = GetSeaLevel(_tempMap[MapSize / 2, MapSize / 2]), // ocean: map centre
|
||||||
|
PixelCount = (int)oceanCount,
|
||||||
|
Centroid = oceanCentroid
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Bodies 2..N: lakes, 4-connected like CalculateTrueOcean ---
|
||||||
|
Vector2I[] directions = { Vector2I.Up, Vector2I.Down, Vector2I.Left, Vector2I.Right };
|
||||||
|
Queue<Vector2I> queue = new Queue<Vector2I>();
|
||||||
|
int nextId = 2;
|
||||||
|
long lakePixels = 0;
|
||||||
|
|
||||||
|
for (int x = 0; x < MapSize; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < MapSize; y++)
|
||||||
|
{
|
||||||
|
if (_waterBodyIds[x, y] != 0 || !IsLakePixel(x, y)) continue;
|
||||||
|
|
||||||
|
if (nextId > ushort.MaxValue)
|
||||||
|
{
|
||||||
|
GD.PrintErr($"[WaterBodies] ⚠⚠ More than {ushort.MaxValue - 1} water bodies — u16 id space exhausted. Remaining lakes left unlabeled.");
|
||||||
|
x = MapSize; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ushort id = (ushort)nextId++;
|
||||||
|
long count = 0;
|
||||||
|
double cx = 0, cy = 0;
|
||||||
|
|
||||||
|
_waterBodyIds[x, y] = id;
|
||||||
|
queue.Enqueue(new Vector2I(x, y));
|
||||||
|
while (queue.Count > 0)
|
||||||
|
{
|
||||||
|
Vector2I current = queue.Dequeue();
|
||||||
|
count++; cx += current.X; cy += current.Y;
|
||||||
|
foreach (var dir in directions)
|
||||||
|
{
|
||||||
|
Vector2I nb = current + dir;
|
||||||
|
if (nb.X < 0 || nb.X >= MapSize || nb.Y < 0 || nb.Y >= MapSize) continue;
|
||||||
|
if (_waterBodyIds[nb.X, nb.Y] != 0 || !IsLakePixel(nb.X, nb.Y)) continue;
|
||||||
|
_waterBodyIds[nb.X, nb.Y] = id;
|
||||||
|
queue.Enqueue(nb);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lakePixels += count;
|
||||||
|
int centX = Mathf.Clamp((int)Mathf.Round((float)(cx / count)), 0, MapSize - 1);
|
||||||
|
int centY = Mathf.Clamp((int)Mathf.Round((float)(cy / count)), 0, MapSize - 1);
|
||||||
|
_waterBodies.Add(new WaterBodyInfo
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Type = WaterBodyInfo.TYPE_LAKE,
|
||||||
|
Salinity = WaterBodyInfo.SALINITY_FRESH, // provisional default
|
||||||
|
SurfaceLevel = GetSeaLevel(_tempMap[centX, centY]),
|
||||||
|
PixelCount = (int)count,
|
||||||
|
Centroid = new Vector2((float)(cx / count), (float)(cy / count))
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
double seconds = (Time.GetTicksMsec() - t0) / 1000.0;
|
||||||
|
GD.Print($"{T()} [WaterBodies] {_waterBodies.Count} bodies in {seconds:F1}s: ocean {oceanCount} px, {_waterBodies.Count - 1} lakes totalling {lakePixels} px.");
|
||||||
|
|
||||||
|
RunPriorityFloodDiagnostics();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Priority-flood pit-filling over the post-topography heightmap — VALIDATED
|
||||||
|
/// DIAGNOSTICS ONLY. Serializes nothing (deliberately no BSIN section: basin data
|
||||||
|
/// goes stale the moment coast smoothing changes terrain; the rivers stage
|
||||||
|
/// recomputes fresh — see BLUEPRINT_FORMAT.md). Reports closed-basin statistics
|
||||||
|
/// and asserts two invariants: (a) filled ≥ original everywhere; (b) on the filled
|
||||||
|
/// surface every pixel has a non-ascending 8-neighbour path to the map border
|
||||||
|
/// (checked in full via a reverse BFS, not a sample).
|
||||||
|
/// </summary>
|
||||||
|
private void RunPriorityFloodDiagnostics()
|
||||||
|
{
|
||||||
|
ulong t0 = Time.GetTicksMsec();
|
||||||
|
int n = MapSize;
|
||||||
|
int total = n * n;
|
||||||
|
|
||||||
|
// 1-D row-major copies (idx = x * n + y) for speed.
|
||||||
|
float[] original = new float[total];
|
||||||
|
for (int x = 0; x < n; x++)
|
||||||
|
for (int y = 0; y < n; y++)
|
||||||
|
original[x * n + y] = _heightMap[x, y];
|
||||||
|
float[] filled = (float[])original.Clone();
|
||||||
|
|
||||||
|
// --- Priority-flood (Barnes et al. variant: heap + plain pit queue) ---
|
||||||
|
bool[] visited = new bool[total];
|
||||||
|
var heap = new PriorityQueue<int, float>();
|
||||||
|
var pit = new Queue<int>();
|
||||||
|
|
||||||
|
void Seed(int idx) { if (!visited[idx]) { visited[idx] = true; heap.Enqueue(idx, filled[idx]); } }
|
||||||
|
for (int x = 0; x < n; x++) { Seed(x * n); Seed(x * n + (n - 1)); }
|
||||||
|
for (int y = 0; y < n; y++) { Seed(y); Seed((n - 1) * n + y); }
|
||||||
|
|
||||||
|
while (heap.Count > 0 || pit.Count > 0)
|
||||||
|
{
|
||||||
|
int c = pit.Count > 0 ? pit.Dequeue() : heap.Dequeue();
|
||||||
|
float fc = filled[c];
|
||||||
|
int cx = c / n, cy = c % n;
|
||||||
|
for (int dx = -1; dx <= 1; dx++)
|
||||||
|
{
|
||||||
|
for (int dy = -1; dy <= 1; dy++)
|
||||||
|
{
|
||||||
|
if (dx == 0 && dy == 0) continue;
|
||||||
|
int nx = cx + dx, ny = cy + dy;
|
||||||
|
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||||
|
int ni = nx * n + ny;
|
||||||
|
if (visited[ni]) continue;
|
||||||
|
visited[ni] = true;
|
||||||
|
if (filled[ni] <= fc) { filled[ni] = fc; pit.Enqueue(ni); }
|
||||||
|
else heap.Enqueue(ni, filled[ni]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
double floodSeconds = (Time.GetTicksMsec() - t0) / 1000.0;
|
||||||
|
|
||||||
|
// --- Invariant (a): filled ≥ original everywhere ---
|
||||||
|
long invariantAViolations = 0;
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
if (filled[i] < original[i]) invariantAViolations++;
|
||||||
|
|
||||||
|
// --- Invariant (b): full reverse BFS from the border over non-descending
|
||||||
|
// edges; a pixel is reachable iff it has a non-ascending 8-neighbour path
|
||||||
|
// down to the border on the filled surface. ---
|
||||||
|
bool[] reachable = new bool[total];
|
||||||
|
var bfs = new Queue<int>();
|
||||||
|
void SeedB(int idx) { if (!reachable[idx]) { reachable[idx] = true; bfs.Enqueue(idx); } }
|
||||||
|
for (int x = 0; x < n; x++) { SeedB(x * n); SeedB(x * n + (n - 1)); }
|
||||||
|
for (int y = 0; y < n; y++) { SeedB(y); SeedB((n - 1) * n + y); }
|
||||||
|
while (bfs.Count > 0)
|
||||||
|
{
|
||||||
|
int c = bfs.Dequeue();
|
||||||
|
float fc = filled[c];
|
||||||
|
int cx = c / n, cy = c % n;
|
||||||
|
for (int dx = -1; dx <= 1; dx++)
|
||||||
|
{
|
||||||
|
for (int dy = -1; dy <= 1; dy++)
|
||||||
|
{
|
||||||
|
if (dx == 0 && dy == 0) continue;
|
||||||
|
int nx = cx + dx, ny = cy + dy;
|
||||||
|
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||||
|
int ni = nx * n + ny;
|
||||||
|
if (reachable[ni] || filled[ni] < fc) continue;
|
||||||
|
reachable[ni] = true;
|
||||||
|
bfs.Enqueue(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
long invariantBViolations = 0;
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
if (!reachable[i]) invariantBViolations++;
|
||||||
|
|
||||||
|
if (invariantAViolations > 0)
|
||||||
|
GD.PrintErr($"[PriorityFlood] ⚠⚠ INVARIANT (a) VIOLATED: {invariantAViolations} pixels have filled < original.");
|
||||||
|
if (invariantBViolations > 0)
|
||||||
|
GD.PrintErr($"[PriorityFlood] ⚠⚠ INVARIANT (b) VIOLATED: {invariantBViolations} pixels lack a non-ascending path to the border.");
|
||||||
|
|
||||||
|
// --- Closed-basin statistics on LAND (shared predicate), 8-connected ---
|
||||||
|
long landPixels = 0, basinLandPixels = 0;
|
||||||
|
var basinAreas = new List<long>();
|
||||||
|
var basinDepths = new List<float>();
|
||||||
|
bool[] counted = new bool[total];
|
||||||
|
var comp = new Queue<int>();
|
||||||
|
for (int x = 0; x < n; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < n; y++)
|
||||||
|
{
|
||||||
|
int i = x * n + y;
|
||||||
|
bool land = !IsWaterPixel(x, y);
|
||||||
|
if (land) landPixels++;
|
||||||
|
if (!land || counted[i] || filled[i] <= original[i]) continue;
|
||||||
|
|
||||||
|
long area = 0; float maxDepth = 0f;
|
||||||
|
counted[i] = true;
|
||||||
|
comp.Enqueue(i);
|
||||||
|
while (comp.Count > 0)
|
||||||
|
{
|
||||||
|
int c = comp.Dequeue();
|
||||||
|
area++;
|
||||||
|
float d = filled[c] - original[c];
|
||||||
|
if (d > maxDepth) maxDepth = d;
|
||||||
|
int cx2 = c / n, cy2 = c % n;
|
||||||
|
for (int dx = -1; dx <= 1; dx++)
|
||||||
|
{
|
||||||
|
for (int dy = -1; dy <= 1; dy++)
|
||||||
|
{
|
||||||
|
if (dx == 0 && dy == 0) continue;
|
||||||
|
int nx = cx2 + dx, ny = cy2 + dy;
|
||||||
|
if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;
|
||||||
|
int ni = nx * n + ny;
|
||||||
|
if (counted[ni] || filled[ni] <= original[ni] || IsWaterPixel(nx, ny)) continue;
|
||||||
|
counted[ni] = true;
|
||||||
|
comp.Enqueue(ni);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
basinLandPixels += area;
|
||||||
|
basinAreas.Add(area);
|
||||||
|
basinDepths.Add(maxDepth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
basinAreas.Sort();
|
||||||
|
basinDepths.Sort();
|
||||||
|
long P(List<long> s, double q) => s.Count == 0 ? 0 : s[Mathf.Clamp((int)(q * s.Count), 0, s.Count - 1)];
|
||||||
|
float Pf(List<float> s, double q) => s.Count == 0 ? 0 : s[Mathf.Clamp((int)(q * s.Count), 0, s.Count - 1)];
|
||||||
|
|
||||||
|
double totalSeconds = (Time.GetTicksMsec() - t0) / 1000.0;
|
||||||
|
GD.Print($"{T()} [PriorityFlood] flood {floodSeconds:F1}s, total (with invariants+stats) {totalSeconds:F1}s.");
|
||||||
|
GD.Print($"{T()} [PriorityFlood] invariants: (a) {(invariantAViolations == 0 ? "PASS" : "FAIL")}, (b) {(invariantBViolations == 0 ? "PASS" : "FAIL")} (full check, no sampling).");
|
||||||
|
GD.Print($"{T()} [PriorityFlood] closed basins on land: {basinAreas.Count}; land px in basins {basinLandPixels}/{landPixels} ({(landPixels > 0 ? 100.0 * basinLandPixels / landPixels : 0):F1}%).");
|
||||||
|
GD.Print($"{T()} [PriorityFlood] area px: p50 {P(basinAreas, 0.5)}, p90 {P(basinAreas, 0.9)}, max {(basinAreas.Count > 0 ? basinAreas[basinAreas.Count - 1] : 0)}; " +
|
||||||
|
$"count ≥100px {basinAreas.FindAll(a => a >= 100).Count}, ≥1000px {basinAreas.FindAll(a => a >= 1000).Count}, ≥10000px {basinAreas.FindAll(a => a >= 10000).Count}.");
|
||||||
|
GD.Print($"{T()} [PriorityFlood] max depth (raw h): p50 {Pf(basinDepths, 0.5):F4}, p90 {Pf(basinDepths, 0.9):F4}, max {(basinDepths.Count > 0 ? basinDepths[basinDepths.Count - 1] : 0):F4}; " +
|
||||||
|
$"deeper than 0.01: {basinDepths.FindAll(d => d > 0.01f).Count}, deeper than 0.05: {basinDepths.FindAll(d => d > 0.05f).Count}.");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Paints the water-stage snapshot from the stage's own outputs (the biome grid
|
||||||
|
/// does not exist yet at this point in the pipeline): ocean deep blue, lakes a
|
||||||
|
/// distinct lighter blue, land neutral grey.
|
||||||
|
/// </summary>
|
||||||
|
private void DrawWaterStageTexture()
|
||||||
|
{
|
||||||
|
Image img = Image.CreateEmpty(MapSize, MapSize, false, Image.Format.Rgba8);
|
||||||
|
Color land = new Color(0.45f, 0.45f, 0.42f);
|
||||||
|
Color ocean = new Color(0.05f, 0.2f, 0.45f);
|
||||||
|
Color lake = new Color(0.35f, 0.7f, 0.9f);
|
||||||
|
for (int x = 0; x < MapSize; x++)
|
||||||
|
{
|
||||||
|
for (int y = 0; y < MapSize; y++)
|
||||||
|
{
|
||||||
|
ushort id = _waterBodyIds[x, y];
|
||||||
|
img.SetPixel(x, y, id == 0 ? land : (id == 1 ? ocean : lake));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Texture = ImageTexture.CreateFromImage(img);
|
||||||
|
}
|
||||||
|
|
||||||
private void AssignBiomesAndDraw()
|
private void AssignBiomesAndDraw()
|
||||||
{
|
{
|
||||||
Image mapImage = Image.CreateEmpty(MapSize, MapSize, false, Image.Format.Rgba8);
|
Image mapImage = Image.CreateEmpty(MapSize, MapSize, false, Image.Format.Rgba8);
|
||||||
|
|
@ -451,10 +754,15 @@ public partial class MapGenerator : TextureRect
|
||||||
float h = _heightMap[x, y];
|
float h = _heightMap[x, y];
|
||||||
float t = _tempMap[x, y];
|
float t = _tempMap[x, y];
|
||||||
Biome b;
|
Biome b;
|
||||||
|
|
||||||
float currentSeaLevel = GetSeaLevel(t);
|
float currentSeaLevel = GetSeaLevel(t);
|
||||||
|
|
||||||
if (h < currentSeaLevel) b = _isTrueOcean[x, y] ? Biome.Ocean : Biome.Lake;
|
// Water classification comes from the SHARED predicates (task 03), so the
|
||||||
|
// water-bodies stage and the biome classifier cannot disagree. Same rules
|
||||||
|
// as before, verbatim: below local sea level -> Ocean if true-ocean
|
||||||
|
// connected, else Lake.
|
||||||
|
if (IsOceanPixel(x, y)) b = Biome.Ocean;
|
||||||
|
else if (IsLakePixel(x, y)) b = Biome.Lake;
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
float baseDist = new Vector2(x, y).DistanceTo(_impactCenter);
|
float baseDist = new Vector2(x, y).DistanceTo(_impactCenter);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue