fix: close generated coastlines and extend cast bounds

This commit is contained in:
Alexander Sellite 2026-08-21 22:12:14 -04:00
parent 2c17b7b377
commit f85e9bceeb
16 changed files with 250 additions and 57 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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")])

View file

@ -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

View file

@ -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

View file

@ -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:

View file

@ -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(