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