Skip to main content

terminals_core/substrate/
projection.rs

1//! Projection trait — the core abstraction for ComputeAtom views.
2//!
3//! Each projection is a deterministic pure function over a byte slice.
4//! Projections define their own memory layout (byte_size) and contribute
5//! to the atom's shape hash (σ).
6
7/// Unique identifier for a projection type.
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
9pub enum ProjectionId {
10    Splat,
11    Kuramoto,
12    Expert,
13    Graph,
14    Thermal,
15}
16
17impl ProjectionId {
18    /// Deterministic hash contribution for the projection type itself.
19    pub fn type_hash(self) -> u32 {
20        match self {
21            ProjectionId::Splat => 0x5A1A_0001,
22            ProjectionId::Kuramoto => 0x4B55_0002,
23            ProjectionId::Expert => 0x3E58_0003,
24            ProjectionId::Graph => 0x4752_0004,
25            ProjectionId::Thermal => 0x5448_0005,
26        }
27    }
28}
29
30/// A projection is a typed view over a contiguous byte region within a ComputeAtom.
31///
32/// Implementations must be deterministic: same bytes in → same state out.
33/// `byte_size()` is const per type (known at compile time).
34pub trait Projection: Sized {
35    /// Fixed byte size of this projection's memory region.
36    fn byte_size() -> usize;
37
38    /// Projection type identifier.
39    fn id() -> ProjectionId;
40
41    /// Read projection state from a byte slice.
42    /// Panics if `buf.len() < Self::byte_size()`.
43    fn read(buf: &[u8]) -> Self;
44
45    /// Write projection state into a byte slice.
46    /// Panics if `buf.len() < Self::byte_size()`.
47    fn write(&self, buf: &mut [u8]);
48
49    /// Contribution to the atom's shape hash (FNV-1a input).
50    fn shape_hash_contribution(&self) -> u32;
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    #[test]
58    fn test_projection_id_type_hash_unique() {
59        let ids = [
60            ProjectionId::Splat,
61            ProjectionId::Kuramoto,
62            ProjectionId::Expert,
63            ProjectionId::Graph,
64            ProjectionId::Thermal,
65        ];
66        let hashes: Vec<u32> = ids.iter().map(|id| id.type_hash()).collect();
67        for i in 0..hashes.len() {
68            for j in (i + 1)..hashes.len() {
69                assert_ne!(hashes[i], hashes[j], "Hash collision between {:?} and {:?}", ids[i], ids[j]);
70            }
71        }
72    }
73}