class_name SonarScan extends RefCounted const SonarScanState = preload("res://game/domain/sonar/sonar_scan_state.gd") const SonarSampleBuffer = preload("res://game/domain/sonar/sonar_sample_buffer.gd") const SonarContact = preload("res://game/domain/sonar/sonar_contact.gd") var scan_id: StringName var session_id: StringName var sensor_transform: Transform3D var minimum_range_m: float var maximum_range_m: float var horizontal_opening_radians: float var vertical_opening_radians: float var reveal_duration_seconds: float var recovery_duration_seconds: float var samples: SonarSampleBuffer var contacts: Array[SonarContact] var minimum_elevation_m: float var maximum_elevation_m: float var state: SonarScanState.Value var reveal_progress: float func _init( p_scan_id: StringName, p_session_id: StringName, p_sensor_transform: Transform3D, p_minimum_range_m: float, p_maximum_range_m: float, p_horizontal_opening_radians: float, p_vertical_opening_radians: float, p_reveal_duration_seconds: float, p_recovery_duration_seconds: float, p_samples: SonarSampleBuffer, p_contacts: Array[SonarContact], p_minimum_elevation_m: float, p_maximum_elevation_m: float, p_state: SonarScanState.Value, p_reveal_progress: float ) -> void: scan_id = p_scan_id session_id = p_session_id sensor_transform = p_sensor_transform minimum_range_m = p_minimum_range_m maximum_range_m = p_maximum_range_m horizontal_opening_radians = p_horizontal_opening_radians vertical_opening_radians = p_vertical_opening_radians reveal_duration_seconds = p_reveal_duration_seconds recovery_duration_seconds = p_recovery_duration_seconds samples = p_samples contacts = p_contacts.duplicate() minimum_elevation_m = p_minimum_elevation_m maximum_elevation_m = p_maximum_elevation_m state = p_state reveal_progress = p_reveal_progress func with_phase(next_state: SonarScanState.Value, next_progress: float) -> SonarScan: return new( scan_id, session_id, sensor_transform, minimum_range_m, maximum_range_m, horizontal_opening_radians, vertical_opening_radians, reveal_duration_seconds, recovery_duration_seconds, samples, contacts, minimum_elevation_m, maximum_elevation_m, next_state, next_progress ) func validation_error() -> String: if scan_id.is_empty() or session_id.is_empty(): return "sonar scan identifiers must not be empty" if not is_finite(minimum_range_m) or minimum_range_m < 0.0: return "sonar minimum range is invalid" if not is_finite(maximum_range_m) or maximum_range_m <= minimum_range_m: return "sonar maximum range must exceed minimum range" if ( not is_finite(horizontal_opening_radians) or horizontal_opening_radians <= 0.0 or horizontal_opening_radians >= PI ): return "sonar horizontal opening is invalid" if ( not is_finite(vertical_opening_radians) or vertical_opening_radians <= 0.0 or vertical_opening_radians >= PI ): return "sonar vertical opening is invalid" if reveal_duration_seconds <= 0.0 or recovery_duration_seconds < 0.0: return "sonar phase durations are invalid" if samples == null: return "sonar sample buffer is missing" var buffer_error := samples.validation_error() if not buffer_error.is_empty(): return buffer_error for contact: SonarContact in contacts: if contact == null: return "sonar contact is missing" var contact_error := contact.validation_error() if not contact_error.is_empty(): return contact_error if not is_finite(minimum_elevation_m) or not is_finite(maximum_elevation_m): return "sonar elevation scale is not finite" if maximum_elevation_m <= minimum_elevation_m: return "sonar elevation scale is empty" if not SonarScanState.is_valid(state): return "sonar scan state is invalid" if not is_finite(reveal_progress) or reveal_progress < 0.0 or reveal_progress > 1.0: return "sonar reveal progress must be between 0 and 1" return ""