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