fix(tools): canyons impose a floor material through rock outcrops; tighten showcase contracts
Review findings: the mesa forced rock onto the canyon bed, two runners declared the wrong TEST_COUNT, and the escarpment check could pass on the slope alone. Canyon gains floor_material, the showcase is re-baked with sand beds, the contract measures the step across the crest band, counts distinct ledges, and probes the crevasse against the open plateau. Degenerate recipe parameters now fail an assert. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
abffa737ec
commit
d91e6bd02f
8 files changed
+73
-36
No files matched your search
Binary file not shown.
Binary file not shown.
|
Before Width: | Height: | Size: 104 KiB After Width: | Height: | Size: 104 KiB |
@@ -82,7 +82,7 @@ ou `smoothstep`) et **mono-segment**. Il manque :
|
|||||||
|
|
||||||
| Primitive | Paramètres | Forme |
|
| Primitive | Paramètres | Forme |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `Canyon` | `path: PackedVector2Array`, `half_width_m`, `depth_m`, `wall_softness_m`, `edge_seed`, `edge_amplitude_m` | tranchée à fond plat le long d'une polyligne ; parois `smoothstep` sur `wall_softness_m` ; distance au tracé perturbée par un bruit de contour ; se referme sur une demi-largeur à chaque bout |
|
| `Canyon` | `path: PackedVector2Array`, `half_width_m`, `depth_m`, `wall_softness_m`, `edge_seed`, `edge_amplitude_m`, `floor_material` | tranchée à fond plat le long d'une polyligne ; parois `smoothstep` sur `wall_softness_m` ; distance au tracé perturbée par un bruit de contour ; se referme sur une demi-largeur à chaque bout ; impose `floor_material` (sable en général) sur le fond plat, même à travers une butte rocheuse |
|
||||||
| `Outcrop` | `centre`, `radius_x_m`, `radius_z_m`, `height_m`, `wall_width_m`, `edge_seed`, `edge_amplitude_m`, `rock_top` | butte elliptique à sommet plat, flanc de largeur `wall_width_m`, contour perturbé ; matériau roche partout où elle dépasse 30 % de sa hauteur si `rock_top` |
|
| `Outcrop` | `centre`, `radius_x_m`, `radius_z_m`, `height_m`, `wall_width_m`, `edge_seed`, `edge_amplitude_m`, `rock_top` | butte elliptique à sommet plat, flanc de largeur `wall_width_m`, contour perturbé ; matériau roche partout où elle dépasse 30 % de sa hauteur si `rock_top` |
|
||||||
| `BoulderField` | `centre`, `radius_m`, `seed`, `count`, `boulder_radius_min_m`, `boulder_radius_max_m`, `height_ratio` | `count` demi-ellipsoïdes tirés par `RandomNumberGenerator` seedé dans le disque, combinés par **maximum** ; roche sur toute leur emprise |
|
| `BoulderField` | `centre`, `radius_m`, `seed`, `count`, `boulder_radius_min_m`, `boulder_radius_max_m`, `height_ratio` | `count` demi-ellipsoïdes tirés par `RandomNumberGenerator` seedé dans le disque, combinés par **maximum** ; roche sur toute leur emprise |
|
||||||
| `Escarpment` | `path: PackedVector2Array`, `drop_m`, `softness_m`, `slope_beyond`, `edge_seed`, `edge_amplitude_m` | `Step` le long d'une polyligne : le terrain descend à **droite** du sens de parcours ; ligne de crête perturbée par un bruit de contour |
|
| `Escarpment` | `path: PackedVector2Array`, `drop_m`, `softness_m`, `slope_beyond`, `edge_seed`, `edge_amplitude_m` | `Step` le long d'une polyligne : le terrain descend à **droite** du sens de parcours ; ligne de crête perturbée par un bruit de contour |
|
||||||
@@ -129,7 +129,7 @@ Plan de masse (nord en haut) :
|
|||||||
| 55°, gradins 4 m, fond ridé entre buttes |
|
| 55°, gradins 4 m, fond ridé entre buttes |
|
||||||
| [départ] |
|
| [départ] |
|
||||||
| SW : chaos de blocs SE : éperons |
|
| SW : chaos de blocs SE : éperons |
|
||||||
| 80 blocs r 2–6 m sur deux buttes |
|
| 90 blocs r 2–6 m sur deux buttes |
|
||||||
| sable ridé alvéolées, |
|
| sable ridé alvéolées, |
|
||||||
| chenal 20 m |
|
| chenal 20 m |
|
||||||
| ====== falaise sinueuse (Escarpment) ===== |
|
| ====== falaise sinueuse (Escarpment) ===== |
|
||||||
@@ -148,13 +148,15 @@ Plan de masse (nord en haut) :
|
|||||||
gradins : au moins deux niveaux plats ; un fond plat ne bouge pas) ;
|
gradins : au moins deux niveaux plats ; un fond plat ne bouge pas) ;
|
||||||
cuisson du Banc du Vigilant identique à l'artefact versionné.
|
cuisson du Banc du Vigilant identique à l'artefact versionné.
|
||||||
- **Contrat (`run_site_content_tests.gd`, section showcase)** : canyon
|
- **Contrat (`run_site_content_tests.gd`, section showcase)** : canyon
|
||||||
(profondeur ≥ 20 m sous la mesa, fond plat à ± 1 m sur 10 m, paroi ≥ 60°
|
(profondeur ≥ 18 m sous le sommet bruité de la mesa, fond de sable plat à
|
||||||
quelque part sur une transversale, ≥ 2 replats de gradin) ; crevasse
|
± 1 m sur 10 m, paroi ≥ 60° quelque part sur une transversale, ≥ 2 replats
|
||||||
(fond ≥ 16 m sous les bords, largeur au fond ≤ 8 m) ; chaos (roche sur un
|
distincts) ; crevasse (fond de sable ≥ 16 m sous le plateau ouvert, 3 à 8 m
|
||||||
|
de large à 10 m sous le plateau) ; chaos (roche sur un
|
||||||
bloc, sable entre deux blocs, ≥ 40 cellules de roche distinctes… mesuré par
|
bloc, sable entre deux blocs, ≥ 40 cellules de roche distinctes… mesuré par
|
||||||
fraction de roche dans le disque) ; éperons (chenal plus bas que les deux
|
fraction de roche dans le disque) ; éperons (chenal plus bas que les deux
|
||||||
buttes, roche sur chaque butte) ; falaise (drop ≥ 20 m entre deux points de
|
buttes, roche sur chaque butte) ; falaise (marche ≥ 18 m mesurée dans la
|
||||||
part et d'autre) ; bornes et profondeurs globales.
|
seule bande de crête, pour que la pente en contrebas ne masque pas une
|
||||||
|
marche absente) ; bornes et profondeurs globales.
|
||||||
- **Intégration (`run_site_terrain_integration.gd`)** : chargement du showcase
|
- **Intégration (`run_site_terrain_integration.gd`)** : chargement du showcase
|
||||||
sous 3 s, rayon dans le canyon plus bas que sur la mesa, scan de référence
|
sous 3 s, rayon dans le canyon plus bas que sur la mesa, scan de référence
|
||||||
lisant roche et sable.
|
lisant roche et sable.
|
||||||
|
|||||||
@@ -15,20 +15,23 @@ const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.g
|
|||||||
const SITE_PATH := "res://content/sites/zone1_vigilant.tres"
|
const SITE_PATH := "res://content/sites/zone1_vigilant.tres"
|
||||||
const SHOWCASE_PATH := "res://content/sites/terrain_showcase.tres"
|
const SHOWCASE_PATH := "res://content/sites/terrain_showcase.tres"
|
||||||
# Showcase probe positions (docs/briefs/terrain-varied-landforms.md §5). The canyon
|
# Showcase probe positions (docs/briefs/terrain-varied-landforms.md §5). The canyon
|
||||||
# transect crosses its first leg at right angles; the crevasse transect is the
|
# transect crosses its second leg at right angles; the crevasse transect is the
|
||||||
# z = -120 row between the two buttes.
|
# z = -120 row between the two buttes, compared with the open plateau on the same row;
|
||||||
|
# the escarpment is probed across its step band at x = 0, where the crest is at z = 217.
|
||||||
const SHOWCASE_MESA_TOP := Vector2(-80.0, -90.0)
|
const SHOWCASE_MESA_TOP := Vector2(-80.0, -90.0)
|
||||||
const SHOWCASE_CANYON_MID := Vector2(-130.0, -135.0)
|
const SHOWCASE_CANYON_MID := Vector2(-130.0, -135.0)
|
||||||
const SHOWCASE_CANYON_AXIS := Vector2(40.0, -30.0)
|
const SHOWCASE_CANYON_AXIS := Vector2(40.0, -30.0)
|
||||||
const SHOWCASE_CREVASSE_ROW_Z := -120.0
|
const SHOWCASE_CREVASSE_ROW_Z := -120.0
|
||||||
const SHOWCASE_CREVASSE_SHELF_X := 118.0
|
const SHOWCASE_CREVASSE_PLAIN_X := 0.0
|
||||||
const SHOWCASE_CREVASSE_FLOOR_X := 124.0
|
const SHOWCASE_CREVASSE_FLOOR_X := 124.0
|
||||||
const SHOWCASE_BOULDER_FIELD_CENTRE := Vector2(-120.0, 110.0)
|
const SHOWCASE_BOULDER_FIELD_CENTRE := Vector2(-120.0, 110.0)
|
||||||
const SHOWCASE_BOULDER_FIELD_RADIUS_M := 75.0
|
const SHOWCASE_BOULDER_FIELD_RADIUS_M := 75.0
|
||||||
const SHOWCASE_WEST_SPUR := Vector2(80.0, 120.0)
|
const SHOWCASE_WEST_SPUR := Vector2(80.0, 120.0)
|
||||||
const SHOWCASE_CHANNEL := Vector2(125.0, 120.0)
|
const SHOWCASE_CHANNEL := Vector2(125.0, 120.0)
|
||||||
const SHOWCASE_EAST_SPUR := Vector2(170.0, 125.0)
|
const SHOWCASE_EAST_SPUR := Vector2(170.0, 125.0)
|
||||||
const STEEP_WALL_RISE_PER_M := 1.7 # tan(60 degrees)
|
const SHOWCASE_ESCARPMENT_CREST_Z := 217.0
|
||||||
|
const SHOWCASE_ESCARPMENT_BAND_M := 14.0 # softness 6 m plus 8 m of outline noise
|
||||||
|
const STEEP_WALL_RISE_PER_M := 1.73 # tan(60 degrees)
|
||||||
const LEDGE_RISE_PER_M := 0.35
|
const LEDGE_RISE_PER_M := 0.35
|
||||||
const ROUTE_CLEARANCE_M := 3.0
|
const ROUTE_CLEARANCE_M := 3.0
|
||||||
const MINIMUM_LANDMARK_SPACING_M := 60.0
|
const MINIMUM_LANDMARK_SPACING_M := 60.0
|
||||||
@@ -36,7 +39,7 @@ const MAXIMUM_ANCHOR_TO_WRECK_M := 320.0
|
|||||||
const MAXIMUM_WRECK_SPACING_M := 260.0
|
const MAXIMUM_WRECK_SPACING_M := 260.0
|
||||||
const HULL_HALF_LENGTH_M := 14.0
|
const HULL_HALF_LENGTH_M := 14.0
|
||||||
const HULL_SEABED_TOLERANCE_M := 2.0
|
const HULL_SEABED_TOLERANCE_M := 2.0
|
||||||
const TEST_COUNT := 52
|
const TEST_COUNT := 54
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
var _failures: Array[String] = []
|
||||||
|
|
||||||
@@ -161,7 +164,7 @@ func _test_showcase_content() -> void:
|
|||||||
return
|
return
|
||||||
_expect(field.cell_size_m() == 1.0 and field.columns() == 513 and field.rows() == 513, "showcase grid is 513 x 513 cells of 1 m")
|
_expect(field.cell_size_m() == 1.0 and field.columns() == 513 and field.rows() == 513, "showcase grid is 513 x 513 cells of 1 m")
|
||||||
_expect(definition.playable_bounds_m.is_equal_approx(Rect2(-220.0, -220.0, 440.0, 440.0)), "showcase playable zone is 440 x 440 m")
|
_expect(definition.playable_bounds_m.is_equal_approx(Rect2(-220.0, -220.0, 440.0, 440.0)), "showcase playable zone is 440 x 440 m")
|
||||||
_expect(field.minimum_elevation_m() >= -80.5 and field.maximum_elevation_m() <= -18.5, "showcase terrain stays between 19 m and 80 m deep")
|
_expect(field.minimum_elevation_m() >= -80.5 and field.maximum_elevation_m() <= -19.0, "showcase terrain stays between 19 m and 80 m deep")
|
||||||
var anchors := definition.placements_of_kind(SitePlacement.Kind.VESSEL_ANCHOR)
|
var anchors := definition.placements_of_kind(SitePlacement.Kind.VESSEL_ANCHOR)
|
||||||
_expect(anchors.size() == 1 and definition.placements.size() == 1, "showcase has a single placement, the mooring beacon")
|
_expect(anchors.size() == 1 and definition.placements.size() == 1, "showcase has a single placement, the mooring beacon")
|
||||||
var spawn := definition.spawn_position_m
|
var spawn := definition.spawn_position_m
|
||||||
@@ -171,6 +174,7 @@ func _test_showcase_content() -> void:
|
|||||||
var mesa_top := field.elevation_at(SHOWCASE_MESA_TOP.x, SHOWCASE_MESA_TOP.y)
|
var mesa_top := field.elevation_at(SHOWCASE_MESA_TOP.x, SHOWCASE_MESA_TOP.y)
|
||||||
var canyon_floor := field.elevation_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y)
|
var canyon_floor := field.elevation_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y)
|
||||||
_expect(mesa_top - canyon_floor >= 18.0, "canyon floor is at least 18 m below the mesa top (%.1f m)" % (mesa_top - canyon_floor))
|
_expect(mesa_top - canyon_floor >= 18.0, "canyon floor is at least 18 m below the mesa top (%.1f m)" % (mesa_top - canyon_floor))
|
||||||
|
_expect(field.material_at(SHOWCASE_CANYON_MID.x, SHOWCASE_CANYON_MID.y) == SonarMaterial.Value.SAND, "canyon floor is sand through the rock mesa")
|
||||||
var axis := SHOWCASE_CANYON_AXIS.normalized()
|
var axis := SHOWCASE_CANYON_AXIS.normalized()
|
||||||
var lowest := INF
|
var lowest := INF
|
||||||
var highest := -INF
|
var highest := -INF
|
||||||
@@ -186,23 +190,30 @@ func _test_showcase_content() -> void:
|
|||||||
var point := SHOWCASE_CANYON_MID + normal * float(offset)
|
var point := SHOWCASE_CANYON_MID + normal * float(offset)
|
||||||
transect.append(field.elevation_at(point.x, point.y))
|
transect.append(field.elevation_at(point.x, point.y))
|
||||||
var steepest := 0.0
|
var steepest := 0.0
|
||||||
var ledge_pairs := 0
|
var ledges := 0
|
||||||
|
var on_ledge := false
|
||||||
for index in range(transect.size() - 1):
|
for index in range(transect.size() - 1):
|
||||||
var rise := absf(transect[index + 1] - transect[index])
|
var rise := absf(transect[index + 1] - transect[index])
|
||||||
steepest = maxf(steepest, rise)
|
steepest = maxf(steepest, rise)
|
||||||
var in_wall := transect[index] > canyon_floor + 1.0 and transect[index] < mesa_top - 1.0
|
var in_wall := transect[index] > canyon_floor + 1.0 and transect[index] < mesa_top - 1.0
|
||||||
|
# A ledge is a run of flat metres inside the wall, separated from the next by a riser.
|
||||||
if in_wall and rise < LEDGE_RISE_PER_M:
|
if in_wall and rise < LEDGE_RISE_PER_M:
|
||||||
ledge_pairs += 1
|
if not on_ledge:
|
||||||
_expect(steepest >= STEEP_WALL_RISE_PER_M, "canyon wall is steeper than 60 degrees somewhere (%.1f m per m)" % steepest)
|
ledges += 1
|
||||||
_expect(ledge_pairs >= 2, "canyon wall carries at least two ledges (%d flat metres)" % ledge_pairs)
|
on_ledge = true
|
||||||
|
else:
|
||||||
|
on_ledge = false
|
||||||
|
_expect(steepest >= STEEP_WALL_RISE_PER_M, "canyon wall is steeper than 60 degrees somewhere (%.2f m per m)" % steepest)
|
||||||
|
_expect(ledges >= 2, "canyon walls carry at least two distinct ledges (%d)" % ledges)
|
||||||
|
|
||||||
# NE: crevasse between the buttes.
|
# NE: crevasse between the buttes.
|
||||||
var shelf := field.elevation_at(SHOWCASE_CREVASSE_SHELF_X, SHOWCASE_CREVASSE_ROW_Z)
|
var plain := field.elevation_at(SHOWCASE_CREVASSE_PLAIN_X, SHOWCASE_CREVASSE_ROW_Z)
|
||||||
var crevasse_floor := field.elevation_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z)
|
var crevasse_floor := field.elevation_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z)
|
||||||
_expect(shelf - crevasse_floor >= 16.0, "crevasse floor is at least 16 m below the rim (%.1f m)" % (shelf - crevasse_floor))
|
_expect(plain - crevasse_floor >= 16.0, "crevasse floor is at least 16 m below the open plateau (%.1f m)" % (plain - crevasse_floor))
|
||||||
|
_expect(field.material_at(SHOWCASE_CREVASSE_FLOOR_X, SHOWCASE_CREVASSE_ROW_Z) == SonarMaterial.Value.SAND, "crevasse floor is sand")
|
||||||
var deep_cells := 0
|
var deep_cells := 0
|
||||||
for x in range(100, 146):
|
for x in range(100, 146):
|
||||||
if field.elevation_at(float(x), SHOWCASE_CREVASSE_ROW_Z) < shelf - 10.0:
|
if field.elevation_at(float(x), SHOWCASE_CREVASSE_ROW_Z) < plain - 10.0:
|
||||||
deep_cells += 1
|
deep_cells += 1
|
||||||
_expect(deep_cells >= 3 and deep_cells <= 8, "crevasse is 3 to 8 m wide at 10 m down (%d m)" % deep_cells)
|
_expect(deep_cells >= 3 and deep_cells <= 8, "crevasse is 3 to 8 m wide at 10 m down (%d m)" % deep_cells)
|
||||||
|
|
||||||
@@ -234,10 +245,11 @@ func _test_showcase_content() -> void:
|
|||||||
)
|
)
|
||||||
_expect(field.material_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y + 30.0) == SonarMaterial.Value.SAND, "the channel floor is sand")
|
_expect(field.material_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y + 30.0) == SonarMaterial.Value.SAND, "the channel floor is sand")
|
||||||
|
|
||||||
# S: escarpment.
|
# S: escarpment, measured across its step band only so the slope below cannot
|
||||||
var above := field.elevation_at(0.0, 190.0)
|
# stand in for a missing step (the slope alone would give about 7 m here).
|
||||||
var below := field.elevation_at(0.0, 250.0)
|
var above := field.elevation_at(0.0, SHOWCASE_ESCARPMENT_CREST_Z - SHOWCASE_ESCARPMENT_BAND_M)
|
||||||
_expect(above - below >= 20.0, "the escarpment drops at least 20 m (%.1f m)" % (above - below))
|
var below := field.elevation_at(0.0, SHOWCASE_ESCARPMENT_CREST_Z + SHOWCASE_ESCARPMENT_BAND_M)
|
||||||
|
_expect(above - below >= 18.0, "the escarpment steps down at least 18 m across its crest band (%.1f m)" % (above - below))
|
||||||
|
|
||||||
|
|
||||||
func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> bool:
|
func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> bool:
|
||||||
|
|||||||
@@ -240,7 +240,7 @@ func _test_showcase_site() -> void:
|
|||||||
var mesa_top := _terrain_height(space, terrain, -80.0, -90.0, [])
|
var mesa_top := _terrain_height(space, terrain, -80.0, -90.0, [])
|
||||||
_expect(mesa_top - canyon_floor >= 18.0, "collision rays find the canyon under the mesa (%.1f m vs %.1f m)" % [canyon_floor, mesa_top])
|
_expect(mesa_top - canyon_floor >= 18.0, "collision rays find the canyon under the mesa (%.1f m vs %.1f m)" % [canyon_floor, mesa_top])
|
||||||
|
|
||||||
# Sensor above the canyon looking along its first leg (the acquisition probes along
|
# Sensor above the canyon looking along its second leg (the acquisition probes along
|
||||||
# local +Z): rock walls and sand floor.
|
# local +Z): rock walls and sand floor.
|
||||||
var sensor := Transform3D(Basis.IDENTITY, Vector3(-130.0, -15.0, -135.0))
|
var sensor := Transform3D(Basis.IDENTITY, Vector3(-130.0, -15.0, -135.0))
|
||||||
sensor.basis = Basis.looking_at(-Vector3(40.0, 0.0, -30.0).normalized(), Vector3.UP)
|
sensor.basis = Basis.looking_at(-Vector3(40.0, 0.0, -30.0).normalized(), Vector3.UP)
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
|
|||||||
|
|
||||||
const ZONE1_TERRAIN_PATH := "res://content/sites/zone1_vigilant_terrain.res"
|
const ZONE1_TERRAIN_PATH := "res://content/sites/zone1_vigilant_terrain.res"
|
||||||
|
|
||||||
const TEST_COUNT := 40
|
const TEST_COUNT := 44
|
||||||
|
|
||||||
var _failures: Array[String] = []
|
var _failures: Array[String] = []
|
||||||
|
|
||||||
@@ -121,16 +121,18 @@ func _test_modifiers_and_primitive_materials() -> void:
|
|||||||
func _test_canyon() -> void:
|
func _test_canyon() -> void:
|
||||||
var canyon := SiteTerrainBaker.Canyon.new(
|
var canyon := SiteTerrainBaker.Canyon.new(
|
||||||
PackedVector2Array([Vector2(-20.0, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 20.0)]),
|
PackedVector2Array([Vector2(-20.0, 0.0), Vector2(0.0, 0.0), Vector2(0.0, 20.0)]),
|
||||||
4.0, 10.0, 1.0, 0, 0.0
|
4.0, 10.0, 1.0, 0, 0.0, SonarMaterial.Value.SAND
|
||||||
)
|
)
|
||||||
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 0.0), -10.0), "canyon floor sits at depth on the first leg")
|
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 0.0), -10.0), "canyon floor sits at depth on the first leg")
|
||||||
|
_expect(canyon.material_at(-10.0, 0.0) == SonarMaterial.Value.SAND, "canyon imposes its floor material on the flat floor")
|
||||||
|
_expect(canyon.material_at(-10.0, 4.0) == SiteTerrainBaker.NO_MATERIAL, "canyon leaves its walls to the slope rule")
|
||||||
_expect(is_equal_approx(canyon.elevation_delta(1.5, 10.0), -10.0), "canyon floor is flat across its width on the second leg")
|
_expect(is_equal_approx(canyon.elevation_delta(1.5, 10.0), -10.0), "canyon floor is flat across its width on the second leg")
|
||||||
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 6.0), 0.0), "canyon stops past half width plus softness")
|
_expect(is_equal_approx(canyon.elevation_delta(-10.0, 6.0), 0.0), "canyon stops past half width plus softness")
|
||||||
_expect(is_equal_approx(canyon.elevation_delta(15.0, -15.0), 0.0), "canyon is zero far from the path")
|
_expect(is_equal_approx(canyon.elevation_delta(15.0, -15.0), 0.0), "canyon is zero far from the path")
|
||||||
_expect(is_equal_approx(canyon.elevation_delta(0.0, 20.0), 0.0), "canyon closes at its last vertex")
|
_expect(is_equal_approx(canyon.elevation_delta(0.0, 20.0), 0.0), "canyon closes at its last vertex")
|
||||||
_expect(canyon.elevation_delta(-10.0, 4.0) < -4.0 and canyon.elevation_delta(-10.0, 4.0) > -6.0, "canyon wall is half way down at half width")
|
_expect(canyon.elevation_delta(-10.0, 4.0) < -4.0 and canyon.elevation_delta(-10.0, 4.0) > -6.0, "canyon wall is half way down at half width")
|
||||||
var rough := SiteTerrainBaker.Canyon.new(
|
var rough := SiteTerrainBaker.Canyon.new(
|
||||||
PackedVector2Array([Vector2(-200.0, 0.0), Vector2(200.0, 0.0)]), 8.0, 10.0, 2.0, 7, 3.0
|
PackedVector2Array([Vector2(-200.0, 0.0), Vector2(200.0, 0.0)]), 8.0, 10.0, 2.0, 7, 3.0, SiteTerrainBaker.NO_MATERIAL
|
||||||
)
|
)
|
||||||
var wall_values: Dictionary = {}
|
var wall_values: Dictionary = {}
|
||||||
for x in range(-100, 101, 10):
|
for x in range(-100, 101, 10):
|
||||||
|
|||||||
@@ -170,13 +170,15 @@ class Groove extends Primitive:
|
|||||||
## Flat-floored trench along a polyline. The floor sits depth_m below the surroundings
|
## Flat-floored trench along a polyline. The floor sits depth_m below the surroundings
|
||||||
## wherever the (noise-perturbed) distance to the path is under half_width_m minus
|
## wherever the (noise-perturbed) distance to the path is under half_width_m minus
|
||||||
## wall_softness_m; the walls climb over 2 * wall_softness_m; the trench closes over one
|
## wall_softness_m; the walls climb over 2 * wall_softness_m; the trench closes over one
|
||||||
## half-width at each end.
|
## half-width at each end. floor_material (a SonarMaterial value, or NO_MATERIAL) is
|
||||||
|
## imposed on the flat floor, so a canyon cut through a rock outcrop keeps a sand bed.
|
||||||
class Canyon extends Primitive:
|
class Canyon extends Primitive:
|
||||||
var path: PackedVector2Array
|
var path: PackedVector2Array
|
||||||
var half_width_m: float
|
var half_width_m: float
|
||||||
var depth_m: float
|
var depth_m: float
|
||||||
var wall_softness_m: float
|
var wall_softness_m: float
|
||||||
var edge_amplitude_m: float
|
var edge_amplitude_m: float
|
||||||
|
var floor_material: int
|
||||||
var _noise: FastNoiseLite
|
var _noise: FastNoiseLite
|
||||||
var _length_m: float
|
var _length_m: float
|
||||||
|
|
||||||
@@ -186,17 +188,31 @@ class Canyon extends Primitive:
|
|||||||
p_depth_m: float,
|
p_depth_m: float,
|
||||||
p_wall_softness_m: float,
|
p_wall_softness_m: float,
|
||||||
p_edge_seed: int,
|
p_edge_seed: int,
|
||||||
p_edge_amplitude_m: float
|
p_edge_amplitude_m: float,
|
||||||
|
p_floor_material: int
|
||||||
) -> void:
|
) -> void:
|
||||||
|
assert(p_path.size() >= 2, "a canyon path needs at least two vertices")
|
||||||
|
assert(p_half_width_m > 0.0, "a canyon needs a positive half width")
|
||||||
path = p_path
|
path = p_path
|
||||||
half_width_m = p_half_width_m
|
half_width_m = p_half_width_m
|
||||||
depth_m = p_depth_m
|
depth_m = p_depth_m
|
||||||
wall_softness_m = p_wall_softness_m
|
wall_softness_m = p_wall_softness_m
|
||||||
edge_amplitude_m = p_edge_amplitude_m
|
edge_amplitude_m = p_edge_amplitude_m
|
||||||
|
floor_material = p_floor_material
|
||||||
_noise = edge_noise(p_edge_seed, p_edge_amplitude_m)
|
_noise = edge_noise(p_edge_seed, p_edge_amplitude_m)
|
||||||
_length_m = path_length(p_path)
|
_length_m = path_length(p_path)
|
||||||
|
|
||||||
func elevation_delta(x_m: float, z_m: float) -> float:
|
func elevation_delta(x_m: float, z_m: float) -> float:
|
||||||
|
return -depth_m * _presence(x_m, z_m)
|
||||||
|
|
||||||
|
func material_at(x_m: float, z_m: float) -> int:
|
||||||
|
# Full presence means the flat floor: inside the walls and clear of the end tapers.
|
||||||
|
if floor_material != NO_MATERIAL and _presence(x_m, z_m) >= 0.999:
|
||||||
|
return floor_material
|
||||||
|
return NO_MATERIAL
|
||||||
|
|
||||||
|
## 1 on the flat floor, 0 outside the trench, in between on the walls and end tapers.
|
||||||
|
func _presence(x_m: float, z_m: float) -> float:
|
||||||
var sample := path_sample(Vector2(x_m, z_m), path)
|
var sample := path_sample(Vector2(x_m, z_m), path)
|
||||||
var distance := sample.x + edge_offset(_noise, edge_amplitude_m, x_m, z_m)
|
var distance := sample.x + edge_offset(_noise, edge_amplitude_m, x_m, z_m)
|
||||||
var inside := 1.0 - smoothstep(
|
var inside := 1.0 - smoothstep(
|
||||||
@@ -205,13 +221,14 @@ class Canyon extends Primitive:
|
|||||||
distance
|
distance
|
||||||
)
|
)
|
||||||
var taper := smoothstep(0.0, half_width_m, sample.y) * smoothstep(0.0, half_width_m, _length_m - sample.y)
|
var taper := smoothstep(0.0, half_width_m, sample.y) * smoothstep(0.0, half_width_m, _length_m - sample.y)
|
||||||
return -depth_m * inside * taper
|
return inside * taper
|
||||||
|
|
||||||
|
|
||||||
## Step along a polyline: the terrain drops by drop_m on the right of the travel direction
|
## Step along a polyline: the terrain drops by drop_m on the right of the travel direction
|
||||||
## (south of an eastbound path), over 2 * softness_m, then keeps falling at slope_beyond.
|
## (south of an eastbound path), over 2 * softness_m, then keeps falling at slope_beyond.
|
||||||
## The crest line is roughened by outline noise. Extend the path past the field edges:
|
## The crest line is roughened by outline noise. The path must extend past the field
|
||||||
## beyond its ends the contour wraps around the end vertex.
|
## edges: beyond an end vertex the drop is decided by the half-plane of the last segment,
|
||||||
|
## which leaves a seam along the path's extension.
|
||||||
class Escarpment extends Primitive:
|
class Escarpment extends Primitive:
|
||||||
var path: PackedVector2Array
|
var path: PackedVector2Array
|
||||||
var drop_m: float
|
var drop_m: float
|
||||||
@@ -228,6 +245,7 @@ class Escarpment extends Primitive:
|
|||||||
p_edge_seed: int,
|
p_edge_seed: int,
|
||||||
p_edge_amplitude_m: float
|
p_edge_amplitude_m: float
|
||||||
) -> void:
|
) -> void:
|
||||||
|
assert(p_path.size() >= 2, "an escarpment path needs at least two vertices")
|
||||||
path = p_path
|
path = p_path
|
||||||
drop_m = p_drop_m
|
drop_m = p_drop_m
|
||||||
softness_m = p_softness_m
|
softness_m = p_softness_m
|
||||||
@@ -267,6 +285,7 @@ class Outcrop extends Primitive:
|
|||||||
p_edge_amplitude_m: float,
|
p_edge_amplitude_m: float,
|
||||||
p_rock_top: bool
|
p_rock_top: bool
|
||||||
) -> void:
|
) -> void:
|
||||||
|
assert(p_radius_x_m > 0.0 and p_radius_z_m > 0.0, "an outcrop needs positive radii")
|
||||||
centre = p_centre
|
centre = p_centre
|
||||||
radius_x_m = p_radius_x_m
|
radius_x_m = p_radius_x_m
|
||||||
radius_z_m = p_radius_z_m
|
radius_z_m = p_radius_z_m
|
||||||
@@ -435,6 +454,7 @@ class Strata extends Modifier:
|
|||||||
p_centre: Vector2,
|
p_centre: Vector2,
|
||||||
p_radius_m: float
|
p_radius_m: float
|
||||||
) -> void:
|
) -> void:
|
||||||
|
assert(p_layer_height_m > 0.0, "strata need a positive layer height")
|
||||||
layer_height_m = p_layer_height_m
|
layer_height_m = p_layer_height_m
|
||||||
sharpness = clampf(p_sharpness, 0.0, 0.999)
|
sharpness = clampf(p_sharpness, 0.0, 0.999)
|
||||||
centre = p_centre
|
centre = p_centre
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ extends RefCounted
|
|||||||
## mooring beacon at the centre. Field 512 x 512 m at 1 m, +X east, +Z south.
|
## mooring beacon at the centre. Field 512 x 512 m at 1 m, +X east, +Z south.
|
||||||
|
|
||||||
const SiteTerrainBaker = preload("res://tools/site_terrain_baker.gd")
|
const SiteTerrainBaker = preload("res://tools/site_terrain_baker.gd")
|
||||||
|
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
|
||||||
const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
|
const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
|
||||||
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
|
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd")
|
||||||
const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
|
const SitePlacement = preload("res://game/domain/sites/site_placement.gd")
|
||||||
@@ -59,15 +60,15 @@ static func recipe() -> SiteTerrainBaker.Recipe:
|
|||||||
# modifier (one ledge every 2.8 m across, resolvable at 1 m per cell), rippled
|
# modifier (one ledge every 2.8 m across, resolvable at 1 m per cell), rippled
|
||||||
# sand on the floor.
|
# sand on the floor.
|
||||||
SiteTerrainBaker.Outcrop.new(MESA_CENTRE, 95.0, 85.0, 6.0, 10.0, 101, 6.0, true),
|
SiteTerrainBaker.Outcrop.new(MESA_CENTRE, 95.0, 85.0, 6.0, 10.0, 101, 6.0, true),
|
||||||
SiteTerrainBaker.Canyon.new(PackedVector2Array(CANYON_PATH), CANYON_HALF_WIDTH_M, CANYON_DEPTH_M, 7.0, 102, 2.0),
|
SiteTerrainBaker.Canyon.new(PackedVector2Array(CANYON_PATH), CANYON_HALF_WIDTH_M, CANYON_DEPTH_M, 7.0, 102, 2.0, SonarMaterial.Value.SAND),
|
||||||
SiteTerrainBaker.Ripples.new(Vector2(0.8, -0.6), 4.0, 0.25, Vector2(-130.0, -140.0), 70.0),
|
SiteTerrainBaker.Ripples.new(Vector2(0.8, -0.6), 4.0, 0.25, Vector2(-130.0, -140.0), 70.0),
|
||||||
|
|
||||||
# NE — la Fente: two buttes 8-9 m high with a 6 m wide, 20 m deep slot between them.
|
# NE — la Fente: two buttes 8-9 m high with a 6 m wide, 20 m deep slot between them.
|
||||||
SiteTerrainBaker.Outcrop.new(WEST_BUTTE_CENTRE, 38.0, 70.0, 8.0, 8.0, 201, 5.0, true),
|
SiteTerrainBaker.Outcrop.new(WEST_BUTTE_CENTRE, 38.0, 70.0, 8.0, 8.0, 201, 5.0, true),
|
||||||
SiteTerrainBaker.Outcrop.new(EAST_BUTTE_CENTRE, 38.0, 75.0, 9.0, 8.0, 202, 5.0, true),
|
SiteTerrainBaker.Outcrop.new(EAST_BUTTE_CENTRE, 38.0, 75.0, 9.0, 8.0, 202, 5.0, true),
|
||||||
SiteTerrainBaker.Canyon.new(PackedVector2Array(CREVASSE_PATH), CREVASSE_HALF_WIDTH_M, CREVASSE_DEPTH_M, 1.0, 203, 1.0),
|
SiteTerrainBaker.Canyon.new(PackedVector2Array(CREVASSE_PATH), CREVASSE_HALF_WIDTH_M, CREVASSE_DEPTH_M, 1.0, 203, 1.0, SonarMaterial.Value.SAND),
|
||||||
|
|
||||||
# SW — le Chaos: ninety boulders 2-6 m across plus six coral heads, on rippled sand.
|
# SW — le Chaos: ninety boulders of 2-6 m radius plus six coral heads, on rippled sand.
|
||||||
SiteTerrainBaker.BoulderField.new(BOULDER_FIELD_CENTRE, BOULDER_FIELD_RADIUS_M, 301, 90, 2.0, 6.0, 0.7),
|
SiteTerrainBaker.BoulderField.new(BOULDER_FIELD_CENTRE, BOULDER_FIELD_RADIUS_M, 301, 90, 2.0, 6.0, 0.7),
|
||||||
SiteTerrainBaker.BoulderField.new(Vector2(-100.0, 130.0), 40.0, 302, 6, 6.0, 9.0, 0.6),
|
SiteTerrainBaker.BoulderField.new(Vector2(-100.0, 130.0), 40.0, 302, 6, 6.0, 9.0, 0.6),
|
||||||
SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.3), 3.5, 0.3, BOULDER_FIELD_CENTRE, 110.0),
|
SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.3), 3.5, 0.3, BOULDER_FIELD_CENTRE, 110.0),
|
||||||
|
|||||||
Reference in new issue
Block a user