test(engine-specific): ✅ Add unit tests for species generation logic, including edge cases and validation assertions
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
c78443fa4b
commit
805d4306f7
1 changed files with 178 additions and 0 deletions
178
engine/tests/unit/test_species_generation.gd
Normal file
178
engine/tests/unit/test_species_generation.gd
Normal file
|
|
@ -0,0 +1,178 @@
|
|||
extends GutTest
|
||||
## M2b Block 1 verification — species generation diversity and constraint validation.
|
||||
## Tests 1.5 (diversity) and 1.6 (constraint validation) from the M2b task list.
|
||||
|
||||
const SpeciesGeneratorScript = preload("res://engine/src/models/world/species_generator.gd")
|
||||
const TraitSetScript = preload("res://engine/src/models/world/species_traits.gd")
|
||||
|
||||
const ALL_BIOMES: Array = [
|
||||
"deep_ocean", "shallow_ocean", "coral_reef", "estuary", "lake", "pond",
|
||||
"river", "mangrove", "tropical_rainforest", "tropical_dry_forest",
|
||||
"savanna", "desert", "temperate_forest", "temperate_grassland",
|
||||
"chaparral", "swamp", "bog", "boreal_forest", "tundra", "polar_desert",
|
||||
"montane_forest", "cloud_forest", "alpine_meadow", "alpine_tundra",
|
||||
"permanent_ice", "subterranean",
|
||||
]
|
||||
|
||||
const TEST_SEEDS: Array = [42, 137, 256, 404, 512, 777, 999, 1234, 2048, 3141]
|
||||
const MIN_PRODUCERS: int = 3
|
||||
const MIN_HERBIVORES: int = 3
|
||||
const MIN_PREDATORS: int = 2
|
||||
|
||||
|
||||
func before_all() -> void:
|
||||
DataLoader.load_theme("age-of-four")
|
||||
|
||||
|
||||
## 1.5: Each biome generates at least 3 producers + 3 herbivores + 2 predators
|
||||
## across 10 seeds.
|
||||
func test_species_diversity_per_biome() -> void:
|
||||
var report: Array[String] = []
|
||||
var failures: Array[String] = []
|
||||
|
||||
for biome_id: String in ALL_BIOMES:
|
||||
var weights: Dictionary = DataLoader.get_biome_trait_weights(biome_id)
|
||||
var total_producers: int = 0
|
||||
var total_herbivores: int = 0
|
||||
var total_predators: int = 0
|
||||
var total_species: int = 0
|
||||
|
||||
for seed_val: int in TEST_SEEDS:
|
||||
var species: Array = SpeciesGeneratorScript.generate(
|
||||
biome_id, 3, seed_val, weights
|
||||
)
|
||||
total_species += species.size()
|
||||
for ts: Variant in species:
|
||||
match ts.diet:
|
||||
"producer":
|
||||
total_producers += 1
|
||||
"herbivore":
|
||||
total_herbivores += 1
|
||||
"carnivore", "omnivore":
|
||||
total_predators += 1
|
||||
|
||||
var line: String = (
|
||||
"%s: %d species, %d producers, %d herbivores, %d predators"
|
||||
% [biome_id, total_species, total_producers, total_herbivores, total_predators]
|
||||
)
|
||||
report.append(line)
|
||||
|
||||
if total_producers < MIN_PRODUCERS:
|
||||
failures.append("%s: only %d producers (need %d)" % [biome_id, total_producers, MIN_PRODUCERS])
|
||||
if total_herbivores < MIN_HERBIVORES:
|
||||
failures.append("%s: only %d herbivores (need %d)" % [biome_id, total_herbivores, MIN_HERBIVORES])
|
||||
if total_predators < MIN_PREDATORS:
|
||||
failures.append("%s: only %d predators (need %d)" % [biome_id, total_predators, MIN_PREDATORS])
|
||||
|
||||
# Print diversity report
|
||||
gut.p("=== Species Diversity Report (10 seeds, quality 3) ===")
|
||||
for line: String in report:
|
||||
gut.p(line)
|
||||
|
||||
if failures.size() > 0:
|
||||
gut.p("=== FAILURES ===")
|
||||
for f: String in failures:
|
||||
gut.p(f)
|
||||
fail_test("Diversity check failed for %d biomes" % failures.size())
|
||||
else:
|
||||
pass_test("All biomes meet diversity minimums")
|
||||
|
||||
|
||||
## 1.6: Zero invalid species generated across all biomes on 10 seeds.
|
||||
func test_zero_invalid_species() -> void:
|
||||
var invalid_count: int = 0
|
||||
var total_count: int = 0
|
||||
|
||||
for biome_id: String in ALL_BIOMES:
|
||||
var weights: Dictionary = DataLoader.get_biome_trait_weights(biome_id)
|
||||
|
||||
for seed_val: int in TEST_SEEDS:
|
||||
var species: Array = SpeciesGeneratorScript.generate(
|
||||
biome_id, 3, seed_val, weights
|
||||
)
|
||||
for ts: Variant in species:
|
||||
total_count += 1
|
||||
if not ts.validate():
|
||||
invalid_count += 1
|
||||
gut.p(
|
||||
"INVALID: %s in %s (seed %d)" % [ts.trait_hash, biome_id, seed_val]
|
||||
)
|
||||
|
||||
gut.p("Total species generated: %d, invalid: %d" % [total_count, invalid_count])
|
||||
assert_eq(invalid_count, 0, "All generated species must pass validate()")
|
||||
|
||||
|
||||
## Verify amphibious adjacency boost produces amphibious species.
|
||||
func test_amphibious_adjacency_boost() -> void:
|
||||
var weights: Dictionary = DataLoader.get_biome_trait_weights("temperate_forest")
|
||||
var amphibious_without: int = 0
|
||||
var amphibious_with: int = 0
|
||||
var trials: int = 10
|
||||
|
||||
for seed_val: int in TEST_SEEDS:
|
||||
var normal: Array = SpeciesGeneratorScript.generate(
|
||||
"temperate_forest", 3, seed_val, weights, false
|
||||
)
|
||||
var boosted: Array = SpeciesGeneratorScript.generate(
|
||||
"temperate_forest", 3, seed_val, weights, true
|
||||
)
|
||||
for ts: Variant in normal:
|
||||
if ts.habitat == "amphibious":
|
||||
amphibious_without += 1
|
||||
for ts: Variant in boosted:
|
||||
if ts.habitat == "amphibious":
|
||||
amphibious_with += 1
|
||||
|
||||
gut.p(
|
||||
"Amphibious species: without boost=%d, with boost=%d (across %d seeds)"
|
||||
% [amphibious_without, amphibious_with, trials]
|
||||
)
|
||||
assert_true(
|
||||
amphibious_with >= amphibious_without,
|
||||
"Adjacency boost should not reduce amphibious species count"
|
||||
)
|
||||
|
||||
|
||||
## Verify migration pattern detection.
|
||||
func test_migration_pattern_detection() -> void:
|
||||
var flying_herd := TraitSetScript.new()
|
||||
flying_herd.size = "medium"
|
||||
flying_herd.diet = "herbivore"
|
||||
flying_herd.habitat = "aerial"
|
||||
flying_herd.locomotion = "flying"
|
||||
flying_herd.reproduction = "k_strategy"
|
||||
flying_herd.thermal = "warm_blooded"
|
||||
flying_herd.social = "herd"
|
||||
assert_eq(
|
||||
SpeciesGeneratorScript.get_migration_pattern(flying_herd),
|
||||
"seasonal",
|
||||
"Aerial herd should get seasonal migration"
|
||||
)
|
||||
|
||||
var walking_pack := TraitSetScript.new()
|
||||
walking_pack.size = "medium"
|
||||
walking_pack.diet = "carnivore"
|
||||
walking_pack.habitat = "terrestrial"
|
||||
walking_pack.locomotion = "walking"
|
||||
walking_pack.reproduction = "k_strategy"
|
||||
walking_pack.thermal = "warm_blooded"
|
||||
walking_pack.social = "pack"
|
||||
assert_eq(
|
||||
SpeciesGeneratorScript.get_migration_pattern(walking_pack),
|
||||
null,
|
||||
"Walking pack should not get seasonal migration"
|
||||
)
|
||||
|
||||
var flying_swarm := TraitSetScript.new()
|
||||
flying_swarm.size = "tiny"
|
||||
flying_swarm.diet = "herbivore"
|
||||
flying_swarm.habitat = "terrestrial"
|
||||
flying_swarm.locomotion = "flying"
|
||||
flying_swarm.reproduction = "r_strategy"
|
||||
flying_swarm.thermal = "cold_blooded"
|
||||
flying_swarm.social = "swarm"
|
||||
assert_eq(
|
||||
SpeciesGeneratorScript.get_migration_pattern(flying_swarm),
|
||||
"seasonal",
|
||||
"Flying swarm should get seasonal migration"
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue