feagi_brain_development/
neuroembryogenesis.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Neuroembryogenesis - Brain Development from Genome.
6
7This module orchestrates the development of a functional connectome (phenotype)
8from a genome blueprint (genotype). It coordinates:
9
101. **Corticogenesis**: Creating cortical area structures
112. **Voxelogenesis**: Establishing 3D spatial framework
123. **Neurogenesis**: Generating neurons within cortical areas
134. **Synaptogenesis**: Forming synaptic connections between neurons
14
15The process is biologically inspired by embryonic brain development.
16
17Copyright 2025 Neuraville Inc.
18Licensed under the Apache License, Version 2.0
19*/
20
21use crate::connectome_manager::ConnectomeManager;
22use crate::models::{CorticalArea, CorticalID};
23use crate::types::{BduError, BduResult};
24use feagi_evolutionary::RuntimeGenome;
25use feagi_npu_neural::types::{Precision, QuantizationSpec};
26use parking_lot::RwLock;
27use std::sync::Arc;
28use tracing::{debug, error, info, trace, warn};
29
30/// Development stage tracking
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum DevelopmentStage {
33    /// Initial state, not started
34    Initialization,
35    /// Creating cortical area structures
36    Corticogenesis,
37    /// Establishing spatial framework
38    Voxelogenesis,
39    /// Generating neurons
40    Neurogenesis,
41    /// Forming synaptic connections
42    Synaptogenesis,
43    /// Development completed successfully
44    Completed,
45    /// Development failed
46    Failed,
47}
48
49/// Development progress information
50#[derive(Debug, Clone)]
51pub struct DevelopmentProgress {
52    /// Current development stage
53    pub stage: DevelopmentStage,
54    /// Progress percentage within current stage (0-100)
55    pub progress: u8,
56    /// Cortical areas created
57    pub cortical_areas_created: usize,
58    /// Neurons created
59    pub neurons_created: usize,
60    /// Synapses created
61    pub synapses_created: usize,
62    /// Duration of development in milliseconds
63    pub duration_ms: u64,
64}
65
66impl Default for DevelopmentProgress {
67    fn default() -> Self {
68        Self {
69            stage: DevelopmentStage::Initialization,
70            progress: 0,
71            cortical_areas_created: 0,
72            neurons_created: 0,
73            synapses_created: 0,
74            duration_ms: 0,
75        }
76    }
77}
78
79/// Neuroembryogenesis orchestrator
80///
81/// Manages the development of a brain from genome instructions.
82/// Uses ConnectomeManager to build the actual neural structures.
83///
84/// # Type Parameters
85/// - `T: NeuralValue`: The numeric precision for the connectome (f32, INT8Value, f16)
86pub struct Neuroembryogenesis {
87    /// Reference to ConnectomeManager for building structures
88    connectome_manager: Arc<RwLock<ConnectomeManager>>,
89
90    /// Current development progress
91    progress: Arc<RwLock<DevelopmentProgress>>,
92
93    /// Start time for duration tracking
94    start_time: std::time::Instant,
95}
96
97impl Neuroembryogenesis {
98    /// Create a new neuroembryogenesis instance
99    pub fn new(connectome_manager: Arc<RwLock<ConnectomeManager>>) -> Self {
100        Self {
101            connectome_manager,
102            progress: Arc::new(RwLock::new(DevelopmentProgress::default())),
103            start_time: std::time::Instant::now(),
104        }
105    }
106
107    /// Get current development progress
108    pub fn get_progress(&self) -> DevelopmentProgress {
109        self.progress.read().clone()
110    }
111
112    /// Incrementally add cortical areas to an existing connectome
113    ///
114    /// This is for adding new cortical areas after the initial genome has been loaded.
115    /// Unlike `develop_from_genome()`, this only processes the new areas.
116    ///
117    /// # Arguments
118    /// * `areas` - The cortical areas to add
119    /// * `genome` - The full runtime genome (needed for synaptogenesis context)
120    ///
121    /// # Returns
122    /// * Number of neurons created and synapses created
123    pub fn add_cortical_areas(
124        &mut self,
125        areas: Vec<CorticalArea>,
126        genome: &RuntimeGenome,
127    ) -> BduResult<(usize, usize)> {
128        info!(target: "feagi-bdu", "🧬 Incrementally adding {} cortical areas", areas.len());
129
130        let mut total_neurons = 0;
131        let mut total_synapses = 0;
132
133        // Stage 1: Add cortical area structures (Corticogenesis)
134        for area in &areas {
135            let mut manager = self.connectome_manager.write();
136            manager.add_cortical_area(area.clone())?;
137            info!(target: "feagi-bdu", "  βœ“ Added cortical area structure: {}", area.cortical_id.as_base_64());
138        }
139
140        // Stage 2: Create neurons for each area (Neurogenesis)
141        // CRITICAL: Create core area neurons FIRST to ensure deterministic IDs
142        use feagi_structures::genomic::cortical_area::CoreCorticalType;
143        let death_id = CoreCorticalType::Death.to_cortical_id();
144        let power_id = CoreCorticalType::Power.to_cortical_id();
145        let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
146
147        let mut core_areas = Vec::new();
148        let mut other_areas = Vec::new();
149
150        // Separate core areas from other areas
151        for area in &areas {
152            if area.cortical_id == death_id {
153                core_areas.push((0, area)); // Area 0 = _death
154            } else if area.cortical_id == power_id {
155                core_areas.push((1, area)); // Area 1 = _power
156            } else if area.cortical_id == fatigue_id {
157                core_areas.push((2, area)); // Area 2 = _fatigue
158            } else {
159                other_areas.push(area);
160            }
161        }
162
163        // Sort core areas by their deterministic index (0, 1, 2)
164        core_areas.sort_by_key(|(idx, _)| *idx);
165
166        // STEP 1: Create core area neurons FIRST
167        if !core_areas.is_empty() {
168            info!(target: "feagi-bdu", "  🎯 Creating core area neurons FIRST ({} areas) for deterministic IDs", core_areas.len());
169            for (core_idx, area) in &core_areas {
170                let neurons_created = {
171                    let mut manager = self.connectome_manager.write();
172                    manager.create_neurons_for_area(&area.cortical_id)
173                };
174
175                match neurons_created {
176                    Ok(count) => {
177                        total_neurons += count as usize;
178                        info!(target: "feagi-bdu", "  βœ… Created {} neurons for core area {} (deterministic ID: neuron {})",
179                            count, area.cortical_id.as_base_64(), core_idx);
180                    }
181                    Err(e) => {
182                        error!(target: "feagi-bdu", "  ❌ FATAL: Failed to create neurons for core area {}: {}", area.cortical_id.as_base_64(), e);
183                        return Err(e);
184                    }
185                }
186            }
187        }
188
189        // STEP 2: Create neurons for other areas
190        for area in &other_areas {
191            let neurons_created = {
192                let mut manager = self.connectome_manager.write();
193                manager.create_neurons_for_area(&area.cortical_id)
194            };
195
196            match neurons_created {
197                Ok(count) => {
198                    total_neurons += count as usize;
199                    trace!(
200                        target: "feagi-bdu",
201                        "Created {} neurons for area {}",
202                        count,
203                        area.cortical_id.as_base_64()
204                    );
205                }
206                Err(e) => {
207                    error!(target: "feagi-bdu", "  ❌ FATAL: Failed to create neurons for {}: {}", area.cortical_id.as_base_64(), e);
208                    // CRITICAL: NPU capacity errors must propagate to UI
209                    return Err(e);
210                }
211            }
212        }
213
214        // Stage 3: Create synapses for each area (Synaptogenesis)
215        for area in &areas {
216            // Check if area has mappings
217            let has_dstmap = area
218                .properties
219                .get("cortical_mapping_dst")
220                .and_then(|v| v.as_object())
221                .map(|m| !m.is_empty())
222                .unwrap_or(false);
223
224            if !has_dstmap {
225                debug!(target: "feagi-bdu", "  No mappings for area {}", area.cortical_id.as_base_64());
226                continue;
227            }
228
229            let synapses_created = {
230                let mut manager = self.connectome_manager.write();
231                manager.apply_cortical_mapping(&area.cortical_id)
232            };
233
234            match synapses_created {
235                Ok(count) => {
236                    total_synapses += count as usize;
237                    trace!(
238                        target: "feagi-bdu",
239                        "Created {} synapses for area {}",
240                        count,
241                        area.cortical_id
242                    );
243                }
244                Err(e) => {
245                    warn!(target: "feagi-bdu", "  ⚠️ Failed to create synapses for {}: {}", area.cortical_id, e);
246                    let estimated = estimate_synapses_for_area(area, genome);
247                    total_synapses += estimated;
248                }
249            }
250        }
251
252        info!(target: "feagi-bdu", "βœ… Incremental add complete: {} areas, {} neurons, {} synapses",
253              areas.len(), total_neurons, total_synapses);
254
255        Ok((total_neurons, total_synapses))
256    }
257
258    /// Develop the brain from a genome
259    ///
260    /// This is the main entry point that orchestrates all development stages.
261    pub fn develop_from_genome(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
262        info!(target: "feagi-bdu","🧬 Starting neuroembryogenesis for genome: {}", genome.metadata.genome_id);
263
264        // Phase 5: Parse quantization precision and dispatch to type-specific builder
265        let _quantization_precision = &genome.physiology.quantization_precision;
266        // Precision parsing handled in genome loader
267        let quant_spec = QuantizationSpec::default();
268
269        info!(target: "feagi-bdu",
270            "   Quantization precision: {:?} (range: [{}, {}] for membrane potential)",
271            quant_spec.precision,
272            quant_spec.membrane_potential_min,
273            quant_spec.membrane_potential_max
274        );
275
276        // Phase 6: Type dispatch - Neuroembryogenesis is now fully generic!
277        // The precision is determined by the type T of this Neuroembryogenesis instance.
278        // All stages (corticogenesis, neurogenesis, synaptogenesis) automatically use the correct type.
279        match quant_spec.precision {
280            Precision::FP32 => {
281                info!(target: "feagi-bdu", "   βœ“ Using FP32 (32-bit floating-point) - highest precision");
282                info!(target: "feagi-bdu", "   Memory usage: Baseline (4 bytes/neuron for membrane potential)");
283            }
284            Precision::INT8 => {
285                info!(target: "feagi-bdu", "   βœ“ Using INT8 (8-bit integer) - memory efficient");
286                info!(target: "feagi-bdu", "   Memory reduction: 42% (1 byte/neuron for membrane potential)");
287                info!(target: "feagi-bdu", "   Quantization range: [{}, {}]",
288                    quant_spec.membrane_potential_min,
289                    quant_spec.membrane_potential_max);
290                // Note: If this Neuroembryogenesis was created with <f32>, this will warn below
291                // The caller must create Neuroembryogenesis::<INT8Value> to use INT8
292            }
293            Precision::FP16 => {
294                warn!(target: "feagi-bdu", "   FP16 quantization requested but not yet implemented.");
295                warn!(target: "feagi-bdu", "   FP16 support planned for future GPU optimization.");
296                // Note: Requires f16 type and implementation
297            }
298        }
299
300        // Type consistency is now handled by DynamicNPU at creation time
301        // The caller (main.rs) peeks at genome precision and creates the correct DynamicNPU variant
302        info!(target: "feagi-bdu", "   βœ“ Quantization handled by DynamicNPU (dispatches at runtime)");
303
304        // Update stage: Initialization
305        self.update_stage(DevelopmentStage::Initialization, 0);
306
307        // Stage 1: Corticogenesis - Create cortical area structures
308        self.corticogenesis(genome)?;
309
310        // Stage 2: Voxelogenesis - Establish spatial framework
311        self.voxelogenesis(genome)?;
312
313        // Stage 3: Neurogenesis - Generate neurons
314        self.neurogenesis(genome)?;
315
316        // Stage 4: Synaptogenesis - Form synaptic connections
317        self.synaptogenesis(genome)?;
318
319        // Mark as completed
320        self.update_stage(DevelopmentStage::Completed, 100);
321
322        let progress = self.progress.read();
323        info!(target: "feagi-bdu",
324            "βœ… Neuroembryogenesis completed in {}ms: {} cortical areas, {} neurons, {} synapses",
325            progress.duration_ms,
326            progress.cortical_areas_created,
327            progress.neurons_created,
328            progress.synapses_created
329        );
330
331        Ok(())
332    }
333
334    /// Stage 1: Corticogenesis - Create cortical area structures
335    fn corticogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
336        self.update_stage(DevelopmentStage::Corticogenesis, 0);
337        info!(target: "feagi-bdu","🧠 Stage 1: Corticogenesis - Creating {} cortical areas", genome.cortical_areas.len());
338        info!(target: "feagi-bdu","πŸ” Genome brain_regions check: is_empty={}, count={}",
339              genome.brain_regions.is_empty(), genome.brain_regions.len());
340        if !genome.brain_regions.is_empty() {
341            info!(target: "feagi-bdu","   Existing regions: {:?}", genome.brain_regions.keys().collect::<Vec<_>>());
342        }
343
344        let total_areas = genome.cortical_areas.len();
345
346        // CRITICAL: Minimize lock scope - only hold lock when actually adding areas
347        for (idx, (cortical_id, area)) in genome.cortical_areas.iter().enumerate() {
348            // Add cortical area to connectome - lock held only during this operation
349            {
350                let mut manager = self.connectome_manager.write();
351                manager.add_cortical_area(area.clone())?;
352            } // Lock released immediately after adding
353
354            // Update progress (doesn't need lock)
355            let progress_pct = ((idx + 1) * 100 / total_areas.max(1)) as u8;
356            self.update_progress(|p| {
357                p.cortical_areas_created = idx + 1;
358                p.progress = progress_pct;
359            });
360
361            trace!(target: "feagi-bdu", "Created cortical area: {} ({})", cortical_id, area.name);
362        }
363
364        // Ensure brain regions structure exists (auto-generate if missing)
365        // This matches Python's normalize_brain_region_membership() behavior
366        info!(target: "feagi-bdu","πŸ” BRAIN REGION AUTO-GEN CHECK: genome.brain_regions.is_empty() = {}", genome.brain_regions.is_empty());
367        let (brain_regions_to_add, region_parent_map) = if genome.brain_regions.is_empty() {
368            info!(target: "feagi-bdu","  βœ… TRIGGERING AUTO-GENERATION: No brain_regions in genome - auto-generating default root region");
369            info!(target: "feagi-bdu","  πŸ“Š Genome has {} cortical areas to process", genome.cortical_areas.len());
370
371            // Collect all cortical area IDs
372            let all_cortical_ids = genome.cortical_areas.keys().cloned().collect::<Vec<_>>();
373            info!(target: "feagi-bdu","  πŸ“Š Collected {} cortical area IDs: {:?}", all_cortical_ids.len(),
374            if all_cortical_ids.len() <= 5 {
375                format!("{:?}", all_cortical_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>())
376            } else {
377                format!("{:?}...", all_cortical_ids[0..5].iter().map(|id| id.to_string()).collect::<Vec<_>>())
378            });
379
380            // Classify areas into inputs/outputs based on their AreaType
381            let mut auto_inputs = Vec::new();
382            let mut auto_outputs = Vec::new();
383
384            // Classify areas into categories following Python's normalize_brain_region_membership()
385            let mut ipu_areas = Vec::new(); // Sensory inputs
386            let mut opu_areas = Vec::new(); // Motor outputs
387            let mut core_areas = Vec::new(); // Core/maintenance (like _power)
388            let mut custom_memory_areas = Vec::new(); // CUSTOM/MEMORY (go to subregion)
389
390            for (area_id, area) in genome.cortical_areas.iter() {
391                // Classify following Python's logic with gradual migration to new type system:
392                // 1. Areas starting with "_" are always CORE
393                // 2. Check cortical_type_new (new strongly-typed system) - Phase 2+
394                // 3. Check cortical_group property (parsed from genome)
395                // 4. Fallback to area_type (old simple enum)
396
397                let area_id_str = area_id.to_string();
398                // Note: Core IDs are 8-byte padded and start with "___" (three underscores)
399                let category = if area_id_str.starts_with("___") {
400                    "CORE"
401                } else if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
402                    // Use cortical type from CorticalID
403                    use feagi_structures::genomic::cortical_area::CorticalAreaType;
404                    match cortical_type {
405                        CorticalAreaType::Core(_) => "CORE",
406                        CorticalAreaType::BrainInput(_) => "IPU",
407                        CorticalAreaType::BrainOutput(_) => "OPU",
408                        CorticalAreaType::Memory(_) => "MEMORY",
409                        CorticalAreaType::Custom(_) => "CUSTOM",
410                    }
411                } else {
412                    // Fallback to cortical_group property or area_type
413                    let cortical_group = area
414                        .properties
415                        .get("cortical_group")
416                        .and_then(|v| v.as_str())
417                        .map(|s| s.to_uppercase());
418
419                    match cortical_group.as_deref() {
420                        Some("IPU") => "IPU",
421                        Some("OPU") => "OPU",
422                        Some("CORE") => "CORE",
423                        Some("MEMORY") => "MEMORY",
424                        Some("CUSTOM") => "CUSTOM",
425                        _ => "CUSTOM", // Default fallback
426                    }
427                };
428
429                // Phase 3: Enhanced logging with detailed type information
430                if ipu_areas.len() + opu_areas.len() + core_areas.len() + custom_memory_areas.len()
431                    < 5
432                {
433                    let source = if area.cortical_id.as_cortical_type().is_ok() {
434                        "cortical_id_type"
435                    } else if area.properties.contains_key("cortical_group") {
436                        "cortical_group"
437                    } else {
438                        "default_fallback"
439                    };
440
441                    // Phase 3: Show detailed type information if available
442                    if area.cortical_id.as_cortical_type().is_ok() {
443                        let type_desc = crate::cortical_type_utils::describe_cortical_type(area);
444                        let frame_handling =
445                            if crate::cortical_type_utils::uses_absolute_frames(area) {
446                                "absolute"
447                            } else if crate::cortical_type_utils::uses_incremental_frames(area) {
448                                "incremental"
449                            } else {
450                                "n/a"
451                            };
452                        info!(target: "feagi-bdu","    πŸ” {}, frames={}, source={}",
453                              type_desc, frame_handling, source);
454                    } else {
455                        info!(target: "feagi-bdu","    πŸ” Area {}: category={}, source={}",
456                              area_id_str, category, source);
457                    }
458                }
459
460                // Assign to appropriate list
461                match category {
462                    "IPU" => {
463                        ipu_areas.push(*area_id);
464                        auto_inputs.push(*area_id);
465                    }
466                    "OPU" => {
467                        opu_areas.push(*area_id);
468                        auto_outputs.push(*area_id);
469                    }
470                    "CORE" => {
471                        core_areas.push(*area_id);
472                    }
473                    "MEMORY" | "CUSTOM" => {
474                        custom_memory_areas.push(*area_id);
475                    }
476                    _ => {}
477                }
478            }
479
480            info!(target: "feagi-bdu","  πŸ“Š Classification complete: IPU={}, OPU={}, CORE={}, CUSTOM/MEMORY={}",
481                  ipu_areas.len(), opu_areas.len(), core_areas.len(), custom_memory_areas.len());
482
483            // Build brain region structure following Python's normalize_brain_region_membership()
484            use feagi_structures::genomic::brain_regions::{BrainRegion, RegionID, RegionType};
485            let mut regions_map = std::collections::HashMap::new();
486
487            // Step 1: Create root region with only IPU/OPU/CORE areas
488            let mut root_area_ids = Vec::new();
489            root_area_ids.extend(ipu_areas.iter().cloned());
490            root_area_ids.extend(opu_areas.iter().cloned());
491            root_area_ids.extend(core_areas.iter().cloned());
492
493            // Analyze connections to determine actual inputs/outputs for root
494            let (root_inputs, root_outputs) =
495                Self::analyze_region_io(&root_area_ids, &genome.cortical_areas);
496
497            // Convert CorticalID to base64 for with_areas()
498            // Create a root region with a generated RegionID
499            let root_region_id = RegionID::new();
500            let root_region_id_str = root_region_id.to_string();
501
502            let mut root_region = BrainRegion::new(
503                root_region_id,
504                "Root Brain Region".to_string(),
505                RegionType::Undefined,
506            )
507            .expect("Failed to create root region")
508            .with_areas(root_area_ids.iter().cloned());
509
510            // Store inputs/outputs based on connection analysis
511            if !root_inputs.is_empty() {
512                root_region
513                    .add_property("inputs".to_string(), serde_json::json!(root_inputs.clone()));
514            }
515            if !root_outputs.is_empty() {
516                root_region.add_property(
517                    "outputs".to_string(),
518                    serde_json::json!(root_outputs.clone()),
519                );
520            }
521
522            info!(target: "feagi-bdu","  βœ… Created root region with {} areas (IPU={}, OPU={}, CORE={}) - analyzed: {} inputs, {} outputs",
523                  root_area_ids.len(), ipu_areas.len(), opu_areas.len(), core_areas.len(),
524                  root_inputs.len(), root_outputs.len());
525
526            // Step 2: Create subregion for CUSTOM/MEMORY areas if any exist
527            let mut subregion_id = None;
528            if !custom_memory_areas.is_empty() {
529                // Convert CorticalID to base64 for sorting and hashing
530                let mut custom_memory_strs: Vec<String> = custom_memory_areas
531                    .iter()
532                    .map(|id| id.as_base_64())
533                    .collect();
534                custom_memory_strs.sort(); // Sort for deterministic hash
535                let combined = custom_memory_strs.join("|");
536
537                // Use a simple hash (matching Python's sha1[:8])
538                use std::collections::hash_map::DefaultHasher;
539                use std::hash::{Hash, Hasher};
540                let mut hasher = DefaultHasher::new();
541                combined.hash(&mut hasher);
542                let hash = hasher.finish();
543                let hash_hex = format!("{:08x}", hash as u32);
544                let region_id = format!("region_autogen_{}", hash_hex);
545
546                // Analyze connections to determine inputs/outputs for subregion
547                let (subregion_inputs, subregion_outputs) =
548                    Self::analyze_region_io(&custom_memory_areas, &genome.cortical_areas);
549
550                // Calculate smart position: Place autogen region outside root's bounding box
551                let autogen_position =
552                    Self::calculate_autogen_region_position(&root_area_ids, genome);
553
554                // Create subregion
555                let mut subregion = BrainRegion::new(
556                    RegionID::new(), // Generate new UUID instead of using string
557                    "Autogen Region".to_string(),
558                    RegionType::Undefined, // RegionType no longer has Custom variant
559                )
560                .expect("Failed to create subregion")
561                .with_areas(custom_memory_areas.iter().cloned());
562
563                // Set 3D coordinates (place outside root's bounding box)
564                subregion.add_property(
565                    "coordinate_3d".to_string(),
566                    serde_json::json!(autogen_position),
567                );
568                subregion.add_property("coordinate_2d".to_string(), serde_json::json!([0, 0]));
569
570                // Store inputs/outputs for subregion
571                if !subregion_inputs.is_empty() {
572                    subregion.add_property(
573                        "inputs".to_string(),
574                        serde_json::json!(subregion_inputs.clone()),
575                    );
576                }
577                if !subregion_outputs.is_empty() {
578                    subregion.add_property(
579                        "outputs".to_string(),
580                        serde_json::json!(subregion_outputs.clone()),
581                    );
582                }
583
584                let subregion_id_str = subregion.region_id.to_string();
585
586                info!(target: "feagi-bdu","  βœ… Created subregion '{}' with {} CUSTOM/MEMORY areas ({} inputs, {} outputs)",
587                      region_id, custom_memory_areas.len(), subregion_inputs.len(), subregion_outputs.len());
588
589                regions_map.insert(subregion_id_str.clone(), subregion);
590                subregion_id = Some(subregion_id_str);
591            }
592
593            regions_map.insert(root_region_id_str.clone(), root_region);
594
595            // Count total inputs/outputs across all regions
596            let total_inputs = root_inputs.len()
597                + if let Some(ref sid) = subregion_id {
598                    regions_map
599                        .get(sid)
600                        .and_then(|r| r.properties.get("inputs"))
601                        .and_then(|v| v.as_array())
602                        .map(|a| a.len())
603                        .unwrap_or(0)
604                } else {
605                    0
606                };
607
608            let total_outputs = root_outputs.len()
609                + if let Some(ref sid) = subregion_id {
610                    regions_map
611                        .get(sid)
612                        .and_then(|r| r.properties.get("outputs"))
613                        .and_then(|v| v.as_array())
614                        .map(|a| a.len())
615                        .unwrap_or(0)
616                } else {
617                    0
618                };
619
620            info!(target: "feagi-bdu","  βœ… Auto-generated {} brain region(s) with {} total cortical areas ({} total inputs, {} total outputs)",
621                  regions_map.len(), all_cortical_ids.len(), total_inputs, total_outputs);
622
623            // Return (regions_map, parent_map) so we can properly link hierarchy
624            let mut parent_map = std::collections::HashMap::new();
625            if let Some(ref sub_id) = subregion_id {
626                parent_map.insert(sub_id.clone(), root_region_id_str.clone());
627                info!(target: "feagi-bdu","  πŸ”— Parent relationship: {} -> {}", sub_id, root_region_id_str);
628            }
629
630            (regions_map, parent_map)
631        } else {
632            info!(target: "feagi-bdu","  πŸ“‹ Genome already has {} brain regions - using existing structure", genome.brain_regions.len());
633            // For existing genomes, we don't have parent info readily available
634            // TODO: Extract parent relationships from genome structure
635            (
636                genome.brain_regions.clone(),
637                std::collections::HashMap::new(),
638            )
639        };
640
641        // Add brain regions with proper parent relationships - minimize lock scope
642        {
643            let mut manager = self.connectome_manager.write();
644            let brain_region_count = brain_regions_to_add.len();
645            info!(target: "feagi-bdu","  Adding {} brain regions from genome", brain_region_count);
646
647            // First add root (no parent) - need to find it by iterating since key is UUID
648            let root_entry = brain_regions_to_add
649                .iter()
650                .find(|(_, region)| region.name == "Root Brain Region");
651            if let Some((root_id, root_region)) = root_entry {
652                manager.add_brain_region(root_region.clone(), None)?;
653                debug!(target: "feagi-bdu","    βœ“ Added brain region: {} (Root Brain Region) [parent=None]", root_id);
654            }
655
656            // Then add other regions with their parent relationships
657            for (region_id, region) in brain_regions_to_add.iter() {
658                if region.name == "Root Brain Region" {
659                    continue; // Already added
660                }
661
662                let parent_id = region_parent_map.get(region_id).cloned();
663                manager.add_brain_region(region.clone(), parent_id.clone())?;
664                debug!(target: "feagi-bdu","    βœ“ Added brain region: {} ({}) [parent={:?}]",
665                       region_id, region.name, parent_id);
666            }
667
668            info!(target: "feagi-bdu","  Total brain regions in ConnectomeManager: {}", manager.get_brain_region_ids().len());
669        } // Lock released
670
671        self.update_stage(DevelopmentStage::Corticogenesis, 100);
672        info!(target: "feagi-bdu","  βœ… Corticogenesis complete: {} cortical areas created", total_areas);
673
674        Ok(())
675    }
676
677    /// Stage 2: Voxelogenesis - Establish spatial framework
678    fn voxelogenesis(&mut self, _genome: &RuntimeGenome) -> BduResult<()> {
679        self.update_stage(DevelopmentStage::Voxelogenesis, 0);
680        info!(target: "feagi-bdu","πŸ“ Stage 2: Voxelogenesis - Establishing spatial framework");
681
682        // Spatial framework is implicitly established by cortical area dimensions
683        // The Morton spatial hash in ConnectomeManager handles the actual indexing
684
685        self.update_stage(DevelopmentStage::Voxelogenesis, 100);
686        info!(target: "feagi-bdu","  βœ… Voxelogenesis complete: Spatial framework established");
687
688        Ok(())
689    }
690
691    /// Stage 3: Neurogenesis - Generate neurons within cortical areas
692    ///
693    /// This uses ConnectomeManager which delegates to NPU's SIMD-optimized batch operations.
694    /// Each cortical area is processed with `create_cortical_area_neurons()` which creates
695    /// ALL neurons for that area in one vectorized operation (not a loop).
696    ///
697    /// CRITICAL: Core areas (0=_death, 1=_power, 2=_fatigue) are created FIRST to ensure
698    /// deterministic neuron IDs (neuron 0 for area 0, neuron 1 for area 1, neuron 2 for area 2).
699    fn neurogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
700        self.update_stage(DevelopmentStage::Neurogenesis, 0);
701        info!(target: "feagi-bdu","πŸ”¬ Stage 3: Neurogenesis - Generating neurons (SIMD-optimized batches)");
702
703        let expected_neurons = genome.stats.innate_neuron_count;
704        info!(target: "feagi-bdu","  Expected innate neurons from genome: {}", expected_neurons);
705
706        // CRITICAL: Identify core areas first to ensure deterministic neuron IDs
707        use feagi_structures::genomic::cortical_area::CoreCorticalType;
708        let death_id = CoreCorticalType::Death.to_cortical_id();
709        let power_id = CoreCorticalType::Power.to_cortical_id();
710        let fatigue_id = CoreCorticalType::Fatigue.to_cortical_id();
711
712        let mut core_areas = Vec::new();
713        let mut other_areas = Vec::new();
714
715        // Separate core areas from other areas
716        for (cortical_id, area) in genome.cortical_areas.iter() {
717            if *cortical_id == death_id {
718                core_areas.push((0, *cortical_id, area)); // Area 0 = _death
719            } else if *cortical_id == power_id {
720                core_areas.push((1, *cortical_id, area)); // Area 1 = _power
721            } else if *cortical_id == fatigue_id {
722                core_areas.push((2, *cortical_id, area)); // Area 2 = _fatigue
723            } else {
724                other_areas.push((*cortical_id, area));
725            }
726        }
727
728        // Sort core areas by their deterministic index (0, 1, 2)
729        core_areas.sort_by_key(|(idx, _, _)| *idx);
730
731        info!(target: "feagi-bdu","  🎯 Creating core area neurons FIRST ({} areas) for deterministic IDs", core_areas.len());
732
733        let mut total_neurons_created = 0;
734        let mut processed_count = 0;
735        let total_areas = genome.cortical_areas.len();
736
737        // STEP 1: Create core area neurons FIRST (in order: 0, 1, 2)
738        for (core_idx, cortical_id, area) in &core_areas {
739            let per_voxel_count = area
740                .properties
741                .get("neurons_per_voxel")
742                .and_then(|v| v.as_u64())
743                .unwrap_or(1) as i64;
744
745            let cortical_id_str = cortical_id.to_string();
746            info!(target: "feagi-bdu","  πŸ”‹ [CORE-AREA {}] {} - dimensions: {:?}, per_voxel: {}",
747                core_idx, cortical_id_str, area.dimensions, per_voxel_count);
748
749            if per_voxel_count == 0 {
750                warn!(target: "feagi-bdu","  ⚠️ Skipping core area {} - per_voxel_neuron_cnt is 0", cortical_id_str);
751                continue;
752            }
753
754            // Create neurons for core area (ensures deterministic ID: area 0β†’neuron 0, area 1β†’neuron 1, area 2β†’neuron 2)
755            let neurons_created = {
756                let manager_arc = self.connectome_manager.clone();
757                let mut manager = manager_arc.write();
758                manager.create_neurons_for_area(cortical_id)
759            };
760
761            match neurons_created {
762                Ok(count) => {
763                    total_neurons_created += count as usize;
764                    info!(target: "feagi-bdu","  βœ… Created {} neurons for core area {} (deterministic ID: neuron {})",
765                        count, cortical_id_str, core_idx);
766                }
767                Err(e) => {
768                    error!(target: "feagi-bdu","  ❌ FATAL: Failed to create neurons for core area {}: {}", cortical_id_str, e);
769                    return Err(e);
770                }
771            }
772
773            processed_count += 1;
774            let progress_pct = (processed_count * 100 / total_areas.max(1)) as u8;
775            self.update_progress(|p| {
776                p.neurons_created = total_neurons_created;
777                p.progress = progress_pct;
778            });
779        }
780
781        // STEP 2: Create neurons for all other areas
782        info!(target: "feagi-bdu","  πŸ“¦ Creating neurons for {} other areas", other_areas.len());
783        for (cortical_id, area) in &other_areas {
784            // Get neurons_per_voxel from typed field (single source of truth)
785            let _per_voxel_count = area
786                .properties
787                .get("neurons_per_voxel")
788                .and_then(|v| v.as_u64())
789                .unwrap_or(1) as i64;
790
791            let per_voxel_count = area
792                .properties
793                .get("neurons_per_voxel")
794                .and_then(|v| v.as_u64())
795                .unwrap_or(1) as i64;
796
797            let cortical_id_str = cortical_id.to_string();
798
799            if per_voxel_count == 0 {
800                warn!(target: "feagi-bdu","  ⚠️ Skipping area {} - per_voxel_neuron_cnt is 0 (will have NO neurons!)", cortical_id_str);
801                continue;
802            }
803
804            // Call ConnectomeManager to create neurons (delegates to NPU)
805            // CRITICAL: Minimize lock scope - only hold lock during neuron creation
806            let neurons_created = {
807                let manager_arc = self.connectome_manager.clone();
808                let mut manager = manager_arc.write();
809                manager.create_neurons_for_area(cortical_id)
810            }; // Lock released immediately
811
812            match neurons_created {
813                Ok(count) => {
814                    total_neurons_created += count as usize;
815                    trace!(
816                        target: "feagi-bdu",
817                        "Created {} neurons for area {}",
818                        count,
819                        cortical_id_str
820                    );
821                }
822                Err(e) => {
823                    // If NPU not connected, calculate expected count
824                    warn!(target: "feagi-bdu","  Failed to create neurons for {}: {} (NPU may not be connected)",
825                        cortical_id_str, e);
826                    let total_voxels = area.dimensions.width as usize
827                        * area.dimensions.height as usize
828                        * area.dimensions.depth as usize;
829                    let expected = total_voxels * per_voxel_count as usize;
830                    total_neurons_created += expected;
831                }
832            }
833
834            processed_count += 1;
835            // Update progress
836            let progress_pct = (processed_count * 100 / total_areas.max(1)) as u8;
837            self.update_progress(|p| {
838                p.neurons_created = total_neurons_created;
839                p.progress = progress_pct;
840            });
841        }
842
843        // Compare with genome stats (info only - stats may count only innate neurons while we create all voxels)
844        if expected_neurons > 0 && total_neurons_created != expected_neurons {
845            trace!(target: "feagi-bdu",
846                created_neurons = total_neurons_created,
847                genome_stats_innate = expected_neurons,
848                "Neuron creation complete (genome stats may only count innate neurons)"
849            );
850        }
851
852        self.update_stage(DevelopmentStage::Neurogenesis, 100);
853        info!(target: "feagi-bdu","  βœ… Neurogenesis complete: {} neurons created", total_neurons_created);
854
855        Ok(())
856    }
857
858    /// Stage 4: Synaptogenesis - Form synaptic connections between neurons
859    ///
860    /// This uses ConnectomeManager which delegates to NPU's morphology functions.
861    /// Each morphology application (`apply_projector_morphology`, etc.) processes ALL neurons
862    /// from the source area and creates ALL synapses in one SIMD-optimized batch operation.
863    fn synaptogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
864        self.update_stage(DevelopmentStage::Synaptogenesis, 0);
865        info!(target: "feagi-bdu","πŸ”— Stage 4: Synaptogenesis - Forming synaptic connections (SIMD-optimized batches)");
866
867        let expected_synapses = genome.stats.innate_synapse_count;
868        info!(target: "feagi-bdu","  Expected innate synapses from genome: {}", expected_synapses);
869
870        let mut total_synapses_created = 0;
871        let total_areas = genome.cortical_areas.len();
872
873        // Process each source area via ConnectomeManager (each mapping = one SIMD batch)
874        // NOTE: Loop is over AREAS, not synapses. Each area applies all mappings in batch calls.
875        for (idx, (_src_cortical_id, src_area)) in genome.cortical_areas.iter().enumerate() {
876            // Check if area has mappings
877            let has_dstmap = src_area
878                .properties
879                .get("cortical_mapping_dst")
880                .and_then(|v| v.as_object())
881                .map(|m| !m.is_empty())
882                .unwrap_or(false);
883
884            if !has_dstmap {
885                trace!(target: "feagi-bdu", "No dstmap for area {}", &src_area.cortical_id);
886                continue;
887            }
888
889            // Call ConnectomeManager to apply cortical mappings (delegates to NPU)
890            // CRITICAL: Minimize lock scope - only hold lock during synapse creation
891            // Use src_area.cortical_id (the actual ID stored in ConnectomeManager)
892            let src_cortical_id = &src_area.cortical_id;
893            let src_cortical_id_str = src_cortical_id.to_string(); // For logging
894            let synapses_created = {
895                let manager_arc = self.connectome_manager.clone();
896                let mut manager = manager_arc.write();
897                manager.apply_cortical_mapping(src_cortical_id)
898            }; // Lock released immediately
899
900            match synapses_created {
901                Ok(count) => {
902                    total_synapses_created += count as usize;
903                    trace!(
904                        target: "feagi-bdu",
905                        "Created {} synapses for area {}",
906                        count,
907                        src_cortical_id_str
908                    );
909                }
910                Err(e) => {
911                    // If NPU not connected, estimate count
912                    warn!(target: "feagi-bdu","  Failed to create synapses for {}: {} (NPU may not be connected)",
913                        src_cortical_id_str, e);
914                    let estimated = estimate_synapses_for_area(src_area, genome);
915                    total_synapses_created += estimated;
916                }
917            }
918
919            // Update progress
920            let progress_pct = ((idx + 1) * 100 / total_areas.max(1)) as u8;
921            self.update_progress(|p| {
922                p.synapses_created = total_synapses_created;
923                p.progress = progress_pct;
924            });
925        }
926
927        // CRITICAL: Rebuild the NPU synapse index so newly created synapses are visible to
928        // queries (e.g. get_outgoing_synapses / synapse counts) and propagation.
929        //
930        // Note: We do this once at the end for performance.
931        let npu_arc = {
932            let manager = self.connectome_manager.read();
933            manager.get_npu().cloned()
934        };
935        if let Some(npu_arc) = npu_arc {
936            let mut npu_lock = npu_arc
937                .lock()
938                .map_err(|e| BduError::Internal(format!("Failed to lock NPU: {}", e)))?;
939            npu_lock.rebuild_synapse_index();
940
941            // Refresh cached counts after index rebuild.
942            let manager = self.connectome_manager.read();
943            manager.update_cached_synapse_count();
944        }
945
946        // CRITICAL: Register memory areas with PlasticityExecutor after all mappings are created
947        // This ensures memory areas have their complete upstream_cortical_areas lists populated.
948        #[cfg(feature = "plasticity")]
949        {
950            use feagi_evolutionary::extract_memory_properties;
951            use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
952
953            let manager = self.connectome_manager.read();
954            if let Some(executor) = manager.get_plasticity_executor() {
955                let mut registered_count = 0;
956
957                // Iterate through all cortical areas and register memory areas
958                for area_id in manager.get_cortical_area_ids() {
959                    if let Some(area) = manager.get_cortical_area(area_id) {
960                        if let Some(mem_props) = extract_memory_properties(&area.properties) {
961                            let upstream_areas = manager.get_upstream_cortical_areas(area_id);
962
963                            // Ensure FireLedger tracks upstream areas with at least the required temporal depth.
964                            // Dense, burst-aligned tracking is required for correct memory pattern hashing.
965                            if let Some(npu_arc) = manager.get_npu() {
966                                if let Ok(mut npu) = npu_arc.lock() {
967                                    let existing_configs = npu.get_all_fire_ledger_configs();
968                                    for &upstream_idx in &upstream_areas {
969                                        let existing = existing_configs
970                                            .iter()
971                                            .find(|(idx, _)| *idx == upstream_idx)
972                                            .map(|(_, w)| *w)
973                                            .unwrap_or(0);
974
975                                        let desired = mem_props.temporal_depth as usize;
976                                        let resolved = existing.max(desired);
977                                        if resolved != existing {
978                                            if let Err(e) = npu.configure_fire_ledger_window(
979                                                upstream_idx,
980                                                resolved,
981                                            ) {
982                                                warn!(
983                                                    target: "feagi-bdu",
984                                                    "Failed to configure FireLedger window for upstream area idx={} (requested={}): {}",
985                                                    upstream_idx,
986                                                    resolved,
987                                                    e
988                                                );
989                                            }
990                                        }
991                                    }
992                                } else {
993                                    warn!(target: "feagi-bdu", "Failed to lock NPU for FireLedger configuration");
994                                }
995                            }
996
997                            if let Ok(exec) = executor.lock() {
998                                let lifecycle_config = MemoryNeuronLifecycleConfig {
999                                    initial_lifespan: mem_props.init_lifespan,
1000                                    lifespan_growth_rate: mem_props.lifespan_growth_rate,
1001                                    longterm_threshold: mem_props.longterm_threshold,
1002                                    max_reactivations: 1000,
1003                                };
1004
1005                                exec.register_memory_area(
1006                                    area.cortical_idx,
1007                                    area_id.as_base_64(),
1008                                    mem_props.temporal_depth,
1009                                    upstream_areas.clone(),
1010                                    Some(lifecycle_config),
1011                                );
1012
1013                                registered_count += 1;
1014                            }
1015                        }
1016                    }
1017                }
1018                let _ = registered_count; // count retained for future metrics if needed
1019            }
1020        }
1021
1022        // Verify against genome stats
1023        if expected_synapses > 0 {
1024            let diff = (total_synapses_created as i64 - expected_synapses as i64).abs();
1025            let diff_pct = (diff as f64 / expected_synapses.max(1) as f64) * 100.0;
1026
1027            if diff_pct > 10.0 {
1028                warn!(target: "feagi-bdu",
1029                    "Synapse count variance: created {} but genome stats expected {} ({:.1}% difference)",
1030                    total_synapses_created, expected_synapses, diff_pct
1031                );
1032            } else {
1033                info!(target: "feagi-bdu",
1034                    "Synapse count matches genome stats within {:.1}% ({} vs {})",
1035                    diff_pct, total_synapses_created, expected_synapses
1036                );
1037            }
1038        }
1039
1040        self.update_stage(DevelopmentStage::Synaptogenesis, 100);
1041        info!(target: "feagi-bdu","  βœ… Synaptogenesis complete: {} synapses created", total_synapses_created);
1042
1043        Ok(())
1044    }
1045}
1046
1047/// Estimate synapse count for an area (fallback when NPU not connected)
1048///
1049/// This is only used when NPU is not available for actual synapse creation.
1050fn estimate_synapses_for_area(
1051    src_area: &CorticalArea,
1052    genome: &feagi_evolutionary::RuntimeGenome,
1053) -> usize {
1054    let dstmap = match src_area.properties.get("cortical_mapping_dst") {
1055        Some(serde_json::Value::Object(map)) => map,
1056        _ => return 0,
1057    };
1058
1059    let mut total = 0;
1060
1061    for (dst_id, rules) in dstmap {
1062        // Convert string dst_id to CorticalID for lookup
1063        let dst_cortical_id = match feagi_evolutionary::string_to_cortical_id(dst_id) {
1064            Ok(id) => id,
1065            Err(_) => continue,
1066        };
1067        let dst_area = match genome.cortical_areas.get(&dst_cortical_id) {
1068            Some(area) => area,
1069            None => continue,
1070        };
1071
1072        let rules_array = match rules.as_array() {
1073            Some(arr) => arr,
1074            None => continue,
1075        };
1076
1077        for rule in rules_array {
1078            let morphology_id = rule
1079                .get("morphology_id")
1080                .and_then(|v| v.as_str())
1081                .unwrap_or("unknown");
1082            let scalar = rule
1083                .get("morphology_scalar")
1084                .and_then(|v| v.as_i64())
1085                .unwrap_or(1) as usize;
1086
1087            // Simplified estimation
1088            let src_per_voxel = src_area
1089                .properties
1090                .get("neurons_per_voxel")
1091                .and_then(|v| v.as_u64())
1092                .unwrap_or(1) as usize;
1093            let dst_per_voxel = dst_area
1094                .properties
1095                .get("neurons_per_voxel")
1096                .and_then(|v| v.as_u64())
1097                .unwrap_or(1) as usize;
1098
1099            let src_voxels =
1100                src_area.dimensions.width * src_area.dimensions.height * src_area.dimensions.depth;
1101            let dst_voxels =
1102                dst_area.dimensions.width * dst_area.dimensions.height * dst_area.dimensions.depth;
1103
1104            let src_neurons = src_voxels as usize * src_per_voxel;
1105            let dst_neurons = dst_voxels as usize * dst_per_voxel as usize;
1106
1107            // Basic estimation by morphology type
1108            let count = match morphology_id {
1109                "block_to_block" => src_neurons * dst_per_voxel * scalar,
1110                "projector" => src_neurons * dst_neurons * scalar,
1111                _ if morphology_id.contains("lateral") => src_neurons * scalar,
1112                _ => (src_neurons * scalar).min(src_neurons * dst_neurons / 10),
1113            };
1114
1115            total += count;
1116        }
1117    }
1118
1119    total
1120}
1121
1122impl Neuroembryogenesis {
1123    /// Calculate position for autogen region based on root region's bounding box
1124    fn calculate_autogen_region_position(
1125        root_area_ids: &[CorticalID],
1126        genome: &feagi_evolutionary::RuntimeGenome,
1127    ) -> [i32; 3] {
1128        if root_area_ids.is_empty() {
1129            return [100, 0, 0];
1130        }
1131
1132        let mut min_x = i32::MAX;
1133        let mut max_x = i32::MIN;
1134        let mut min_y = i32::MAX;
1135        let mut max_y = i32::MIN;
1136        let mut min_z = i32::MAX;
1137        let mut max_z = i32::MIN;
1138
1139        for cortical_id in root_area_ids {
1140            if let Some(area) = genome.cortical_areas.get(cortical_id) {
1141                let pos: (i32, i32, i32) = area.position.into();
1142                let dims = (
1143                    area.dimensions.width as i32,
1144                    area.dimensions.height as i32,
1145                    area.dimensions.depth as i32,
1146                );
1147
1148                min_x = min_x.min(pos.0);
1149                max_x = max_x.max(pos.0 + dims.0);
1150                min_y = min_y.min(pos.1);
1151                max_y = max_y.max(pos.1 + dims.1);
1152                min_z = min_z.min(pos.2);
1153                max_z = max_z.max(pos.2 + dims.2);
1154            }
1155        }
1156
1157        let bbox_width = (max_x - min_x).max(1);
1158        let padding = (bbox_width / 5).max(50);
1159        let autogen_x = max_x + padding;
1160        let autogen_y = (min_y + max_y) / 2;
1161        let autogen_z = (min_z + max_z) / 2;
1162
1163        info!(target: "feagi-bdu",
1164              "  πŸ“ Autogen position: ({}, {}, {}) [padding: {}]",
1165              autogen_x, autogen_y, autogen_z, padding);
1166
1167        [autogen_x, autogen_y, autogen_z]
1168    }
1169
1170    /// Analyze region inputs/outputs based on cortical connections
1171    ///
1172    /// Following Python's _auto_assign_region_io() logic:
1173    /// - OUTPUT: Any area in the region that connects to an area OUTSIDE the region
1174    /// - INPUT: Any area in the region that receives connection from OUTSIDE the region
1175    fn analyze_region_io(
1176        region_area_ids: &[feagi_structures::genomic::cortical_area::CorticalID],
1177        all_cortical_areas: &std::collections::HashMap<CorticalID, CorticalArea>,
1178    ) -> (Vec<String>, Vec<String>) {
1179        let area_set: std::collections::HashSet<_> = region_area_ids.iter().cloned().collect();
1180        let mut inputs = Vec::new();
1181        let mut outputs = Vec::new();
1182
1183        // Helper to extract destination area IDs from cortical_mapping_dst (as strings)
1184        let extract_destinations = |area: &CorticalArea| -> Vec<String> {
1185            area.properties
1186                .get("cortical_mapping_dst")
1187                .and_then(|v| v.as_object())
1188                .map(|obj| obj.keys().cloned().collect())
1189                .unwrap_or_default()
1190        };
1191
1192        // Find OUTPUTS: areas in region that connect to areas OUTSIDE region
1193        for area_id in region_area_ids {
1194            if let Some(area) = all_cortical_areas.get(area_id) {
1195                let destinations = extract_destinations(area);
1196                // Convert destination strings to CorticalID for comparison
1197                let external_destinations: Vec<_> = destinations
1198                    .iter()
1199                    .filter_map(|dest| feagi_evolutionary::string_to_cortical_id(dest).ok())
1200                    .filter(|dest_id| !area_set.contains(dest_id))
1201                    .collect();
1202
1203                if !external_destinations.is_empty() {
1204                    outputs.push(area_id.as_base_64());
1205                }
1206            }
1207        }
1208
1209        // Find INPUTS: areas in region receiving connections from OUTSIDE region
1210        for (source_area_id, source_area) in all_cortical_areas.iter() {
1211            // Skip areas that are inside the region
1212            if area_set.contains(source_area_id) {
1213                continue;
1214            }
1215
1216            let destinations = extract_destinations(source_area);
1217            for dest_str in destinations {
1218                if let Ok(dest_id) = feagi_evolutionary::string_to_cortical_id(&dest_str) {
1219                    if area_set.contains(&dest_id) {
1220                        let dest_string = dest_id.as_base_64();
1221                        if !inputs.contains(&dest_string) {
1222                            inputs.push(dest_string);
1223                        }
1224                    }
1225                }
1226            }
1227        }
1228
1229        (inputs, outputs)
1230    }
1231
1232    /// Update development stage
1233    fn update_stage(&self, stage: DevelopmentStage, progress: u8) {
1234        let mut p = self.progress.write();
1235        p.stage = stage;
1236        p.progress = progress;
1237        p.duration_ms = self.start_time.elapsed().as_millis() as u64;
1238    }
1239
1240    /// Update progress with a closure
1241    fn update_progress<F>(&self, f: F)
1242    where
1243        F: FnOnce(&mut DevelopmentProgress),
1244    {
1245        let mut p = self.progress.write();
1246        f(&mut p);
1247        p.duration_ms = self.start_time.elapsed().as_millis() as u64;
1248    }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253    use super::*;
1254    use feagi_evolutionary::create_genome_with_core_morphologies;
1255    use feagi_structures::genomic::cortical_area::CorticalAreaDimensions;
1256
1257    #[test]
1258    fn test_neuroembryogenesis_creation() {
1259        let manager = ConnectomeManager::instance();
1260        let neuro = Neuroembryogenesis::new(manager);
1261
1262        let progress = neuro.get_progress();
1263        assert_eq!(progress.stage, DevelopmentStage::Initialization);
1264        assert_eq!(progress.progress, 0);
1265    }
1266
1267    #[test]
1268    fn test_development_from_minimal_genome() {
1269        ConnectomeManager::reset_for_testing(); // Ensure clean state
1270        let manager = ConnectomeManager::instance();
1271        let mut neuro = Neuroembryogenesis::new(manager.clone());
1272
1273        // Create a minimal genome with one cortical area
1274        let mut genome = create_genome_with_core_morphologies(
1275            "test_genome".to_string(),
1276            "Test Genome".to_string(),
1277        );
1278
1279        let cortical_id = CorticalID::try_from_bytes(b"cst_neur").unwrap(); // Use valid custom cortical ID
1280        let cortical_type = cortical_id
1281            .as_cortical_type()
1282            .expect("Failed to get cortical type");
1283        let area = CorticalArea::new(
1284            cortical_id,
1285            0,
1286            "Test Area".to_string(),
1287            CorticalAreaDimensions::new(10, 10, 10).unwrap(),
1288            (0, 0, 0).into(),
1289            cortical_type,
1290        )
1291        .expect("Failed to create cortical area");
1292        genome.cortical_areas.insert(cortical_id, area);
1293
1294        // Run neuroembryogenesis
1295        let result = neuro.develop_from_genome(&genome);
1296        assert!(result.is_ok(), "Development failed: {:?}", result);
1297
1298        // Check progress
1299        let progress = neuro.get_progress();
1300        assert_eq!(progress.stage, DevelopmentStage::Completed);
1301        assert_eq!(progress.progress, 100);
1302        assert_eq!(progress.cortical_areas_created, 1);
1303
1304        // Verify cortical area was added to connectome
1305        let mgr = manager.read();
1306        // Note: Due to parallel test execution with shared singleton, we just verify the area exists
1307        assert!(
1308            mgr.has_cortical_area(&cortical_id),
1309            "Cortical area should have been added to connectome"
1310        );
1311
1312        println!("βœ… Development completed in {}ms", progress.duration_ms);
1313    }
1314}