From f85e9bceebe99349e8f9b2218417f725ca463529 Mon Sep 17 00:00:00 2001 From: Voyager Date: Fri, 21 Aug 2026 22:12:14 -0400 Subject: [PATCH] fix: close generated coastlines and extend cast bounds --- tests/generated_world_runtime_validation.gd | 82 +++++++++++-------- .../terrain_chunk_compatibility_report.gd | 1 + .../terrain_chunk_generator_test.tscn | 2 +- tests/terrain_chunk_generator_validation.gd | 54 ++++++++++-- tests/world_layout_validation.gd | 21 +++++ .../chunks/definitions/chunk_0001.tres | 1 + .../chunks/definitions/chunk_0013.tres | 1 + .../chunks/definitions/chunk_0017.tres | 4 +- .../chunks/definitions/chunk_0018.tres | 5 +- .../chunks/definitions/chunk_0019.tres | 29 +++++++ .../chunks/definitions/chunk_0020.tres | 29 +++++++ .../chunks/terrain_chunk_catalog.tres | 6 +- world/generation/generated_world_region.tscn | 2 +- world/generation/terrain_chunk_definition.gd | 5 ++ world/generation/terrain_chunk_generator.gd | 59 +++++++++++-- world/test_world.gd | 6 +- 16 files changed, 250 insertions(+), 57 deletions(-) create mode 100644 world/generation/chunks/definitions/chunk_0019.tres create mode 100644 world/generation/chunks/definitions/chunk_0020.tres diff --git a/tests/generated_world_runtime_validation.gd b/tests/generated_world_runtime_validation.gd index 60ef84c..0f7a4dc 100644 --- a/tests/generated_world_runtime_validation.gd +++ b/tests/generated_world_runtime_validation.gd @@ -116,40 +116,31 @@ func _validate_generated_region( 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(beach_count == 44 and grass_coast_count == 0) + assert(beach_corner_count == 3 and grass_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") == 0) + assert(_placement_count(placements, "chunk_0018") == 0) + assert(_placement_count(placements, "chunk_0019") == 1) + assert(_placement_count(placements, "chunk_0020") == 1) + for index: int in placements.size(): + var coordinate := Vector2i( + index % generator.grid_size.x, + index / generator.grid_size.x, ) - 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) + if placements[index].begins_with("chunk_0001@"): + assert(generator._distance_from_map_boundary(coordinate) <= 1) + elif placements[index].begins_with("chunk_0013@"): + assert(generator._distance_from_map_boundary(coordinate) <= 2) assert(_placement_count(placements, "chunk_0013") >= 1) assert( generator.get_generated_chunks_root().get_child_count() @@ -362,6 +353,8 @@ func _validate_elevated_cliff_feature( &"chunk_0015", &"chunk_0017", &"chunk_0018", + &"chunk_0019", + &"chunk_0020", ] var has_coastal_feature := false for record: Dictionary in generator.placement_records(): @@ -375,6 +368,8 @@ func _validate_elevated_cliff_feature( &"chunk_0015", &"chunk_0017", &"chunk_0018", + &"chunk_0019", + &"chunk_0020", ]: continue var coordinate: Vector2i = record.get("coordinate", Vector2i.ZERO) @@ -482,6 +477,8 @@ func _validate_elevated_cliff_feature( &"chunk_0015", &"chunk_0017", &"chunk_0018", + &"chunk_0019", + &"chunk_0020", ]: continue if stable_id in [&"chunk_0014", &"chunk_0015"]: @@ -491,14 +488,27 @@ func _validate_elevated_cliff_feature( 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 transition_base_mesh := &"chunk_0000" + if stable_id in [&"chunk_0017", &"chunk_0018"]: + transition_base_mesh = &"chunk_0007" + elif stable_id in [&"chunk_0019", &"chunk_0020"]: + transition_base_mesh = &"chunk_0008" var base_mesh := TerrainChunkAnalyzer.find_primary_mesh( base_layer, - &"chunk_0005" if is_transition else &"chunk_0000", + transition_base_mesh, ) var overlay_mesh := TerrainChunkAnalyzer.find_primary_mesh( overlay, - &"chunk_0015" if is_transition else stable_id, + ( + &"chunk_0015" + if stable_id in [ + &"chunk_0017", + &"chunk_0018", + &"chunk_0019", + &"chunk_0020", + ] + else stable_id + ), ) assert(base_mesh != null and base_mesh.mesh != null) assert(overlay_mesh != null and overlay_mesh.mesh != null) diff --git a/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd b/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd index fbd78c1..0d35543 100644 --- a/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd +++ b/tests/manual/terrain_chunk_generator/terrain_chunk_compatibility_report.gd @@ -113,6 +113,7 @@ func _make_generator() -> TerrainChunkGenerator: [ "chunk_spawn", "chunk_0001", + "chunk_0002", "chunk_0003", "chunk_0004", ] diff --git a/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn b/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn index b29d4f8..df3a835 100644 --- a/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn +++ b/tests/manual/terrain_chunk_generator/terrain_chunk_generator_test.tscn @@ -26,7 +26,7 @@ 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_0003", "chunk_0004") +required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004") grass_sand_smoothing_enabled = true elevated_cliff_feature_enabled = true diff --git a/tests/terrain_chunk_generator_validation.gd b/tests/terrain_chunk_generator_validation.gd index 5aa7219..591467b 100644 --- a/tests/terrain_chunk_generator_validation.gd +++ b/tests/terrain_chunk_generator_validation.gd @@ -23,11 +23,14 @@ const EXPECTED_IDS: Array[String] = [ "chunk_0016", "chunk_0017", "chunk_0018", + "chunk_0019", + "chunk_0020", "chunk_spawn", ] const REQUIRED_IDS: Array[String] = [ "chunk_spawn", "chunk_0001", + "chunk_0002", "chunk_0003", "chunk_0004", ] @@ -54,6 +57,8 @@ const EXPECTED_VARIANT_COUNTS: Dictionary[StringName, int] = { &"chunk_0016": 4, &"chunk_0017": 4, &"chunk_0018": 4, + &"chunk_0019": 4, + &"chunk_0020": 4, &"chunk_spawn": 1, } @@ -101,6 +106,8 @@ func _validate_catalog() -> void: 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 cliff_beach_transition_right := CATALOG.definition_for_id(&"chunk_0019") + var cliff_beach_transition_left := CATALOG.definition_for_id(&"chunk_0020") 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") @@ -122,6 +129,8 @@ func _validate_catalog() -> void: and cliff_sea_corner != null and cliff_sea_transition_right != null and cliff_sea_transition_left != null + and cliff_beach_transition_right != null + and cliff_beach_transition_left != null and stream_variant != null and cliff_top != null and cliff_corner != null @@ -145,6 +154,8 @@ func _validate_catalog() -> void: and cliff_sea_corner != null and cliff_sea_transition_right != null and cliff_sea_transition_left != null + and cliff_beach_transition_right != null + and cliff_beach_transition_left != null and stream_variant != null and cliff_top != null and cliff_corner != null @@ -168,8 +179,12 @@ func _validate_catalog() -> void: ( "sand" in sand.required_neighbor_tags and "coast" in sand.required_neighbor_tags + and sand.maximum_boundary_distance == 1 + ), + ( + "Every flat-sand chunk must remain attached to sand or authored " + + "coast within the first inland terrain ring." ), - "Every flat-sand chunk must remain attached to sand or authored coast.", ) _check( spawn.minimum_required_neighbors == 3, @@ -270,6 +285,7 @@ func _validate_catalog() -> void: _check( grass_sand_diagonal.must_be_interior and not grass_sand_diagonal.participates_in_base_solver + and grass_sand_diagonal.maximum_boundary_distance == 2 and "mixed_surface" in grass_sand_diagonal.tags, ( "The grass-sand diagonal must remain an inland post-process " @@ -355,7 +371,7 @@ func _validate_catalog() -> void: _check( transition.overlay_only and transition.base_layer_scene != null - and transition.base_layer_mesh_name == &"chunk_0005" + and transition.base_layer_mesh_name == &"chunk_0007" and not transition.must_be_interior and transition.prefers_map_boundary and "transition" in transition.tags, @@ -371,11 +387,37 @@ func _validate_catalog() -> void: "The right coastal transition must align its south-facing base coast.", ) _check( - cliff_sea_transition_left.base_layer_quarter_turns == 0 + cliff_sea_transition_left.base_layer_quarter_turns == 1 and cliff_sea_transition_left.ocean_facing_edges == (1 << int(TerrainChunkTopology.Edge.EAST)), "The left coastal transition must align its east-facing base coast.", ) + for transition: TerrainChunkDefinition in [ + cliff_beach_transition_right, + cliff_beach_transition_left, + ]: + _check( + transition.overlay_only + and transition.base_layer_scene != null + and transition.base_layer_mesh_name == &"chunk_0008" + and not transition.must_be_interior + and transition.prefers_map_boundary + and "sand" in transition.tags + and "transition" in transition.tags, + ( + "Each raised beach endpoint must close over an authored " + + "level-one beach corner." + ), + ) + _check( + cliff_beach_transition_right.base_layer_quarter_turns == 3 + and cliff_beach_transition_right.ocean_facing_edges + == (1 << int(TerrainChunkTopology.Edge.SOUTH)) + and cliff_beach_transition_left.base_layer_quarter_turns == 1 + and cliff_beach_transition_left.ocean_facing_edges + == (1 << int(TerrainChunkTopology.Edge.EAST)), + "The raised beach endpoint corners must face their open coastline.", + ) _check( cliff_sea_edge.ocean_facing_edges == (1 << int(TerrainChunkTopology.Edge.EAST)), @@ -1066,6 +1108,8 @@ func _validate_authored_rotations(generator: TerrainChunkGenerator) -> void: &"chunk_0015", &"chunk_0017", &"chunk_0018", + &"chunk_0019", + &"chunk_0020", ]: for quarter_turns: int in 4: var coast_variant := _find_variant( @@ -1117,8 +1161,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)) == 77, - "The current catalog must expose all 77 authored rotations.", + int(summary.get("variant_count", 0)) == 85, + "The current catalog must expose all 85 authored rotations.", ) _check( int(summary.get("solver_variant_count", 0)) diff --git a/tests/world_layout_validation.gd b/tests/world_layout_validation.gd index 63f03ae..86bb00a 100644 --- a/tests/world_layout_validation.gd +++ b/tests/world_layout_validation.gd @@ -86,6 +86,7 @@ func _validate_world_switching() -> void: assert(world.get_world_layout() == WorldLayout.GENERATED) assert(world.get_fishing_shop() != null) assert(world.get_player_storage() != null) + _validate_world_boundary_clearance(world) assert(world.activate_world(WorldLayout.STARTER_ISLAND, 13579)) assert(world.get_world_layout() == WorldLayout.STARTER_ISLAND) assert(world.get_generation_seed() == 13579) @@ -103,6 +104,26 @@ func _validate_world_switching() -> void: await process_frame +func _validate_world_boundary_clearance(world: TestWorld) -> void: + var active_region := world.get("_active_region") as WorldRegion + assert(active_region != null) + var half := active_region.get_playable_half_extents() + var north := world.get_node("WorldBounds/North") as StaticBody3D + var south := world.get_node("WorldBounds/South") as StaticBody3D + var west := world.get_node("WorldBounds/West") as StaticBody3D + var east := world.get_node("WorldBounds/East") as StaticBody3D + var fishing_defaults := FishingSpot.new() + assert( + TestWorld.WORLD_BOUNDARY_SHORELINE_CLEARANCE + > fishing_defaults.maximum_cast_distance + 1.0 + ) + fishing_defaults.free() + assert(north.position.z - half.y >= 18.0) + assert(-south.position.z - half.y >= 18.0) + assert(-west.position.x - half.x >= 18.0) + assert(east.position.x - half.x >= 18.0) + + func _validate_network_metadata() -> void: var hello: Dictionary = NetworkProtocol.make_server_hello( true, diff --git a/world/generation/chunks/definitions/chunk_0001.tres b/world/generation/chunks/definitions/chunk_0001.tres index 516ea38..7ee7c2e 100644 --- a/world/generation/chunks/definitions/chunk_0001.tres +++ b/world/generation/chunks/definitions/chunk_0001.tres @@ -16,3 +16,4 @@ allowed_neighbor_tags = PackedStringArray("sand", "grass") required_neighbor_tags = PackedStringArray("sand", "coast") minimum_required_neighbors = 1 preferred_neighbor_tags = PackedStringArray("coast", "sand") +maximum_boundary_distance = 1 diff --git a/world/generation/chunks/definitions/chunk_0013.tres b/world/generation/chunks/definitions/chunk_0013.tres index 22b6061..22dfcd8 100644 --- a/world/generation/chunks/definitions/chunk_0013.tres +++ b/world/generation/chunks/definitions/chunk_0013.tres @@ -26,3 +26,4 @@ south_surface_tags = PackedStringArray("grass") west_surface_tags = PackedStringArray("sand") preferred_neighbor_tags = PackedStringArray("grass", "sand", "transition") must_be_interior = true +maximum_boundary_distance = 2 diff --git a/world/generation/chunks/definitions/chunk_0017.tres b/world/generation/chunks/definitions/chunk_0017.tres index f7552ea..f1300dc 100644 --- a/world/generation/chunks/definitions/chunk_0017.tres +++ b/world/generation/chunks/definitions/chunk_0017.tres @@ -2,7 +2,7 @@ [ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] [ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0015.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0005.glb" id="3_base_coast"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0007.glb" id="3_base_coast"] [resource] script = ExtResource("1_definition") @@ -11,7 +11,7 @@ label = "second-tier grass sea transition right" packed_scene = ExtResource("2_scene") primary_mesh_name = &"chunk_0015" base_layer_scene = ExtResource("3_base_coast") -base_layer_mesh_name = &"chunk_0005" +base_layer_mesh_name = &"chunk_0007" base_layer_quarter_turns = 3 overlay_only = true selection_weight = 0.12 diff --git a/world/generation/chunks/definitions/chunk_0018.tres b/world/generation/chunks/definitions/chunk_0018.tres index f15421b..c41424f 100644 --- a/world/generation/chunks/definitions/chunk_0018.tres +++ b/world/generation/chunks/definitions/chunk_0018.tres @@ -2,7 +2,7 @@ [ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] [ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0015.glb" id="2_scene"] -[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0005.glb" id="3_base_coast"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0007.glb" id="3_base_coast"] [resource] script = ExtResource("1_definition") @@ -11,7 +11,8 @@ label = "second-tier grass sea transition left" packed_scene = ExtResource("2_scene") primary_mesh_name = &"chunk_0015" base_layer_scene = ExtResource("3_base_coast") -base_layer_mesh_name = &"chunk_0005" +base_layer_mesh_name = &"chunk_0007" +base_layer_quarter_turns = 1 overlay_only = true selection_weight = 0.12 maximum_placements = 8 diff --git a/world/generation/chunks/definitions/chunk_0019.tres b/world/generation/chunks/definitions/chunk_0019.tres new file mode 100644 index 0000000..dd7db5b --- /dev/null +++ b/world/generation/chunks/definitions/chunk_0019.tres @@ -0,0 +1,29 @@ +[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] + +[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0015.glb" id="2_scene"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0008.glb" id="3_base_coast"] + +[resource] +script = ExtResource("1_definition") +stable_id = &"chunk_0019" +label = "second-tier beach sea transition right" +packed_scene = ExtResource("2_scene") +primary_mesh_name = &"chunk_0015" +base_layer_scene = ExtResource("3_base_coast") +base_layer_mesh_name = &"chunk_0008" +base_layer_quarter_turns = 3 +overlay_only = true +selection_weight = 0.12 +maximum_placements = 8 +tags = PackedStringArray("land", "walkable", "sand", "coast", "cliff", "corner", "transition", "elevation_2", "elevation_2_border", "elevation_2_sea_edge") +allowed_neighbor_tags = PackedStringArray("elevation_2") +north_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge", "elevation_2_border") +east_allowed_neighbor_tags = PackedStringArray("sand") +west_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge", "elevation_2_border") +required_neighbor_tags = PackedStringArray("elevation_2_border") +minimum_required_neighbors = 2 +preferred_neighbor_tags = PackedStringArray("elevation_2", "coast") +prefers_map_boundary = true +ocean_facing_edges = 4 +buried_cliff_seam_edges = 9 diff --git a/world/generation/chunks/definitions/chunk_0020.tres b/world/generation/chunks/definitions/chunk_0020.tres new file mode 100644 index 0000000..4014c5b --- /dev/null +++ b/world/generation/chunks/definitions/chunk_0020.tres @@ -0,0 +1,29 @@ +[gd_resource type="Resource" script_class="TerrainChunkDefinition" load_steps=4 format=3] + +[ext_resource type="Script" path="res://world/generation/terrain_chunk_definition.gd" id="1_definition"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0015.glb" id="2_scene"] +[ext_resource type="PackedScene" path="res://world/generation/chunks/assets/chunk_0008.glb" id="3_base_coast"] + +[resource] +script = ExtResource("1_definition") +stable_id = &"chunk_0020" +label = "second-tier beach sea transition left" +packed_scene = ExtResource("2_scene") +primary_mesh_name = &"chunk_0015" +base_layer_scene = ExtResource("3_base_coast") +base_layer_mesh_name = &"chunk_0008" +base_layer_quarter_turns = 1 +overlay_only = true +selection_weight = 0.12 +maximum_placements = 8 +tags = PackedStringArray("land", "walkable", "sand", "coast", "cliff", "corner", "transition", "elevation_2", "elevation_2_border", "elevation_2_sea_edge") +allowed_neighbor_tags = PackedStringArray("elevation_2") +north_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge", "elevation_2_border") +south_allowed_neighbor_tags = PackedStringArray("sand") +west_allowed_neighbor_tags = PackedStringArray("elevation_2_sea_edge", "elevation_2_border") +required_neighbor_tags = PackedStringArray("elevation_2_border") +minimum_required_neighbors = 2 +preferred_neighbor_tags = PackedStringArray("elevation_2", "coast") +prefers_map_boundary = true +ocean_facing_edges = 2 +buried_cliff_seam_edges = 9 diff --git a/world/generation/chunks/terrain_chunk_catalog.tres b/world/generation/chunks/terrain_chunk_catalog.tres index 1b3cdf9..b27601a 100644 --- a/world/generation/chunks/terrain_chunk_catalog.tres +++ b/world/generation/chunks/terrain_chunk_catalog.tres @@ -1,4 +1,4 @@ -[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=23 format=3] +[gd_resource type="Resource" script_class="TerrainChunkCatalog" load_steps=25 format=3] [ext_resource type="Script" path="res://world/generation/terrain_chunk_catalog.gd" id="1_catalog"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0000.tres" id="2_grass"] @@ -22,8 +22,10 @@ [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0016.tres" id="20_stream_variant"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0017.tres" id="21_cliff_sea_transition_right"] [ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0018.tres" id="22_cliff_sea_transition_left"] +[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0019.tres" id="23_cliff_beach_transition_right"] +[ext_resource type="Resource" path="res://world/generation/chunks/definitions/chunk_0020.tres" id="24_cliff_beach_transition_left"] [resource] script = ExtResource("1_catalog") chunk_size = 10.0 -definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("20_stream_variant"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("12_beach_ocean_corner"), ExtResource("17_grass_sand_diagonal"), ExtResource("13_cliff_top"), ExtResource("14_cliff_corner"), ExtResource("15_cliff_edge"), ExtResource("16_cliff_ramp"), ExtResource("18_cliff_sea_edge"), ExtResource("19_cliff_sea_corner"), ExtResource("21_cliff_sea_transition_right"), ExtResource("22_cliff_sea_transition_left"), ExtResource("8_spawn")]) +definitions = Array[ExtResource("7_definition_script")]([ExtResource("2_grass"), ExtResource("3_sand"), ExtResource("4_beach"), ExtResource("5_stream"), ExtResource("20_stream_variant"), ExtResource("6_pond"), ExtResource("9_grass_ocean_edge"), ExtResource("10_grass_beach_transition"), ExtResource("11_grass_ocean_corner"), ExtResource("12_beach_ocean_corner"), ExtResource("17_grass_sand_diagonal"), ExtResource("13_cliff_top"), ExtResource("14_cliff_corner"), ExtResource("15_cliff_edge"), ExtResource("16_cliff_ramp"), ExtResource("18_cliff_sea_edge"), ExtResource("19_cliff_sea_corner"), ExtResource("21_cliff_sea_transition_right"), ExtResource("22_cliff_sea_transition_left"), ExtResource("23_cliff_beach_transition_right"), ExtResource("24_cliff_beach_transition_left"), ExtResource("8_spawn")]) diff --git a/world/generation/generated_world_region.tscn b/world/generation/generated_world_region.tscn index dec37ae..3b81a39 100644 --- a/world/generation/generated_world_region.tscn +++ b/world/generation/generated_world_region.tscn @@ -36,7 +36,7 @@ generation_seed = 13001 generate_on_ready = false build_collision = true force_center_chunk_id = &"chunk_spawn" -required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0003", "chunk_0004") +required_chunk_ids = PackedStringArray("chunk_spawn", "chunk_0001", "chunk_0002", "chunk_0003", "chunk_0004") grass_sand_smoothing_enabled = true elevated_cliff_feature_enabled = true required_chunk_weight_multiplier = 64.0 diff --git a/world/generation/terrain_chunk_definition.gd b/world/generation/terrain_chunk_definition.gd index 01e7255..ae8feaf 100644 --- a/world/generation/terrain_chunk_definition.gd +++ b/world/generation/terrain_chunk_definition.gd @@ -69,6 +69,11 @@ var allowed_rotation_mask := 15 ## Interior-only pieces may not occupy any outer grid cell. Coastal elevated ## pieces use separate authored definitions rather than weakening this rule. @export var must_be_interior := false +## Optional maximum cardinal distance from the closest map edge. Negative +## values allow the definition anywhere. This keeps shoreline materials such +## as flat sand in a deliberate coastal band without baking a particular +## stable ID into the generator. +@export_range(-1, 1024, 1) var maximum_boundary_distance := -1 ## Coastal transition pieces should normally migrate toward the generated ## region's perimeter while remaining legal in the interior. @export var prefers_map_boundary := false diff --git a/world/generation/terrain_chunk_generator.gd b/world/generation/terrain_chunk_generator.gd index c8ab4e2..2e6b61c 100644 --- a/world/generation/terrain_chunk_generator.gd +++ b/world/generation/terrain_chunk_generator.gd @@ -45,6 +45,9 @@ const MAX_PACKED_SOLVER_VARIANTS := 62 @export var elevated_cliff_sea_transition_right_chunk_id: StringName = &"chunk_0017" @export var elevated_cliff_sea_transition_left_chunk_id: StringName = &"chunk_0018" @export var elevated_cliff_coast_base_chunk_id: StringName = &"chunk_0005" +@export var elevated_cliff_beach_base_chunk_id: StringName = &"chunk_0002" +@export var elevated_cliff_beach_transition_right_chunk_id: StringName = &"chunk_0019" +@export var elevated_cliff_beach_transition_left_chunk_id: StringName = &"chunk_0020" @export_range(0.0, 1.0, 0.05) var elevated_cliff_ramp_chance := 1.0 ## A smaller third tier reuses the existing corner geometry at the authored ## two-meter level interval. It is nested into the lower cliff assembly rather @@ -1232,6 +1235,9 @@ func _prepare_elevated_feature_region() -> bool: elevated_cliff_sea_transition_right_chunk_id, elevated_cliff_sea_transition_left_chunk_id, elevated_cliff_coast_base_chunk_id, + elevated_cliff_beach_base_chunk_id, + elevated_cliff_beach_transition_right_chunk_id, + elevated_cliff_beach_transition_left_chunk_id, ]: if catalog.definition_for_id(stable_id) == null: push_error("Elevated cliff feature references missing chunk %s." % stable_id) @@ -1429,6 +1435,10 @@ func _apply_grass_sand_smoothing() -> bool: ) if ( diagonal != null + and _variant_respects_ocean_boundary( + diagonal, + coordinate, + ) and _candidate_matches_placed_neighbors(diagonal, coordinate) ): candidates.append({"index": index, "variant": diagonal}) @@ -1495,16 +1505,21 @@ func _apply_elevated_feature() -> bool: ELEVATED_FEATURE_RADIUS, feature_random, ) + var grass_coast_count := int( + _placement_counts.get(elevated_cliff_coast_base_chunk_id, 0) + ) + var beach_coast_count := int( + _placement_counts.get(elevated_cliff_beach_base_chunk_id, 0) + ) if ( _secondary_elevated_feature_center.x >= 0 - and int( - _placement_counts.get(elevated_cliff_coast_base_chunk_id, 0) - ) > 0 + and (grass_coast_count > 0 or beach_coast_count > 0) ): placements.append_array( _coastal_elevated_feature_placement_specs( _secondary_elevated_feature_center, feature_random, + beach_coast_count > grass_coast_count, ) ) for spec: Dictionary in placements: @@ -1613,6 +1628,7 @@ func _elevated_feature_placement_specs( func _coastal_elevated_feature_placement_specs( center: Vector2i, feature_random: RandomNumberGenerator, + use_beach_base: bool, ) -> Array[Dictionary]: var placements := _elevated_feature_placement_specs( center, @@ -1631,6 +1647,7 @@ func _coastal_elevated_feature_placement_specs( spec["id"] = _coastal_transition_id( coordinate, int(spec["turns"]), + use_beach_base, ) elif outside_edge_count == 1 and ( spec["id"] == elevated_cliff_edge_chunk_id @@ -1643,11 +1660,20 @@ func _coastal_elevated_feature_placement_specs( func _coastal_transition_id( coordinate: Vector2i, quarter_turns: int, + use_beach_base: bool = false, ) -> StringName: - for stable_id: StringName in [ - elevated_cliff_sea_transition_right_chunk_id, - elevated_cliff_sea_transition_left_chunk_id, - ]: + var transition_ids: Array[StringName] = [] + if use_beach_base: + transition_ids.assign([ + elevated_cliff_beach_transition_right_chunk_id, + elevated_cliff_beach_transition_left_chunk_id, + ]) + else: + transition_ids.assign([ + elevated_cliff_sea_transition_right_chunk_id, + elevated_cliff_sea_transition_left_chunk_id, + ]) + for stable_id: StringName in transition_ids: var definition := catalog.definition_for_id(stable_id) var variant := _authored_variant(definition, quarter_turns) if ( @@ -1761,6 +1787,15 @@ func _variant_respects_ocean_boundary( variant: TerrainChunkVariant, coordinate: Vector2i, ) -> bool: + var maximum_boundary_distance := ( + variant.definition.maximum_boundary_distance + ) + if ( + maximum_boundary_distance >= 0 + and _distance_from_map_boundary(coordinate) + > maximum_boundary_distance + ): + return false var ocean_edges := variant.rotated_edge_mask( variant.definition.ocean_facing_edges ) @@ -2163,6 +2198,16 @@ func _coordinate_is_on_boundary(coordinate: Vector2i) -> bool: ) +func _distance_from_map_boundary(coordinate: Vector2i) -> int: + return mini( + mini(coordinate.x, coordinate.y), + mini( + grid_size.x - 1 - coordinate.x, + grid_size.y - 1 - coordinate.y, + ), + ) + + func _neighbor_requirement_validation_error( layout: Array[TerrainChunkVariant], ) -> String: diff --git a/world/test_world.gd b/world/test_world.gd index 0d99e45..af75018 100644 --- a/world/test_world.gd +++ b/world/test_world.gd @@ -14,6 +14,10 @@ const GENERATED_REGION_SCENE: PackedScene = preload( const STARTER_ISLAND_REGION_SCENE: PackedScene = preload( "res://world/regions/starter_island_region.tscn" ) +## Keep the player boundary beyond the longest fishing cast. The boundary is +## solid terrain to character movement and therefore also blocks cast-arc +## probes when it sits directly against the authored shoreline. +const WORLD_BOUNDARY_SHORELINE_CLEARANCE := 18.0 @onready var _regions_root: Node3D = $Regions @onready var _active_region: WorldRegion = _find_active_region() @@ -185,7 +189,7 @@ func _configure_world_coverage() -> void: if _active_region == null: return var half: Vector2 = _active_region.get_playable_half_extents() - var wall_margin := 2.0 + var wall_margin := WORLD_BOUNDARY_SHORELINE_CLEARANCE var wall_height := 14.0 var bounds_root := $WorldBounds as Node3D var north_south_size := Vector3(