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::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        for area in &areas {
142            let neurons_created = {
143                let mut manager = self.connectome_manager.write();
144                manager.create_neurons_for_area(&area.cortical_id)
145            };
146
147            match neurons_created {
148                Ok(count) => {
149                    total_neurons += count as usize;
150                    trace!(
151                        target: "feagi-bdu",
152                        "Created {} neurons for area {}",
153                        count,
154                        area.cortical_id.as_base_64()
155                    );
156                }
157                Err(e) => {
158                    error!(target: "feagi-bdu", "  ❌ FATAL: Failed to create neurons for {}: {}", area.cortical_id.as_base_64(), e);
159                    // CRITICAL: NPU capacity errors must propagate to UI
160                    return Err(e);
161                }
162            }
163        }
164
165        // Stage 3: Create synapses for each area (Synaptogenesis)
166        for area in &areas {
167            // Check if area has mappings
168            let has_dstmap = area
169                .properties
170                .get("cortical_mapping_dst")
171                .and_then(|v| v.as_object())
172                .map(|m| !m.is_empty())
173                .unwrap_or(false);
174
175            if !has_dstmap {
176                debug!(target: "feagi-bdu", "  No mappings for area {}", area.cortical_id.as_base_64());
177                continue;
178            }
179
180            let synapses_created = {
181                let mut manager = self.connectome_manager.write();
182                manager.apply_cortical_mapping(&area.cortical_id)
183            };
184
185            match synapses_created {
186                Ok(count) => {
187                    total_synapses += count as usize;
188                    trace!(
189                        target: "feagi-bdu",
190                        "Created {} synapses for area {}",
191                        count,
192                        area.cortical_id
193                    );
194                }
195                Err(e) => {
196                    warn!(target: "feagi-bdu", "  ⚠️ Failed to create synapses for {}: {}", area.cortical_id, e);
197                    let estimated = estimate_synapses_for_area(area, genome);
198                    total_synapses += estimated;
199                }
200            }
201        }
202
203        info!(target: "feagi-bdu", "✅ Incremental add complete: {} areas, {} neurons, {} synapses",
204              areas.len(), total_neurons, total_synapses);
205
206        Ok((total_neurons, total_synapses))
207    }
208
209    /// Develop the brain from a genome
210    ///
211    /// This is the main entry point that orchestrates all development stages.
212    pub fn develop_from_genome(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
213        info!(target: "feagi-bdu","🧬 Starting neuroembryogenesis for genome: {}", genome.metadata.genome_id);
214
215        // Phase 5: Parse quantization precision and dispatch to type-specific builder
216        let _quantization_precision = &genome.physiology.quantization_precision;
217        // Precision parsing handled in genome loader
218        let quant_spec = QuantizationSpec::default();
219
220        info!(target: "feagi-bdu",
221            "   Quantization precision: {:?} (range: [{}, {}] for membrane potential)",
222            quant_spec.precision,
223            quant_spec.membrane_potential_min,
224            quant_spec.membrane_potential_max
225        );
226
227        // Phase 6: Type dispatch - Neuroembryogenesis is now fully generic!
228        // The precision is determined by the type T of this Neuroembryogenesis instance.
229        // All stages (corticogenesis, neurogenesis, synaptogenesis) automatically use the correct type.
230        match quant_spec.precision {
231            Precision::FP32 => {
232                info!(target: "feagi-bdu", "   ✓ Using FP32 (32-bit floating-point) - highest precision");
233                info!(target: "feagi-bdu", "   Memory usage: Baseline (4 bytes/neuron for membrane potential)");
234            }
235            Precision::INT8 => {
236                info!(target: "feagi-bdu", "   ✓ Using INT8 (8-bit integer) - memory efficient");
237                info!(target: "feagi-bdu", "   Memory reduction: 42% (1 byte/neuron for membrane potential)");
238                info!(target: "feagi-bdu", "   Quantization range: [{}, {}]",
239                    quant_spec.membrane_potential_min,
240                    quant_spec.membrane_potential_max);
241                // Note: If this Neuroembryogenesis was created with <f32>, this will warn below
242                // The caller must create Neuroembryogenesis::<INT8Value> to use INT8
243            }
244            Precision::FP16 => {
245                warn!(target: "feagi-bdu", "   FP16 quantization requested but not yet implemented.");
246                warn!(target: "feagi-bdu", "   FP16 support planned for future GPU optimization.");
247                // Note: Requires f16 type and implementation
248            }
249        }
250
251        // Type consistency is now handled by DynamicNPU at creation time
252        // The caller (main.rs) peeks at genome precision and creates the correct DynamicNPU variant
253        info!(target: "feagi-bdu", "   ✓ Quantization handled by DynamicNPU (dispatches at runtime)");
254
255        // Update stage: Initialization
256        self.update_stage(DevelopmentStage::Initialization, 0);
257
258        // Stage 1: Corticogenesis - Create cortical area structures
259        self.corticogenesis(genome)?;
260
261        // Stage 2: Voxelogenesis - Establish spatial framework
262        self.voxelogenesis(genome)?;
263
264        // Stage 3: Neurogenesis - Generate neurons
265        self.neurogenesis(genome)?;
266
267        // Stage 4: Synaptogenesis - Form synaptic connections
268        self.synaptogenesis(genome)?;
269
270        // Mark as completed
271        self.update_stage(DevelopmentStage::Completed, 100);
272
273        let progress = self.progress.read();
274        info!(target: "feagi-bdu",
275            "✅ Neuroembryogenesis completed in {}ms: {} cortical areas, {} neurons, {} synapses",
276            progress.duration_ms,
277            progress.cortical_areas_created,
278            progress.neurons_created,
279            progress.synapses_created
280        );
281
282        Ok(())
283    }
284
285    /// Stage 1: Corticogenesis - Create cortical area structures
286    fn corticogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
287        self.update_stage(DevelopmentStage::Corticogenesis, 0);
288        info!(target: "feagi-bdu","🧠 Stage 1: Corticogenesis - Creating {} cortical areas", genome.cortical_areas.len());
289        info!(target: "feagi-bdu","🔍 Genome brain_regions check: is_empty={}, count={}",
290              genome.brain_regions.is_empty(), genome.brain_regions.len());
291        if !genome.brain_regions.is_empty() {
292            info!(target: "feagi-bdu","   Existing regions: {:?}", genome.brain_regions.keys().collect::<Vec<_>>());
293        }
294
295        let total_areas = genome.cortical_areas.len();
296
297        // CRITICAL: Minimize lock scope - only hold lock when actually adding areas
298        for (idx, (cortical_id, area)) in genome.cortical_areas.iter().enumerate() {
299            // Add cortical area to connectome - lock held only during this operation
300            {
301                let mut manager = self.connectome_manager.write();
302                manager.add_cortical_area(area.clone())?;
303            } // Lock released immediately after adding
304
305            // Update progress (doesn't need lock)
306            let progress_pct = ((idx + 1) * 100 / total_areas.max(1)) as u8;
307            self.update_progress(|p| {
308                p.cortical_areas_created = idx + 1;
309                p.progress = progress_pct;
310            });
311
312            trace!(target: "feagi-bdu", "Created cortical area: {} ({})", cortical_id, area.name);
313        }
314
315        // Ensure brain regions structure exists (auto-generate if missing)
316        // This matches Python's normalize_brain_region_membership() behavior
317        info!(target: "feagi-bdu","🔍 BRAIN REGION AUTO-GEN CHECK: genome.brain_regions.is_empty() = {}", genome.brain_regions.is_empty());
318        let (brain_regions_to_add, region_parent_map) = if genome.brain_regions.is_empty() {
319            info!(target: "feagi-bdu","  ✅ TRIGGERING AUTO-GENERATION: No brain_regions in genome - auto-generating default root region");
320            info!(target: "feagi-bdu","  📊 Genome has {} cortical areas to process", genome.cortical_areas.len());
321
322            // Collect all cortical area IDs
323            let all_cortical_ids = genome.cortical_areas.keys().cloned().collect::<Vec<_>>();
324            info!(target: "feagi-bdu","  📊 Collected {} cortical area IDs: {:?}", all_cortical_ids.len(),
325            if all_cortical_ids.len() <= 5 {
326                format!("{:?}", all_cortical_ids.iter().map(|id| id.to_string()).collect::<Vec<_>>())
327            } else {
328                format!("{:?}...", all_cortical_ids[0..5].iter().map(|id| id.to_string()).collect::<Vec<_>>())
329            });
330
331            // Classify areas into inputs/outputs based on their AreaType
332            let mut auto_inputs = Vec::new();
333            let mut auto_outputs = Vec::new();
334
335            // Classify areas into categories following Python's normalize_brain_region_membership()
336            let mut ipu_areas = Vec::new(); // Sensory inputs
337            let mut opu_areas = Vec::new(); // Motor outputs
338            let mut core_areas = Vec::new(); // Core/maintenance (like _power)
339            let mut custom_memory_areas = Vec::new(); // CUSTOM/MEMORY (go to subregion)
340
341            for (area_id, area) in genome.cortical_areas.iter() {
342                // Classify following Python's logic with gradual migration to new type system:
343                // 1. Areas starting with "_" are always CORE
344                // 2. Check cortical_type_new (new strongly-typed system) - Phase 2+
345                // 3. Check cortical_group property (parsed from genome)
346                // 4. Fallback to area_type (old simple enum)
347
348                let area_id_str = area_id.to_string();
349                // Note: Core IDs are 8-byte padded and start with "___" (three underscores)
350                let category = if area_id_str.starts_with("___") {
351                    "CORE"
352                } else if let Ok(cortical_type) = area.cortical_id.as_cortical_type() {
353                    // Use cortical type from CorticalID
354                    use feagi_structures::genomic::cortical_area::CorticalAreaType;
355                    match cortical_type {
356                        CorticalAreaType::Core(_) => "CORE",
357                        CorticalAreaType::BrainInput(_) => "IPU",
358                        CorticalAreaType::BrainOutput(_) => "OPU",
359                        CorticalAreaType::Memory(_) => "MEMORY",
360                        CorticalAreaType::Custom(_) => "CUSTOM",
361                    }
362                } else {
363                    // Fallback to cortical_group property or area_type
364                    let cortical_group = area
365                        .properties
366                        .get("cortical_group")
367                        .and_then(|v| v.as_str())
368                        .map(|s| s.to_uppercase());
369
370                    match cortical_group.as_deref() {
371                        Some("IPU") => "IPU",
372                        Some("OPU") => "OPU",
373                        Some("CORE") => "CORE",
374                        Some("MEMORY") => "MEMORY",
375                        Some("CUSTOM") => "CUSTOM",
376                        _ => "CUSTOM", // Default fallback
377                    }
378                };
379
380                // Phase 3: Enhanced logging with detailed type information
381                if ipu_areas.len() + opu_areas.len() + core_areas.len() + custom_memory_areas.len()
382                    < 5
383                {
384                    let source = if area.cortical_id.as_cortical_type().is_ok() {
385                        "cortical_id_type"
386                    } else if area.properties.contains_key("cortical_group") {
387                        "cortical_group"
388                    } else {
389                        "default_fallback"
390                    };
391
392                    // Phase 3: Show detailed type information if available
393                    if area.cortical_id.as_cortical_type().is_ok() {
394                        let type_desc = crate::cortical_type_utils::describe_cortical_type(area);
395                        let frame_handling =
396                            if crate::cortical_type_utils::uses_absolute_frames(area) {
397                                "absolute"
398                            } else if crate::cortical_type_utils::uses_incremental_frames(area) {
399                                "incremental"
400                            } else {
401                                "n/a"
402                            };
403                        info!(target: "feagi-bdu","    🔍 {}, frames={}, source={}",
404                              type_desc, frame_handling, source);
405                    } else {
406                        info!(target: "feagi-bdu","    🔍 Area {}: category={}, source={}",
407                              area_id_str, category, source);
408                    }
409                }
410
411                // Assign to appropriate list
412                match category {
413                    "IPU" => {
414                        ipu_areas.push(*area_id);
415                        auto_inputs.push(*area_id);
416                    }
417                    "OPU" => {
418                        opu_areas.push(*area_id);
419                        auto_outputs.push(*area_id);
420                    }
421                    "CORE" => {
422                        core_areas.push(*area_id);
423                    }
424                    "MEMORY" | "CUSTOM" => {
425                        custom_memory_areas.push(*area_id);
426                    }
427                    _ => {}
428                }
429            }
430
431            info!(target: "feagi-bdu","  📊 Classification complete: IPU={}, OPU={}, CORE={}, CUSTOM/MEMORY={}",
432                  ipu_areas.len(), opu_areas.len(), core_areas.len(), custom_memory_areas.len());
433
434            // Build brain region structure following Python's normalize_brain_region_membership()
435            use feagi_structures::genomic::brain_regions::{BrainRegion, RegionID, RegionType};
436            let mut regions_map = std::collections::HashMap::new();
437
438            // Step 1: Create root region with only IPU/OPU/CORE areas
439            let mut root_area_ids = Vec::new();
440            root_area_ids.extend(ipu_areas.iter().cloned());
441            root_area_ids.extend(opu_areas.iter().cloned());
442            root_area_ids.extend(core_areas.iter().cloned());
443
444            // Analyze connections to determine actual inputs/outputs for root
445            let (root_inputs, root_outputs) =
446                Self::analyze_region_io(&root_area_ids, &genome.cortical_areas);
447
448            // Convert CorticalID to base64 for with_areas()
449            // Create a root region with a generated RegionID
450            let root_region_id = RegionID::new();
451            let root_region_id_str = root_region_id.to_string();
452
453            let mut root_region = BrainRegion::new(
454                root_region_id,
455                "Root Brain Region".to_string(),
456                RegionType::Undefined,
457            )
458            .expect("Failed to create root region")
459            .with_areas(root_area_ids.iter().cloned());
460
461            // Store inputs/outputs based on connection analysis
462            if !root_inputs.is_empty() {
463                root_region
464                    .add_property("inputs".to_string(), serde_json::json!(root_inputs.clone()));
465            }
466            if !root_outputs.is_empty() {
467                root_region.add_property(
468                    "outputs".to_string(),
469                    serde_json::json!(root_outputs.clone()),
470                );
471            }
472
473            info!(target: "feagi-bdu","  ✅ Created root region with {} areas (IPU={}, OPU={}, CORE={}) - analyzed: {} inputs, {} outputs",
474                  root_area_ids.len(), ipu_areas.len(), opu_areas.len(), core_areas.len(),
475                  root_inputs.len(), root_outputs.len());
476
477            // Step 2: Create subregion for CUSTOM/MEMORY areas if any exist
478            let mut subregion_id = None;
479            if !custom_memory_areas.is_empty() {
480                // Convert CorticalID to base64 for sorting and hashing
481                let mut custom_memory_strs: Vec<String> = custom_memory_areas
482                    .iter()
483                    .map(|id| id.as_base_64())
484                    .collect();
485                custom_memory_strs.sort(); // Sort for deterministic hash
486                let combined = custom_memory_strs.join("|");
487
488                // Use a simple hash (matching Python's sha1[:8])
489                use std::collections::hash_map::DefaultHasher;
490                use std::hash::{Hash, Hasher};
491                let mut hasher = DefaultHasher::new();
492                combined.hash(&mut hasher);
493                let hash = hasher.finish();
494                let hash_hex = format!("{:08x}", hash as u32);
495                let region_id = format!("region_autogen_{}", hash_hex);
496
497                // Analyze connections to determine inputs/outputs for subregion
498                let (subregion_inputs, subregion_outputs) =
499                    Self::analyze_region_io(&custom_memory_areas, &genome.cortical_areas);
500
501                // Calculate smart position: Place autogen region outside root's bounding box
502                let autogen_position =
503                    Self::calculate_autogen_region_position(&root_area_ids, genome);
504
505                // Create subregion
506                let mut subregion = BrainRegion::new(
507                    RegionID::new(), // Generate new UUID instead of using string
508                    "Autogen Region".to_string(),
509                    RegionType::Undefined, // RegionType no longer has Custom variant
510                )
511                .expect("Failed to create subregion")
512                .with_areas(custom_memory_areas.iter().cloned());
513
514                // Set 3D coordinates (place outside root's bounding box)
515                subregion.add_property(
516                    "coordinate_3d".to_string(),
517                    serde_json::json!(autogen_position),
518                );
519                subregion.add_property("coordinate_2d".to_string(), serde_json::json!([0, 0]));
520
521                // Store inputs/outputs for subregion
522                if !subregion_inputs.is_empty() {
523                    subregion.add_property(
524                        "inputs".to_string(),
525                        serde_json::json!(subregion_inputs.clone()),
526                    );
527                }
528                if !subregion_outputs.is_empty() {
529                    subregion.add_property(
530                        "outputs".to_string(),
531                        serde_json::json!(subregion_outputs.clone()),
532                    );
533                }
534
535                let subregion_id_str = subregion.region_id.to_string();
536
537                info!(target: "feagi-bdu","  ✅ Created subregion '{}' with {} CUSTOM/MEMORY areas ({} inputs, {} outputs)",
538                      region_id, custom_memory_areas.len(), subregion_inputs.len(), subregion_outputs.len());
539
540                regions_map.insert(subregion_id_str.clone(), subregion);
541                subregion_id = Some(subregion_id_str);
542            }
543
544            regions_map.insert(root_region_id_str.clone(), root_region);
545
546            // Count total inputs/outputs across all regions
547            let total_inputs = root_inputs.len()
548                + if let Some(ref sid) = subregion_id {
549                    regions_map
550                        .get(sid)
551                        .and_then(|r| r.properties.get("inputs"))
552                        .and_then(|v| v.as_array())
553                        .map(|a| a.len())
554                        .unwrap_or(0)
555                } else {
556                    0
557                };
558
559            let total_outputs = root_outputs.len()
560                + if let Some(ref sid) = subregion_id {
561                    regions_map
562                        .get(sid)
563                        .and_then(|r| r.properties.get("outputs"))
564                        .and_then(|v| v.as_array())
565                        .map(|a| a.len())
566                        .unwrap_or(0)
567                } else {
568                    0
569                };
570
571            info!(target: "feagi-bdu","  ✅ Auto-generated {} brain region(s) with {} total cortical areas ({} total inputs, {} total outputs)",
572                  regions_map.len(), all_cortical_ids.len(), total_inputs, total_outputs);
573
574            // Return (regions_map, parent_map) so we can properly link hierarchy
575            let mut parent_map = std::collections::HashMap::new();
576            if let Some(ref sub_id) = subregion_id {
577                parent_map.insert(sub_id.clone(), root_region_id_str.clone());
578                info!(target: "feagi-bdu","  🔗 Parent relationship: {} -> {}", sub_id, root_region_id_str);
579            }
580
581            (regions_map, parent_map)
582        } else {
583            info!(target: "feagi-bdu","  📋 Genome already has {} brain regions - using existing structure", genome.brain_regions.len());
584            // For existing genomes, we don't have parent info readily available
585            // TODO: Extract parent relationships from genome structure
586            (
587                genome.brain_regions.clone(),
588                std::collections::HashMap::new(),
589            )
590        };
591
592        // Add brain regions with proper parent relationships - minimize lock scope
593        {
594            let mut manager = self.connectome_manager.write();
595            let brain_region_count = brain_regions_to_add.len();
596            info!(target: "feagi-bdu","  Adding {} brain regions from genome", brain_region_count);
597
598            // First add root (no parent) - need to find it by iterating since key is UUID
599            let root_entry = brain_regions_to_add
600                .iter()
601                .find(|(_, region)| region.name == "Root Brain Region");
602            if let Some((root_id, root_region)) = root_entry {
603                manager.add_brain_region(root_region.clone(), None)?;
604                debug!(target: "feagi-bdu","    ✓ Added brain region: {} (Root Brain Region) [parent=None]", root_id);
605            }
606
607            // Then add other regions with their parent relationships
608            for (region_id, region) in brain_regions_to_add.iter() {
609                if region.name == "Root Brain Region" {
610                    continue; // Already added
611                }
612
613                let parent_id = region_parent_map.get(region_id).cloned();
614                manager.add_brain_region(region.clone(), parent_id.clone())?;
615                debug!(target: "feagi-bdu","    ✓ Added brain region: {} ({}) [parent={:?}]",
616                       region_id, region.name, parent_id);
617            }
618
619            info!(target: "feagi-bdu","  Total brain regions in ConnectomeManager: {}", manager.get_brain_region_ids().len());
620        } // Lock released
621
622        self.update_stage(DevelopmentStage::Corticogenesis, 100);
623        info!(target: "feagi-bdu","  ✅ Corticogenesis complete: {} cortical areas created", total_areas);
624
625        Ok(())
626    }
627
628    /// Stage 2: Voxelogenesis - Establish spatial framework
629    fn voxelogenesis(&mut self, _genome: &RuntimeGenome) -> BduResult<()> {
630        self.update_stage(DevelopmentStage::Voxelogenesis, 0);
631        info!(target: "feagi-bdu","📐 Stage 2: Voxelogenesis - Establishing spatial framework");
632
633        // Spatial framework is implicitly established by cortical area dimensions
634        // The Morton spatial hash in ConnectomeManager handles the actual indexing
635
636        self.update_stage(DevelopmentStage::Voxelogenesis, 100);
637        info!(target: "feagi-bdu","  ✅ Voxelogenesis complete: Spatial framework established");
638
639        Ok(())
640    }
641
642    /// Stage 3: Neurogenesis - Generate neurons within cortical areas
643    ///
644    /// This uses ConnectomeManager which delegates to NPU's SIMD-optimized batch operations.
645    /// Each cortical area is processed with `create_cortical_area_neurons()` which creates
646    /// ALL neurons for that area in one vectorized operation (not a loop).
647    fn neurogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
648        self.update_stage(DevelopmentStage::Neurogenesis, 0);
649        info!(target: "feagi-bdu","🔬 Stage 3: Neurogenesis - Generating neurons (SIMD-optimized batches)");
650
651        let expected_neurons = genome.stats.innate_neuron_count;
652        info!(target: "feagi-bdu","  Expected innate neurons from genome: {}", expected_neurons);
653
654        let mut total_neurons_created = 0;
655        let total_areas = genome.cortical_areas.len();
656
657        // Process each cortical area via ConnectomeManager (each area = one SIMD batch)
658        // NOTE: Loop is over AREAS, not neurons. Each area creates all its neurons in ONE batch call.
659        for (idx, (_cortical_id, area)) in genome.cortical_areas.iter().enumerate() {
660            // Get neurons_per_voxel from typed field (single source of truth)
661            let per_voxel_count = area
662                .properties
663                .get("neurons_per_voxel")
664                .and_then(|v| v.as_u64())
665                .unwrap_or(1) as i64;
666
667            // 🔍 DIAGNOSTIC: Use area.cortical_id (the actual ID stored in ConnectomeManager)
668            // This MUST match what was registered during corticogenesis!
669            let cortical_id_str = area.cortical_id.to_string();
670            // Note: Core IDs are 8-byte padded: "___power" and "___death" or "_power__" and "_death__"
671            if cortical_id_str == "___power"
672                || cortical_id_str == "___death"
673                || cortical_id_str == "_power__"
674                || cortical_id_str == "_death__"
675            {
676                info!(target: "feagi-bdu","  🔍 [CORE-AREA] {} - dimensions: {:?}, per_voxel: {}",
677                    cortical_id_str, area.dimensions, per_voxel_count);
678            }
679
680            if per_voxel_count == 0 {
681                warn!(target: "feagi-bdu","  ⚠️ Skipping area {} - per_voxel_neuron_cnt is 0 (will have NO neurons!)", cortical_id_str);
682                continue;
683            }
684
685            // Call ConnectomeManager to create neurons (delegates to NPU)
686            // CRITICAL: Minimize lock scope - only hold lock during neuron creation
687            let neurons_created = {
688                let manager_arc = self.connectome_manager.clone();
689                let mut manager = manager_arc.write();
690                manager.create_neurons_for_area(&area.cortical_id)
691            }; // Lock released immediately
692
693            match neurons_created {
694                Ok(count) => {
695                    total_neurons_created += count as usize;
696                    trace!(
697                        target: "feagi-bdu",
698                        "Created {} neurons for area {}",
699                        count,
700                        cortical_id_str
701                    );
702                }
703                Err(e) => {
704                    // If NPU not connected, calculate expected count
705                    warn!(target: "feagi-bdu","  Failed to create neurons for {}: {} (NPU may not be connected)",
706                        cortical_id_str, e);
707                    let total_voxels = area.dimensions.width as usize
708                        * area.dimensions.height as usize
709                        * area.dimensions.depth as usize;
710                    let expected = total_voxels * per_voxel_count as usize;
711                    total_neurons_created += expected;
712                }
713            }
714
715            // Update progress
716            let progress_pct = ((idx + 1) * 100 / total_areas.max(1)) as u8;
717            self.update_progress(|p| {
718                p.neurons_created = total_neurons_created;
719                p.progress = progress_pct;
720            });
721        }
722
723        // Compare with genome stats (info only - stats may count only innate neurons while we create all voxels)
724        if expected_neurons > 0 && total_neurons_created != expected_neurons {
725            trace!(target: "feagi-bdu",
726                created_neurons = total_neurons_created,
727                genome_stats_innate = expected_neurons,
728                "Neuron creation complete (genome stats may only count innate neurons)"
729            );
730        }
731
732        self.update_stage(DevelopmentStage::Neurogenesis, 100);
733        info!(target: "feagi-bdu","  ✅ Neurogenesis complete: {} neurons created", total_neurons_created);
734
735        Ok(())
736    }
737
738    /// Stage 4: Synaptogenesis - Form synaptic connections between neurons
739    ///
740    /// This uses ConnectomeManager which delegates to NPU's morphology functions.
741    /// Each morphology application (`apply_projector_morphology`, etc.) processes ALL neurons
742    /// from the source area and creates ALL synapses in one SIMD-optimized batch operation.
743    fn synaptogenesis(&mut self, genome: &RuntimeGenome) -> BduResult<()> {
744        self.update_stage(DevelopmentStage::Synaptogenesis, 0);
745        info!(target: "feagi-bdu","🔗 Stage 4: Synaptogenesis - Forming synaptic connections (SIMD-optimized batches)");
746
747        let expected_synapses = genome.stats.innate_synapse_count;
748        info!(target: "feagi-bdu","  Expected innate synapses from genome: {}", expected_synapses);
749
750        let mut total_synapses_created = 0;
751        let total_areas = genome.cortical_areas.len();
752
753        // Process each source area via ConnectomeManager (each mapping = one SIMD batch)
754        // NOTE: Loop is over AREAS, not synapses. Each area applies all mappings in batch calls.
755        for (idx, (_src_cortical_id, src_area)) in genome.cortical_areas.iter().enumerate() {
756            // Check if area has mappings
757            let has_dstmap = src_area
758                .properties
759                .get("cortical_mapping_dst")
760                .and_then(|v| v.as_object())
761                .map(|m| !m.is_empty())
762                .unwrap_or(false);
763
764            if !has_dstmap {
765                trace!(target: "feagi-bdu", "No dstmap for area {}", &src_area.cortical_id);
766                continue;
767            }
768
769            // Call ConnectomeManager to apply cortical mappings (delegates to NPU)
770            // CRITICAL: Minimize lock scope - only hold lock during synapse creation
771            // Use src_area.cortical_id (the actual ID stored in ConnectomeManager)
772            let src_cortical_id = &src_area.cortical_id;
773            let src_cortical_id_str = src_cortical_id.to_string(); // For logging
774            let synapses_created = {
775                let manager_arc = self.connectome_manager.clone();
776                let mut manager = manager_arc.write();
777                manager.apply_cortical_mapping(src_cortical_id)
778            }; // Lock released immediately
779
780            match synapses_created {
781                Ok(count) => {
782                    total_synapses_created += count as usize;
783                    trace!(
784                        target: "feagi-bdu",
785                        "Created {} synapses for area {}",
786                        count,
787                        src_cortical_id_str
788                    );
789                }
790                Err(e) => {
791                    // If NPU not connected, estimate count
792                    warn!(target: "feagi-bdu","  Failed to create synapses for {}: {} (NPU may not be connected)",
793                        src_cortical_id_str, e);
794                    let estimated = estimate_synapses_for_area(src_area, genome);
795                    total_synapses_created += estimated;
796                }
797            }
798
799            // Update progress
800            let progress_pct = ((idx + 1) * 100 / total_areas.max(1)) as u8;
801            self.update_progress(|p| {
802                p.synapses_created = total_synapses_created;
803                p.progress = progress_pct;
804            });
805        }
806
807        // Verify against genome stats
808        if expected_synapses > 0 {
809            let diff = (total_synapses_created as i64 - expected_synapses as i64).abs();
810            let diff_pct = (diff as f64 / expected_synapses.max(1) as f64) * 100.0;
811
812            if diff_pct > 10.0 {
813                warn!(target: "feagi-bdu",
814                    "Synapse count variance: created {} but genome stats expected {} ({:.1}% difference)",
815                    total_synapses_created, expected_synapses, diff_pct
816                );
817            } else {
818                info!(target: "feagi-bdu",
819                    "Synapse count matches genome stats within {:.1}% ({} vs {})",
820                    diff_pct, total_synapses_created, expected_synapses
821                );
822            }
823        }
824
825        self.update_stage(DevelopmentStage::Synaptogenesis, 100);
826        info!(target: "feagi-bdu","  ✅ Synaptogenesis complete: {} synapses created", total_synapses_created);
827
828        Ok(())
829    }
830}
831
832/// Estimate synapse count for an area (fallback when NPU not connected)
833///
834/// This is only used when NPU is not available for actual synapse creation.
835fn estimate_synapses_for_area(
836    src_area: &CorticalArea,
837    genome: &feagi_evolutionary::RuntimeGenome,
838) -> usize {
839    let dstmap = match src_area.properties.get("cortical_mapping_dst") {
840        Some(serde_json::Value::Object(map)) => map,
841        _ => return 0,
842    };
843
844    let mut total = 0;
845
846    for (dst_id, rules) in dstmap {
847        // Convert string dst_id to CorticalID for lookup
848        let dst_cortical_id = match feagi_evolutionary::string_to_cortical_id(dst_id) {
849            Ok(id) => id,
850            Err(_) => continue,
851        };
852        let dst_area = match genome.cortical_areas.get(&dst_cortical_id) {
853            Some(area) => area,
854            None => continue,
855        };
856
857        let rules_array = match rules.as_array() {
858            Some(arr) => arr,
859            None => continue,
860        };
861
862        for rule in rules_array {
863            let morphology_id = rule
864                .get("morphology_id")
865                .and_then(|v| v.as_str())
866                .unwrap_or("unknown");
867            let scalar = rule
868                .get("morphology_scalar")
869                .and_then(|v| v.as_i64())
870                .unwrap_or(1) as usize;
871
872            // Simplified estimation
873            let src_per_voxel = src_area
874                .properties
875                .get("neurons_per_voxel")
876                .and_then(|v| v.as_u64())
877                .unwrap_or(1) as usize;
878            let dst_per_voxel = dst_area
879                .properties
880                .get("neurons_per_voxel")
881                .and_then(|v| v.as_u64())
882                .unwrap_or(1) as usize;
883
884            let src_voxels =
885                src_area.dimensions.width * src_area.dimensions.height * src_area.dimensions.depth;
886            let dst_voxels =
887                dst_area.dimensions.width * dst_area.dimensions.height * dst_area.dimensions.depth;
888
889            let src_neurons = src_voxels as usize * src_per_voxel;
890            let dst_neurons = dst_voxels as usize * dst_per_voxel as usize;
891
892            // Basic estimation by morphology type
893            let count = match morphology_id {
894                "block_to_block" => src_neurons * dst_per_voxel * scalar,
895                "projector" => src_neurons * dst_neurons * scalar,
896                _ if morphology_id.contains("lateral") => src_neurons * scalar,
897                _ => (src_neurons * scalar).min(src_neurons * dst_neurons / 10),
898            };
899
900            total += count;
901        }
902    }
903
904    total
905}
906
907impl Neuroembryogenesis {
908    /// Calculate position for autogen region based on root region's bounding box
909    fn calculate_autogen_region_position(
910        root_area_ids: &[CorticalID],
911        genome: &feagi_evolutionary::RuntimeGenome,
912    ) -> [i32; 3] {
913        if root_area_ids.is_empty() {
914            return [100, 0, 0];
915        }
916
917        let mut min_x = i32::MAX;
918        let mut max_x = i32::MIN;
919        let mut min_y = i32::MAX;
920        let mut max_y = i32::MIN;
921        let mut min_z = i32::MAX;
922        let mut max_z = i32::MIN;
923
924        for cortical_id in root_area_ids {
925            if let Some(area) = genome.cortical_areas.get(cortical_id) {
926                let pos: (i32, i32, i32) = area.position.into();
927                let dims = (
928                    area.dimensions.width as i32,
929                    area.dimensions.height as i32,
930                    area.dimensions.depth as i32,
931                );
932
933                min_x = min_x.min(pos.0);
934                max_x = max_x.max(pos.0 + dims.0);
935                min_y = min_y.min(pos.1);
936                max_y = max_y.max(pos.1 + dims.1);
937                min_z = min_z.min(pos.2);
938                max_z = max_z.max(pos.2 + dims.2);
939            }
940        }
941
942        let bbox_width = (max_x - min_x).max(1);
943        let padding = (bbox_width / 5).max(50);
944        let autogen_x = max_x + padding;
945        let autogen_y = (min_y + max_y) / 2;
946        let autogen_z = (min_z + max_z) / 2;
947
948        info!(target: "feagi-bdu",
949              "  📐 Autogen position: ({}, {}, {}) [padding: {}]",
950              autogen_x, autogen_y, autogen_z, padding);
951
952        [autogen_x, autogen_y, autogen_z]
953    }
954
955    /// Analyze region inputs/outputs based on cortical connections
956    ///
957    /// Following Python's _auto_assign_region_io() logic:
958    /// - OUTPUT: Any area in the region that connects to an area OUTSIDE the region
959    /// - INPUT: Any area in the region that receives connection from OUTSIDE the region
960    fn analyze_region_io(
961        region_area_ids: &[feagi_structures::genomic::cortical_area::CorticalID],
962        all_cortical_areas: &std::collections::HashMap<CorticalID, CorticalArea>,
963    ) -> (Vec<String>, Vec<String>) {
964        let area_set: std::collections::HashSet<_> = region_area_ids.iter().cloned().collect();
965        let mut inputs = Vec::new();
966        let mut outputs = Vec::new();
967
968        // Helper to extract destination area IDs from cortical_mapping_dst (as strings)
969        let extract_destinations = |area: &CorticalArea| -> Vec<String> {
970            area.properties
971                .get("cortical_mapping_dst")
972                .and_then(|v| v.as_object())
973                .map(|obj| obj.keys().cloned().collect())
974                .unwrap_or_default()
975        };
976
977        // Find OUTPUTS: areas in region that connect to areas OUTSIDE region
978        for area_id in region_area_ids {
979            if let Some(area) = all_cortical_areas.get(area_id) {
980                let destinations = extract_destinations(area);
981                // Convert destination strings to CorticalID for comparison
982                let external_destinations: Vec<_> = destinations
983                    .iter()
984                    .filter_map(|dest| feagi_evolutionary::string_to_cortical_id(dest).ok())
985                    .filter(|dest_id| !area_set.contains(dest_id))
986                    .collect();
987
988                if !external_destinations.is_empty() {
989                    outputs.push(area_id.as_base_64());
990                }
991            }
992        }
993
994        // Find INPUTS: areas in region receiving connections from OUTSIDE region
995        for (source_area_id, source_area) in all_cortical_areas.iter() {
996            // Skip areas that are inside the region
997            if area_set.contains(source_area_id) {
998                continue;
999            }
1000
1001            let destinations = extract_destinations(source_area);
1002            for dest_str in destinations {
1003                if let Ok(dest_id) = feagi_evolutionary::string_to_cortical_id(&dest_str) {
1004                    if area_set.contains(&dest_id) {
1005                        let dest_string = dest_id.as_base_64();
1006                        if !inputs.contains(&dest_string) {
1007                            inputs.push(dest_string);
1008                        }
1009                    }
1010                }
1011            }
1012        }
1013
1014        (inputs, outputs)
1015    }
1016
1017    /// Update development stage
1018    fn update_stage(&self, stage: DevelopmentStage, progress: u8) {
1019        let mut p = self.progress.write();
1020        p.stage = stage;
1021        p.progress = progress;
1022        p.duration_ms = self.start_time.elapsed().as_millis() as u64;
1023    }
1024
1025    /// Update progress with a closure
1026    fn update_progress<F>(&self, f: F)
1027    where
1028        F: FnOnce(&mut DevelopmentProgress),
1029    {
1030        let mut p = self.progress.write();
1031        f(&mut p);
1032        p.duration_ms = self.start_time.elapsed().as_millis() as u64;
1033    }
1034}
1035
1036#[cfg(test)]
1037mod tests {
1038    use super::*;
1039    use feagi_evolutionary::create_genome_with_core_morphologies;
1040    use feagi_structures::genomic::cortical_area::CorticalAreaDimensions;
1041
1042    #[test]
1043    fn test_neuroembryogenesis_creation() {
1044        let manager = ConnectomeManager::instance();
1045        let neuro = Neuroembryogenesis::new(manager);
1046
1047        let progress = neuro.get_progress();
1048        assert_eq!(progress.stage, DevelopmentStage::Initialization);
1049        assert_eq!(progress.progress, 0);
1050    }
1051
1052    #[test]
1053    fn test_development_from_minimal_genome() {
1054        ConnectomeManager::reset_for_testing(); // Ensure clean state
1055        let manager = ConnectomeManager::instance();
1056        let mut neuro = Neuroembryogenesis::new(manager.clone());
1057
1058        // Create a minimal genome with one cortical area
1059        let mut genome = create_genome_with_core_morphologies(
1060            "test_genome".to_string(),
1061            "Test Genome".to_string(),
1062        );
1063
1064        let cortical_id = CorticalID::try_from_bytes(b"cst_neur").unwrap(); // Use valid custom cortical ID
1065        let cortical_type = cortical_id
1066            .as_cortical_type()
1067            .expect("Failed to get cortical type");
1068        let area = CorticalArea::new(
1069            cortical_id,
1070            0,
1071            "Test Area".to_string(),
1072            CorticalAreaDimensions::new(10, 10, 10).unwrap(),
1073            (0, 0, 0).into(),
1074            cortical_type,
1075        )
1076        .expect("Failed to create cortical area");
1077        genome.cortical_areas.insert(cortical_id, area);
1078
1079        // Run neuroembryogenesis
1080        let result = neuro.develop_from_genome(&genome);
1081        assert!(result.is_ok(), "Development failed: {:?}", result);
1082
1083        // Check progress
1084        let progress = neuro.get_progress();
1085        assert_eq!(progress.stage, DevelopmentStage::Completed);
1086        assert_eq!(progress.progress, 100);
1087        assert_eq!(progress.cortical_areas_created, 1);
1088
1089        // Verify cortical area was added to connectome
1090        let mgr = manager.read();
1091        // Note: Due to parallel test execution with shared singleton, we just verify the area exists
1092        assert!(
1093            mgr.has_cortical_area(&cortical_id),
1094            "Cortical area should have been added to connectome"
1095        );
1096
1097        println!("✅ Development completed in {}ms", progress.duration_ms);
1098    }
1099}