1 Commits
Author SHA1 Message Date
OlivierandClaude Fable 5.1 50b5aac069 Add Hebat Allah wreck to the sunlit shallows site
Quality Gate / Quality Gate (pull_request) Successful in 6m49s
First wreck in the production world layer. A WreckActor (StaticBody3D)
instantiates a two-LOD glTF visual, builds a trimesh collision from a
separate lighter glTF, and carries the sonar metadata (metal, 0.9
reflectivity) so the bathymetry rays echo it. The sunlit shallows site
places the MV Hebat Allah 62 m ahead of the submarine on the floor.

The assets derive from a CC BY 4.0 photogrammetry scan by Holger Buss
(Sketchfab). The raw 274 MB scan stays outside the repo; the committed
GLBs come from a reproducible Blender pipeline under scripts/assets/:
skirt cut, 44 m rescale, merge by distance (the scan is a triangle soup),
fragment removal, 0.4 m voxel remesh, decimation to 58k/14.5k/2.9k
triangles, smart UVs and a 2K albedo rebaked from a luminance-rebuilt
texture (the scan's red channel is dead).

docs/ASSET_LICENSES.md starts the licence registry required by the design
plan and records the attribution owed in the credits. The site suite
gains twelve assertions covering sonar metadata, collision, LOD handover
and triangle budgets. Godot extracts the embedded albedo next to the glb
on import; those PNGs are ignored like the cockpit ones.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-10-07 07:30:21 +02:00
2 changed files with 9 additions and 52 deletions

No files matched your search

@@ -51,8 +51,8 @@ const ConfirmationDialogScreen = preload(
const RebindResult = preload("res://game/application/input/rebind_result.gd") const RebindResult = preload("res://game/application/input/rebind_result.gd")
const MAIN_MENU_SCENE = preload("res://game/presentation/main_menu/main_menu_screen.tscn") const MAIN_MENU_SCENE = preload("res://game/presentation/main_menu/main_menu_screen.tscn")
const DIVE_WORLD_SCENE_PATH := ( const VEHICLE_TEST_WORLD_SCENE_PATH := (
"res://game/composition/vertical_slice/sunlit_shallows_world.tscn" "res://game/composition/vertical_slice/vehicle_test_world.tscn"
) )
const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.tscn") const COCKPIT_HUD_SCENE = preload("res://game/presentation/cockpit/cockpit_hud.tscn")
const SONAR_DISPLAY_SCENE = preload( const SONAR_DISPLAY_SCENE = preload(
@@ -66,7 +66,7 @@ const CONFIRMATION_SCENE = preload(
"res://game/presentation/confirmation/confirmation_dialog.tscn" "res://game/presentation/confirmation/confirmation_dialog.tscn"
) )
const DIVE_SESSION_ID := &"sunlit_shallows_dive" const DIVE_SESSION_ID := &"vertical_slice_vehicle_test"
const CONFIRM_ABANDON := &"abandon_dive" const CONFIRM_ABANDON := &"abandon_dive"
const CONFIRM_MAIN_MENU := &"return_main_menu" const CONFIRM_MAIN_MENU := &"return_main_menu"
const CONFIRM_QUIT := &"quit_game" const CONFIRM_QUIT := &"quit_game"
@@ -105,7 +105,7 @@ func _ready() -> void:
_show_main_menu() _show_main_menu()
func _start_dive() -> void: func _start_vehicle_test() -> void:
var rules = DiveRulesResolver.new().resolve( var rules = DiveRulesResolver.new().resolve(
DiveDifficultyProfile.standard(), DiveDifficultyProfile.standard(),
DiveUpgradeEffects.new(), DiveUpgradeEffects.new(),
@@ -117,7 +117,7 @@ func _start_dive() -> void:
if not session.is_ready(): if not session.is_ready():
return return
var world_scene := load(DIVE_WORLD_SCENE_PATH) as PackedScene var world_scene := load(VEHICLE_TEST_WORLD_SCENE_PATH) as PackedScene
if world_scene == null: if world_scene == null:
return return
var world := world_scene.instantiate() as Node3D var world := world_scene.instantiate() as Node3D
@@ -457,7 +457,7 @@ func _show_main_menu() -> void:
_replace_active_world(null) _replace_active_world(null)
var screen := MAIN_MENU_SCENE.instantiate() as MainMenuScreen var screen := MAIN_MENU_SCENE.instantiate() as MainMenuScreen
_replace_active_screen(screen) _replace_active_screen(screen)
screen.new_game_requested.connect(_start_dive) screen.new_game_requested.connect(_start_vehicle_test)
screen.quit_requested.connect(_quit_game) screen.quit_requested.connect(_quit_game)
@@ -29,14 +29,8 @@ const ControlsSettingsScreen = preload(
const ProductionInputAdapter = preload( const ProductionInputAdapter = preload(
"res://game/infrastructure/input/production_input_adapter.gd" "res://game/infrastructure/input/production_input_adapter.gd"
) )
const SunlitShallowsEnvironment = preload(
"res://game/world/sites/sunlit_shallows_environment.gd"
)
const WreckActor = preload("res://game/world/wrecks/wreck_actor.gd")
const SonarService = preload("res://game/application/sonar/sonar_service.gd")
const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd")
const TEST_COUNT := 99 const TEST_COUNT := 94
var _failures: Array[String] = [] var _failures: Array[String] = []
@@ -126,32 +120,7 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
var hud := composition.active_screen() as CockpitHud var hud := composition.active_screen() as CockpitHud
var sonar_service = composition.sonar_service() var sonar_service = composition.sonar_service()
var sonar_display = composition.sonar_display() var sonar_display = composition.sonar_display()
var active_world := composition.active_world() _expect(composition.active_world() != null, "vehicle test world is assembled")
var site: SunlitShallowsEnvironment = null
var wreck: WreckActor = null
var floor_body: StaticBody3D = null
if active_world != null:
site = active_world.get_node_or_null("Environment") as SunlitShallowsEnvironment
if site != null:
wreck = site.get_node_or_null("HebatAllahWreck") as WreckActor
floor_body = site.get_node_or_null("FlatFloor") as StaticBody3D
_expect(active_world != null and active_world.name == "SunlitShallowsWorld", "new game loads sunlit shallows world")
_expect(site != null, "sunlit environment is active")
_expect(
floor_body != null and floor_body.get_node_or_null("Collision") is CollisionShape3D,
"active dive has a physical seabed"
)
_expect(
wreck != null and wreck.get_node_or_null("Collision") is CollisionShape3D,
"active dive contains the collidable Hebat Allah wreck"
)
_expect(
actor != null
and wreck != null
and (wreck.global_position - actor.global_position).dot(-actor.global_basis.z) > 0.0
and actor.global_position.distance_to(wreck.global_position) < SonarService.MAXIMUM_RANGE_M,
"wreck starts ahead of the submarine within sonar range"
)
_expect(actor != null, "production submarine actor is assembled") _expect(actor != null, "production submarine actor is assembled")
_expect(camera != null, "cockpit camera is attached to the submarine") _expect(camera != null, "cockpit camera is attached to the submarine")
_expect(instruments != null, "physical cockpit instruments are assembled") _expect(instruments != null, "physical cockpit instruments are assembled")
@@ -253,11 +222,7 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
var first_scan = sonar_service.current_scan() var first_scan = sonar_service.current_scan()
_expect(first_scan != null, "manual sonar command publishes a typed scan") _expect(first_scan != null, "manual sonar command publishes a typed scan")
_expect(first_scan.samples.observed_count() > 1000, "reference acquisition captures dense bathymetry") _expect(first_scan.samples.observed_count() > 1000, "reference acquisition captures dense bathymetry")
var has_wreck_contact := false _expect(first_scan.contacts.size() >= 1, "metal calibration target becomes a distinct contact")
for contact in first_scan.contacts:
if contact.material == SonarMaterial.Value.METAL:
has_wreck_contact = true
_expect(has_wreck_contact, "manual sonar sees the metal wreck in the active world")
_expect("RELEVÉ EN COURS" in sonar_display.status_text(), "sonar display reports progressive reveal") _expect("RELEVÉ EN COURS" in sonar_display.status_text(), "sonar display reports progressive reveal")
_expect(not sonar_display.idle_prompt_visible(), "sonar data replaces the idle instruction") _expect(not sonar_display.idle_prompt_visible(), "sonar data replaces the idle instruction")
for frame in 18: for frame in 18:
@@ -278,20 +243,12 @@ func _test_vehicle_slice(composition: VerticalSliceRoot) -> void:
_expect(not sonar_display.is_expanded(), "expanded sonar returns to cockpit view") _expect(not sonar_display.is_expanded(), "expanded sonar returns to cockpit view")
var start_position: Vector3 = actor.global_position var start_position: Vector3 = actor.global_position
var start_wreck_distance: float = (
actor.global_position.distance_to(wreck.global_position) if wreck != null else INF
)
var starting_battery: float = composition.latest_update().snapshot.battery.remaining_kwh var starting_battery: float = composition.latest_update().snapshot.battery.remaining_kwh
Input.action_press(ProductionInputAdapter.ACTION_FORWARD) Input.action_press(ProductionInputAdapter.ACTION_FORWARD)
for frame in 35: for frame in 35:
await physics_frame await physics_frame
Input.action_release(ProductionInputAdapter.ACTION_FORWARD) Input.action_release(ProductionInputAdapter.ACTION_FORWARD)
_expect(actor.global_position.distance_to(start_position) > 0.1, "keyboard propulsion moves the submarine") _expect(actor.global_position.distance_to(start_position) > 0.1, "keyboard propulsion moves the submarine")
_expect(
wreck != null
and actor.global_position.distance_to(wreck.global_position) < start_wreck_distance - 0.1,
"forward propulsion closes the distance to the wreck"
)
_expect(actor.velocity.length() > 0.1, "propulsion builds inertial velocity") _expect(actor.velocity.length() > 0.1, "propulsion builds inertial velocity")
_expect( _expect(
composition.latest_update().snapshot.battery.remaining_kwh < starting_battery, composition.latest_update().snapshot.battery.remaining_kwh < starting_battery,