forked from woofmeow/straywild
216 lines
5.6 KiB
GDScript
216 lines
5.6 KiB
GDScript
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)
|