extends SceneTree const SonarMaterial = preload("res://game/domain/sonar/sonar_material.gd") const SonarScanState = preload("res://game/domain/sonar/sonar_scan_state.gd") const SonarSample = preload("res://game/domain/sonar/sonar_sample.gd") const SonarSampleBuffer = preload( "res://game/domain/sonar/sonar_sample_buffer.gd" ) const SonarCaptureRequest = preload( "res://game/application/sonar/sonar_capture_request.gd" ) const SonarCaptureSample = preload( "res://game/application/sonar/sonar_capture_sample.gd" ) const SonarCaptureResult = preload( "res://game/application/sonar/sonar_capture_result.gd" ) const SonarAcquisitionPort = preload( "res://game/application/interfaces/sonar_acquisition_port.gd" ) const SonarService = preload("res://game/application/sonar/sonar_service.gd") const TEST_COUNT := 34 var _failures: Array[String] = [] class RecordingAcquisition extends SonarAcquisitionPort: var requests: Array[SonarCaptureRequest] = [] func capture(request: SonarCaptureRequest) -> SonarCaptureResult: requests.append(request) var samples: Array[SonarCaptureSample] = [ SonarCaptureSample.new( 0, 0, Vector3(-2.0, -7.0, 8.0), Vector3.UP, 0.9, -7.0, SonarMaterial.Value.METAL ), SonarCaptureSample.new( 0, 1, Vector3(-1.0, -7.0, 8.0), Vector3.UP, 0.8, -7.0, SonarMaterial.Value.METAL ), SonarCaptureSample.new( 5, 5, Vector3(12.0, -11.0, 24.0), Vector3.UP, 1.0, -11.0, SonarMaterial.Value.METAL ), SonarCaptureSample.new( 2, 2, Vector3(0.0, -14.0, 16.0), Vector3.UP, 0.6, -14.0, SonarMaterial.Value.ROCK ), ] return SonarCaptureResult.new(true, samples) func _init() -> void: _test_sample_buffer_contract() _test_service_lifecycle_and_contacts() if _failures.is_empty(): print("Sonar application unit tests: %d passed, 0 failed." % TEST_COUNT) quit(0) else: for failure: String in _failures: push_error(failure) print("Sonar application unit tests: %d failed." % _failures.size()) quit(1) func _test_sample_buffer_contract() -> void: var sample := SonarSample.new( Vector3(1.0, -3.0, 8.0), Vector3.UP, 0.75, -3.0, SonarMaterial.Value.ROCK, true ) _expect(sample.validation_error().is_empty(), "observed typed sample is valid") var sand_sample := SonarSample.new( Vector3(2.0, -4.0, 9.0), Vector3.UP, 0.4, -4.0, SonarMaterial.Value.SAND, true ) _expect(sand_sample.validation_error().is_empty(), "sand is a valid terrain material") _expect(SonarMaterial.is_valid(SonarMaterial.Value.SAND), "sand is inside the material range") var buffer := SonarSampleBuffer.new(3, 2) _expect(buffer.set_sample(1, 2, sample).is_empty(), "sample writes inside buffer bounds") _expect(buffer.observed_count() == 1, "buffer counts observed cells") var restored: SonarSample = buffer.sample_at(1, 2) _expect( restored.observed and is_equal_approx(restored.intensity, 0.75), "typed accessor restores aligned sample fields" ) _expect(not buffer.sample_at(4, 4).observed, "out-of-range accessor returns unknown sample") _expect(buffer.seal().is_empty(), "valid sample buffer seals") _expect( buffer.set_sample(0, 0, sample) == "sonar sample buffer is sealed", "sealed sample buffer rejects mutation" ) _expect(buffer.validation_error().is_empty(), "sealed aligned buffer is publishable") func _test_service_lifecycle_and_contacts() -> void: var acquisition := RecordingAcquisition.new() var service := SonarService.new(acquisition, &"test-dive") var first = service.request_scan(Transform3D.IDENTITY) _expect(first.accepted, "manual sonar request is accepted while ready") _expect( acquisition.requests.size() == 1 and acquisition.requests[0].columns == 49 and acquisition.requests[0].rows == 31, "acquisition receives baseline grid dimensions" ) _expect( is_equal_approx(acquisition.requests[0].maximum_range_m, 95.0) and is_equal_approx(rad_to_deg(acquisition.requests[0].horizontal_opening_radians), 76.0), "acquisition receives baseline range and opening" ) _expect(first.scan.validation_error().is_empty(), "published sonar scan is valid") _expect(first.scan.samples.observed_count() == 4, "capture becomes a compact sealed buffer") _expect(first.scan.contacts.size() == 2, "separated metal groups become distinct contacts") _expect( first.scan.contacts[0].sample_count == 2 and first.scan.contacts[1].sample_count == 1, "adjacent metal samples cluster deterministically" ) _expect(service.visible_row_count() == 0, "scan has no immediate visible return") _expect(not service.request_scan(Transform3D.IDENTITY).accepted, "request during reveal is rejected") service.advance(0.1) _expect(service.visible_row_count() == 1, "first return appears at four percent") _expect( service.current_scan().state == SonarScanState.Value.REVEALING, "scan remains in reveal after its first return" ) service.advance(2.4) _expect( service.current_scan().state == SonarScanState.Value.RECOVERY, "completed reveal enters recovery" ) _expect(service.previous_scan() == null, "previous scan is released after replacement") _expect(not service.request_scan(Transform3D.IDENTITY).accepted, "request during recovery is rejected") service.advance(0.35) _expect(service.current_scan().state == SonarScanState.Value.READY, "recovery returns sonar to ready") var second = service.request_scan(Transform3D.IDENTITY) _expect(second.accepted, "next manual sonar request is accepted") _expect( service.previous_scan().scan_id == first.scan.scan_id, "one previous scan is retained" ) _expect( is_equal_approx(service.previous_scan_opacity(), 0.52), "previous scan starts at baseline opacity" ) service.advance(1.25) _expect( is_equal_approx(service.current_scan().reveal_progress, 0.5), "reveal advances from explicit elapsed time" ) _expect( service.previous_scan_opacity() > 0.0 and service.previous_scan_opacity() < 0.52, "previous scan fades during replacement" ) _expect( service.visible_row_count() > 1 and service.visible_row_count() < 31, "partial reveal exposes only near rows" ) service.advance(1.25) _expect( service.current_scan().state == SonarScanState.Value.RECOVERY, "second reveal completes after 2.5 seconds" ) _expect(service.previous_scan() == null, "faded previous scan is discarded") service.advance(0.35) _expect(service.current_scan().state == SonarScanState.Value.READY, "second recovery completes") _expect(first.scan.scan_id != second.scan.scan_id, "scan identifiers are unique and ordered") _expect(acquisition.requests.size() == 2, "rejected commands do not invoke acquisition") _expect( is_equal_approx(first.scan.maximum_elevation_m - first.scan.minimum_elevation_m, 7.0), "scan retains a real elevation scale wider than the minimum span" ) func _expect(condition: bool, label: String) -> void: if not condition: _failures.append(label)