1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
13#[repr(i32)]
14pub enum Domain {
15 #[default]
17 Unspecified = 0,
18 Subsurface = 1,
23 Surface = 2,
28 Air = 3,
33}
34
35impl Domain {
36 pub fn name(&self) -> &'static str {
38 match self {
39 Domain::Unspecified => "Unspecified",
40 Domain::Subsurface => "Subsurface",
41 Domain::Surface => "Surface",
42 Domain::Air => "Air",
43 }
44 }
45
46 pub fn code(&self) -> &'static str {
48 match self {
49 Domain::Unspecified => "UNK",
50 Domain::Subsurface => "SUB",
51 Domain::Surface => "SFC",
52 Domain::Air => "AIR",
53 }
54 }
55
56 pub fn can_detect(&self, target: Domain) -> bool {
61 match (self, target) {
62 (Domain::Subsurface, Domain::Subsurface) => true,
64 (Domain::Surface, Domain::Surface) => true,
65 (Domain::Air, Domain::Air) => true,
66
67 (Domain::Air, Domain::Surface) => true,
69
70 (Domain::Surface, Domain::Air) => true,
72
73 (Domain::Subsurface, Domain::Surface) => true,
75
76 (Domain::Surface, Domain::Subsurface) => true,
78
79 (Domain::Air, Domain::Subsurface) => false,
81
82 (Domain::Subsurface, Domain::Air) => false,
84
85 (Domain::Unspecified, target) => Domain::Surface.can_detect(target),
87 (source, Domain::Unspecified) => source.can_detect(Domain::Surface),
88 }
89 }
90
91 pub fn can_engage(&self, target: Domain) -> bool {
95 match (self, target) {
96 (Domain::Subsurface, Domain::Subsurface) => true,
98 (Domain::Surface, Domain::Surface) => true,
99 (Domain::Air, Domain::Air) => true,
100
101 (Domain::Air, Domain::Surface) => true,
103
104 (Domain::Surface, Domain::Air) => true,
106
107 (Domain::Surface, Domain::Subsurface) => true,
109
110 (Domain::Subsurface, Domain::Surface) => true,
112
113 (Domain::Air, Domain::Subsurface) => true,
115
116 (Domain::Subsurface, Domain::Air) => false,
118
119 (Domain::Unspecified, target) => Domain::Surface.can_engage(target),
121 (source, Domain::Unspecified) => source.can_engage(Domain::Surface),
122 }
123 }
124
125 pub fn all() -> &'static [Domain] {
127 &[Domain::Subsurface, Domain::Surface, Domain::Air]
128 }
129
130 pub fn parse(s: &str) -> Option<Domain> {
132 match s.to_lowercase().as_str() {
133 "subsurface" | "sub" | "underwater" | "underground" => Some(Domain::Subsurface),
134 "surface" | "sfc" | "ground" | "sea" => Some(Domain::Surface),
135 "air" | "airborne" | "aerial" | "sky" => Some(Domain::Air),
136 _ => None,
137 }
138 }
139}
140
141impl TryFrom<i32> for Domain {
142 type Error = ();
143
144 fn try_from(value: i32) -> Result<Self, Self::Error> {
145 match value {
146 0 => Ok(Domain::Unspecified),
147 1 => Ok(Domain::Subsurface),
148 2 => Ok(Domain::Surface),
149 3 => Ok(Domain::Air),
150 _ => Err(()),
151 }
152 }
153}
154
155impl std::fmt::Display for Domain {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "{}", self.name())
158 }
159}
160
161#[derive(Debug, Clone, PartialEq, Eq, Default, Serialize, Deserialize)]
163pub struct DomainSet {
164 subsurface: bool,
165 surface: bool,
166 air: bool,
167}
168
169impl DomainSet {
170 pub fn empty() -> Self {
172 Self::default()
173 }
174
175 pub fn single(domain: Domain) -> Self {
177 let mut set = Self::empty();
178 set.add(domain);
179 set
180 }
181
182 pub fn from_domains(domains: &[Domain]) -> Self {
184 let mut set = Self::empty();
185 for domain in domains {
186 set.add(*domain);
187 }
188 set
189 }
190
191 pub fn all() -> Self {
193 Self {
194 subsurface: true,
195 surface: true,
196 air: true,
197 }
198 }
199
200 pub fn add(&mut self, domain: Domain) {
202 match domain {
203 Domain::Subsurface => self.subsurface = true,
204 Domain::Surface => self.surface = true,
205 Domain::Air => self.air = true,
206 Domain::Unspecified => {} }
208 }
209
210 pub fn remove(&mut self, domain: Domain) {
212 match domain {
213 Domain::Subsurface => self.subsurface = false,
214 Domain::Surface => self.surface = false,
215 Domain::Air => self.air = false,
216 Domain::Unspecified => {} }
218 }
219
220 pub fn contains(&self, domain: Domain) -> bool {
222 match domain {
223 Domain::Subsurface => self.subsurface,
224 Domain::Surface => self.surface,
225 Domain::Air => self.air,
226 Domain::Unspecified => true, }
228 }
229
230 pub fn is_empty(&self) -> bool {
232 !self.subsurface && !self.surface && !self.air
233 }
234
235 pub fn count(&self) -> usize {
237 (self.subsurface as usize) + (self.surface as usize) + (self.air as usize)
238 }
239
240 pub fn is_multi_domain(&self) -> bool {
242 self.count() > 1
243 }
244
245 pub fn intersection(&self, other: &DomainSet) -> DomainSet {
247 DomainSet {
248 subsurface: self.subsurface && other.subsurface,
249 surface: self.surface && other.surface,
250 air: self.air && other.air,
251 }
252 }
253
254 pub fn union(&self, other: &DomainSet) -> DomainSet {
256 DomainSet {
257 subsurface: self.subsurface || other.subsurface,
258 surface: self.surface || other.surface,
259 air: self.air || other.air,
260 }
261 }
262
263 pub fn iter(&self) -> impl Iterator<Item = Domain> + '_ {
265 Domain::all().iter().copied().filter(|d| self.contains(*d))
266 }
267
268 pub fn to_vec(&self) -> Vec<Domain> {
270 self.iter().collect()
271 }
272}
273
274impl std::fmt::Display for DomainSet {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 let domains: Vec<&str> = self.iter().map(|d| d.code()).collect();
277 if domains.is_empty() {
278 write!(f, "NONE")
279 } else {
280 write!(f, "{}", domains.join("+"))
281 }
282 }
283}
284
285#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
287pub enum SensorType {
288 ElectroOptical,
291 Infrared,
294 Radar,
297 Sonar,
300 Acoustic,
303 Sigint,
306 Mad,
309}
310
311impl SensorType {
312 pub fn operating_domains(&self) -> DomainSet {
314 match self {
315 SensorType::ElectroOptical => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
316 SensorType::Infrared => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
317 SensorType::Radar => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
318 SensorType::Sonar => DomainSet::from_domains(&[Domain::Subsurface, Domain::Surface]),
319 SensorType::Acoustic => DomainSet::all(),
320 SensorType::Sigint => DomainSet::all(),
321 SensorType::Mad => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
322 }
323 }
324
325 pub fn detection_domains(&self) -> DomainSet {
327 match self {
328 SensorType::ElectroOptical => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
329 SensorType::Infrared => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
330 SensorType::Radar => DomainSet::from_domains(&[Domain::Surface, Domain::Air]),
331 SensorType::Sonar => DomainSet::from_domains(&[Domain::Subsurface, Domain::Surface]),
332 SensorType::Acoustic => DomainSet::all(),
333 SensorType::Sigint => DomainSet::all(),
334 SensorType::Mad => DomainSet::single(Domain::Subsurface),
336 }
337 }
338
339 pub fn name(&self) -> &'static str {
341 match self {
342 SensorType::ElectroOptical => "Electro-Optical",
343 SensorType::Infrared => "Infrared",
344 SensorType::Radar => "Radar",
345 SensorType::Sonar => "Sonar",
346 SensorType::Acoustic => "Acoustic",
347 SensorType::Sigint => "SIGINT",
348 SensorType::Mad => "MAD",
349 }
350 }
351
352 pub fn code(&self) -> &'static str {
354 match self {
355 SensorType::ElectroOptical => "EO",
356 SensorType::Infrared => "IR",
357 SensorType::Radar => "RAD",
358 SensorType::Sonar => "SON",
359 SensorType::Acoustic => "ACO",
360 SensorType::Sigint => "SIG",
361 SensorType::Mad => "MAD",
362 }
363 }
364}
365
366#[derive(Debug, Clone, PartialEq)]
368pub struct DetectionCheck {
369 pub can_detect: bool,
371 pub reason: String,
373 pub modifier: i32,
375}
376
377impl DetectionCheck {
378 pub fn check(
380 sensor_domain: Domain,
381 sensor_type: SensorType,
382 target_domain: Domain,
383 ) -> DetectionCheck {
384 let operating = sensor_type.operating_domains();
385 let detecting = sensor_type.detection_domains();
386
387 if !operating.contains(sensor_domain) {
389 return DetectionCheck {
390 can_detect: false,
391 reason: format!(
392 "{} cannot operate in {} domain",
393 sensor_type.name(),
394 sensor_domain.name()
395 ),
396 modifier: 0,
397 };
398 }
399
400 if !detecting.contains(target_domain) {
402 return DetectionCheck {
403 can_detect: false,
404 reason: format!(
405 "{} cannot detect targets in {} domain",
406 sensor_type.name(),
407 target_domain.name()
408 ),
409 modifier: 0,
410 };
411 }
412
413 let modifier = if sensor_domain == target_domain {
415 0 } else {
417 match (sensor_domain, target_domain) {
418 (Domain::Air, Domain::Surface) => 1,
420 (Domain::Surface, Domain::Air) => -1,
422 (Domain::Air, Domain::Subsurface) => -2,
424 (Domain::Surface, Domain::Subsurface) => -1,
425 (Domain::Subsurface, Domain::Surface) => -1,
427 _ => 0,
428 }
429 };
430
431 DetectionCheck {
432 can_detect: true,
433 reason: format!(
434 "{} in {} can detect {} targets",
435 sensor_type.name(),
436 sensor_domain.name(),
437 target_domain.name()
438 ),
439 modifier,
440 }
441 }
442}
443
444#[cfg(test)]
445mod tests {
446 use super::*;
447
448 #[test]
449 fn test_domain_names() {
450 assert_eq!(Domain::Subsurface.name(), "Subsurface");
451 assert_eq!(Domain::Surface.name(), "Surface");
452 assert_eq!(Domain::Air.name(), "Air");
453 assert_eq!(Domain::Unspecified.name(), "Unspecified");
454 }
455
456 #[test]
457 fn test_domain_codes() {
458 assert_eq!(Domain::Subsurface.code(), "SUB");
459 assert_eq!(Domain::Surface.code(), "SFC");
460 assert_eq!(Domain::Air.code(), "AIR");
461 }
462
463 #[test]
464 fn test_domain_same_domain_detection() {
465 assert!(Domain::Subsurface.can_detect(Domain::Subsurface));
466 assert!(Domain::Surface.can_detect(Domain::Surface));
467 assert!(Domain::Air.can_detect(Domain::Air));
468 }
469
470 #[test]
471 fn test_domain_cross_domain_detection() {
472 assert!(Domain::Air.can_detect(Domain::Surface));
474 assert!(Domain::Surface.can_detect(Domain::Air));
476 assert!(!Domain::Air.can_detect(Domain::Subsurface));
478 assert!(!Domain::Subsurface.can_detect(Domain::Air));
480 assert!(Domain::Surface.can_detect(Domain::Subsurface));
482 assert!(Domain::Subsurface.can_detect(Domain::Surface));
484 }
485
486 #[test]
487 fn test_domain_engagement() {
488 assert!(Domain::Air.can_engage(Domain::Air));
490 assert!(Domain::Surface.can_engage(Domain::Surface));
491 assert!(Domain::Subsurface.can_engage(Domain::Subsurface));
492
493 assert!(Domain::Air.can_engage(Domain::Surface));
495 assert!(Domain::Air.can_engage(Domain::Subsurface));
497 assert!(Domain::Surface.can_engage(Domain::Air));
499 assert!(Domain::Surface.can_engage(Domain::Subsurface));
501 assert!(Domain::Subsurface.can_engage(Domain::Surface));
503 assert!(!Domain::Subsurface.can_engage(Domain::Air));
505 }
506
507 #[test]
508 fn test_domain_from_i32() {
509 assert_eq!(Domain::try_from(0), Ok(Domain::Unspecified));
510 assert_eq!(Domain::try_from(1), Ok(Domain::Subsurface));
511 assert_eq!(Domain::try_from(2), Ok(Domain::Surface));
512 assert_eq!(Domain::try_from(3), Ok(Domain::Air));
513 assert!(Domain::try_from(99).is_err());
514 }
515
516 #[test]
517 fn test_domain_from_str() {
518 assert_eq!(Domain::parse("subsurface"), Some(Domain::Subsurface));
519 assert_eq!(Domain::parse("SUB"), Some(Domain::Subsurface));
520 assert_eq!(Domain::parse("underwater"), Some(Domain::Subsurface));
521 assert_eq!(Domain::parse("surface"), Some(Domain::Surface));
522 assert_eq!(Domain::parse("ground"), Some(Domain::Surface));
523 assert_eq!(Domain::parse("air"), Some(Domain::Air));
524 assert_eq!(Domain::parse("AIRBORNE"), Some(Domain::Air));
525 assert_eq!(Domain::parse("invalid"), None);
526 }
527
528 #[test]
529 fn test_domain_default() {
530 assert_eq!(Domain::default(), Domain::Unspecified);
531 }
532
533 #[test]
534 fn test_domain_all() {
535 let all = Domain::all();
536 assert_eq!(all.len(), 3);
537 assert!(all.contains(&Domain::Subsurface));
538 assert!(all.contains(&Domain::Surface));
539 assert!(all.contains(&Domain::Air));
540 assert!(!all.contains(&Domain::Unspecified));
541 }
542
543 #[test]
546 fn test_domain_set_empty() {
547 let set = DomainSet::empty();
548 assert!(set.is_empty());
549 assert_eq!(set.count(), 0);
550 assert!(!set.is_multi_domain());
551 }
552
553 #[test]
554 fn test_domain_set_single() {
555 let set = DomainSet::single(Domain::Air);
556 assert!(!set.is_empty());
557 assert_eq!(set.count(), 1);
558 assert!(set.contains(Domain::Air));
559 assert!(!set.contains(Domain::Surface));
560 assert!(!set.is_multi_domain());
561 }
562
563 #[test]
564 fn test_domain_set_all() {
565 let set = DomainSet::all();
566 assert_eq!(set.count(), 3);
567 assert!(set.is_multi_domain());
568 assert!(set.contains(Domain::Subsurface));
569 assert!(set.contains(Domain::Surface));
570 assert!(set.contains(Domain::Air));
571 }
572
573 #[test]
574 fn test_domain_set_from_domains() {
575 let set = DomainSet::from_domains(&[Domain::Air, Domain::Surface]);
576 assert_eq!(set.count(), 2);
577 assert!(set.is_multi_domain());
578 assert!(set.contains(Domain::Air));
579 assert!(set.contains(Domain::Surface));
580 assert!(!set.contains(Domain::Subsurface));
581 }
582
583 #[test]
584 fn test_domain_set_add_remove() {
585 let mut set = DomainSet::empty();
586
587 set.add(Domain::Air);
588 assert!(set.contains(Domain::Air));
589 assert_eq!(set.count(), 1);
590
591 set.add(Domain::Surface);
592 assert_eq!(set.count(), 2);
593
594 set.remove(Domain::Air);
595 assert!(!set.contains(Domain::Air));
596 assert_eq!(set.count(), 1);
597 }
598
599 #[test]
600 fn test_domain_set_intersection() {
601 let set1 = DomainSet::from_domains(&[Domain::Air, Domain::Surface]);
602 let set2 = DomainSet::from_domains(&[Domain::Surface, Domain::Subsurface]);
603
604 let intersection = set1.intersection(&set2);
605 assert_eq!(intersection.count(), 1);
606 assert!(intersection.contains(Domain::Surface));
607 }
608
609 #[test]
610 fn test_domain_set_union() {
611 let set1 = DomainSet::from_domains(&[Domain::Air]);
612 let set2 = DomainSet::from_domains(&[Domain::Surface]);
613
614 let union = set1.union(&set2);
615 assert_eq!(union.count(), 2);
616 assert!(union.contains(Domain::Air));
617 assert!(union.contains(Domain::Surface));
618 }
619
620 #[test]
621 fn test_domain_set_iter() {
622 let set = DomainSet::from_domains(&[Domain::Air, Domain::Subsurface]);
623 let domains: Vec<Domain> = set.iter().collect();
624
625 assert_eq!(domains.len(), 2);
626 assert!(domains.contains(&Domain::Air));
627 assert!(domains.contains(&Domain::Subsurface));
628 }
629
630 #[test]
631 fn test_domain_set_display() {
632 assert_eq!(DomainSet::empty().to_string(), "NONE");
633 assert_eq!(DomainSet::single(Domain::Air).to_string(), "AIR");
634 assert_eq!(
635 DomainSet::from_domains(&[Domain::Air, Domain::Surface]).to_string(),
636 "SFC+AIR"
637 );
638 }
639
640 #[test]
643 fn test_sensor_type_eo_domains() {
644 let eo = SensorType::ElectroOptical;
645 let operating = eo.operating_domains();
646 let detecting = eo.detection_domains();
647
648 assert!(operating.contains(Domain::Surface));
649 assert!(operating.contains(Domain::Air));
650 assert!(!operating.contains(Domain::Subsurface));
651
652 assert!(detecting.contains(Domain::Surface));
653 assert!(detecting.contains(Domain::Air));
654 assert!(!detecting.contains(Domain::Subsurface));
655 }
656
657 #[test]
658 fn test_sensor_type_sonar_domains() {
659 let sonar = SensorType::Sonar;
660 let operating = sonar.operating_domains();
661 let detecting = sonar.detection_domains();
662
663 assert!(operating.contains(Domain::Subsurface));
664 assert!(operating.contains(Domain::Surface));
665 assert!(!operating.contains(Domain::Air));
666
667 assert!(detecting.contains(Domain::Subsurface));
668 assert!(detecting.contains(Domain::Surface));
669 }
670
671 #[test]
672 fn test_sensor_type_mad_domains() {
673 let mad = SensorType::Mad;
674 let operating = mad.operating_domains();
675 let detecting = mad.detection_domains();
676
677 assert!(operating.contains(Domain::Air));
679 assert!(operating.contains(Domain::Surface));
680 assert!(detecting.contains(Domain::Subsurface));
682 assert_eq!(detecting.count(), 1);
683 }
684
685 #[test]
686 fn test_sensor_type_acoustic_all_domains() {
687 let acoustic = SensorType::Acoustic;
688 let operating = acoustic.operating_domains();
689
690 assert_eq!(operating.count(), 3);
691 assert!(operating.contains(Domain::Subsurface));
692 assert!(operating.contains(Domain::Surface));
693 assert!(operating.contains(Domain::Air));
694 }
695
696 #[test]
699 fn test_detection_check_same_domain() {
700 let check = DetectionCheck::check(Domain::Air, SensorType::Radar, Domain::Air);
701 assert!(check.can_detect);
702 assert_eq!(check.modifier, 0);
703 }
704
705 #[test]
706 fn test_detection_check_air_to_surface() {
707 let check = DetectionCheck::check(Domain::Air, SensorType::ElectroOptical, Domain::Surface);
708 assert!(check.can_detect);
709 assert_eq!(check.modifier, 1); }
711
712 #[test]
713 fn test_detection_check_surface_to_air() {
714 let check = DetectionCheck::check(Domain::Surface, SensorType::Radar, Domain::Air);
715 assert!(check.can_detect);
716 assert_eq!(check.modifier, -1); }
718
719 #[test]
720 fn test_detection_check_eo_cannot_detect_subsurface() {
721 let check = DetectionCheck::check(
722 Domain::Surface,
723 SensorType::ElectroOptical,
724 Domain::Subsurface,
725 );
726 assert!(!check.can_detect);
727 assert!(check.reason.contains("cannot detect"));
728 }
729
730 #[test]
731 fn test_detection_check_sonar_cannot_operate_in_air() {
732 let check = DetectionCheck::check(Domain::Air, SensorType::Sonar, Domain::Subsurface);
733 assert!(!check.can_detect);
734 assert!(check.reason.contains("cannot operate"));
735 }
736
737 #[test]
738 fn test_detection_check_mad_from_air_to_subsurface() {
739 let check = DetectionCheck::check(Domain::Air, SensorType::Mad, Domain::Subsurface);
740 assert!(check.can_detect);
741 assert_eq!(check.modifier, -2); }
743
744 #[test]
745 fn test_detection_check_sonar_subsurface_to_surface() {
746 let check = DetectionCheck::check(Domain::Subsurface, SensorType::Sonar, Domain::Surface);
747 assert!(check.can_detect);
748 assert_eq!(check.modifier, -1); }
750}