use crate::earth_colony_protocol::PlanetaryBody;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum AggregationStrategy {
Synchronous,
AsyncWeighted,
EpochBased { ticks_per_epoch: u32 },
Autonomous,
}
impl AggregationStrategy {
pub fn for_latency(one_way_delay_secs: f64) -> Self {
if one_way_delay_secs < 10.0 {
Self::Synchronous
} else if one_way_delay_secs < 1800.0 {
Self::AsyncWeighted
} else if one_way_delay_secs < 5400.0 {
Self::EpochBased {
ticks_per_epoch: 12,
}
} else {
Self::Autonomous
}
}
pub fn for_body(body: PlanetaryBody) -> Self {
Self::for_latency(body.light_delay_to_earth_secs())
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LatencyWeighting {
pub source: PlanetaryBody,
pub base_weight: f64,
pub staleness_ticks: u32,
pub tau: f64,
pub effective_weight: f64,
}
impl LatencyWeighting {
pub fn compute(source: PlanetaryBody, base_weight: f64, staleness_ticks: u32) -> Self {
let tau = match source {
PlanetaryBody::Moon => 1.0, PlanetaryBody::Mars => 3.0, PlanetaryBody::Europa => 6.0, PlanetaryBody::Titan => 12.0, _ => 2.0,
};
let effective_weight = base_weight * (-1.0 * staleness_ticks as f64 / tau).exp();
Self {
source,
base_weight,
staleness_ticks,
tau,
effective_weight,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CrossPlanetaryCheckpoint {
pub source: PlanetaryBody,
pub source_tick: u32,
pub arrival_tick: u32,
pub model_version: u64,
pub local_samples: u64,
pub quality_score: f64,
pub weights_hash: [u8; 32],
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_aggregation_strategy_selection() {
assert_eq!(
AggregationStrategy::for_body(PlanetaryBody::Moon),
AggregationStrategy::Synchronous
);
assert_eq!(
AggregationStrategy::for_body(PlanetaryBody::Mars),
AggregationStrategy::AsyncWeighted
);
assert!(matches!(
AggregationStrategy::for_body(PlanetaryBody::Europa),
AggregationStrategy::EpochBased { .. }
));
assert!(matches!(
AggregationStrategy::for_body(PlanetaryBody::Titan),
AggregationStrategy::EpochBased { .. }
));
}
#[test]
fn test_staleness_penalty() {
let fresh = LatencyWeighting::compute(PlanetaryBody::Mars, 1.0, 0);
let stale = LatencyWeighting::compute(PlanetaryBody::Mars, 1.0, 6);
assert!(fresh.effective_weight > stale.effective_weight);
let titan_stale = LatencyWeighting::compute(PlanetaryBody::Titan, 1.0, 6);
assert!(titan_stale.effective_weight > stale.effective_weight);
}
#[test]
fn test_fresh_update_full_weight() {
let w = LatencyWeighting::compute(PlanetaryBody::Earth, 0.8, 0);
assert!((w.effective_weight - 0.8).abs() < 0.001);
}
}