test(sites): geometric contract and integration checks for the terrain showcase
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
f7e60315b1
commit
caa74b3ee1
2 files changed
+151
-2
No files matched your search
@@ -13,19 +13,37 @@ const DiveAccessibilityAdjustments = preload(
|
||||
const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.gd")
|
||||
|
||||
const SITE_PATH := "res://content/sites/zone1_vigilant.tres"
|
||||
const SHOWCASE_PATH := "res://content/sites/terrain_showcase.tres"
|
||||
# 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
|
||||
# z = -120 row between the two buttes.
|
||||
const SHOWCASE_MESA_TOP := Vector2(-80.0, -90.0)
|
||||
const SHOWCASE_CANYON_MID := Vector2(-130.0, -135.0)
|
||||
const SHOWCASE_CANYON_AXIS := Vector2(40.0, -30.0)
|
||||
const SHOWCASE_CREVASSE_ROW_Z := -120.0
|
||||
const SHOWCASE_CREVASSE_SHELF_X := 118.0
|
||||
const SHOWCASE_CREVASSE_FLOOR_X := 124.0
|
||||
const SHOWCASE_BOULDER_FIELD_CENTRE := Vector2(-120.0, 110.0)
|
||||
const SHOWCASE_BOULDER_FIELD_RADIUS_M := 75.0
|
||||
const SHOWCASE_WEST_SPUR := Vector2(80.0, 120.0)
|
||||
const SHOWCASE_CHANNEL := Vector2(125.0, 120.0)
|
||||
const SHOWCASE_EAST_SPUR := Vector2(170.0, 125.0)
|
||||
const STEEP_WALL_RISE_PER_M := 1.7 # tan(60 degrees)
|
||||
const LEDGE_RISE_PER_M := 0.35
|
||||
const ROUTE_CLEARANCE_M := 3.0
|
||||
const MINIMUM_LANDMARK_SPACING_M := 60.0
|
||||
const MAXIMUM_ANCHOR_TO_WRECK_M := 320.0
|
||||
const MAXIMUM_WRECK_SPACING_M := 260.0
|
||||
const HULL_HALF_LENGTH_M := 14.0
|
||||
const HULL_SEABED_TOLERANCE_M := 2.0
|
||||
const TEST_COUNT := 34
|
||||
const TEST_COUNT := 52
|
||||
|
||||
var _failures: Array[String] = []
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_test_zone1_content()
|
||||
_test_showcase_content()
|
||||
|
||||
if _failures.is_empty():
|
||||
print("Site content contract tests: %d passed, 0 failed." % TEST_COUNT)
|
||||
@@ -132,6 +150,96 @@ func _test_zone1_content() -> void:
|
||||
_expect(_hull_lies_along_seabed(field, targets[0]), "the target hull lies along the groove floor")
|
||||
|
||||
|
||||
func _test_showcase_content() -> void:
|
||||
var definition := load(SHOWCASE_PATH) as DiveSiteDefinition
|
||||
_expect(definition != null, "showcase definition loads")
|
||||
if definition == null:
|
||||
return
|
||||
_expect(definition.validation_error().is_empty(), "showcase definition is valid: %s" % definition.validation_error())
|
||||
var field := definition.height_field()
|
||||
if field == null:
|
||||
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(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")
|
||||
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")
|
||||
var spawn := definition.spawn_position_m
|
||||
_expect(spawn.y - field.elevation_at(spawn.x, spawn.z) >= 10.0, "showcase spawn has at least 10 m of water under the keel")
|
||||
|
||||
# NW: stratified canyon through the mesa.
|
||||
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)
|
||||
_expect(mesa_top - canyon_floor >= 18.0, "canyon floor is at least 18 m below the mesa top (%.1f m)" % (mesa_top - canyon_floor))
|
||||
var axis := SHOWCASE_CANYON_AXIS.normalized()
|
||||
var lowest := INF
|
||||
var highest := -INF
|
||||
for along in range(-5, 6):
|
||||
var point := SHOWCASE_CANYON_MID + axis * float(along)
|
||||
var elevation := field.elevation_at(point.x, point.y)
|
||||
lowest = minf(lowest, elevation)
|
||||
highest = maxf(highest, elevation)
|
||||
_expect(highest - lowest <= 1.0, "canyon floor is flat within 1 m over 10 m along the axis")
|
||||
var normal := Vector2(-axis.y, axis.x)
|
||||
var transect := PackedFloat32Array()
|
||||
for offset in range(-30, 31):
|
||||
var point := SHOWCASE_CANYON_MID + normal * float(offset)
|
||||
transect.append(field.elevation_at(point.x, point.y))
|
||||
var steepest := 0.0
|
||||
var ledge_pairs := 0
|
||||
for index in range(transect.size() - 1):
|
||||
var rise := absf(transect[index + 1] - transect[index])
|
||||
steepest = maxf(steepest, rise)
|
||||
var in_wall := transect[index] > canyon_floor + 1.0 and transect[index] < mesa_top - 1.0
|
||||
if in_wall and rise < LEDGE_RISE_PER_M:
|
||||
ledge_pairs += 1
|
||||
_expect(steepest >= STEEP_WALL_RISE_PER_M, "canyon wall is steeper than 60 degrees somewhere (%.1f m per m)" % steepest)
|
||||
_expect(ledge_pairs >= 2, "canyon wall carries at least two ledges (%d flat metres)" % ledge_pairs)
|
||||
|
||||
# NE: crevasse between the buttes.
|
||||
var shelf := field.elevation_at(SHOWCASE_CREVASSE_SHELF_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))
|
||||
var deep_cells := 0
|
||||
for x in range(100, 146):
|
||||
if field.elevation_at(float(x), SHOWCASE_CREVASSE_ROW_Z) < shelf - 10.0:
|
||||
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)
|
||||
|
||||
# SW: boulder field on sand.
|
||||
var rock_cells := 0
|
||||
var disc_cells := 0
|
||||
var radius := int(SHOWCASE_BOULDER_FIELD_RADIUS_M)
|
||||
for z in range(-radius, radius + 1):
|
||||
for x in range(-radius, radius + 1):
|
||||
var point := SHOWCASE_BOULDER_FIELD_CENTRE + Vector2(x, z)
|
||||
if point.distance_to(SHOWCASE_BOULDER_FIELD_CENTRE) > SHOWCASE_BOULDER_FIELD_RADIUS_M:
|
||||
continue
|
||||
disc_cells += 1
|
||||
if field.material_at(point.x, point.y) == SonarMaterial.Value.ROCK:
|
||||
rock_cells += 1
|
||||
var rock_fraction := float(rock_cells) / float(disc_cells)
|
||||
_expect(rock_fraction >= 0.25 and rock_fraction <= 0.6, "boulder field is 25 to 60 %% rock (%.0f %%)" % (rock_fraction * 100.0))
|
||||
_expect(field.material_at(SHOWCASE_BOULDER_FIELD_CENTRE.x + 100.0, SHOWCASE_BOULDER_FIELD_CENTRE.y) == SonarMaterial.Value.SAND, "the sand plain beside the boulder field is sand")
|
||||
|
||||
# SE: two spurs and a sand channel.
|
||||
var west_spur := field.elevation_at(SHOWCASE_WEST_SPUR.x, SHOWCASE_WEST_SPUR.y)
|
||||
var east_spur := field.elevation_at(SHOWCASE_EAST_SPUR.x, SHOWCASE_EAST_SPUR.y)
|
||||
var channel := field.elevation_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y)
|
||||
_expect(west_spur - channel >= 5.0 and east_spur - channel >= 5.0, "the channel runs at least 5 m below both spurs")
|
||||
_expect(
|
||||
field.material_at(SHOWCASE_WEST_SPUR.x, SHOWCASE_WEST_SPUR.y) == SonarMaterial.Value.ROCK
|
||||
and field.material_at(SHOWCASE_EAST_SPUR.x, SHOWCASE_EAST_SPUR.y) == SonarMaterial.Value.ROCK,
|
||||
"both spur summits are rock"
|
||||
)
|
||||
_expect(field.material_at(SHOWCASE_CHANNEL.x, SHOWCASE_CHANNEL.y + 30.0) == SonarMaterial.Value.SAND, "the channel floor is sand")
|
||||
|
||||
# S: escarpment.
|
||||
var above := field.elevation_at(0.0, 190.0)
|
||||
var below := field.elevation_at(0.0, 250.0)
|
||||
_expect(above - below >= 20.0, "the escarpment drops at least 20 m (%.1f m)" % (above - below))
|
||||
|
||||
|
||||
func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> bool:
|
||||
var seabed := field.elevation_at(placement.position_m.x, placement.position_m.z)
|
||||
var bottom := placement.position_m.y - placement.footprint_m.y * 0.5
|
||||
|
||||
Reference in new issue
Block a user