using Godot;
namespace IslaApocalypse.Tools
{
///
/// Raw height-field I/O: little-endian float32, x-major (index = x * MapSize + y).
///
/// ⭐ THIS FILE IS WHY PRESENTATION AND GENERATION STAY SEPARATE. A relief render reads a dumped
/// height field and re-colours it; it never re-runs the generator. So a look change provably
/// cannot move the terrain — not by convention, but because the colours are computed from a
/// file the renderer cannot write.
///
/// It also makes a byte-level port-fidelity oracle possible later: two generators agree, or
/// their dumps differ. → `Design - Tooling - Iteration and Batching.md`, "build the oracle
/// before the taste-iteration".
///
public static class HeightField
{
public static void Save(float[,] height, int mapSize, string absolutePath)
{
using var f = Godot.FileAccess.Open(absolutePath, Godot.FileAccess.ModeFlags.Write);
if (f == null)
{
GD.PrintErr($"[HeightField] cannot write {absolutePath}: {Godot.FileAccess.GetOpenError()}");
return;
}
for (int x = 0; x < mapSize; x++)
for (int y = 0; y < mapSize; y++)
f.StoreFloat(height[x, y]);
}
/// Load a dump. Returns null if absent or the wrong size for .
public static float[,] Load(string absolutePath, int mapSize)
{
if (!Godot.FileAccess.FileExists(absolutePath)) return null;
using var f = Godot.FileAccess.Open(absolutePath, Godot.FileAccess.ModeFlags.Read);
if (f == null) return null;
long expected = (long)mapSize * mapSize * 4;
if ((long)f.GetLength() != expected)
{
GD.PrintErr($"[HeightField] {absolutePath} is {f.GetLength()} bytes, expected {expected} " +
$"for MapSize {mapSize} — ignoring it rather than guessing.");
return null;
}
var h = new float[mapSize, mapSize];
for (int x = 0; x < mapSize; x++)
for (int y = 0; y < mapSize; y++)
h[x, y] = f.GetFloat();
return h;
}
}
}