Files
Ransom-s-wake/tests/unit/run_site_baker_tests.gd
T

162 lines
7.2 KiB
GDScript

extends SceneTree
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const SiteTerrainBaker = preload("res://tools/site_terrain_baker.gd")
const VehicleTestRecipe = preload("res://tools/sites/vehicle_test_terrain_recipe.gd")
const Zone1Recipe = preload("res://tools/sites/zone1_vigilant_recipe.gd")
const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
const ZONE1_TERRAIN_PATH := "res://content/sites/zone1_vigilant_terrain.res"
const TEST_COUNT := 26
var _failures: Array[String] = []
func _init() -> void:
_test_plane_and_blob()
_test_materials_and_determinism()
_test_modifiers_and_primitive_materials()
_test_zone1_artefact_is_stable()
_test_canyon()
_test_escarpment()
if _failures.is_empty():
print("Site baker unit tests: %d passed, 0 failed." % TEST_COUNT)
quit(0)
else:
for failure: String in _failures:
push_error(failure)
print("Site baker unit tests: %d failed." % _failures.size())
quit(1)
func _small_recipe() -> SiteTerrainBaker.Recipe:
var recipe := SiteTerrainBaker.Recipe.new()
recipe.columns = 21
recipe.rows = 21
recipe.cell_size_m = 2.0
recipe.origin_x_m = -20.0
recipe.origin_z_m = -20.0
recipe.floor_elevation_m = -80.0
recipe.rock_slope_degrees = 20.0
return recipe
func _test_plane_and_blob() -> void:
var recipe := _small_recipe()
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.0, 0.1, 0.0, 0.0))
var data := SiteTerrainBaker.bake(recipe)
_expect(data != null and data.validation_error().is_empty(), "plane recipe bakes valid data")
_expect(is_equal_approx(data.elevations_m[data.index_of(10, 10)], -30.0), "plane depth at the reference point")
_expect(is_equal_approx(data.elevations_m[data.index_of(10, 20)], -28.0), "plane slopes along +Z")
_expect(data.materials[data.index_of(10, 10)] == SonarMaterial.Value.SAND, "gentle plane is sand")
recipe.primitives.append(SiteTerrainBaker.Blob.new(Vector2(0.0, 0.0), 4.0, 4.0, 10.0))
data = SiteTerrainBaker.bake(recipe)
_expect(is_equal_approx(data.elevations_m[data.index_of(10, 10)], -20.0), "blob adds its amplitude at the centre")
_expect(data.materials[data.index_of(12, 10)] == SonarMaterial.Value.ROCK, "steep blob flank is rock")
func _test_materials_and_determinism() -> void:
var recipe := _small_recipe()
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.0, 0.0, 0.0, 0.0))
recipe.primitives.append(SiteTerrainBaker.Roughness.new(1933, 0.05, 0.5))
recipe.material_patches.append(
SiteTerrainBaker.MaterialPatch.new(Vector2(10.0, 10.0), 3.0, SonarMaterial.Value.ROCK)
)
var first := SiteTerrainBaker.bake(recipe)
var second := SiteTerrainBaker.bake(recipe)
_expect(first.elevations_m == second.elevations_m, "same recipe bakes identical elevations")
_expect(first.materials == second.materials, "same recipe bakes identical materials")
_expect(first.materials[first.index_of(15, 15)] == SonarMaterial.Value.ROCK, "material patch overrides the slope rule")
var calibration := SiteTerrainBaker.bake(VehicleTestRecipe.recipe())
_expect(calibration != null and calibration.validation_error().is_empty(), "calibration recipe bakes valid data")
## Test doubles for the extension points: a modifier that lifts the field by one metre
## and a primitive that forces rock on a flat plane.
class Lift extends SiteTerrainBaker.Modifier:
func modify(_x_m: float, _z_m: float, elevation_m: float, _gradient: Vector2) -> float:
return elevation_m + 1.0
class GradientProbe extends SiteTerrainBaker.Modifier:
var gradient_at_centre := Vector2.ZERO
func modify(x_m: float, z_m: float, elevation_m: float, gradient: Vector2) -> float:
if is_zero_approx(x_m) and is_zero_approx(z_m):
gradient_at_centre = gradient
return elevation_m
class RockDisc extends SiteTerrainBaker.Primitive:
func material_at(x_m: float, z_m: float) -> int:
if Vector2(x_m, z_m).length() <= 5.0:
return SonarMaterial.Value.ROCK
return SiteTerrainBaker.NO_MATERIAL
func _test_modifiers_and_primitive_materials() -> void:
var recipe := _small_recipe()
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.2, 0.0, 0.0, 0.0))
var probe := GradientProbe.new()
recipe.modifiers.append(probe)
recipe.modifiers.append(Lift.new())
var data := SiteTerrainBaker.bake(recipe)
_expect(is_equal_approx(data.elevations_m[data.index_of(10, 10)], -29.0), "modifier runs after the primitive sum")
_expect(probe.gradient_at_centre.is_equal_approx(Vector2(0.2, 0.0)), "modifier receives the local gradient (%s)" % probe.gradient_at_centre)
var flat := _small_recipe()
flat.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.0, 0.0, 0.0, 0.0))
flat.primitives.append(RockDisc.new())
var flat_data := SiteTerrainBaker.bake(flat)
_expect(flat_data.materials[flat_data.index_of(10, 10)] == SonarMaterial.Value.ROCK, "primitive material wins over the slope rule")
_expect(flat_data.materials[flat_data.index_of(0, 0)] == SonarMaterial.Value.SAND, "primitive material is local")
func _test_canyon() -> void:
var canyon := SiteTerrainBaker.Canyon.new(
PackedVector2Array([Vector2(-20.0, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 20.0)]),
4.0, 10.0, 1.0, 0, 0.0
)
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 0.0), -10.0), "canyon floor sits at depth on the first leg")
_expect(is_equal_approx(canyon.elevation_delta(1.5, 10.0), -10.0), "canyon floor is flat across its width on the second leg")
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 6.0), 0.0), "canyon stops past half width plus softness")
_expect(is_equal_approx(canyon.elevation_delta(15.0, -15.0), 0.0), "canyon is zero far from the path")
_expect(is_equal_approx(canyon.elevation_delta(0.0, 20.0), 0.0), "canyon closes at its last vertex")
_expect(canyon.elevation_delta(-10.0, 4.0) < -4.0 and canyon.elevation_delta(-10.0, 4.0) > -6.0, "canyon wall is half way down at half width")
var rough := SiteTerrainBaker.Canyon.new(
PackedVector2Array([Vector2(-200.0, 0.0), Vector2(200.0, 0.0)]), 8.0, 10.0, 2.0, 7, 3.0
)
var wall_values: Dictionary = {}
for x in range(-100, 101, 10):
wall_values[snappedf(rough.elevation_delta(float(x), 8.0), 0.01)] = true
_expect(wall_values.size() > 3, "outline noise makes the canyon wall irregular along the path")
func _test_escarpment() -> void:
var cliff := SiteTerrainBaker.Escarpment.new(
PackedVector2Array([Vector2(-50.0, 0.0), Vector2(50.0, 0.0)]), 10.0, 2.0, 0.5, 0, 0.0
)
_expect(is_equal_approx(cliff.elevation_delta(0.0, -10.0), 0.0), "escarpment leaves the left (north) side alone")
_expect(is_equal_approx(cliff.elevation_delta(0.0, 2.0), -10.0), "escarpment drops by drop_m on the right (south) side")
_expect(is_equal_approx(cliff.elevation_delta(0.0, 12.0), -15.0), "escarpment keeps falling at slope_beyond")
func _test_zone1_artefact_is_stable() -> void:
var versioned := load(ZONE1_TERRAIN_PATH) as SiteTerrainData
_expect(versioned != null, "versioned zone 1 terrain loads")
if versioned == null:
return
var baked := SiteTerrainBaker.bake(Zone1Recipe.recipe())
_expect(
baked.elevations_m == versioned.elevations_m and baked.materials == versioned.materials,
"zone 1 recipe still bakes the versioned artefact byte for byte"
)
func _expect(condition: bool, label: String) -> void:
if not condition:
_failures.append(label)