Add MrGameEng.WorldGen: deterministic procedural world generation
CI / build-test (push) Successful in 1m14s
CI / build-test (push) Successful in 1m14s
Introduce a WorldGen module under Simulation with reusable helpers for seed-based terrain heightmaps: PerlinNoise (deterministic 2D gradient noise), FractalNoise (fBm over octaves), Falloff (island edge masks), Heightmap (row-major grid with min-max normalize) and HeightmapGenerator (settings to normalized heightmap). All deterministic from an integer seed, allocation-free per sample, covered by xUnit tests. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ef1111bcb6
commit
53659450a6
@@ -0,0 +1,124 @@
|
||||
using MrGameEng.WorldGen;
|
||||
using Xunit;
|
||||
|
||||
namespace MrGameEng.WorldGen.Tests;
|
||||
|
||||
public class WorldGenTests
|
||||
{
|
||||
private static HeightmapSettings Settings(int seed) =>
|
||||
HeightmapSettings.Default with
|
||||
{
|
||||
Seed = seed,
|
||||
};
|
||||
|
||||
[Fact]
|
||||
public void Generate_SameSeed_ProducesIdenticalMap()
|
||||
{
|
||||
var a = new HeightmapGenerator(Settings(1234)).Generate(64, 48);
|
||||
var b = new HeightmapGenerator(Settings(1234)).Generate(64, 48);
|
||||
|
||||
Assert.Equal(a.Values, b.Values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_DifferentSeeds_ProduceDifferentMaps()
|
||||
{
|
||||
var a = new HeightmapGenerator(Settings(1)).Generate(64, 48);
|
||||
var b = new HeightmapGenerator(Settings(2)).Generate(64, 48);
|
||||
|
||||
Assert.NotEqual(a.Values, b.Values);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_AfterNormalize_ValuesSpanUnitRange()
|
||||
{
|
||||
var map = new HeightmapGenerator(Settings(7)).Generate(80, 60);
|
||||
|
||||
var min = float.MaxValue;
|
||||
var max = float.MinValue;
|
||||
foreach (var v in map.Values)
|
||||
{
|
||||
Assert.InRange(v, 0f, 1f);
|
||||
min = MathF.Min(min, v);
|
||||
max = MathF.Max(max, v);
|
||||
}
|
||||
|
||||
Assert.Equal(0f, min, 3);
|
||||
Assert.Equal(1f, max, 3);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Generate_WithIsland_EdgesAreLowerThanCentre()
|
||||
{
|
||||
var settings = HeightmapSettings.Default with { Seed = 42, IslandStrength = 1f };
|
||||
var map = new HeightmapGenerator(settings).Generate(64, 64);
|
||||
|
||||
// Average the border ring vs. a centred block.
|
||||
var edge = 0f;
|
||||
var edgeCount = 0;
|
||||
for (var x = 0; x < map.Width; x++)
|
||||
{
|
||||
edge += map[x, 0] + map[x, map.Height - 1];
|
||||
edgeCount += 2;
|
||||
}
|
||||
|
||||
var centre = 0f;
|
||||
var centreCount = 0;
|
||||
for (var y = map.Height / 2 - 4; y < map.Height / 2 + 4; y++)
|
||||
{
|
||||
for (var x = map.Width / 2 - 4; x < map.Width / 2 + 4; x++)
|
||||
{
|
||||
centre += map[x, y];
|
||||
centreCount++;
|
||||
}
|
||||
}
|
||||
|
||||
Assert.True(
|
||||
edge / edgeCount < centre / centreCount,
|
||||
"island edges should sit below centre"
|
||||
);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PerlinSample_StaysWithinUnitRange()
|
||||
{
|
||||
var noise = new PerlinNoise(99);
|
||||
for (var i = 0; i < 2000; i++)
|
||||
{
|
||||
var v = noise.Sample(i * 0.37f, i * 0.19f);
|
||||
Assert.InRange(v, -1f, 1f);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PerlinSample_IsContinuousBetweenNeighbours()
|
||||
{
|
||||
var noise = new PerlinNoise(5);
|
||||
var previous = noise.Sample(0f, 0f);
|
||||
for (var i = 1; i < 500; i++)
|
||||
{
|
||||
var current = noise.Sample(i * 0.01f, 0f);
|
||||
Assert.True(
|
||||
MathF.Abs(current - previous) < 0.2f,
|
||||
"noise jumped between adjacent samples"
|
||||
);
|
||||
previous = current;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PerlinSample_AtIntegerLattice_IsZero()
|
||||
{
|
||||
var noise = new PerlinNoise(3);
|
||||
Assert.Equal(0f, noise.Sample(4f, 7f), 5);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IslandFalloff_CentreIsHigherThanEdge()
|
||||
{
|
||||
Assert.True(Falloff.Island(0f, 0f) > Falloff.Island(1f, 0f));
|
||||
Assert.True(Falloff.Island(0f, 0f) > Falloff.Island(0f, 1f));
|
||||
Assert.Equal(1f, Falloff.Island(0f, 0f), 5);
|
||||
Assert.Equal(0f, Falloff.Island(1f, 1f), 5);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user