1use std::collections::HashMap;
54
55use std::sync::{Arc, RwLock};
56
57#[derive(Debug, Clone, Copy, PartialEq, Hash)]
61
62pub enum GrowthPattern {
63 Mycelium,
65
66 Capillary,
68
69 SlimeMold,
71
72 FractalLSystem,
74
75 Hybrid,
77}
78
79#[derive(Debug, Clone, PartialEq, Hash)]
81
82pub enum NodeType {
83 Root,
85
86 Service,
88
89 Database,
91
92 Cache,
94
95 Gateway,
97
98 LoadBalancer,
100
101 Queue,
103
104 Leaf,
106
107 Custom(String),
109}
110
111#[derive(Debug, Clone, PartialEq, Hash)]
113
114pub enum ConnectionType {
115 DataFlow,
117
118 Control,
120
121 Replication,
123
124 Backup,
126}
127
128#[derive(Debug, Clone)]
132
133pub struct Seed {
134 pub id: String,
136
137 pub position: (f64, f64, f64),
139
140 pub energy: f64,
142
143 pub type_tag: String,
145
146 pub preferred_pattern: Option<GrowthPattern>,
148}
149
150impl Seed {
151 pub fn new(id: &str, x: f64, y: f64, z: f64, type_tag: &str) -> Self {
152 Self {
153 id: id.to_string(),
154
155 position: (x, y, z),
156
157 energy: 100.0,
158
159 type_tag: type_tag.to_string(),
160
161 preferred_pattern: None,
162 }
163 }
164
165 pub fn with_energy(mut self, energy: f64) -> Self {
166 self.energy = energy;
167
168 self
169 }
170
171 pub fn with_pattern(mut self, pattern: GrowthPattern) -> Self {
172 self.preferred_pattern = Some(pattern);
173
174 self
175 }
176
177 pub fn rng(&self) -> rand::rngs::StdRng {
180 use rand::SeedableRng;
181 let mut h = 0xcbf29ce484222325u64;
182 for b in self.id.as_bytes() {
183 h ^= *b as u64;
184 h = h.wrapping_mul(0x100000001b3);
185 }
186 h = h.wrapping_mul(31).wrapping_add(self.position.0.to_bits());
187 h = h.wrapping_mul(31).wrapping_add(self.position.1.to_bits());
188 h = h.wrapping_mul(31).wrapping_add(self.position.2.to_bits());
189 rand::rngs::StdRng::seed_from_u64(h)
190 }
191}
192
193#[derive(Debug, Clone)]
195
196pub struct MorphogenField {
197 pub gradients: HashMap<(i32, i32, i32), f64>,
199
200 pub attractors: Vec<(f64, f64, f64, f64)>, pub repellents: Vec<(f64, f64, f64, f64)>,
204
205 pub diffusion_rate: f64,
207
208 pub evaporation_rate: f64,
210}
211
212impl Default for MorphogenField {
213 fn default() -> Self {
214 Self::new()
215 }
216}
217
218impl MorphogenField {
219 pub fn new() -> Self {
220 Self {
221 gradients: HashMap::new(),
222
223 attractors: Vec::new(),
224
225 repellents: Vec::new(),
226
227 diffusion_rate: 0.1,
228
229 evaporation_rate: 0.01,
230 }
231 }
232
233 pub fn concentration_at(&self, x: f64, y: f64, z: f64) -> f64 {
235 let gx = x.floor() as i32;
236
237 let gy = y.floor() as i32;
238
239 let gz = z.floor() as i32;
240
241 let mut total = 0.0;
242
243 for dx in -1..=1 {
244 for dy in -1..=1 {
245 for dz in -1..=1 {
246 if let Some(val) = self.gradients.get(&(gx + dx, gy + dy, gz + dz)) {
247 let dist = ((x - (gx + dx) as f64).powi(2)
248 + (y - (gy + dy) as f64).powi(2)
249 + (z - (gz + dz) as f64).powi(2))
250 .sqrt()
251 .max(0.001);
252
253 total += val / dist;
254 }
255 }
256 }
257 }
258
259 for (ax, ay, az, strength) in &self.attractors {
262 let dist = ((x - ax).powi(2) + (y - ay).powi(2) + (z - az).powi(2))
263 .sqrt()
264 .max(0.001);
265
266 total += strength / dist;
267 }
268
269 for (rx, ry, rz, strength) in &self.repellents {
272 let dist = ((x - rx).powi(2) + (y - ry).powi(2) + (z - rz).powi(2))
273 .sqrt()
274 .max(0.001);
275
276 total -= strength / dist;
277 }
278
279 total
280 }
281
282 pub fn add_attractor(&mut self, x: f64, y: f64, z: f64, strength: f64) {
284 self.attractors.push((x, y, z, strength));
285
286 let key = (x.floor() as i32, y.floor() as i32, z.floor() as i32);
287
288 *self.gradients.entry(key).or_insert(0.0) += strength;
289 }
290
291 pub fn diffuse(&mut self) {
293 let old = self.gradients.clone();
294
295 for ((x, y, z), val) in &old {
296 if *val < 0.001 {
297 continue;
298 }
299
300 for dx in -1..=1 {
301 for dy in -1..=1 {
302 for dz in -1..=1 {
303 if dx == 0 && dy == 0 && dz == 0 {
304 continue;
305 }
306
307 let spread = val * self.diffusion_rate / 26.0;
308
309 *self
310 .gradients
311 .entry((x + dx, y + dy, z + dz))
312 .or_insert(0.0) += spread;
313 }
314 }
315 }
316 }
317 }
318
319 pub fn evaporate(&mut self) {
321 self.gradients.retain(|_, v| {
322 *v *= 1.0 - self.evaporation_rate;
323
324 *v > 0.001
325 });
326 }
327}
328
329#[derive(Debug, Clone)]
331
332pub struct GrowthConfig {
333 pub pattern: GrowthPattern,
335
336 pub max_nodes: usize,
338
339 pub branching_probability: f64,
341
342 pub branching_angle: f64,
344
345 pub energy_decay: f64,
347
348 pub min_energy_for_branch: f64,
350
351 pub max_depth: u32,
353
354 pub anastomosis_probability: f64,
356
357 pub prune_idle_cycles: u32,
359
360 pub trail_persistence: f64,
362
363 pub trail_evaporation: f64,
365
366 pub lsystem_rules: Vec<(char, String)>,
368}
369
370impl Default for GrowthConfig {
371 fn default() -> Self {
372 Self {
373 pattern: GrowthPattern::Mycelium,
374
375 max_nodes: 500,
376
377 branching_probability: 0.3,
378
379 branching_angle: 0.6,
380
381 energy_decay: 0.1,
382
383 min_energy_for_branch: 30.0,
384
385 max_depth: 20,
386
387 anastomosis_probability: 0.05,
388
389 prune_idle_cycles: 10,
390
391 trail_persistence: 0.9,
392
393 trail_evaporation: 0.05,
394
395 lsystem_rules: vec![('A', "AB".to_string()), ('B', "A[B]A".to_string())],
396 }
397 }
398}
399
400impl GrowthConfig {
401 pub fn mycelium_default() -> Self {
402 Self {
403 pattern: GrowthPattern::Mycelium,
404
405 branching_probability: 0.35,
406
407 branching_angle: 0.8,
408
409 energy_decay: 0.08,
410
411 min_energy_for_branch: 25.0,
412
413 anastomosis_probability: 0.08,
414
415 ..Self::default()
416 }
417 }
418
419 pub fn capillary_default() -> Self {
420 Self {
421 pattern: GrowthPattern::Capillary,
422
423 branching_probability: 0.5,
424
425 branching_angle: 0.4,
426
427 energy_decay: 0.15,
428
429 min_energy_for_branch: 20.0,
430
431 max_depth: 12,
432
433 ..Self::default()
434 }
435 }
436
437 pub fn slime_mold_default() -> Self {
438 Self {
439 pattern: GrowthPattern::SlimeMold,
440
441 max_nodes: 300,
442
443 trail_persistence: 0.9,
444
445 trail_evaporation: 0.01,
446
447 ..Self::default()
448 }
449 }
450
451 pub fn fractal_lsystem_default() -> Self {
452 Self {
453 pattern: GrowthPattern::FractalLSystem,
454
455 branching_angle: 0.5,
456
457 max_depth: 8,
458
459 lsystem_rules: vec![('F', "FF+[+F-F-F]-[-F+F+F]".to_string())],
460
461 ..Self::default()
462 }
463 }
464}
465
466#[derive(Debug, Clone)]
468
469pub struct MorphNode {
470 pub id: usize,
471
472 pub position: (f64, f64, f64),
473
474 pub node_type: NodeType,
475
476 pub name: String,
477
478 pub latency_base_ms: f64,
479
480 pub capacity: f64,
481
482 pub energy: f64,
483
484 pub depth: u32,
485
486 pub metadata: HashMap<String, String>,
487}
488
489#[derive(Debug, Clone)]
491
492pub struct MorphConnection {
493 pub id: usize,
494
495 pub from_node: usize,
496
497 pub to_node: usize,
498
499 pub weight: f64,
500
501 pub bandwidth: f64,
502
503 pub protocol: String,
504
505 pub latency_ms: f64,
506
507 pub connection_type: ConnectionType,
508
509 pub is_active: bool,
510
511 pub age_cycles: u32,
512}
513
514#[derive(Debug, Clone)]
516
517pub struct GrowthMetrics {
518 pub node_count: usize,
519
520 pub connection_count: usize,
521
522 pub max_depth: u32,
523
524 pub avg_branching_factor: f64,
525
526 pub fractal_dimension: f64,
527
528 pub total_energy: f64,
529
530 pub redundancy_score: f64,
531
532 pub avg_path_length: f64,
533}
534
535#[derive(Debug, Clone)]
537
538pub struct Organism {
539 pub id: String,
540
541 pub name: String,
542
543 pub nodes: Vec<MorphNode>,
544
545 pub connections: Vec<MorphConnection>,
546
547 pub growth_pattern: GrowthPattern,
548
549 pub generation: u32,
550
551 pub fitness_score: f64,
552
553 pub age_cycles: u32,
554
555 pub seed: Seed,
556
557 pub metrics: Option<GrowthMetrics>,
558
559 pub metadata: HashMap<String, String>,
560}
561
562impl Organism {
563 pub fn new(id: &str, name: &str, seed: Seed, pattern: GrowthPattern) -> Self {
564 Self {
565 id: id.to_string(),
566
567 name: name.to_string(),
568
569 nodes: Vec::new(),
570
571 connections: Vec::new(),
572
573 growth_pattern: pattern,
574
575 generation: 0,
576
577 fitness_score: 0.0,
578
579 age_cycles: 0,
580
581 seed,
582
583 metrics: None,
584
585 metadata: HashMap::new(),
586 }
587 }
588
589 pub fn calculate_metrics(&mut self) -> GrowthMetrics {
591 let node_count = self.nodes.len();
592
593 let connection_count = self.connections.len();
594
595 let max_depth = self.nodes.iter().map(|n| n.depth).max().unwrap_or(0);
596
597 let branching = if node_count > 1 {
600 let internal_nodes = self
601 .nodes
602 .iter()
603 .filter(|n| {
604 self.connections
605 .iter()
606 .filter(|c| c.from_node == n.id)
607 .count()
608 > 1
609 })
610 .count();
611
612 internal_nodes as f64 / node_count.max(1) as f64
613 } else {
614 0.0
615 };
616
617 let redundancy = if node_count > 1 {
620 connection_count as f64 / (node_count - 1).max(1) as f64
621 } else {
622 0.0
623 };
624
625 let avg_path = if connection_count > 0 && node_count > 1 {
628 (node_count as f64).ln() / (redundancy.max(0.1)).ln()
629 } else {
630 0.0
631 };
632
633 let total_energy: f64 = self.nodes.iter().map(|n| n.energy).sum();
634
635 let metrics = GrowthMetrics {
636 node_count,
637
638 connection_count,
639
640 max_depth,
641
642 avg_branching_factor: branching,
643
644 fractal_dimension: (node_count as f64).ln() / ((max_depth.max(2)) as f64).ln(),
645
646 total_energy,
647
648 redundancy_score: redundancy,
649
650 avg_path_length: avg_path,
651 };
652
653 self.metrics = Some(metrics.clone());
654
655 metrics
656 }
657
658 pub fn is_alive(&self) -> bool {
660 self.nodes.iter().any(|n| n.energy > 0.0)
661 }
662
663 pub fn age_one_cycle(&mut self) {
665 self.age_cycles += 1;
666
667 for node in &mut self.nodes {
668 node.energy *= 0.99; }
670 }
671}
672
673use rand::Rng;
678
679fn random_direction(rng: &mut impl Rng) -> (f64, f64, f64) {
680 let theta = rng.gen::<f64>() * std::f64::consts::TAU;
681
682 let phi = (rng.gen::<f64>() * 2.0 - 1.0).acos();
683
684 (
685 theta.sin() * phi.cos(),
686 theta.sin() * phi.sin(),
687 theta.cos(),
688 )
689}
690
691pub fn mycelium_growth(seed: &Seed, field: &MorphogenField, config: &GrowthConfig) -> Organism {
696 let mut rng = seed.rng();
697 let mut organism = Organism::new(
698 &format!("mycelium_{}", rng.gen::<u32>()),
699 &format!("Mycelium_{}", seed.type_tag),
700 seed.clone(),
701 GrowthPattern::Mycelium,
702 );
703
704 organism.nodes.push(MorphNode {
707 id: 0,
708
709 position: seed.position,
710
711 node_type: NodeType::Root,
712
713 name: format!("root_{}", seed.type_tag),
714
715 latency_base_ms: 1.0,
716
717 capacity: 100.0,
718
719 energy: seed.energy,
720
721 depth: 0,
722
723 metadata: HashMap::new(),
724 });
725
726 struct HyphaTip {
729 node_id: usize,
730
731 position: (f64, f64, f64),
732
733 direction: (f64, f64, f64),
734
735 energy: f64,
736
737 depth: u32,
738
739 idle_cycles: u32,
740 }
741
742 let mut tips: Vec<HyphaTip> = vec![HyphaTip {
743 node_id: 0,
744
745 position: seed.position,
746
747 direction: random_direction(&mut rng),
748
749 energy: seed.energy,
750
751 depth: 0,
752
753 idle_cycles: 0,
754 }];
755
756 let mut next_node_id = 1;
757
758 let mut next_conn_id = 1;
759
760 let mut grown = 0;
761
762 while grown < config.max_nodes && !tips.is_empty() {
763 let mut new_tips = Vec::new();
764
765 let mut dead_indices = Vec::new();
766
767 for (idx, tip) in tips.iter_mut().enumerate() {
768 if tip.energy <= 0.0 || tip.depth >= config.max_depth {
769 dead_indices.push(idx);
770
771 continue;
772 }
773
774 let (cx, cy, cz) = tip.position;
777
778 let current_conc = field.concentration_at(cx, cy, cz);
779
780 let step = 0.5;
781
782 let (gx, gy, gz) = if current_conc > 0.01 {
783 let forward = field.concentration_at(
784 cx + tip.direction.0 * step,
785 cy + tip.direction.1 * step,
786 cz + tip.direction.2 * step,
787 );
788
789 if forward > current_conc {
790 tip.direction } else {
792 let mut best_dir = tip.direction;
795
796 let mut best_conc = forward;
797
798 for _ in 0..8 {
799 let test_dir = random_direction(&mut rng);
800
801 let tc = field.concentration_at(
802 cx + test_dir.0 * step,
803 cy + test_dir.1 * step,
804 cz + test_dir.2 * step,
805 );
806
807 if tc > best_conc {
808 best_conc = tc;
809
810 best_dir = test_dir;
811 }
812 }
813
814 best_dir
815 }
816 } else {
817 tip.direction
818 };
819
820 if rng.gen::<f64>() < config.branching_probability
823 && tip.energy > config.min_energy_for_branch
824 && grown < config.max_nodes - 1
825 {
826 let branch_dir = random_direction(&mut rng);
827
828 let branch_energy = tip.energy * 0.6;
829
830 let new_id = next_node_id;
831
832 next_node_id += 1;
833
834 grown += 1;
835
836 organism.nodes.push(MorphNode {
837 id: new_id,
838
839 position: tip.position,
840
841 node_type: NodeType::Service,
842
843 name: format!("hypha_{}", new_id),
844
845 latency_base_ms: 2.0 + rng.gen::<f64>() * 5.0,
846
847 capacity: 30.0 + rng.gen::<f64>() * 70.0,
848
849 energy: branch_energy,
850
851 depth: tip.depth + 1,
852
853 metadata: HashMap::new(),
854 });
855
856 organism.connections.push(MorphConnection {
857 id: next_conn_id,
858
859 from_node: tip.node_id,
860
861 to_node: new_id,
862
863 weight: 0.5,
864
865 bandwidth: 1000.0 + rng.gen::<f64>() * 9000.0,
866
867 protocol: "gRPC".to_string(),
868
869 latency_ms: 1.0,
870
871 connection_type: ConnectionType::DataFlow,
872
873 is_active: true,
874
875 age_cycles: 0,
876 });
877
878 next_conn_id += 1;
879
880 new_tips.push(HyphaTip {
881 node_id: new_id,
882
883 position: tip.position,
884
885 direction: branch_dir,
886
887 energy: branch_energy,
888
889 depth: tip.depth + 1,
890
891 idle_cycles: 0,
892 });
893
894 tip.energy *= 0.5;
895 }
896
897 let nx = cx + gx * step;
900
901 let ny = cy + gy * step;
902
903 let nz = cz + gz * step;
904
905 tip.position = (nx, ny, nz);
906
907 tip.energy -= config.energy_decay;
908
909 tip.idle_cycles += 1;
910
911 for node in &organism.nodes {
914 if node.id == tip.node_id {
915 continue;
916 }
917
918 let dist = ((nx - node.position.0).powi(2)
919 + (ny - node.position.1).powi(2)
920 + (nz - node.position.2).powi(2))
921 .sqrt();
922
923 if dist < 0.8 && rng.gen::<f64>() < config.anastomosis_probability {
924 organism.connections.push(MorphConnection {
925 id: next_conn_id,
926
927 from_node: tip.node_id,
928
929 to_node: node.id,
930
931 weight: 0.7,
932
933 bandwidth: 2000.0 + rng.gen::<f64>() * 8000.0,
934
935 protocol: "gRPC".to_string(),
936
937 latency_ms: 0.5,
938
939 connection_type: ConnectionType::DataFlow,
940
941 is_active: true,
942
943 age_cycles: 0,
944 });
945
946 next_conn_id += 1;
947
948 dead_indices.push(idx);
949
950 break;
951 }
952 }
953 }
954
955 dead_indices.sort_unstable();
958
959 dead_indices.dedup();
960
961 for i in dead_indices.into_iter().rev() {
962 if i < tips.len() {
963 tips.remove(i);
964 }
965 }
966
967 tips.extend(new_tips);
968
969 tips.retain(|t| t.idle_cycles < config.prune_idle_cycles);
972 }
973
974 organism.calculate_metrics();
975
976 organism
977}
978
979pub fn capillary_growth(seed: &Seed, field: &MorphogenField, config: &GrowthConfig) -> Organism {
984 let mut rng = seed.rng();
985 let mut organism = Organism::new(
986 &format!("capillary_{}", rng.gen::<u32>()),
987 &format!("Capillary_{}", seed.type_tag),
988 seed.clone(),
989 GrowthPattern::Capillary,
990 );
991
992 organism.nodes.push(MorphNode {
995 id: 0,
996
997 position: seed.position,
998
999 node_type: NodeType::Root,
1000
1001 name: format!("root_{}", seed.type_tag),
1002
1003 latency_base_ms: 0.5,
1004
1005 capacity: 1000.0,
1006
1007 energy: seed.energy,
1008
1009 depth: 0,
1010
1011 metadata: HashMap::new(),
1012 });
1013
1014 let mut next_node_id = 1;
1015
1016 let mut next_conn_id = 1;
1017
1018 #[allow(clippy::too_many_arguments)]
1021 fn branch(
1022 organism: &mut Organism,
1023
1024 parent_id: usize,
1025
1026 parent_pos: (f64, f64, f64),
1027
1028 parent_capacity: f64,
1029
1030 depth: u32,
1031
1032 max_depth: u32,
1033
1034 angle: f64,
1035
1036 field: &MorphogenField,
1037
1038 next_node_id: &mut usize,
1039
1040 next_conn_id: &mut usize,
1041 ) {
1042 if depth >= max_depth || *next_node_id >= 500 {
1043 organism.nodes.push(MorphNode {
1046 id: *next_node_id,
1047
1048 position: parent_pos,
1049
1050 node_type: NodeType::Leaf,
1051
1052 name: format!("capillary_{}", *next_node_id),
1053
1054 latency_base_ms: 10.0 + depth as f64 * 2.0,
1055
1056 capacity: parent_capacity * 0.1,
1057
1058 energy: 100.0 - depth as f64 * 5.0,
1059
1060 depth,
1061
1062 metadata: HashMap::new(),
1063 });
1064
1065 organism.connections.push(MorphConnection {
1066 id: *next_conn_id,
1067
1068 from_node: parent_id,
1069
1070 to_node: *next_node_id,
1071
1072 weight: 0.5,
1073
1074 bandwidth: parent_capacity * 0.1,
1075
1076 protocol: if depth > 3 {
1077 "local".to_string()
1078 } else {
1079 "HTTP/2".to_string()
1080 },
1081
1082 latency_ms: 1.0 + depth as f64,
1083
1084 connection_type: ConnectionType::DataFlow,
1085
1086 is_active: true,
1087
1088 age_cycles: 0,
1089 });
1090
1091 *next_node_id += 1;
1092
1093 *next_conn_id += 1;
1094
1095 return;
1096 }
1097
1098 let directions = [
1101 (angle.cos(), angle.sin(), 0.0),
1102 (angle.cos(), -angle.sin(), 0.0),
1103 ];
1104
1105 let use_z = if depth > 2 && depth.is_multiple_of(2) {
1108 [
1109 (angle.cos(), 0.0, angle.sin()),
1110 (0.0, angle.cos(), angle.sin()),
1111 ]
1112 } else {
1113 directions
1114 };
1115
1116 let step = 1.0 / (depth + 1) as f64;
1117
1118 let child_capacity = parent_capacity * 0.6;
1119
1120 for (i, &(dx, dy, dz)) in use_z.iter().enumerate() {
1121 let child_pos = (
1122 parent_pos.0 + dx * step,
1123 parent_pos.1 + dy * step,
1124 parent_pos.2 + dz * step,
1125 );
1126
1127 let attr_conc = field.concentration_at(child_pos.0, child_pos.1, child_pos.2);
1130
1131 let adjusted_pos = if attr_conc > 0.1 {
1132 (
1133 child_pos.0 + attr_conc * 0.1,
1134 child_pos.1 + attr_conc * 0.1,
1135 child_pos.2 + attr_conc * 0.1,
1136 )
1137 } else {
1138 child_pos
1139 };
1140
1141 let child_id = *next_node_id;
1142
1143 *next_node_id += 1;
1144
1145 let node_type = if depth < 2 {
1146 NodeType::Gateway
1147 } else if depth < 4 {
1148 NodeType::LoadBalancer
1149 } else if depth < 6 {
1150 NodeType::Service
1151 } else {
1152 NodeType::Cache
1153 };
1154
1155 organism.nodes.push(MorphNode {
1156 id: child_id,
1157
1158 position: adjusted_pos,
1159
1160 node_type,
1161
1162 name: format!("branch_{}_{}", depth, i),
1163
1164 latency_base_ms: 1.0 + depth as f64 * 1.5,
1165
1166 capacity: child_capacity,
1167
1168 energy: 100.0 - depth as f64 * 3.0,
1169
1170 depth,
1171
1172 metadata: HashMap::new(),
1173 });
1174
1175 organism.connections.push(MorphConnection {
1176 id: *next_conn_id,
1177
1178 from_node: parent_id,
1179
1180 to_node: child_id,
1181
1182 weight: 0.6 - depth as f64 * 0.02,
1183
1184 bandwidth: child_capacity,
1185
1186 protocol: if depth < 3 {
1187 "HTTP/2".to_string()
1188 } else {
1189 "gRPC".to_string()
1190 },
1191
1192 latency_ms: 1.0 + depth as f64 * 0.5,
1193
1194 connection_type: ConnectionType::DataFlow,
1195
1196 is_active: true,
1197
1198 age_cycles: 0,
1199 });
1200
1201 *next_conn_id += 1;
1202
1203 branch(
1206 organism,
1207 child_id,
1208 adjusted_pos,
1209 child_capacity,
1210 depth + 1,
1211 max_depth,
1212 angle * 0.85,
1213 field,
1214 next_node_id,
1215 next_conn_id,
1216 );
1217 }
1218
1219 if depth > 1 && depth < 4 && use_z.len() >= 2 {
1222 let from_id = *next_node_id - 1;
1223
1224 let to_id = *next_node_id - 2;
1225
1226 organism.connections.push(MorphConnection {
1227 id: *next_conn_id,
1228
1229 from_node: from_id,
1230
1231 to_node: to_id,
1232
1233 weight: 0.2,
1234
1235 bandwidth: child_capacity * 0.3,
1236
1237 protocol: "backup".to_string(),
1238
1239 latency_ms: 5.0,
1240
1241 connection_type: ConnectionType::Replication,
1242
1243 is_active: true,
1244
1245 age_cycles: 0,
1246 });
1247
1248 *next_conn_id += 1;
1249 }
1250 }
1251
1252 branch(
1253 &mut organism,
1254 0,
1255 seed.position,
1256 1000.0,
1257 1,
1258 config.max_depth,
1259 config.branching_angle,
1260 field,
1261 &mut next_node_id,
1262 &mut next_conn_id,
1263 );
1264
1265 organism.calculate_metrics();
1266
1267 organism
1268}
1269
1270pub fn slime_mold_growth(seed: &Seed, field: &MorphogenField, config: &GrowthConfig) -> Organism {
1277 let mut rng = seed.rng();
1278 let mut organism = Organism::new(
1279 &format!("slime_{}", rng.gen::<u32>()),
1280 &format!("SlimeMold_{}", seed.type_tag),
1281 seed.clone(),
1282 GrowthPattern::SlimeMold,
1283 );
1284
1285 let grid_size = 64;
1288
1289 let mut trail: Vec<Vec<f64>> = vec![vec![0.0; grid_size]; grid_size];
1290
1291 let mut particles: Vec<(f64, f64, f64)> = Vec::new(); let food_sources: Vec<(usize, usize)> = (0..8)
1296 .map(|i| {
1297 let fx = ((seed.position.0 + (i as f64 * 3.0).sin() * 20.0) as usize)
1298 .clamp(0, grid_size - 1);
1299
1300 let fy = ((seed.position.1 + (i as f64 * 3.0).cos() * 20.0) as usize)
1301 .clamp(0, grid_size - 1);
1302
1303 trail[fy][fx] = 1.0; (fx, fy)
1306 })
1307 .collect();
1308
1309 for _ in 0..50 {
1312 let angle = rng.gen::<f64>() * std::f64::consts::TAU;
1313
1314 particles.push((
1315 seed.position.0.max(0.0).min(grid_size as f64 - 1.0),
1316 seed.position.1.max(0.0).min(grid_size as f64 - 1.0),
1317 angle,
1318 ));
1319 }
1320
1321 let sensor_angle = std::f64::consts::FRAC_PI_4; let sensor_distance = 3.0;
1326
1327 let rotation_angle = std::f64::consts::FRAC_PI_6; let step_size = 1.0;
1330
1331 let mut grid_attractors: HashMap<(i32, i32), f64> = HashMap::new();
1332
1333 for _step in 0..1000 {
1334 for particle in &mut particles {
1335 let (px, py, angle) = *particle;
1336
1337 let sensors = [
1340 (angle, 1.0), (angle - sensor_angle, 1.0), (angle + sensor_angle, 1.0), (angle - sensor_angle * 2.0, 0.8), (angle + sensor_angle * 2.0, 0.8), ];
1346
1347 let mut best_conc = -1.0;
1348
1349 let mut best_angle = angle;
1350
1351 for (sensor_angle, weight) in &sensors {
1352 let sensor_x = (px + sensor_angle.cos() * sensor_distance) as usize;
1353
1354 let sensor_y = (py + sensor_angle.sin() * sensor_distance) as usize;
1355
1356 if sensor_x < grid_size && sensor_y < grid_size {
1357 let trail_val = trail[sensor_y][sensor_x];
1358
1359 let conc = trail_val * weight;
1360
1361 let field_val = field.concentration_at(sensor_x as f64, sensor_y as f64, 0.0);
1364
1365 let total = conc + field_val * 0.3;
1366
1367 if total > best_conc {
1368 best_conc = total;
1369
1370 best_angle = *sensor_angle;
1371 }
1372 }
1373 }
1374
1375 let new_angle = if best_conc < 0.0 {
1378 angle + (rng.gen::<f64>() - 0.5) * rotation_angle * 2.0
1379 } else {
1380 let diff = best_angle - angle;
1381
1382 if diff.abs() > rotation_angle {
1383 angle + rotation_angle.copysign(diff)
1384 } else {
1385 best_angle
1386 }
1387 };
1388
1389 let new_x = px + new_angle.cos() * step_size;
1392
1393 let new_y = py + new_angle.sin() * step_size;
1394
1395 if new_x >= 0.0
1398 && new_x < grid_size as f64 - 1.0
1399 && new_y >= 0.0
1400 && new_y < grid_size as f64 - 1.0
1401 {
1402 particle.0 = new_x;
1403
1404 particle.1 = new_y;
1405
1406 particle.2 = new_angle;
1407
1408 let gx = new_x as usize;
1411
1412 let gy = new_y as usize;
1413
1414 trail[gy][gx] = (trail[gy][gx] + config.trail_persistence).min(1.0);
1415
1416 let key = (gx as i32, gy as i32);
1419
1420 *grid_attractors.entry(key).or_insert(0.0) += 0.01;
1421 }
1422 }
1423
1424 for row in trail.iter_mut() {
1427 for cell in row.iter_mut() {
1428 *cell *= 1.0 - config.trail_evaporation;
1429
1430 if *cell < 0.01 {
1431 *cell = 0.0;
1432 }
1433 }
1434 }
1435 }
1436
1437 let mut id_map: HashMap<(i32, i32), usize> = HashMap::new();
1440
1441 let mut next_id = 0;
1442
1443 let mut next_conn = 1;
1444
1445 for (y, row) in trail.iter().enumerate() {
1448 for (x, &cell) in row.iter().enumerate() {
1449 if cell > 0.15 {
1450 let node_id = next_id;
1451
1452 next_id += 1;
1453
1454 id_map.insert((x as i32, y as i32), node_id);
1455
1456 let node_type = if food_sources.contains(&(x, y)) {
1457 NodeType::Root
1458 } else if trail[y][x] > 0.8 {
1459 NodeType::Gateway
1460 } else if trail[y][x] > 0.5 {
1461 NodeType::Service
1462 } else {
1463 NodeType::Cache
1464 };
1465
1466 organism.nodes.push(MorphNode {
1467 id: node_id,
1468
1469 position: (x as f64, y as f64, 0.0),
1470
1471 node_type,
1472
1473 name: format!("slime_{}", node_id),
1474
1475 latency_base_ms: 5.0 - trail[y][x] * 3.0,
1476
1477 capacity: trail[y][x] * 100.0,
1478
1479 energy: trail[y][x] * 50.0,
1480
1481 depth: 0,
1482
1483 metadata: HashMap::new(),
1484 });
1485 }
1486 }
1487 }
1488
1489 for (&(x, y), &from_id) in &id_map {
1492 for (dx, dy) in &[(1, 0), (0, 1), (1, 1), (-1, 1)] {
1493 let nx = x + dx;
1494
1495 let ny = y + dy;
1496
1497 if let Some(&to_id) = id_map.get(&(nx, ny)) {
1498 let avg_trail =
1499 (trail[y as usize][x as usize] + trail[ny as usize][nx as usize]) / 2.0;
1500
1501 organism.connections.push(MorphConnection {
1502 id: next_conn,
1503
1504 from_node: from_id,
1505
1506 to_node: to_id,
1507
1508 weight: avg_trail,
1509
1510 bandwidth: avg_trail * 10000.0,
1511
1512 protocol: "gRPC".to_string(),
1513
1514 latency_ms: (1.0 - avg_trail).max(0.1) * 10.0,
1515
1516 connection_type: ConnectionType::DataFlow,
1517
1518 is_active: true,
1519
1520 age_cycles: 0,
1521 });
1522
1523 next_conn += 1;
1524 }
1525 }
1526 }
1527
1528 organism.calculate_metrics();
1529
1530 organism
1531}
1532
1533pub fn fractal_growth(seed: &Seed, _field: &MorphogenField, config: &GrowthConfig) -> Organism {
1538 let mut rng = seed.rng();
1539 let mut organism = Organism::new(
1540 &format!("fractal_{}", rng.gen::<u32>()),
1541 &format!("Fractal_{}", seed.type_tag),
1542 seed.clone(),
1543 GrowthPattern::FractalLSystem,
1544 );
1545
1546 let axiom = if seed.type_tag.contains("cache") {
1549 "F[+F][-F]F"
1550 } else if seed.type_tag.contains("database") {
1551 "F[+F[+F][-F]][-F[+F][-F]]"
1552 } else {
1553 "F+F+F+F"
1554 };
1555
1556 let mut current = axiom.to_string();
1557
1558 for _gen in 0..config.max_depth {
1559 let mut next = String::new();
1560
1561 for ch in current.chars() {
1562 let mut replaced = false;
1563
1564 for (rule_from, rule_to) in &config.lsystem_rules {
1565 if ch == *rule_from {
1566 next.push_str(rule_to);
1567
1568 replaced = true;
1569
1570 break;
1571 }
1572 }
1573
1574 if !replaced {
1575 next.push(ch);
1576 }
1577 }
1578
1579 current = next;
1580
1581 if current.len() > 2000 {
1582 break;
1583 } }
1585
1586 struct TurtleState {
1589 x: f64,
1590 y: f64,
1591 z: f64,
1592
1593 angle_xy: f64,
1594
1595 angle_z: f64,
1596 }
1597
1598 let mut turtle = TurtleState {
1599 x: seed.position.0,
1600
1601 y: seed.position.1,
1602
1603 z: seed.position.2,
1604
1605 angle_xy: 0.0,
1606
1607 angle_z: 0.0,
1608 };
1609
1610 let mut node_stack: Vec<(usize, TurtleState)> = Vec::new();
1611
1612 let mut next_node_id = 0;
1613
1614 let mut next_conn_id = 1;
1615
1616 organism.nodes.push(MorphNode {
1619 id: next_node_id,
1620
1621 position: (turtle.x, turtle.y, turtle.z),
1622
1623 node_type: NodeType::Root,
1624
1625 name: "fractal_root".to_string(),
1626
1627 latency_base_ms: 0.5,
1628
1629 capacity: 100.0,
1630
1631 energy: 100.0,
1632
1633 depth: 0,
1634
1635 metadata: HashMap::new(),
1636 });
1637
1638 let mut last_node_id = next_node_id;
1639
1640 next_node_id += 1;
1641
1642 for ch in current.chars() {
1643 if next_node_id >= config.max_nodes {
1644 break;
1645 }
1646
1647 match ch {
1648 'F' | 'A' | 'B' => {
1649 let step = 1.0;
1652
1653 let nx = turtle.x + turtle.angle_xy.cos() * turtle.angle_z.cos() * step;
1654
1655 let ny = turtle.y + turtle.angle_xy.sin() * turtle.angle_z.cos() * step;
1656
1657 let nz = turtle.z + turtle.angle_z.sin() * step;
1658
1659 let node_type = match ch {
1660 'A' => NodeType::Gateway,
1661
1662 'B' => NodeType::Database,
1663
1664 _ => NodeType::Service,
1665 };
1666
1667 let node_id = next_node_id;
1668
1669 next_node_id += 1;
1670
1671 organism.nodes.push(MorphNode {
1672 id: node_id,
1673
1674 position: (nx, ny, nz),
1675
1676 node_type,
1677
1678 name: format!("fnode_{}", node_id),
1679
1680 latency_base_ms: 2.0,
1681
1682 capacity: 50.0,
1683
1684 energy: 80.0,
1685
1686 depth: node_stack.len() as u32,
1687
1688 metadata: HashMap::new(),
1689 });
1690
1691 organism.connections.push(MorphConnection {
1692 id: next_conn_id,
1693
1694 from_node: last_node_id,
1695
1696 to_node: node_id,
1697
1698 weight: 0.5,
1699
1700 bandwidth: 5000.0,
1701
1702 protocol: "HTTP/2".to_string(),
1703
1704 latency_ms: 1.0,
1705
1706 connection_type: ConnectionType::DataFlow,
1707
1708 is_active: true,
1709
1710 age_cycles: 0,
1711 });
1712
1713 next_conn_id += 1;
1714
1715 turtle.x = nx;
1716
1717 turtle.y = ny;
1718
1719 turtle.z = nz;
1720
1721 last_node_id = node_id;
1722 }
1723
1724 '+' => {
1725 turtle.angle_xy += config.branching_angle;
1726 }
1727
1728 '-' => {
1729 turtle.angle_xy -= config.branching_angle;
1730 }
1731
1732 '[' => {
1733 node_stack.push((
1736 last_node_id,
1737 TurtleState {
1738 x: turtle.x,
1739
1740 y: turtle.y,
1741
1742 z: turtle.z,
1743
1744 angle_xy: turtle.angle_xy,
1745
1746 angle_z: turtle.angle_z,
1747 },
1748 ));
1749 }
1750
1751 ']' => {
1752 if let Some((saved_node, saved_turtle)) = node_stack.pop() {
1755 turtle = saved_turtle;
1756
1757 last_node_id = saved_node;
1758 }
1759 }
1760
1761 '^' => {
1762 turtle.angle_z += config.branching_angle * 0.5;
1763 }
1764
1765 'v' => {
1766 turtle.angle_z -= config.branching_angle * 0.5;
1767 }
1768
1769 _ => {}
1770 }
1771 }
1772
1773 organism.calculate_metrics();
1774
1775 organism
1776}
1777
1778#[derive(Debug, Clone, PartialEq)]
1782
1783pub enum FitnessObjective {
1784 MinimizeLatency,
1786
1787 MaximizeThroughput,
1789
1790 MinimizeCost,
1792
1793 MaximizeRedundancy,
1795
1796 Balanced,
1798
1799 Custom(String),
1801}
1802
1803#[derive(Debug, Clone)]
1805
1806pub struct FitnessResult {
1807 pub score: f64,
1808
1809 pub latency_score: f64,
1810
1811 pub throughput_score: f64,
1812
1813 pub cost_score: f64,
1814
1815 pub redundancy_score: f64,
1816
1817 pub detail: String,
1818}
1819
1820pub fn evaluate_fitness(organism: &Organism, objective: &FitnessObjective) -> FitnessResult {
1822 let metrics = organism.metrics.as_ref().cloned().unwrap_or_else(|| {
1823 let mut o = organism.clone();
1824
1825 o.calculate_metrics()
1826 });
1827
1828 let node_count = metrics.node_count.max(1) as f64;
1829
1830 let conn_count = metrics.connection_count.max(1) as f64;
1831
1832 let _avg_depth = metrics.max_depth.max(1) as f64;
1833
1834 let redundancy = metrics.redundancy_score;
1835
1836 let avg_latency: f64 = if organism.connections.is_empty() {
1839 50.0
1840 } else {
1841 organism
1842 .connections
1843 .iter()
1844 .map(|c| c.latency_ms)
1845 .sum::<f64>()
1846 / conn_count
1847 };
1848
1849 let latency_score = (100.0 / (avg_latency + 10.0)).min(1.0);
1850
1851 let avg_bandwidth: f64 = if organism.connections.is_empty() {
1854 0.0
1855 } else {
1856 organism
1857 .connections
1858 .iter()
1859 .map(|c| c.bandwidth)
1860 .sum::<f64>()
1861 / conn_count
1862 };
1863
1864 let throughput_score = (avg_bandwidth / 20000.0).min(1.0);
1865
1866 let cost_score = (1.0 - (node_count / 20.0).min(1.0)).max(0.05);
1869
1870 let redundancy_score = (redundancy / 3.0).min(1.0);
1873
1874 let fractal_efficiency = (metrics.fractal_dimension / 3.0).min(1.0);
1877
1878 let complexity_penalty = ((conn_count / node_count.max(1.0)) / 3.0).min(1.0) * 0.15;
1880
1881 let score = match objective {
1882 FitnessObjective::MinimizeLatency => {
1883 latency_score * 0.6 + throughput_score * 0.2 + cost_score * 0.1 + redundancy_score * 0.1
1884 }
1885
1886 FitnessObjective::MaximizeThroughput => {
1887 throughput_score * 0.5 + latency_score * 0.2 + redundancy_score * 0.2 + cost_score * 0.1
1888 }
1889
1890 FitnessObjective::MinimizeCost => {
1891 cost_score * 0.6 + latency_score * 0.2 + throughput_score * 0.1 + redundancy_score * 0.1
1892 }
1893
1894 FitnessObjective::MaximizeRedundancy => {
1895 redundancy_score * 0.5 + latency_score * 0.2 + throughput_score * 0.2 + cost_score * 0.1
1896 }
1897
1898 FitnessObjective::Balanced => {
1899 latency_score * 0.3 + throughput_score * 0.3 + cost_score * 0.2 + redundancy_score * 0.2
1900 }
1901
1902 FitnessObjective::Custom(_) => {
1903 (latency_score + throughput_score + cost_score + redundancy_score) / 4.0
1904 }
1905 };
1906
1907 FitnessResult {
1908 score: (score * fractal_efficiency * (1.0 - complexity_penalty)).clamp(0.0, 1.0),
1909
1910 latency_score,
1911
1912 throughput_score,
1913
1914 cost_score,
1915
1916 redundancy_score,
1917
1918 detail: format!(
1919 "nodes={}, conns={}, avg_lat={:.1}ms, avg_bw={:.0}, redund={:.2}",
1920 node_count, conn_count, avg_latency, avg_bandwidth, redundancy
1921 ),
1922 }
1923}
1924
1925pub struct MorphogenesisEngine {
1929 pub config: Arc<RwLock<GrowthConfig>>,
1931
1932 pub organisms: Arc<RwLock<Vec<Organism>>>,
1934
1935 pub field: Arc<RwLock<MorphogenField>>,
1937
1938 pub generation: Arc<RwLock<u32>>,
1940
1941 pub evolution_history: Arc<RwLock<Vec<f64>>>,
1943}
1944
1945impl Default for MorphogenesisEngine {
1946 fn default() -> Self {
1947 Self::new()
1948 }
1949}
1950
1951impl MorphogenesisEngine {
1952 pub fn new() -> Self {
1954 Self {
1955 config: Arc::new(RwLock::new(GrowthConfig::default())),
1956
1957 organisms: Arc::new(RwLock::new(Vec::new())),
1958
1959 field: Arc::new(RwLock::new(MorphogenField::new())),
1960
1961 generation: Arc::new(RwLock::new(0)),
1962
1963 evolution_history: Arc::new(RwLock::new(Vec::new())),
1964 }
1965 }
1966
1967 pub fn set_config(&self, config: GrowthConfig) {
1969 *crate::sync_guard::write_lock(&self.config) = config;
1970 }
1971
1972 pub fn set_field(&self, field: MorphogenField) {
1974 *crate::sync_guard::write_lock(&self.field) = field;
1975 }
1976
1977 pub fn grow_from_seed(&self, seed: &Seed, pattern: Option<GrowthPattern>) -> Organism {
1979 let config = crate::sync_guard::read_lock(&self.config).clone();
1980
1981 let pattern = pattern.or(seed.preferred_pattern).unwrap_or(config.pattern);
1982
1983 let field = crate::sync_guard::read_lock(&self.field).clone();
1984
1985 let organism = match pattern {
1986 GrowthPattern::Mycelium => mycelium_growth(seed, &field, &config),
1987
1988 GrowthPattern::Capillary => capillary_growth(seed, &field, &config),
1989
1990 GrowthPattern::SlimeMold => slime_mold_growth(seed, &field, &config),
1991
1992 GrowthPattern::FractalLSystem => fractal_growth(seed, &field, &config),
1993
1994 GrowthPattern::Hybrid => {
1995 let mycelium = mycelium_growth(seed, &field, &config);
1998
1999 if mycelium.fitness_score > 0.3 {
2000 mycelium
2001 } else {
2002 capillary_growth(seed, &field, &config)
2003 }
2004 }
2005 };
2006
2007 let fitness = evaluate_fitness(&organism, &FitnessObjective::Balanced);
2008
2009 let mut org = organism;
2010
2011 org.fitness_score = fitness.score;
2012
2013 crate::sync_guard::write_lock(&self.organisms).push(org.clone());
2014
2015 org
2016 }
2017
2018 pub fn evolve_population(
2026 &self,
2027
2028 seeds: &[Seed],
2029
2030 generations: u32,
2031
2032 objective: &FitnessObjective,
2033
2034 population_size: usize,
2035 ) -> Vec<Organism> {
2036 if seeds.is_empty() {
2037 return Vec::new();
2038 }
2039
2040 let _config = crate::sync_guard::read_lock(&self.config).clone();
2041
2042 let patterns = [
2043 GrowthPattern::Mycelium,
2044 GrowthPattern::Capillary,
2045 GrowthPattern::SlimeMold,
2046 GrowthPattern::FractalLSystem,
2047 ];
2048
2049 let mut population: Vec<Organism> = seeds
2052 .iter()
2053 .flat_map(|seed| {
2054 patterns
2055 .iter()
2056 .map(|pattern| {
2057 let field = crate::sync_guard::read_lock(&self.field).clone();
2058
2059 let config = crate::sync_guard::read_lock(&self.config).clone();
2060
2061 let mut org = match pattern {
2062 GrowthPattern::Mycelium => mycelium_growth(seed, &field, &config),
2063
2064 GrowthPattern::Capillary => capillary_growth(seed, &field, &config),
2065
2066 GrowthPattern::SlimeMold => slime_mold_growth(seed, &field, &config),
2067
2068 GrowthPattern::FractalLSystem => fractal_growth(seed, &field, &config),
2069
2070 GrowthPattern::Hybrid => unreachable!(),
2071 };
2072
2073 let fitness = evaluate_fitness(&org, objective);
2074
2075 org.fitness_score = fitness.score;
2076
2077 org
2078 })
2079 .collect::<Vec<_>>()
2080 })
2081 .collect();
2082
2083 for gen in 0..generations {
2086 *crate::sync_guard::write_lock(&self.generation) = gen + 1;
2087
2088 population.sort_by(|a, b| b.fitness_score.total_cmp(&a.fitness_score));
2091
2092 if let Some(best) = population.first() {
2095 crate::sync_guard::write_lock(&self.evolution_history).push(best.fitness_score);
2096 }
2097
2098 let keep_count = (population_size / 3).max(population.len() / 3);
2101
2102 let survivors: Vec<Organism> = population.into_iter().take(keep_count).collect();
2103
2104 let mut new_population = survivors.clone();
2107
2108 let mut rng = {
2111 use rand::SeedableRng;
2112 rand::rngs::StdRng::seed_from_u64(
2113 0x9E37_79B9_7F4A_7C15 ^ (gen as u64).wrapping_mul(0x100000001b3),
2114 )
2115 };
2116
2117 let mut rng_idx = 0;
2118
2119 while new_population.len() < population_size {
2120 let parent = &survivors[rng_idx % survivors.len()];
2121
2122 rng_idx += 1;
2123
2124 let mutation_factor = 0.8 + rng.gen::<f64>() * 0.4;
2127
2128 let mutated_seed = Seed {
2129 id: format!("evo_{}_{}", gen, new_population.len()),
2130
2131 position: (
2132 parent.seed.position.0 + (rng.gen::<f64>() - 0.5) * 5.0,
2133 parent.seed.position.1 + (rng.gen::<f64>() - 0.5) * 5.0,
2134 parent.seed.position.2 + (rng.gen::<f64>() - 0.5) * 5.0,
2135 ),
2136
2137 energy: parent.seed.energy * mutation_factor,
2138
2139 type_tag: parent.seed.type_tag.clone(),
2140
2141 preferred_pattern: Some(patterns[rng.gen::<usize>() % patterns.len()]),
2142 };
2143
2144 let field = crate::sync_guard::read_lock(&self.field).clone();
2145
2146 let config = crate::sync_guard::read_lock(&self.config).clone();
2147
2148 let pattern = mutated_seed.preferred_pattern.unwrap_or(config.pattern);
2149
2150 let mut child = match pattern {
2151 GrowthPattern::Mycelium => mycelium_growth(&mutated_seed, &field, &config),
2152
2153 GrowthPattern::Capillary => capillary_growth(&mutated_seed, &field, &config),
2154
2155 GrowthPattern::SlimeMold => slime_mold_growth(&mutated_seed, &field, &config),
2156
2157 GrowthPattern::FractalLSystem => fractal_growth(&mutated_seed, &field, &config),
2158
2159 GrowthPattern::Hybrid => unreachable!(),
2160 };
2161
2162 child.generation = gen + 1;
2163
2164 let fitness = evaluate_fitness(&child, objective);
2165
2166 child.fitness_score = fitness.score;
2167
2168 new_population.push(child);
2169 }
2170
2171 population = new_population;
2172 }
2173
2174 population.sort_by(|a, b| b.fitness_score.total_cmp(&a.fitness_score));
2177
2178 let top: Vec<Organism> = population.into_iter().take(population_size / 2).collect();
2179
2180 *crate::sync_guard::write_lock(&self.organisms) = top.clone();
2181
2182 top
2183 }
2184
2185 pub fn prune_weak(&self, organism: &mut Organism, threshold: f64) -> usize {
2187 let before = organism.connections.len();
2188
2189 organism
2192 .connections
2193 .retain(|c| c.is_active && c.weight > threshold);
2194
2195 let connected_nodes: std::collections::HashSet<usize> = organism
2198 .connections
2199 .iter()
2200 .flat_map(|c| vec![c.from_node, c.to_node])
2201 .collect();
2202
2203 organism.nodes.retain(|n| connected_nodes.contains(&n.id));
2204
2205 before - organism.connections.len()
2206 }
2207
2208 pub fn analyze_topology(organism: &Organism) -> HashMap<String, f64> {
2210 let mut analysis = HashMap::new();
2211
2212 let n = organism.nodes.len() as f64;
2213
2214 let e = organism.connections.len() as f64;
2215
2216 if n > 0.0 {
2219 analysis.insert("avg_degree".to_string(), (e * 2.0 / n).round());
2220 }
2221
2222 if n > 1.0 {
2225 let max_edges = n * (n - 1.0) / 2.0;
2226
2227 analysis.insert(
2228 "density".to_string(),
2229 (e / max_edges * 100.0 * 100.0).round() / 100.0,
2230 );
2231 }
2232
2233 let internal = organism
2236 .nodes
2237 .iter()
2238 .filter(|n| organism.connections.iter().any(|c| c.from_node == n.id))
2239 .count() as f64;
2240
2241 if internal > 0.0 {
2242 analysis.insert(
2243 "branching_ratio".to_string(),
2244 (n / internal * 100.0).round() / 100.0,
2245 );
2246 }
2247
2248 if !organism.connections.is_empty() {
2251 let avg_lat: f64 = organism
2252 .connections
2253 .iter()
2254 .map(|c| c.latency_ms)
2255 .sum::<f64>()
2256 / e;
2257
2258 analysis.insert(
2259 "avg_latency_ms".to_string(),
2260 (avg_lat * 100.0).round() / 100.0,
2261 );
2262 }
2263
2264 if n > 1.0 {
2267 analysis.insert(
2268 "redundancy".to_string(),
2269 (e / (n - 1.0) * 100.0).round() / 100.0,
2270 );
2271 }
2272
2273 analysis
2274 }
2275
2276 pub fn get_best_organism(&self) -> Option<Organism> {
2278 let organisms = crate::sync_guard::read_lock(&self.organisms);
2279
2280 organisms
2281 .iter()
2282 .max_by(|a, b| a.fitness_score.total_cmp(&b.fitness_score))
2283 .cloned()
2284 }
2285
2286 pub fn evolution_summary(&self) -> Vec<(u32, f64)> {
2288 let history = crate::sync_guard::read_lock(&self.evolution_history);
2289
2290 history
2291 .iter()
2292 .enumerate()
2293 .map(|(i, s)| (i as u32 + 1, *s))
2294 .collect()
2295 }
2296}
2297
2298#[cfg(not(target_arch = "wasm32"))]
2302pub fn express_as_architecture(organism: &Organism) -> crate::architecture_simulator::Architecture {
2303 let mut components = std::collections::HashMap::new();
2304
2305 let mut connections = Vec::new();
2306
2307 for node in &organism.nodes {
2308 let comp_type = match node.node_type {
2309 NodeType::Root | NodeType::Gateway => {
2310 crate::architecture_simulator::ComponentType::Software
2311 }
2312
2313 NodeType::Database | NodeType::Cache => {
2314 crate::architecture_simulator::ComponentType::Storage
2315 }
2316
2317 NodeType::LoadBalancer | NodeType::Queue => {
2318 crate::architecture_simulator::ComponentType::Network
2319 }
2320
2321 NodeType::Service | NodeType::Leaf => {
2322 crate::architecture_simulator::ComponentType::Software
2323 }
2324
2325 NodeType::Custom(_) => {
2326 crate::architecture_simulator::ComponentType::Custom(node.node_type.to_string())
2327 }
2328 };
2329
2330 let mut capacity = std::collections::HashMap::new();
2331
2332 capacity.insert("memory_mb".to_string(), node.capacity);
2333
2334 components.insert(
2335 node.id.to_string(),
2336 crate::architecture_simulator::Component {
2337 id: node.id.to_string(),
2338
2339 name: node.name.clone(),
2340
2341 component_type: comp_type,
2342
2343 capacity,
2344
2345 load: 0.0,
2346
2347 error_rate: 0.01,
2348
2349 latency_ms: node.latency_base_ms,
2350 },
2351 );
2352 }
2353
2354 for conn in &organism.connections {
2355 if !conn.is_active {
2356 continue;
2357 }
2358
2359 connections.push(crate::architecture_simulator::Connection {
2360 from: conn.from_node.to_string(),
2361
2362 to: conn.to_node.to_string(),
2363
2364 bandwidth: conn.bandwidth,
2365
2366 protocol: conn.protocol.clone(),
2367
2368 latency_ms: conn.latency_ms,
2369
2370 packet_loss: 0.001,
2371 });
2372 }
2373
2374 crate::architecture_simulator::Architecture {
2375 id: format!("morph_{}", organism.id),
2376
2377 name: organism.name.clone(),
2378
2379 description: format!(
2380 "Morphogenetically grown {:?} (gen={}, fitness={:.3})",
2381 organism.growth_pattern, organism.generation, organism.fitness_score
2382 ),
2383
2384 components,
2385
2386 connections,
2387
2388 version: organism.generation,
2389
2390 cohesion_score: organism.fitness_score,
2391 }
2392}
2393
2394#[cfg(not(target_arch = "wasm32"))]
2401pub fn trigger_from_vagus(
2402 vagus_tone: &crate::vagus::VagusTone,
2403
2404 system_pulse: &crate::vagus::SystemPulse,
2405
2406 engine: &MorphogenesisEngine,
2407
2408 threshold: f64,
2409) -> Option<Organism> {
2410 if vagus_tone.current > threshold {
2413 return None; }
2415
2416 let stress_source = if system_pulse.cpu_pressure > 0.8 {
2419 "cpu_bound"
2420 } else if system_pulse.memory_pressure > 0.8 {
2421 "memory_bound"
2422 } else if system_pulse.network_pressure > 0.8 {
2423 "network_bound"
2424 } else {
2425 "general"
2426 };
2427
2428 let seed = Seed {
2431 id: format!("compensate_{}", rand::random::<u32>()),
2432
2433 position: (0.0, 0.0, 0.0),
2434
2435 energy: vagus_tone.baseline * 100.0,
2436
2437 type_tag: stress_source.to_string(),
2438
2439 preferred_pattern: match stress_source {
2440 "network_bound" => Some(GrowthPattern::Mycelium), "memory_bound" => Some(GrowthPattern::Capillary), "cpu_bound" => Some(GrowthPattern::FractalLSystem), _ => Some(GrowthPattern::Hybrid),
2447 },
2448 };
2449
2450 let organism = engine.grow_from_seed(&seed, None);
2451
2452 if organism.fitness_score > 0.3 {
2453 Some(organism)
2454 } else {
2455 None
2456 }
2457}
2458
2459pub fn map_to_neuroplasticity(organism: &Organism) -> Vec<(u32, u32, f32)> {
2465 organism
2466 .connections
2467 .iter()
2468 .filter(|c| c.is_active)
2469 .map(|c| {
2470 let weight = (c.weight * 0.5
2471 + c.bandwidth / 20000.0 * 0.3
2472 + (1.0 - c.latency_ms / 20.0).max(0.0) * 0.2) as f32;
2473
2474 (c.from_node as u32, c.to_node as u32, weight.clamp(0.0, 1.0))
2475 })
2476 .collect()
2477}
2478
2479pub fn export_fingerprint(organism: &Organism) -> Vec<u8> {
2488 use std::hash::{Hash, Hasher};
2489
2490 use std::collections::hash_map::DefaultHasher;
2491
2492 let mut fingerprint = Vec::new();
2493
2494 fingerprint.push(match organism.growth_pattern {
2497 GrowthPattern::Mycelium => 0x01,
2498
2499 GrowthPattern::Capillary => 0x02,
2500
2501 GrowthPattern::SlimeMold => 0x03,
2502
2503 GrowthPattern::FractalLSystem => 0x04,
2504
2505 GrowthPattern::Hybrid => 0xFF,
2506 });
2507
2508 let mut hasher = DefaultHasher::new();
2511
2512 organism.nodes.len().hash(&mut hasher);
2513
2514 organism.connections.len().hash(&mut hasher);
2515
2516 ((organism.fitness_score * 1000.0) as u64).hash(&mut hasher);
2517
2518 let structural_hash = hasher.finish();
2519
2520 fingerprint.extend_from_slice(&structural_hash.to_le_bytes());
2521
2522 if let Some(ref metrics) = organism.metrics {
2525 let fractal_quant = (metrics.fractal_dimension * 100.0) as u16;
2526
2527 let redundancy_quant = (metrics.redundancy_score * 100.0) as u16;
2528
2529 let depth_quant = metrics.max_depth as u8;
2530
2531 fingerprint.extend_from_slice(&fractal_quant.to_le_bytes());
2532
2533 fingerprint.extend_from_slice(&redundancy_quant.to_le_bytes());
2534
2535 fingerprint.push(depth_quant);
2536 }
2537
2538 fingerprint
2539}
2540
2541use std::fmt;
2544
2545impl fmt::Display for NodeType {
2546 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2547 match self {
2548 NodeType::Root => write!(f, "Root"),
2549
2550 NodeType::Service => write!(f, "Service"),
2551
2552 NodeType::Database => write!(f, "Database"),
2553
2554 NodeType::Cache => write!(f, "Cache"),
2555
2556 NodeType::Gateway => write!(f, "Gateway"),
2557
2558 NodeType::LoadBalancer => write!(f, "LoadBalancer"),
2559
2560 NodeType::Queue => write!(f, "Queue"),
2561
2562 NodeType::Leaf => write!(f, "Leaf"),
2563
2564 NodeType::Custom(s) => write!(f, "Custom({})", s),
2565 }
2566 }
2567}
2568
2569impl fmt::Display for GrowthPattern {
2570 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2571 match self {
2572 GrowthPattern::Mycelium => write!(f, "Mycelium"),
2573
2574 GrowthPattern::Capillary => write!(f, "Capillary"),
2575
2576 GrowthPattern::SlimeMold => write!(f, "SlimeMold"),
2577
2578 GrowthPattern::FractalLSystem => write!(f, "FractalLSystem"),
2579
2580 GrowthPattern::Hybrid => write!(f, "Hybrid"),
2581 }
2582 }
2583}
2584
2585impl fmt::Display for ConnectionType {
2586 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2587 match self {
2588 ConnectionType::DataFlow => write!(f, "DataFlow"),
2589
2590 ConnectionType::Control => write!(f, "Control"),
2591
2592 ConnectionType::Replication => write!(f, "Replication"),
2593
2594 ConnectionType::Backup => write!(f, "Backup"),
2595 }
2596 }
2597}
2598
2599impl fmt::Display for Organism {
2600 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
2601 writeln!(
2602 f,
2603 "Organism: {} (gen={}, fitness={:.3})",
2604 self.name, self.generation, self.fitness_score
2605 )?;
2606
2607 writeln!(f, " Pattern: {}", self.growth_pattern)?;
2608
2609 writeln!(
2610 f,
2611 " Nodes: {}, Connections: {}",
2612 self.nodes.len(),
2613 self.connections.len()
2614 )?;
2615
2616 if let Some(ref m) = self.metrics {
2617 writeln!(
2618 f,
2619 " Max depth: {}, Fractal dim: {:.3}",
2620 m.max_depth, m.fractal_dimension
2621 )?;
2622
2623 writeln!(
2624 f,
2625 " Redundancy: {:.3}, Avg path: {:.3}",
2626 m.redundancy_score, m.avg_path_length
2627 )?;
2628 }
2629
2630 Ok(())
2631 }
2632}
2633
2634pub fn merge_organisms(organisms: &[Organism]) -> Option<Organism> {
2638 if organisms.is_empty() {
2639 return None;
2640 }
2641
2642 let mut merged = organisms[0].clone();
2643
2644 merged.id = format!("merged_{}", rand::random::<u32>());
2645
2646 merged.name = format!("Merged_{}", organisms.len());
2647
2648 let mut next_node_id = merged.nodes.len();
2649
2650 let mut next_conn_id = merged.connections.len();
2651
2652 for org in organisms.iter().skip(1) {
2653 for node in &org.nodes {
2654 let mut new_node = node.clone();
2655
2656 new_node.id = next_node_id;
2657
2658 merged.nodes.push(new_node);
2659
2660 next_node_id += 1;
2661 }
2662
2663 for conn in &org.connections {
2664 merged.connections.push(MorphConnection {
2665 id: next_conn_id,
2666
2667 from_node: conn.from_node,
2668
2669 to_node: conn.to_node,
2670
2671 ..conn.clone()
2672 });
2673
2674 next_conn_id += 1;
2675 }
2676 }
2677
2678 merged.calculate_metrics();
2679
2680 Some(merged)
2681}
2682
2683pub fn simplify_organism(organism: &mut Organism) -> usize {
2685 let _before = organism.nodes.len();
2686
2687 let mut removed = std::collections::HashSet::new();
2688
2689 let mut i = 0;
2692
2693 while i < organism.nodes.len() {
2694 if removed.contains(&i) {
2695 i += 1;
2696 continue;
2697 }
2698
2699 let node = &organism.nodes[i];
2700
2701 if node.node_type == NodeType::Leaf && node.energy < 10.0 {
2702 removed.insert(i);
2703 }
2704
2705 i += 1;
2706 }
2707
2708 let old_count = organism.nodes.len();
2711
2712 organism.nodes.retain(|n| !removed.contains(&n.id));
2713
2714 let removed_count = old_count - organism.nodes.len();
2715
2716 let active_ids: std::collections::HashSet<usize> =
2719 organism.nodes.iter().map(|n| n.id).collect();
2720
2721 organism
2722 .connections
2723 .retain(|c| active_ids.contains(&c.from_node) && active_ids.contains(&c.to_node));
2724
2725 organism.calculate_metrics();
2726
2727 removed_count
2728}
2729
2730impl NodeType {
2733 #[allow(clippy::inherent_to_string_shadow_display)]
2734 pub fn to_string(&self) -> String {
2735 match self {
2736 NodeType::Root => "Root".to_string(),
2737
2738 NodeType::Service => "Service".to_string(),
2739
2740 NodeType::Database => "Database".to_string(),
2741
2742 NodeType::Cache => "Cache".to_string(),
2743
2744 NodeType::Gateway => "Gateway".to_string(),
2745
2746 NodeType::LoadBalancer => "LoadBalancer".to_string(),
2747
2748 NodeType::Queue => "Queue".to_string(),
2749
2750 NodeType::Leaf => "Leaf".to_string(),
2751
2752 NodeType::Custom(s) => s.clone(),
2753 }
2754 }
2755}
2756
2757#[cfg(test)]
2760mod tests {
2761
2762 use super::*;
2763
2764 fn test_seed() -> Seed {
2765 Seed::new("test_seed", 0.0, 0.0, 0.0, "service")
2766 }
2767
2768 #[test]
2769
2770 fn test_seed_creation() {
2771 let seed = test_seed();
2772
2773 assert_eq!(seed.id, "test_seed");
2774
2775 assert_eq!(seed.energy, 100.0);
2776 }
2777
2778 #[test]
2779
2780 fn test_mycelium_growth() {
2781 let seed = test_seed();
2782
2783 let field = MorphogenField::new();
2784
2785 let config = GrowthConfig::mycelium_default();
2786
2787 let organism = mycelium_growth(&seed, &field, &config);
2788
2789 assert!(
2790 !organism.nodes.is_empty(),
2791 "Mycelium growth should produce nodes"
2792 );
2793
2794 assert!(
2795 organism.nodes.len() > 1,
2796 "Should have more than just the root node"
2797 );
2798
2799 assert!(organism.fitness_score >= 0.0);
2800 }
2801
2802 #[test]
2803
2804 fn test_capillary_growth() {
2805 let seed = test_seed();
2806
2807 let field = MorphogenField::new();
2808
2809 let config = GrowthConfig::capillary_default();
2810
2811 let organism = capillary_growth(&seed, &field, &config);
2812
2813 assert!(
2814 !organism.nodes.is_empty(),
2815 "Capillary growth should produce nodes"
2816 );
2817 }
2818
2819 #[test]
2820
2821 fn test_slime_mold_growth() {
2822 let seed = Seed::new("slime2", 0.0, 0.0, 0.0, "service");
2824
2825 let field = MorphogenField::new();
2826
2827 let config = GrowthConfig::slime_mold_default();
2828
2829 let organism = slime_mold_growth(&seed, &field, &config);
2830
2831 assert!(
2832 !organism.nodes.is_empty(),
2833 "slime mold must produce nodes with the deterministic seed"
2834 );
2835 }
2836
2837 #[test]
2838
2839 fn test_fractal_growth() {
2840 let seed = test_seed();
2841
2842 let field = MorphogenField::new();
2843
2844 let config = GrowthConfig::fractal_lsystem_default();
2845
2846 let organism = fractal_growth(&seed, &field, &config);
2847
2848 assert!(
2849 !organism.nodes.is_empty(),
2850 "Fractal growth should produce nodes"
2851 );
2852 }
2853
2854 #[test]
2855
2856 fn test_engine_grow_from_seed() {
2857 let engine = MorphogenesisEngine::new();
2858
2859 let seed = test_seed();
2860
2861 let organism = engine.grow_from_seed(&seed, Some(GrowthPattern::Mycelium));
2862
2863 assert!(!organism.nodes.is_empty());
2864
2865 assert!(organism.metrics.is_some());
2866 }
2867
2868 #[test]
2869
2870 fn test_evolve_population() {
2871 let engine = MorphogenesisEngine::new();
2872
2873 let seeds = vec![test_seed()];
2874
2875 let results = engine.evolve_population(&seeds, 2, &FitnessObjective::Balanced, 10);
2876
2877 assert!(!results.is_empty(), "Evolution should produce results");
2878
2879 if results.len() > 1 {
2882 assert!(results[0].fitness_score >= results[1].fitness_score);
2883 }
2884 }
2885
2886 #[test]
2887 fn evolution_is_deterministic_for_the_same_seeds() {
2888 let seeds = vec![
2889 test_seed(),
2890 Seed::new("b", 1.0, 2.0, 3.0, "service"),
2891 Seed::new("c", -1.0, 0.5, 2.0, "cache"),
2892 ];
2893
2894 let first = MorphogenesisEngine::new().evolve_population(
2895 &seeds,
2896 3,
2897 &FitnessObjective::Balanced,
2898 12,
2899 );
2900 let second = MorphogenesisEngine::new().evolve_population(
2901 &seeds,
2902 3,
2903 &FitnessObjective::Balanced,
2904 12,
2905 );
2906
2907 assert_eq!(first.len(), second.len());
2908 for (a, b) in first.iter().zip(second.iter()) {
2909 assert_eq!(a.seed.id, b.seed.id, "mutated seed identity must match");
2910 assert_eq!(
2911 export_fingerprint(a),
2912 export_fingerprint(b),
2913 "organisms must be byte-identical across runs"
2914 );
2915 }
2916 }
2917
2918 #[test]
2919
2920 fn test_evaluate_fitness() {
2921 let seed = test_seed();
2922
2923 let field = MorphogenField::new();
2924
2925 let config = GrowthConfig::default();
2926
2927 let organism = mycelium_growth(&seed, &field, &config);
2928
2929 let fitness = evaluate_fitness(&organism, &FitnessObjective::Balanced);
2930
2931 assert!(fitness.score >= 0.0 && fitness.score <= 1.0);
2932
2933 let latency_fitness = evaluate_fitness(&organism, &FitnessObjective::MinimizeLatency);
2934
2935 assert!(latency_fitness.score >= 0.0 && latency_fitness.score <= 1.0);
2936 }
2937
2938 #[test]
2939
2940 fn test_morphogen_field() {
2941 let mut field = MorphogenField::new();
2942
2943 field.add_attractor(5.0, 5.0, 5.0, 10.0);
2944
2945 let near_conc = field.concentration_at(5.0, 5.0, 5.0);
2946
2947 let far_conc = field.concentration_at(50.0, 50.0, 50.0);
2948
2949 assert!(
2950 near_conc > far_conc,
2951 "Concentration should be higher near attractor"
2952 );
2953 }
2954
2955 #[test]
2956
2957 fn test_organism_metrics() {
2958 let seed = test_seed();
2959
2960 let field = MorphogenField::new();
2961
2962 let config = GrowthConfig::default();
2963
2964 let mut organism = mycelium_growth(&seed, &field, &config);
2965
2966 organism.calculate_metrics();
2967
2968 assert!(organism.metrics.is_some());
2969
2970 let metrics = organism.metrics.unwrap();
2971
2972 assert_eq!(metrics.node_count, organism.nodes.len());
2973
2974 assert_eq!(metrics.connection_count, organism.connections.len());
2975 }
2976
2977 #[test]
2978
2979 fn test_prune_weak() {
2980 let engine = MorphogenesisEngine::new();
2981
2982 let seed = test_seed();
2983
2984 let field = MorphogenField::new();
2985
2986 let config = GrowthConfig::default();
2987
2988 let mut organism = mycelium_growth(&seed, &field, &config);
2989
2990 let before = organism.connections.len();
2991
2992 let pruned = engine.prune_weak(&mut organism, 0.5);
2993
2994 assert!(pruned <= before);
2995 }
2996
2997 #[test]
2998
2999 fn test_express_as_architecture() {
3000 let seed = test_seed();
3001
3002 let field = MorphogenField::new();
3003
3004 let config = GrowthConfig::default();
3005
3006 let organism = mycelium_growth(&seed, &field, &config);
3007
3008 let arch = express_as_architecture(&organism);
3009
3010 assert_eq!(arch.components.len(), organism.nodes.len());
3011 }
3012
3013 #[test]
3014
3015 fn test_trigger_from_vagus() {
3016 let engine = MorphogenesisEngine::new();
3017
3018 use crate::vagus;
3019
3020 let low_tone = vagus::VagusTone {
3021 current: 0.2,
3022
3023 baseline: 0.5,
3024
3025 trend: -0.1,
3026
3027 volatility: 0.3,
3028
3029 last_update: 0,
3030 };
3031
3032 let pulse = vagus::SystemPulse {
3033 timestamp: 0,
3034
3035 cpu_pressure: 0.9,
3036
3037 memory_pressure: 0.5,
3038
3039 io_pressure: 0.5,
3040
3041 network_pressure: 0.3,
3042
3043 request_rate: 100.0,
3044
3045 error_rate: 0.05,
3046
3047 hrv: 0.3,
3048 };
3049
3050 let result = trigger_from_vagus(&low_tone, &pulse, &engine, 0.5);
3051
3052 assert!(
3053 result.is_some(),
3054 "Should trigger growth when vagus tone is low"
3055 );
3056 }
3057
3058 #[test]
3059
3060 fn test_merge_organisms() {
3061 let seed = test_seed();
3062
3063 let field = MorphogenField::new();
3064
3065 let config = GrowthConfig::default();
3066
3067 let org1 = mycelium_growth(&seed, &field, &config);
3068
3069 let org2 = capillary_growth(&seed, &field, &config);
3070
3071 let merged = merge_organisms(&[org1, org2]);
3072
3073 assert!(merged.is_some());
3074
3075 assert!(!merged.unwrap().nodes.is_empty());
3076 }
3077
3078 #[test]
3079
3080 fn test_simplify_organism() {
3081 let seed = test_seed();
3082
3083 let field = MorphogenField::new();
3084
3085 let config = GrowthConfig::default();
3086
3087 let mut organism = mycelium_growth(&seed, &field, &config);
3088
3089 let removed = simplify_organism(&mut organism);
3090
3091 let _ = removed;
3092 }
3093
3094 #[test]
3095
3096 fn test_fingerprint_export() {
3097 let seed = test_seed();
3098
3099 let field = MorphogenField::new();
3100
3101 let config = GrowthConfig::default();
3102
3103 let organism = mycelium_growth(&seed, &field, &config);
3104
3105 let fp = export_fingerprint(&organism);
3106
3107 assert!(!fp.is_empty(), "Fingerprint should not be empty");
3108
3109 assert_eq!(fp[0], 0x01, "Mycelium pattern marker");
3110 }
3111
3112 #[test]
3113
3114 fn test_diffusion() {
3115 let mut field = MorphogenField::new();
3116
3117 field.add_attractor(0.0, 0.0, 0.0, 100.0);
3118
3119 field.diffuse();
3120
3121 let conc = field.concentration_at(0.0, 0.0, 0.0);
3122
3123 assert!(
3124 conc > 0.0,
3125 "After diffusion, concentration should remain > 0"
3126 );
3127 }
3128
3129 #[test]
3130
3131 fn test_evaporation() {
3132 let mut field = MorphogenField::new();
3133
3134 field.add_attractor(0.0, 0.0, 0.0, 1.0);
3135
3136 field.evaporate();
3137
3138 assert!(field.gradients.is_empty() || field.gradients.values().any(|v| *v < 1.0));
3139 }
3140
3141 #[test]
3142
3143 fn test_analyze_topology() {
3144 let seed = test_seed();
3145
3146 let field = MorphogenField::new();
3147
3148 let config = GrowthConfig::default();
3149
3150 let organism = mycelium_growth(&seed, &field, &config);
3151
3152 let analysis = MorphogenesisEngine::analyze_topology(&organism);
3153
3154 assert!(analysis.contains_key("avg_degree"));
3155
3156 assert!(analysis.contains_key("density"));
3157
3158 assert!(analysis.contains_key("avg_latency_ms"));
3159 }
3160
3161 #[test]
3162
3163 fn test_display_traits() {
3164 let seed = test_seed();
3165
3166 let field = MorphogenField::new();
3167
3168 let config = GrowthConfig::default();
3169
3170 let mut organism = mycelium_growth(&seed, &field, &config);
3171
3172 organism.fitness_score = 0.75;
3173
3174 let display_str = format!("{}", organism);
3175
3176 assert!(display_str.contains("Organism:"));
3177
3178 assert!(display_str.contains("fitness=0.75"));
3179 }
3180
3181 #[test]
3182
3183 fn test_growth_config_defaults() {
3184 let mycelium = GrowthConfig::mycelium_default();
3185
3186 assert_eq!(mycelium.pattern, GrowthPattern::Mycelium);
3187
3188 assert!(mycelium.branching_probability > 0.0);
3189
3190 let capillary = GrowthConfig::capillary_default();
3191
3192 assert_eq!(capillary.pattern, GrowthPattern::Capillary);
3193
3194 let slime = GrowthConfig::slime_mold_default();
3195
3196 assert_eq!(slime.pattern, GrowthPattern::SlimeMold);
3197
3198 let fractal = GrowthConfig::fractal_lsystem_default();
3199
3200 assert_eq!(fractal.pattern, GrowthPattern::FractalLSystem);
3201 }
3202
3203 #[test]
3204
3205 fn test_evolution_summary() {
3206 let engine = MorphogenesisEngine::new();
3207
3208 let seeds = vec![test_seed()];
3209
3210 engine.evolve_population(&seeds, 3, &FitnessObjective::Balanced, 8);
3211
3212 let summary = engine.evolution_summary();
3213
3214 assert!(!summary.is_empty());
3215
3216 assert_eq!(summary.len(), 3);
3217 }
3218
3219 #[test]
3220
3221 fn test_get_best_organism() {
3222 let engine = MorphogenesisEngine::new();
3223
3224 let seeds = vec![test_seed()];
3225
3226 engine.evolve_population(&seeds, 2, &FitnessObjective::Balanced, 6);
3227
3228 let best = engine.get_best_organism();
3229
3230 assert!(best.is_some());
3231 }
3232
3233 #[test]
3234
3235 fn test_node_type_conversion() {
3236 assert_eq!(NodeType::Root.to_string(), "Root");
3237
3238 assert_eq!(NodeType::Service.to_string(), "Service");
3239
3240 assert_eq!(NodeType::Cache.to_string(), "Cache");
3241
3242 assert_eq!(NodeType::Custom("test".to_string()).to_string(), "test");
3243 }
3244
3245 #[test]
3246
3247 fn test_different_growth_patterns_produce_different_structures() {
3248 let seed = test_seed();
3249
3250 let field = MorphogenField::new();
3251
3252 let config = GrowthConfig::default();
3253
3254 let org1 = mycelium_growth(&seed, &field, &config);
3255
3256 let org2 = fractal_growth(&seed, &field, &config);
3257
3258 assert!(org1.nodes.len() > 1);
3261
3262 assert!(org2.nodes.len() > 1);
3263 }
3264
3265 #[test]
3266
3267 fn test_organism_is_alive() {
3268 let seed = test_seed();
3269
3270 let field = MorphogenField::new();
3271
3272 let config = GrowthConfig::default();
3273
3274 let organism = mycelium_growth(&seed, &field, &config);
3275
3276 assert!(organism.is_alive());
3277 }
3278
3279 #[test]
3280
3281 fn test_map_to_neuroplasticity() {
3282 let seed = test_seed();
3283
3284 let field = MorphogenField::new();
3285
3286 let config = GrowthConfig::default();
3287
3288 let organism = mycelium_growth(&seed, &field, &config);
3289
3290 let pathways = map_to_neuroplasticity(&organism);
3291
3292 assert_eq!(
3293 pathways.len(),
3294 organism.connections.iter().filter(|c| c.is_active).count()
3295 );
3296 }
3297
3298 #[test]
3299
3300 fn test_hybrid_growth() {
3301 let engine = MorphogenesisEngine::new();
3302
3303 engine.set_config(GrowthConfig {
3304 pattern: GrowthPattern::Hybrid,
3305
3306 ..GrowthConfig::default()
3307 });
3308
3309 let seed = test_seed();
3310
3311 let organism = engine.grow_from_seed(&seed, None);
3312
3313 assert!(!organism.nodes.is_empty());
3314 }
3315
3316 #[test]
3317
3318 fn test_seed_with_energy() {
3319 let seed = Seed::new("high", 0.0, 0.0, 0.0, "db")
3320 .with_energy(500.0)
3321 .with_pattern(GrowthPattern::Capillary);
3322
3323 assert_eq!(seed.energy, 500.0);
3324
3325 assert_eq!(seed.preferred_pattern, Some(GrowthPattern::Capillary));
3326 }
3327
3328 #[test]
3329
3330 fn test_organism_age() {
3331 let seed = test_seed();
3332
3333 let field = MorphogenField::new();
3334
3335 let config = GrowthConfig::default();
3336
3337 let mut organism = mycelium_growth(&seed, &field, &config);
3338
3339 let initial_energy: f64 = organism.nodes.iter().map(|n| n.energy).sum();
3340
3341 organism.age_one_cycle();
3342
3343 let after_energy: f64 = organism.nodes.iter().map(|n| n.energy).sum();
3344
3345 assert!(after_energy < initial_energy, "Aging should reduce energy");
3346
3347 assert_eq!(organism.age_cycles, 1);
3348 }
3349
3350 #[test]
3351
3352 fn test_map_to_architecture_preserves_structure() {
3353 let seed = Seed::new("arch_test", 0.0, 0.0, 0.0, "gateway");
3354
3355 let field = MorphogenField::new();
3356
3357 let config = GrowthConfig::capillary_default();
3358
3359 let organism = capillary_growth(&seed, &field, &config);
3360
3361 let arch = express_as_architecture(&organism);
3362
3363 assert_eq!(arch.components.len(), organism.nodes.len());
3364 }
3365}