forked from woofmeow/straywild
Add deterministic generated worlds
This commit is contained in:
parent
19f63bd656
commit
e8888ed05a
87 changed files with 3743 additions and 139 deletions
216
tests/terrain_chunk_generator_validation.gd
Normal file
216
tests/terrain_chunk_generator_validation.gd
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
extends SceneTree
|
||||
|
||||
const CATALOG: TerrainChunkCatalog = preload(
|
||||
"res://world/generation/chunks/terrain_chunk_catalog.tres"
|
||||
)
|
||||
const EXPECTED_IDS: Array[String] = [
|
||||
"chunk_0000",
|
||||
"chunk_0001",
|
||||
"chunk_0002",
|
||||
"chunk_0003",
|
||||
"chunk_0004",
|
||||
"chunk_spawn",
|
||||
]
|
||||
const REQUIRED_IDS: Array[String] = [
|
||||
"chunk_spawn",
|
||||
"chunk_0001",
|
||||
"chunk_0002",
|
||||
"chunk_0003",
|
||||
"chunk_0004",
|
||||
]
|
||||
const TEST_SEED := 13001
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
call_deferred(&"_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_validate_catalog()
|
||||
_validate_profiles()
|
||||
_validate_generation()
|
||||
_finish()
|
||||
|
||||
|
||||
func _validate_catalog() -> void:
|
||||
for error: String in CATALOG.validation_errors():
|
||||
_check(false, error)
|
||||
_check(
|
||||
CATALOG.definitions.size() == EXPECTED_IDS.size(),
|
||||
"Catalog must contain the authored chunks and spawn definition.",
|
||||
)
|
||||
for stable_id: String in EXPECTED_IDS:
|
||||
var definition := CATALOG.definition_for_id(StringName(stable_id))
|
||||
_check(definition != null, "Catalog is missing %s." % stable_id)
|
||||
if definition == null:
|
||||
continue
|
||||
_check(
|
||||
definition.packed_scene != null,
|
||||
"%s must reference an imported GLB." % stable_id,
|
||||
)
|
||||
|
||||
|
||||
func _validate_profiles() -> void:
|
||||
for definition: TerrainChunkDefinition in CATALOG.definitions:
|
||||
var variants := TerrainChunkAnalyzer.create_variants(
|
||||
definition,
|
||||
CATALOG.chunk_size,
|
||||
)
|
||||
_check(
|
||||
not variants.is_empty(),
|
||||
"%s must produce at least one rotation variant."
|
||||
% definition.stable_id,
|
||||
)
|
||||
for variant: TerrainChunkVariant in variants:
|
||||
_check(
|
||||
variant.edge_profiles.size() == 4,
|
||||
"%s must expose four edge profiles." % variant.stable_key(),
|
||||
)
|
||||
for profile: TerrainChunkEdgeProfile in variant.edge_profiles:
|
||||
_check(
|
||||
not profile.points.is_empty(),
|
||||
"%s has an empty edge profile." % variant.stable_key(),
|
||||
)
|
||||
|
||||
|
||||
func _validate_generation() -> void:
|
||||
var first := _make_generator()
|
||||
root.add_child(first)
|
||||
var first_solved := first.generate()
|
||||
_check(first_solved, "Generator must solve the prototype 5x5 grid.")
|
||||
if not first_solved:
|
||||
first.free()
|
||||
return
|
||||
var first_keys := first.placement_keys()
|
||||
_check(first_keys.size() == 25, "Generator must place exactly 25 chunks.")
|
||||
for stable_id: String in REQUIRED_IDS:
|
||||
_check(
|
||||
_placements_contain(first_keys, stable_id),
|
||||
"Generated diagnostic must contain %s." % stable_id,
|
||||
)
|
||||
_check(
|
||||
_all_neighbor_edges_match(first),
|
||||
"Every generated neighboring edge must match.",
|
||||
)
|
||||
_check(
|
||||
_count_placements(first_keys, "chunk_spawn") == 1,
|
||||
"Generated layouts must contain exactly one spawn chunk.",
|
||||
)
|
||||
_check(
|
||||
first_keys[12].begins_with("chunk_spawn@"),
|
||||
"The spawn chunk must occupy the center cell.",
|
||||
)
|
||||
|
||||
var second := _make_generator()
|
||||
root.add_child(second)
|
||||
var second_solved := second.generate()
|
||||
_check(second_solved, "Repeated deterministic generation must solve.")
|
||||
if not second_solved:
|
||||
first.free()
|
||||
second.free()
|
||||
return
|
||||
_check(
|
||||
second.placement_keys() == first_keys,
|
||||
"The same seed and catalog must produce the same layout.",
|
||||
)
|
||||
first.free()
|
||||
second.free()
|
||||
|
||||
for seed_offset: int in range(1, 6):
|
||||
var generator := _make_generator()
|
||||
generator.generation_seed = TEST_SEED + seed_offset
|
||||
root.add_child(generator)
|
||||
var solved := generator.generate()
|
||||
_check(
|
||||
solved,
|
||||
"Generator must solve seed %d." % generator.generation_seed,
|
||||
)
|
||||
if solved:
|
||||
var keys := generator.placement_keys()
|
||||
for stable_id: String in REQUIRED_IDS:
|
||||
_check(
|
||||
_placements_contain(keys, stable_id),
|
||||
"Seed %d must contain %s."
|
||||
% [generator.generation_seed, stable_id],
|
||||
)
|
||||
_check(
|
||||
_all_neighbor_edges_match(generator),
|
||||
"Seed %d has a mismatched neighboring edge."
|
||||
% generator.generation_seed,
|
||||
)
|
||||
generator.free()
|
||||
|
||||
|
||||
func _make_generator() -> TerrainChunkGenerator:
|
||||
var generator := TerrainChunkGenerator.new()
|
||||
generator.catalog = CATALOG
|
||||
generator.grid_size = Vector2i(5, 5)
|
||||
generator.generation_seed = TEST_SEED
|
||||
generator.generate_on_ready = false
|
||||
generator.build_collision = false
|
||||
generator.force_center_chunk_id = &"chunk_spawn"
|
||||
generator.required_chunk_ids = PackedStringArray(REQUIRED_IDS)
|
||||
generator.maximum_backtracks = 100000
|
||||
return generator
|
||||
|
||||
|
||||
func _placements_contain(keys: PackedStringArray, stable_id: String) -> bool:
|
||||
for key: String in keys:
|
||||
if key.begins_with(stable_id + "@"):
|
||||
return true
|
||||
return false
|
||||
|
||||
|
||||
func _count_placements(keys: PackedStringArray, stable_id: String) -> int:
|
||||
var count := 0
|
||||
for key: String in keys:
|
||||
if key.begins_with(stable_id + "@"):
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
func _all_neighbor_edges_match(generator: TerrainChunkGenerator) -> bool:
|
||||
for index: int in generator._placements.size():
|
||||
var coordinate := Vector2i(
|
||||
index % generator.grid_size.x,
|
||||
index / generator.grid_size.x,
|
||||
)
|
||||
var current: TerrainChunkVariant = generator._placements[index]
|
||||
if coordinate.x > 0:
|
||||
var west: TerrainChunkVariant = generator._placements[index - 1]
|
||||
if not generator._edges_are_compatible(
|
||||
current,
|
||||
TerrainChunkTopology.Edge.WEST,
|
||||
west,
|
||||
TerrainChunkTopology.Edge.EAST,
|
||||
):
|
||||
return false
|
||||
if coordinate.y > 0:
|
||||
var north: TerrainChunkVariant = (
|
||||
generator._placements[index - generator.grid_size.x]
|
||||
)
|
||||
if not generator._edges_are_compatible(
|
||||
current,
|
||||
TerrainChunkTopology.Edge.NORTH,
|
||||
north,
|
||||
TerrainChunkTopology.Edge.SOUTH,
|
||||
):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func _check(condition: bool, message: String) -> void:
|
||||
if not condition:
|
||||
_failures.append(message)
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
if _failures.is_empty():
|
||||
print("Terrain chunk generator validation: PASS")
|
||||
quit(0)
|
||||
return
|
||||
for failure: String in _failures:
|
||||
printerr("Terrain chunk generator validation: ", failure)
|
||||
quit(1)
|
||||
Loading…
Add table
Add a link
Reference in a new issue