Skip to main content

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