extends SceneTree const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd") const DiveSiteDefinition = preload("res://game/domain/sites/dive_site_definition.gd") const TerrainHeightField = preload("res://game/domain/sites/terrain_height_field.gd") const SitePlacement = preload("res://game/domain/sites/site_placement.gd") const SiteRoute = preload("res://game/domain/sites/site_route.gd") const DiveDifficultyProfile = preload("res://game/domain/rules/dive_difficulty_profile.gd") const DiveUpgradeEffects = preload("res://game/domain/rules/dive_upgrade_effects.gd") const DiveAccessibilityAdjustments = preload( "res://game/domain/rules/dive_accessibility_adjustments.gd" ) const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.gd") const SITE_PATH := "res://content/sites/zone1_vigilant.tres" 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 var _failures: Array[String] = [] func _init() -> void: _test_zone1_content() if _failures.is_empty(): print("Site content contract tests: %d passed, 0 failed." % TEST_COUNT) quit(0) else: for failure: String in _failures: push_error(failure) print("Site content contract tests: %d failed." % _failures.size()) quit(1) func _test_zone1_content() -> void: var definition := load(SITE_PATH) as DiveSiteDefinition _expect(definition != null, "zone 1 definition loads") if definition == null: return _expect(definition.validation_error().is_empty(), "zone 1 definition is valid: %s" % definition.validation_error()) var field := definition.height_field() if field == null: return var rules = DiveRulesResolver.new().resolve( DiveDifficultyProfile.standard(), DiveUpgradeEffects.new(), DiveAccessibilityAdjustments.new() ) var certified_m: float = rules.certified_depth_m var critical_m: float = rules.critical_depth_m _expect(definition.playable_bounds_m.is_equal_approx(Rect2(-300.0, -300.0, 600.0, 600.0)), "playable zone is 600 x 600 m") _expect(field.minimum_elevation_m() >= -88.5 and field.maximum_elevation_m() <= -19.5, "terrain stays between 20 m and 88 m deep") _expect(field.cell_size_m() == 1.5 and field.columns() == 513 and field.rows() == 513, "terrain grid is 513 x 513 cells of 1.5 m") var targets := definition.placements_of_kind(SitePlacement.Kind.WRECK_TARGET) var decoys := definition.placements_of_kind(SitePlacement.Kind.WRECK_DECOY) var items := definition.placements_of_kind(SitePlacement.Kind.SECONDARY_ITEM) var anchors := definition.placements_of_kind(SitePlacement.Kind.VESSEL_ANCHOR) var debris := definition.placements_of_kind(SitePlacement.Kind.DEBRIS) var landmarks := definition.placements_of_kind(SitePlacement.Kind.LANDMARK) _expect(targets.size() == 1, "exactly one target wreck") _expect(decoys.size() == 2, "exactly two decoy wrecks") _expect(items.size() == 1, "exactly one secondary item") _expect(anchors.size() == 1, "exactly one vessel anchor") _expect(debris.size() >= 3, "a debris trail of at least three pieces") _expect(landmarks.size() >= 4, "at least four named landmarks") for wreck: SitePlacement in targets + decoys: _expect(-wreck.position_m.y <= certified_m, "%s lies above the certified depth" % wreck.placement_id) _expect(_rests_on_terrain(field, wreck), "%s rests on or in the seabed" % wreck.placement_id) if items.size() == 1: var item_depth := -items[0].position_m.y _expect(item_depth > certified_m and item_depth <= critical_m, "secondary item is between 100 %% and 115 %% of the certified depth (%.1f m)" % item_depth) _expect(_rests_on_terrain(field, items[0]), "secondary item rests on the seabed") for piece: SitePlacement in debris: if -piece.position_m.y < 45.0 or -piece.position_m.y > 66.0 or not _rests_on_terrain(field, piece): _failures.append("debris %s is off the slope or floating" % piece.placement_id) _expect(true, "debris pieces checked individually") if anchors.size() == 1: var seabed_under_anchor := -field.elevation_at(anchors[0].position_m.x, anchors[0].position_m.z) _expect(seabed_under_anchor >= 20.0 and seabed_under_anchor <= 30.0, "vessel anchors over 20-30 m of seabed") var spawn := definition.spawn_position_m _expect(spawn.y - field.elevation_at(spawn.x, spawn.z) >= 8.0, "spawn has at least 8 m of water under the keel") _expect(is_equal_approx(-spawn.y, 10.0), "spawn is 10 m deep") var safe := definition.route(&"safe_ridge") var risky := definition.route(&"risky_groove") _expect(safe != null and safe.maximum_depth_m == 32.0, "safe route is declared with a 32 m ceiling") _expect(risky != null and risky.maximum_depth_m == 48.0, "risky route is declared with a 48 m ceiling") if safe != null and risky != null and targets.size() == 1: _expect(_route_clears_terrain(field, safe), "safe route clears the terrain by %.0f m" % ROUTE_CLEARANCE_M) _expect(_route_clears_terrain(field, risky), "risky route clears the terrain by %.0f m" % ROUTE_CLEARANCE_M) var target := targets[0].position_m _expect( _xz_distance(safe.waypoints_m[-1], target) <= 25.0 and _xz_distance(risky.waypoints_m[-1], target) <= 25.0, "both routes end within 25 m of the target" ) _expect(_route_length(risky) < _route_length(safe) * 0.9, "risky route is shorter than the safe route") var spaced := true for i in landmarks.size(): for j in range(i + 1, landmarks.size()): if _xz_distance(landmarks[i].position_m, landmarks[j].position_m) < MINIMUM_LANDMARK_SPACING_M: spaced = false _expect(spaced, "landmarks are at least 60 m apart") var wrecks := targets + decoys if anchors.size() == 1: var anchor := anchors[0].position_m var farthest := 0.0 for wreck: SitePlacement in wrecks: farthest = maxf(farthest, _xz_distance(anchor, wreck.position_m)) _expect(farthest <= MAXIMUM_ANCHOR_TO_WRECK_M, "every wreck lies within 320 m of the mooring (farthest %.0f m)" % farthest) var widest := 0.0 for i in wrecks.size(): for j in range(i + 1, wrecks.size()): widest = maxf(widest, _xz_distance(wrecks[i].position_m, wrecks[j].position_m)) _expect(widest <= MAXIMUM_WRECK_SPACING_M, "every pair of wrecks lies within 260 m (widest %.0f m)" % widest) if targets.size() == 1: var target_xz := Vector2(targets[0].position_m.x, targets[0].position_m.z) var spawn_xz := Vector2(spawn.x, spawn.z) var yaw := definition.spawn_yaw_radians var spawn_forward := Vector2(-sin(yaw), -cos(yaw)) _expect(spawn_forward.dot(target_xz - spawn_xz) > 0.0, "the spawn faces the target wreck") _expect(_hull_lies_along_seabed(field, targets[0]), "the target hull lies along the groove floor") 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 return bottom >= seabed - placement.footprint_m.y and bottom <= seabed + 1.0 func _hull_lies_along_seabed(field: TerrainHeightField, hull: SitePlacement) -> bool: var centre := Vector2(hull.position_m.x, hull.position_m.z) var long_axis := Vector2(cos(hull.yaw_radians), -sin(hull.yaw_radians)) var centre_seabed := field.elevation_at(centre.x, centre.y) for side: float in [-1.0, 1.0]: var end := centre + long_axis * HULL_HALF_LENGTH_M * side if absf(field.elevation_at(end.x, end.y) - centre_seabed) > HULL_SEABED_TOLERANCE_M: return false return true func _route_clears_terrain(field: TerrainHeightField, route: SiteRoute) -> bool: for i in range(route.waypoints_m.size() - 1): var a := route.waypoints_m[i] var b := route.waypoints_m[i + 1] var steps := maxi(int(ceilf(a.distance_to(b) / 2.0)), 1) for step in range(steps + 1): var point := a.lerp(b, float(step) / float(steps)) if point.y - field.elevation_at(point.x, point.z) < ROUTE_CLEARANCE_M: return false return true func _route_length(route: SiteRoute) -> float: var length := 0.0 for i in range(route.waypoints_m.size() - 1): length += route.waypoints_m[i].distance_to(route.waypoints_m[i + 1]) return length func _xz_distance(a: Vector3, b: Vector3) -> float: return Vector2(a.x, a.z).distance_to(Vector2(b.x, b.z)) func _expect(condition: bool, label: String) -> void: if not condition: _failures.append(label)