extends SceneTree const SonarModelScript = preload("res://src/sonar_model.gd") var _last_cells: Array[Dictionary] = [] var _last_previous_cells: Array[Dictionary] = [] var _progress_cells: Array[Dictionary] = [] var _failures: Array[String] = [] var _completion_count := 0 func _init() -> void: _run.call_deferred() func _run() -> void: change_scene_to_file("res://scenes/poc_b.tscn") await process_frame await physics_frame var submarine := current_scene.get_node("Submarine") as CharacterBody3D var sonar := current_scene.get_node("SonarSystem") var hud := current_scene.get_node("SonarHud") sonar.ping_progress.connect(_on_ping_progress) sonar.ping_completed.connect(_on_ping_completed) _expect(absf(float(sonar.ping_duration()) - 2.5) < 0.001, "scan lasts 2.5 seconds") _expect(float(sonar.max_range) >= 95.0, "bathymetric range reaches 95 metres") _expect(float(sonar.horizontal_fov_degrees) >= 76.0, "scan covers a wider frontal area") sonar.request_ping() for frame in 3: await physics_frame _expect(_completion_count == 0, "scan does not return immediately") for frame in 157: await physics_frame _expect(not _last_cells.is_empty(), "calibration scan returns structured relief cells") _expect( _bathymetry_cell_count(_last_cells) > 500, "scan reconstructs a dense bathymetric surface" ) _expect( _last_cells[0].has("row") and _last_cells[0].has("column"), "each return keeps its grid coordinates" ) _expect( _last_cells[0].has("elevation"), "bathymetric samples encode normalized elevation" ) _expect(_metal_cell_count(_last_cells) > 0, "calibration scan detects the metal object") _expect( int(hud.relief_renderer().current_face_count()) > 0, "relief cells produce connected facets" ) hud.set_expanded(true) _expect(hud.is_expanded(), "sonar can expand without pausing the submarine") hud.set_expanded(false) var first_scan_size := _last_cells.size() for frame in 16: await physics_frame submarine.controls_enabled = false submarine.rotation.y = 0.35 sonar.request_ping() for frame in 3: await physics_frame _expect( _last_previous_cells.size() == first_scan_size, "new scan keeps exactly one previous relief during replacement" ) _expect( _progress_cells.size() < first_scan_size, "replacement starts near the submarine instead of appearing at once" ) for frame in 157: await physics_frame _expect(_completion_count == 2, "second scan completes after progressive replacement") var exit_code := 0 if _failures.is_empty(): print("POC B integration: 14 passed, 0 failed.") else: for failure in _failures: push_error(failure) print("POC B integration: %d failed." % _failures.size()) exit_code = 1 # AudioStreamGeneratorPlayback is reference-counted by the active player. # Stop it and release the scene before quitting so the integration suite also # verifies a clean ObjectDB shutdown. var audio_player := current_scene.get_node_or_null("SonarAudio") as AudioStreamPlayer if audio_player != null: audio_player.stop() audio_player.stream = null await process_frame current_scene.queue_free() await process_frame quit(exit_code) func _on_ping_progress( current_cells: Array[Dictionary], previous_cells: Array[Dictionary], _progress: float ) -> void: _progress_cells = current_cells.duplicate(true) _last_previous_cells = previous_cells.duplicate(true) func _on_ping_completed( current_cells: Array[Dictionary], previous_cells: Array[Dictionary] ) -> void: _last_cells = current_cells.duplicate(true) _last_previous_cells = previous_cells.duplicate(true) _completion_count += 1 func _metal_cell_count(cells: Array[Dictionary]) -> int: var count := 0 for cell: Dictionary in cells: if int(cell["material"]) == SonarModelScript.MaterialCategory.METAL: count += 1 return count func _bathymetry_cell_count(cells: Array[Dictionary]) -> int: var count := 0 for cell: Dictionary in cells: if String(cell.get("sample_type", "")) == "bathymetry": count += 1 return count func _expect(condition: bool, label: String) -> void: if not condition: _failures.append(label)