Expand generated terrain and coastal elevations

This commit is contained in:
Alexander Sellite 2026-08-21 21:33:30 -04:00
parent 6d0e996583
commit 339812763d
28 changed files with 906 additions and 171 deletions

View file

@ -13,7 +13,7 @@ const GeneratedRiverPool: FishPool = preload(
"res://fish/pools/generated_river_pool.tres"
)
const FIRST_SEED := 13001
const SECOND_SEED := 13004
const SECOND_SEED := 13002
const EXPECTED_PROP_IDS: Array[StringName] = [
&"prop_bridge",
&"prop_dock",
@ -110,23 +110,47 @@ func _validate_generated_region(
assert(placements[center_index].begins_with("chunk_spawn@"))
assert(_placement_count(placements, "chunk_spawn") == 1)
assert(_placement_count(placements, "chunk_0001") >= 1)
assert(_placement_count(placements, "chunk_0002") >= 1)
assert(_placement_count(placements, "chunk_0003") >= 1)
assert(_placement_count(placements, "chunk_0016") >= 1)
assert(_placement_count(placements, "chunk_0004") >= 1)
assert(_placement_count(placements, "chunk_0005") >= 1)
assert(_placement_count(placements, "chunk_0006") >= 1)
assert(_placement_count(placements, "chunk_0007") >= 1)
assert(_placement_count(placements, "chunk_0008") >= 1)
var beach_count := _placement_count(placements, "chunk_0002")
var grass_coast_count := _placement_count(placements, "chunk_0005")
var grass_corner_count := _placement_count(placements, "chunk_0007")
var beach_corner_count := _placement_count(placements, "chunk_0008")
assert((beach_count > 0) != (grass_coast_count > 0))
if beach_count > 0:
assert(beach_count == 48 and grass_coast_count == 0)
assert(beach_corner_count == 4 and grass_corner_count == 0)
assert(_placement_count(placements, "chunk_0009") == 9)
assert(_placement_count(placements, "chunk_0010") == 4)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 12
)
assert(_placement_count(placements, "chunk_0012") == 1)
for stable_id: String in [
"chunk_0014",
"chunk_0015",
"chunk_0017",
"chunk_0018",
]:
assert(_placement_count(placements, stable_id) == 0)
else:
assert(grass_coast_count == 44 and beach_count == 0)
assert(grass_corner_count == 3 and beach_corner_count == 0)
assert(_placement_count(placements, "chunk_0009") == 10)
assert(_placement_count(placements, "chunk_0010") == 5)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 14
)
assert(_placement_count(placements, "chunk_0012") == 2)
assert(_placement_count(placements, "chunk_0014") == 2)
assert(_placement_count(placements, "chunk_0015") == 1)
assert(_placement_count(placements, "chunk_0017") == 1)
assert(_placement_count(placements, "chunk_0018") == 1)
assert(_placement_count(placements, "chunk_0013") >= 1)
assert(_placement_count(placements, "chunk_0009") == 1)
assert(_placement_count(placements, "chunk_0010") == 4)
assert(
_placement_count(placements, "chunk_0011")
+ _placement_count(placements, "chunk_0012")
== 4
)
assert(_placement_count(placements, "chunk_0012") <= 1)
assert(
generator.get_generated_chunks_root().get_child_count()
== expected_chunk_count
@ -164,6 +188,7 @@ func _validate_generated_region(
_validate_ocean_facing_record(record, generator.grid_size)
if "fresh_water" in tags:
fresh_placement_count += 1
_validate_complete_coastline(generator)
assert(fresh_root.get_child_count() == fresh_placement_count)
for child: Node in fresh_root.get_children():
var fresh := child as WaterBodyAuthoring
@ -191,6 +216,7 @@ func _validate_generated_region(
assert(decorations != null and decorations.get_child_count() > 0)
assert(anchors != null)
assert(anchors.get_spawn_positions().size() > 0)
_validate_tree_gatherable_anchors(region, decorations, anchors)
var palm_clusters: Dictionary[int, Array] = {}
var tree_scales: Array[float] = []
var tree_yaws: Dictionary[float, bool] = {}
@ -295,7 +321,7 @@ func _validate_generated_region(
)
assert(not palm_clusters.is_empty())
for members: Array in palm_clusters.values():
assert(members.size() >= 2 and members.size() <= 3)
assert(members.size() >= 1 and members.size() <= 3)
for first_index: int in members.size():
for second_index: int in range(first_index + 1, members.size()):
var first := members[first_index] as Node3D
@ -314,14 +340,14 @@ func _validate_generated_region(
region,
&"biome_forest",
&"grass_tree",
) >= 2
) >= 4
)
assert(
_decoration_biome_group_count(
region,
&"biome_pine_forest",
&"grass_tree",
) >= 2
) >= 4
)
@ -329,7 +355,15 @@ func _validate_elevated_cliff_feature(
generator: TerrainChunkGenerator,
) -> void:
var elevated_coordinates: Dictionary[Vector2i, StringName] = {}
var top_coordinate := Vector2i(-1, -1)
var top_coordinates: Array[Vector2i] = []
var ramp_records: Array[Dictionary] = []
var coastal_ids: Array[StringName] = [
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]
var has_coastal_feature := false
for record: Dictionary in generator.placement_records():
var stable_id: StringName = record.get("stable_id", &"")
if stable_id not in [
@ -337,23 +371,100 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]:
continue
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1)
assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1)
if stable_id in coastal_ids:
has_coastal_feature = true
assert(
coordinate.x in [0, generator.grid_size.x - 1]
or coordinate.y in [0, generator.grid_size.y - 1]
)
else:
assert(coordinate.x > 0 and coordinate.x < generator.grid_size.x - 1)
assert(coordinate.y > 0 and coordinate.y < generator.grid_size.y - 1)
elevated_coordinates[coordinate] = stable_id
if stable_id == &"chunk_0009":
top_coordinate = coordinate
assert(elevated_coordinates.size() == 9)
assert(top_coordinate != Vector2i(-1, -1))
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
top_coordinates.append(coordinate)
elif stable_id == &"chunk_0012":
ramp_records.append(record)
assert(elevated_coordinates.size() == (34 if has_coastal_feature else 25))
assert(top_coordinates.size() == (10 if has_coastal_feature else 9))
assert(ramp_records.size() == (2 if has_coastal_feature else 1))
var feature_center := Vector2i(-1, -1)
for candidate: Vector2i in top_coordinates:
var neighboring_top_count := 0
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
neighboring_top_count += int(
elevated_coordinates.get(
candidate + Vector2i(column_offset, row_offset),
&"",
) == &"chunk_0009"
)
if neighboring_top_count == 9:
feature_center = candidate
break
assert(feature_center != Vector2i(-1, -1))
for row_offset: int in range(-2, 3):
for column_offset: int in range(-2, 3):
assert(
elevated_coordinates.has(
top_coordinate + Vector2i(column_offset, row_offset)
feature_center + Vector2i(column_offset, row_offset)
)
)
if has_coastal_feature:
var secondary_top := Vector2i(-1, -1)
for coordinate: Vector2i in top_coordinates:
if (
absi(coordinate.x - feature_center.x) > 1
or absi(coordinate.y - feature_center.y) > 1
):
secondary_top = coordinate
break
assert(secondary_top != Vector2i(-1, -1))
for row_offset: int in range(-1, 2):
for column_offset: int in range(-1, 2):
assert(
elevated_coordinates.has(
secondary_top + Vector2i(column_offset, row_offset)
)
)
var found_deep_top_landing := false
for ramp_record: Dictionary in ramp_records:
var ramp_coordinate: Vector2i = ramp_record.get(
"coordinate", Vector2i(-1, -1)
)
var ramp_turns := int(ramp_record.get("rotation_quarters", 0))
var high_edge := TerrainChunkTopology.rotated_edge(
TerrainChunkTopology.Edge.WEST,
ramp_turns,
)
var high_offset := TerrainChunkTopology.grid_offset(high_edge)
assert(
elevated_coordinates.get(ramp_coordinate + high_offset, &"")
== &"chunk_0009"
)
found_deep_top_landing = (
found_deep_top_landing
or elevated_coordinates.get(
ramp_coordinate + high_offset * 2,
&"",
) == &"chunk_0009"
)
var low_coordinate := ramp_coordinate - high_offset
var low_index := (
low_coordinate.y * generator.grid_size.x + low_coordinate.x
)
assert(
generator.placement_keys()[low_index].begins_with("chunk_0000@")
or generator.placement_keys()[low_index].begins_with("chunk_spawn@")
)
assert(found_deep_top_landing)
var generated := generator.get_generated_chunks_root()
assert(generated != null)
@ -367,25 +478,33 @@ func _validate_elevated_cliff_feature(
&"chunk_0010",
&"chunk_0011",
&"chunk_0012",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]:
continue
if stable_id in [&"chunk_0014", &"chunk_0015"]:
assert(chunk_root.get_node_or_null("TerrainBaseLayer") == null)
continue
layered_count += 1
var base_layer := chunk_root.get_node_or_null("TerrainBaseLayer")
var overlay := chunk_root.get_node_or_null("TerrainOverlay")
assert(base_layer != null and overlay != null)
var is_transition := stable_id in [&"chunk_0017", &"chunk_0018"]
var base_mesh := TerrainChunkAnalyzer.find_primary_mesh(
base_layer,
&"chunk_0000",
&"chunk_0005" if is_transition else &"chunk_0000",
)
var overlay_mesh := TerrainChunkAnalyzer.find_primary_mesh(
overlay,
stable_id,
&"chunk_0015" if is_transition else stable_id,
)
assert(base_mesh != null and base_mesh.mesh != null)
assert(overlay_mesh != null and overlay_mesh.mesh != null)
assert(base_mesh.has_node("TerrainBaseLayerCollision"))
assert(overlay_mesh.has_node("TerrainCollision"))
assert(layered_count == 9)
assert(layered_count == (31 if has_coastal_feature else 25))
var stacked_keys := generator.stacked_elevated_placement_keys()
assert(stacked_keys.size() in [0, 4])
if stacked_keys.is_empty():
@ -441,6 +560,28 @@ func _validate_ocean_facing_record(
)
func _validate_complete_coastline(generator: TerrainChunkGenerator) -> void:
for record: Dictionary in generator.placement_records():
var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO)
var ocean_edges := int(record.get("ocean_facing_edges", 0))
for edge_value: int in TerrainChunkTopology.Edge.values():
var neighbor := (
coordinate
+ TerrainChunkTopology.grid_offset(
edge_value as TerrainChunkTopology.Edge
)
)
var outside := (
neighbor.x < 0
or neighbor.y < 0
or neighbor.x >= generator.grid_size.x
or neighbor.y >= generator.grid_size.y
)
assert(
((ocean_edges & (1 << edge_value)) != 0) == outside
)
func _ocean_direction(ocean_edges: int) -> Vector3:
var result := Vector3.ZERO
for edge_value: int in TerrainChunkTopology.Edge.values():
@ -522,13 +663,19 @@ func _validate_prop_catalog(region: GeneratedWorldRegion) -> void:
+ catalog.definition_for_id(&"prop_tree_3").selection_weight
)
assert(forest_rule.allows_prop(large_tree))
assert(large_tree.selection_weight > small_tree_weight)
assert(large_tree.selection_weight <= small_tree_weight)
var pine_forest := region.get_biome_catalog().definition_for_id(
&"biome_pine_forest"
)
var pine_rule := pine_forest.prop_rule_for_group(&"grass_tree")
assert(pine_rule.allows_prop(large_pine))
assert(large_pine.selection_weight > pine.selection_weight)
assert(
is_equal_approx(large_pine.selection_weight, pine.selection_weight)
)
assert(forest_rule.minimum_placements == 4)
assert(forest_rule.placement_attempts_per_chunk == 4)
assert(pine_rule.minimum_placements == 4)
assert(pine_rule.placement_attempts_per_chunk == 4)
func _validate_biome_catalog(
@ -623,6 +770,57 @@ func _validate_decoration_transform(
assert(absf(ground_position.y - prop.global_position.y) <= 0.01)
func _validate_tree_gatherable_anchors(
region: GeneratedWorldRegion,
decorations: Node3D,
anchors: GatherableAnchorSet3D,
) -> void:
var eligible_props: Array[Node3D] = []
for child: Node in decorations.get_children():
var prop := child as Node3D
if prop == null:
continue
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
if definition != null and definition.gatherable_anchor_height > 0.0:
eligible_props.append(prop)
var positions := anchors.get_spawn_positions()
assert(positions.size() == eligible_props.size())
for prop: Node3D in eligible_props:
var prop_id := StringName(prop.get_meta(&"terrain_prop_id", &""))
var definition := region.get_prop_catalog().definition_for_id(prop_id)
var visual_scale := float(
prop.get_meta(&"terrain_prop_visual_scale", 1.0)
)
var nearest_anchor := Vector3(INF, INF, INF)
var nearest_distance_squared := INF
for position: Vector3 in positions:
var distance_squared := position.distance_squared_to(
prop.global_position
)
if distance_squared < nearest_distance_squared:
nearest_distance_squared = distance_squared
nearest_anchor = position
assert(nearest_anchor.is_finite())
var horizontal_distance := Vector2(
nearest_anchor.x - prop.global_position.x,
nearest_anchor.z - prop.global_position.z,
).length()
assert(
horizontal_distance
>= definition.gatherable_anchor_surface_radius() * visual_scale
)
assert(
absf(
nearest_anchor.y
- (
prop.global_position.y
+ definition.gatherable_anchor_height * visual_scale
)
) <= 0.001
)
func _find_mesh_instance(root_node: Node) -> MeshInstance3D:
if root_node is MeshInstance3D:
return root_node as MeshInstance3D
@ -662,13 +860,15 @@ func _validate_authored_chunk_surfaces(
var generated := generator.get_generated_chunks_root()
var found_beach := false
var found_stream := false
var found_alternate_stream := false
var found_pond := false
var found_grass_ocean_edge := false
var found_grass_beach_transition := false
var found_grass_ocean_corner := false
var found_beach_ocean_corner := false
var found_grass_sand_diagonal := false
var beach_family := (
_placement_count(generator.placement_keys(), "chunk_0002") > 0
)
for chunk_root: Node in generated.get_children():
if chunk_root.name.begins_with("chunk_0002r"):
var beach := chunk_root.find_child(
@ -695,9 +895,7 @@ func _validate_authored_chunk_surfaces(
assert("dirt" in stream_materials)
assert("grass_lite" in stream_materials)
assert("dirt_wall" in stream_materials)
if stream_id == &"chunk_0016":
found_alternate_stream = true
else:
if stream_id == &"chunk_0003":
found_stream = true
elif chunk_root.name.begins_with("chunk_0004r"):
var pond := chunk_root.find_child(
@ -761,15 +959,20 @@ func _validate_authored_chunk_surfaces(
assert("grass_lite" in diagonal_materials)
assert("sand" in diagonal_materials)
found_grass_sand_diagonal = true
assert(found_beach)
assert(found_stream)
assert(found_alternate_stream)
assert(found_pond)
assert(found_grass_ocean_edge)
assert(found_grass_beach_transition)
assert(found_grass_ocean_corner)
assert(found_beach_ocean_corner)
assert(found_grass_sand_diagonal)
if beach_family:
assert(found_beach)
assert(found_beach_ocean_corner)
assert(not found_grass_ocean_edge)
assert(not found_grass_ocean_corner)
else:
assert(not found_beach)
assert(not found_beach_ocean_corner)
assert(found_grass_ocean_edge)
assert(found_grass_ocean_corner)
assert(not found_grass_beach_transition)
func _material_names(mesh_instance: MeshInstance3D) -> PackedStringArray:

View file

@ -104,7 +104,7 @@ func _run() -> void:
func _make_generator() -> TerrainChunkGenerator:
var generator := TerrainChunkGenerator.new()
generator.catalog = CATALOG
generator.grid_size = Vector2i(7, 7)
generator.grid_size = Vector2i(9, 9)
generator.generation_seed = 13001
generator.generate_on_ready = false
generator.build_collision = false
@ -113,13 +113,8 @@ func _make_generator() -> TerrainChunkGenerator:
[
"chunk_spawn",
"chunk_0001",
"chunk_0002",
"chunk_0003",
"chunk_0004",
"chunk_0005",
"chunk_0006",
"chunk_0007",
"chunk_0008",
]
)
generator.grass_sand_smoothing_enabled = true

View file

@ -21,12 +21,12 @@ shadow_enabled = true
[node name="TerrainChunkGenerator" type="Node3D" parent="."]
script = ExtResource("2_generator")
catalog = ExtResource("3_catalog")
grid_size = Vector2i(11, 9)
grid_size = Vector2i(15, 13)
generation_seed = 13001
build_collision = true
show_chunk_labels = false
force_center_chunk_id = &"chunk_spawn"
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004", "chunk_0005", "chunk_0006", "chunk_0007", "chunk_0008")
required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0003", "chunk_0004")
grass_sand_smoothing_enabled = true
elevated_cliff_feature_enabled = true

View file

@ -21,18 +21,15 @@ const EXPECTED_IDS: Array[String] = [
"chunk_0014",
"chunk_0015",
"chunk_0016",
"chunk_0017",
"chunk_0018",
"chunk_spawn",
]
const REQUIRED_IDS: Array[String] = [
"chunk_spawn",
"chunk_0001",
"chunk_0002",
"chunk_0003",
"chunk_0004",
"chunk_0005",
"chunk_0006",
"chunk_0007",
"chunk_0008",
]
const TEST_SEED := 13001
const CONNECTOR_STRESS_SEED := 287
@ -55,6 +52,8 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = {
&"chunk_0014": 4,
&"chunk_0015": 4,
&"chunk_0016": 4,
&"chunk_0017": 4,
&"chunk_0018": 4,
&"chunk_spawn": 1,
}
@ -97,8 +96,11 @@ func _validate_catalog() -> void:
var grass_ocean_corner := CATALOG.definition_for_id(&"chunk_0007")
var beach_ocean_corner := CATALOG.definition_for_id(&"chunk_0008")
var grass_sand_diagonal := CATALOG.definition_for_id(&"chunk_0013")
var pond := CATALOG.definition_for_id(&"chunk_0004")
var cliff_sea_edge := CATALOG.definition_for_id(&"chunk_0014")
var cliff_sea_corner := CATALOG.definition_for_id(&"chunk_0015")
var cliff_sea_transition_right := CATALOG.definition_for_id(&"chunk_0017")
var cliff_sea_transition_left := CATALOG.definition_for_id(&"chunk_0018")
var stream_variant := CATALOG.definition_for_id(&"chunk_0016")
var cliff_top := CATALOG.definition_for_id(&"chunk_0009")
var cliff_corner := CATALOG.definition_for_id(&"chunk_0010")
@ -115,8 +117,11 @@ func _validate_catalog() -> void:
and grass_ocean_corner != null
and beach_ocean_corner != null
and grass_sand_diagonal != null
and pond != null
and cliff_sea_edge != null
and cliff_sea_corner != null
and cliff_sea_transition_right != null
and cliff_sea_transition_left != null
and stream_variant != null
and cliff_top != null
and cliff_corner != null
@ -135,8 +140,11 @@ func _validate_catalog() -> void:
and grass_ocean_corner != null
and beach_ocean_corner != null
and grass_sand_diagonal != null
and pond != null
and cliff_sea_edge != null
and cliff_sea_corner != null
and cliff_sea_transition_right != null
and cliff_sea_transition_left != null
and stream_variant != null
and cliff_top != null
and cliff_corner != null
@ -157,8 +165,11 @@ func _validate_catalog() -> void:
"Grass must permit the inland side of a flat-sand transition.",
)
_check(
"coast" in sand.required_neighbor_tags,
"Every flat-sand chunk must remain attached to an authored beach.",
(
"sand" in sand.required_neighbor_tags
and "coast" in sand.required_neighbor_tags
),
"Every flat-sand chunk must remain attached to sand or authored coast.",
)
_check(
spawn.minimum_required_neighbors == 3,
@ -281,6 +292,12 @@ func _validate_catalog() -> void:
"The diagonal's authored edges must retain two sand and two grass sides.",
)
var stream := CATALOG.definition_for_id(&"chunk_0003")
_check(
pond.water_inlet_edges == pond.water_outlet_edges
and pond.water_inlet_edges
== (1 << int(TerrainChunkTopology.Edge.NORTH)),
"A pond must terminate either end of an inland freshwater chain.",
)
_check(
stream != null
and is_equal_approx(stream.selection_weight, 0.3)
@ -331,6 +348,34 @@ func _validate_catalog() -> void:
+ "overlay without an inland grass base."
),
)
for transition: TerrainChunkDefinition in [
cliff_sea_transition_right,
cliff_sea_transition_left,
]:
_check(
transition.overlay_only
and transition.base_layer_scene != null
and transition.base_layer_mesh_name == &"chunk_0005"
and not transition.must_be_interior
and transition.prefers_map_boundary
and "transition" in transition.tags,
(
"Each raised-coast endpoint must layer the deep corner "
+ "over an authored level-one coastline."
),
)
_check(
cliff_sea_transition_right.base_layer_quarter_turns == 3
and cliff_sea_transition_right.ocean_facing_edges
== (1 << int(TerrainChunkTopology.Edge.SOUTH)),
"The right coastal transition must align its south-facing base coast.",
)
_check(
cliff_sea_transition_left.base_layer_quarter_turns == 0
and cliff_sea_transition_left.ocean_facing_edges
== (1 << int(TerrainChunkTopology.Edge.EAST)),
"The left coastal transition must align its east-facing base coast.",
)
_check(
cliff_sea_edge.ocean_facing_edges
== (1 << int(TerrainChunkTopology.Edge.EAST)),
@ -486,21 +531,17 @@ 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 7x7 grid.")
_check(first_solved, "Generator must solve the prototype 9x9 grid.")
if not first_solved:
first.free()
return
var first_keys := first.placement_keys()
_check(first_keys.size() == 49, "Generator must place exactly 49 chunks.")
_check(first_keys.size() == 81, "Generator must place exactly 81 chunks.")
for stable_id: String in REQUIRED_IDS:
_check(
_placements_contain(first_keys, stable_id),
"Generated diagnostic must contain %s." % stable_id,
)
_check(
_placements_contain(first_keys, "chunk_0013"),
"Generated terrain must replace sharp grass-sand corners with diagonals.",
)
_check(
_all_neighbor_edges_match(first),
"Every generated neighboring edge must match.",
@ -510,7 +551,7 @@ func _validate_generation() -> void:
"Generated layouts must contain exactly one spawn chunk.",
)
_check(
first_keys[24].begins_with("chunk_spawn@"),
first_keys[40].begins_with("chunk_spawn@"),
"The spawn chunk must occupy the center cell.",
)
_validate_layout_rules(first)
@ -837,18 +878,24 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
"The beach corner's west side must join a rotated straight beach.",
)
var cliff_top_zero := _find_variant(generator, &"chunk_0009", 0)
var cliff_edge_zero := _find_variant(generator, &"chunk_0011", 0)
var cliff_edge_south := _find_variant(generator, &"chunk_0011", 3)
var cliff_sea_edge_zero := _find_variant(generator, &"chunk_0014", 0)
var cliff_sea_edge_south := _find_variant(generator, &"chunk_0014", 3)
var cliff_sea_corner_zero := _find_variant(generator, &"chunk_0015", 0)
_check(
cliff_top_zero != null
and cliff_edge_zero != null
and cliff_edge_south != null
and cliff_sea_edge_zero != null
and cliff_sea_edge_south != null
and cliff_sea_corner_zero != null,
"The second-tier sea edge variants must be available.",
"The inland and coastal second-tier cliff variants must be available.",
)
if (
cliff_top_zero != null
and cliff_edge_zero != null
and cliff_edge_south != null
and cliff_sea_edge_zero != null
and cliff_sea_edge_south != null
and cliff_sea_corner_zero != null
@ -892,6 +939,42 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
),
"The second-tier sea corner must join two straight sea edges.",
)
_check(
generator._edges_are_compatible(
cliff_sea_edge_zero,
TerrainChunkTopology.Edge.NORTH,
cliff_edge_zero,
TerrainChunkTopology.Edge.SOUTH,
)
and generator._edges_are_compatible(
cliff_sea_edge_zero,
TerrainChunkTopology.Edge.SOUTH,
cliff_edge_zero,
TerrainChunkTopology.Edge.NORTH,
),
(
"A coastal cliff edge must transition directly into its "
+ "inland edge counterpart."
),
)
_check(
generator._edges_are_compatible(
cliff_sea_corner_zero,
TerrainChunkTopology.Edge.NORTH,
cliff_edge_zero,
TerrainChunkTopology.Edge.SOUTH,
)
and generator._edges_are_compatible(
cliff_sea_corner_zero,
TerrainChunkTopology.Edge.WEST,
cliff_edge_south,
TerrainChunkTopology.Edge.EAST,
),
(
"A coastal cliff corner must transition directly into "
+ "ordinary inland cliff edges."
),
)
var flat_grass := _find_variant(generator, &"chunk_0000", 0)
var flat_sand := _find_variant(generator, &"chunk_0001", 0)
_check(
@ -981,6 +1064,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
&"chunk_0008",
&"chunk_0014",
&"chunk_0015",
&"chunk_0017",
&"chunk_0018",
]:
for quarter_turns: int in 4:
var coast_variant := _find_variant(
@ -1032,8 +1117,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void:
func _validate_generation_summary(generator: TerrainChunkGenerator) -> void:
var summary := generator._build_summary()
_check(
int(summary.get("variant_count", 0)) == 69,
"The current catalog must expose all 69 authored rotations.",
int(summary.get("variant_count", 0)) == 77,
"The current catalog must expose all 77 authored rotations.",
)
_check(
int(summary.get("solver_variant_count", 0))
@ -1080,7 +1165,7 @@ func _find_variant(
func _make_generator() -> TerrainChunkGenerator:
var generator := TerrainChunkGenerator.new()
generator.catalog = CATALOG
generator.grid_size = Vector2i(7, 7)
generator.grid_size = Vector2i(9, 9)
generator.generation_seed = TEST_SEED
generator.generate_on_ready = false
generator.build_collision = false
@ -1185,6 +1270,23 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
),
"Every coastline chunk must direct its ocean edge outside the grid.",
)
var ocean_edges := placement.rotated_edge_mask(
placement.definition.ocean_facing_edges
)
for edge_value: int in TerrainChunkTopology.Edge.values():
var neighbor_coordinate := (
coordinate
+ TerrainChunkTopology.grid_offset(
edge_value as TerrainChunkTopology.Edge
)
)
var outside := not generator._coordinate_is_inside_grid(
neighbor_coordinate
)
_check(
((ocean_edges & (1 << edge_value)) != 0) == outside,
"Every map-boundary edge must use authored coastline geometry.",
)
if placement.definition.stable_id == &"chunk_0006":
var touches_beach := false
var touches_grass_edge := false
@ -1257,7 +1359,7 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
)
if placement.definition.stable_id != &"chunk_0001":
continue
var touches_coast := false
var touches_sand_or_coast := false
for edge_value: int in TerrainChunkTopology.Edge.values():
var neighbor_coordinate := (
coordinate
@ -1271,11 +1373,14 @@ func _validate_layout_rules(generator: TerrainChunkGenerator) -> void:
neighbor_coordinate.y * generator.grid_size.x
+ neighbor_coordinate.x
]
if "coast" in neighbor.definition.tags:
touches_coast = true
if (
"sand" in neighbor.definition.tags
or "coast" in neighbor.definition.tags
):
touches_sand_or_coast = true
_check(
touches_coast,
"Every flat-sand chunk must remain directly attached to a beach.",
touches_sand_or_coast,
"Every flat-sand chunk must remain attached to sand or coast.",
)