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