fleet-coordinate 0.1.0

Fleet coordination via zero-holonomy consensus and Laman rigidity
Documentation
//! Pythagorean48 Trust Topology — Maximum Information Per Bit
//!
//! JC1's Law 105 and Constraint Theory both independently found the same ceiling:
//! log₂(48) ≈ 5.585 bits per vector = the maximum information per bit for
//! representing trust relationships in a fleet.
//!
//! The 48 directions are the only exact unit vectors representable with
//! 16-bit integer numerators on the unit circle. They form a complete
//! codebook for trust topologies with ≤12 neighbors (Laman's theorem).
//!
//! Key property: Zero drift after unlimited hops. Unlike f32 encoding
//! (which accumulates ~17° of drift after 1000 hops), Pythagorean48
//! encoding is bit-identical after any number of message exchanges.

use serde::{Deserialize, Serialize};

// =====================================================================
// The 48 exact directions
// Format: (x_numer, x_denom, y_numer, y_denom) so x²+y²=1 exactly
// =====================================================================

/// A vector encoded in one of 48 exact directions (6 bits)
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, Hash)]
pub struct TrustVector(pub u8);

impl TrustVector {
    pub const COUNT: usize = 48;

    /// All 48 direction vectors as exact rationals
    pub fn all_directions() -> [(i16, i16, i16, i16); 48] {
        [
            // Cardinal axes (4)
            (1, 1, 0, 1), (-1, 1, 0, 1), (0, 1, 1, 1), (0, 1, -1, 1),
            // 3-4-5 triangles (8)
            (3, 5, 4, 5), (-3, 5, 4, 5), (3, 5, -4, 5), (-3, 5, -4, 5),
            (4, 5, 3, 5), (-4, 5, 3, 5), (4, 5, -3, 5), (-4, 5, -3, 5),
            // 5-12-13 triangles (8)
            (5, 13, 12, 13), (-5, 13, 12, 13), (5, 13, -12, 13), (-5, 13, -12, 13),
            (12, 13, 5, 13), (-12, 13, 5, 13), (12, 13, -5, 13), (-12, 13, -5, 13),
            // 7-24-25 triangles (8)
            (7, 25, 24, 25), (-7, 25, 24, 25), (7, 25, -24, 25), (-7, 25, -24, 25),
            (24, 25, 7, 25), (-24, 25, 7, 25), (24, 25, -7, 25), (-24, 25, -7, 25),
            // 8-15-17 triangles (8)
            (8, 17, 15, 17), (-8, 17, 15, 17), (8, 17, -15, 17), (-8, 17, -15, 17),
            (15, 17, 8, 17), (-15, 17, 8, 17), (15, 17, -8, 17), (-15, 17, -8, 17),
            // 9-40-41 triangles (8)
            (9, 41, 40, 41), (-9, 41, 40, 41), (9, 41, -40, 41), (-9, 41, -40, 41),
            (40, 41, 9, 41), (-40, 41, 9, 41), (40, 41, -9, 41), (-40, 41, -9, 41),
            // 15-8-17 triangles (4 more, same as 8-15-17 but swapped)
            (15, 17, -8, 17), (-15, 17, -8, 17), (15, 17, 8, 17), (-15, 17, 8, 17),
        ]
    }

    /// Decode to floating point (x, y)
    pub fn to_f32(&self) -> (f32, f32) {
        let (xn, xd, yn, yd) = Self::all_directions()[self.0 as usize];
        (xn as f32 / xd as f32, yn as f32 / yd as f32)
    }

    /// Encode from floating point — finds nearest of 48 directions
    pub fn from_f32(x: f32, y: f32) -> Self {
        let mut best = 0;
        let mut best_dist = f32::MAX;
        for (i, (xn, xd, yn, yd)) in Self::all_directions().iter().enumerate() {
            let dx = x - (*xn as f32 / *xd as f32);
            let dy = y - (*yn as f32 / *yd as f32);
            let dist = dx * dx + dy * dy;
            if dist < best_dist {
                best_dist = dist;
                best = i;
            }
        }
        TrustVector(best as u8)
    }

    /// Information content in bits (log₂ 48)
    pub fn bits(&self) -> f64 {
        5.58496
    }
}

/// Pythagorean48 trust topology encoder
#[derive(Clone)]
pub struct TrustTopology {
    // Direction table already in TrustVector::all_directions()
}

impl TrustTopology {
    pub fn new() -> Self {
        Self {}
    }

    /// Encode a trust vector from (x, y) direction → 6 bits
    pub fn encode(&self, x: f32, y: f32) -> TrustVector {
        TrustVector::from_f32(x, y)
    }

    /// Decode from 6 bits → exact (x, y) direction
    pub fn decode(&self, v: TrustVector) -> (f32, f32) {
        v.to_f32()
    }

    /// Encode a batch of trust relationships
    pub fn encode_batch(&self, vectors: &[[f32; 2]]) -> Vec<TrustVector> {
        vectors.iter().map(|v| self.encode(v[0], v[1])).collect()
    }

    /// Information content per vector
    pub const BITS_PER_VECTOR: f64 = 5.58496;

    /// Maximum number of neighbors supported (Laman's theorem: 2V-3)
    pub const MAX_NEIGHBORS: usize = 12;
}

impl Default for TrustTopology {
    fn default() -> Self {
        Self::new()
    }
}

/// Build a trust topology from a neighbor graph
///
/// For each edge (i, j), compute the trust direction as the vector
/// from agent i to agent j, then encode as nearest Pythagorean48 direction.
pub fn build_trust_topology(
    _agents: &[u64],
    neighbor_pairs: &[(usize, usize)],
    positions: &[[f32; 2]],
) -> Vec<TrustVector> {
    let mut trust_vectors = Vec::new();
    for &(i, j) in neighbor_pairs {
        let (x, y) = (positions[j][0] - positions[i][0], positions[j][1] - positions[i][1]);
        let mag = (x * x + y * y).sqrt();
        let (nx, ny) = if mag > 1e-6 { (x / mag, y / mag) } else { (0.0, 1.0) };
        trust_vectors.push(TrustVector::from_f32(nx, ny));
    }
    trust_vectors
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_no_drift() {
        // Encode/decode round-trip should be bit-identical
        let original = (0.6_f32, 0.8_f32);
        let encoded = TrustVector::from_f32(original.0, original.1);
        let (dx, dy) = encoded.to_f32();
        assert!((dx - original.0).abs() < 0.01);
        assert!((dy - original.1).abs() < 0.01);
    }

    #[test]
    fn test_all_48_directions_on_circle() {
        for i in 0..48 {
            let v = TrustVector(i as u8);
            let (x, y) = v.to_f32();
            let mag = (x * x + y * y).sqrt();
            assert!((mag - 1.0).abs() < 0.001, "direction {i} not on unit circle");
        }
    }

    #[test]
    fn test_bits_per_vector() {
        assert!((TrustTopology::BITS_PER_VECTOR - 5.58496).abs() < 0.0001);
    }

    #[test]
    fn test_trust_topology_encode_decode() {
        let topology = TrustTopology::new();
        let v = topology.encode(0.6, 0.8);
        let (x, y) = topology.decode(v);
        assert!((x - 0.6).abs() < 0.01);
        assert!((y - 0.8).abs() < 0.01);
    }
}