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