Skip to main content

terminals_core/substrate/
fold.rs

1//! Fold/Unfold — HVM γ/δ for Sematon serialization.
2//!
3//! Fold compresses a Sematon into a portable binary representation
4//! suitable for AXON wire relay, mesh handoff, or persistence.
5//! Unfold reverses the process.
6
7use serde::{Deserialize, Serialize};
8use super::sematon::Sematon;
9
10/// Folded (serializable) representation of a Sematon.
11/// Payload is JSON-stringified for portability across surfaces.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct FoldedSematon {
14    pub id: String,
15    pub payload_json: String,
16    pub witness_r: f32,
17    pub witness_entropy: f32,
18    pub witness_converged: bool,
19    pub witness_step: u32,
20    pub address_base: u32,
21    pub address_coeff0: u16,
22    pub address_coeff1: u16,
23    pub entropy: f32,
24    pub density: f32,
25    pub impedance: f32,
26    pub shape_hash: u32,
27    pub constructive: bool,
28    pub source: String,
29}
30
31/// Fold a Sematon into a portable representation (HVM γ — construct).
32pub fn fold_sematon<T: Clone + Serialize>(sematon: &Sematon<T>) -> FoldedSematon {
33    FoldedSematon {
34        id: sematon.id.clone(),
35        payload_json: serde_json::to_string(&sematon.payload).unwrap_or_default(),
36        witness_r: sematon.witness.r,
37        witness_entropy: sematon.witness.entropy,
38        witness_converged: sematon.witness.converged,
39        witness_step: sematon.witness.step,
40        address_base: sematon.address.base,
41        address_coeff0: sematon.address.coeff0,
42        address_coeff1: sematon.address.coeff1,
43        entropy: sematon.entropy,
44        density: sematon.density,
45        impedance: sematon.impedance,
46        shape_hash: sematon.shape_hash,
47        constructive: sematon.constructive,
48        source: sematon.source.clone(),
49    }
50}
51
52/// Unfold a portable representation back into a Sematon (HVM δ — duplicate).
53pub fn unfold_sematon<T: Clone + for<'de> Deserialize<'de>>(
54    folded: &FoldedSematon,
55) -> Result<Sematon<T>, String> {
56    let payload: T = serde_json::from_str(&folded.payload_json)
57        .map_err(|e| format!("Failed to deserialize payload: {}", e))?;
58
59    Ok(Sematon {
60        id: folded.id.clone(),
61        payload,
62        witness: super::witness::ConvergenceWitness {
63            r: folded.witness_r,
64            entropy: folded.witness_entropy,
65            converged: folded.witness_converged,
66            step: folded.witness_step,
67        },
68        address: super::graph::PadicAddr {
69            base: folded.address_base,
70            coeff0: folded.address_coeff0,
71            coeff1: folded.address_coeff1,
72        },
73        entropy: folded.entropy,
74        density: folded.density,
75        impedance: folded.impedance,
76        shape_hash: folded.shape_hash,
77        constructive: folded.constructive,
78        source: folded.source.clone(),
79    })
80}
81
82/// Fold a sematon to a JSON string (for AXON wire or mesh relay).
83pub fn fold_to_json<T: Clone + Serialize>(sematon: &Sematon<T>) -> String {
84    let folded = fold_sematon(sematon);
85    serde_json::to_string(&folded).unwrap_or_default()
86}
87
88/// Unfold a sematon from a JSON string.
89pub fn unfold_from_json<T: Clone + for<'de> Deserialize<'de>>(
90    json: &str,
91) -> Result<Sematon<T>, String> {
92    let folded: FoldedSematon =
93        serde_json::from_str(json).map_err(|e| format!("Failed to parse FoldedSematon: {}", e))?;
94    unfold_sematon(&folded)
95}
96
97#[cfg(test)]
98mod tests {
99    use super::*;
100    use super::super::witness::ConvergenceWitness;
101    use super::super::graph::PadicAddr;
102
103    #[test]
104    fn test_fold_unfold_roundtrip() {
105        let w = ConvergenceWitness {
106            r: 0.95,
107            entropy: 0.3,
108            converged: true,
109            step: 42,
110        };
111        let original = Sematon::new(
112            vec![1.0f32, 2.0, 3.0],
113            w,
114            PadicAddr { base: 3, coeff0: 1, coeff1: 7 },
115            "test-surface",
116        );
117
118        let folded = fold_sematon(&original);
119        let restored: Sematon<Vec<f32>> = unfold_sematon(&folded).unwrap();
120
121        assert_eq!(restored.id, original.id);
122        assert_eq!(restored.payload, original.payload);
123        assert!((restored.witness.r - original.witness.r).abs() < 1e-6);
124        assert_eq!(restored.witness.converged, original.witness.converged);
125        assert_eq!(restored.address.base, original.address.base);
126        assert_eq!(restored.shape_hash, original.shape_hash);
127        assert_eq!(restored.constructive, original.constructive);
128    }
129
130    #[test]
131    fn test_fold_to_json_roundtrip() {
132        let w = ConvergenceWitness { r: 0.8, entropy: 0.5, converged: false, step: 10 };
133        let original = Sematon::new("hello world".to_string(), w, PadicAddr::default(), "test");
134
135        let json = fold_to_json(&original);
136        let restored: Sematon<String> = unfold_from_json(&json).unwrap();
137
138        assert_eq!(restored.payload, "hello world");
139        assert_eq!(restored.id, original.id);
140    }
141
142    #[test]
143    fn test_unfold_bad_json_errors() {
144        let result: Result<Sematon<u32>, _> = unfold_from_json("not valid json");
145        assert!(result.is_err());
146    }
147}