terminals_core/substrate/
emergence.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
10#[repr(u8)]
11pub enum OpCategory {
12 Structural = 0,
13 Environmental = 1,
14 Biological = 2,
15 Informational = 3,
16 Temporal = 4,
17 Acoustic = 5,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
22#[repr(u8)]
23pub enum EmergenceRarity {
24 Common = 0,
25 Uncommon = 1,
26 Rare = 2,
27 Legendary = 3,
28}
29
30#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct EmergenceResult {
33 pub op_a: u32,
34 pub op_b: u32,
35 pub rarity: EmergenceRarity,
36 pub behavior_index: u8,
38 pub visual_override: [f32; 3],
40}
41
42const OP_CATEGORIES: [OpCategory; 55] = [
45 OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
47 OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
48 OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
49 OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
50 OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
52 OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
53 OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
54 OpCategory::Environmental,
55 OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
57 OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
58 OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
59 OpCategory::Informational, OpCategory::Informational, OpCategory::Informational,
61 OpCategory::Informational, OpCategory::Informational, OpCategory::Informational,
62 OpCategory::Informational, OpCategory::Informational,
63 OpCategory::Temporal, OpCategory::Temporal, OpCategory::Temporal,
65 OpCategory::Temporal, OpCategory::Temporal, OpCategory::Temporal,
66 OpCategory::Temporal, OpCategory::Temporal,
67 OpCategory::Acoustic, OpCategory::Acoustic, OpCategory::Acoustic,
69 OpCategory::Acoustic, OpCategory::Acoustic, OpCategory::Acoustic,
70 OpCategory::Acoustic, OpCategory::Acoustic,
71];
72
73pub fn op_category(op_id: u32) -> OpCategory {
75 if (op_id as usize) < OP_CATEGORIES.len() {
76 OP_CATEGORIES[op_id as usize]
77 } else {
78 OpCategory::Structural }
80}
81
82fn category_distance(a: OpCategory, b: OpCategory) -> u32 {
85 if (a == OpCategory::Temporal && b == OpCategory::Acoustic)
87 || (a == OpCategory::Acoustic && b == OpCategory::Temporal)
88 {
89 return 4;
90 }
91 let ai = a as i32;
92 let bi = b as i32;
93 (ai - bi).unsigned_abs()
94}
95
96fn distance_to_rarity(distance: u32) -> EmergenceRarity {
98 match distance {
99 0 => EmergenceRarity::Common,
100 1 => EmergenceRarity::Uncommon,
101 2..=3 => EmergenceRarity::Rare,
102 _ => EmergenceRarity::Legendary,
103 }
104}
105
106pub fn compose_ops(op_a: u32, op_b: u32) -> EmergenceResult {
109 let (lo, hi) = if op_a <= op_b { (op_a, op_b) } else { (op_b, op_a) };
110
111 let cat_a = op_category(lo);
112 let cat_b = op_category(hi);
113 let distance = category_distance(cat_a, cat_b);
114 let rarity = distance_to_rarity(distance);
115
116 let pair_hash = lo.wrapping_mul(7919) ^ hi.wrapping_mul(104729);
118 let behavior_index = (pair_hash % 16) as u8;
119
120 let visual_override = match rarity {
122 EmergenceRarity::Common => [0.0, 0.1, 0.2],
123 EmergenceRarity::Uncommon => [0.15, 0.2, 0.4],
124 EmergenceRarity::Rare => [0.3, 0.4, 0.7],
125 EmergenceRarity::Legendary => [0.5, 0.6, 1.0],
126 };
127
128 EmergenceResult {
129 op_a: lo,
130 op_b: hi,
131 rarity,
132 behavior_index,
133 visual_override,
134 }
135}
136
137pub fn best_emergence(active_ops: u64) -> Option<EmergenceResult> {
141 let ops: Vec<u32> = (0u32..55).filter(|&i| active_ops & (1u64 << i) != 0).collect();
142 if ops.len() < 2 {
143 return None;
144 }
145
146 let mut best: Option<EmergenceResult> = None;
147 for i in 0..ops.len() {
148 for j in (i + 1)..ops.len() {
149 let result = compose_ops(ops[i], ops[j]);
150 if best.as_ref().is_none_or(|b| result.rarity > b.rarity) {
151 best = Some(result);
152 }
153 }
154 }
155 best
156}
157
158pub fn emergence_to_json(result: &EmergenceResult) -> String {
160 serde_json::to_string(result).unwrap_or_default()
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 #[test]
168 fn test_same_category_common() {
169 let result = compose_ops(0, 1);
171 assert_eq!(result.rarity, EmergenceRarity::Common);
172 }
173
174 #[test]
175 fn test_adjacent_category_uncommon() {
176 let result = compose_ops(11, 12);
178 assert_eq!(result.rarity, EmergenceRarity::Uncommon);
179 }
180
181 #[test]
182 fn test_distant_category_rare() {
183 let result = compose_ops(0, 31);
185 assert_eq!(result.rarity, EmergenceRarity::Rare);
186 }
187
188 #[test]
189 fn test_temporal_acoustic_legendary() {
190 let result = compose_ops(39, 47);
192 assert_eq!(result.rarity, EmergenceRarity::Legendary);
193 }
194
195 #[test]
196 fn test_compose_deterministic() {
197 let r1 = compose_ops(5, 20);
198 let r2 = compose_ops(5, 20);
199 assert_eq!(r1.rarity, r2.rarity);
200 assert_eq!(r1.behavior_index, r2.behavior_index);
201 }
202
203 #[test]
204 fn test_compose_symmetric() {
205 let r1 = compose_ops(5, 20);
206 let r2 = compose_ops(20, 5);
207 assert_eq!(r1.rarity, r2.rarity);
208 assert_eq!(r1.op_a, r2.op_a);
209 assert_eq!(r1.op_b, r2.op_b);
210 }
211
212 #[test]
213 fn test_best_emergence_no_ops() {
214 assert!(best_emergence(0).is_none());
215 }
216
217 #[test]
218 fn test_best_emergence_single_op() {
219 assert!(best_emergence(1 << 5).is_none());
220 }
221
222 #[test]
223 fn test_best_emergence_multiple() {
224 let bits = (1u64 << 0) | (1u64 << 39) | (1u64 << 47);
226 let best = best_emergence(bits).unwrap();
227 assert_eq!(best.rarity, EmergenceRarity::Legendary); }
229
230 #[test]
231 fn test_emergence_to_json() {
232 let result = compose_ops(0, 47);
233 let json = emergence_to_json(&result);
234 assert!(json.contains("rarity"));
235 assert!(json.contains("visual_override"));
236 }
237
238 #[test]
239 fn test_op_category_bounds() {
240 assert_eq!(op_category(0), OpCategory::Structural);
241 assert_eq!(op_category(54), OpCategory::Acoustic);
242 assert_eq!(op_category(100), OpCategory::Structural); }
244}