Skip to main content

feagi_evolutionary/
templates.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Genome templates for FEAGI.
6
7Provides templates for creating genomes from scratch, including:
8- Minimal genome template
9- Cortical area templates (IPU, OPU, CORE)
10- Default neural parameters
11- Embedded default genomes
12
13Copyright 2025 Neuraville Inc.
14Licensed under the Apache License, Version 2.0
15*/
16
17use crate::{
18    GenomeMetadata, GenomeSignatures, GenomeStats, MorphologyRegistry, PhysiologyConfig,
19    RuntimeGenome,
20};
21use feagi_structures::genomic::cortical_area::CoreCorticalType;
22use feagi_structures::genomic::cortical_area::{CorticalArea, CorticalAreaDimensions};
23use feagi_structures::genomic::descriptors::GenomeCoordinate3D;
24use serde_json::Value;
25use std::collections::HashMap;
26
27/// Embedded essential genome (loaded at compile time)
28pub const ESSENTIAL_GENOME_JSON: &str = include_str!("../genomes/essential_genome.json");
29
30/// Embedded barebones genome (loaded at compile time)
31pub const BAREBONES_GENOME_JSON: &str = include_str!("../genomes/barebones_genome.json");
32
33/// Embedded test genome (loaded at compile time)
34pub const TEST_GENOME_JSON: &str = include_str!("../genomes/test_genome.json");
35
36/// Embedded vision genome (loaded at compile time)
37pub const VISION_GENOME_JSON: &str = include_str!("../genomes/vision_genome.json");
38
39/// Default neural properties for all cortical areas
40pub fn get_default_neural_properties() -> HashMap<String, Value> {
41    let mut props = HashMap::new();
42    props.insert("per_voxel_neuron_cnt".to_string(), Value::from(1));
43    props.insert("synapse_attractivity".to_string(), Value::from(100.0));
44    props.insert("degeneration".to_string(), Value::from(0.0));
45    props.insert("psp_uniform_distribution".to_string(), Value::from(true));
46    props.insert("postsynaptic_current_max".to_string(), Value::from(10000.0));
47    props.insert("postsynaptic_current".to_string(), Value::from(500.0));
48    props.insert("firing_threshold".to_string(), Value::from(0.1));
49    props.insert("refractory_period".to_string(), Value::from(0));
50    props.insert("leak_coefficient".to_string(), Value::from(0.0));
51    props.insert("leak_variability".to_string(), Value::from(0.0));
52    props.insert("consecutive_fire_cnt_max".to_string(), Value::from(0));
53    props.insert("snooze_length".to_string(), Value::from(0));
54    props.insert("mp_charge_accumulation".to_string(), Value::from(false));
55    props.insert("mp_driven_psp".to_string(), Value::from(false));
56    props.insert("neuron_excitability".to_string(), Value::from(1.0));
57    props.insert("visualization".to_string(), Value::from(true));
58    props.insert("memory_twin_of".to_string(), Value::Null);
59    props.insert(
60        "cortical_mapping_dst".to_string(),
61        Value::Object(serde_json::Map::new()),
62    );
63    props
64}
65
66/// Create _death cortical area (cortical_idx = 0) from template
67pub fn create_death_area() -> CorticalArea {
68    let cortical_id = CoreCorticalType::Death.to_cortical_id();
69    let cortical_type = cortical_id
70        .as_cortical_type()
71        .expect("Death cortical ID should map to Core type");
72
73    let mut area = CorticalArea::new(
74        cortical_id,
75        0, // cortical_idx = 0 (reserved)
76        "Death".to_string(),
77        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
78        GenomeCoordinate3D::new(0, 0, -20),
79        cortical_type,
80    )
81    .expect("Failed to create _death area");
82
83    let mut props = get_default_neural_properties();
84    props.insert("cortical_group".to_string(), Value::from("CORE"));
85    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, -20]));
86    area.properties = props;
87    area
88}
89
90/// Create _power cortical area (cortical_idx = 1) from template
91pub fn create_power_area() -> CorticalArea {
92    let cortical_id = CoreCorticalType::Power.to_cortical_id();
93    let cortical_type = cortical_id
94        .as_cortical_type()
95        .expect("Power cortical ID should map to Core type");
96
97    let mut area = CorticalArea::new(
98        cortical_id,
99        1, // cortical_idx = 1 (reserved)
100        "Brain_Power".to_string(),
101        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
102        GenomeCoordinate3D::new(0, 0, -20),
103        cortical_type,
104    )
105    .expect("Failed to create _power area");
106
107    let mut props = get_default_neural_properties();
108    props.insert("cortical_group".to_string(), Value::from("CORE"));
109    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, -10]));
110    props.insert("firing_threshold".to_string(), Value::from(0.1));
111    props.insert("postsynaptic_current".to_string(), Value::from(500.0));
112    props.insert("neuron_excitability".to_string(), Value::from(100.0));
113    area.properties = props;
114    area
115}
116
117/// Create _fatigue cortical area (cortical_idx = 2) from template
118pub fn create_fatigue_area() -> CorticalArea {
119    let cortical_id = CoreCorticalType::Fatigue.to_cortical_id();
120    let cortical_type = cortical_id
121        .as_cortical_type()
122        .expect("Fatigue cortical ID should map to Core type");
123
124    let mut area = CorticalArea::new(
125        cortical_id,
126        2, // cortical_idx = 2 (reserved)
127        "Fatigue".to_string(),
128        CorticalAreaDimensions::new(1, 1, 1).expect("Failed to create dimensions"),
129        GenomeCoordinate3D::new(0, 0, -30),
130        cortical_type,
131    )
132    .expect("Failed to create _fatigue area");
133
134    let mut props = get_default_neural_properties();
135    props.insert("cortical_group".to_string(), Value::from("CORE"));
136    props.insert("2d_coordinate".to_string(), Value::from(vec![-10, 0]));
137    area.properties = props;
138    area
139}
140
141/// Create a minimal empty genome
142pub fn create_minimal_genome(genome_id: String, genome_title: String) -> RuntimeGenome {
143    RuntimeGenome {
144        metadata: GenomeMetadata {
145            genome_id,
146            genome_title,
147            genome_description: "Minimal genome template".to_string(),
148            version: "2.0".to_string(),
149            timestamp: std::time::SystemTime::now()
150                .duration_since(std::time::UNIX_EPOCH)
151                .unwrap()
152                .as_secs_f64(),
153            brain_regions_root: None, // Will be set after neuroembryogenesis
154        },
155        cortical_areas: HashMap::new(),
156        brain_regions: HashMap::new(),
157        morphologies: MorphologyRegistry::new(),
158        physiology: PhysiologyConfig::default(),
159        signatures: GenomeSignatures {
160            genome: String::new(),
161            blueprint: String::new(),
162            physiology: String::new(),
163            morphologies: None,
164        },
165        stats: GenomeStats::default(),
166    }
167}
168
169/// Create a genome with core areas (_death, _power)
170pub fn create_genome_with_core_areas(genome_id: String, genome_title: String) -> RuntimeGenome {
171    let mut genome = create_minimal_genome(genome_id, genome_title);
172
173    // Add core areas (convert 6-char strings to CorticalID)
174    let death_id =
175        crate::genome::parser::string_to_cortical_id("_death").expect("Valid cortical ID");
176    let power_id =
177        crate::genome::parser::string_to_cortical_id("_power").expect("Valid cortical ID");
178    let fatigue_id =
179        crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid cortical ID");
180
181    genome.cortical_areas.insert(death_id, create_death_area());
182    genome.cortical_areas.insert(power_id, create_power_area());
183    genome
184        .cortical_areas
185        .insert(fatigue_id, create_fatigue_area());
186
187    genome
188}
189
190/// Create a genome with core morphologies
191pub fn create_genome_with_core_morphologies(
192    genome_id: String,
193    genome_title: String,
194) -> RuntimeGenome {
195    let mut genome = create_minimal_genome(genome_id, genome_title);
196
197    // Add core morphologies
198    add_core_morphologies(&mut genome.morphologies);
199
200    genome
201}
202
203/// CRITICAL: Ensure a genome has all required core components
204///
205/// This function checks if a genome has:
206/// 1. Core cortical areas (_death, _power)
207/// 2. Core morphologies (block_to_block, projector, etc.)
208///
209/// If any are missing, they are automatically added. This ensures every genome
210/// can function properly regardless of its source.
211///
212/// # Arguments
213/// * `genome` - The genome to validate and fix
214///
215/// # Returns
216/// A tuple of (areas_added, morphologies_added) indicating what was added
217pub fn ensure_core_components(genome: &mut RuntimeGenome) -> (usize, usize) {
218    let mut areas_added = 0;
219    let mut morphologies_added = 0;
220
221    // Convert core area IDs
222    let death_id =
223        crate::genome::parser::string_to_cortical_id("_death").expect("Valid cortical ID");
224    let power_id =
225        crate::genome::parser::string_to_cortical_id("_power").expect("Valid cortical ID");
226    let fatigue_id =
227        crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid cortical ID");
228
229    // 1. Ensure core cortical areas exist
230    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(death_id) {
231        let death_area = create_death_area();
232        e.insert(death_area);
233        areas_added += 1;
234        tracing::info!("Added missing core area: _death (cortical_idx=0)");
235    }
236
237    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(power_id) {
238        let power_area = create_power_area();
239        e.insert(power_area);
240        areas_added += 1;
241        tracing::info!("Added missing core area: _power (cortical_idx=1)");
242    }
243
244    if let std::collections::hash_map::Entry::Vacant(e) = genome.cortical_areas.entry(fatigue_id) {
245        let fatigue_area = create_fatigue_area();
246        e.insert(fatigue_area);
247        areas_added += 1;
248        tracing::info!("Added missing core area: _fatigue (cortical_idx=2)");
249    }
250
251    // 2. Ensure core morphologies exist
252    let required_morphologies = vec![
253        "block_to_block",
254        "projector",
255        "episodic_memory",
256        "memory_replay",
257        "associative_memory",
258        "all_to_0-0-0",
259        "0-0-0_to_all",
260        "lateral_+x",
261        "lateral_-x",
262        "lateral_+y",
263        "lateral_-y",
264        "lateral_+z",
265        "lateral_-z",
266    ];
267
268    for morph_name in required_morphologies {
269        if !genome.morphologies.contains(morph_name) {
270            morphologies_added += 1;
271        }
272    }
273
274    // Add all missing core morphologies in one call
275    if morphologies_added > 0 {
276        add_core_morphologies(&mut genome.morphologies);
277        tracing::info!("Added {} missing core morphologies", morphologies_added);
278    }
279
280    (areas_added, morphologies_added)
281}
282
283/// Add core morphologies to a registry
284pub fn add_core_morphologies(registry: &mut MorphologyRegistry) {
285    use crate::{Morphology, MorphologyParameters, MorphologyType};
286
287    // block_to_block - Connect neurons in same position
288    registry.add_morphology(
289        "block_to_block".to_string(),
290        Morphology {
291            morphology_type: MorphologyType::Vectors,
292            parameters: MorphologyParameters::Vectors {
293                vectors: vec![[0, 0, 0]],
294            },
295            class: "core".to_string(),
296        },
297    );
298
299    // projector - Function-based morphology
300    registry.add_morphology(
301        "projector".to_string(),
302        Morphology {
303            morphology_type: MorphologyType::Functions,
304            parameters: MorphologyParameters::Functions {},
305            class: "core".to_string(),
306        },
307    );
308
309    // episodic_memory - Function-based morphology
310    registry.add_morphology(
311        "episodic_memory".to_string(),
312        Morphology {
313            morphology_type: MorphologyType::Functions,
314            parameters: MorphologyParameters::Functions {},
315            class: "core".to_string(),
316        },
317    );
318
319    // memory_replay - Function-based morphology
320    registry.add_morphology(
321        "memory_replay".to_string(),
322        Morphology {
323            morphology_type: MorphologyType::Functions,
324            parameters: MorphologyParameters::Functions {},
325            class: "core".to_string(),
326        },
327    );
328
329    // associative_memory (bi-directional STDP) - Function-based morphology
330    registry.add_morphology(
331        "associative_memory".to_string(),
332        Morphology {
333            morphology_type: MorphologyType::Functions,
334            parameters: MorphologyParameters::Functions {},
335            class: "core".to_string(),
336        },
337    );
338
339    // all_to_0-0-0 - Connect all neurons to origin
340    registry.add_morphology(
341        "all_to_0-0-0".to_string(),
342        Morphology {
343            morphology_type: MorphologyType::Patterns,
344            parameters: MorphologyParameters::Patterns {
345                patterns: vec![[
346                    vec![
347                        crate::PatternElement::Wildcard,
348                        crate::PatternElement::Wildcard,
349                        crate::PatternElement::Wildcard,
350                    ],
351                    vec![
352                        crate::PatternElement::Value(0),
353                        crate::PatternElement::Value(0),
354                        crate::PatternElement::Value(0),
355                    ],
356                ]],
357            },
358            class: "core".to_string(),
359        },
360    );
361
362    // 0-0-0_to_all - Connect origin to all neurons
363    registry.add_morphology(
364        "0-0-0_to_all".to_string(),
365        Morphology {
366            morphology_type: MorphologyType::Patterns,
367            parameters: MorphologyParameters::Patterns {
368                patterns: vec![[
369                    vec![
370                        crate::PatternElement::Value(0),
371                        crate::PatternElement::Value(0),
372                        crate::PatternElement::Value(0),
373                    ],
374                    vec![
375                        crate::PatternElement::Wildcard,
376                        crate::PatternElement::Wildcard,
377                        crate::PatternElement::Wildcard,
378                    ],
379                ]],
380            },
381            class: "core".to_string(),
382        },
383    );
384
385    // lateral_+x - Connect along +X axis
386    registry.add_morphology(
387        "lateral_+x".to_string(),
388        Morphology {
389            morphology_type: MorphologyType::Vectors,
390            parameters: MorphologyParameters::Vectors {
391                vectors: vec![[1, 0, 0]],
392            },
393            class: "core".to_string(),
394        },
395    );
396
397    // lateral_-x - Connect along -X axis
398    registry.add_morphology(
399        "lateral_-x".to_string(),
400        Morphology {
401            morphology_type: MorphologyType::Vectors,
402            parameters: MorphologyParameters::Vectors {
403                vectors: vec![[-1, 0, 0]],
404            },
405            class: "core".to_string(),
406        },
407    );
408
409    // lateral_+y - Connect along +Y axis
410    registry.add_morphology(
411        "lateral_+y".to_string(),
412        Morphology {
413            morphology_type: MorphologyType::Vectors,
414            parameters: MorphologyParameters::Vectors {
415                vectors: vec![[0, 1, 0]],
416            },
417            class: "core".to_string(),
418        },
419    );
420
421    // lateral_-y - Connect along -Y axis
422    registry.add_morphology(
423        "lateral_-y".to_string(),
424        Morphology {
425            morphology_type: MorphologyType::Vectors,
426            parameters: MorphologyParameters::Vectors {
427                vectors: vec![[0, -1, 0]],
428            },
429            class: "core".to_string(),
430        },
431    );
432
433    // lateral_+z - Connect along +Z axis
434    registry.add_morphology(
435        "lateral_+z".to_string(),
436        Morphology {
437            morphology_type: MorphologyType::Vectors,
438            parameters: MorphologyParameters::Vectors {
439                vectors: vec![[0, 0, 1]],
440            },
441            class: "core".to_string(),
442        },
443    );
444
445    // lateral_-z - Connect along -Z axis
446    registry.add_morphology(
447        "lateral_-z".to_string(),
448        Morphology {
449            morphology_type: MorphologyType::Vectors,
450            parameters: MorphologyParameters::Vectors {
451                vectors: vec![[0, 0, -1]],
452            },
453            class: "core".to_string(),
454        },
455    );
456}
457
458/// Load essential genome from embedded JSON
459///
460/// Automatically ensures core components (_death, _power, core morphologies) are present
461pub fn load_essential_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
462    use crate::genome::loader::load_genome_from_json;
463    let mut genome = load_genome_from_json(ESSENTIAL_GENOME_JSON)?;
464    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
465    if areas_added > 0 || morphs_added > 0 {
466        tracing::info!(
467            "Essential genome: added {} core areas, {} core morphologies",
468            areas_added,
469            morphs_added
470        );
471    }
472    Ok(genome)
473}
474
475/// Load barebones genome from embedded JSON
476///
477/// Automatically ensures core components (_death, _power, core morphologies) are present
478pub fn load_barebones_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
479    use crate::genome::loader::load_genome_from_json;
480    let mut genome = load_genome_from_json(BAREBONES_GENOME_JSON)?;
481    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
482    if areas_added > 0 || morphs_added > 0 {
483        tracing::info!(
484            "Barebones genome: added {} core areas, {} core morphologies",
485            areas_added,
486            morphs_added
487        );
488    }
489    Ok(genome)
490}
491
492/// Load test genome from embedded JSON
493///
494/// Automatically ensures core components (_death, _power, core morphologies) are present
495pub fn load_test_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
496    use crate::genome::loader::load_genome_from_json;
497    let mut genome = load_genome_from_json(TEST_GENOME_JSON)?;
498    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
499    if areas_added > 0 || morphs_added > 0 {
500        tracing::info!(
501            "Test genome: added {} core areas, {} core morphologies",
502            areas_added,
503            morphs_added
504        );
505    }
506    Ok(genome)
507}
508
509/// Load vision genome from embedded JSON
510///
511/// Automatically ensures core components (_death, _power, core morphologies) are present
512pub fn load_vision_genome() -> Result<RuntimeGenome, crate::types::EvoError> {
513    use crate::genome::loader::load_genome_from_json;
514    let mut genome = load_genome_from_json(VISION_GENOME_JSON)?;
515    let (areas_added, morphs_added) = ensure_core_components(&mut genome);
516    if areas_added > 0 || morphs_added > 0 {
517        tracing::info!(
518            "Vision genome: added {} core areas, {} core morphologies",
519            areas_added,
520            morphs_added
521        );
522    }
523    Ok(genome)
524}
525
526#[cfg(test)]
527mod tests {
528    use super::*;
529
530    #[test]
531    fn test_create_minimal_genome() {
532        let genome = create_minimal_genome("test_genome".to_string(), "Test Genome".to_string());
533
534        assert_eq!(genome.metadata.genome_id, "test_genome");
535        assert_eq!(genome.metadata.version, "2.0");
536        assert_eq!(genome.cortical_areas.len(), 0);
537        assert_eq!(genome.morphologies.count(), 0);
538    }
539
540    #[test]
541    fn test_create_genome_with_core_areas() {
542        let genome =
543            create_genome_with_core_areas("test_genome".to_string(), "Test Genome".to_string());
544
545        assert_eq!(genome.metadata.genome_id, "test_genome");
546        assert_eq!(genome.cortical_areas.len(), 3);
547
548        let death_id = crate::genome::parser::string_to_cortical_id("_death").expect("Valid ID");
549        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
550        let fatigue_id =
551            crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid ID");
552        assert!(genome.cortical_areas.contains_key(&death_id));
553        assert!(genome.cortical_areas.contains_key(&power_id));
554        assert!(genome.cortical_areas.contains_key(&fatigue_id));
555
556        // Verify _power has correct properties
557        let power = genome.cortical_areas.get(&power_id).unwrap();
558        assert_eq!(power.cortical_id.as_base_64(), power_id.as_base_64());
559        assert_eq!(power.cortical_idx, 1);
560        assert_eq!(power.dimensions.width, 1);
561        assert_eq!(power.dimensions.height, 1);
562        assert_eq!(power.dimensions.depth, 1);
563    }
564
565    #[test]
566    fn test_create_genome_with_core_morphologies() {
567        let genome = create_genome_with_core_morphologies(
568            "test_genome".to_string(),
569            "Test Genome".to_string(),
570        );
571
572        assert_eq!(genome.metadata.genome_id, "test_genome");
573        assert!(genome.morphologies.count() > 0);
574        assert!(genome.morphologies.contains("block_to_block"));
575        assert!(genome.morphologies.contains("projector"));
576        assert!(genome.morphologies.contains("lateral_+x"));
577    }
578
579    #[test]
580    fn test_add_core_morphologies() {
581        let mut registry = MorphologyRegistry::new();
582        add_core_morphologies(&mut registry);
583
584        // Should have at least 11 core morphologies
585        assert!(registry.count() >= 11);
586        assert!(registry.contains("block_to_block"));
587        assert!(registry.contains("projector"));
588        assert!(registry.contains("all_to_0-0-0"));
589        assert!(registry.contains("lateral_+x"));
590        assert!(registry.contains("lateral_-z"));
591    }
592
593    #[test]
594    fn test_embedded_genomes_exist() {
595        // Test that embedded genome strings are not empty
596        // These are compile-time constants, so they're always non-empty
597        // The assertions verify the constants are defined correctly
598        #[allow(clippy::const_is_empty)]
599        {
600            assert!(!ESSENTIAL_GENOME_JSON.is_empty());
601            assert!(!BAREBONES_GENOME_JSON.is_empty());
602            assert!(!TEST_GENOME_JSON.is_empty());
603            assert!(!VISION_GENOME_JSON.is_empty());
604        }
605    }
606
607    #[test]
608    fn test_load_essential_genome() {
609        let genome = load_essential_genome().expect("Failed to load essential genome");
610        assert!(!genome.cortical_areas.is_empty());
611        // Essential genome should have _power
612        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
613        assert!(genome.cortical_areas.contains_key(&power_id));
614    }
615
616    #[test]
617    fn test_ensure_core_components_adds_missing_areas() {
618        // Create a minimal genome without core areas
619        let mut genome = create_minimal_genome("test".to_string(), "Test".to_string());
620
621        assert_eq!(genome.cortical_areas.len(), 0);
622
623        // Ensure core components
624        let (areas_added, _) = ensure_core_components(&mut genome);
625
626        // Should have added _death, _power, and _fatigue
627        assert_eq!(areas_added, 3);
628
629        let death_id = crate::genome::parser::string_to_cortical_id("_death").expect("Valid ID");
630        let power_id = crate::genome::parser::string_to_cortical_id("_power").expect("Valid ID");
631        let fatigue_id =
632            crate::genome::parser::string_to_cortical_id("_fatigue").expect("Valid ID");
633        assert!(genome.cortical_areas.contains_key(&death_id));
634        assert!(genome.cortical_areas.contains_key(&power_id));
635        assert!(genome.cortical_areas.contains_key(&fatigue_id));
636
637        // Verify cortical_idx assignments
638        assert_eq!(
639            genome.cortical_areas.get(&death_id).unwrap().cortical_idx,
640            0
641        );
642        assert_eq!(
643            genome.cortical_areas.get(&power_id).unwrap().cortical_idx,
644            1
645        );
646        assert_eq!(
647            genome.cortical_areas.get(&fatigue_id).unwrap().cortical_idx,
648            2
649        );
650    }
651
652    #[test]
653    fn test_ensure_core_components_adds_missing_morphologies() {
654        // Create a genome with core areas but no morphologies
655        let mut genome = create_genome_with_core_areas("test".to_string(), "Test".to_string());
656
657        assert_eq!(genome.morphologies.count(), 0);
658
659        // Ensure core components
660        let (_, morphs_added) = ensure_core_components(&mut genome);
661
662        // Should have added core morphologies
663        assert!(morphs_added > 0);
664        assert!(genome.morphologies.contains("block_to_block"));
665        assert!(genome.morphologies.contains("projector"));
666        assert!(genome.morphologies.contains("episodic_memory"));
667        assert!(genome.morphologies.contains("lateral_+x"));
668    }
669
670    #[test]
671    fn test_ensure_core_components_idempotent() {
672        // Create a genome with all core components
673        let mut genome = create_genome_with_core_areas("test".to_string(), "Test".to_string());
674        add_core_morphologies(&mut genome.morphologies);
675
676        // Run ensure_core_components
677        let (areas_added, morphs_added) = ensure_core_components(&mut genome);
678
679        // Should not add anything (already present)
680        assert_eq!(areas_added, 0);
681        assert_eq!(morphs_added, 0);
682    }
683}