diff --git a/content/sites/zone1_vigilant.tres b/content/sites/zone1_vigilant.tres index 890f4c4..2276bda 100644 --- a/content/sites/zone1_vigilant.tres +++ b/content/sites/zone1_vigilant.tres @@ -9,15 +9,15 @@ script = ExtResource("1_n3xpu") placement_id = &"prospection_vessel" kind = 0 -position_m = Vector3(-12, -37.10001, 108) -yaw_radians = 0.35 +position_m = Vector3(-14.154642, -37.198338, 98.39586) +yaw_radians = -1.380808047881126 footprint_m = Vector3(28, 8, 9) [sub_resource type="Resource" id="Resource_xcojm"] script = ExtResource("1_n3xpu") placement_id = &"decoy_yacht" kind = 1 -position_m = Vector3(-40, -24.655516, -200) +position_m = Vector3(-30, -25.199738, -150) yaw_radians = 1.1 footprint_m = Vector3(12, 3, 4) @@ -71,7 +71,7 @@ footprint_m = Vector3(4, 1.5, 1.5) script = ExtResource("1_n3xpu") placement_id = &"vessel_anchor" kind = 5 -position_m = Vector3(0, -14, -250) +position_m = Vector3(0, -14, -210) [sub_resource type="Resource" id="Resource_e72vy"] script = ExtResource("1_n3xpu") @@ -101,13 +101,13 @@ 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, -250, 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, -26, 100, -14, -30, 98) maximum_depth_m = 32.0 [sub_resource type="Resource" id="Resource_lcu7s"] script = ExtResource("2_36hd6") route_id = &"risky_groove" -waypoints_m = PackedVector3Array(0, -14, -250, -60, -20, -150, -50, -31, -100, -35, -33, -20, -22, -34, 60, -14, -33, 98) +waypoints_m = PackedVector3Array(0, -14, -210, -60, -20, -150, -50, -31, -100, -35, -33, -20, -22, -34, 60, -14, -33, 98) maximum_depth_m = 48.0 [resource] @@ -116,6 +116,7 @@ site_id = &"zone1_vigilant" display_name = "Banc du Vigilant" terrain = ExtResource("4_i0v8k") playable_bounds_m = Rect2(-300, -300, 600, 600) -spawn_position_m = Vector3(0, -10, -250) +spawn_position_m = Vector3(0, -10, -210) +spawn_yaw_radians = 3.141592653589793 placements = Array[ExtResource("1_n3xpu")]([SubResource("Resource_rqt47"), SubResource("Resource_xcojm"), SubResource("Resource_yljal"), SubResource("Resource_aqgwj"), SubResource("Resource_aervf"), SubResource("Resource_qgawl"), SubResource("Resource_q0ace"), SubResource("Resource_hd5cl"), SubResource("Resource_wy8hl"), SubResource("Resource_e72vy"), SubResource("Resource_3n33i"), SubResource("Resource_cdmnh"), SubResource("Resource_u8dqp"), SubResource("Resource_3uttw")]) routes = Array[ExtResource("2_36hd6")]([SubResource("Resource_xvesq"), SubResource("Resource_lcu7s")]) diff --git a/docs/briefs/terrain-site-zone1-gameplay.md b/docs/briefs/terrain-site-zone1-gameplay.md index b18ae33..1b20e2f 100644 --- a/docs/briefs/terrain-site-zone1-gameplay.md +++ b/docs/briefs/terrain-site-zone1-gameplay.md @@ -86,7 +86,7 @@ crédibilité, conformément à la direction artistique. | Champ de hauteur | 513 × 513 cellules, 1,5 m par cellule, élévations float32 (`SiteTerrainData`) | | Profondeur min / max du terrain | 20 m / 88 m | | Profondeur d'écrasement coque I | hors terrain : le fond ne tue jamais par lui-même | -| Mouillage du navire | centre nord du plateau, 24 m de fond | +| Mouillage du navire | centre nord du plateau, 25 m de fond | | Point de départ du sous-marin | sous le navire, 10 m de profondeur | Les distances sont dimensionnées pour l'autonomie Standard (environ 1,2 km, @@ -150,14 +150,14 @@ correspond pas au rapport. ### 4.4 Les deux routes vers la cible -**Route sûre — par l'Échine (≈ 470 m, 26–32 m).** Depuis le navire, suivre la +**Route sûre — par l'Échine (≈ 450 m, 26–32 m).** Depuis le navire, suivre la crête longue vers le sud. Elle est lumineuse, large, sans obstacle, et se lit d'un seul scan. Elle mène au bord du plateau au-dessus de la Saignée : il faut ensuite descendre dans le sillon par son extrémité nord, à faible pente. Coût : temps et batterie. Récompense : zéro dégât, retour facile à estimer. -**Route risquée — par la Saignée (≈ 370 m, 36–48 m).** Un sillon de 12 à +**Route risquée — par la Saignée (≈ 340 m, 36–48 m).** Un sillon de 12 à 18 m de large, aux parois de 8 à 10 m, qui coupe le plateau en diagonale et débouche directement sur la proue de la cible. Sombre (les projecteurs deviennent nécessaires), étroit (collisions), en pente (la batterie de diff --git a/tests/contract/run_site_content_tests.gd b/tests/contract/run_site_content_tests.gd index 8c5ac9b..0c171cf 100644 --- a/tests/contract/run_site_content_tests.gd +++ b/tests/contract/run_site_content_tests.gd @@ -15,7 +15,11 @@ const DiveRulesResolver = preload("res://game/domain/rules/dive_rules_resolver.g const SITE_PATH := "res://content/sites/zone1_vigilant.tres" const ROUTE_CLEARANCE_M := 3.0 const MINIMUM_LANDMARK_SPACING_M := 60.0 -const TEST_COUNT := 30 +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] = [] @@ -107,6 +111,26 @@ func _test_zone1_content() -> void: 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) @@ -114,6 +138,17 @@ func _rests_on_terrain(field: TerrainHeightField, placement: SitePlacement) -> b 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] diff --git a/tools/sites/zone1_vigilant_recipe.gd b/tools/sites/zone1_vigilant_recipe.gd index 63a1e0b..7d1e74d 100644 --- a/tools/sites/zone1_vigilant_recipe.gd +++ b/tools/sites/zone1_vigilant_recipe.gd @@ -16,6 +16,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 +# 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 static func recipe() -> SiteTerrainBaker.Recipe: @@ -30,7 +33,7 @@ static func recipe() -> SiteTerrainBaker.Recipe: result.rock_reflectivity = 0.72 result.sand_reflectivity = 0.45 result.primitives = [ - # Plateau: 24 m deep at the vessel (z = -300), 6 m deeper over 400 m toward the south. + # Plateau: 24 m deep at z = -300 (about 25 m at the mooring), 6 m deeper over 400 m toward the south. SiteTerrainBaker.SlopedPlane.new(24.0, 0.0, -0.015, 0.0, -300.0), # L'Échine: long north-south rock ridge, 6 m high, flanks steeper than 20°. SiteTerrainBaker.Ridge.new(RIDGE_START, RIDGE_END, 12.0, 6.0, 25.0), @@ -63,18 +66,23 @@ static func recipe() -> SiteTerrainBaker.Recipe: static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition: var field := TerrainHeightField.from_data(terrain) + # The target hull's long axis (local +X) lies along la Saignée. + var groove_axis := (GROOVE_END - GROOVE_START).normalized() + var groove_yaw := atan2(-groove_axis.y, groove_axis.x) + var target_xz := GROOVE_END - groove_axis * TARGET_SET_BACK_M var result := DiveSiteDefinition.new() result.site_id = &"zone1_vigilant" result.display_name = "Banc du Vigilant" result.terrain = terrain result.playable_bounds_m = Rect2(-300.0, -300.0, 600.0, 600.0) - result.spawn_position_m = Vector3(0.0, -10.0, -250.0) - result.spawn_yaw_radians = 0.0 + # Spawn beside the mooring, facing south toward the site. + result.spawn_position_m = Vector3(0.0, -10.0, -210.0) + result.spawn_yaw_radians = PI result.placements = [ _on_seabed(field, &"prospection_vessel", SitePlacement.Kind.WRECK_TARGET, - Vector2(-12.0, 108.0), 0.35, Vector3(28.0, 8.0, 9.0), 2.0), + target_xz, groove_yaw, Vector3(28.0, 8.0, 9.0), 2.0), _on_seabed(field, &"decoy_yacht", SitePlacement.Kind.WRECK_DECOY, - Vector2(-40.0, -200.0), 1.1, Vector3(12.0, 3.0, 4.0), 0.5), + Vector2(-30.0, -150.0), 1.1, Vector3(12.0, 3.0, 4.0), 0.5), _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, @@ -87,7 +95,7 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition: Vector2(-9.0, 146.0), 0.0, Vector3(2.0, 1.5, 2.0), 0.2), _on_seabed(field, &"debris_4", SitePlacement.Kind.DEBRIS, Vector2(-7.0, 152.0), 2.0, Vector3(4.0, 1.5, 1.5), 0.3), - _marker(&"vessel_anchor", SitePlacement.Kind.VESSEL_ANCHOR, Vector3(0.0, -14.0, -250.0)), + _marker(&"vessel_anchor", SitePlacement.Kind.VESSEL_ANCHOR, Vector3(0.0, -14.0, -210.0)), _marker(&"echine", SitePlacement.Kind.LANDMARK, Vector3(75.0, -20.0, -50.0)), _marker(&"deux_freres", SitePlacement.Kind.LANDMARK, Vector3(-151.0, -18.0, -125.0)), _marker(&"rides", SitePlacement.Kind.LANDMARK, Vector3(0.0, -25.0, -230.0)), @@ -96,12 +104,12 @@ static func definition(terrain: SiteTerrainData) -> DiveSiteDefinition: ] result.routes = [ _route(&"safe_ridge", 32.0, [ - Vector3(0.0, -14.0, -250.0), Vector3(70.0, -16.0, -200.0), Vector3(76.0, -16.0, -100.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), ]), _route(&"risky_groove", 48.0, [ - Vector3(0.0, -14.0, -250.0), Vector3(-60.0, -20.0, -150.0), Vector3(-50.0, -31.0, -100.0), + Vector3(0.0, -14.0, -210.0), Vector3(-60.0, -20.0, -150.0), Vector3(-50.0, -31.0, -100.0), Vector3(-35.0, -33.0, -20.0), Vector3(-22.0, -34.0, 60.0), Vector3(-14.0, -33.0, 98.0), ]), ]