use crate::status::{STATUS_PRECEDENCE_MASK, STATUS_PRECEDENCE_SHIFT};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[repr(u8)]
pub enum ModePrecedence {
#[default]
Hold = 0,
HardSeparation = 1,
Operator = 2,
Formation = 3,
Flocking = 4,
}
impl ModePrecedence {
pub const fn as_status_bits(self) -> u8 {
((self as u8) << STATUS_PRECEDENCE_SHIFT) & STATUS_PRECEDENCE_MASK
}
pub const fn from_status_bits(status: u8) -> Self {
match (status & STATUS_PRECEDENCE_MASK) >> STATUS_PRECEDENCE_SHIFT {
1 => Self::HardSeparation,
2 => Self::Operator,
3 => Self::Formation,
4 => Self::Flocking,
_ => Self::Hold,
}
}
pub const fn as_wire(self) -> &'static str {
match self {
Self::HardSeparation => "hard-separation",
Self::Operator => "operator",
Self::Formation => "formation",
Self::Flocking => "flocking",
Self::Hold => "hold",
}
}
pub fn from_wire(s: &str) -> Self {
match s {
"hard-separation" => Self::HardSeparation,
"operator" => Self::Operator,
"formation" => Self::Formation,
"flocking" => Self::Flocking,
_ => Self::Hold,
}
}
pub const ARBITRATION_ORDER: [Self; 5] = [
Self::HardSeparation,
Self::Operator,
Self::Formation,
Self::Flocking,
Self::Hold,
];
}
pub fn precedence_rank(level: ModePrecedence) -> usize {
ModePrecedence::ARBITRATION_ORDER
.iter()
.position(|c| *c == level)
.unwrap_or(ModePrecedence::ARBITRATION_ORDER.len())
}
pub fn arbitrate(active: impl IntoIterator<Item = ModePrecedence>) -> ModePrecedence {
active
.into_iter()
.min_by_key(|l| precedence_rank(*l))
.unwrap_or(ModePrecedence::Hold)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::status::{STATUS_ARMED, STATUS_HERO};
#[test]
fn from_wire_inverts_as_wire_and_degrades_unknown_to_hold() {
for level in ModePrecedence::ARBITRATION_ORDER {
assert_eq!(ModePrecedence::from_wire(level.as_wire()), level);
}
assert_eq!(
ModePrecedence::from_wire("murmuration"),
ModePrecedence::Hold
);
assert_eq!(ModePrecedence::from_wire(""), ModePrecedence::Hold);
}
#[test]
fn every_level_round_trips_through_the_status_field() {
let mut seen = std::collections::BTreeSet::new();
for level in ModePrecedence::ARBITRATION_ORDER {
let bits = level.as_status_bits();
assert_eq!(bits & !STATUS_PRECEDENCE_MASK, 0, "{level:?} leaked a bit");
assert!(seen.insert(bits), "{level:?} duplicates another encoding");
assert_eq!(ModePrecedence::from_status_bits(bits), level);
assert_eq!(
ModePrecedence::from_status_bits(bits | STATUS_ARMED | STATUS_HERO),
level
);
}
assert_eq!(seen.len(), 5);
}
#[test]
fn unassigned_encodings_degrade_to_hold() {
for raw in [5u8, 6, 7] {
let status = raw << STATUS_PRECEDENCE_SHIFT;
assert_eq!(
ModePrecedence::from_status_bits(status),
ModePrecedence::Hold,
"encoding {raw} must degrade to hold"
);
}
}
#[test]
fn the_arbitration_ladder_is_highest_authority_first() {
assert_eq!(
ModePrecedence::ARBITRATION_ORDER.map(|l| l.as_wire()),
[
"hard-separation",
"operator",
"formation",
"flocking",
"hold"
]
);
}
const ALL: [ModePrecedence; 5] = [
ModePrecedence::Hold,
ModePrecedence::HardSeparation,
ModePrecedence::Operator,
ModePrecedence::Formation,
ModePrecedence::Flocking,
];
#[test]
fn hold_is_the_zero_value_so_a_silent_node_is_truthful() {
assert_eq!(ModePrecedence::default(), ModePrecedence::Hold);
assert_eq!(ModePrecedence::Hold as u8, 0);
assert_eq!(ModePrecedence::Hold.as_status_bits(), 0);
assert_eq!(ModePrecedence::from_status_bits(0), ModePrecedence::Hold);
}
#[test]
fn wire_strings_are_exactly_the_contract() {
assert_eq!(ModePrecedence::HardSeparation.as_wire(), "hard-separation");
assert_eq!(ModePrecedence::Operator.as_wire(), "operator");
assert_eq!(ModePrecedence::Formation.as_wire(), "formation");
assert_eq!(ModePrecedence::Flocking.as_wire(), "flocking");
assert_eq!(ModePrecedence::Hold.as_wire(), "hold");
}
#[test]
fn status_bits_round_trip_without_touching_the_condition_bits() {
let flags: u8 = 0b0001_1111;
for level in ALL {
let packed = flags | level.as_status_bits();
assert_eq!(packed & 0b0001_1111, flags, "{level:?} clobbered a flag");
assert_eq!(ModePrecedence::from_status_bits(packed), level);
}
}
#[test]
fn arbitration_order_enumerates_every_variant_once() {
let mut seen = ModePrecedence::ARBITRATION_ORDER.to_vec();
seen.sort_by_key(|l| *l as u8);
seen.dedup();
assert_eq!(seen.len(), 5, "ARBITRATION_ORDER must be a permutation");
for level in ALL {
assert!(ModePrecedence::ARBITRATION_ORDER.contains(&level));
}
}
#[test]
fn rank_is_the_ladder_and_not_the_discriminant() {
assert_eq!(precedence_rank(ModePrecedence::HardSeparation), 0);
assert_eq!(precedence_rank(ModePrecedence::Operator), 1);
assert_eq!(precedence_rank(ModePrecedence::Formation), 2);
assert_eq!(precedence_rank(ModePrecedence::Flocking), 3);
assert_eq!(precedence_rank(ModePrecedence::Hold), 4);
assert!(
precedence_rank(ModePrecedence::HardSeparation)
< precedence_rank(ModePrecedence::Flocking)
);
assert!(
(ModePrecedence::HardSeparation as u8) < (ModePrecedence::Flocking as u8),
"discriminant order really is the trap this test describes"
);
}
#[test]
fn arbitrate_picks_the_correct_level_for_every_combination() {
for mask in 1u32..32 {
let mut subset = Vec::new();
for (bit, level) in ALL.iter().enumerate() {
if mask & (1 << bit) != 0 {
subset.push(*level);
}
}
let expect = *subset
.iter()
.min_by_key(|l| precedence_rank(**l))
.expect("non-empty");
assert_eq!(arbitrate(subset.clone()), expect, "subset {subset:?}");
subset.reverse();
assert_eq!(
arbitrate(subset.clone()),
expect,
"order must not matter: {subset:?}"
);
}
}
#[test]
fn arbitrate_of_nothing_is_hold() {
assert_eq!(arbitrate(std::iter::empty()), ModePrecedence::Hold);
}
}