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:
OlivierandClaude Fable 5.1 committed 2026-10-06 22:00:24 +02:00
1 parent abffa737ec
commit d91e6bd02f
8 files changed
+73 -36

No files matched your search

+25 -5
View File
@@ -170,13 +170,15 @@ class Groove extends Primitive:
## 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
## 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:
var path: PackedVector2Array
var half_width_m: float
var depth_m: float
var wall_softness_m: float
var edge_amplitude_m: float
var floor_material: int
var _noise: FastNoiseLite
var _length_m: float
@@ -186,17 +188,31 @@ class Canyon extends Primitive:
p_depth_m: float,
p_wall_softness_m: float,
p_edge_seed: int,
p_edge_amplitude_m: float
p_edge_amplitude_m: float,
p_floor_material: int
) -> 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
half_width_m = p_half_width_m
depth_m = p_depth_m
wall_softness_m = p_wall_softness_m
edge_amplitude_m = p_edge_amplitude_m
floor_material = p_floor_material
_noise = edge_noise(p_edge_seed, p_edge_amplitude_m)
_length_m = path_length(p_path)
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 distance := sample.x + edge_offset(_noise, edge_amplitude_m, x_m, z_m)
var inside := 1.0 - smoothstep(
@@ -205,13 +221,14 @@ class Canyon extends Primitive:
distance
)
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
## (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:
## beyond its ends the contour wraps around the end vertex.
## The crest line is roughened by outline noise. The path must extend past the field
## 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:
var path: PackedVector2Array
var drop_m: float
@@ -228,6 +245,7 @@ class Escarpment extends Primitive:
p_edge_seed: int,
p_edge_amplitude_m: float
) -> void:
assert(p_path.size() >= 2, "an escarpment path needs at least two vertices")
path = p_path
drop_m = p_drop_m
softness_m = p_softness_m
@@ -267,6 +285,7 @@ class Outcrop extends Primitive:
p_edge_amplitude_m: float,
p_rock_top: bool
) -> void:
assert(p_radius_x_m > 0.0 and p_radius_z_m > 0.0, "an outcrop needs positive radii")
centre = p_centre
radius_x_m = p_radius_x_m
radius_z_m = p_radius_z_m
@@ -435,6 +454,7 @@ class Strata extends Modifier:
p_centre: Vector2,
p_radius_m: float
) -> 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
+4 -3
View File
@@ -5,6 +5,7 @@ extends RefCounted
## 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 SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const SiteTerrainData = preload("res://game/domain/sites/site_terrain_data.gd")
const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.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
# sand on the floor.
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),
# 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(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(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),