feat(sites): strata on the Banc du Vigilant drop-off and multi-scale seabed relief

Strata now take a world rectangle, so the 4 m ledges cover the whole break
and talus south of z = 118 m without touching la Saignée or the landmarks.
Roughness gains octave and fractal-type parameters; a SlopeRoughness
modifier roughens rock faces only. Both sites are re-baked with hummocks
from 250 m down to 5 m. The secondary item, the first debris piece and one
safe-route waypoint move a few metres to keep their contracts.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
OlivierandClaude Fable 5.1 committed 2026-10-06 22:50:21 +02:00
1 parent 4af014288b
commit f22a5e69aa
21 files changed
+247 -43

No files matched your search

Binary file not shown.
+10 -10
View File
@@ -9,7 +9,7 @@
script = ExtResource("1_n3xpu")
placement_id = &"prospection_vessel"
kind = 0
position_m = Vector3(-14.154642, -37.198338, 98.39586)
position_m = Vector3(-14.154642, -36.88306, 98.39586)
yaw_radians = -1.380808047881126
footprint_m = Vector3(28, 8, 9)
@@ -17,7 +17,7 @@ footprint_m = Vector3(28, 8, 9)
script = ExtResource("1_n3xpu")
placement_id = &"decoy_yacht"
kind = 1
position_m = Vector3(-30, -25.199738, -150)
position_m = Vector3(-30, -24.996498, -150)
yaw_radians = 1.1
footprint_m = Vector3(12, 3, 4)
@@ -25,7 +25,7 @@ footprint_m = Vector3(12, 3, 4)
script = ExtResource("1_n3xpu")
placement_id = &"decoy_coaster"
kind = 1
position_m = Vector3(140, -35.265244, 40)
position_m = Vector3(140, -35.34944, 40)
yaw_radians = -0.6
footprint_m = Vector3(26, 6, 8)
@@ -33,14 +33,14 @@ footprint_m = Vector3(26, 6, 8)
script = ExtResource("1_n3xpu")
placement_id = &"secondary_item"
kind = 2
position_m = Vector3(-6, -61.61916, 157)
position_m = Vector3(-6, -63.26594, 160)
footprint_m = Vector3(1, 1, 1)
[sub_resource type="Resource" id="Resource_aervf"]
script = ExtResource("1_n3xpu")
placement_id = &"debris_1"
kind = 3
position_m = Vector3(-12, -45.756382, 134)
position_m = Vector3(-12, -45.80824, 136)
yaw_radians = 0.4
footprint_m = Vector3(3, 2, 2)
@@ -48,7 +48,7 @@ footprint_m = Vector3(3, 2, 2)
script = ExtResource("1_n3xpu")
placement_id = &"debris_2"
kind = 3
position_m = Vector3(-10, -48.518223, 139)
position_m = Vector3(-10, -47.295185, 139)
yaw_radians = 1.2
footprint_m = Vector3(6, 2.5, 2.5)
@@ -56,14 +56,14 @@ footprint_m = Vector3(6, 2.5, 2.5)
script = ExtResource("1_n3xpu")
placement_id = &"debris_3"
kind = 3
position_m = Vector3(-9, -53.75445, 146)
position_m = Vector3(-9, -52.854576, 146)
footprint_m = Vector3(2, 1.5, 2)
[sub_resource type="Resource" id="Resource_hd5cl"]
script = ExtResource("1_n3xpu")
placement_id = &"debris_4"
kind = 3
position_m = Vector3(-7, -58.142864, 152)
position_m = Vector3(-7, -58.174175, 152)
yaw_radians = 2.0
footprint_m = Vector3(4, 1.5, 1.5)
@@ -91,7 +91,7 @@ position_m = Vector3(0, -25, -230)
[sub_resource type="Resource" id="Resource_u8dqp"]
script = ExtResource("1_n3xpu")
placement_id = &"saignee_entrance"
position_m = Vector3(-56.978443, -35.474903, -124.287895)
position_m = Vector3(-56.978443, -35.54769, -124.287895)
[sub_resource type="Resource" id="Resource_3uttw"]
script = ExtResource("1_n3xpu")
@@ -101,7 +101,7 @@ position_m = Vector3(40, -40, 125)
[sub_resource type="Resource" id="Resource_xvesq"]
script = ExtResource("2_36hd6")
route_id = &"safe_ridge"
waypoints_m = PackedVector3Array(0, -14, -210, 70, -16, -200, 76, -16, -100, 78, -16, 0, 76, -18, 80, 40, -24, 105, 0, -26, 100, -14, -30, 98)
waypoints_m = PackedVector3Array(0, -14, -210, 70, -16, -200, 76, -16, -100, 78, -16, 0, 76, -18, 80, 40, -24, 105, 0, -24, 100, -14, -30, 98)
maximum_depth_m = 32.0
[sub_resource type="Resource" id="Resource_lcu7s"]
Binary file not shown.
@@ -74,8 +74,11 @@ pente, des matériaux imposés par primitive et des `MaterialPatch`.
(butte ou fosse à sommet plat, contour bruité, matériau roche imposé),
`BoulderField` (blocs semi-ellipsoïdaux tirés par graine, combinés par
maximum, roche imposée).
- Modificateur : `Strata` quantifie les pentes raides en gradins horizontaux ;
les fonds et sommets plats ne bougent pas.
- Modificateurs : `Strata` quantifie les pentes raides en gradins horizontaux
dans un rectangle monde, les fonds et sommets plats ne bougent pas ;
`SlopeRoughness` ajoute un bruit à crêtes pondéré par la pente (roche
éraillée, sable lisse). `Roughness` accepte un nombre d'octaves et un type
fractal pour donner du relief de fond à plusieurs échelles.
Tout aléa passe par une graine explicite (`FastNoiseLite`,
`RandomNumberGenerator`) ; `tests/unit/run_site_baker_tests.gd` vérifie que
Binary file not shown.

Before

Width:  |  Height:  |  Size: 163 KiB

After

Width:  |  Height:  |  Size: 164 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 146 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 74 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 129 KiB

After

Width:  |  Height:  |  Size: 129 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d3qajwshxpplq"
path="res://.godot/imported/zone1_vigilant_plateau.png-6e6da03a831034d096e040e9808fd1b8.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/briefs/captures/zone1_vigilant_plateau.png"
dest_files=["res://.godot/imported/zone1_vigilant_plateau.png-6e6da03a831034d096e040e9808fd1b8.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 74 KiB

@@ -0,0 +1,40 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cm2nm65ktggp"
path="res://.godot/imported/zone1_vigilant_tombant.png-71b9b2e227c187a355cfe8ea0a7a15f7.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://docs/briefs/captures/zone1_vigilant_tombant.png"
dest_files=["res://.godot/imported/zone1_vigilant_tombant.png-71b9b2e227c187a355cfe8ea0a7a15f7.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/uastc_level=0
compress/rdo_quality_loss=0.0
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/channel_remap/red=0
process/channel_remap/green=1
process/channel_remap/blue=2
process/channel_remap/alpha=3
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1
@@ -2,6 +2,14 @@
**Statut : validé par Olivier le 2026-10-06** (plan de masse, objet secondaire à 62 m, pipeline par script de cuisson, nom « Banc du Vigilant » conservé). Sert de spécification de référence pour l'implémentation.
**Recuisson du 2026-10-06 (demande d'Olivier)** : la rupture et le tombant
sont stratifiés en gradins de 4 m (`Strata`, bande z ≥ 118 m, hors Saignée et
amers) ; le plateau reçoit un relief de fond à trois échelles (250–15 m ± 2,2 m,
80–10 m ± 1,2 m, 20–5 m ± 0,6 m) et la roche un bruit de pente (`SlopeRoughness`).
L'objet secondaire est reculé de 3 m (63 m), le premier débris de 2 m, et le
point de la route sûre au-dessus de la lèvre de la Saignée remonté à 24 m pour
garder 3 m de dégagement. Voir `docs/briefs/terrain-varied-landforms.md` §4.3 bis.
Rédigée le 2026-10-06 à partir de `docs/GAME_DESIGN_PLAN.md`,
`docs/VERTICAL_SLICE_TECHNICAL_PLAN.md`, `docs/POC_B_TECHNICAL_BASELINE.md`,
des ADR 0004, 0006, 0008, 0010 et du code sous `game/world/`. Rien n'a été
+21 -5
View File
@@ -91,12 +91,23 @@ ou `smoothstep`) et **mono-segment**. Il manque :
| Modificateur | Paramètres | Effet |
|---|---|---|
| `Strata` | `layer_height_m`, `sharpness` (0–1), `minimum_slope_degrees`, `full_slope_degrees`, `centre`, `radius_m` (0 = partout) | quantifie l'élévation en gradins de `layer_height_m` là où la pente dépasse `minimum_slope_degrees` (plein effet à `full_slope_degrees`) ; `sharpness` 0 = aucun effet, 1 = marches franches ; le fond plat et le plateau ne bougent pas |
| `Strata` | `layer_height_m`, `sharpness` (0–1), `minimum_slope_degrees`, `full_slope_degrees`, `region_m: Rect2` (vide = partout) | quantifie l'élévation en gradins de `layer_height_m` là où la pente dépasse `minimum_slope_degrees` (plein effet à `full_slope_degrees`) ; `sharpness` 0 = aucun effet, 1 = marches franches ; le fond plat et le plateau ne bougent pas |
Formule : `f = frac(e / h)`, `f' = clamp((f - 0.5) / (1 - s) + 0.5, 0, 1)`,
`e' = lerp(e, floor(e / h) * h + h * f', w)` avec
`w = smoothstep(tan(min), tan(full), |gradient|) * masque`.
### 4.3 bis Relief de fond (ajout du 2026-10-06, demande d'Olivier)
Les plateaux cuits avec la seule `Roughness` à 50 m (± 0,6 m) restaient
plats. `Roughness` prend maintenant un nombre d'octaves et un type fractal
(`FRACTAL_FBM` roulant, `FRACTAL_RIDGED` à crêtes) : une fréquence de 0,004
sur cinq octaves donne des mamelons et des cuvettes de 250 m à 15 m en une
primitive. Le modificateur `SlopeRoughness` (`seed`, `frequency`,
`amplitude_m`, `minimum_slope_degrees`, `full_slope_degrees`) ajoute un bruit
à crêtes pondéré par la pente : la roche s'éraille, le sable reste lisse. Les
deux sites l'utilisent (ondulation ± 2,2 m, roche ± 0,7 m).
### 4.4 Ce qui ne change pas
Le contrat de données (`SiteTerrainData`, ADR 0012 §2), l'acteur de monde, la
@@ -170,7 +181,10 @@ Plan de masse (nord en haut) :
(champ de hauteur vu de dessus, roche teintée) et cinq vues en jeu, graybox,
`terrain_showcase_canyon.png` (gradins et fond ridé), `_crevasse.png`,
`_boulders.png`, `_spurs.png`, `_escarpment.png` (face de la falaise vue du
pied). Le rendu (brouillard, rides en albédo) n'est pas l'objet de ce brief.
pied) ; pour le Banc du Vigilant recuit, `zone1_vigilant_tombant.png`
(gradins du tombant) et `zone1_vigilant_plateau.png` (plateau ondulé). Le
rendu (brouillard, rides en albédo, absence d'ombrage de détail) n'est pas
l'objet de ce brief : à ce stade, le relief se lit surtout au sonar.
## 8. Décisions laissées à Olivier
@@ -179,9 +193,11 @@ Plan de masse (nord en haut) :
couche : à 1 m par cellule, des couches de 4 m sur une paroi à 55° ; une
paroi à 76° (première cuisson) ne montrait aucun gradin. Les sites de mission restent à 1,5 m sauf décision contraire ; une
crevasse de 5 m à 1,5 m par cellule n'a que trois cellules de large.
2. **Strates partout ?** Le modificateur est appliqué au canyon seulement.
L'appliquer à la falaise sud du Banc du Vigilant est un changement de
recette, donc de `.res`, donc une re-cuisson à valider.
2. **Strates sur le Banc du Vigilant** : décidé par Olivier le 2026-10-06,
appliquées à la rupture et au tombant (bande z ≥ 118 m, couches de 4 m,
plein effet dès 40° ; la Saignée, l'Échine et les amers sont hors bande).
Le `.res` du site a été recuit ; l'objet secondaire, les débris et les
routes restent dans leurs contrats.
3. **Sites de mission suivants** (U-Boot sous falaise, océanographique en
canyon) : les primitives sont prêtes ; le plan de masse de chaque site
reste à proposer et valider séparément.
+36 -4
View File
@@ -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 TEST_COUNT := 44
const TEST_COUNT := 48
var _failures: Array[String] = []
@@ -23,6 +23,7 @@ func _init() -> void:
_test_outcrop()
_test_boulder_field()
_test_strata()
_test_slope_roughness()
if _failures.is_empty():
print("Site baker unit tests: %d passed, 0 failed." % TEST_COUNT)
@@ -187,7 +188,7 @@ func _test_strata() -> void:
recipe.origin_z_m = -10.0
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 1.0, 0.0, 0.0, 0.0))
var plain := SiteTerrainBaker.bake(recipe)
recipe.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Vector2.ZERO, 0.0))
recipe.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2()))
var terraced := SiteTerrainBaker.bake(recipe)
var flat_cells := 0
var riser_cells := 0
@@ -202,17 +203,48 @@ func _test_strata() -> void:
var flat := _small_recipe()
flat.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.3, 0.0, 0.0, 0.0, 0.0))
flat.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Vector2.ZERO, 0.0))
flat.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2()))
var flat_data := SiteTerrainBaker.bake(flat)
_expect(is_equal_approx(flat_data.elevations_m[flat_data.index_of(10, 10)], -30.3), "strata leave flat ground alone")
var identity := _small_recipe()
identity.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 1.0, 0.0, 0.0, 0.0))
var before := SiteTerrainBaker.bake(identity)
identity.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.0, 20.0, 35.0, Vector2.ZERO, 0.0))
identity.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.0, 20.0, 35.0, Rect2()))
var after := SiteTerrainBaker.bake(identity)
_expect(before.elevations_m == after.elevations_m, "sharpness 0 is the identity")
# Depth 30.7 keeps the plane off the layer boundaries, where quantisation is the identity.
var banded := _small_recipe()
banded.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.7, 1.0, 0.0, 0.0, 0.0))
var untouched := SiteTerrainBaker.bake(banded)
banded.modifiers.append(SiteTerrainBaker.Strata.new(2.0, 0.9, 20.0, 35.0, Rect2(-20.0, 0.0, 40.0, 20.0)))
var half := SiteTerrainBaker.bake(banded)
var north_same := half.elevations_m[half.index_of(13, 5)] == untouched.elevations_m[untouched.index_of(13, 5)]
var south_changed := half.elevations_m[half.index_of(13, 15)] != untouched.elevations_m[untouched.index_of(13, 15)]
_expect(north_same and south_changed, "strata stay inside their region")
func _test_slope_roughness() -> void:
var recipe := _small_recipe()
recipe.primitives.append(SiteTerrainBaker.SlopedPlane.new(30.0, 0.0, 0.0, 0.0, 0.0))
recipe.primitives.append(SiteTerrainBaker.Blob.new(Vector2(10.0, 10.0), 5.0, 5.0, 12.0))
var smooth := SiteTerrainBaker.bake(recipe)
recipe.modifiers.append(SiteTerrainBaker.SlopeRoughness.new(5, 0.2, 1.0, 20.0, 40.0))
var rough := SiteTerrainBaker.bake(recipe)
_expect(rough.elevations_m[rough.index_of(0, 0)] == smooth.elevations_m[smooth.index_of(0, 0)], "slope roughness leaves flat sand untouched")
var flank_changed := false
for column in range(13, 18):
flank_changed = flank_changed or rough.elevations_m[rough.index_of(column, 15)] != smooth.elevations_m[smooth.index_of(column, 15)]
_expect(flank_changed, "slope roughness roughens the steep flank")
var octaves := _small_recipe()
octaves.primitives.append(SiteTerrainBaker.Roughness.new(7, 0.01, 2.0, 5, FastNoiseLite.FRACTAL_RIDGED))
var ridged := SiteTerrainBaker.bake(octaves)
var within := true
for elevation: float in ridged.elevations_m:
within = within and absf(elevation) <= 2.0 + 0.001
_expect(within, "roughness amplitude bounds the ridged multi-octave noise")
func _test_zone1_artefact_is_stable() -> void:
var versioned := load(ZONE1_TERRAIN_PATH) as SiteTerrainData
+54 -12
View File
@@ -410,18 +410,28 @@ class Ripples extends Primitive:
return amplitude_m * sin(phase) * falloff
## Seeded fractal noise added everywhere. frequency is 1 / wavelength of the largest
## octave; each further octave halves the wavelength and the amplitude, so a low
## frequency with five octaves gives hummocks, swales and ripple-scale detail at once.
## FRACTAL_RIDGED makes sharp crests (rock), FRACTAL_FBM rolling ground (sediment).
class Roughness extends Primitive:
var amplitude_m: float
var _noise: FastNoiseLite
func _init(p_seed: int, p_frequency: float, p_amplitude_m: float) -> void:
func _init(
p_seed: int,
p_frequency: float,
p_amplitude_m: float,
p_octaves: int = 3,
p_fractal_type: FastNoiseLite.FractalType = FastNoiseLite.FRACTAL_FBM
) -> void:
amplitude_m = p_amplitude_m
_noise = FastNoiseLite.new()
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
_noise.seed = p_seed
_noise.frequency = p_frequency
_noise.fractal_type = FastNoiseLite.FRACTAL_FBM
_noise.fractal_octaves = 3
_noise.fractal_type = p_fractal_type
_noise.fractal_octaves = p_octaves
func elevation_delta(x_m: float, z_m: float) -> float:
return _noise.get_noise_2d(x_m, z_m) * amplitude_m
@@ -437,12 +447,13 @@ class Modifier extends RefCounted:
## Quantises steep ground into horizontal ledges layer_height_m apart: the stratified
## walls of a canyon or cliff. Flat floors and summits are below minimum_slope_degrees and
## stay untouched; the effect is full from full_slope_degrees. sharpness 0 leaves the
## field unchanged, 1 cuts hard steps. radius_m 0 applies everywhere.
## field unchanged, 1 cuts hard steps. region_m is a world-space (x, z) rectangle with a
## hard edge; an empty rectangle applies everywhere. Ledges need two to three cells of
## wall per layer to be resolved: at 1.5 m per cell, 4 m layers on a 50 degree wall.
class Strata extends Modifier:
var layer_height_m: float
var sharpness: float
var centre: Vector2
var radius_m: float
var region_m: Rect2
var _minimum_tangent: float
var _full_tangent: float
@@ -451,21 +462,19 @@ class Strata extends Modifier:
p_sharpness: float,
p_minimum_slope_degrees: float,
p_full_slope_degrees: float,
p_centre: Vector2,
p_radius_m: float
p_region_m: Rect2
) -> void:
assert(p_layer_height_m > 0.0, "strata need a positive layer height")
layer_height_m = p_layer_height_m
sharpness = clampf(p_sharpness, 0.0, 0.999)
centre = p_centre
radius_m = p_radius_m
region_m = p_region_m
_minimum_tangent = tan(deg_to_rad(p_minimum_slope_degrees))
_full_tangent = tan(deg_to_rad(p_full_slope_degrees))
func modify(x_m: float, z_m: float, elevation_m: float, gradient: Vector2) -> float:
if region_m.has_area() and not region_m.has_point(Vector2(x_m, z_m)):
return elevation_m
var weight := smoothstep(_minimum_tangent, _full_tangent, gradient.length())
if radius_m > 0.0:
weight *= 1.0 - smoothstep(radius_m * 0.8, radius_m, Vector2(x_m, z_m).distance_to(centre))
if weight <= 0.0:
return elevation_m
var layer := floorf(elevation_m / layer_height_m)
@@ -474,6 +483,39 @@ class Strata extends Modifier:
return lerpf(elevation_m, (layer + sharpened) * layer_height_m, weight)
## Seeded noise weighted by the local slope: rock faces and ridge flanks get crumbly
## while flat sand stays smooth. Zero below minimum_slope_degrees, full amplitude from
## full_slope_degrees.
class SlopeRoughness extends Modifier:
var amplitude_m: float
var _noise: FastNoiseLite
var _minimum_tangent: float
var _full_tangent: float
func _init(
p_seed: int,
p_frequency: float,
p_amplitude_m: float,
p_minimum_slope_degrees: float,
p_full_slope_degrees: float
) -> void:
amplitude_m = p_amplitude_m
_noise = FastNoiseLite.new()
_noise.noise_type = FastNoiseLite.TYPE_SIMPLEX_SMOOTH
_noise.seed = p_seed
_noise.frequency = p_frequency
_noise.fractal_type = FastNoiseLite.FRACTAL_RIDGED
_noise.fractal_octaves = 3
_minimum_tangent = tan(deg_to_rad(p_minimum_slope_degrees))
_full_tangent = tan(deg_to_rad(p_full_slope_degrees))
func modify(x_m: float, z_m: float, elevation_m: float, gradient: Vector2) -> float:
var weight := smoothstep(_minimum_tangent, _full_tangent, gradient.length())
if weight <= 0.0:
return elevation_m
return elevation_m + _noise.get_noise_2d(x_m, z_m) * amplitude_m * weight
class MaterialPatch extends RefCounted:
var centre: Vector2
var radius_m: float
+12 -6
View File
@@ -73,10 +73,10 @@ static func recipe() -> SiteTerrainBaker.Recipe:
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),
# SE — les Éperons: two pitted rock spurs 6-7 m high framing a 20 m sand channel
# SE — les Éperons: two pitted rock spurs 7-8 m high framing a 20 m sand channel
# littered with small blocks.
SiteTerrainBaker.Outcrop.new(WEST_SPUR_CENTRE, 30.0, 60.0, 7.0, 5.0, 401, 7.0, true),
SiteTerrainBaker.Outcrop.new(EAST_SPUR_CENTRE, 30.0, 55.0, 6.0, 5.0, 402, 7.0, true),
SiteTerrainBaker.Outcrop.new(WEST_SPUR_CENTRE, 30.0, 60.0, 8.0, 5.0, 401, 7.0, true),
SiteTerrainBaker.Outcrop.new(EAST_SPUR_CENTRE, 30.0, 55.0, 7.0, 5.0, 402, 7.0, true),
SiteTerrainBaker.BoulderField.new(CHANNEL_CENTRE, 50.0, 403, 12, 1.5, 3.0, 0.8),
# S — la Corniche: sinuous 22 m escarpment, then a 42 degree slope to the 80 m floor,
@@ -86,13 +86,19 @@ static func recipe() -> SiteTerrainBaker.Recipe:
SiteTerrainBaker.Outcrop.new(Vector2(40.0, 236.0), 10.0, 9.0, 4.0, 9.0, 503, 2.0, true),
SiteTerrainBaker.Outcrop.new(Vector2(130.0, 245.0), 14.0, 11.0, 6.0, 11.0, 504, 2.0, true),
# Roughness: broad undulation plus cell-scale pitting.
# Roughness: hummocks and swales from 250 m down to 15 m, the 50 m texture,
# and cell-scale pitting.
SiteTerrainBaker.Roughness.new(2026, 0.004, 2.2, 5),
SiteTerrainBaker.Roughness.new(2029, 0.012, 1.2, 4),
SiteTerrainBaker.Roughness.new(2030, 0.05, 0.6, 3),
SiteTerrainBaker.Roughness.new(1933, 0.02, 0.6),
SiteTerrainBaker.Roughness.new(2027, 0.15, 0.2),
]
result.modifiers = [
# Strata on the mesa flanks and canyon walls only: 4 m ledges.
SiteTerrainBaker.Strata.new(4.0, 0.85, 30.0, 50.0, MESA_CENTRE, 130.0),
# Crumbly rock faces, smooth sand. Before the strata so the ledges stay flat.
SiteTerrainBaker.SlopeRoughness.new(2028, 0.12, 0.7, 20.0, 40.0),
# Strata on the north-west quarter only (mesa flanks and canyon walls): 4 m ledges.
SiteTerrainBaker.Strata.new(4.0, 0.85, 30.0, 50.0, Rect2(-256.0, -256.0, 256.0, 256.0)),
]
return result
+21 -4
View File
@@ -15,6 +15,9 @@ const GROOVE_END := Vector2(-10.0, 120.0)
const RIDGE_START := Vector2(70.0, -150.0)
const RIDGE_END := Vector2(80.0, 90.0)
const BREAK_Z := 125.0
# Strata apply from here south: past the groove's end cap (120 m) and still 3 m before
# the break's smoothstep has dropped half a metre.
const STRATA_START_Z := 118.0
# The target's centre sits this far up the groove from GROOVE_END, so its 28 m hull
# stops one groove half-width short of the end cap and rests on the flat groove floor.
const TARGET_SET_BACK_M := 22.0
@@ -57,8 +60,22 @@ static func recipe() -> SiteTerrainBaker.Recipe:
SiteTerrainBaker.Blob.new(Vector2(25.0, 198.0), 6.3, 6.3, 2.0),
# Les Rides: sand ripples near the vessel only.
SiteTerrainBaker.Ripples.new(Vector2(1.0, 0.35), 9.0, 0.45, Vector2(0.0, -220.0), 90.0),
# Deterministic roughness everywhere.
# Deterministic roughness everywhere: the original 50 m texture, plus hummocks
# and swales from 250 m down to 15 m (five octaves) so the plateau is never flat.
SiteTerrainBaker.Roughness.new(1933, 0.02, 0.6),
SiteTerrainBaker.Roughness.new(1934, 0.004, 2.2, 5),
SiteTerrainBaker.Roughness.new(1936, 0.012, 1.2, 4),
# Hummocks of 20 m down to 5 m, the scale the pilot sees through the fog.
SiteTerrainBaker.Roughness.new(1937, 0.05, 0.6, 3),
]
result.modifiers = [
# La rupture et le tombant are a carbonate drop-off: 4 m strata on the break
# (full effect) and fainter ledges on the 35 degree talus below. The band starts
# south of the target hull and of la Saignée's end cap, so neither changes.
# Crumbly rock on every slope steeper than the sand rule, smooth sand elsewhere.
# Before the strata so the ledges stay flat.
SiteTerrainBaker.SlopeRoughness.new(1935, 0.12, 0.7, 20.0, 40.0),
SiteTerrainBaker.Strata.new(4.0, 0.85, 25.0, 40.0, Rect2(-384.0, STRATA_START_Z, 768.0, 384.0 - STRATA_START_Z)),
]
result.material_patches = [
SiteTerrainBaker.MaterialPatch.new(Vector2(140.0, 40.0), 28.0, SonarMaterial.Value.ROCK),
@@ -90,9 +107,9 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition:
_on_seabed(field, &"decoy_coaster", SitePlacement.Kind.WRECK_DECOY,
Vector2(140.0, 40.0), -0.6, Vector3(26.0, 6.0, 8.0), 1.2),
_on_seabed(field, &"secondary_item", SitePlacement.Kind.SECONDARY_ITEM,
Vector2(-6.0, 157.0), 0.0, Vector3(1.0, 1.0, 1.0), 0.0),
Vector2(-6.0, 160.0), 0.0, Vector3(1.0, 1.0, 1.0), 0.0),
_on_seabed(field, &"debris_1", SitePlacement.Kind.DEBRIS,
Vector2(-12.0, 134.0), 0.4, Vector3(3.0, 2.0, 2.0), 0.3),
Vector2(-12.0, 136.0), 0.4, Vector3(3.0, 2.0, 2.0), 0.3),
_on_seabed(field, &"debris_2", SitePlacement.Kind.DEBRIS,
Vector2(-10.0, 139.0), 1.2, Vector3(6.0, 2.5, 2.5), 0.4),
_on_seabed(field, &"debris_3", SitePlacement.Kind.DEBRIS,
@@ -110,7 +127,7 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition:
_route(&"safe_ridge", 32.0, [
Vector3(0.0, -14.0, -210.0), Vector3(70.0, -16.0, -200.0), Vector3(76.0, -16.0, -100.0),
Vector3(78.0, -16.0, 0.0), Vector3(76.0, -18.0, 80.0), Vector3(40.0, -24.0, 105.0),
Vector3(0.0, -26.0, 100.0), Vector3(-14.0, -30.0, 98.0),
Vector3(0.0, -24.0, 100.0), Vector3(-14.0, -30.0, 98.0),
]),
_route(&"risky_groove", 48.0, [
Vector3(0.0, -14.0, -210.0), Vector3(-60.0, -20.0, -150.0), Vector3(-50.0, -31.0, -100.0),