Skip to main content

feagi_services/impls/
genome_service_impl.rs

1// Copyright 2025 Neuraville Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4/*!
5Genome service implementation.
6
7Copyright 2025 Neuraville Inc.
8Licensed under the Apache License, Version 2.0
9*/
10
11use crate::traits::GenomeService;
12use crate::types::*;
13use async_trait::async_trait;
14use feagi_brain_development::models::CorticalAreaExt;
15use feagi_brain_development::neuroembryogenesis::Neuroembryogenesis;
16use feagi_brain_development::ConnectomeManager;
17use feagi_evolutionary::{get_default_neural_properties, MemoryAreaProperties};
18use feagi_npu_burst_engine::{BurstLoopRunner, ParameterUpdateQueue};
19use feagi_structures::genomic::cortical_area::descriptors::{
20    CorticalSubUnitIndex, CorticalUnitIndex,
21};
22use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::{
23    FrameChangeHandling, PercentageNeuronPositioning,
24};
25use feagi_structures::genomic::cortical_area::{
26    CorticalArea, CorticalAreaDimensions, CorticalAreaType, CorticalID,
27    IOCorticalAreaConfigurationFlag,
28};
29use feagi_structures::genomic::{MotorCorticalUnit, SensoryCorticalUnit};
30use parking_lot::RwLock;
31use serde_json::Value;
32use std::collections::HashMap;
33use std::sync::Arc;
34use tracing::{info, trace, warn};
35
36use crate::genome::{ChangeType, CorticalChangeClassifier};
37
38fn frame_handling_label(frame: FrameChangeHandling) -> &'static str {
39    match frame {
40        FrameChangeHandling::Absolute => "Absolute",
41        FrameChangeHandling::Incremental => "Incremental",
42    }
43}
44
45fn positioning_label(positioning: PercentageNeuronPositioning) -> &'static str {
46    match positioning {
47        PercentageNeuronPositioning::Linear => "Linear",
48        PercentageNeuronPositioning::Fractional => "Fractional",
49    }
50}
51
52fn signage_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
53    match flag {
54        IOCorticalAreaConfigurationFlag::SignedPercentage(..)
55        | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
56        | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
57        | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..) => "Percentage Signed",
58        IOCorticalAreaConfigurationFlag::Percentage(..)
59        | IOCorticalAreaConfigurationFlag::Percentage2D(..)
60        | IOCorticalAreaConfigurationFlag::Percentage3D(..)
61        | IOCorticalAreaConfigurationFlag::Percentage4D(..) => "Percentage Unsigned",
62        IOCorticalAreaConfigurationFlag::CartesianPlane(..) => "Cartesian Plane",
63        IOCorticalAreaConfigurationFlag::Misc(..) => "Misc",
64        IOCorticalAreaConfigurationFlag::Boolean => "Boolean",
65    }
66}
67
68/// Merge default template and memory properties into provided values.
69/// Existing values always override defaults.
70fn merge_memory_area_properties(
71    base: HashMap<String, Value>,
72    extra: Option<&HashMap<String, Value>>,
73) -> HashMap<String, Value> {
74    let mut defaults = get_default_neural_properties();
75    let memory_defaults = MemoryAreaProperties::default();
76    defaults
77        .entry("cortical_group".to_string())
78        .or_insert(Value::from("MEMORY"));
79    defaults
80        .entry("is_mem_type".to_string())
81        .or_insert(Value::from(true));
82    defaults
83        .entry("temporal_depth".to_string())
84        .or_insert(Value::from(memory_defaults.temporal_depth));
85    defaults
86        .entry("longterm_mem_threshold".to_string())
87        .or_insert(Value::from(memory_defaults.longterm_threshold));
88    defaults
89        .entry("lifespan_growth_rate".to_string())
90        .or_insert(Value::from(memory_defaults.lifespan_growth_rate));
91    defaults
92        .entry("init_lifespan".to_string())
93        .or_insert(Value::from(memory_defaults.init_lifespan));
94
95    defaults.extend(base);
96    if let Some(extra_props) = extra {
97        defaults.extend(extra_props.clone());
98    }
99    defaults
100}
101
102fn behavior_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
103    match flag {
104        IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
105        IOCorticalAreaConfigurationFlag::CartesianPlane(frame)
106        | IOCorticalAreaConfigurationFlag::Misc(frame)
107        | IOCorticalAreaConfigurationFlag::Percentage(frame, _)
108        | IOCorticalAreaConfigurationFlag::Percentage2D(frame, _)
109        | IOCorticalAreaConfigurationFlag::Percentage3D(frame, _)
110        | IOCorticalAreaConfigurationFlag::Percentage4D(frame, _)
111        | IOCorticalAreaConfigurationFlag::SignedPercentage(frame, _)
112        | IOCorticalAreaConfigurationFlag::SignedPercentage2D(frame, _)
113        | IOCorticalAreaConfigurationFlag::SignedPercentage3D(frame, _)
114        | IOCorticalAreaConfigurationFlag::SignedPercentage4D(frame, _) => {
115            frame_handling_label(*frame)
116        }
117    }
118}
119
120fn coding_type_label_from_flag(flag: &IOCorticalAreaConfigurationFlag) -> &'static str {
121    match flag {
122        IOCorticalAreaConfigurationFlag::Percentage(_, positioning)
123        | IOCorticalAreaConfigurationFlag::Percentage2D(_, positioning)
124        | IOCorticalAreaConfigurationFlag::Percentage3D(_, positioning)
125        | IOCorticalAreaConfigurationFlag::Percentage4D(_, positioning)
126        | IOCorticalAreaConfigurationFlag::SignedPercentage(_, positioning)
127        | IOCorticalAreaConfigurationFlag::SignedPercentage2D(_, positioning)
128        | IOCorticalAreaConfigurationFlag::SignedPercentage3D(_, positioning)
129        | IOCorticalAreaConfigurationFlag::SignedPercentage4D(_, positioning) => {
130            positioning_label(*positioning)
131        }
132        IOCorticalAreaConfigurationFlag::CartesianPlane(..)
133        | IOCorticalAreaConfigurationFlag::Misc(..)
134        | IOCorticalAreaConfigurationFlag::Boolean => "Not Applicable",
135    }
136}
137
138fn io_unit_reference_from_cortical_id(cortical_id: &CorticalID) -> Option<[u8; 3]> {
139    let bytes = cortical_id.as_bytes();
140    if bytes[0] != b'i' && bytes[0] != b'o' {
141        return None;
142    }
143    Some([bytes[1], bytes[2], bytes[3]])
144}
145
146fn io_coding_options_for_unit(cortical_id: &CorticalID) -> Option<IOCodingOptions> {
147    let unit_ref = io_unit_reference_from_cortical_id(cortical_id)?;
148    let is_input = cortical_id.as_bytes()[0] == b'i';
149
150    let (accepted_type, allowed_frames) = if is_input {
151        let unit = SensoryCorticalUnit::list_all()
152            .iter()
153            .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
154        (
155            unit.get_accepted_wrapped_io_data_type(),
156            unit.get_allowed_frame_change_handling(),
157        )
158    } else {
159        let unit = MotorCorticalUnit::list_all()
160            .iter()
161            .find(|u| u.get_cortical_id_unit_reference() == unit_ref)?;
162        (
163            unit.get_accepted_wrapped_io_data_type(),
164            unit.get_allowed_frame_change_handling(),
165        )
166    };
167
168    let mut signage_options = Vec::new();
169    let mut behavior_options = Vec::new();
170    let mut coding_type_options = Vec::new();
171
172    let io_flag = match cortical_id.extract_io_data_flag() {
173        Ok(flag) => flag,
174        Err(err) => {
175            warn!(
176                target: "feagi-services",
177                "[IO-CODING] {} failed to extract io_flag: {} (accepted_type={})",
178                cortical_id,
179                err,
180                accepted_type
181            );
182            return None;
183        }
184    };
185    signage_options.push(signage_label_from_flag(&io_flag).to_string());
186
187    let supports_frame_handling = !matches!(io_flag, IOCorticalAreaConfigurationFlag::Boolean);
188    if supports_frame_handling {
189        if let Some(frames) = allowed_frames {
190            for frame in frames {
191                behavior_options.push(frame_handling_label(*frame).to_string());
192            }
193        } else {
194            behavior_options.push("Absolute".to_string());
195            behavior_options.push("Incremental".to_string());
196        }
197    } else {
198        behavior_options.push("Not Applicable".to_string());
199    }
200
201    let supports_positioning = matches!(
202        io_flag,
203        IOCorticalAreaConfigurationFlag::Percentage(..)
204            | IOCorticalAreaConfigurationFlag::Percentage2D(..)
205            | IOCorticalAreaConfigurationFlag::Percentage3D(..)
206            | IOCorticalAreaConfigurationFlag::Percentage4D(..)
207            | IOCorticalAreaConfigurationFlag::SignedPercentage(..)
208            | IOCorticalAreaConfigurationFlag::SignedPercentage2D(..)
209            | IOCorticalAreaConfigurationFlag::SignedPercentage3D(..)
210            | IOCorticalAreaConfigurationFlag::SignedPercentage4D(..)
211    );
212    if supports_positioning {
213        coding_type_options.push("Linear".to_string());
214        coding_type_options.push("Fractional".to_string());
215    } else {
216        coding_type_options.push("Not Applicable".to_string());
217    }
218
219    if signage_options.is_empty() {
220        warn!(
221            target: "feagi-services",
222            "[IO-CODING] {} empty signage_options (accepted_type={}, io_flag={:?})",
223            cortical_id,
224            accepted_type,
225            io_flag
226        );
227    }
228    Some(IOCodingOptions {
229        signage_options,
230        behavior_options,
231        coding_type_options,
232    })
233}
234
235/// Default implementation of GenomeService
236pub struct GenomeServiceImpl {
237    connectome: Arc<RwLock<ConnectomeManager>>,
238    parameter_queue: Option<ParameterUpdateQueue>,
239    /// Currently loaded genome (source of truth for structural changes)
240    /// This is updated when genome is loaded or when cortical areas are modified
241    current_genome: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
242    /// Counter tracking how many genomes have been loaded (increments on each load)
243    genome_load_counter: Arc<RwLock<i32>>,
244    /// Timestamp of when the current genome was loaded
245    genome_load_timestamp: Arc<RwLock<Option<i64>>>,
246    /// Optional burst runner for refreshing cortical_id cache
247    burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
248}
249
250impl GenomeServiceImpl {
251    pub fn new(connectome: Arc<RwLock<ConnectomeManager>>) -> Self {
252        Self {
253            connectome,
254            parameter_queue: None,
255            current_genome: Arc::new(RwLock::new(None)),
256            genome_load_counter: Arc::new(RwLock::new(0)),
257            genome_load_timestamp: Arc::new(RwLock::new(None)),
258            burst_runner: None,
259        }
260    }
261
262    pub fn new_with_parameter_queue(
263        connectome: Arc<RwLock<ConnectomeManager>>,
264        parameter_queue: ParameterUpdateQueue,
265    ) -> Self {
266        Self {
267            connectome,
268            parameter_queue: Some(parameter_queue),
269            current_genome: Arc::new(RwLock::new(None)),
270            genome_load_counter: Arc::new(RwLock::new(0)),
271            genome_load_timestamp: Arc::new(RwLock::new(None)),
272            burst_runner: None,
273        }
274    }
275
276    /// Set the burst runner for cache refresh
277    pub fn set_burst_runner(&mut self, burst_runner: Arc<RwLock<BurstLoopRunner>>) {
278        self.burst_runner = Some(burst_runner);
279    }
280
281    /// Refresh cortical_id cache in burst runner
282    fn refresh_burst_runner_cache(&self) {
283        if let Some(ref burst_runner) = self.burst_runner {
284            let manager = self.connectome.read();
285            let mappings = manager.get_all_cortical_idx_to_id_mappings();
286            let chunk_sizes = manager.get_all_visualization_granularities();
287            let mapping_count = mappings.len();
288            let burst_runner_write = burst_runner.write();
289            burst_runner_write.refresh_cortical_id_mappings(mappings);
290            burst_runner_write.refresh_visualization_granularities(chunk_sizes);
291            info!(target: "feagi-services", "Refreshed burst runner cache with {} cortical areas", mapping_count);
292        }
293    }
294
295    /// Get a reference to the current genome Arc
296    /// This allows other services to share access to the genome for persistence
297    pub fn get_current_genome_arc(&self) -> Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>> {
298        Arc::clone(&self.current_genome)
299    }
300}
301
302#[async_trait]
303impl GenomeService for GenomeServiceImpl {
304    async fn load_genome(&self, params: LoadGenomeParams) -> ServiceResult<GenomeInfo> {
305        info!(target: "feagi-services", "Loading genome from JSON");
306
307        // Parse genome using feagi-evo (this is CPU-bound, but relatively fast)
308        let mut genome = feagi_evolutionary::load_genome_from_json(&params.json_str)
309            .map_err(|e| ServiceError::InvalidInput(format!("Failed to parse genome: {}", e)))?;
310        let (_areas_added, morphs_added) = feagi_evolutionary::ensure_core_components(&mut genome);
311        if morphs_added > 0 {
312            info!(
313                target: "feagi-services",
314                "Added {} missing core morphologies during genome load",
315                morphs_added
316            );
317        }
318
319        // Extract simulation_timestep from genome physiology (will be returned in GenomeInfo)
320        let simulation_timestep = genome.physiology.simulation_timestep;
321        info!(target: "feagi-services", "Genome simulation_timestep: {} seconds", simulation_timestep);
322
323        // Store genome for future updates (source of truth for structural changes)
324        info!(target: "feagi-services", "Storing RuntimeGenome with {} cortical areas, {} morphologies",
325            genome.cortical_areas.len(), genome.morphologies.iter().count());
326        *self.current_genome.write() = Some(genome.clone());
327
328        // Increment genome load counter and set timestamp
329        let genome_num = {
330            let mut counter = self.genome_load_counter.write();
331            *counter += 1;
332            *counter
333        };
334
335        let genome_timestamp = std::time::SystemTime::now()
336            .duration_since(std::time::UNIX_EPOCH)
337            .ok()
338            .map(|d| d.as_secs() as i64);
339
340        *self.genome_load_timestamp.write() = genome_timestamp;
341
342        info!(target: "feagi-services", "Genome load #{}, timestamp: {:?}", genome_num, genome_timestamp);
343
344        // Load into connectome via ConnectomeManager
345        // This involves synaptogenesis which can be CPU-intensive, so run it on a blocking thread
346        // CRITICAL: Add timeout to prevent hanging during shutdown
347        // Note: spawn_blocking tasks cannot be cancelled, but timeout ensures we don't wait forever
348        // CRITICAL FIX: Don't hold write lock during entire operation - let neuroembryogenesis manage locks
349        // This prevents deadlock when neuroembryogenesis tries to acquire its own write locks
350        let connectome_clone = self.connectome.clone();
351        let blocking_handle = tokio::task::spawn_blocking(
352            move || -> Result<feagi_brain_development::neuroembryogenesis::DevelopmentProgress, ServiceError> {
353                // Acquire write lock only for prepare/resize operations
354                let mut genome_clone = genome;
355                let (prepare_result, resize_result) = {
356                    let mut manager = connectome_clone.write();
357                    let prepare_result = manager.prepare_for_new_genome();
358                    let resize_result = prepare_result
359                        .as_ref()
360                        .ok()
361                        .map(|_| manager.resize_for_genome(&genome_clone));
362                    (prepare_result, resize_result)
363                }; // Lock released here
364
365                prepare_result.map_err(|e| {
366                    tracing::error!(target: "feagi-services", "prepare_for_new_genome failed: {}", e);
367                    ServiceError::from(e)
368                })?;
369                if let Some(resize_result) = resize_result {
370                    resize_result.map_err(|e| {
371                        tracing::error!(target: "feagi-services", "resize_for_genome failed: {}", e);
372                        ServiceError::from(e)
373                    })?;
374                }
375
376                // Now call develop_from_genome without holding the lock
377                // It will acquire its own locks internally
378                let manager_arc = feagi_brain_development::ConnectomeManager::instance();
379                let mut neuro = Neuroembryogenesis::new(manager_arc.clone());
380                neuro.develop_from_genome(&genome_clone).map_err(|e| {
381                    tracing::error!(target: "feagi-services", "Neuroembryogenesis failed: {}", e);
382                    ServiceError::Backend(format!("Neuroembryogenesis failed: {}", e))
383                })?;
384
385                // Ensure core cortical areas exist after neuroembryogenesis
386                // (they may have been added during corticogenesis, but we ensure they exist)
387                {
388                    let mut manager = manager_arc.write();
389                    manager.ensure_core_cortical_areas().map_err(|e| {
390                        tracing::error!(target: "feagi-services", "ensure_core_cortical_areas failed: {}", e);
391                        ServiceError::Backend(format!("Failed to ensure core cortical areas: {}", e))
392                    })?;
393                }
394
395                // After neuroembryogenesis, update genome metadata with root_region_id
396                let root_region_id = manager_arc.read().get_root_region_id();
397                if let Some(root_id) = root_region_id {
398                    genome_clone.metadata.brain_regions_root = Some(root_id);
399                    info!(target: "feagi-services", "✅ Set genome brain_regions_root: {}", genome_clone.metadata.brain_regions_root.as_ref().unwrap());
400                } else {
401                    warn!(target: "feagi-services", "⚠️ No root region found after neuroembryogenesis");
402                }
403
404                Ok(neuro.get_progress())
405            },
406        );
407
408        // Wait with timeout - if timeout expires, abort the blocking task
409        let progress = match tokio::time::timeout(
410            tokio::time::Duration::from_secs(300), // 5 minute timeout
411            blocking_handle,
412        )
413        .await
414        {
415            Ok(Ok(result)) => result?,
416            Ok(Err(e)) => {
417                return Err(ServiceError::Backend(format!(
418                    "Blocking task panicked: {}",
419                    e
420                )))
421            }
422            Err(_) => {
423                // Timeout expired - abort the task (though it may continue running)
424                warn!(target: "feagi-services", "Genome loading timed out after 5 minutes - aborting");
425                return Err(ServiceError::Backend(
426                    "Genome loading timed out after 5 minutes".to_string(),
427                ));
428            }
429        };
430
431        info!(
432            target: "feagi-services",
433            "Genome loaded: {} cortical areas, {} neurons, {} synapses created",
434            progress.cortical_areas_created,
435            progress.neurons_created,
436            progress.synapses_created
437        );
438
439        // CRITICAL: Sync auto-generated brain regions back to RuntimeGenome
440        // BDU may auto-generate brain regions if the genome didn't have any.
441        // We need to sync these back to current_genome so they're included when saving.
442        let brain_regions_from_bdu = {
443            let manager = self.connectome.read();
444            let hierarchy = manager.get_brain_region_hierarchy();
445            hierarchy.get_all_regions()
446        };
447
448        if !brain_regions_from_bdu.is_empty() {
449            let mut current_genome_guard = self.current_genome.write();
450            if let Some(ref mut genome) = *current_genome_guard {
451                // Only update if BDU has more regions (handles auto-generation case)
452                if brain_regions_from_bdu.len() > genome.brain_regions.len() {
453                    info!(
454                        target: "feagi-services",
455                        "Syncing {} auto-generated brain regions from BDU to RuntimeGenome",
456                        brain_regions_from_bdu.len()
457                    );
458                    genome.brain_regions = brain_regions_from_bdu;
459                }
460            }
461        }
462
463        // Return genome info with simulation_timestep
464        let (cortical_area_count, brain_region_count) = {
465            let manager = self.connectome.read();
466            let cortical_area_count = manager.get_cortical_area_count();
467            let brain_region_ids = manager.get_brain_region_ids();
468            let brain_region_count = brain_region_ids.len();
469            (cortical_area_count, brain_region_count)
470        };
471
472        // Refresh burst runner cache after genome load
473        self.refresh_burst_runner_cache();
474
475        Ok(GenomeInfo {
476            genome_id: "current".to_string(),
477            genome_title: "Current Genome".to_string(),
478            version: "2.1".to_string(),
479            cortical_area_count,
480            brain_region_count,
481            simulation_timestep,          // From genome physiology
482            genome_num: Some(genome_num), // Actual load counter
483            genome_timestamp,             // Timestamp when genome was loaded
484        })
485    }
486
487    async fn save_genome(&self, params: SaveGenomeParams) -> ServiceResult<String> {
488        info!(target: "feagi-services", "Saving genome to JSON");
489
490        // Check if we have a RuntimeGenome stored (includes morphologies, physiology, etc.)
491        let genome_opt = self.current_genome.read().clone();
492
493        let mut genome = genome_opt.ok_or_else(|| {
494            ServiceError::Internal(
495                "No RuntimeGenome stored. Genome must be loaded via load_genome() before it can be saved.".to_string()
496            )
497        })?;
498
499        info!(target: "feagi-services", "✅ RuntimeGenome loaded, exporting in flat format v3.0");
500
501        // Debug: Check all property values in RuntimeGenome before saving
502        for (cortical_id, area) in &genome.cortical_areas {
503            let area_id_str = cortical_id.as_base_64();
504            info!(
505                target: "feagi-services",
506                "[GENOME-SAVE] Area {} has {} properties in RuntimeGenome",
507                area_id_str,
508                area.properties.len()
509            );
510
511            // Log key properties that should be saved
512            let key_props = [
513                "mp_driven_psp",
514                "snooze_length",
515                "consecutive_fire_cnt_max",
516                "firing_threshold_increment_x",
517                "firing_threshold_increment_y",
518                "firing_threshold_increment_z",
519                "firing_threshold",
520                "leak_coefficient",
521                "refractory_period",
522                "neuron_excitability",
523            ];
524
525            for prop_name in &key_props {
526                if let Some(prop_value) = area.properties.get(*prop_name) {
527                    info!(
528                        target: "feagi-services",
529                        "[GENOME-SAVE] Area {} property {}={}",
530                        area_id_str, prop_name, prop_value
531                    );
532                }
533            }
534        }
535
536        // Update metadata if provided
537        if let Some(id) = params.genome_id {
538            genome.metadata.genome_id = id;
539        }
540        if let Some(title) = params.genome_title {
541            genome.metadata.genome_title = title;
542        }
543
544        // Use the full RuntimeGenome saver (produces flat format v3.0)
545        let json_str = feagi_evolutionary::save_genome_to_json(&genome)
546            .map_err(|e| ServiceError::Internal(format!("Failed to save genome: {}", e)))?;
547
548        info!(target: "feagi-services", "✅ Genome exported successfully (flat format v3.0)");
549        Ok(json_str)
550    }
551
552    async fn get_genome_info(&self) -> ServiceResult<GenomeInfo> {
553        trace!(target: "feagi-services", "Getting genome info");
554
555        // CRITICAL: Minimize lock scope - drop lock immediately after reading values
556        let (cortical_area_count, brain_region_count) = {
557            let manager = self.connectome.read();
558            let cortical_area_count = manager.get_cortical_area_count();
559            let brain_region_ids = manager.get_brain_region_ids();
560            let brain_region_count = brain_region_ids.len();
561            trace!(
562                target: "feagi-services",
563                "Reading genome info: {} cortical areas, {} brain regions",
564                cortical_area_count,
565                brain_region_count
566            );
567            trace!(
568                target: "feagi-services",
569                "Brain region IDs: {:?}",
570                brain_region_ids.iter().take(10).collect::<Vec<_>>()
571            );
572            (cortical_area_count, brain_region_count)
573        }; // Lock dropped here
574
575        // Get simulation_timestep from stored genome if available
576        let simulation_timestep = {
577            let genome_opt = self.current_genome.read();
578            genome_opt
579                .as_ref()
580                .map(|g| g.physiology.simulation_timestep)
581                .unwrap_or(0.025) // Default if no genome loaded
582        };
583
584        // Get actual genome load counter and timestamp
585        let genome_num = {
586            let counter = self.genome_load_counter.read();
587            if *counter > 0 {
588                Some(*counter)
589            } else {
590                None // No genome loaded yet
591            }
592        };
593
594        let genome_timestamp = *self.genome_load_timestamp.read();
595
596        Ok(GenomeInfo {
597            genome_id: "current".to_string(),
598            genome_title: "Current Genome".to_string(),
599            version: "2.1".to_string(),
600            cortical_area_count,
601            brain_region_count,
602            simulation_timestep,
603            genome_num,
604            genome_timestamp,
605        })
606    }
607
608    async fn validate_genome(&self, json_str: String) -> ServiceResult<bool> {
609        trace!(target: "feagi-services", "Validating genome JSON");
610
611        // Parse genome
612        let genome = feagi_evolutionary::load_genome_from_json(&json_str)
613            .map_err(|e| ServiceError::InvalidInput(format!("Failed to parse genome: {}", e)))?;
614
615        // Validate genome structure
616        let validation = feagi_evolutionary::validate_genome(&genome);
617
618        if !validation.errors.is_empty() {
619            return Err(ServiceError::InvalidInput(format!(
620                "Genome validation failed: {} errors, {} warnings. First error: {}",
621                validation.errors.len(),
622                validation.warnings.len(),
623                validation
624                    .errors
625                    .first()
626                    .unwrap_or(&"Unknown error".to_string())
627            )));
628        }
629
630        Ok(true)
631    }
632
633    async fn reset_connectome(&self) -> ServiceResult<()> {
634        info!(target: "feagi-services", "Resetting connectome");
635
636        // Use ConnectomeManager's prepare_for_new_genome method
637        self.connectome
638            .write()
639            .prepare_for_new_genome()
640            .map_err(ServiceError::from)?;
641
642        info!(target: "feagi-services", "Connectome reset complete");
643        Ok(())
644    }
645
646    async fn create_cortical_areas(
647        &self,
648        params: Vec<CreateCorticalAreaParams>,
649    ) -> ServiceResult<Vec<CorticalAreaInfo>> {
650        info!(target: "feagi-services", "Creating {} new cortical areas via GenomeService", params.len());
651
652        // Step 1: Build CorticalArea structures
653        let mut areas_to_add = Vec::new();
654        for param in &params {
655            // Convert String to CorticalID
656            let cortical_id_typed = CorticalID::try_from_base_64(&param.cortical_id)
657                .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
658
659            // Get cortical area type from the cortical ID
660            let area_type = cortical_id_typed.as_cortical_type().map_err(|e| {
661                ServiceError::InvalidInput(format!("Failed to determine cortical area type: {}", e))
662            })?;
663
664            // Create CorticalArea
665            let mut area = CorticalArea::new(
666                cortical_id_typed,
667                0, // Auto-assigned by ConnectomeManager
668                param.name.clone(),
669                CorticalAreaDimensions::new(
670                    param.dimensions.0 as u32,
671                    param.dimensions.1 as u32,
672                    param.dimensions.2 as u32,
673                )?,
674                param.position.into(), // Convert (i32, i32, i32) to GenomeCoordinate3D
675                area_type,
676            )?;
677
678            // Apply all neural parameters
679            if let Some(visible) = param.visible {
680                area.add_property_mut("visible".to_string(), serde_json::json!(visible));
681            }
682            if let Some(sub_group) = &param.sub_group {
683                area.add_property_mut("sub_group".to_string(), serde_json::json!(sub_group));
684            }
685            if let Some(neurons_per_voxel) = param.neurons_per_voxel {
686                area.add_property_mut(
687                    "neurons_per_voxel".to_string(),
688                    serde_json::json!(neurons_per_voxel),
689                );
690            }
691            if let Some(postsynaptic_current) = param.postsynaptic_current {
692                area.add_property_mut(
693                    "postsynaptic_current".to_string(),
694                    serde_json::json!(postsynaptic_current),
695                );
696            }
697            if let Some(plasticity_constant) = param.plasticity_constant {
698                area.add_property_mut(
699                    "plasticity_constant".to_string(),
700                    serde_json::json!(plasticity_constant),
701                );
702            }
703            if let Some(degeneration) = param.degeneration {
704                area.add_property_mut("degeneration".to_string(), serde_json::json!(degeneration));
705            }
706            if let Some(psp_uniform_distribution) = param.psp_uniform_distribution {
707                area.add_property_mut(
708                    "psp_uniform_distribution".to_string(),
709                    serde_json::json!(psp_uniform_distribution),
710                );
711            }
712            if let Some(firing_threshold_increment) = param.firing_threshold_increment {
713                area.add_property_mut(
714                    "firing_threshold_increment".to_string(),
715                    serde_json::json!(firing_threshold_increment),
716                );
717            }
718            if let Some(firing_threshold_limit) = param.firing_threshold_limit {
719                area.add_property_mut(
720                    "firing_threshold_limit".to_string(),
721                    serde_json::json!(firing_threshold_limit),
722                );
723            }
724            if let Some(consecutive_fire_count) = param.consecutive_fire_count {
725                area.add_property_mut(
726                    "consecutive_fire_limit".to_string(),
727                    serde_json::json!(consecutive_fire_count),
728                );
729            }
730            if let Some(snooze_period) = param.snooze_period {
731                area.add_property_mut(
732                    "snooze_period".to_string(),
733                    serde_json::json!(snooze_period),
734                );
735            }
736            if let Some(refractory_period) = param.refractory_period {
737                area.add_property_mut(
738                    "refractory_period".to_string(),
739                    serde_json::json!(refractory_period),
740                );
741            }
742            if let Some(leak_coefficient) = param.leak_coefficient {
743                area.add_property_mut(
744                    "leak_coefficient".to_string(),
745                    serde_json::json!(leak_coefficient),
746                );
747            }
748            if let Some(leak_variability) = param.leak_variability {
749                area.add_property_mut(
750                    "leak_variability".to_string(),
751                    serde_json::json!(leak_variability),
752                );
753            }
754            if let Some(burst_engine_active) = param.burst_engine_active {
755                area.add_property_mut(
756                    "burst_engine_active".to_string(),
757                    serde_json::json!(burst_engine_active),
758                );
759            }
760            if matches!(area_type, CorticalAreaType::Memory(_)) {
761                let merged = merge_memory_area_properties(
762                    area.properties.clone(),
763                    param.properties.as_ref(),
764                );
765                area.properties = merged;
766            } else if let Some(properties) = &param.properties {
767                area.properties = properties.clone();
768            }
769
770            areas_to_add.push(area);
771        }
772
773        // Step 2: Add to runtime genome (source of truth)
774        {
775            let mut genome_lock = self.current_genome.write();
776            if let Some(ref mut genome) = *genome_lock {
777                for area in &areas_to_add {
778                    genome.cortical_areas.insert(area.cortical_id, area.clone());
779                    info!(target: "feagi-services", "Added {} to runtime genome", area.cortical_id.as_base_64());
780                    if let Some(parent) = area
781                        .properties
782                        .get("parent_region_id")
783                        .and_then(|v| v.as_str())
784                    {
785                        let region = genome.brain_regions.get_mut(parent).ok_or_else(|| {
786                            ServiceError::InvalidInput(format!(
787                                "Unknown parent_region_id '{}' for new cortical area {}",
788                                parent,
789                                area.cortical_id.as_base_64()
790                            ))
791                        })?;
792                        region.cortical_areas.insert(area.cortical_id);
793                    }
794                }
795            } else {
796                return Err(ServiceError::Backend("No genome loaded".to_string()));
797            }
798        }
799
800        // Step 3: Get genome for neuroembryogenesis context
801        let genome_clone = {
802            let genome_lock = self.current_genome.read();
803            genome_lock
804                .as_ref()
805                .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?
806                .clone()
807        };
808
809        // Step 4: Call neuroembryogenesis to create structures, neurons, and synapses
810        let (neurons_created, synapses_created) = {
811            let connectome_clone = self.connectome.clone();
812            tokio::task::spawn_blocking(move || {
813                let mut neuro = Neuroembryogenesis::new(connectome_clone);
814                neuro.add_cortical_areas(areas_to_add.clone(), &genome_clone)
815            })
816            .await
817            .map_err(|e| ServiceError::Backend(format!("Neuroembryogenesis task failed: {}", e)))?
818            .map_err(|e| ServiceError::Backend(format!("Neuroembryogenesis failed: {}", e)))?
819        };
820
821        info!(target: "feagi-services",
822              "✅ Created {} cortical areas: {} neurons, {} synapses",
823              params.len(), neurons_created, synapses_created);
824
825        // Refresh burst runner cache after creating areas
826        self.refresh_burst_runner_cache();
827
828        // Step 5: Fetch and return area information
829        let mut created_areas = Vec::new();
830        for param in &params {
831            match self.get_cortical_area_info(&param.cortical_id).await {
832                Ok(area_info) => created_areas.push(area_info),
833                Err(e) => {
834                    warn!(target: "feagi-services", "Created area {} but failed to fetch info: {}", param.cortical_id, e);
835                    return Err(ServiceError::Backend(format!(
836                        "Created areas but failed to fetch info for {}: {}",
837                        param.cortical_id, e
838                    )));
839                }
840            }
841        }
842
843        Ok(created_areas)
844    }
845
846    async fn update_cortical_area(
847        &self,
848        cortical_id: &str,
849        changes: HashMap<String, Value>,
850    ) -> ServiceResult<CorticalAreaInfo> {
851        info!(target: "feagi-services", "Updating cortical area: {} with {} changes", cortical_id, changes.len());
852
853        // Convert String to CorticalID (supports legacy core aliases)
854        let cortical_id_typed = feagi_evolutionary::string_to_cortical_id(cortical_id)
855            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
856
857        let mut changes = changes;
858        let mut effective_cortical_id = cortical_id_typed;
859        let mut effective_cortical_id_str = cortical_id.to_string();
860
861        // Verify cortical area exists.
862        // If not found, best-effort resolve IO cortical IDs that were recently remapped
863        // by coding changes (Absolute/Incremental, Linear/Fractional), where bytes 4-5
864        // change but the unit identifier + group/subunit stay the same.
865        {
866            let manager = self.connectome.read();
867            if !manager.has_cortical_area(&effective_cortical_id) {
868                let target_bytes = effective_cortical_id.as_bytes();
869                let mut candidates: Vec<_> = manager
870                    .get_cortical_area_ids()
871                    .iter()
872                    .map(|c| **c)
873                    .filter(|candidate| {
874                        let c = candidate.as_bytes();
875                        c[0] == target_bytes[0] // i/o namespace
876                            && c[1] == target_bytes[1]
877                            && c[2] == target_bytes[2]
878                            && c[3] == target_bytes[3] // cortical unit identifier
879                            && c[6] == target_bytes[6] // sub-unit index
880                            && c[7] == target_bytes[7] // unit(group) index
881                    })
882                    .collect();
883                if candidates.len() == 1 {
884                    let resolved = candidates.remove(0);
885                    info!(
886                        target: "feagi-services",
887                        "[GENOME-UPDATE] Resolved remapped cortical ID '{}' -> '{}'",
888                        cortical_id,
889                        resolved.as_base_64()
890                    );
891                    effective_cortical_id = resolved;
892                    effective_cortical_id_str = resolved.as_base_64();
893                } else {
894                    return Err(ServiceError::NotFound {
895                        resource: "CorticalArea".to_string(),
896                        id: cortical_id.to_string(),
897                    });
898                }
899            }
900        }
901
902        if changes.contains_key("group_id") {
903            let new_id = self.apply_unit_index_update(
904                &effective_cortical_id,
905                &effective_cortical_id_str,
906                &changes,
907            )?;
908            effective_cortical_id = new_id;
909            effective_cortical_id_str = new_id.as_base_64();
910            self.regenerate_mappings_for_area(&effective_cortical_id)?;
911            changes.remove("group_id");
912            if changes.is_empty() {
913                return self
914                    .get_cortical_area_info(&effective_cortical_id_str)
915                    .await;
916            }
917        }
918
919        // Classify changes for intelligent routing
920        let change_type = CorticalChangeClassifier::classify_changes(&changes);
921        CorticalChangeClassifier::log_classification_result(&changes, change_type);
922
923        // Route based on change type
924        match change_type {
925            ChangeType::Parameter => {
926                // Fast path: Direct neuron updates (~2-5ms, NO synapse rebuild)
927                self.update_parameters_only(&effective_cortical_id_str, changes)
928                    .await
929            }
930            ChangeType::Metadata => {
931                // Fastest path: Metadata updates only (~1ms)
932                self.update_metadata_only(&effective_cortical_id_str, changes)
933                    .await
934            }
935            ChangeType::Structural => {
936                // Structural path: Requires synapse rebuild (~100-200ms)
937                self.update_with_localized_rebuild(&effective_cortical_id_str, changes)
938                    .await
939            }
940            ChangeType::Hybrid => {
941                // Hybrid path: Handle each type separately
942                let separated = CorticalChangeClassifier::separate_changes_by_type(&changes);
943
944                // Process in order: metadata first, then parameters, then structural
945                if let Some(metadata_changes) = separated.get(&ChangeType::Metadata) {
946                    if !metadata_changes.is_empty() {
947                        self.update_metadata_only(
948                            &effective_cortical_id_str,
949                            metadata_changes.clone(),
950                        )
951                        .await?;
952                    }
953                }
954
955                if let Some(param_changes) = separated.get(&ChangeType::Parameter) {
956                    if !param_changes.is_empty() {
957                        self.update_parameters_only(
958                            &effective_cortical_id_str,
959                            param_changes.clone(),
960                        )
961                        .await?;
962                    }
963                }
964
965                if let Some(struct_changes) = separated.get(&ChangeType::Structural) {
966                    if !struct_changes.is_empty() {
967                        self.update_with_localized_rebuild(
968                            &effective_cortical_id_str,
969                            struct_changes.clone(),
970                        )
971                        .await?;
972                    }
973                }
974
975                // Return updated info
976                self.get_cortical_area_info(&effective_cortical_id_str)
977                    .await
978            }
979        }
980    }
981}
982
983impl GenomeServiceImpl {
984    /// Fast path: Update only neuron parameters without synapse rebuild
985    ///
986    /// Performance: ~1-2µs to queue (non-blocking), applied in next burst cycle
987    async fn update_parameters_only(
988        &self,
989        cortical_id: &str,
990        changes: HashMap<String, Value>,
991    ) -> ServiceResult<CorticalAreaInfo> {
992        info!(target: "feagi-services", "[FAST-UPDATE] Parameter-only update for {}", cortical_id);
993
994        // Convert String to CorticalID
995        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
996            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
997        // Get cortical index for NPU updates
998        let cortical_idx = {
999            let manager = self.connectome.read();
1000            manager
1001                .get_cortical_idx(&cortical_id_typed)
1002                .ok_or_else(|| ServiceError::NotFound {
1003                    resource: "CorticalArea".to_string(),
1004                    id: cortical_id.to_string(),
1005                })?
1006        };
1007
1008        // Queue parameter updates for burst loop to consume (non-blocking!)
1009        if let Some(queue) = &self.parameter_queue {
1010            // Get base threshold for spatial gradient updates
1011            let base_threshold = {
1012                let manager = self.connectome.read();
1013                manager
1014                    .get_cortical_area(&cortical_id_typed)
1015                    .map(|area| area.firing_threshold())
1016            };
1017
1018            for (param_name, value) in &changes {
1019                // Only queue parameters that affect NPU neurons
1020                let classifier = CorticalChangeClassifier::parameter_changes();
1021                if classifier.contains(param_name.as_str()) {
1022                    // Include base threshold for spatial gradient updates
1023                    let bt = if param_name == "neuron_fire_threshold_increment"
1024                        || param_name == "firing_threshold_increment"
1025                    {
1026                        base_threshold
1027                    } else {
1028                        None
1029                    };
1030
1031                    queue.push(feagi_npu_burst_engine::ParameterUpdate {
1032                        cortical_idx,
1033                        cortical_id: cortical_id.to_string(),
1034                        parameter_name: param_name.clone(),
1035                        value: value.clone(),
1036                        dimensions: None, // Not needed anymore - neurons have stored positions
1037                        neurons_per_voxel: None,
1038                        base_threshold: bt,
1039                    });
1040                    trace!(
1041                        target: "feagi-services",
1042                        "[PARAM-QUEUE] Queued {}={} for area {}",
1043                        param_name,
1044                        value,
1045                        cortical_id
1046                    );
1047                }
1048            }
1049            info!(target: "feagi-services", "[FAST-UPDATE] Queued parameter updates (will apply in next burst)");
1050        } else {
1051            warn!(target: "feagi-services", "Parameter queue not available - updates will not affect neurons");
1052        }
1053
1054        // Persist parameter-only updates into the live ConnectomeManager so API reads (and BV UI)
1055        // reflect the same values that are applied to the NPU.
1056        //
1057        // IMPORTANT: The parameter queue updates runtime neuron state; ConnectomeManager is the
1058        // source-of-truth for cortical-area *reported* properties.
1059        {
1060            let mut manager = self.connectome.write();
1061            if let Some(area) = manager.get_cortical_area_mut(&cortical_id_typed) {
1062                for (key, value) in &changes {
1063                    match key.as_str() {
1064                        // Thresholds
1065                        "firing_threshold" | "neuron_fire_threshold" => {
1066                            if let Some(v) = value.as_f64() {
1067                                area.properties
1068                                    .insert("firing_threshold".to_string(), serde_json::json!(v));
1069                            }
1070                        }
1071                        "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1072                            if let Some(v) = value.as_f64() {
1073                                area.properties.insert(
1074                                    "firing_threshold_limit".to_string(),
1075                                    serde_json::json!(v),
1076                                );
1077                            }
1078                        }
1079                        // Spatial gradient increments
1080                        "firing_threshold_increment_x" => {
1081                            if let Some(v) = value.as_f64() {
1082                                area.properties.insert(
1083                                    "firing_threshold_increment_x".to_string(),
1084                                    serde_json::json!(v),
1085                                );
1086                            }
1087                        }
1088                        "firing_threshold_increment_y" => {
1089                            if let Some(v) = value.as_f64() {
1090                                area.properties.insert(
1091                                    "firing_threshold_increment_y".to_string(),
1092                                    serde_json::json!(v),
1093                                );
1094                            }
1095                        }
1096                        "firing_threshold_increment_z" => {
1097                            if let Some(v) = value.as_f64() {
1098                                area.properties.insert(
1099                                    "firing_threshold_increment_z".to_string(),
1100                                    serde_json::json!(v),
1101                                );
1102                            }
1103                        }
1104                        "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1105                            if let Some(arr) = value.as_array() {
1106                                if arr.len() == 3 {
1107                                    if let (Some(x), Some(y), Some(z)) =
1108                                        (arr[0].as_f64(), arr[1].as_f64(), arr[2].as_f64())
1109                                    {
1110                                        area.properties.insert(
1111                                            "firing_threshold_increment_x".to_string(),
1112                                            serde_json::json!(x),
1113                                        );
1114                                        area.properties.insert(
1115                                            "firing_threshold_increment_y".to_string(),
1116                                            serde_json::json!(y),
1117                                        );
1118                                        area.properties.insert(
1119                                            "firing_threshold_increment_z".to_string(),
1120                                            serde_json::json!(z),
1121                                        );
1122                                    }
1123                                }
1124                            } else if let Some(obj) = value.as_object() {
1125                                if let (Some(x), Some(y), Some(z)) = (
1126                                    obj.get("x").and_then(|v| v.as_f64()),
1127                                    obj.get("y").and_then(|v| v.as_f64()),
1128                                    obj.get("z").and_then(|v| v.as_f64()),
1129                                ) {
1130                                    area.properties.insert(
1131                                        "firing_threshold_increment_x".to_string(),
1132                                        serde_json::json!(x),
1133                                    );
1134                                    area.properties.insert(
1135                                        "firing_threshold_increment_y".to_string(),
1136                                        serde_json::json!(y),
1137                                    );
1138                                    area.properties.insert(
1139                                        "firing_threshold_increment_z".to_string(),
1140                                        serde_json::json!(z),
1141                                    );
1142                                }
1143                            }
1144                        }
1145
1146                        // Timing/decay
1147                        "refractory_period" | "neuron_refractory_period" | "refrac" => {
1148                            if let Some(v) = value.as_u64() {
1149                                area.properties.insert(
1150                                    "refractory_period".to_string(),
1151                                    serde_json::json!(v as u32),
1152                                );
1153                            }
1154                        }
1155                        "leak_coefficient" | "neuron_leak_coefficient" | "leak" => {
1156                            if let Some(v) = value.as_f64() {
1157                                area.properties
1158                                    .insert("leak_coefficient".to_string(), serde_json::json!(v));
1159                            }
1160                        }
1161
1162                        // Burst gating
1163                        "consecutive_fire_cnt_max"
1164                        | "neuron_consecutive_fire_count"
1165                        | "consecutive_fire_count" => {
1166                            if let Some(v) = value.as_u64() {
1167                                // ConnectomeManager getters expect `consecutive_fire_limit`.
1168                                area.properties.insert(
1169                                    "consecutive_fire_limit".to_string(),
1170                                    serde_json::json!(v as u32),
1171                                );
1172                            }
1173                        }
1174                        "snooze_length" | "neuron_snooze_period" | "snooze_period" => {
1175                            if let Some(v) = value.as_u64() {
1176                                // ConnectomeManager getters expect `snooze_period`.
1177                                area.properties.insert(
1178                                    "snooze_period".to_string(),
1179                                    serde_json::json!(v as u32),
1180                                );
1181                            }
1182                        }
1183
1184                        // Excitability (BV uses percent UI but sends 0..=1 to the API)
1185                        "neuron_excitability" | "excitability" => {
1186                            if let Some(v) = value.as_f64() {
1187                                if (0.0..=1.0).contains(&v) {
1188                                    area.properties.insert(
1189                                        "neuron_excitability".to_string(),
1190                                        serde_json::json!(v),
1191                                    );
1192                                } else {
1193                                    warn!(
1194                                        target: "feagi-services",
1195                                        "[FAST-UPDATE] Ignoring neuron_excitability={} for area {} (expected 0..=1)",
1196                                        v,
1197                                        cortical_id
1198                                    );
1199                                }
1200                            }
1201                        }
1202
1203                        // PSP + degeneration + plasticity
1204                        "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1205                            if let Some(v) = value.as_f64() {
1206                                area.properties.insert(
1207                                    "postsynaptic_current".to_string(),
1208                                    serde_json::json!(v),
1209                                );
1210                            }
1211                        }
1212                        "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1213                            if let Some(v) = value.as_f64() {
1214                                area.properties.insert(
1215                                    "postsynaptic_current_max".to_string(),
1216                                    serde_json::json!(v),
1217                                );
1218                            }
1219                        }
1220                        "degeneration" | "neuron_degeneracy_coefficient" => {
1221                            if let Some(v) = value.as_f64() {
1222                                area.properties
1223                                    .insert("degeneration".to_string(), serde_json::json!(v));
1224                            }
1225                        }
1226                        "plasticity_constant" | "neuron_plasticity_constant" => {
1227                            if let Some(v) = value.as_f64() {
1228                                area.properties.insert(
1229                                    "plasticity_constant".to_string(),
1230                                    serde_json::json!(v),
1231                                );
1232                            }
1233                        }
1234                        "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1235                            if let Some(v) = value.as_bool() {
1236                                area.properties.insert(
1237                                    "psp_uniform_distribution".to_string(),
1238                                    serde_json::json!(v),
1239                                );
1240                            }
1241                        }
1242
1243                        // Memory parameters (used by plasticity registration + API display)
1244                        "init_lifespan" | "neuron_init_lifespan" => {
1245                            if let Some(v) = value.as_u64() {
1246                                area.properties.insert(
1247                                    "init_lifespan".to_string(),
1248                                    serde_json::json!(v as u32),
1249                                );
1250                            }
1251                        }
1252                        "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1253                            // Accept integer and float representations.
1254                            if let Some(v) =
1255                                value.as_f64().or_else(|| value.as_u64().map(|u| u as f64))
1256                            {
1257                                area.properties.insert(
1258                                    "lifespan_growth_rate".to_string(),
1259                                    serde_json::json!(v as f32),
1260                                );
1261                            }
1262                        }
1263                        "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1264                            if let Some(v) = value.as_u64() {
1265                                area.properties.insert(
1266                                    "longterm_mem_threshold".to_string(),
1267                                    serde_json::json!(v as u32),
1268                                );
1269                            }
1270                        }
1271                        "temporal_depth" => {
1272                            if let Some(v) = value.as_u64() {
1273                                if v == 0 {
1274                                    warn!(
1275                                        target: "feagi-services",
1276                                        "[FAST-UPDATE] Ignoring temporal_depth=0 for area {} (temporal_depth must be >= 1)",
1277                                        cortical_id
1278                                    );
1279                                } else {
1280                                    area.properties.insert(
1281                                        "temporal_depth".to_string(),
1282                                        serde_json::json!(v as u32),
1283                                    );
1284                                }
1285                            }
1286                        }
1287
1288                        // Membrane potential / runtime flags
1289                        "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1290                            if let Some(v) = value.as_bool() {
1291                                area.properties.insert(
1292                                    "mp_charge_accumulation".to_string(),
1293                                    serde_json::json!(v),
1294                                );
1295                            }
1296                        }
1297                        "mp_driven_psp" | "neuron_mp_driven_psp" => {
1298                            if let Some(v) = value.as_bool() {
1299                                area.properties
1300                                    .insert("mp_driven_psp".to_string(), serde_json::json!(v));
1301                            }
1302                        }
1303
1304                        // Burst engine
1305                        "burst_engine_active" => {
1306                            if let Some(v) = value.as_bool() {
1307                                area.properties.insert(
1308                                    "burst_engine_active".to_string(),
1309                                    serde_json::json!(v),
1310                                );
1311                            }
1312                        }
1313                        "visualization_voxel_granularity" => {
1314                            // Only store if != 1x1x1 (default), delete if set to 1x1x1
1315                            // Handle both integer and float JSON values
1316                            if let Some(arr) = value.as_array() {
1317                                if arr.len() == 3 {
1318                                    // Try to parse as integers (u64) or floats (f64), then convert to u32
1319                                    let x_opt = arr[0]
1320                                        .as_u64()
1321                                        .or_else(|| arr[0].as_f64().map(|f| f as u64));
1322                                    let y_opt = arr[1]
1323                                        .as_u64()
1324                                        .or_else(|| arr[1].as_f64().map(|f| f as u64));
1325                                    let z_opt = arr[2]
1326                                        .as_u64()
1327                                        .or_else(|| arr[2].as_f64().map(|f| f as u64));
1328
1329                                    if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1330                                        let x_u32 = x as u32;
1331                                        let y_u32 = y as u32;
1332                                        let z_u32 = z as u32;
1333
1334                                        // Default is 1x1x1 - only store if different
1335                                        if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
1336                                            // Remove override (return to default)
1337                                            area.properties
1338                                                .remove("visualization_voxel_granularity");
1339                                        } else {
1340                                            // Store override (non-default value) as integer array
1341                                            area.properties.insert(
1342                                                "visualization_voxel_granularity".to_string(),
1343                                                serde_json::json!([x_u32, y_u32, z_u32]),
1344                                            );
1345                                        }
1346                                    }
1347                                }
1348                            }
1349                        }
1350
1351                        _ => {}
1352                    }
1353                }
1354            }
1355            manager.refresh_cortical_area_hashes(true, false);
1356        }
1357
1358        // Update RuntimeGenome if available (CRITICAL for save/load persistence!)
1359        if let Some(genome) = self.current_genome.write().as_mut() {
1360            if let Some(area) = genome.cortical_areas.get_mut(&cortical_id_typed) {
1361                trace!(
1362                    target: "feagi-services",
1363                    "[GENOME-UPDATE] Updating RuntimeGenome for area {}",
1364                    cortical_id
1365                );
1366                for (key, value) in &changes {
1367                    match key.as_str() {
1368                        "neuron_fire_threshold" | "firing_threshold" => {
1369                            if let Some(v) = value.as_f64() {
1370                                area.properties
1371                                    .insert("firing_threshold".to_string(), serde_json::json!(v));
1372                            }
1373                        }
1374                        "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1375                            if let Some(v) = value.as_f64() {
1376                                area.properties.insert(
1377                                    "firing_threshold_limit".to_string(),
1378                                    serde_json::json!(v),
1379                                );
1380                            }
1381                        }
1382                        "firing_threshold_increment_x" => {
1383                            if let Some(v) = value.as_f64() {
1384                                area.properties.insert(
1385                                    "firing_threshold_increment_x".to_string(),
1386                                    serde_json::json!(v),
1387                                );
1388                            }
1389                        }
1390                        "firing_threshold_increment_y" => {
1391                            if let Some(v) = value.as_f64() {
1392                                area.properties.insert(
1393                                    "firing_threshold_increment_y".to_string(),
1394                                    serde_json::json!(v),
1395                                );
1396                            }
1397                        }
1398                        "firing_threshold_increment_z" => {
1399                            if let Some(v) = value.as_f64() {
1400                                area.properties.insert(
1401                                    "firing_threshold_increment_z".to_string(),
1402                                    serde_json::json!(v),
1403                                );
1404                            }
1405                        }
1406                        "leak_coefficient" | "neuron_leak_coefficient" => {
1407                            if let Some(v) = value.as_f64() {
1408                                area.properties
1409                                    .insert("leak_coefficient".to_string(), serde_json::json!(v));
1410                            }
1411                        }
1412                        "leak_variability" | "neuron_leak_variability" => {
1413                            if let Some(v) = value.as_f64() {
1414                                area.properties
1415                                    .insert("leak_variability".to_string(), serde_json::json!(v));
1416                            }
1417                        }
1418                        "refractory_period" | "neuron_refractory_period" => {
1419                            if let Some(v) = value.as_u64() {
1420                                area.properties.insert(
1421                                    "refractory_period".to_string(),
1422                                    serde_json::json!(v as u32),
1423                                );
1424                            }
1425                        }
1426                        "snooze_period" | "neuron_snooze_period" => {
1427                            if let Some(v) = value.as_u64() {
1428                                // Converter expects "snooze_length" not "snooze_period"
1429                                area.properties.insert(
1430                                    "snooze_length".to_string(),
1431                                    serde_json::json!(v as u32),
1432                                );
1433                            }
1434                        }
1435                        "consecutive_fire_count" | "neuron_consecutive_fire_count" => {
1436                            if let Some(v) = value.as_u64() {
1437                                // Converter expects "consecutive_fire_cnt_max" not "consecutive_fire_count"
1438                                area.properties.insert(
1439                                    "consecutive_fire_cnt_max".to_string(),
1440                                    serde_json::json!(v as u32),
1441                                );
1442                            }
1443                        }
1444                        "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1445                            if let Some(v) = value.as_f64() {
1446                                area.properties.insert(
1447                                    "postsynaptic_current".to_string(),
1448                                    serde_json::json!(v),
1449                                );
1450                            }
1451                        }
1452                        "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1453                            if let Some(v) = value.as_f64() {
1454                                area.properties.insert(
1455                                    "postsynaptic_current_max".to_string(),
1456                                    serde_json::json!(v),
1457                                );
1458                            }
1459                        }
1460                        "plasticity_constant" | "neuron_plasticity_constant" => {
1461                            if let Some(v) = value.as_f64() {
1462                                area.properties.insert(
1463                                    "plasticity_constant".to_string(),
1464                                    serde_json::json!(v),
1465                                );
1466                            }
1467                        }
1468                        "degeneration" | "neuron_degeneracy_coefficient" => {
1469                            if let Some(v) = value.as_f64() {
1470                                area.properties
1471                                    .insert("degeneration".to_string(), serde_json::json!(v));
1472                            }
1473                        }
1474                        "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1475                            if let Some(v) = value.as_bool() {
1476                                area.properties.insert(
1477                                    "psp_uniform_distribution".to_string(),
1478                                    serde_json::json!(v),
1479                                );
1480                            }
1481                        }
1482                        "mp_driven_psp" | "neuron_mp_driven_psp" => {
1483                            if let Some(v) = value.as_bool() {
1484                                area.properties
1485                                    .insert("mp_driven_psp".to_string(), serde_json::json!(v));
1486                                info!(
1487                                    target: "feagi-services",
1488                                    "[GENOME-UPDATE] Updated mp_driven_psp={} in RuntimeGenome for area {}",
1489                                    v, cortical_id
1490                                );
1491                            } else {
1492                                warn!(
1493                                    target: "feagi-services",
1494                                    "[GENOME-UPDATE] Failed to update mp_driven_psp: value is not a bool (got {:?})",
1495                                    value
1496                                );
1497                            }
1498                        }
1499                        "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1500                            if let Some(v) = value.as_bool() {
1501                                area.properties.insert(
1502                                    "mp_charge_accumulation".to_string(),
1503                                    serde_json::json!(v),
1504                                );
1505                            }
1506                        }
1507                        "neuron_excitability" => {
1508                            if let Some(v) = value.as_f64() {
1509                                area.properties.insert(
1510                                    "neuron_excitability".to_string(),
1511                                    serde_json::json!(v),
1512                                );
1513                            }
1514                        }
1515                        "init_lifespan" | "neuron_init_lifespan" => {
1516                            if let Some(v) = value.as_u64() {
1517                                area.properties.insert(
1518                                    "init_lifespan".to_string(),
1519                                    serde_json::json!(v as u32),
1520                                );
1521                            }
1522                        }
1523                        "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1524                            if let Some(v) = value.as_u64() {
1525                                area.properties.insert(
1526                                    "lifespan_growth_rate".to_string(),
1527                                    serde_json::json!(v as u32),
1528                                );
1529                            }
1530                        }
1531                        "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1532                            if let Some(v) = value.as_u64() {
1533                                area.properties.insert(
1534                                    "longterm_mem_threshold".to_string(),
1535                                    serde_json::json!(v as u32),
1536                                );
1537                            }
1538                        }
1539                        "temporal_depth" => {
1540                            if let Some(v) = value.as_u64() {
1541                                if v == 0 {
1542                                    warn!(
1543                                        target: "feagi-services",
1544                                        "[GENOME-UPDATE] Ignoring temporal_depth=0 for area {} (temporal_depth must be >= 1)",
1545                                        cortical_id
1546                                    );
1547                                } else {
1548                                    area.properties.insert(
1549                                        "temporal_depth".to_string(),
1550                                        serde_json::json!(v as u32),
1551                                    );
1552                                }
1553                            }
1554                        }
1555                        "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1556                            // Converter expects separate x, y, z properties, not an array
1557                            if let Some(arr) = value.as_array() {
1558                                if arr.len() == 3 {
1559                                    if let (Some(x), Some(y), Some(z)) =
1560                                        (arr[0].as_f64(), arr[1].as_f64(), arr[2].as_f64())
1561                                    {
1562                                        area.properties.insert(
1563                                            "firing_threshold_increment_x".to_string(),
1564                                            serde_json::json!(x),
1565                                        );
1566                                        area.properties.insert(
1567                                            "firing_threshold_increment_y".to_string(),
1568                                            serde_json::json!(y),
1569                                        );
1570                                        area.properties.insert(
1571                                            "firing_threshold_increment_z".to_string(),
1572                                            serde_json::json!(z),
1573                                        );
1574                                    }
1575                                }
1576                            } else if let Some(obj) = value.as_object() {
1577                                // Convert {x, y, z} to separate properties
1578                                if let (Some(x), Some(y), Some(z)) = (
1579                                    obj.get("x").and_then(|v| v.as_f64()),
1580                                    obj.get("y").and_then(|v| v.as_f64()),
1581                                    obj.get("z").and_then(|v| v.as_f64()),
1582                                ) {
1583                                    area.properties.insert(
1584                                        "firing_threshold_increment_x".to_string(),
1585                                        serde_json::json!(x),
1586                                    );
1587                                    area.properties.insert(
1588                                        "firing_threshold_increment_y".to_string(),
1589                                        serde_json::json!(y),
1590                                    );
1591                                    area.properties.insert(
1592                                        "firing_threshold_increment_z".to_string(),
1593                                        serde_json::json!(z),
1594                                    );
1595                                }
1596                            }
1597                        }
1598                        "burst_engine_active" => {
1599                            if let Some(v) = value.as_bool() {
1600                                area.properties.insert(
1601                                    "burst_engine_active".to_string(),
1602                                    serde_json::json!(v),
1603                                );
1604                            }
1605                        }
1606                        "visualization_voxel_granularity" => {
1607                            // Only store if != 1x1x1 (default), delete if set to 1x1x1
1608                            // Handle both integer and float JSON values
1609                            if let Some(arr) = value.as_array() {
1610                                if arr.len() == 3 {
1611                                    // Try to parse as integers (u64) or floats (f64), then convert to u32
1612                                    let x_opt = arr[0]
1613                                        .as_u64()
1614                                        .or_else(|| arr[0].as_f64().map(|f| f as u64));
1615                                    let y_opt = arr[1]
1616                                        .as_u64()
1617                                        .or_else(|| arr[1].as_f64().map(|f| f as u64));
1618                                    let z_opt = arr[2]
1619                                        .as_u64()
1620                                        .or_else(|| arr[2].as_f64().map(|f| f as u64));
1621
1622                                    if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
1623                                        let x_u32 = x as u32;
1624                                        let y_u32 = y as u32;
1625                                        let z_u32 = z as u32;
1626
1627                                        // Default is 1x1x1 - only store if different
1628                                        if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
1629                                            // Remove override (return to default)
1630                                            area.properties
1631                                                .remove("visualization_voxel_granularity");
1632                                        } else {
1633                                            // Store override (non-default value) as integer array
1634                                            area.properties.insert(
1635                                                "visualization_voxel_granularity".to_string(),
1636                                                serde_json::json!([x_u32, y_u32, z_u32]),
1637                                            );
1638                                        }
1639                                    }
1640                                }
1641                            }
1642                        }
1643                        _ => {}
1644                    }
1645                }
1646            } else {
1647                warn!(
1648                    target: "feagi-services",
1649                    "[GENOME-UPDATE] WARNING: Cortical area {} not found in RuntimeGenome - property updates will not persist to saved genome!",
1650                    cortical_id
1651                );
1652            }
1653        } else {
1654            warn!(
1655                target: "feagi-services",
1656                "[GENOME-UPDATE] WARNING: No RuntimeGenome loaded - property updates will not persist to saved genome!"
1657            );
1658        }
1659
1660        // Update ConnectomeManager metadata for consistency
1661        {
1662            let mut manager = self.connectome.write();
1663            let area = manager
1664                .get_cortical_area_mut(&cortical_id_typed)
1665                .ok_or_else(|| ServiceError::NotFound {
1666                    resource: "CorticalArea".to_string(),
1667                    id: cortical_id.to_string(),
1668                })?;
1669
1670            // Update BDU metadata fields
1671            for (key, value) in &changes {
1672                match key.as_str() {
1673                    "neuron_fire_threshold" | "firing_threshold" => {
1674                        if let Some(v) = value.as_f64() {
1675                            area.add_property_mut(
1676                                "firing_threshold".to_string(),
1677                                serde_json::json!(v),
1678                            );
1679                        }
1680                    }
1681                    "firing_threshold_limit" | "neuron_firing_threshold_limit" => {
1682                        if let Some(v) = value.as_f64() {
1683                            area.add_property_mut(
1684                                "firing_threshold_limit".to_string(),
1685                                serde_json::json!(v),
1686                            );
1687                        }
1688                    }
1689                    "leak_coefficient" | "neuron_leak_coefficient" => {
1690                        if let Some(v) = value.as_f64() {
1691                            area.add_property_mut(
1692                                "leak_coefficient".to_string(),
1693                                serde_json::json!(v),
1694                            );
1695                        }
1696                    }
1697                    "leak_variability" | "neuron_leak_variability" => {
1698                        if let Some(v) = value.as_f64() {
1699                            area.add_property_mut(
1700                                "leak_variability".to_string(),
1701                                serde_json::json!(v),
1702                            );
1703                        }
1704                    }
1705                    "refractory_period" | "neuron_refractory_period" => {
1706                        if let Some(v) = value.as_u64() {
1707                            area.add_property_mut(
1708                                "refractory_period".to_string(),
1709                                serde_json::json!(v as u32),
1710                            );
1711                        }
1712                    }
1713                    "snooze_period" | "neuron_snooze_period" => {
1714                        if let Some(v) = value.as_u64() {
1715                            area.add_property_mut(
1716                                "snooze_period".to_string(),
1717                                serde_json::json!(v as u32),
1718                            );
1719                        }
1720                    }
1721                    "consecutive_fire_count" | "neuron_consecutive_fire_count" => {
1722                        if let Some(v) = value.as_u64() {
1723                            area.add_property_mut(
1724                                "consecutive_fire_limit".to_string(),
1725                                serde_json::json!(v as u32),
1726                            );
1727                        }
1728                    }
1729                    "plasticity_constant" | "neuron_plasticity_constant" => {
1730                        if let Some(v) = value.as_f64() {
1731                            area.add_property_mut(
1732                                "plasticity_constant".to_string(),
1733                                serde_json::json!(v),
1734                            );
1735                        }
1736                    }
1737                    "degeneration" | "neuron_degeneracy_coefficient" => {
1738                        if let Some(v) = value.as_f64() {
1739                            area.add_property_mut("degeneration".to_string(), serde_json::json!(v));
1740                        }
1741                    }
1742                    "postsynaptic_current" | "neuron_post_synaptic_potential" => {
1743                        if let Some(v) = value.as_f64() {
1744                            area.add_property_mut(
1745                                "postsynaptic_current".to_string(),
1746                                serde_json::json!(v),
1747                            );
1748                        }
1749                    }
1750                    "postsynaptic_current_max" | "neuron_post_synaptic_potential_max" => {
1751                        if let Some(v) = value.as_f64() {
1752                            area.add_property_mut(
1753                                "postsynaptic_current_max".to_string(),
1754                                serde_json::json!(v),
1755                            );
1756                        }
1757                    }
1758                    "psp_uniform_distribution" | "neuron_psp_uniform_distribution" => {
1759                        if let Some(v) = value.as_bool() {
1760                            area.add_property_mut(
1761                                "psp_uniform_distribution".to_string(),
1762                                serde_json::json!(v),
1763                            );
1764                        }
1765                    }
1766                    "mp_driven_psp" | "neuron_mp_driven_psp" => {
1767                        if let Some(v) = value.as_bool() {
1768                            area.add_property_mut(
1769                                "mp_driven_psp".to_string(),
1770                                serde_json::json!(v),
1771                            );
1772                            info!(
1773                                target: "feagi-services",
1774                                "[CONNECTOME-UPDATE] Updated mp_driven_psp={} in ConnectomeManager for area {}",
1775                                v, cortical_id
1776                            );
1777                        }
1778                    }
1779                    "mp_charge_accumulation" | "neuron_mp_charge_accumulation" => {
1780                        if let Some(v) = value.as_bool() {
1781                            area.add_property_mut(
1782                                "mp_charge_accumulation".to_string(),
1783                                serde_json::json!(v),
1784                            );
1785                        }
1786                    }
1787                    "excitability" | "neuron_excitability" => {
1788                        if let Some(v) = value.as_f64() {
1789                            area.add_property_mut("excitability".to_string(), serde_json::json!(v));
1790                        }
1791                    }
1792                    "init_lifespan" | "neuron_init_lifespan" => {
1793                        if let Some(v) = value.as_u64() {
1794                            area.add_property_mut(
1795                                "init_lifespan".to_string(),
1796                                serde_json::json!(v as u32),
1797                            );
1798                        }
1799                    }
1800                    "lifespan_growth_rate" | "neuron_lifespan_growth_rate" => {
1801                        if let Some(v) = value.as_u64() {
1802                            area.add_property_mut(
1803                                "lifespan_growth_rate".to_string(),
1804                                serde_json::json!(v as u32),
1805                            );
1806                        }
1807                    }
1808                    "longterm_mem_threshold" | "neuron_longterm_mem_threshold" => {
1809                        if let Some(v) = value.as_u64() {
1810                            area.add_property_mut(
1811                                "longterm_mem_threshold".to_string(),
1812                                serde_json::json!(v as u32),
1813                            );
1814                        }
1815                    }
1816                    "firing_threshold_increment" | "neuron_fire_threshold_increment" => {
1817                        // Expect either array [x, y, z] or dict {x, y, z}
1818                        if let Some(arr) = value.as_array() {
1819                            if arr.len() == 3 {
1820                                let x = arr[0].as_f64().unwrap_or(0.0);
1821                                let y = arr[1].as_f64().unwrap_or(0.0);
1822                                let z = arr[2].as_f64().unwrap_or(0.0);
1823
1824                                // Store both array format and individual x/y/z properties
1825                                area.add_property_mut(
1826                                    "firing_threshold_increment".to_string(),
1827                                    serde_json::json!(arr),
1828                                );
1829                                area.add_property_mut(
1830                                    "firing_threshold_increment_x".to_string(),
1831                                    serde_json::json!(x),
1832                                );
1833                                area.add_property_mut(
1834                                    "firing_threshold_increment_y".to_string(),
1835                                    serde_json::json!(y),
1836                                );
1837                                area.add_property_mut(
1838                                    "firing_threshold_increment_z".to_string(),
1839                                    serde_json::json!(z),
1840                                );
1841                            }
1842                        } else if let Some(obj) = value.as_object() {
1843                            // Convert {x, y, z} to individual properties
1844                            let x = obj.get("x").and_then(|v| v.as_f64()).unwrap_or(0.0);
1845                            let y = obj.get("y").and_then(|v| v.as_f64()).unwrap_or(0.0);
1846                            let z = obj.get("z").and_then(|v| v.as_f64()).unwrap_or(0.0);
1847
1848                            area.add_property_mut(
1849                                "firing_threshold_increment".to_string(),
1850                                serde_json::json!([x, y, z]),
1851                            );
1852                            area.add_property_mut(
1853                                "firing_threshold_increment_x".to_string(),
1854                                serde_json::json!(x),
1855                            );
1856                            area.add_property_mut(
1857                                "firing_threshold_increment_y".to_string(),
1858                                serde_json::json!(y),
1859                            );
1860                            area.add_property_mut(
1861                                "firing_threshold_increment_z".to_string(),
1862                                serde_json::json!(z),
1863                            );
1864                        }
1865                    }
1866                    "burst_engine_active" => {
1867                        if let Some(v) = value.as_bool() {
1868                            area.add_property_mut(
1869                                "burst_engine_active".to_string(),
1870                                serde_json::json!(v),
1871                            );
1872                        }
1873                    }
1874                    _ => {}
1875                }
1876            }
1877            manager.refresh_cortical_area_hashes(true, false);
1878        }
1879
1880        // If memory-related parameters were updated, immediately apply them to the runtime
1881        // plasticity subsystem (and FireLedger tracking), otherwise changes only take effect after save+reload.
1882        //
1883        // BV behavior observed:
1884        // - Update via API updates RuntimeGenome + ConnectomeManager
1885        // - PlasticityService keeps running with the old temporal_depth/lifecycle config until genome reload
1886        //
1887        // This block re-registers the memory area configuration in the live executor.
1888        #[cfg(feature = "plasticity")]
1889        {
1890            let memory_param_changed = changes.keys().any(|k| {
1891                matches!(
1892                    k.as_str(),
1893                    "init_lifespan"
1894                        | "neuron_init_lifespan"
1895                        | "lifespan_growth_rate"
1896                        | "neuron_lifespan_growth_rate"
1897                        | "longterm_mem_threshold"
1898                        | "neuron_longterm_mem_threshold"
1899                        | "temporal_depth"
1900                )
1901            });
1902
1903            if memory_param_changed {
1904                use feagi_evolutionary::extract_memory_properties;
1905                use feagi_npu_plasticity::{MemoryNeuronLifecycleConfig, PlasticityExecutor};
1906
1907                let mut manager = self.connectome.write();
1908                if let Some(area) = manager.get_cortical_area(&cortical_id_typed) {
1909                    if let Some(mem_props) = extract_memory_properties(&area.properties) {
1910                        // Ensure upstream tracking is consistent with current mappings before re-registering.
1911                        let _ = manager
1912                            .refresh_upstream_cortical_areas_from_mappings(&cortical_id_typed);
1913
1914                        // Update FireLedger upstream tracking for this memory area (monotonic-increase).
1915                        // Note: FireLedger track_area is an *exact* setting; this uses max(existing, desired)
1916                        // to avoid shrinking windows that may be required elsewhere (e.g., other memory areas).
1917                        if let Some(npu_arc) = manager.get_npu().cloned() {
1918                            if let Ok(mut npu) = npu_arc.lock() {
1919                                let upstream_areas =
1920                                    manager.get_upstream_cortical_areas(&cortical_id_typed);
1921                                let existing_configs = npu.get_all_fire_ledger_configs();
1922                                let desired = mem_props.temporal_depth as usize;
1923
1924                                for upstream_idx in upstream_areas.iter().copied() {
1925                                    let existing = existing_configs
1926                                        .iter()
1927                                        .find(|(idx, _)| *idx == upstream_idx)
1928                                        .map(|(_, w)| *w)
1929                                        .unwrap_or(0);
1930                                    let resolved = existing.max(desired);
1931                                    if resolved != existing {
1932                                        if let Err(e) =
1933                                            npu.configure_fire_ledger_window(upstream_idx, resolved)
1934                                        {
1935                                            warn!(
1936                                                target: "feagi-services",
1937                                                "[GENOME-UPDATE] Failed to configure FireLedger window for upstream idx={} (requested={}): {}",
1938                                                upstream_idx,
1939                                                resolved,
1940                                                e
1941                                            );
1942                                        }
1943                                    }
1944                                }
1945                            } else {
1946                                warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to lock NPU for FireLedger update");
1947                            }
1948                        }
1949
1950                        // Re-register memory area config in PlasticityExecutor so temporal_depth/lifecycle changes apply immediately.
1951                        if let Some(executor) = manager.get_plasticity_executor() {
1952                            if let Ok(exec) = executor.lock() {
1953                                let upstream_areas =
1954                                    manager.get_upstream_cortical_areas(&cortical_id_typed);
1955                                let upstream_non_memory =
1956                                    manager.filter_non_memory_upstream_areas(&upstream_areas);
1957                                let lifecycle_config = MemoryNeuronLifecycleConfig {
1958                                    initial_lifespan: mem_props.init_lifespan,
1959                                    lifespan_growth_rate: mem_props.lifespan_growth_rate,
1960                                    longterm_threshold: mem_props.longterm_threshold,
1961                                    max_reactivations: 1000,
1962                                };
1963
1964                                exec.register_memory_area(
1965                                    cortical_idx,
1966                                    cortical_id.to_string(),
1967                                    mem_props.temporal_depth,
1968                                    upstream_non_memory,
1969                                    Some(lifecycle_config),
1970                                );
1971                            } else {
1972                                warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to lock PlasticityExecutor for memory-area update");
1973                            }
1974                        }
1975                    }
1976                }
1977            }
1978        }
1979
1980        info!(target: "feagi-services", "[FAST-UPDATE] Parameter update complete");
1981
1982        // Return updated info
1983        self.get_cortical_area_info(cortical_id).await
1984    }
1985
1986    /// Update IO coding parameters and remap the cortical ID.
1987    ///
1988    /// This updates both RuntimeGenome and ConnectomeManager, preserving cortical_idx.
1989    fn apply_io_coding_update(
1990        &self,
1991        cortical_id: &CorticalID,
1992        cortical_id_str: &str,
1993        changes: &HashMap<String, Value>,
1994    ) -> ServiceResult<CorticalID> {
1995        let new_cortical_id_str = changes
1996            .get("new_cortical_id")
1997            .and_then(|v| v.as_str())
1998            .ok_or_else(|| {
1999                ServiceError::InvalidInput(
2000                    "new_cortical_id is required when updating IO coding parameters".to_string(),
2001                )
2002            })?;
2003        let _new_cortical_id = CorticalID::try_from_base_64(new_cortical_id_str).map_err(|e| {
2004            ServiceError::InvalidInput(format!(
2005                "Invalid new_cortical_id '{}': {}",
2006                new_cortical_id_str, e
2007            ))
2008        })?;
2009
2010        let bytes = cortical_id.as_bytes();
2011        let is_input = bytes[0] == b'i';
2012        let is_output = bytes[0] == b'o';
2013        if !is_input && !is_output {
2014            return Err(ServiceError::InvalidInput(
2015                "IO coding updates only apply to IPU/OPU cortical areas".to_string(),
2016            ));
2017        }
2018
2019        let current_flag = cortical_id.extract_io_data_flag().map_err(|e| {
2020            ServiceError::InvalidInput(format!(
2021                "Unable to decode IO configuration from cortical ID '{}': {}",
2022                cortical_id_str, e
2023            ))
2024        })?;
2025
2026        let parse_frame = |raw: &str| -> Option<FrameChangeHandling> {
2027            let lower = raw.trim().to_ascii_lowercase();
2028            match lower.as_str() {
2029                "absolute" => Some(FrameChangeHandling::Absolute),
2030                "incremental" => Some(FrameChangeHandling::Incremental),
2031                _ => None,
2032            }
2033        };
2034        let parse_positioning = |raw: &str| -> Option<PercentageNeuronPositioning> {
2035            let lower = raw.trim().to_ascii_lowercase();
2036            match lower.as_str() {
2037                "linear" => Some(PercentageNeuronPositioning::Linear),
2038                "fractional" => Some(PercentageNeuronPositioning::Fractional),
2039                _ => None,
2040            }
2041        };
2042        let parse_signage = |raw: &str| -> Option<bool> {
2043            let lower = raw.trim().to_ascii_lowercase();
2044            if lower.contains("unsigned") {
2045                Some(false)
2046            } else if lower.contains("signed") {
2047                Some(true)
2048            } else {
2049                None
2050            }
2051        };
2052
2053        let requested_signage = changes.get("coding_signage").and_then(|v| v.as_str());
2054        let requested_behavior = changes.get("coding_behavior").and_then(|v| v.as_str());
2055        let requested_type = changes.get("coding_type").and_then(|v| v.as_str());
2056
2057        let new_flag = match current_flag {
2058            IOCorticalAreaConfigurationFlag::Percentage(frame, positioning) => {
2059                let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2060                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2061                let new_positioning = requested_type
2062                    .and_then(parse_positioning)
2063                    .unwrap_or(positioning);
2064                if signed {
2065                    IOCorticalAreaConfigurationFlag::SignedPercentage(new_frame, new_positioning)
2066                } else {
2067                    IOCorticalAreaConfigurationFlag::Percentage(new_frame, new_positioning)
2068                }
2069            }
2070            IOCorticalAreaConfigurationFlag::Percentage2D(frame, positioning) => {
2071                let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2072                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2073                let new_positioning = requested_type
2074                    .and_then(parse_positioning)
2075                    .unwrap_or(positioning);
2076                if signed {
2077                    IOCorticalAreaConfigurationFlag::SignedPercentage2D(new_frame, new_positioning)
2078                } else {
2079                    IOCorticalAreaConfigurationFlag::Percentage2D(new_frame, new_positioning)
2080                }
2081            }
2082            IOCorticalAreaConfigurationFlag::Percentage3D(frame, positioning) => {
2083                let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2084                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2085                let new_positioning = requested_type
2086                    .and_then(parse_positioning)
2087                    .unwrap_or(positioning);
2088                if signed {
2089                    IOCorticalAreaConfigurationFlag::SignedPercentage3D(new_frame, new_positioning)
2090                } else {
2091                    IOCorticalAreaConfigurationFlag::Percentage3D(new_frame, new_positioning)
2092                }
2093            }
2094            IOCorticalAreaConfigurationFlag::Percentage4D(frame, positioning) => {
2095                let signed = requested_signage.and_then(parse_signage).unwrap_or(false);
2096                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2097                let new_positioning = requested_type
2098                    .and_then(parse_positioning)
2099                    .unwrap_or(positioning);
2100                if signed {
2101                    IOCorticalAreaConfigurationFlag::SignedPercentage4D(new_frame, new_positioning)
2102                } else {
2103                    IOCorticalAreaConfigurationFlag::Percentage4D(new_frame, new_positioning)
2104                }
2105            }
2106            IOCorticalAreaConfigurationFlag::SignedPercentage(frame, positioning) => {
2107                let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2108                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2109                let new_positioning = requested_type
2110                    .and_then(parse_positioning)
2111                    .unwrap_or(positioning);
2112                if signed {
2113                    IOCorticalAreaConfigurationFlag::SignedPercentage(new_frame, new_positioning)
2114                } else {
2115                    IOCorticalAreaConfigurationFlag::Percentage(new_frame, new_positioning)
2116                }
2117            }
2118            IOCorticalAreaConfigurationFlag::SignedPercentage2D(frame, positioning) => {
2119                let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2120                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2121                let new_positioning = requested_type
2122                    .and_then(parse_positioning)
2123                    .unwrap_or(positioning);
2124                if signed {
2125                    IOCorticalAreaConfigurationFlag::SignedPercentage2D(new_frame, new_positioning)
2126                } else {
2127                    IOCorticalAreaConfigurationFlag::Percentage2D(new_frame, new_positioning)
2128                }
2129            }
2130            IOCorticalAreaConfigurationFlag::SignedPercentage3D(frame, positioning) => {
2131                let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2132                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2133                let new_positioning = requested_type
2134                    .and_then(parse_positioning)
2135                    .unwrap_or(positioning);
2136                if signed {
2137                    IOCorticalAreaConfigurationFlag::SignedPercentage3D(new_frame, new_positioning)
2138                } else {
2139                    IOCorticalAreaConfigurationFlag::Percentage3D(new_frame, new_positioning)
2140                }
2141            }
2142            IOCorticalAreaConfigurationFlag::SignedPercentage4D(frame, positioning) => {
2143                let signed = requested_signage.and_then(parse_signage).unwrap_or(true);
2144                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2145                let new_positioning = requested_type
2146                    .and_then(parse_positioning)
2147                    .unwrap_or(positioning);
2148                if signed {
2149                    IOCorticalAreaConfigurationFlag::SignedPercentage4D(new_frame, new_positioning)
2150                } else {
2151                    IOCorticalAreaConfigurationFlag::Percentage4D(new_frame, new_positioning)
2152                }
2153            }
2154            IOCorticalAreaConfigurationFlag::CartesianPlane(frame) => {
2155                if let Some(signage) = requested_signage {
2156                    if !signage.trim().eq_ignore_ascii_case("not applicable") {
2157                        return Err(ServiceError::InvalidInput(
2158                            "coding_signage not supported for CartesianPlane".to_string(),
2159                        ));
2160                    }
2161                }
2162                if let Some(coding_type) = requested_type {
2163                    if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2164                        return Err(ServiceError::InvalidInput(
2165                            "coding_type not supported for CartesianPlane".to_string(),
2166                        ));
2167                    }
2168                }
2169                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2170                IOCorticalAreaConfigurationFlag::CartesianPlane(new_frame)
2171            }
2172            IOCorticalAreaConfigurationFlag::Misc(frame) => {
2173                if let Some(signage) = requested_signage {
2174                    if !signage.trim().eq_ignore_ascii_case("not applicable") {
2175                        return Err(ServiceError::InvalidInput(
2176                            "coding_signage not supported for Misc".to_string(),
2177                        ));
2178                    }
2179                }
2180                if let Some(coding_type) = requested_type {
2181                    if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2182                        return Err(ServiceError::InvalidInput(
2183                            "coding_type not supported for Misc".to_string(),
2184                        ));
2185                    }
2186                }
2187                let new_frame = requested_behavior.and_then(parse_frame).unwrap_or(frame);
2188                IOCorticalAreaConfigurationFlag::Misc(new_frame)
2189            }
2190            IOCorticalAreaConfigurationFlag::Boolean => {
2191                if let Some(signage) = requested_signage {
2192                    if !signage.trim().eq_ignore_ascii_case("boolean")
2193                        && !signage.trim().eq_ignore_ascii_case("not applicable")
2194                    {
2195                        return Err(ServiceError::InvalidInput(
2196                            "coding_signage not supported for Boolean".to_string(),
2197                        ));
2198                    }
2199                }
2200                if let Some(coding_type) = requested_type {
2201                    if !coding_type.trim().eq_ignore_ascii_case("not applicable") {
2202                        return Err(ServiceError::InvalidInput(
2203                            "coding_type not supported for Boolean".to_string(),
2204                        ));
2205                    }
2206                }
2207                if let Some(behavior) = requested_behavior {
2208                    if !behavior.trim().eq_ignore_ascii_case("not applicable") {
2209                        return Err(ServiceError::InvalidInput(
2210                            "coding_behavior not supported for Boolean".to_string(),
2211                        ));
2212                    }
2213                }
2214                IOCorticalAreaConfigurationFlag::Boolean
2215            }
2216        };
2217
2218        let unit_identifier = [bytes[1], bytes[2], bytes[3]];
2219        let cortical_subunit_index = CorticalSubUnitIndex::from(bytes[6]);
2220        let cortical_unit_index = CorticalUnitIndex::from(bytes[7]);
2221        let computed_id = new_flag.as_io_cortical_id(
2222            is_input,
2223            unit_identifier,
2224            cortical_unit_index,
2225            cortical_subunit_index,
2226        );
2227
2228        if computed_id.as_base_64() != new_cortical_id_str {
2229            return Err(ServiceError::InvalidInput(format!(
2230                "new_cortical_id '{}' does not match computed ID '{}'",
2231                new_cortical_id_str,
2232                computed_id.as_base_64()
2233            )));
2234        }
2235        info!(
2236            target: "feagi-services",
2237            "[IO-CODING] Remapping cortical ID {} -> {}",
2238            cortical_id.as_base_64(),
2239            computed_id.as_base_64()
2240        );
2241
2242        let new_cortical_type = if is_input {
2243            CorticalAreaType::BrainInput(new_flag)
2244        } else {
2245            CorticalAreaType::BrainOutput(new_flag)
2246        };
2247
2248        // Update RuntimeGenome if available
2249        if let Some(genome) = self.current_genome.write().as_mut() {
2250            let mut area = genome.cortical_areas.remove(cortical_id).ok_or_else(|| {
2251                ServiceError::NotFound {
2252                    resource: "CorticalArea".to_string(),
2253                    id: cortical_id.as_base_64(),
2254                }
2255            })?;
2256            area.cortical_id = computed_id;
2257            area.cortical_type = new_cortical_type;
2258            genome.cortical_areas.insert(computed_id, area);
2259
2260            for region in genome.brain_regions.values_mut() {
2261                if region.cortical_areas.remove(cortical_id) {
2262                    region.cortical_areas.insert(computed_id);
2263                }
2264            }
2265
2266            let old_id_str = cortical_id.as_base_64();
2267            let new_id_str = computed_id.as_base_64();
2268            for area in genome.cortical_areas.values_mut() {
2269                if let Some(mapping) = area
2270                    .properties
2271                    .get_mut("cortical_mapping_dst")
2272                    .and_then(|v| v.as_object_mut())
2273                {
2274                    if let Some(value) = mapping.remove(&old_id_str) {
2275                        mapping.insert(new_id_str.clone(), value);
2276                    }
2277                }
2278            }
2279        }
2280
2281        // Update ConnectomeManager (runtime only; keep NPU registry unchanged)
2282        {
2283            let mut manager = self.connectome.write();
2284            manager.rename_cortical_area_id_with_options(
2285                cortical_id,
2286                computed_id,
2287                new_cortical_type,
2288                false,
2289            )?;
2290        }
2291
2292        // Refresh burst runner cache to propagate updated cortical_id mapping.
2293        self.refresh_burst_runner_cache();
2294
2295        Ok(computed_id)
2296    }
2297
2298    /// Update the unit index (group_id) for an IO cortical area and remap the cortical ID.
2299    ///
2300    /// This updates both RuntimeGenome and ConnectomeManager, preserving cortical_idx.
2301    fn apply_unit_index_update(
2302        &self,
2303        cortical_id: &CorticalID,
2304        cortical_id_str: &str,
2305        changes: &HashMap<String, Value>,
2306    ) -> ServiceResult<CorticalID> {
2307        let new_group_id_value = changes.get("group_id").ok_or_else(|| {
2308            ServiceError::InvalidInput("group_id is required for unit index update".to_string())
2309        })?;
2310        let new_group_id = if let Some(value) = new_group_id_value.as_u64() {
2311            value
2312                .try_into()
2313                .map_err(|_| ServiceError::InvalidInput("group_id out of range".to_string()))?
2314        } else if let Some(value) = new_group_id_value.as_f64() {
2315            if value.fract() != 0.0 {
2316                return Err(ServiceError::InvalidInput(
2317                    "group_id must be an integer".to_string(),
2318                ));
2319            }
2320            let as_u64 = value as u64;
2321            as_u64
2322                .try_into()
2323                .map_err(|_| ServiceError::InvalidInput("group_id out of range".to_string()))?
2324        } else if let Some(raw) = new_group_id_value.as_str() {
2325            raw.parse::<u8>().map_err(|_| {
2326                ServiceError::InvalidInput("group_id must be an integer".to_string())
2327            })?
2328        } else {
2329            return Err(ServiceError::InvalidInput(
2330                "group_id must be an integer".to_string(),
2331            ));
2332        };
2333
2334        let bytes = cortical_id.as_bytes();
2335        let is_input = bytes[0] == b'i';
2336        let is_output = bytes[0] == b'o';
2337        if !is_input && !is_output {
2338            return Err(ServiceError::InvalidInput(
2339                "Unit index updates only apply to IPU/OPU cortical areas".to_string(),
2340            ));
2341        }
2342
2343        let current_flag = cortical_id.extract_io_data_flag().map_err(|e| {
2344            ServiceError::InvalidInput(format!(
2345                "Unable to decode IO configuration from cortical ID '{}': {}",
2346                cortical_id_str, e
2347            ))
2348        })?;
2349        let unit_identifier = [bytes[1], bytes[2], bytes[3]];
2350        let cortical_subunit_index = CorticalSubUnitIndex::from(bytes[6]);
2351        let cortical_unit_index = CorticalUnitIndex::from(new_group_id);
2352        let computed_id = current_flag.as_io_cortical_id(
2353            is_input,
2354            unit_identifier,
2355            cortical_unit_index,
2356            cortical_subunit_index,
2357        );
2358
2359        let new_cortical_type = if is_input {
2360            CorticalAreaType::BrainInput(current_flag)
2361        } else {
2362            CorticalAreaType::BrainOutput(current_flag)
2363        };
2364
2365        if &computed_id == cortical_id {
2366            if let Some(genome) = self.current_genome.write().as_mut() {
2367                if let Some(area) = genome.cortical_areas.get_mut(cortical_id) {
2368                    area.properties
2369                        .insert("group_id".to_string(), serde_json::json!(new_group_id));
2370                }
2371            }
2372            let mut manager = self.connectome.write();
2373            if let Some(area) = manager.get_cortical_area_mut(cortical_id) {
2374                area.properties
2375                    .insert("group_id".to_string(), serde_json::json!(new_group_id));
2376            }
2377            return Ok(*cortical_id);
2378        }
2379
2380        info!(
2381            target: "feagi-services",
2382            "[UNIT-INDEX] Remapping cortical ID {} -> {}",
2383            cortical_id.as_base_64(),
2384            computed_id.as_base_64()
2385        );
2386
2387        // Update RuntimeGenome if available
2388        if let Some(genome) = self.current_genome.write().as_mut() {
2389            let mut area = genome.cortical_areas.remove(cortical_id).ok_or_else(|| {
2390                ServiceError::NotFound {
2391                    resource: "CorticalArea".to_string(),
2392                    id: cortical_id.as_base_64(),
2393                }
2394            })?;
2395            area.cortical_id = computed_id;
2396            area.cortical_type = new_cortical_type;
2397            area.properties
2398                .insert("group_id".to_string(), serde_json::json!(new_group_id));
2399            genome.cortical_areas.insert(computed_id, area);
2400
2401            for region in genome.brain_regions.values_mut() {
2402                if region.cortical_areas.remove(cortical_id) {
2403                    region.cortical_areas.insert(computed_id);
2404                }
2405            }
2406
2407            let old_id_str = cortical_id.as_base_64();
2408            let new_id_str = computed_id.as_base_64();
2409            for area in genome.cortical_areas.values_mut() {
2410                if let Some(mapping) = area
2411                    .properties
2412                    .get_mut("cortical_mapping_dst")
2413                    .and_then(|v| v.as_object_mut())
2414                {
2415                    if let Some(value) = mapping.remove(&old_id_str) {
2416                        mapping.insert(new_id_str.clone(), value);
2417                    }
2418                }
2419            }
2420        }
2421
2422        // Update ConnectomeManager (runtime only; keep NPU registry unchanged)
2423        {
2424            let mut manager = self.connectome.write();
2425            manager.rename_cortical_area_id_with_options(
2426                cortical_id,
2427                computed_id,
2428                new_cortical_type,
2429                false,
2430            )?;
2431            if let Some(area) = manager.get_cortical_area_mut(&computed_id) {
2432                area.properties
2433                    .insert("group_id".to_string(), serde_json::json!(new_group_id));
2434            }
2435        }
2436
2437        // Refresh burst runner cache to propagate updated cortical_id mapping.
2438        self.refresh_burst_runner_cache();
2439
2440        Ok(computed_id)
2441    }
2442
2443    /// Regenerate incoming/outgoing synapses for a cortical area without rebuilding neurons.
2444    fn regenerate_mappings_for_area(&self, cortical_id: &CorticalID) -> ServiceResult<()> {
2445        let mut manager = self.connectome.write();
2446        let cortical_id_str = cortical_id.as_base_64();
2447
2448        let outgoing_targets = {
2449            let Some(area) = manager.get_cortical_area(cortical_id) else {
2450                return Err(ServiceError::NotFound {
2451                    resource: "CorticalArea".to_string(),
2452                    id: cortical_id_str.clone(),
2453                });
2454            };
2455            let mut targets = Vec::new();
2456            if let Some(mapping) = area
2457                .properties
2458                .get("cortical_mapping_dst")
2459                .and_then(|v| v.as_object())
2460            {
2461                for key in mapping.keys() {
2462                    match CorticalID::try_from_base_64(key) {
2463                        Ok(dst_id) => targets.push(dst_id),
2464                        Err(err) => warn!(
2465                            target: "feagi-services",
2466                            "Invalid cortical_mapping_dst ID '{}': {}",
2467                            key,
2468                            err
2469                        ),
2470                    }
2471                }
2472            }
2473            targets
2474        };
2475
2476        let incoming_sources = {
2477            let mut sources = Vec::new();
2478            for src_id in manager.get_all_cortical_ids() {
2479                if src_id == *cortical_id {
2480                    continue;
2481                }
2482                let Some(src_area) = manager.get_cortical_area(&src_id) else {
2483                    continue;
2484                };
2485                if let Some(mapping) = src_area
2486                    .properties
2487                    .get("cortical_mapping_dst")
2488                    .and_then(|v| v.as_object())
2489                {
2490                    if mapping.contains_key(&cortical_id_str) {
2491                        sources.push(src_id);
2492                    }
2493                }
2494            }
2495            sources
2496        };
2497
2498        for dst_id in outgoing_targets {
2499            if dst_id == *cortical_id {
2500                continue;
2501            }
2502            manager
2503                .regenerate_synapses_for_mapping(cortical_id, &dst_id)
2504                .map_err(|e| {
2505                    ServiceError::Backend(format!(
2506                        "Failed to regenerate outgoing synapses {} -> {}: {}",
2507                        cortical_id.as_base_64(),
2508                        dst_id.as_base_64(),
2509                        e
2510                    ))
2511                })?;
2512        }
2513
2514        for src_id in incoming_sources {
2515            manager
2516                .regenerate_synapses_for_mapping(&src_id, cortical_id)
2517                .map_err(|e| {
2518                    ServiceError::Backend(format!(
2519                        "Failed to regenerate incoming synapses {} -> {}: {}",
2520                        src_id.as_base_64(),
2521                        cortical_id.as_base_64(),
2522                        e
2523                    ))
2524                })?;
2525        }
2526
2527        Ok(())
2528    }
2529
2530    /// Fastest path: Update only metadata without affecting neurons/synapses
2531    ///
2532    /// Performance: ~1ms (metadata changes only)
2533    async fn update_metadata_only(
2534        &self,
2535        cortical_id: &str,
2536        changes: HashMap<String, Value>,
2537    ) -> ServiceResult<CorticalAreaInfo> {
2538        info!(target: "feagi-services", "[METADATA-UPDATE] Metadata-only update for {}", cortical_id);
2539        let needs_burst_cache_refresh = changes.contains_key("visualization_voxel_granularity");
2540        let mut properties_changed = false;
2541        let mut geometry_changed = false;
2542        for key in changes.keys() {
2543            match key.as_str() {
2544                "coordinates_3d" | "coordinate_3d" | "coordinates" | "position"
2545                | "coordinate_2d" | "coordinates_2d" => {
2546                    geometry_changed = true;
2547                }
2548                "cortical_name" | "name" | "visible" | "visualization_voxel_granularity" => {
2549                    properties_changed = true;
2550                }
2551                _ => {
2552                    properties_changed = true;
2553                }
2554            }
2555        }
2556
2557        // Convert cortical_id to CorticalID
2558        let cortical_id_typed =
2559            feagi_evolutionary::string_to_cortical_id(cortical_id).map_err(|e| {
2560                ServiceError::InvalidInput(format!("Invalid cortical ID '{}': {}", cortical_id, e))
2561            })?;
2562        let mut effective_cortical_id = cortical_id_typed;
2563        let mut effective_cortical_id_str = cortical_id.to_string();
2564
2565        let has_coding_update = changes.contains_key("coding_signage")
2566            || changes.contains_key("coding_behavior")
2567            || changes.contains_key("coding_type")
2568            || changes.contains_key("new_cortical_id");
2569        if has_coding_update {
2570            let new_id = self.apply_io_coding_update(&cortical_id_typed, cortical_id, &changes)?;
2571            effective_cortical_id = new_id;
2572            effective_cortical_id_str = new_id.as_base_64();
2573        }
2574
2575        // Update RuntimeGenome if available
2576        if let Some(genome) = self.current_genome.write().as_mut() {
2577            if let Some(area) = genome.cortical_areas.get_mut(&effective_cortical_id) {
2578                for (key, value) in &changes {
2579                    match key.as_str() {
2580                        "cortical_name" | "name" => {
2581                            if let Some(name) = value.as_str() {
2582                                area.name = name.to_string();
2583                            }
2584                        }
2585                        "coordinates_3d" | "coordinate_3d" | "coordinates" | "position" => {
2586                            // Parse coordinates - support both array [x, y, z] and object {"x": x, "y": y, "z": z}
2587                            if let Some(arr) = value.as_array() {
2588                                // Array format: [x, y, z]
2589                                if arr.len() >= 3 {
2590                                    let x = arr[0].as_i64().unwrap_or(0) as i32;
2591                                    let y = arr[1].as_i64().unwrap_or(0) as i32;
2592                                    let z = arr[2].as_i64().unwrap_or(0) as i32;
2593                                    area.position = (x, y, z).into();
2594                                    info!(target: "feagi-services", "[GENOME-UPDATE] Updated position (array format): ({}, {}, {})", x, y, z);
2595                                }
2596                            } else if let Some(obj) = value.as_object() {
2597                                // Object format: {"x": x, "y": y, "z": z}
2598                                let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2599                                let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2600                                let z = obj.get("z").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2601                                area.position = (x, y, z).into();
2602                                info!(target: "feagi-services", "[GENOME-UPDATE] Updated position (object format): ({}, {}, {})", x, y, z);
2603                            }
2604                        }
2605                        "coordinate_2d" | "coordinates_2d" => {
2606                            if let Some(arr) = value.as_array() {
2607                                if arr.len() >= 2 {
2608                                    let x = arr[0].as_i64().unwrap_or(0) as i32;
2609                                    let y = arr[1].as_i64().unwrap_or(0) as i32;
2610                                    area.properties.insert(
2611                                        "coordinate_2d".to_string(),
2612                                        serde_json::json!([x, y]),
2613                                    );
2614                                    info!(target: "feagi-services", "[GENOME-UPDATE] Updated coordinate_2d: ({}, {})", x, y);
2615                                } else {
2616                                    warn!(target: "feagi-services", "[GENOME-UPDATE] coordinate_2d array must have 2 elements, got {}", arr.len());
2617                                }
2618                            } else if let Some(obj) = value.as_object() {
2619                                let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2620                                let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2621                                area.properties
2622                                    .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2623                                info!(target: "feagi-services", "[GENOME-UPDATE] Updated coordinate_2d (object format): ({}, {})", x, y);
2624                            } else {
2625                                warn!(target: "feagi-services", "[GENOME-UPDATE] coordinate_2d must be array or object, got: {:?}", value);
2626                            }
2627                        }
2628                        "visualization_voxel_granularity" => {
2629                            // Only store if != 1x1x1 (default), delete if set to 1x1x1
2630                            info!(target: "feagi-services", "[GENOME-UPDATE] Received visualization_voxel_granularity update: {:?}", value);
2631                            if let Some(arr) = value.as_array() {
2632                                if arr.len() == 3 {
2633                                    // Try to parse as integers (u64) or floats (f64), then convert to u32
2634                                    let x_opt = arr[0]
2635                                        .as_u64()
2636                                        .or_else(|| arr[0].as_f64().map(|f| f as u64));
2637                                    let y_opt = arr[1]
2638                                        .as_u64()
2639                                        .or_else(|| arr[1].as_f64().map(|f| f as u64));
2640                                    let z_opt = arr[2]
2641                                        .as_u64()
2642                                        .or_else(|| arr[2].as_f64().map(|f| f as u64));
2643
2644                                    if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
2645                                        let x_u32 = x as u32;
2646                                        let y_u32 = y as u32;
2647                                        let z_u32 = z as u32;
2648
2649                                        // Default is 1x1x1 - only store if different
2650                                        if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
2651                                            // Remove override (return to default)
2652                                            area.properties
2653                                                .remove("visualization_voxel_granularity");
2654                                            info!(target: "feagi-services", "[GENOME-UPDATE] Removed visualization_voxel_granularity override (returned to default 1x1x1)");
2655                                        } else {
2656                                            // Store override (non-default value) as integer array
2657                                            area.properties.insert(
2658                                                "visualization_voxel_granularity".to_string(),
2659                                                serde_json::json!([x_u32, y_u32, z_u32]),
2660                                            );
2661                                            info!(target: "feagi-services", "[GENOME-UPDATE] Updated visualization_voxel_granularity: ({}, {}, {})", x_u32, y_u32, z_u32);
2662                                        }
2663                                    } else {
2664                                        warn!(target: "feagi-services", "[GENOME-UPDATE] Failed to parse visualization_voxel_granularity array values as integers");
2665                                    }
2666                                } else {
2667                                    warn!(target: "feagi-services", "[GENOME-UPDATE] visualization_voxel_granularity array must have 3 elements, got {}", arr.len());
2668                                }
2669                            } else {
2670                                warn!(target: "feagi-services", "[GENOME-UPDATE] visualization_voxel_granularity must be an array, got: {:?}", value);
2671                            }
2672                        }
2673                        _ => {}
2674                    }
2675                }
2676            }
2677        }
2678
2679        // Update ConnectomeManager metadata
2680        {
2681            let mut manager = self.connectome.write();
2682            let area = manager
2683                .get_cortical_area_mut(&effective_cortical_id)
2684                .ok_or_else(|| ServiceError::NotFound {
2685                    resource: "CorticalArea".to_string(),
2686                    id: effective_cortical_id_str.clone(),
2687                })?;
2688
2689            // Update metadata fields
2690            for (key, value) in &changes {
2691                match key.as_str() {
2692                    "cortical_name" | "name" => {
2693                        if let Some(name) = value.as_str() {
2694                            area.name = name.to_string();
2695                        }
2696                    }
2697                    "visible" => {
2698                        if let Some(v) = value.as_bool() {
2699                            area.add_property_mut("visible".to_string(), serde_json::json!(v));
2700                        }
2701                    }
2702                    "coordinates_3d" | "coordinate_3d" | "coordinates" | "position" => {
2703                        // Parse coordinates - support both array [x, y, z] and object {"x": x, "y": y, "z": z}
2704                        if let Some(arr) = value.as_array() {
2705                            // Array format: [x, y, z]
2706                            if arr.len() >= 3 {
2707                                let x = arr[0].as_i64().unwrap_or(0) as i32;
2708                                let y = arr[1].as_i64().unwrap_or(0) as i32;
2709                                let z = arr[2].as_i64().unwrap_or(0) as i32;
2710                                area.position = (x, y, z).into();
2711                                info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated position (array format): ({}, {}, {})", x, y, z);
2712                            }
2713                        } else if let Some(obj) = value.as_object() {
2714                            // Object format: {"x": x, "y": y, "z": z}
2715                            let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2716                            let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2717                            let z = obj.get("z").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2718                            area.position = (x, y, z).into();
2719                            info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated position (object format): ({}, {}, {})", x, y, z);
2720                        }
2721                    }
2722                    "coordinate_2d" | "coordinates_2d" => {
2723                        if let Some(arr) = value.as_array() {
2724                            if arr.len() >= 2 {
2725                                let x = arr[0].as_i64().unwrap_or(0) as i32;
2726                                let y = arr[1].as_i64().unwrap_or(0) as i32;
2727                                area.properties
2728                                    .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2729                                info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated coordinate_2d: ({}, {})", x, y);
2730                            } else {
2731                                warn!(target: "feagi-services", "[CONNECTOME-UPDATE] coordinate_2d array must have 2 elements, got {}", arr.len());
2732                            }
2733                        } else if let Some(obj) = value.as_object() {
2734                            let x = obj.get("x").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2735                            let y = obj.get("y").and_then(|v| v.as_i64()).unwrap_or(0) as i32;
2736                            area.properties
2737                                .insert("coordinate_2d".to_string(), serde_json::json!([x, y]));
2738                            info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated coordinate_2d (object format): ({}, {})", x, y);
2739                        } else {
2740                            warn!(target: "feagi-services", "[CONNECTOME-UPDATE] coordinate_2d must be array or object, got: {:?}", value);
2741                        }
2742                    }
2743                    "visualization_voxel_granularity" => {
2744                        // Only store if != 1x1x1 (default), delete if set to 1x1x1
2745                        info!(target: "feagi-services", "[CONNECTOME-UPDATE] Received visualization_voxel_granularity update: {:?}", value);
2746                        if let Some(arr) = value.as_array() {
2747                            if arr.len() == 3 {
2748                                // Try to parse as integers (u64) or floats (f64), then convert to u32
2749                                let x_opt = arr[0]
2750                                    .as_u64()
2751                                    .or_else(|| arr[0].as_f64().map(|f| f as u64));
2752                                let y_opt = arr[1]
2753                                    .as_u64()
2754                                    .or_else(|| arr[1].as_f64().map(|f| f as u64));
2755                                let z_opt = arr[2]
2756                                    .as_u64()
2757                                    .or_else(|| arr[2].as_f64().map(|f| f as u64));
2758
2759                                if let (Some(x), Some(y), Some(z)) = (x_opt, y_opt, z_opt) {
2760                                    let x_u32 = x as u32;
2761                                    let y_u32 = y as u32;
2762                                    let z_u32 = z as u32;
2763
2764                                    // Default is 1x1x1 - only store if different
2765                                    if x_u32 == 1 && y_u32 == 1 && z_u32 == 1 {
2766                                        // Remove override (return to default)
2767                                        area.properties.remove("visualization_voxel_granularity");
2768                                        info!(target: "feagi-services", "[CONNECTOME-UPDATE] Removed visualization_voxel_granularity override (returned to default 1x1x1)");
2769                                    } else {
2770                                        // Store override (non-default value) as integer array
2771                                        area.properties.insert(
2772                                            "visualization_voxel_granularity".to_string(),
2773                                            serde_json::json!([x_u32, y_u32, z_u32]),
2774                                        );
2775                                        info!(target: "feagi-services", "[CONNECTOME-UPDATE] Updated visualization_voxel_granularity: ({}, {}, {})", x_u32, y_u32, z_u32);
2776                                    }
2777                                } else {
2778                                    warn!(target: "feagi-services", "[CONNECTOME-UPDATE] Failed to parse visualization_voxel_granularity array values as integers");
2779                                }
2780                            } else {
2781                                warn!(target: "feagi-services", "[CONNECTOME-UPDATE] visualization_voxel_granularity array must have 3 elements, got {}", arr.len());
2782                            }
2783                        } else {
2784                            warn!(target: "feagi-services", "[CONNECTOME-UPDATE] visualization_voxel_granularity must be an array, got: {:?}", value);
2785                        }
2786                    }
2787                    _ => {}
2788                }
2789            }
2790            manager.refresh_cortical_area_hashes(properties_changed, geometry_changed);
2791        }
2792
2793        // Refresh burst runner cache so the NPU aggregation path immediately uses new granularity.
2794        if needs_burst_cache_refresh {
2795            self.refresh_burst_runner_cache();
2796        }
2797
2798        info!(target: "feagi-services", "[METADATA-UPDATE] Metadata update complete");
2799
2800        // Return updated info
2801        self.get_cortical_area_info(&effective_cortical_id_str)
2802            .await
2803    }
2804
2805    /// Structural rebuild: For dimension/density changes requiring synapse rebuild
2806    ///
2807    /// Performance: ~100-200ms (localized to one area, not full brain)
2808    ///
2809    /// CRITICAL: This requires:
2810    /// 1. Deleting all neurons in the area
2811    /// 2. Deleting all incoming/outgoing synapses (automatic via neuron deletion)
2812    /// 3. Recreating neurons with new dimensions/density
2813    /// 4. Rebuilding synapses via cortical mapping
2814    async fn update_with_localized_rebuild(
2815        &self,
2816        cortical_id: &str,
2817        changes: HashMap<String, Value>,
2818    ) -> ServiceResult<CorticalAreaInfo> {
2819        info!(target: "feagi-services", "[STRUCTURAL-REBUILD] Localized rebuild for {}", cortical_id);
2820
2821        // Validate cortical ID format
2822        let _cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
2823            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
2824
2825        if changes.contains_key("group_id") && changes.len() == 1 {
2826            let new_id =
2827                self.apply_unit_index_update(&_cortical_id_typed, cortical_id, &changes)?;
2828            self.regenerate_mappings_for_area(&new_id)?;
2829            return self.get_cortical_area_info(&new_id.as_base_64()).await;
2830        }
2831
2832        // Must run on blocking thread due to heavy ConnectomeManager operations
2833        let connectome = Arc::clone(&self.connectome);
2834        let genome_store = Arc::clone(&self.current_genome);
2835        let cortical_id_owned = cortical_id.to_string();
2836        let burst_runner_clone = self.burst_runner.clone();
2837
2838        tokio::task::spawn_blocking(move || {
2839            Self::do_localized_rebuild(
2840                &cortical_id_owned,
2841                changes,
2842                connectome,
2843                genome_store,
2844                burst_runner_clone,
2845            )
2846        })
2847        .await
2848        .map_err(|e| ServiceError::Backend(format!("Rebuild task panicked: {}", e)))?
2849    }
2850
2851    /// Perform localized rebuild (blocking operation)
2852    fn do_localized_rebuild(
2853        cortical_id: &str,
2854        changes: HashMap<String, Value>,
2855        connectome: Arc<RwLock<ConnectomeManager>>,
2856        genome_store: Arc<RwLock<Option<feagi_evolutionary::RuntimeGenome>>>,
2857        burst_runner: Option<Arc<RwLock<BurstLoopRunner>>>,
2858    ) -> ServiceResult<CorticalAreaInfo> {
2859        info!(
2860            "[STRUCTURAL-REBUILD] Starting localized rebuild for {}",
2861            cortical_id
2862        );
2863
2864        // Convert cortical_id to CorticalID
2865        let cortical_id_typed =
2866            feagi_evolutionary::string_to_cortical_id(cortical_id).map_err(|e| {
2867                ServiceError::InvalidInput(format!("Invalid cortical ID '{}': {}", cortical_id, e))
2868            })?;
2869
2870        // Step 1: Update RuntimeGenome dimensions/density
2871        let (old_dimensions, old_density, new_dimensions, new_density) = {
2872            let mut genome_guard = genome_store.write();
2873            let genome = genome_guard
2874                .as_mut()
2875                .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?;
2876
2877            let area = genome
2878                .cortical_areas
2879                .get_mut(&cortical_id_typed)
2880                .ok_or_else(|| ServiceError::NotFound {
2881                    resource: "CorticalArea".to_string(),
2882                    id: cortical_id.to_string(),
2883                })?;
2884
2885            let old_dims = area.dimensions;
2886            let old_dens = area.neurons_per_voxel();
2887
2888            // Apply dimensional changes
2889            // CRITICAL: For IPU/OPU areas, cortical_dimensions_per_device must be multiplied by dev_count
2890            let is_per_device = changes.contains_key("cortical_dimensions_per_device");
2891
2892            if let Some(dims) = changes
2893                .get("dimensions")
2894                .or_else(|| changes.get("cortical_dimensions"))
2895                .or_else(|| changes.get("cortical_dimensions_per_device"))
2896            {
2897                let (width, height, depth) = if let Some(arr) = dims.as_array() {
2898                    // Handle array format: [width, height, depth]
2899                    if arr.len() >= 3 {
2900                        (
2901                            arr[0].as_u64().unwrap_or(1) as usize,
2902                            arr[1].as_u64().unwrap_or(1) as usize,
2903                            arr[2].as_u64().unwrap_or(1) as usize,
2904                        )
2905                    } else {
2906                        (1, 1, 1)
2907                    }
2908                } else if let Some(obj) = dims.as_object() {
2909                    // Handle object format: {"x": width, "y": height, "z": depth}
2910                    (
2911                        obj.get("x").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2912                        obj.get("y").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2913                        obj.get("z").and_then(|v| v.as_u64()).unwrap_or(1) as usize,
2914                    )
2915                } else {
2916                    (1, 1, 1)
2917                };
2918
2919                // If this is per-device dimensions, multiply depth by dev_count to get total dimensions
2920                let final_depth = if is_per_device {
2921                    // Get dev_count from changes or from existing area properties
2922                    let dev_count = changes
2923                        .get("dev_count")
2924                        .and_then(|v| v.as_u64())
2925                        .or_else(|| area.properties.get("dev_count").and_then(|v| v.as_u64()))
2926                        .unwrap_or(1) as usize;
2927
2928                    info!("[STRUCTURAL-REBUILD] Per-device dimensions: [{}, {}, {}] × dev_count={} → total depth={}",
2929                          width, height, depth, dev_count, depth * dev_count);
2930
2931                    depth * dev_count
2932                } else {
2933                    depth
2934                };
2935
2936                area.dimensions =
2937                    CorticalAreaDimensions::new(width as u32, height as u32, final_depth as u32)?;
2938            }
2939
2940            // Apply density changes
2941            // Update neurons_per_voxel from any of the legacy parameter names
2942            for density_param in [
2943                "neurons_per_voxel",
2944                "per_voxel_neuron_cnt",
2945                "neuron_density",
2946            ] {
2947                if let Some(density) = changes.get(density_param).and_then(|v| v.as_u64()) {
2948                    area.add_property_mut(
2949                        "neurons_per_voxel".to_string(),
2950                        serde_json::json!(density as u32),
2951                    );
2952                    break;
2953                }
2954            }
2955
2956            // Store dev_count and cortical_dimensions_per_device in properties for IPU/OPU areas
2957            if let Some(dev_count) = changes.get("dev_count") {
2958                area.properties
2959                    .insert("dev_count".to_string(), dev_count.clone());
2960            }
2961            if let Some(per_device_dims) = changes.get("cortical_dimensions_per_device") {
2962                area.properties.insert(
2963                    "cortical_dimensions_per_device".to_string(),
2964                    per_device_dims.clone(),
2965                );
2966            }
2967
2968            // Update spatial gradient increment properties if changed
2969            // These require rebuild because thresholds must be recalculated based on position
2970            // Handle neuron_fire_threshold_increment as ARRAY [x, y, z] from BV
2971            if let Some(value) = changes.get("neuron_fire_threshold_increment") {
2972                if let Some(arr) = value.as_array() {
2973                    // Parse array [x, y, z] into separate properties
2974                    if arr.len() >= 3 {
2975                        let x = arr[0].as_f64().unwrap_or(0.0) as f32;
2976                        let y = arr[1].as_f64().unwrap_or(0.0) as f32;
2977                        let z = arr[2].as_f64().unwrap_or(0.0) as f32;
2978
2979                        area.properties.insert(
2980                            "firing_threshold_increment_x".to_string(),
2981                            serde_json::json!(x),
2982                        );
2983                        area.properties.insert(
2984                            "firing_threshold_increment_y".to_string(),
2985                            serde_json::json!(y),
2986                        );
2987                        area.properties.insert(
2988                            "firing_threshold_increment_z".to_string(),
2989                            serde_json::json!(z),
2990                        );
2991
2992                        info!(
2993                            "[STRUCTURAL-REBUILD] Updated firing_threshold_increment to [{}, {}, {}] for area {}",
2994                            x, y, z, cortical_id
2995                        );
2996                    }
2997                }
2998            }
2999
3000            // Handle individual x/y/z properties if sent separately
3001            for increment_param in [
3002                "firing_threshold_increment_x",
3003                "firing_threshold_increment_y",
3004                "firing_threshold_increment_z",
3005            ] {
3006                if let Some(value) = changes.get(increment_param) {
3007                    area.properties
3008                        .insert(increment_param.to_string(), value.clone());
3009                    info!(
3010                        "[STRUCTURAL-REBUILD] Updated {} to {} for area {}",
3011                        increment_param, value, cortical_id
3012                    );
3013                }
3014            }
3015
3016            // Update leak_variability if changed (also requires rebuild)
3017            for param in ["leak_variability", "neuron_leak_variability"] {
3018                if let Some(value) = changes.get(param) {
3019                    area.properties
3020                        .insert("leak_variability".to_string(), value.clone());
3021                    info!(
3022                        "[STRUCTURAL-REBUILD] Updated leak_variability to {} for area {}",
3023                        value, cortical_id
3024                    );
3025                    break;
3026                }
3027            }
3028
3029            (
3030                old_dims,
3031                old_dens,
3032                area.dimensions,
3033                area.neurons_per_voxel(),
3034            )
3035        };
3036
3037        let total_voxels = new_dimensions.width as usize
3038            * new_dimensions.height as usize
3039            * new_dimensions.depth as usize;
3040        let estimated_neurons = total_voxels * new_density as usize;
3041
3042        info!(
3043            "[STRUCTURAL-REBUILD] Dimension: {:?} -> {:?}",
3044            old_dimensions, new_dimensions
3045        );
3046        info!(
3047            "[STRUCTURAL-REBUILD] Density: {} -> {} neurons/voxel",
3048            old_density, new_density
3049        );
3050
3051        if estimated_neurons > 1_000_000 {
3052            warn!(
3053                "[STRUCTURAL-REBUILD] ⚠️ Large area resize: {} neurons estimated. This may take significant time and memory.",
3054                estimated_neurons
3055            );
3056        }
3057
3058        // Step 2: Delete all neurons in the cortical area
3059        let neurons_to_delete = {
3060            let manager = connectome.read();
3061            manager.get_neurons_in_area(&cortical_id_typed)
3062        };
3063
3064        let deleted_count = if !neurons_to_delete.is_empty() {
3065            info!(
3066                "[STRUCTURAL-REBUILD] Deleting {} existing neurons",
3067                neurons_to_delete.len()
3068            );
3069            let mut manager = connectome.write();
3070            manager
3071                .delete_neurons_batch(neurons_to_delete)
3072                .map_err(|e| ServiceError::Backend(format!("Failed to delete neurons: {}", e)))?
3073        } else {
3074            0
3075        };
3076
3077        info!("[STRUCTURAL-REBUILD] Deleted {} neurons", deleted_count);
3078
3079        // Step 3: Update cortical area dimensions and properties in ConnectomeManager
3080        {
3081            let mut manager = connectome.write();
3082            manager
3083                .resize_cortical_area(&cortical_id_typed, new_dimensions)
3084                .map_err(|e| ServiceError::Backend(format!("Failed to resize area: {}", e)))?;
3085
3086            // Update neurons_per_voxel and properties
3087            if let Some(area) = manager.get_cortical_area_mut(&cortical_id_typed) {
3088                area.add_property_mut(
3089                    "neurons_per_voxel".to_string(),
3090                    serde_json::json!(new_density),
3091                );
3092
3093                // Store IPU/OPU properties
3094                if let Some(dev_count) = changes.get("dev_count") {
3095                    area.properties
3096                        .insert("dev_count".to_string(), dev_count.clone());
3097                }
3098                if let Some(per_device_dims) = changes.get("cortical_dimensions_per_device") {
3099                    area.properties.insert(
3100                        "cortical_dimensions_per_device".to_string(),
3101                        per_device_dims.clone(),
3102                    );
3103                }
3104
3105                // ✅ Sync spatial gradient increment properties to ConnectomeManager
3106                // This ensures BV reads back the updated values
3107                // Handle neuron_fire_threshold_increment as ARRAY [x, y, z] from BV
3108                if let Some(value) = changes.get("neuron_fire_threshold_increment") {
3109                    if let Some(arr) = value.as_array() {
3110                        if arr.len() >= 3 {
3111                            let x = arr[0].as_f64().unwrap_or(0.0) as f32;
3112                            let y = arr[1].as_f64().unwrap_or(0.0) as f32;
3113                            let z = arr[2].as_f64().unwrap_or(0.0) as f32;
3114
3115                            area.properties.insert(
3116                                "firing_threshold_increment_x".to_string(),
3117                                serde_json::json!(x),
3118                            );
3119                            area.properties.insert(
3120                                "firing_threshold_increment_y".to_string(),
3121                                serde_json::json!(y),
3122                            );
3123                            area.properties.insert(
3124                                "firing_threshold_increment_z".to_string(),
3125                                serde_json::json!(z),
3126                            );
3127                        }
3128                    }
3129                }
3130
3131                // Handle individual x/y/z properties if sent separately
3132                for increment_param in [
3133                    "firing_threshold_increment_x",
3134                    "firing_threshold_increment_y",
3135                    "firing_threshold_increment_z",
3136                ] {
3137                    if let Some(value) = changes.get(increment_param) {
3138                        area.properties
3139                            .insert(increment_param.to_string(), value.clone());
3140                    }
3141                }
3142
3143                // ✅ Sync leak_variability to ConnectomeManager
3144                for param in ["leak_variability", "neuron_leak_variability"] {
3145                    if let Some(value) = changes.get(param) {
3146                        area.properties
3147                            .insert("leak_variability".to_string(), value.clone());
3148                        break;
3149                    }
3150                }
3151            }
3152            manager.refresh_cortical_area_hashes(true, true);
3153        }
3154
3155        // Step 4: Recreate neurons with new dimensions/density
3156        // CRITICAL PERFORMANCE FIX: Extract data from connectome, release lock, then create neurons
3157        // This prevents blocking API requests during the multi-second neuron creation process
3158        let (cortical_idx, area_data) = {
3159            let manager = connectome.read();
3160            let area = manager
3161                .get_cortical_area(&cortical_id_typed)
3162                .ok_or_else(|| ServiceError::NotFound {
3163                    resource: "CorticalArea".to_string(),
3164                    id: cortical_id.to_string(),
3165                })?;
3166            let cortical_idx = manager
3167                .get_cortical_idx(&cortical_id_typed)
3168                .ok_or_else(|| ServiceError::Backend("Cortical index not found".to_string()))?;
3169
3170            // Extract all data needed for neuron creation
3171            use feagi_brain_development::models::CorticalAreaExt;
3172            (
3173                cortical_idx,
3174                (
3175                    area.dimensions,
3176                    area.neurons_per_voxel(),
3177                    area.firing_threshold(),
3178                    area.firing_threshold_increment_x(),
3179                    area.firing_threshold_increment_y(),
3180                    area.firing_threshold_increment_z(),
3181                    area.firing_threshold_limit(),
3182                    area.leak_coefficient(),
3183                    area.neuron_excitability(),
3184                    area.refractory_period(),
3185                    area.consecutive_fire_count() as u16,
3186                    area.snooze_period(),
3187                    area.mp_charge_accumulation(),
3188                ),
3189            )
3190        };
3191
3192        // Release connectome lock before creating neurons (NPU lock will be held, but connectome is free for API)
3193        let npu_arc_for_creation = {
3194            let manager = connectome.read();
3195            manager
3196                .get_npu()
3197                .ok_or_else(|| ServiceError::Backend("NPU not connected".to_string()))?
3198                .clone()
3199        };
3200
3201        // CRITICAL PERFORMANCE: Event-based lock management
3202        // Lock is held until create_cortical_area_neurons() completes (100% done)
3203        // Timing varies by hardware/topology - we measure actual time, not estimate
3204        // NOTE: Connectome lock is already released, so API can query connectome data
3205        let total_neurons =
3206            area_data.0.width * area_data.0.height * area_data.0.depth * area_data.1;
3207
3208        if total_neurons > 1_000_000 {
3209            info!(
3210                "[STRUCTURAL-REBUILD] Creating large area ({} neurons) - NPU lock held until creation completes",
3211                total_neurons
3212            );
3213        }
3214
3215        let creation_start = std::time::Instant::now();
3216
3217        // CRITICAL PERFORMANCE: For very large areas (>1M neurons), batch by depth layers AND within layers
3218        // to release NPU lock frequently and allow burst loop to run
3219        let neurons_created = if total_neurons > 1_000_000 {
3220            // Batch by depth layers (z-dimension) - each layer is processed separately
3221            // Additionally, batch within each layer if layer is large (>100k neurons)
3222            let mut total_created = 0u32;
3223            let depth = area_data.0.depth;
3224            let width = area_data.0.width;
3225            let height = area_data.0.height;
3226            let neurons_per_voxel = area_data.1;
3227            let neurons_per_layer = (width * height * neurons_per_voxel) as usize;
3228            const BATCH_SIZE: usize = 10_000; // Process neurons in batches of 10k within each layer (reduces lock hold time)
3229
3230            // Determine if we need to batch within layers
3231            let needs_inner_batching = neurons_per_layer > 50_000; // Lower threshold for inner batching
3232
3233            info!(
3234                "[STRUCTURAL-REBUILD] Batching neuron creation: {} layers × {} neurons/layer = {} total{}",
3235                depth, neurons_per_layer, total_neurons,
3236                if needs_inner_batching { " (with inner-layer batching)" } else { "" }
3237            );
3238
3239            for z_layer in 0..depth {
3240                if needs_inner_batching {
3241                    // Large layer: batch within the layer by processing rows in chunks
3242                    let mut layer_created = 0u32;
3243                    let neurons_per_row = (width * neurons_per_voxel) as usize;
3244                    let rows_per_batch = (BATCH_SIZE / neurons_per_row).max(1);
3245                    let total_row_batches = (height as usize).div_ceil(rows_per_batch);
3246
3247                    if z_layer == 0 {
3248                        info!(
3249                            "[STRUCTURAL-REBUILD] Inner-layer batching: {} rows/layer, {} rows/batch, ~{} batches/layer",
3250                            height, rows_per_batch, total_row_batches
3251                        );
3252                    }
3253
3254                    for (batch_idx, row_start) in (0..height).step_by(rows_per_batch).enumerate() {
3255                        let row_end = (row_start + rows_per_batch as u32).min(height);
3256                        let rows_in_batch = row_end - row_start;
3257                        let neurons_in_batch = (rows_in_batch * width * neurons_per_voxel) as usize;
3258
3259                        let batch_start = std::time::Instant::now();
3260
3261                        // Acquire lock, create batch of rows, release lock
3262                        let batch_created = {
3263                            let mut npu_lock = npu_arc_for_creation.lock().map_err(|e| {
3264                                ServiceError::Backend(format!("Failed to lock NPU: {}", e))
3265                            })?;
3266
3267                            // Create neurons for this batch of rows (height=rows_in_batch, y_offset=row_start, z_offset=z_layer)
3268                            npu_lock
3269                                .create_cortical_area_neurons_with_offsets(
3270                                    cortical_idx,
3271                                    width,
3272                                    rows_in_batch,
3273                                    1, // depth=1 (single z-layer)
3274                                    neurons_per_voxel,
3275                                    area_data.2,  // default_threshold
3276                                    area_data.3,  // threshold_increment_x
3277                                    area_data.4,  // threshold_increment_y
3278                                    area_data.5,  // threshold_increment_z
3279                                    area_data.6,  // default_threshold_limit
3280                                    area_data.7,  // default_leak_coefficient
3281                                    0.0,          // default_resting_potential
3282                                    0,            // default_neuron_type
3283                                    area_data.9,  // default_refractory_period
3284                                    area_data.8,  // default_excitability
3285                                    area_data.10, // default_consecutive_fire_limit
3286                                    area_data.11, // default_snooze_period
3287                                    area_data.12, // default_mp_charge_accumulation
3288                                    row_start, // y_offset: create neurons starting at this y-coordinate
3289                                    z_layer,   // z_offset: create neurons at this z-coordinate
3290                                )
3291                                .map_err(|e| {
3292                                    ServiceError::Backend(format!(
3293                                        "NPU neuron creation failed for layer {} rows {}-{}: {}",
3294                                        z_layer, row_start, row_end, e
3295                                    ))
3296                                })?
3297                        };
3298
3299                        let batch_duration = batch_start.elapsed();
3300                        layer_created += batch_created;
3301
3302                        // Log if batch took too long (helps identify if batching is working)
3303                        if batch_duration.as_millis() > 100
3304                            && (batch_idx == 0 || batch_idx == total_row_batches - 1)
3305                        {
3306                            tracing::debug!(
3307                                "[STRUCTURAL-REBUILD] Layer {} batch {}/{}: {} neurons in {:.1}ms (rows {}-{})",
3308                                z_layer, batch_idx + 1, total_row_batches, neurons_in_batch,
3309                                batch_duration.as_millis(), row_start, row_end
3310                            );
3311                        }
3312                    }
3313
3314                    total_created += layer_created;
3315                } else {
3316                    // Small layer: create entire layer in one batch
3317                    let layer_created = {
3318                        let mut npu_lock = npu_arc_for_creation.lock().map_err(|e| {
3319                            ServiceError::Backend(format!("Failed to lock NPU: {}", e))
3320                        })?;
3321
3322                        // Create neurons for this z-layer only (depth=1, z_offset=z_layer)
3323                        npu_lock
3324                            .create_cortical_area_neurons_with_z_offset(
3325                                cortical_idx,
3326                                width,
3327                                height,
3328                                1, // depth=1 (single layer)
3329                                neurons_per_voxel,
3330                                area_data.2,  // default_threshold
3331                                area_data.3,  // threshold_increment_x
3332                                area_data.4,  // threshold_increment_y
3333                                area_data.5,  // threshold_increment_z
3334                                area_data.6,  // default_threshold_limit
3335                                area_data.7,  // default_leak_coefficient
3336                                0.0,          // default_resting_potential
3337                                0,            // default_neuron_type
3338                                area_data.9,  // default_refractory_period
3339                                area_data.8,  // default_excitability
3340                                area_data.10, // default_consecutive_fire_limit
3341                                area_data.11, // default_snooze_period
3342                                area_data.12, // default_mp_charge_accumulation
3343                                z_layer,      // z_offset: create neurons at this z-coordinate
3344                            )
3345                            .map_err(|e| {
3346                                ServiceError::Backend(format!(
3347                                    "NPU neuron creation failed for layer {}: {}",
3348                                    z_layer, e
3349                                ))
3350                            })?
3351                    };
3352
3353                    total_created += layer_created;
3354                }
3355
3356                // Log progress every 5 layers or on last layer
3357                if (z_layer + 1) % 5 == 0 || z_layer == depth - 1 {
3358                    let progress = ((z_layer + 1) as f32 / depth as f32) * 100.0;
3359                    info!(
3360                        "[STRUCTURAL-REBUILD] Progress: {}/{} layers ({}%) - {} neurons created",
3361                        z_layer + 1,
3362                        depth,
3363                        progress as u32,
3364                        total_created
3365                    );
3366                }
3367            }
3368
3369            total_created
3370        } else {
3371            // Small area: create all neurons in one batch (single lock acquisition)
3372            let mut npu_lock = npu_arc_for_creation
3373                .lock()
3374                .map_err(|e| ServiceError::Backend(format!("Failed to lock NPU: {}", e)))?;
3375
3376            npu_lock
3377                .create_cortical_area_neurons(
3378                    cortical_idx,
3379                    area_data.0.width,
3380                    area_data.0.height,
3381                    area_data.0.depth,
3382                    area_data.1,
3383                    area_data.2,
3384                    area_data.3,
3385                    area_data.4,
3386                    area_data.5,
3387                    area_data.6,
3388                    area_data.7,
3389                    0.0,
3390                    0,
3391                    area_data.9,
3392                    area_data.8,
3393                    area_data.10,
3394                    area_data.11,
3395                    area_data.12,
3396                )
3397                .map_err(|e| ServiceError::Backend(format!("NPU neuron creation failed: {}", e)))?
3398        };
3399
3400        let creation_duration = creation_start.elapsed();
3401        info!(
3402            "[STRUCTURAL-REBUILD] Created {} neurons in {:.2}s (NPU lock held until completion)",
3403            neurons_created,
3404            creation_duration.as_secs_f64()
3405        );
3406
3407        if creation_duration.as_secs() > 1 {
3408            warn!(
3409                "[STRUCTURAL-REBUILD] ⚠️ Long creation time: {:.2}s - burst loop was blocked during this period",
3410                creation_duration.as_secs_f64()
3411            );
3412        }
3413
3414        // Step 5: Rebuild outgoing synapses (this area -> others)
3415        let outgoing_targets = {
3416            let genome_guard = genome_store.read();
3417            let genome = genome_guard
3418                .as_ref()
3419                .ok_or_else(|| ServiceError::Backend("No genome loaded".to_string()))?;
3420            let area = genome
3421                .cortical_areas
3422                .get(&cortical_id_typed)
3423                .ok_or_else(|| ServiceError::NotFound {
3424                    resource: "CorticalArea".to_string(),
3425                    id: cortical_id.to_string(),
3426                })?;
3427            let mut targets = Vec::new();
3428            if let Some(mapping) = area
3429                .properties
3430                .get("cortical_mapping_dst")
3431                .and_then(|v| v.as_object())
3432            {
3433                for key in mapping.keys() {
3434                    match CorticalID::try_from_base_64(key) {
3435                        Ok(dst_id) => targets.push(dst_id),
3436                        Err(err) => warn!(
3437                            target: "feagi-services",
3438                            "Invalid cortical_mapping_dst ID '{}': {}",
3439                            key,
3440                            err
3441                        ),
3442                    }
3443                }
3444            }
3445            targets
3446        };
3447        let outgoing_synapses = {
3448            let mut manager = connectome.write();
3449            let mut total = 0u32;
3450            for dst_id in outgoing_targets {
3451                if dst_id == cortical_id_typed {
3452                    continue;
3453                }
3454                let count = manager
3455                    .regenerate_synapses_for_mapping(&cortical_id_typed, &dst_id)
3456                    .map_err(|e| {
3457                        ServiceError::Backend(format!(
3458                            "Failed to rebuild outgoing synapses {} -> {}: {}",
3459                            cortical_id,
3460                            dst_id.as_base_64(),
3461                            e
3462                        ))
3463                    })?;
3464                total = total.saturating_add(count as u32);
3465            }
3466            total
3467        };
3468
3469        info!(
3470            "[STRUCTURAL-REBUILD] Rebuilt {} outgoing synapses",
3471            outgoing_synapses
3472        );
3473
3474        // Step 6: Rebuild incoming synapses (others -> this area)
3475        let incoming_synapses = {
3476            let genome_guard = genome_store.read();
3477            let genome = genome_guard.as_ref().unwrap();
3478
3479            let mut total = 0u32;
3480            for (src_id, src_area) in &genome.cortical_areas {
3481                if src_id == &cortical_id_typed {
3482                    continue; // Skip self (already handled in outgoing)
3483                }
3484
3485                // Check if this area maps to our target area
3486                if let Some(dstmap) = src_area.properties.get("cortical_mapping_dst") {
3487                    if let Some(obj) = dstmap.as_object() {
3488                        if obj.contains_key(cortical_id) {
3489                            // This area has mappings to our target - rebuild them
3490                            let mut manager = connectome.write();
3491                            let count = manager
3492                                .regenerate_synapses_for_mapping(src_id, &cortical_id_typed)
3493                                .map_err(|e| {
3494                                    ServiceError::Backend(format!(
3495                                        "Failed to rebuild incoming synapses from {}: {}",
3496                                        src_id, e
3497                                    ))
3498                                })?;
3499                            total = total.saturating_add(count as u32);
3500                            info!(
3501                                "[STRUCTURAL-REBUILD] Rebuilt {} incoming synapses from {}",
3502                                count, src_id
3503                            );
3504                        }
3505                    }
3506                }
3507            }
3508
3509            total
3510        };
3511
3512        info!(
3513            "[STRUCTURAL-REBUILD] Rebuilt {} total incoming synapses",
3514            incoming_synapses
3515        );
3516
3517        // Step 7: Rebuild synapse index to ensure all new synapses are visible to propagation engine
3518        // This is critical for large areas (e.g., 2M+ neurons) to prevent system hangs
3519        // IMPORTANT: Release connectome lock before acquiring NPU lock to avoid blocking other operations
3520        let npu_arc = {
3521            let manager = connectome.read();
3522            manager
3523                .get_npu()
3524                .ok_or_else(|| ServiceError::Backend("NPU not connected".to_string()))?
3525                .clone()
3526        };
3527
3528        // CRITICAL PERFORMANCE: Release lock between operations to allow burst loop to run
3529        // Event-based: Lock held only until each operation completes, then released
3530
3531        // CRITICAL PERFORMANCE: Event-based lock management - release lock after each operation completes
3532        // This allows burst loop to run between operations, keeping system responsive
3533
3534        info!(
3535            "[STRUCTURAL-REBUILD] Rebuilding synapse index for {} neurons...",
3536            neurons_created
3537        );
3538        let index_rebuild_start = std::time::Instant::now();
3539        {
3540            let mut npu_lock = npu_arc
3541                .lock()
3542                .map_err(|e| ServiceError::Backend(format!("Failed to lock NPU: {}", e)))?;
3543
3544            npu_lock.rebuild_synapse_index();
3545            // Lock released here when scope ends (event-based: operation 100% complete)
3546        }
3547        let index_rebuild_duration = index_rebuild_start.elapsed();
3548        info!(
3549            "[STRUCTURAL-REBUILD] Synapse index rebuild complete in {:.2}s (NPU lock released)",
3550            index_rebuild_duration.as_secs_f64()
3551        );
3552        if index_rebuild_duration.as_millis() > 100 {
3553            warn!(
3554                "[STRUCTURAL-REBUILD] ⚠️ Slow synapse index rebuild: {:.2}s",
3555                index_rebuild_duration.as_secs_f64()
3556            );
3557        }
3558
3559        // No power neuron cache rebuild needed - power neuron is always neuron ID 1 (deterministic)
3560        // Direct O(1) access in phase1_injection_with_synapses, no cache required!
3561
3562        info!(
3563            "[STRUCTURAL-REBUILD] ✅ Complete: {} neurons, {} outgoing, {} incoming synapses",
3564            neurons_created, outgoing_synapses, incoming_synapses
3565        );
3566
3567        // Refresh burst runner cache after structural rebuild (areas may have been resized)
3568        if let Some(ref burst_runner) = burst_runner {
3569            let manager = connectome.read();
3570            let mappings = manager.get_all_cortical_idx_to_id_mappings();
3571            let mapping_count = mappings.len();
3572            burst_runner.write().refresh_cortical_id_mappings(mappings);
3573            info!(target: "feagi-services", "Refreshed burst runner cache with {} cortical areas", mapping_count);
3574        }
3575
3576        // CRITICAL PERFORMANCE: For large areas, skip expensive get_synapse_count_in_area
3577        // which iterates through all neurons (5.7M!) holding the NPU lock for hundreds of ms
3578        // Use known synapse counts from rebuild instead (we just calculated them)
3579        if neurons_created > 100_000 {
3580            info!(
3581                "[STRUCTURAL-REBUILD] Using known counts for large area ({} neurons) to avoid expensive NPU lock",
3582                neurons_created
3583            );
3584            // Get area info but use known counts (avoid expensive NPU iteration)
3585            let manager = connectome.read();
3586            let area = manager
3587                .get_cortical_area(&cortical_id_typed)
3588                .ok_or_else(|| ServiceError::NotFound {
3589                    resource: "CorticalArea".to_string(),
3590                    id: cortical_id.to_string(),
3591                })?;
3592            let cortical_idx = manager
3593                .get_cortical_idx(&cortical_id_typed)
3594                .ok_or_else(|| ServiceError::NotFound {
3595                    resource: "CorticalArea".to_string(),
3596                    id: cortical_id.to_string(),
3597                })?;
3598
3599            // Use known counts from rebuild (no expensive NPU lock needed)
3600            let neuron_count = neurons_created as usize;
3601            let synapse_count = (outgoing_synapses + incoming_synapses) as usize;
3602            let incoming_synapse_count = incoming_synapses as usize;
3603            let outgoing_synapse_count = outgoing_synapses as usize;
3604            let cortical_group = area.get_cortical_group();
3605            let cortical_bytes = cortical_id_typed.as_bytes();
3606            let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
3607            let io_flag = if is_io_area {
3608                cortical_id_typed.extract_io_data_flag().ok()
3609            } else {
3610                None
3611            };
3612            let cortical_subtype = if is_io_area {
3613                String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
3614            } else {
3615                None
3616            };
3617            let unit_id = if is_io_area {
3618                Some(cortical_bytes[6])
3619            } else {
3620                None
3621            };
3622            let group_id = if is_io_area {
3623                Some(cortical_bytes[7])
3624            } else {
3625                None
3626            };
3627            let coding_signage = io_flag
3628                .as_ref()
3629                .map(|flag| signage_label_from_flag(flag).to_string());
3630            let coding_behavior = io_flag
3631                .as_ref()
3632                .map(|flag| behavior_label_from_flag(flag).to_string());
3633            let coding_type = io_flag
3634                .as_ref()
3635                .map(|flag| coding_type_label_from_flag(flag).to_string());
3636            let coding_options = if is_io_area {
3637                io_coding_options_for_unit(&cortical_id_typed)
3638            } else {
3639                None
3640            };
3641            let encoding_type = coding_behavior.clone();
3642            let encoding_format = coding_type.clone();
3643
3644            // Build full response using area properties (same as get_cortical_area_info_blocking)
3645            Ok(CorticalAreaInfo {
3646                cortical_id: area.cortical_id.as_base_64(),
3647                cortical_id_s: area.cortical_id.to_string(),
3648                cortical_idx,
3649                name: area.name.clone(),
3650                dimensions: (
3651                    area.dimensions.width as usize,
3652                    area.dimensions.height as usize,
3653                    area.dimensions.depth as usize,
3654                ),
3655                position: area.position.into(),
3656                area_type: cortical_group
3657                    .clone()
3658                    .unwrap_or_else(|| "CUSTOM".to_string()),
3659                cortical_group: cortical_group
3660                    .clone()
3661                    .unwrap_or_else(|| "CUSTOM".to_string()),
3662                cortical_type: {
3663                    use feagi_evolutionary::extract_memory_properties;
3664                    if extract_memory_properties(&area.properties).is_some() {
3665                        "memory".to_string()
3666                    } else if let Some(group) = &cortical_group {
3667                        match group.as_str() {
3668                            "IPU" => "sensory".to_string(),
3669                            "OPU" => "motor".to_string(),
3670                            "CORE" => "core".to_string(),
3671                            _ => "custom".to_string(),
3672                        }
3673                    } else {
3674                        "custom".to_string()
3675                    }
3676                },
3677                neuron_count,
3678                synapse_count,
3679                incoming_synapse_count,
3680                outgoing_synapse_count,
3681                visible: area.visible(),
3682                sub_group: area.sub_group(),
3683                neurons_per_voxel: area.neurons_per_voxel(),
3684                postsynaptic_current: area.postsynaptic_current() as f64,
3685                postsynaptic_current_max: area.postsynaptic_current_max() as f64,
3686                plasticity_constant: area.plasticity_constant() as f64,
3687                degeneration: area.degeneration() as f64,
3688                psp_uniform_distribution: area.psp_uniform_distribution(),
3689                mp_driven_psp: area.mp_driven_psp(),
3690                firing_threshold: area.firing_threshold() as f64,
3691                firing_threshold_increment: [
3692                    area.firing_threshold_increment_x() as f64,
3693                    area.firing_threshold_increment_y() as f64,
3694                    area.firing_threshold_increment_z() as f64,
3695                ],
3696                firing_threshold_limit: area.firing_threshold_limit() as f64,
3697                consecutive_fire_count: area.consecutive_fire_count(),
3698                snooze_period: area.snooze_period() as u32,
3699                refractory_period: area.refractory_period() as u32,
3700                leak_coefficient: area.leak_coefficient() as f64,
3701                leak_variability: area.leak_variability() as f64,
3702                mp_charge_accumulation: area.mp_charge_accumulation(),
3703                neuron_excitability: area.neuron_excitability() as f64,
3704                burst_engine_active: area.burst_engine_active(),
3705                init_lifespan: area.init_lifespan(),
3706                lifespan_growth_rate: area.lifespan_growth_rate() as f64,
3707                longterm_mem_threshold: area.longterm_mem_threshold(),
3708                temporal_depth: {
3709                    use feagi_evolutionary::extract_memory_properties;
3710                    extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
3711                },
3712                properties: HashMap::new(),
3713                cortical_subtype,
3714                encoding_type,
3715                encoding_format,
3716                unit_id,
3717                group_id,
3718                coding_signage,
3719                coding_behavior,
3720                coding_type,
3721                coding_options,
3722                parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
3723                dev_count: area
3724                    .properties
3725                    .get("dev_count")
3726                    .and_then(|v| v.as_u64().map(|n| n as usize)),
3727                cortical_dimensions_per_device: area
3728                    .properties
3729                    .get("cortical_dimensions_per_device")
3730                    .and_then(|v| v.as_array())
3731                    .and_then(|arr| {
3732                        if arr.len() == 3 {
3733                            Some((
3734                                arr[0].as_u64()? as usize,
3735                                arr[1].as_u64()? as usize,
3736                                arr[2].as_u64()? as usize,
3737                            ))
3738                        } else {
3739                            None
3740                        }
3741                    }),
3742                visualization_voxel_granularity: area
3743                    .properties
3744                    .get("visualization_voxel_granularity")
3745                    .and_then(|v| v.as_array())
3746                    .and_then(|arr| {
3747                        if arr.len() == 3 {
3748                            Some((
3749                                arr[0].as_u64()? as u32,
3750                                arr[1].as_u64()? as u32,
3751                                arr[2].as_u64()? as u32,
3752                            ))
3753                        } else {
3754                            None
3755                        }
3756                    }),
3757            })
3758        } else {
3759            // For smaller areas, use the full info retrieval
3760            Self::get_cortical_area_info_blocking(cortical_id, &connectome)
3761        }
3762    }
3763
3764    /// Helper to get cortical area info (blocking version for spawn_blocking contexts)
3765    fn get_cortical_area_info_blocking(
3766        cortical_id: &str,
3767        connectome: &Arc<RwLock<ConnectomeManager>>,
3768    ) -> ServiceResult<CorticalAreaInfo> {
3769        // Convert String to CorticalID
3770        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
3771            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
3772
3773        let manager = connectome.read();
3774
3775        let area = manager
3776            .get_cortical_area(&cortical_id_typed)
3777            .ok_or_else(|| ServiceError::NotFound {
3778                resource: "CorticalArea".to_string(),
3779                id: cortical_id.to_string(),
3780            })?;
3781
3782        let cortical_idx = manager
3783            .get_cortical_idx(&cortical_id_typed)
3784            .ok_or_else(|| ServiceError::NotFound {
3785                resource: "CorticalArea".to_string(),
3786                id: cortical_id.to_string(),
3787            })?;
3788
3789        let neuron_count = manager.get_neuron_count_in_area(&cortical_id_typed);
3790        let outgoing_synapse_count = manager.get_outgoing_synapse_count_in_area(&cortical_id_typed);
3791        let incoming_synapse_count = manager.get_incoming_synapse_count_in_area(&cortical_id_typed);
3792        let synapse_count = outgoing_synapse_count;
3793
3794        let cortical_group = area.get_cortical_group();
3795        let cortical_bytes = cortical_id_typed.as_bytes();
3796        let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
3797        let io_flag = if is_io_area {
3798            cortical_id_typed.extract_io_data_flag().ok()
3799        } else {
3800            None
3801        };
3802        let cortical_subtype = if is_io_area {
3803            String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
3804        } else {
3805            None
3806        };
3807        let unit_id = if is_io_area {
3808            Some(cortical_bytes[6])
3809        } else {
3810            None
3811        };
3812        let group_id = if is_io_area {
3813            Some(cortical_bytes[7])
3814        } else {
3815            None
3816        };
3817        let coding_signage = io_flag
3818            .as_ref()
3819            .map(|flag| signage_label_from_flag(flag).to_string());
3820        let coding_behavior = io_flag
3821            .as_ref()
3822            .map(|flag| behavior_label_from_flag(flag).to_string());
3823        let coding_type = io_flag
3824            .as_ref()
3825            .map(|flag| coding_type_label_from_flag(flag).to_string());
3826        let coding_options = if is_io_area {
3827            io_coding_options_for_unit(&cortical_id_typed)
3828        } else {
3829            None
3830        };
3831        let encoding_type = coding_behavior.clone();
3832        let encoding_format = coding_type.clone();
3833
3834        Ok(CorticalAreaInfo {
3835            cortical_id: area.cortical_id.as_base_64(),
3836            cortical_id_s: area.cortical_id.to_string(), // Human-readable ASCII string
3837            cortical_idx,
3838            name: area.name.clone(),
3839            dimensions: (
3840                area.dimensions.width as usize,
3841                area.dimensions.height as usize,
3842                area.dimensions.depth as usize,
3843            ),
3844            position: area.position.into(),
3845            area_type: cortical_group
3846                .clone()
3847                .unwrap_or_else(|| "CUSTOM".to_string()),
3848            cortical_group: cortical_group
3849                .clone()
3850                .unwrap_or_else(|| "CUSTOM".to_string()),
3851            // Determine cortical_type based on properties
3852            cortical_type: {
3853                use feagi_evolutionary::extract_memory_properties;
3854                if extract_memory_properties(&area.properties).is_some() {
3855                    "memory".to_string()
3856                } else if let Some(group) = &cortical_group {
3857                    match group.as_str() {
3858                        "IPU" => "sensory".to_string(),
3859                        "OPU" => "motor".to_string(),
3860                        "CORE" => "core".to_string(),
3861                        _ => "custom".to_string(),
3862                    }
3863                } else {
3864                    "custom".to_string()
3865                }
3866            },
3867            neuron_count,
3868            synapse_count,
3869            incoming_synapse_count,
3870            outgoing_synapse_count,
3871            visible: area.visible(),
3872            sub_group: area.sub_group(),
3873            neurons_per_voxel: area.neurons_per_voxel(),
3874            postsynaptic_current: area.postsynaptic_current() as f64,
3875            postsynaptic_current_max: area.postsynaptic_current_max() as f64,
3876            plasticity_constant: area.plasticity_constant() as f64,
3877            degeneration: area.degeneration() as f64,
3878            psp_uniform_distribution: area.psp_uniform_distribution(),
3879            mp_driven_psp: area.mp_driven_psp(),
3880            firing_threshold: area.firing_threshold() as f64,
3881            firing_threshold_increment: [
3882                area.firing_threshold_increment_x() as f64,
3883                area.firing_threshold_increment_y() as f64,
3884                area.firing_threshold_increment_z() as f64,
3885            ],
3886            firing_threshold_limit: area.firing_threshold_limit() as f64,
3887            consecutive_fire_count: area.consecutive_fire_count(),
3888            snooze_period: area.snooze_period() as u32,
3889            refractory_period: area.refractory_period() as u32,
3890            leak_coefficient: area.leak_coefficient() as f64,
3891            leak_variability: area.leak_variability() as f64,
3892            mp_charge_accumulation: area.mp_charge_accumulation(),
3893            neuron_excitability: area.neuron_excitability() as f64,
3894            burst_engine_active: area.burst_engine_active(),
3895            init_lifespan: area.init_lifespan(),
3896            lifespan_growth_rate: area.lifespan_growth_rate() as f64,
3897            longterm_mem_threshold: area.longterm_mem_threshold(),
3898            temporal_depth: {
3899                use feagi_evolutionary::extract_memory_properties;
3900                extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
3901            },
3902            properties: HashMap::new(),
3903            cortical_subtype,
3904            encoding_type,
3905            encoding_format,
3906            unit_id,
3907            group_id,
3908            coding_signage,
3909            coding_behavior,
3910            coding_type,
3911            coding_options,
3912            parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
3913            // Extract dev_count and cortical_dimensions_per_device from properties for IPU/OPU
3914            dev_count: area
3915                .properties
3916                .get("dev_count")
3917                .and_then(|v| v.as_u64().map(|n| n as usize)),
3918            cortical_dimensions_per_device: area
3919                .properties
3920                .get("cortical_dimensions_per_device")
3921                .and_then(|v| v.as_array())
3922                .and_then(|arr| {
3923                    if arr.len() == 3 {
3924                        Some((
3925                            arr[0].as_u64()? as usize,
3926                            arr[1].as_u64()? as usize,
3927                            arr[2].as_u64()? as usize,
3928                        ))
3929                    } else {
3930                        None
3931                    }
3932                }),
3933            visualization_voxel_granularity: area
3934                .properties
3935                .get("visualization_voxel_granularity")
3936                .and_then(|v| v.as_array())
3937                .and_then(|arr| {
3938                    if arr.len() == 3 {
3939                        Some((
3940                            arr[0].as_u64()? as u32,
3941                            arr[1].as_u64()? as u32,
3942                            arr[2].as_u64()? as u32,
3943                        ))
3944                    } else {
3945                        None
3946                    }
3947                }),
3948        })
3949    }
3950
3951    /// Helper to get cortical area info
3952    async fn get_cortical_area_info(&self, cortical_id: &str) -> ServiceResult<CorticalAreaInfo> {
3953        // Convert String to CorticalID
3954        let cortical_id_typed = CorticalID::try_from_base_64(cortical_id)
3955            .map_err(|e| ServiceError::InvalidInput(format!("Invalid cortical ID: {}", e)))?;
3956
3957        let manager = self.connectome.read();
3958
3959        let area = manager
3960            .get_cortical_area(&cortical_id_typed)
3961            .ok_or_else(|| ServiceError::NotFound {
3962                resource: "CorticalArea".to_string(),
3963                id: cortical_id.to_string(),
3964            })?;
3965
3966        // DIAGNOSTIC: Log the position, dimensions, and visibility of the area being queried
3967        tracing::info!(target: "feagi-services",
3968            "get_cortical_area_info: querying {} - position {:?}, dimensions {:?}, visible: {}",
3969            cortical_id, area.position, area.dimensions, area.visible()
3970        );
3971
3972        let cortical_idx = manager
3973            .get_cortical_idx(&cortical_id_typed)
3974            .ok_or_else(|| ServiceError::NotFound {
3975                resource: "CorticalArea".to_string(),
3976                id: cortical_id.to_string(),
3977            })?;
3978
3979        let neuron_count = manager.get_neuron_count_in_area(&cortical_id_typed);
3980        let outgoing_synapse_count = manager.get_outgoing_synapse_count_in_area(&cortical_id_typed);
3981        let incoming_synapse_count = manager.get_incoming_synapse_count_in_area(&cortical_id_typed);
3982        let synapse_count = outgoing_synapse_count;
3983
3984        // Get cortical_group from the area (uses cortical_type_new if available)
3985        let cortical_group = area.get_cortical_group();
3986        let cortical_bytes = cortical_id_typed.as_bytes();
3987        let is_io_area = cortical_bytes[0] == b'i' || cortical_bytes[0] == b'o';
3988        let io_flag = if is_io_area {
3989            cortical_id_typed.extract_io_data_flag().ok()
3990        } else {
3991            None
3992        };
3993        let cortical_subtype = if is_io_area {
3994            String::from_utf8(cortical_bytes[0..4].to_vec()).ok()
3995        } else {
3996            None
3997        };
3998        let unit_id = if is_io_area {
3999            Some(cortical_bytes[6])
4000        } else {
4001            None
4002        };
4003        let group_id = if is_io_area {
4004            Some(cortical_bytes[7])
4005        } else {
4006            None
4007        };
4008        let coding_signage = io_flag
4009            .as_ref()
4010            .map(|flag| signage_label_from_flag(flag).to_string());
4011        let coding_behavior = io_flag
4012            .as_ref()
4013            .map(|flag| behavior_label_from_flag(flag).to_string());
4014        let coding_type = io_flag
4015            .as_ref()
4016            .map(|flag| coding_type_label_from_flag(flag).to_string());
4017        let coding_options = if is_io_area {
4018            io_coding_options_for_unit(&cortical_id_typed)
4019        } else {
4020            None
4021        };
4022        let encoding_type = coding_behavior.clone();
4023        let encoding_format = coding_type.clone();
4024
4025        Ok(CorticalAreaInfo {
4026            cortical_id: area.cortical_id.as_base_64(),
4027            cortical_id_s: area.cortical_id.to_string(), // Human-readable ASCII string
4028            cortical_idx,
4029            name: area.name.clone(),
4030            dimensions: (
4031                area.dimensions.width as usize,
4032                area.dimensions.height as usize,
4033                area.dimensions.depth as usize,
4034            ),
4035            position: area.position.into(),
4036            area_type: cortical_group
4037                .clone()
4038                .unwrap_or_else(|| "CUSTOM".to_string()),
4039            cortical_group: cortical_group
4040                .clone()
4041                .unwrap_or_else(|| "CUSTOM".to_string()),
4042            // Determine cortical_type based on properties
4043            cortical_type: {
4044                use feagi_evolutionary::extract_memory_properties;
4045                if extract_memory_properties(&area.properties).is_some() {
4046                    "memory".to_string()
4047                } else if let Some(group) = &cortical_group {
4048                    match group.as_str() {
4049                        "IPU" => "sensory".to_string(),
4050                        "OPU" => "motor".to_string(),
4051                        "CORE" => "core".to_string(),
4052                        _ => "custom".to_string(),
4053                    }
4054                } else {
4055                    "custom".to_string()
4056                }
4057            },
4058            neuron_count,
4059            synapse_count,
4060            incoming_synapse_count,
4061            outgoing_synapse_count,
4062            visible: area.visible(),
4063            sub_group: area.sub_group(),
4064            neurons_per_voxel: area.neurons_per_voxel(),
4065            postsynaptic_current: area.postsynaptic_current() as f64,
4066            postsynaptic_current_max: area.postsynaptic_current_max() as f64,
4067            plasticity_constant: area.plasticity_constant() as f64,
4068            degeneration: area.degeneration() as f64,
4069            psp_uniform_distribution: area.psp_uniform_distribution(),
4070            mp_driven_psp: area.mp_driven_psp(),
4071            firing_threshold: area.firing_threshold() as f64,
4072            firing_threshold_increment: [
4073                area.firing_threshold_increment_x() as f64,
4074                area.firing_threshold_increment_y() as f64,
4075                area.firing_threshold_increment_z() as f64,
4076            ],
4077            firing_threshold_limit: area.firing_threshold_limit() as f64,
4078            consecutive_fire_count: area.consecutive_fire_count(),
4079            snooze_period: area.snooze_period() as u32,
4080            refractory_period: area.refractory_period() as u32,
4081            leak_coefficient: area.leak_coefficient() as f64,
4082            leak_variability: area.leak_variability() as f64,
4083            mp_charge_accumulation: area.mp_charge_accumulation(),
4084            neuron_excitability: area.neuron_excitability() as f64,
4085            burst_engine_active: area.burst_engine_active(),
4086            init_lifespan: area.init_lifespan(),
4087            lifespan_growth_rate: area.lifespan_growth_rate() as f64,
4088            longterm_mem_threshold: area.longterm_mem_threshold(),
4089            temporal_depth: {
4090                use feagi_evolutionary::extract_memory_properties;
4091                extract_memory_properties(&area.properties).map(|p| p.temporal_depth.max(1))
4092            },
4093            properties: HashMap::new(),
4094            cortical_subtype,
4095            encoding_type,
4096            encoding_format,
4097            unit_id,
4098            group_id,
4099            coding_signage,
4100            coding_behavior,
4101            coding_type,
4102            coding_options,
4103            parent_region_id: manager.get_parent_region_id_for_area(&cortical_id_typed),
4104            // Extract dev_count and cortical_dimensions_per_device from properties for IPU/OPU
4105            dev_count: area
4106                .properties
4107                .get("dev_count")
4108                .and_then(|v| v.as_u64().map(|n| n as usize)),
4109            cortical_dimensions_per_device: area
4110                .properties
4111                .get("cortical_dimensions_per_device")
4112                .and_then(|v| v.as_array())
4113                .and_then(|arr| {
4114                    if arr.len() == 3 {
4115                        Some((
4116                            arr[0].as_u64()? as usize,
4117                            arr[1].as_u64()? as usize,
4118                            arr[2].as_u64()? as usize,
4119                        ))
4120                    } else {
4121                        None
4122                    }
4123                }),
4124            visualization_voxel_granularity: area
4125                .properties
4126                .get("visualization_voxel_granularity")
4127                .and_then(|v| v.as_array())
4128                .and_then(|arr| {
4129                    if arr.len() == 3 {
4130                        Some((
4131                            arr[0].as_u64()? as u32,
4132                            arr[1].as_u64()? as u32,
4133                            arr[2].as_u64()? as u32,
4134                        ))
4135                    } else {
4136                        None
4137                    }
4138                }),
4139        })
4140    }
4141}