// Copyright 2025 Neuraville Inc.
// SPDX-License-Identifier: Apache-2.0

/*
 * Copyright 2025 Neuraville Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 */

//! # Complete Rust NPU
//!
//! Integrates all burst processing phases into a single high-performance NPU.
//!
//! ## Architecture
//! ```text
//! ┌─────────────────────────────────────┐
//! │ RustNPU                            │
//! ├─────────────────────────────────────┤
//! │ - NeuronArray                      │
//! │ - SynapseArray                     │
//! │ - FireCandidateList (FCL)          │
//! │ - FireQueue (current & previous)   │
//! │ - FireLedger                       │
//! │ - SynapticPropagationEngine        │
//! └─────────────────────────────────────┘
//!          ↓
//!     process_burst()
//!          ↓
//! Phase 1: Injection → Phase 2: Dynamics → Phase 3: Archival → Phase 5: Cleanup
//! ```

use crate::fire_ledger::FireLedger;
use crate::fire_structures::FireQueue;
use crate::fq_sampler::{FQSampler, SamplingMode};
use crate::neural_dynamics::*;
use crate::synaptic_propagation::SynapticPropagationEngine;
use ahash::AHashMap;
use ahash::AHashSet;
use feagi_npu_neural::types::*;
use feagi_structures::genomic::cortical_area::CorticalID;
use roaring::RoaringBitmap;
use tracing::{debug, error, info, trace, warn};

// Import Runtime trait for generic runtime abstraction
use feagi_npu_runtime::{NeuronStorage, Runtime, SynapseStorage};

// Import StdRuntime only for backward compatibility and tests
#[cfg(test)]
use feagi_npu_runtime::StdRuntime;

/// Type alias for fire queue sample data structure
type FireQueueSample = AHashMap<u32, (Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<f32>)>;

/// Key for a cortical mapping A→B (cortical_idx indices).
type CorticalMappingKey = (u32, u32);

/// STDP parameters for a plastic cortical mapping A→B.
///
/// Note: Synaptic weights are `u8` (0..255). Updates are additive and clamped.
#[derive(Debug, Clone, Copy)]
pub struct StdpMappingParams {
    pub plasticity_window: usize,
    pub plasticity_constant: i64,
    pub ltp_multiplier: i64,
    pub ltd_multiplier: i64,
}

impl StdpMappingParams {
    pub fn delta_plus_u8(&self) -> u8 {
        let delta = self.plasticity_constant.saturating_mul(self.ltp_multiplier);
        delta.clamp(0, u8::MAX as i64) as u8
    }

    pub fn delta_minus_u8(&self) -> u8 {
        let delta = self.plasticity_constant.saturating_mul(self.ltd_multiplier);
        delta.clamp(0, u8::MAX as i64) as u8
    }
}

/// Burst processing result
#[derive(Debug, Clone)]
pub struct BurstResult {
    /// Neurons that fired this burst
    pub fired_neurons: Vec<NeuronId>,

    /// Number of neurons that fired
    pub neuron_count: usize,

    /// Burst number
    pub burst: u64,

    /// Fire queue sample (for visualization/motor, built while lock is held)
    /// This avoids needing to acquire NPU lock again in burst loop
    pub fire_queue_sample: Option<FireQueueSample>,

    /// Performance metrics
    pub power_injections: usize,
    pub synaptic_injections: usize,
    pub neurons_processed: usize,
    pub neurons_in_refractory: usize,
}

/// Complete Rust Neural Processing Unit with Fine-Grained Locking
///
/// ## Generic Type Parameters
///
/// - **R: Runtime**: The runtime implementation (StdRuntime, EmbeddedRuntime, CudaRuntime, etc.)
/// - **T: NeuralValue**: The numeric type for membrane potentials, thresholds, and resting potentials
///   - `f32`: 32-bit floating point (default, highest precision)
///   - `INT8Value`: 8-bit integer (memory efficient, 42% memory reduction)
///   - `f16`: 16-bit floating point (future, GPU-optimized)
/// - **B: ComputeBackend**: The compute backend (CPUBackend, CUDABackend, WGPUBackend, etc.)
///
/// ## Locking Strategy (Performance-Critical)
///
/// This structure uses fine-grained locking to enable concurrent operations:
///
/// - **RwLock<R::NeuronStorage<T>>**: Multiple readers (burst processing reads many neurons),
///   exclusive writer (neurogenesis, parameter updates)
/// - **RwLock<R::SynapseStorage>**: Multiple readers (burst processing reads synapses),
///   exclusive writer (synaptogenesis, plasticity)
/// - **Mutex<FireStructures>**: Exclusive access (FCL clear, FQ swap, sensory injection)
/// - **AtomicU64**: Lock-free stats (burst_count, neuron_count, etc.)
///
/// ## Benefits
///
/// - Sensory injection locks only fire structures, not neurons/synapses
/// - Burst processing can read neurons while sensory data is being injected
/// - Visualization can sample fire queue without blocking burst processing
/// - Stats queries never block any operation
/// - No dynamic dispatch (backend is monomorphized at compile time)
///
/// ## Multi-Core Performance
///
/// With 30 Hz burst rate + 30 FPS video injection:
/// - **Before**: All operations serialized on one mutex (API unresponsive)
/// - **After**: Concurrent sensory injection + burst processing + API queries
pub struct RustNPU<
    R: Runtime,
    T: NeuralValue,
    B: crate::backend::ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>,
> {
    // Runtime (provides platform-specific storage)
    #[allow(dead_code)]
    runtime: std::sync::Arc<R>,

    // Core data structures (RwLock: many readers, one writer)
    pub(crate) neuron_storage: std::sync::RwLock<R::NeuronStorage<T>>,
    pub(crate) synapse_storage: std::sync::RwLock<R::SynapseStorage>,

    // Fire structures (Mutex: exclusive access for FCL/FQ operations)
    pub(crate) fire_structures: std::sync::Mutex<FireStructures>,

    // Cortical area mapping (RwLock: frequent reads, rare writes)
    pub(crate) area_id_to_name: std::sync::RwLock<AHashMap<u32, String>>,

    // Propagation engine (RwLock: burst reads, rare updates)
    pub(crate) propagation_engine: std::sync::RwLock<SynapticPropagationEngine>,

    // STDP configuration/index (RwLock: burst reads, rare writes on genome/mapping updates)
    pub(crate) stdp_mappings: std::sync::RwLock<AHashMap<CorticalMappingKey, StdpMappingParams>>,
    pub(crate) stdp_mapping_index: std::sync::RwLock<AHashMap<CorticalMappingKey, Vec<usize>>>,

    // Compute backend (Mutex: exclusive access during burst processing)
    // No longer Box<dyn> - monomorphized for better performance
    #[allow(dead_code)]
    pub(crate) backend: std::sync::Mutex<B>,

    // Atomic stats (lock-free reads)
    burst_count: std::sync::atomic::AtomicU64,

    // Configuration (AtomicU32 for f32 as u32 bits)
    power_amount: std::sync::atomic::AtomicU32, // f32::to_bits()

    // Fatigue state (atomic for lock-free reads during burst injection)
    fatigue_active: std::sync::atomic::AtomicBool,
    // REMOVED: power_neuron_cache - no longer needed with deterministic IDs
    // Power neuron is always neuron ID 1 (area 1 → neuron 1), so we can access it directly
}

/// Fire-related structures grouped together for single mutex
pub(crate) struct FireStructures {
    pub(crate) fire_candidate_list: FireCandidateList,
    pub(crate) current_fire_queue: FireQueue,
    pub(crate) previous_fire_queue: FireQueue,
    pub(crate) fire_ledger: FireLedger,
    pub(crate) fq_sampler: FQSampler,
    pub(crate) pending_sensory_injections: Vec<(NeuronId, f32)>,
    /// Staged memory neuron injections to be applied at the start of the next burst.
    ///
    /// Memory neurons live in a reserved ID range (50_000_000+) and are not present in the
    /// regular `NeuronStorage` array. They are executed via a dedicated forced-fire path
    /// in `neural_dynamics`.
    pub(crate) pending_memory_injections: Vec<(NeuronId, u32, f32)>, // (id, cortical_idx, potential)
    /// Per-burst metadata for memory neuron candidates (id -> cortical_idx).
    /// Populated during Phase 1 from `pending_memory_injections` and cleared each burst.
    pub(crate) memory_candidate_cortical_idx: AHashMap<u32, u32>,
    pub(crate) last_fcl_snapshot: Vec<(NeuronId, f32)>,
}

impl<
        R: Runtime,
        T: NeuralValue,
        B: crate::backend::ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>,
    > RustNPU<R, T, B>
{
    /// Create a new Rust NPU with specified capacities
    ///
    /// # Type Parameters
    /// - `R: Runtime`: Runtime implementation (StdRuntime, EmbeddedRuntime, etc.)
    /// - `T: NeuralValue`: Numeric type for membrane potentials (f32, INT8Value, etc.)
    /// - `B: ComputeBackend`: Compute backend implementation (CPUBackend, CUDABackend, etc.)
    ///
    /// # Arguments
    /// * `runtime` - Runtime implementation providing storage
    /// * `backend` - Compute backend for processing
    /// * `neuron_capacity` - Maximum number of neurons
    /// * `synapse_capacity` - Maximum number of synapses
    /// * `fire_ledger_window` - Fire ledger history window size
    pub fn new(
        runtime: R,
        backend: B,
        neuron_capacity: usize,
        synapse_capacity: usize,
        fire_ledger_window: usize,
    ) -> Result<Self> {
        // Create storage using runtime
        let neuron_storage = runtime
            .create_neuron_storage(neuron_capacity)
            .map_err(|e| {
                FeagiError::RuntimeError(format!("Failed to create neuron storage: {:?}", e))
            })?;
        let synapse_storage = runtime
            .create_synapse_storage(synapse_capacity)
            .map_err(|e| {
                FeagiError::RuntimeError(format!("Failed to create synapse storage: {:?}", e))
            })?;

        Ok(Self {
            runtime: std::sync::Arc::new(runtime),
            neuron_storage: std::sync::RwLock::new(neuron_storage),
            synapse_storage: std::sync::RwLock::new(synapse_storage),
            fire_structures: std::sync::Mutex::new(FireStructures {
                fire_candidate_list: FireCandidateList::new(),
                current_fire_queue: FireQueue::new(),
                previous_fire_queue: FireQueue::new(),
                fire_ledger: FireLedger::new(fire_ledger_window),
                fq_sampler: FQSampler::new(1000.0, SamplingMode::Unified),
                pending_sensory_injections: Vec::with_capacity(10000),
                pending_memory_injections: Vec::with_capacity(1024),
                memory_candidate_cortical_idx: AHashMap::new(),
                last_fcl_snapshot: Vec::new(),
            }),
            area_id_to_name: std::sync::RwLock::new(AHashMap::new()),
            propagation_engine: std::sync::RwLock::new(SynapticPropagationEngine::new()),
            stdp_mappings: std::sync::RwLock::new(AHashMap::new()),
            stdp_mapping_index: std::sync::RwLock::new(AHashMap::new()),
            backend: std::sync::Mutex::new(backend),
            burst_count: std::sync::atomic::AtomicU64::new(0),
            power_amount: std::sync::atomic::AtomicU32::new(1.0f32.to_bits()),
            fatigue_active: std::sync::atomic::AtomicBool::new(false),
            // No power neuron cache needed - using deterministic ID (neuron 1)
        })
    }
}

// Test helper methods (only in test builds)
#[cfg(test)]
impl RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend> {
    /// Create a CPU-only NPU for testing with f32 precision (convenience method)
    pub fn new_cpu_only(
        neuron_capacity: usize,
        synapse_capacity: usize,
        fire_ledger_window: usize,
    ) -> Self {
        use crate::backend::CPUBackend;
        use feagi_npu_runtime::StdRuntime;

        let runtime = StdRuntime;
        let backend = CPUBackend::new();

        Self::new(
            runtime,
            backend,
            neuron_capacity,
            synapse_capacity,
            fire_ledger_window,
        )
        .expect("Failed to create test NPU")
    }
}

#[cfg(test)]
impl RustNPU<feagi_npu_runtime::StdRuntime, INT8Value, crate::backend::CPUBackend> {
    /// Create a CPU-only NPU for testing with INT8 precision (convenience method)
    pub fn new_cpu_only(
        neuron_capacity: usize,
        synapse_capacity: usize,
        fire_ledger_window: usize,
    ) -> Self {
        use crate::backend::CPUBackend;
        use feagi_npu_runtime::StdRuntime;

        let runtime = StdRuntime;
        let backend = CPUBackend::new();

        Self::new(
            runtime,
            backend,
            neuron_capacity,
            synapse_capacity,
            fire_ledger_window,
        )
        .expect("Failed to create test NPU")
    }
}

// Backward compatibility: StdRuntime with f32 and CPUBackend
// This is DEPRECATED and only provided for backward compatibility.
// New code should use the generic `RustNPU::new()` and pass the runtime explicitly.
#[cfg(test)]
impl RustNPU<StdRuntime, f32, crate::backend::CPUBackend> {
    /// Create a new Rust NPU with StdRuntime, f32, and CPU backend (backward compatible)
    ///
    /// # Deprecation Notice
    /// This method is DEPRECATED. Use the generic `RustNPU::new()` instead:
    ///
    /// ```ignore
    /// use feagi_npu_runtime::StdRuntime;
    /// use feagi_npu_burst_engine::RustNPU;
    ///
    /// let runtime = StdRuntime::new();
    /// let backend = CPUBackend::new();
    /// let npu = RustNPU::new(runtime, backend, 1000, 10000, 20)?;
    /// ```
    pub fn new_std_cpu(
        neuron_capacity: usize,
        synapse_capacity: usize,
        fire_ledger_window: usize,
    ) -> Result<Self> {
        let backend = crate::backend::CPUBackend::new();
        Self::new(
            StdRuntime::new(),
            backend,
            neuron_capacity,
            synapse_capacity,
            fire_ledger_window,
        )
    }
}

impl<
        R: Runtime,
        T: NeuralValue,
        B: crate::backend::ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>,
    > RustNPU<R, T, B>
{
    /// Set power injection amount (lock-free atomic operation)
    pub fn set_power_amount(&self, amount: f32) {
        self.power_amount
            .store(amount.to_bits(), std::sync::atomic::Ordering::Relaxed);
    }

    /// Get power injection amount (lock-free atomic operation)
    pub fn get_power_amount(&self) -> f32 {
        f32::from_bits(self.power_amount.load(std::sync::atomic::Ordering::Relaxed))
    }

    /// Set fatigue active state (lock-free atomic operation)
    pub fn set_fatigue_active(&self, active: bool) {
        self.fatigue_active
            .store(active, std::sync::atomic::Ordering::Release);
    }

    /// Get fatigue active state (lock-free atomic operation)
    pub fn is_fatigue_active(&self) -> bool {
        self.fatigue_active
            .load(std::sync::atomic::Ordering::Acquire)
    }

    /// Get burst count (lock-free atomic operation)
    pub fn get_burst_count(&self) -> u64 {
        self.burst_count.load(std::sync::atomic::Ordering::Relaxed)
    }

    /// Increment burst count (lock-free atomic operation)
    fn increment_burst_count(&self) -> u64 {
        self.burst_count
            .fetch_add(1, std::sync::atomic::Ordering::Relaxed)
            + 1
    }

    /// Add a neuron to the NPU (LIF model with genome leak only)
    #[allow(clippy::too_many_arguments)]
    pub fn add_neuron(
        &mut self,
        threshold: T,          // Quantized threshold
        threshold_limit: T,    // Quantized threshold limit (MAX = no limit, SIMD-friendly encoding)
        leak_coefficient: f32, // Kept as f32 for precision
        resting_potential: T,  // Quantized resting potential
        neuron_type: i32,
        refractory_period: u16,
        excitability: f32,
        consecutive_fire_limit: u16,
        snooze_period: u16,
        mp_charge_accumulation: bool,
        cortical_area: u32,
        x: u32,
        y: u32,
        z: u32,
    ) -> Result<NeuronId> {
        let neuron_idx = self
            .neuron_storage
            .write()
            .unwrap()
            .add_neuron(
                threshold,
                threshold_limit,
                leak_coefficient,
                resting_potential,
                neuron_type,
                refractory_period,
                excitability,
                consecutive_fire_limit,
                snooze_period,
                mp_charge_accumulation,
                cortical_area,
                x,
                y,
                z,
            )
            .map_err(|e| FeagiError::RuntimeError(format!("Failed to add neuron: {:?}", e)))?;

        let neuron_id = NeuronId(neuron_idx as u32);

        // CRITICAL: Add to propagation engine's neuron-to-area mapping
        // Get the cortical ID from the area_id_to_name mapping
        let area_name = self
            .area_id_to_name
            .read()
            .unwrap()
            .get(&cortical_area)
            .ok_or_else(|| {
                FeagiError::ComputationError(format!(
                    "No cortical area registered for index {}",
                    cortical_area
                ))
            })?
            .clone();
        let cortical_id = CorticalID::try_from_base_64(&area_name).map_err(|e| {
            FeagiError::ComputationError(format!(
                "Failed to convert area name '{}' to CorticalID: {}",
                area_name, e
            ))
        })?;
        self.propagation_engine
            .write()
            .unwrap()
            .neuron_to_area
            .insert(neuron_id, cortical_id);

        // No cache needed - power neuron is always neuron ID 1 (deterministic)

        Ok(neuron_id)
    }

    /// Batch add neurons (optimized for neurogenesis)
    ///
    /// Creates multiple neurons in a single operation with optimal performance.
    /// This is 50-100x faster than calling add_neuron() in a loop.
    ///
    /// Performance benefits:
    /// - Single function call overhead (vs N calls)
    /// - Single lock acquisition (vs N locks from Python)
    /// - Contiguous SoA memory writes
    /// - Batch propagation engine updates
    ///
    /// Returns: (neuron_ids, failed_indices)
    #[allow(clippy::too_many_arguments)]
    pub fn add_neurons_batch(
        &mut self,
        thresholds: Vec<T>,          // Quantized thresholds
        threshold_limits: Vec<T>, // Quantized threshold limits (MAX = no limit, SIMD-friendly encoding)
        leak_coefficients: Vec<f32>, // Kept as f32 for precision
        resting_potentials: Vec<T>, // Quantized resting potentials
        neuron_types: Vec<i32>,
        refractory_periods: Vec<u16>,
        excitabilities: Vec<f32>,
        consecutive_fire_limits: Vec<u16>,
        snooze_periods: Vec<u16>,
        mp_charge_accumulations: Vec<bool>,
        cortical_areas: Vec<u32>,
        x_coords: Vec<u32>,
        y_coords: Vec<u32>,
        z_coords: Vec<u32>,
    ) -> (u32, Vec<usize>) {
        let n = x_coords.len();

        // Get the starting neuron index before adding neurons
        let start_idx = self.neuron_storage.read().unwrap().count();

        // Call the TRUE batch method on neuron_storage (100-1000x faster!)
        match self.neuron_storage.write().unwrap().add_neurons_batch(
            &thresholds,
            &threshold_limits,
            &leak_coefficients,
            &resting_potentials,
            &neuron_types,
            &refractory_periods,
            &excitabilities,
            &consecutive_fire_limits,
            &snooze_periods,
            &mp_charge_accumulations,
            &cortical_areas,
            &x_coords,
            &y_coords,
            &z_coords,
        ) {
            Ok(()) => {
                // Generate neuron IDs based on the starting index
                let neuron_ids: Vec<NeuronId> = (start_idx..start_idx + n)
                    .map(|idx| NeuronId(idx as u32))
                    .collect();

                // BULK update propagation engine's neuron-to-area mapping
                // Reserve capacity upfront to minimize rehashing
                use std::time::Instant;
                let prop_start = Instant::now();
                self.propagation_engine
                    .write()
                    .unwrap()
                    .neuron_to_area
                    .reserve(n);
                let reserve_time = prop_start.elapsed();

                let insert_start = Instant::now();
                // Get the cortical ID from the area_id_to_name mapping
                // cortical_areas[i] is a numeric index, we need to look up the actual CorticalID string
                let area_name_map = self.area_id_to_name.read().unwrap();
                for (i, neuron_id) in neuron_ids.iter().enumerate() {
                    if let Some(area_name) = area_name_map.get(&cortical_areas[i]) {
                        if let Ok(cortical_id) = CorticalID::try_from_base_64(area_name) {
                            self.propagation_engine
                                .write()
                                .unwrap()
                                .neuron_to_area
                                .insert(*neuron_id, cortical_id);
                        } else {
                            tracing::error!(
                                "Failed to convert area name '{}' to CorticalID",
                                area_name
                            );
                        }
                    } else {
                        tracing::error!(
                            "No cortical area registered for index {}",
                            cortical_areas[i]
                        );
                    }
                }
                let insert_time = insert_start.elapsed();

                debug!(
                    n,
                    reserve_ns = reserve_time.as_nanos(),
                    inserts_ns = insert_time.as_nanos(),
                    mapping_size = self
                        .propagation_engine
                        .write()
                        .unwrap()
                        .neuron_to_area
                        .len(),
                    "[PROP-ENGINE] Neuron-to-area mapping updated"
                );

                // CRITICAL PERFORMANCE: Cache power neurons (cortical_area = 1) to avoid scanning all neurons
                // This is especially important for large neuron counts (3M+) where scanning is expensive
                // No cache needed - power neuron is always neuron ID 1 (deterministic)

                // NOTE: Cache will be built lazily on first access to get_neurons_in_cortical_area
                // To optimize further, call prepopulate_cortical_area_cache() after genome load

                // ✅ ARCHITECTURE FIX: Return only success COUNT, not full Vec<u32> of IDs
                // Python doesn't need IDs - Rust owns all neuron data!
                // This eliminates expensive PyO3 Vec→list conversion (was 4s bottleneck!)
                (neuron_ids.len() as u32, Vec::new())
            }
            Err(_) => {
                // All failed - return 0 success count and all indices as failed
                (0, (0..n).collect())
            }
        }
    }

    /// Create neurons for a cortical area with uniform properties
    ///
    /// This is the CORRECT architecture - Python passes only scalars, Rust generates everything
    ///
    /// # Arguments
    /// * `cortical_idx` - Cortical area index
    /// * `width` - X dimension
    /// * `height` - Y dimension
    /// * `depth` - Z dimension
    /// * `neurons_per_voxel` - Neurons per spatial position
    /// * `default_threshold` - Base firing threshold at (0,0,0) position
    /// * `threshold_increment_x` - Threshold increment per X position
    /// * `threshold_increment_y` - Threshold increment per Y position
    /// * `threshold_increment_z` - Threshold increment per Z position
    /// * `default_threshold_limit` - Default firing threshold limit (maximum MP to fire, MAX = no limit, SIMD-friendly encoding)
    /// * `default_leak_coefficient` - Default leak rate
    /// * `default_resting_potential` - Default resting potential
    /// * `default_neuron_type` - Default neuron type
    /// * `default_refractory_period` - Default refractory period
    /// * `default_excitability` - Default excitability
    /// * `default_consecutive_fire_limit` - Default consecutive fire limit
    /// * `default_snooze_period` - Default snooze period
    /// * `default_mp_charge_accumulation` - Default MP charge accumulation flag
    ///
    /// # Returns
    /// * `Ok(count)` - Number of neurons created
    /// * `Err` - If capacity exceeded or other error
    #[allow(clippy::too_many_arguments)]
    pub fn create_cortical_area_neurons(
        &mut self,
        cortical_idx: u32,
        width: u32,
        height: u32,
        depth: u32,
        neurons_per_voxel: u32,
        default_threshold: f32,
        threshold_increment_x: f32,
        threshold_increment_y: f32,
        threshold_increment_z: f32,
        default_threshold_limit: f32,
        default_leak_coefficient: f32,
        default_resting_potential: f32,
        default_neuron_type: i32,
        default_refractory_period: u16,
        default_excitability: f32,
        default_consecutive_fire_limit: u16,
        default_snooze_period: u16,
        default_mp_charge_accumulation: bool,
    ) -> Result<u32> {
        // Call with z_offset=0 (default behavior)
        self.create_cortical_area_neurons_with_z_offset(
            cortical_idx,
            width,
            height,
            depth,
            neurons_per_voxel,
            default_threshold,
            threshold_increment_x,
            threshold_increment_y,
            threshold_increment_z,
            default_threshold_limit,
            default_leak_coefficient,
            default_resting_potential,
            default_neuron_type,
            default_refractory_period,
            default_excitability,
            default_consecutive_fire_limit,
            default_snooze_period,
            default_mp_charge_accumulation,
            0, // z_offset = 0 (start at z=0)
        )
    }

    /// Create neurons for a cortical area with optional z-offset (for batched creation)
    /// z_offset: Starting z-coordinate (allows creating neurons at specific depth layers)
    /// y_offset: Starting y-coordinate (allows creating neurons at specific row ranges)
    /// This enables batched neuron creation that releases the NPU lock between batches
    #[allow(clippy::too_many_arguments)]
    pub fn create_cortical_area_neurons_with_z_offset(
        &mut self,
        cortical_idx: u32,
        width: u32,
        height: u32,
        depth: u32,
        neurons_per_voxel: u32,
        default_threshold: f32,
        threshold_increment_x: f32,
        threshold_increment_y: f32,
        threshold_increment_z: f32,
        default_threshold_limit: f32,
        default_leak_coefficient: f32,
        default_resting_potential: f32,
        default_neuron_type: i32,
        default_refractory_period: u16,
        default_excitability: f32,
        default_consecutive_fire_limit: u16,
        default_snooze_period: u16,
        default_mp_charge_accumulation: bool,
        z_offset: u32,
    ) -> Result<u32> {
        // Call with y_offset=0 (default behavior)
        self.create_cortical_area_neurons_with_offsets(
            cortical_idx,
            width,
            height,
            depth,
            neurons_per_voxel,
            default_threshold,
            threshold_increment_x,
            threshold_increment_y,
            threshold_increment_z,
            default_threshold_limit,
            default_leak_coefficient,
            default_resting_potential,
            default_neuron_type,
            default_refractory_period,
            default_excitability,
            default_consecutive_fire_limit,
            default_snooze_period,
            default_mp_charge_accumulation,
            0, // y_offset = 0
            z_offset,
        )
    }

    /// Create neurons for a cortical area with optional y and z offsets (for batched creation)
    /// y_offset: Starting y-coordinate (allows creating neurons at specific row ranges)
    /// z_offset: Starting z-coordinate (allows creating neurons at specific depth layers)
    /// This enables fine-grained batched neuron creation that releases the NPU lock frequently
    #[allow(clippy::too_many_arguments)]
    pub fn create_cortical_area_neurons_with_offsets(
        &mut self,
        cortical_idx: u32,
        width: u32,
        height: u32,
        depth: u32,
        neurons_per_voxel: u32,
        default_threshold: f32,
        threshold_increment_x: f32,
        threshold_increment_y: f32,
        threshold_increment_z: f32,
        default_threshold_limit: f32,
        default_leak_coefficient: f32,
        default_resting_potential: f32,
        default_neuron_type: i32,
        default_refractory_period: u16,
        default_excitability: f32,
        default_consecutive_fire_limit: u16,
        default_snooze_period: u16,
        default_mp_charge_accumulation: bool,
        y_offset: u32,
        z_offset: u32,
    ) -> Result<u32> {
        use std::time::Instant;
        let fn_start = Instant::now();

        // Calculate total neurons
        let total_neurons = (width * height * depth * neurons_per_voxel) as usize;

        // Performance diagnostic - only visible with --debug flag
        debug!(
            cortical_idx,
            total_neurons, "[NEUROGENESIS] Creating neurons for cortical area"
        );

        if total_neurons == 0 {
            return Ok(0);
        }

        let alloc_start = Instant::now();
        // ✅ SPATIAL GRADIENT: Calculate thresholds based on (x,y,z) position
        // Base threshold at (0,0,0), incremented by position dot product with increment vector
        // threshold(x,y,z) = base + x*increment_x + y*increment_y + z*increment_z
        let mut thresholds = Vec::with_capacity(total_neurons);
        let mut threshold_limits = Vec::with_capacity(total_neurons);

        // Pre-compute if we have spatial gradients
        let has_x_gradient = threshold_increment_x.abs() > f32::EPSILON;
        let has_y_gradient = threshold_increment_y.abs() > f32::EPSILON;
        let has_z_gradient = threshold_increment_z.abs() > f32::EPSILON;
        let has_any_gradient = has_x_gradient || has_y_gradient || has_z_gradient;

        if has_any_gradient {
            // Calculate position-based thresholds (spatial gradient)
            for x in 0..width {
                for y in 0..height {
                    for z in 0..depth {
                        // Calculate threshold for this voxel position (with y_offset and z_offset)
                        let actual_y = y + y_offset;
                        let actual_z = z + z_offset;
                        let threshold_at_pos = default_threshold
                            + (x as f32 * threshold_increment_x)
                            + (actual_y as f32 * threshold_increment_y)
                            + (actual_z as f32 * threshold_increment_z);

                        // Apply to all neurons in this voxel
                        for _ in 0..neurons_per_voxel {
                            thresholds.push(T::from_f32(threshold_at_pos));
                            // SIMD-friendly encoding: 0.0 means no limit, convert to MAX
                            let threshold_limit = if default_threshold_limit == 0.0 {
                                T::max_value()
                            } else {
                                T::from_f32(default_threshold_limit)
                            };
                            threshold_limits.push(threshold_limit);
                        }
                    }
                }
            }
        } else {
            // Uniform thresholds (no spatial gradient) - faster path
            thresholds.resize(total_neurons, T::from_f32(default_threshold));
            // SIMD-friendly encoding: 0.0 means no limit, convert to MAX
            let threshold_limit = if default_threshold_limit == 0.0 {
                T::max_value()
            } else {
                T::from_f32(default_threshold_limit)
            };
            threshold_limits.resize(total_neurons, threshold_limit);
        }

        // ✅ SIMD-OPTIMIZED: Fill uniform values with bulk operations (LLVM auto-vectorizes!)
        let leak_coefficients = vec![default_leak_coefficient; total_neurons];
        let resting_potentials = vec![T::from_f32(default_resting_potential); total_neurons];
        let neuron_types = vec![default_neuron_type; total_neurons];
        let refractory_periods = vec![default_refractory_period; total_neurons];
        let excitabilities = vec![default_excitability; total_neurons];
        // SIMD-friendly encoding: 0 means no limit, convert to MAX
        let consecutive_fire_limit = if default_consecutive_fire_limit == 0 {
            u16::MAX
        } else {
            default_consecutive_fire_limit
        };
        let consecutive_fire_limits = vec![consecutive_fire_limit; total_neurons];
        let snooze_periods = vec![default_snooze_period; total_neurons];
        let mp_charge_accumulations = vec![default_mp_charge_accumulation; total_neurons];
        let cortical_areas = vec![cortical_idx; total_neurons];

        // ✅ OPTIMIZED: Pre-size coordinate vectors, fill with direct indexing (no bounds checking!)
        let mut x_coords = vec![0u32; total_neurons];
        let mut y_coords = vec![0u32; total_neurons];
        let mut z_coords = vec![0u32; total_neurons];

        // Generate coordinates in cache-friendly order with direct writes (with y_offset and z_offset)
        let mut idx = 0;
        for x in 0..width {
            for y in 0..height {
                for z in 0..depth {
                    for _ in 0..neurons_per_voxel {
                        x_coords[idx] = x;
                        y_coords[idx] = y + y_offset; // Apply y_offset to actual y-coordinate
                        z_coords[idx] = z + z_offset; // Apply z_offset to actual z-coordinate
                        idx += 1;
                    }
                }
            }
        }
        let alloc_time = alloc_start.elapsed();

        let batch_start = Instant::now();
        // Call existing batch creation (already optimized with SIMD)
        let (success_count, failed) = self.add_neurons_batch(
            thresholds,
            threshold_limits,
            leak_coefficients,
            resting_potentials,
            neuron_types,
            refractory_periods,
            excitabilities,
            consecutive_fire_limits,
            snooze_periods,
            mp_charge_accumulations,
            cortical_areas,
            x_coords,
            y_coords,
            z_coords,
        );

        let batch_time = batch_start.elapsed();
        let total_time = fn_start.elapsed();

        // Performance metrics - only visible with --debug flag
        debug!(
            total_neurons,
            alloc_us = alloc_time.as_micros(),
            batch_us = batch_time.as_micros(),
            total_us = total_time.as_micros(),
            "[NEUROGENESIS] Neuron creation timing"
        );

        if !failed.is_empty() {
            let current_count = self.neuron_storage.read().unwrap().count();
            let capacity = self.neuron_storage.read().unwrap().capacity();
            return Err(FeagiError::ComputationError(format!(
                "Failed to create {} neurons (requested: {}, succeeded: {}, current: {}/{} capacity) - NPU CAPACITY EXCEEDED",
                failed.len(),
                total_neurons,
                success_count,
                current_count,
                capacity
            )));
        }

        Ok(success_count)
    }

    /// Add a synapse to the NPU
    pub fn add_synapse(
        &mut self,
        source: NeuronId,
        target: NeuronId,
        weight: SynapticWeight,
        conductance: SynapticConductance,
        synapse_type: SynapseType,
    ) -> Result<usize> {
        let result = self
            .synapse_storage
            .write()
            .unwrap()
            .add_synapse(
                source.0,
                target.0,
                weight.0,
                conductance.0,
                synapse_type as u8,
            )
            .map_err(|e| FeagiError::RuntimeError(format!("Failed to add synapse: {:?}", e)))?;

        // NOTE: This does NOT rebuild the synapse index for performance reasons.
        // Callers should call rebuild_synapse_index() once after bulk synapse additions.
        // For single synapse additions in tests, call rebuild_synapse_index() manually.

        Ok(result)
    }

    /// Batch add synapses (SIMD-optimized)
    ///
    /// Creates multiple synapses in a single operation with optimal performance.
    /// This is 50-100x faster than calling add_synapse() in a loop.
    ///
    /// Performance:
    /// - Single function call overhead (vs N calls)
    /// - Contiguous SoA memory writes
    /// - Batch source_index updates
    ///
    /// Returns: (successful_count, failed_indices)
    pub fn add_synapses_batch(
        &mut self,
        sources: Vec<NeuronId>,
        targets: Vec<NeuronId>,
        weights: Vec<SynapticWeight>,
        postsynaptic_potentials: Vec<SynapticConductance>, // TODO: Rename type to SynapticPSP
        synapse_types: Vec<SynapseType>,
    ) -> Result<()> {
        // Convert NeuronId/Weight types to raw u32/u8 for SynapseArray
        let source_ids: Vec<u32> = sources.iter().map(|n| n.0).collect();
        let target_ids: Vec<u32> = targets.iter().map(|n| n.0).collect();
        let weight_vals: Vec<u8> = weights.iter().map(|w| w.0).collect();
        let psp_vals: Vec<u8> = postsynaptic_potentials.iter().map(|c| c.0).collect();
        let type_vals: Vec<u8> = synapse_types
            .iter()
            .map(|t| match t {
                SynapseType::Excitatory => 0,
                SynapseType::Inhibitory => 1,
            })
            .collect();

        self.synapse_storage
            .write()
            .unwrap()
            .add_synapses_batch(
                &source_ids,
                &target_ids,
                &weight_vals,
                &psp_vals,
                &type_vals,
            )
            .map_err(|e| FeagiError::RuntimeError(format!("Failed to add synapses batch: {:?}", e)))
    }

    /// Remove a synapse by source and target
    ///
    /// Remove a synapse by source and target
    ///
    /// NOTE: This does NOT rebuild the synapse index for performance reasons.
    /// Callers should call rebuild_synapse_index() once after bulk removals.
    /// Until rebuilt, propagation engine will skip deleted synapses via valid_mask check,
    /// but this wastes CPU cycles iterating stale index entries.
    ///
    /// Note: This searches for the synapse index first, which is O(n).
    /// For better performance, use remove_synapses_between() for batch operations.
    pub fn remove_synapse(&mut self, source: NeuronId, target: NeuronId) -> bool {
        let synapse_storage = self.synapse_storage.read().unwrap();
        // Find synapse index by searching for matching source and target
        let idx = synapse_storage
            .source_neurons()
            .iter()
            .zip(synapse_storage.target_neurons().iter())
            .enumerate()
            .find(|(_, (&s, &t))| s == source.0 && t == target.0)
            .map(|(idx, _)| idx);
        drop(synapse_storage);

        if let Some(idx) = idx {
            self.synapse_storage
                .write()
                .unwrap()
                .remove_synapse(idx)
                .map(|_| true)
                .unwrap_or(false)
        } else {
            false
        }
    }

    /// Batch remove all synapses from specified source neurons (SIMD-optimized)
    ///
    /// Performance: 50-100x faster than individual deletions for cortical mapping removal
    /// Returns: number of synapses deleted
    pub fn remove_synapses_from_sources(&mut self, sources: Vec<NeuronId>) -> usize {
        let source_ids: Vec<u32> = sources.iter().map(|n| n.0).collect();
        self.synapse_storage
            .write()
            .unwrap()
            .remove_synapses_from_sources(&source_ids)
            .unwrap_or_else(|e| {
                panic!(
                    "Invariant violation: remove_synapses_from_sources failed (sources={}): {e:?}",
                    source_ids.len()
                )
            })
    }

    /// Batch remove synapses between source and target neuron sets
    ///
    /// NOTE: This does NOT rebuild the synapse index. Call rebuild_synapse_index() after bulk removals.
    ///
    /// Note: The trait method only supports single source-target pairs.
    /// This method calls remove_synapses_between() for each source-target combination.
    ///
    /// Returns: number of synapses deleted
    pub fn remove_synapses_between(
        &mut self,
        sources: Vec<NeuronId>,
        targets: Vec<NeuronId>,
    ) -> usize {
        let mut total_removed = 0;
        let mut synapse_storage = self.synapse_storage.write().unwrap();
        for &source in &sources {
            for &target in &targets {
                if let Ok(removed) = synapse_storage.remove_synapses_between(source.0, target.0) {
                    total_removed += removed;
                }
            }
        }
        total_removed
    }

    /// Efficiently remove synapses from a set of source neurons to a set of target neurons.
    ///
    /// This is intended for cortical mapping updates/removals where synapses must be pruned
    /// deterministically without destroying other outgoing synapses from the same source area.
    ///
    /// Implementation detail:
    /// - Uses the propagation engine's `synapse_index` to iterate only synapses reachable from
    ///   the given source neurons.
    /// - Marks matching synapses invalid via `valid_mask` (no compaction).
    ///
    /// NOTE: This does NOT rebuild the synapse index. Call `rebuild_synapse_index()` after bulk removals.
    pub fn remove_synapses_from_sources_to_targets(
        &mut self,
        sources: Vec<NeuronId>,
        targets: Vec<NeuronId>,
    ) -> usize {
        if sources.is_empty() || targets.is_empty() {
            return 0;
        }

        let target_set: AHashSet<u32> = targets.into_iter().map(|n| n.0).collect();

        // Lock order: synapse_storage -> propagation_engine (matches rebuild path to avoid deadlocks)
        let mut synapse_storage = self.synapse_storage.write().unwrap();
        let prop_engine = self.propagation_engine.read().unwrap();

        let mut removed = 0usize;
        for source in sources {
            let Some(indices) = prop_engine.synapse_index.get(&source) else {
                continue;
            };
            for &syn_idx in indices {
                if syn_idx >= synapse_storage.count() {
                    continue;
                }
                if !synapse_storage.valid_mask()[syn_idx] {
                    continue;
                }
                let target = synapse_storage.target_neurons()[syn_idx];
                if target_set.contains(&target) && synapse_storage.remove_synapse(syn_idx).is_ok() {
                    removed += 1;
                }
            }
        }

        removed
    }

    /// Update synapse weight
    ///
    /// Note: This searches for the synapse index first, which is O(n).
    pub fn update_synapse_weight(
        &mut self,
        source: NeuronId,
        target: NeuronId,
        new_weight: SynapticWeight,
    ) -> bool {
        let synapse_storage = self.synapse_storage.read().unwrap();
        // Find synapse index by searching for matching source and target
        let idx = synapse_storage
            .source_neurons()
            .iter()
            .zip(synapse_storage.target_neurons().iter())
            .enumerate()
            .find(|(_, (&s, &t))| s == source.0 && t == target.0)
            .map(|(idx, _)| idx);
        drop(synapse_storage);

        if let Some(idx) = idx {
            self.synapse_storage
                .write()
                .unwrap()
                .update_weight(idx, new_weight.0)
                .map(|_| true)
                .unwrap_or(false)
        } else {
            false
        }
    }

    /// Rebuild indexes after modifications (call after bulk modifications)
    pub fn rebuild_indexes(&mut self) {
        // ZERO-COPY: Pass synapse_storage by reference
        let synapse_storage_read = self.synapse_storage.read().unwrap();
        self.propagation_engine
            .write()
            .unwrap()
            .build_synapse_index(&*synapse_storage_read);
    }

    /// Set neuron to cortical area mapping for propagation engine
    pub fn set_neuron_mapping(&mut self, mapping: AHashMap<NeuronId, CorticalID>) {
        self.propagation_engine
            .write()
            .unwrap()
            .set_neuron_mapping(mapping);
    }

    /// Set mp_driven_psp flags for cortical areas
    /// When enabled for an area, synaptic PSP will be dynamically set from source neuron's membrane potential
    pub fn set_mp_driven_psp_flags(&mut self, flags: AHashMap<CorticalID, bool>) {
        self.propagation_engine
            .write()
            .unwrap()
            .set_mp_driven_psp_flags(flags);
    }

    /// Set mp_driven_psp for a single cortical area (in-place).
    pub fn set_mp_driven_psp_flag(&mut self, cortical_id: CorticalID, enabled: bool) {
        self.propagation_engine
            .write()
            .unwrap()
            .set_mp_driven_psp_flag(cortical_id, enabled);
    }

    /// Set psp_uniform_distribution flags for cortical areas
    /// When false (default): PSP value is divided among all outgoing synapses from the source neuron
    /// When true: Full PSP value is applied to each outgoing synapse
    pub fn set_psp_uniform_distribution_flags(&mut self, flags: AHashMap<CorticalID, bool>) {
        self.propagation_engine
            .write()
            .unwrap()
            .set_psp_uniform_distribution_flags(flags);
    }

    /// Set psp_uniform_distribution for a single cortical area (in-place).
    pub fn set_psp_uniform_distribution_flag(&mut self, cortical_id: CorticalID, enabled: bool) {
        self.propagation_engine
            .write()
            .unwrap()
            .set_psp_uniform_distribution_flag(cortical_id, enabled);
    }

    // ===== SENSORY INJECTION API =====

    /// Inject sensory neurons into FCL (called from Rust sensory threads)
    /// This is the PRIMARY method for Rust-native sensory injection
    pub fn inject_sensory_batch(&mut self, neuron_ids: &[NeuronId], potential: f32) {
        // 🔍 DEBUG: Log first batch injection
        static FIRST_BATCH_LOGGED: std::sync::atomic::AtomicBool =
            std::sync::atomic::AtomicBool::new(false);
        if !FIRST_BATCH_LOGGED.load(std::sync::atomic::Ordering::Relaxed) && !neuron_ids.is_empty()
        {
            debug!(
                "[NPU-INJECT] 🔍 First batch: count={}, potential={}",
                neuron_ids.len(),
                potential
            );
            info!(
                "[NPU-INJECT]    First 5 NeuronIds: {:?}",
                &neuron_ids[0..neuron_ids.len().min(5)]
            );
            info!(
                "[NPU-INJECT]    FCL size before: {}",
                self.fire_structures
                    .lock()
                    .unwrap()
                    .fire_candidate_list
                    .len()
            );
            FIRST_BATCH_LOGGED.store(true, std::sync::atomic::Ordering::Relaxed);
        }

        for &neuron_id in neuron_ids {
            self.fire_structures
                .lock()
                .unwrap()
                .fire_candidate_list
                .add_candidate(neuron_id, potential);
        }

        // 🔍 DEBUG: Log FCL size after first injection
        static FIRST_BATCH_AFTER_LOGGED: std::sync::atomic::AtomicBool =
            std::sync::atomic::AtomicBool::new(false);
        if !FIRST_BATCH_AFTER_LOGGED.load(std::sync::atomic::Ordering::Relaxed)
            && !neuron_ids.is_empty()
        {
            info!(
                "[NPU-INJECT]    FCL size after: {}",
                self.fire_structures
                    .lock()
                    .unwrap()
                    .fire_candidate_list
                    .len()
            );
            FIRST_BATCH_AFTER_LOGGED.store(true, std::sync::atomic::Ordering::Relaxed);
        }
    }

    /// Stage sensory neurons for next burst (thread-safe, prevents FCL clear race)
    /// XYZP data from agents is staged here and injected AFTER fcl.clear() in Phase 1
    /// NOTE: Prefer inject_sensory_xyzp() for cleaner architecture
    pub fn inject_sensory_with_potentials(&mut self, neurons: &[(NeuronId, f32)]) {
        let mut fire_structures = self.fire_structures.lock().unwrap();
        if let Some(pending) = Some(&mut fire_structures.pending_sensory_injections) {
            // OPTIMIZATION: Reserve capacity to avoid reallocations during extend_from_slice
            // This reduces memory allocations and improves performance for high-frequency sensory streams
            let current_len = pending.len();
            let additional_capacity = neurons.len();
            if pending.capacity() < current_len + additional_capacity {
                pending.reserve(additional_capacity);
            }
            pending.extend_from_slice(neurons);

            // 🔍 DEBUG: Log first staging
            static FIRST_STAGING_LOGGED: std::sync::atomic::AtomicBool =
                std::sync::atomic::AtomicBool::new(false);
            if !FIRST_STAGING_LOGGED.load(std::sync::atomic::Ordering::Relaxed)
                && !neurons.is_empty()
            {
                info!("[NPU-STAGE] 🎯 Staged {} sensory neurons for next burst (prevents FCL clear race)", neurons.len());
                info!(
                    "[NPU-STAGE]    Queue now has {} pending injections",
                    pending.len()
                );
                FIRST_STAGING_LOGGED.store(true, std::sync::atomic::Ordering::Relaxed);
            }
        }
    }

    /// Get a clone of the FCL for inspection (debugging only)
    pub fn get_fcl_clone(&self) -> FireCandidateList {
        self.fire_structures
            .lock()
            .unwrap()
            .fire_candidate_list
            .clone()
    }

    /// Batch coordinate lookup - converts multiple (x,y,z) coordinates to neuron IDs
    /// Much faster than calling get_neuron_at_coordinates in a loop (1000x speedup for 4410 lookups)
    pub fn batch_get_neuron_ids_from_coordinates(
        &self,
        cortical_area: u32,
        coordinates: &[(u32, u32, u32)],
    ) -> Vec<NeuronId> {
        self.neuron_storage
            .read()
            .unwrap()
            .batch_coordinate_lookup(cortical_area, coordinates)
            .into_iter()
            .filter_map(|opt_idx| opt_idx.map(|idx| NeuronId(idx as u32)))
            .collect()
    }

    /// Batch coordinate lookup preserving indices - returns Option<NeuronId> for each input coordinate
    /// This preserves the index mapping (None if neuron not found at that coordinate)
    pub fn batch_get_neuron_ids_from_coordinates_with_none(
        &self,
        cortical_area: u32,
        coordinates: &[(u32, u32, u32)],
    ) -> Vec<Option<NeuronId>> {
        self.neuron_storage
            .read()
            .unwrap()
            .batch_coordinate_lookup(cortical_area, coordinates)
            .into_iter()
            .map(|opt_idx| opt_idx.map(|idx| NeuronId(idx as u32)))
            .collect()
    }

    /// Get last FCL snapshot (captured before clear in previous burst)
    /// Returns Vec of (NeuronId, potential) pairs
    pub fn get_last_fcl_snapshot(&self) -> Vec<(NeuronId, f32)> {
        self.fire_structures
            .lock()
            .unwrap()
            .last_fcl_snapshot
            .clone()
    }

    /// Get last FCL snapshot (captured before clear in previous burst), including cortical_idx resolution.
    ///
    /// Determinism/strictness:
    /// - Memory neurons are resolved via `memory_candidate_cortical_idx` (they are not in `neuron_storage`).
    /// - Regular neurons are resolved via `neuron_storage` cortical area mapping.
    /// - If any neuron cannot be resolved, this returns an error (no silent fallback to core areas).
    pub fn get_last_fcl_snapshot_with_cortical_idx(
        &self,
    ) -> core::result::Result<Vec<(NeuronId, u32, f32)>, FeagiError> {
        // Clone snapshot + memory map under a single lock so they remain consistent.
        let (snapshot, memory_map) = {
            let fire_structures = self.fire_structures.lock().unwrap();
            (
                fire_structures.last_fcl_snapshot.clone(),
                fire_structures.memory_candidate_cortical_idx.clone(),
            )
        };

        let neuron_storage = self.neuron_storage.read().unwrap();
        let mut out = Vec::with_capacity(snapshot.len());

        for (neuron_id, potential) in snapshot {
            let cortical_idx = if let Some(&idx) = memory_map.get(&neuron_id.0) {
                idx
            } else {
                neuron_storage
                    .get_cortical_area(neuron_id.0 as usize)
                    .ok_or_else(|| {
                        FeagiError::RuntimeError(format!(
                            "Unresolvable neuron cortical area in FCL snapshot: neuron_id={}",
                            neuron_id.0
                        ))
                    })?
            };

            out.push((neuron_id, cortical_idx, potential));
        }

        Ok(out)
    }

    // ===== END SENSORY INJECTION API =====

    // ===== MEMORY NEURON INJECTION API =====

    /// Stage a memory neuron injection to be applied at the start of the next burst.
    ///
    /// Memory neurons are identified by an ID in the reserved memory range (50_000_000+). They are
    /// not stored in the regular neuron array; instead they are force-fired by the dynamics layer.
    pub fn inject_memory_neuron_to_fcl(
        &mut self,
        neuron_id: u32,
        cortical_idx: u32,
        potential: f32,
    ) {
        let mut fire_structures = self.fire_structures.lock().unwrap();
        fire_structures.pending_memory_injections.push((
            NeuronId(neuron_id),
            cortical_idx,
            potential,
        ));
    }

    /// Register a dynamic (non-storage-backed) neuron’s cortical mapping for synaptic propagation.
    ///
    /// Required for memory neuron IDs (50_000_000+) so propagation can resolve source/destination areas.
    pub fn register_dynamic_neuron_mapping(&mut self, neuron_id: u32, cortical_id: CorticalID) {
        let mut engine = self.propagation_engine.write().unwrap();
        engine
            .neuron_to_area
            .insert(NeuronId(neuron_id), cortical_id);
    }

    // ===== POWER INJECTION =====
    // Power neurons are identified by cortical_idx = 1 in the neuron array
    // No separate list needed - single source of truth!

    /// Process a single burst (MAIN METHOD - FINE-GRAINED LOCKING)
    ///
    /// This is the complete neural processing pipeline:
    /// Phase 1: Injection → Phase 2: Dynamics → Phase 3: Archival →
    /// Phase 4: Queue Swap → Phase 5: FQ Sampling → Phase 6: Cleanup
    ///
    /// 🔋 Power neurons are auto-discovered from neuron_storage (cortical_idx = 1)
    ///
    /// ## Fine-Grained Locking Strategy:
    /// - Neuron/synapse arrays: RwLock (concurrent reads during propagation)
    /// - Fire structures: Mutex (exclusive for FCL/FQ operations)
    /// - Burst count: Atomic (lock-free)
    pub fn process_burst(&self) -> Result<BurstResult> {
        let burst_count = self.increment_burst_count();
        let power_amount = self.get_power_amount();
        let burst_start = std::time::Instant::now();

        // Lock neuron/synapse arrays for reading (allows concurrent sensory injection to fire_structures)
        let lock_start = std::time::Instant::now();
        let mut neuron_storage = self.neuron_storage.write().unwrap();
        let mut propagation_engine = self.propagation_engine.write().unwrap();

        // Lock fire structures (FCL, FQ, Fire Ledger)
        let mut fire_structures = self.fire_structures.lock().unwrap();
        let lock_duration = lock_start.elapsed();

        // Phase 1: Injection (power + synaptic propagation + staged sensory)
        // Use CURRENT_fire_queue (which contains fired neurons from the PREVIOUS burst after swap)
        // Note: At burst N start, current_fire_queue contains burst N-1's results (set at end of burst N-1)
        let previous_fq = fire_structures.current_fire_queue.clone();
        // PERFORMANCE + REAL-TIME:
        // Avoid cloning `pending_sensory_injections` (O(n) copy) on every burst.
        // Under dense sensory injection (e.g., diffThreshold=0), this clone can dominate burst time
        // and create visible drift that looks like buffering.
        //
        // Instead, move the staged injections out (preserving capacity) and let Phase 1 drain it.
        // Any injections arriving while the burst loop holds `fire_structures` will already block
        // on the lock; after Phase 1 completes we restore the vector back.
        let mut pending_sensory = Vec::new();
        std::mem::swap(
            &mut pending_sensory,
            &mut fire_structures.pending_sensory_injections,
        );
        let pending_mutex = std::sync::Mutex::new(pending_sensory);

        // Drain staged memory neuron injections (t+1 semantics)
        let mut pending_memory = Vec::new();
        std::mem::swap(
            &mut pending_memory,
            &mut fire_structures.pending_memory_injections,
        );

        let phase1_start = std::time::Instant::now();
        let injection_result = {
            let synapse_storage = self.synapse_storage.read().unwrap();
            let fatigue_active = self
                .fatigue_active
                .load(std::sync::atomic::Ordering::Acquire);
            // No cache needed - directly access neuron 1 (deterministic ID)
            phase1_injection_with_synapses(
                &mut fire_structures.fire_candidate_list,
                &mut *neuron_storage,
                &mut propagation_engine,
                &previous_fq,
                power_amount,
                &*synapse_storage,
                &pending_mutex,
                fatigue_active,
            )?
        };
        let phase1_duration = phase1_start.elapsed();
        fire_structures.pending_sensory_injections = pending_mutex.into_inner().unwrap();

        // Apply memory neuron injections after Phase 1 (so they’re not affected by Phase 1 staging logic).
        // These become candidates for Phase 2 and will be force-fired by the dynamics layer.
        fire_structures.memory_candidate_cortical_idx.clear();
        for (neuron_id, cortical_idx, potential) in pending_memory.drain(..) {
            fire_structures
                .memory_candidate_cortical_idx
                .insert(neuron_id.0, cortical_idx);
            fire_structures
                .fire_candidate_list
                .add_candidate(neuron_id, potential);
        }
        // Restore vector back (empty, but preserves capacity for future injections).
        fire_structures.pending_memory_injections = pending_memory;

        // Phase 2: Neural Dynamics (membrane potential updates, threshold checks, firing)
        let phase2_start = std::time::Instant::now();
        let dynamics_result = process_neural_dynamics(
            &fire_structures.fire_candidate_list,
            Some(&fire_structures.memory_candidate_cortical_idx),
            &mut *neuron_storage,
            burst_count,
        )?;
        let phase2_duration = phase2_start.elapsed();

        // Phase 3: Archival (ZERO-COPY archive to Fire Ledger)
        let phase3_start = std::time::Instant::now();
        fire_structures
            .fire_ledger
            .archive_burst(burst_count, &dynamics_result.fire_queue)
            .map_err(|e| FeagiError::RuntimeError(format!("FireLedger archive failed: {e}")))?;

        // Phase 3.5: Synaptic Plasticity (STDP-like) updates
        // Uses FireLedger window ending at this burst and applies weight updates to affect burst t+1.
        self.apply_stdp_updates_for_burst(burst_count, &fire_structures.fire_ledger)?;
        let phase3_duration = phase3_start.elapsed();

        // Phase 4: Swap fire queues (current becomes previous for next burst)
        let phase4_start = std::time::Instant::now();
        fire_structures.previous_fire_queue = fire_structures.current_fire_queue.clone();
        fire_structures.current_fire_queue = dynamics_result.fire_queue.clone();

        // Phase 5: Sample fire queue for visualization (FQ Sampler)
        let current_fq_clone = fire_structures.current_fire_queue.clone();
        fire_structures.fq_sampler.sample(&current_fq_clone);

        // CRITICAL PERFORMANCE FIX: Build fire queue sample while NPU lock is still held
        // This avoids needing to acquire the lock again in the burst loop (saves 2-5 seconds!)
        let fire_queue_sample = if !fire_structures.current_fire_queue.is_empty() {
            let mut sample = ahash::AHashMap::new();
            for (cortical_idx, neurons) in &fire_structures.current_fire_queue.neurons_by_area {
                let mut neuron_ids = Vec::with_capacity(neurons.len());
                let mut coords_x = Vec::with_capacity(neurons.len());
                let mut coords_y = Vec::with_capacity(neurons.len());
                let mut coords_z = Vec::with_capacity(neurons.len());
                let mut potentials = Vec::with_capacity(neurons.len());

                for neuron in neurons {
                    neuron_ids.push(neuron.neuron_id.0);
                    coords_x.push(neuron.x);
                    coords_y.push(neuron.y);
                    coords_z.push(neuron.z);
                    potentials.push(neuron.membrane_potential);
                }

                sample.insert(
                    *cortical_idx,
                    (neuron_ids, coords_x, coords_y, coords_z, potentials),
                );
            }
            Some(sample)
        } else {
            None
        };

        // Phase 6: Cleanup (snapshot FCL before clearing for API access)
        fire_structures.last_fcl_snapshot = fire_structures.fire_candidate_list.iter().collect();
        fire_structures.fire_candidate_list.clear();
        let phase4_duration = phase4_start.elapsed();

        let total_duration = burst_start.elapsed();

        // Burst timing telemetry - warn only for truly slow bursts (>200ms) that indicate real problems
        let is_slow = total_duration.as_millis() > 200;

        if is_slow {
            let neuron_count = neuron_storage.count();
            warn!(
                "[BURST-TIMING] Burst {}: total={:.2}ms | locks={:.2}ms | phase1={:.2}ms | phase2={:.2}ms | phase3={:.2}ms | phase4={:.2}ms | neurons={} | fired={} | candidates={}",
                burst_count,
                total_duration.as_secs_f64() * 1000.0,
                lock_duration.as_secs_f64() * 1000.0,
                phase1_duration.as_secs_f64() * 1000.0,
                phase2_duration.as_secs_f64() * 1000.0,
                phase3_duration.as_secs_f64() * 1000.0,
                phase4_duration.as_secs_f64() * 1000.0,
                neuron_count,
                dynamics_result.neurons_fired,
                fire_structures.fire_candidate_list.len()
            );
        }

        // Build result
        let fired_neurons = fire_structures.current_fire_queue.get_all_neuron_ids();

        Ok(BurstResult {
            neuron_count: fired_neurons.len(),
            fired_neurons,
            burst: burst_count,
            fire_queue_sample,
            power_injections: injection_result.power_injections,
            synaptic_injections: injection_result.synaptic_injections,
            neurons_processed: dynamics_result.neurons_processed,
            neurons_in_refractory: dynamics_result.neurons_in_refractory,
        })
    }

    // Removed duplicate - using atomic version at line 147

    /// Register a cortical area name for visualization encoding
    /// This mapping is populated during neuroembryogenesis
    ///
    /// ARCHITECTURE: For core areas (0=_death, 1=_power, 2=_fatigue), automatically creates
    /// a single neuron (1x1x1) with deterministic ID matching the area ID:
    /// - Area 0 → neuron ID 0
    /// - Area 1 → neuron ID 1
    /// - Area 2 → neuron ID 2
    ///
    /// This eliminates the need to scan for power neurons (O(1) lookup instead of O(n))
    pub fn register_cortical_area(&mut self, area_id: u32, cortical_name: String) {
        self.area_id_to_name
            .write()
            .unwrap()
            .insert(area_id, cortical_name);

        // CRITICAL ARCHITECTURE: Create core area neurons with deterministic IDs
        // Core areas (0, 1, 2) get neurons at matching IDs (0, 1, 2)
        if area_id <= 2 {
            let neuron_storage = self.neuron_storage.read().unwrap();
            let neuron_id = NeuronId(area_id);
            let neuron_idx = neuron_id.0 as usize;

            // Check if neuron already exists at this ID
            // CRITICAL: Check count() (actual initialized size), not just capacity()
            let needs_creation = if neuron_idx < neuron_storage.count() {
                // Neuron storage has been initialized up to this index - check if neuron exists and is valid
                let is_valid = neuron_storage
                    .valid_mask()
                    .get(neuron_idx)
                    .copied()
                    .unwrap_or(false);
                let belongs_to_area =
                    neuron_storage.cortical_areas().get(neuron_idx).copied() == Some(area_id);
                // If neuron doesn't exist or doesn't belong to this area, needs creation
                !is_valid || !belongs_to_area
            } else {
                // Neuron storage hasn't been initialized up to this index yet - needs creation
                true
            };
            drop(neuron_storage);

            if needs_creation {
                // Ensure previous core area neurons exist (must be created in order: 0, then 1, then 2)
                if area_id > 0 {
                    let prev_neuron_storage = self.neuron_storage.read().unwrap();
                    let prev_neuron_idx = (area_id - 1) as usize;
                    let prev_exists = prev_neuron_idx < prev_neuron_storage.count()
                        && prev_neuron_storage
                            .valid_mask()
                            .get(prev_neuron_idx)
                            .copied()
                            .unwrap_or(false);
                    drop(prev_neuron_storage);

                    if !prev_exists {
                        warn!(
                            "[NPU] Core area {} registered before area {} - core areas should be registered in order (0, 1, 2)",
                            area_id, area_id - 1
                        );
                    }
                }

                // Create core area neuron with deterministic ID (1x1x1, single neuron)
                // Use default neuron parameters suitable for core areas
                let _neuron_id = self
                    .add_neuron(
                        T::from_f32(1.0), // threshold
                        T::max_value(), // threshold_limit (MAX = no limit, SIMD-friendly encoding)
                        0.1,            // leak_coefficient
                        T::from_f32(0.0), // resting_potential
                        0,              // neuron_type
                        5,              // refractory_period
                        1.0,            // excitability
                        u16::MAX, // consecutive_fire_limit (MAX = unlimited, SIMD-friendly encoding)
                        0,        // snooze_period
                        true,     // mp_charge_accumulation
                        area_id,  // cortical_area
                        0,        // x
                        0,        // y
                        0,        // z
                    )
                    .unwrap_or_else(|e| {
                        warn!(
                            "[NPU] Failed to create core area neuron for area {}: {}",
                            area_id, e
                        );
                        NeuronId(0) // Fallback, but this should not happen
                    });

                // Verify neuron was created at correct ID
                let neuron_storage = self.neuron_storage.read().unwrap();
                let created_idx = _neuron_id.0 as usize;
                if created_idx != area_id as usize {
                    warn!(
                        "[NPU] Core area {} neuron created at ID {} instead of deterministic ID {}",
                        area_id, created_idx, area_id
                    );
                } else {
                    info!(
                        "[NPU] Created core area {} neuron with deterministic ID {} (1x1x1)",
                        area_id, area_id
                    );
                }
                drop(neuron_storage);
            }
        }
    }

    /// Get the cortical area name for a given area_id
    /// Returns None if the area_id is not registered
    pub fn get_cortical_area_name(&self, area_id: u32) -> Option<String> {
        self.area_id_to_name.read().unwrap().get(&area_id).cloned()
    }

    /// Get the cortical area ID for a given cortical name
    /// Returns None if the name is not registered
    pub fn get_cortical_area_id(&self, cortical_name: &str) -> Option<u32> {
        let area_map = self.area_id_to_name.read().unwrap();
        for (&area_id, name) in area_map.iter() {
            if name == cortical_name {
                return Some(area_id);
            }
        }
        None
    }

    // ===== PUBLIC ACCESSORS FOR PYTHON BINDINGS =====

    /// Get neuron at specific coordinate (for Python bindings)
    pub fn get_neuron_id_at_coordinate(
        &self,
        cortical_area: u32,
        x: u32,
        y: u32,
        z: u32,
    ) -> Option<u32> {
        self.neuron_storage
            .read()
            .unwrap()
            .get_neuron_at_coordinate(cortical_area, x, y, z)
            .map(|idx| idx as u32)
    }

    /// Get neuron property by index (for Python bindings)
    pub fn get_neuron_property_by_index(&self, idx: usize, property: &str) -> Option<f32> {
        let neuron_storage = self.neuron_storage.read().unwrap();
        if idx >= neuron_storage.count() {
            return None;
        }
        match property {
            "threshold" => neuron_storage.thresholds().get(idx).map(|&v| v.to_f32()),
            "threshold_limit" => neuron_storage
                .threshold_limits()
                .get(idx)
                .map(|&v| v.to_f32()),
            "leak_coefficient" => neuron_storage.leak_coefficients().get(idx).copied(),
            "membrane_potential" => neuron_storage
                .membrane_potentials()
                .get(idx)
                .map(|&v| v.to_f32()),
            "resting_potential" => neuron_storage
                .resting_potentials()
                .get(idx)
                .map(|&v| v.to_f32()),
            "excitability" => neuron_storage.excitabilities().get(idx).copied(),
            _ => None,
        }
    }

    /// Get neuron property u16 by index (for Python bindings)
    pub fn get_neuron_property_u16_by_index(&self, idx: usize, property: &str) -> Option<u16> {
        let neuron_storage = self.neuron_storage.read().unwrap();
        if idx >= neuron_storage.count() {
            return None;
        }
        match property {
            "refractory_period" => neuron_storage.refractory_periods().get(idx).copied(),
            "consecutive_fire_limit" => neuron_storage.consecutive_fire_limits().get(idx).copied(),
            _ => None,
        }
    }

    /// Get neuron array snapshot for FCL inspection (for Python bindings)
    pub fn get_neuron_storage_snapshot(&self) -> (usize, Vec<u32>, Vec<bool>) {
        let neuron_storage = self.neuron_storage.read().unwrap();
        (
            neuron_storage.count(),
            neuron_storage.cortical_areas().to_vec(),
            neuron_storage.valid_mask().to_vec(),
        )
    }

    /// Get the number of registered cortical areas
    pub fn get_registered_cortical_area_count(&self) -> usize {
        self.area_id_to_name.read().unwrap().len()
    }

    /// Get all registered cortical areas as (idx, name) pairs
    pub fn get_all_cortical_areas(&self) -> Vec<(u32, String)> {
        self.area_id_to_name
            .read()
            .unwrap()
            .iter()
            .map(|(&idx, name)| (idx, name.clone()))
            .collect()
    }

    /// Check if a genome is loaded (has neurons)
    /// Returns true if NPU has any valid neurons, false otherwise
    pub fn is_genome_loaded(&self) -> bool {
        let neuron_storage = self.neuron_storage.read().unwrap();
        neuron_storage.count() > 0 && neuron_storage.valid_mask().iter().any(|&valid| valid)
    }

    /// Find neuron ID at specific X,Y,Z coordinates within a cortical area
    /// Returns None if no neuron exists at that position
    pub fn get_neuron_at_coordinates(
        &self,
        cortical_area: u32,
        x: u32,
        y: u32,
        z: u32,
    ) -> Option<NeuronId> {
        for neuron_idx in 0..self.neuron_storage.read().unwrap().count() {
            if self.neuron_storage.read().unwrap().valid_mask()[neuron_idx]
                && self.neuron_storage.read().unwrap().cortical_areas()[neuron_idx] == cortical_area
            {
                let coord_idx = neuron_idx * 3;
                if self.neuron_storage.read().unwrap().coordinates()[coord_idx] == x
                    && self.neuron_storage.read().unwrap().coordinates()[coord_idx + 1] == y
                    && self.neuron_storage.read().unwrap().coordinates()[coord_idx + 2] == z
                {
                    return Some(NeuronId(neuron_idx as u32));
                }
            }
        }
        None
    }

    /// Inject sensory neurons using cortical area CorticalID and XYZ coordinates
    /// This is the high-level API for sensory injection from agents
    /// OPTIMIZATION: Takes CorticalID directly to avoid string conversion in hot path
    pub fn inject_sensory_xyzp_by_id(
        &mut self,
        cortical_id: &CorticalID,
        xyzp_data: &[(u32, u32, u32, f32)],
    ) -> usize {
        use tracing::error;

        let cortical_id_str = cortical_id.to_string();
        let cortical_id_base64 = cortical_id.as_base_64();

        // Convert CorticalID to cortical_area index (OPTIMIZED: removed expensive area map iteration)
        let cortical_area = match self.get_cortical_area_id(&cortical_id_str) {
            Some(id) => id,
            None => {
                // Also try base64 lookup
                match self.get_cortical_area_id(&cortical_id_base64) {
                    Some(id) => id,
                    None => {
                        error!(
                            "[NPU] Unknown cortical area: '{}' (base64: {})",
                            cortical_id_str, cortical_id_base64
                        );
                        // Only log available areas on error (not every call!)
                        let available_areas: Vec<String> = self
                            .area_id_to_name
                            .read()
                            .unwrap()
                            .values()
                            .cloned()
                            .collect();
                        error!(
                            "[NPU] Available cortical areas ({} total): {:?}",
                            available_areas.len(),
                            available_areas
                        );
                        return 0;
                    }
                }
            }
        };

        // 🚀 BATCH coordinate-to-ID conversion (1000x faster than individual lookups!)
        // Extract coordinates
        let coords: Vec<(u32, u32, u32)> =
            xyzp_data.iter().map(|(x, y, z, _p)| (*x, *y, *z)).collect();

        // Batch lookup
        let neuron_ids = self
            .neuron_storage
            .read()
            .unwrap()
            .batch_coordinate_lookup(cortical_area, &coords);

        // Build (NeuronId, potential) pairs (filter out None)
        let mut neuron_potential_pairs = Vec::with_capacity(neuron_ids.len());
        for (opt_idx, (_x, _y, _z, potential)) in neuron_ids.iter().zip(xyzp_data.iter()) {
            if let Some(idx) = opt_idx {
                neuron_potential_pairs.push((NeuronId(*idx as u32), *potential));
            }
        }
        let found_count = neuron_potential_pairs.len();

        // Inject found neurons
        if !neuron_potential_pairs.is_empty() {
            self.inject_sensory_with_potentials(&neuron_potential_pairs);
        }

        // Only log if there's an issue.
        //
        // IMPORTANT: NPU is performance-sensitive. Any extra diagnostics must be gated behind
        // tracing level checks so we don't do O(n) work (min/max scans, neuron counts, allocations)
        // on every missed injection when TRACE isn't enabled.
        if found_count == 0 && tracing::enabled!(tracing::Level::TRACE) {
            // Root-cause diagnostics: report coordinate ranges so callers can compare against genome bbx/bby/bbz.
            let (min_x, max_x, min_y, max_y, min_z, max_z) = coords.iter().fold(
                (u32::MAX, 0u32, u32::MAX, 0u32, u32::MAX, 0u32),
                |(min_x, max_x, min_y, max_y, min_z, max_z), &(x, y, z)| {
                    (
                        min_x.min(x),
                        max_x.max(x),
                        min_y.min(y),
                        max_y.max(y),
                        min_z.min(z),
                        max_z.max(z),
                    )
                },
            );
            let area_neuron_count = self
                .neuron_storage
                .read()
                .unwrap()
                .get_neuron_count(cortical_area);
            let coord_sample: Vec<(u32, u32, u32)> = coords.iter().copied().take(5).collect();

            trace!(
                "[NPU] No neurons found for injection! Area: '{}', cortical_area_idx={}, coords={}, x=[{}..{}], y=[{}..{}], z=[{}..{}], area_neurons={}, sample={:?}",
                cortical_id_str,
                cortical_area,
                coords.len(),
                min_x,
                max_x,
                min_y,
                max_y,
                min_z,
                max_z,
                area_neuron_count,
                coord_sample
            );
        }

        found_count
    }

    /// Inject sensory neurons using separate XYZP arrays (optimized; avoids building a tuple Vec upstream).
    ///
    /// This is a performance-focused variant of `inject_sensory_xyzp_by_id` intended for hot paths
    /// (e.g., high-rate vision) where upstream code already has SoA buffers (x/y/z/p).
    ///
    /// Semantics are identical to `inject_sensory_xyzp_by_id`:
    /// - Batch coordinate lookup
    /// - Filter missing neurons
    /// - Inject found neurons with provided potentials
    pub fn inject_sensory_xyzp_arrays_by_id(
        &mut self,
        cortical_id: &CorticalID,
        x_coords: &[u32],
        y_coords: &[u32],
        z_coords: &[u32],
        potentials: &[f32],
    ) -> usize {
        use tracing::error;

        if x_coords.len() != y_coords.len()
            || x_coords.len() != z_coords.len()
            || x_coords.len() != potentials.len()
        {
            error!(
                "[NPU] Invalid XYZP arrays: x={}, y={}, z={}, p={}",
                x_coords.len(),
                y_coords.len(),
                z_coords.len(),
                potentials.len()
            );
            return 0;
        }

        let cortical_id_str = cortical_id.to_string();
        let cortical_id_base64 = cortical_id.as_base_64();

        let cortical_area = match self.get_cortical_area_id(&cortical_id_str) {
            Some(id) => id,
            None => match self.get_cortical_area_id(&cortical_id_base64) {
                Some(id) => id,
                None => {
                    error!(
                        "[NPU] Unknown cortical area: '{}' (base64: {})",
                        cortical_id_str, cortical_id_base64
                    );
                    return 0;
                }
            },
        };

        // OPTIMIZATION: Use batch_coordinate_lookup_from_slices to avoid allocating Vec<(u32, u32, u32)>
        // This eliminates one Vec allocation per frame, significantly improving performance for high-frequency streams
        let coord_lookup_start = std::time::Instant::now();
        let neuron_ids = {
            let _span = tracing::span!(
                tracing::Level::DEBUG,
                "batch_coordinate_lookup",
                num_coords = x_coords.len()
            )
            .entered();
            self.neuron_storage
                .read()
                .unwrap()
                .batch_coordinate_lookup_from_slices(cortical_area, x_coords, y_coords, z_coords)
        };
        let coord_lookup_duration = coord_lookup_start.elapsed();
        let sim_timestep = crate::sim_timestep();
        if !x_coords.is_empty()
            && sim_timestep > std::time::Duration::from_nanos(0)
            && coord_lookup_duration > sim_timestep
        {
            tracing::warn!(
                "[NPU-INJECT] Coordinate lookup exceeded timestep: {:.2}ms > {:.2}ms ({} coordinates)",
                coord_lookup_duration.as_secs_f64() * 1000.0,
                sim_timestep.as_secs_f64() * 1000.0,
                x_coords.len()
            );
        }

        // OPTIMIZATION: Use iterator chain to build pairs more efficiently
        // This is more idiomatic and allows better compiler optimizations
        let pair_build_start = std::time::Instant::now();
        let neuron_potential_pairs: Vec<(NeuronId, f32)> = {
            let _span = tracing::span!(
                tracing::Level::DEBUG,
                "build_neuron_potential_pairs",
                num_neuron_ids = neuron_ids.len()
            )
            .entered();
            neuron_ids
                .iter()
                .enumerate()
                .filter_map(|(i, opt_idx)| opt_idx.map(|idx| (NeuronId(idx as u32), potentials[i])))
                .collect()
        };
        let pair_build_duration = pair_build_start.elapsed();
        let found_count = neuron_potential_pairs.len();
        let sim_timestep = crate::sim_timestep();
        if found_count > 0
            && sim_timestep > std::time::Duration::from_nanos(0)
            && pair_build_duration > sim_timestep
        {
            tracing::warn!(
                "[NPU-INJECT] Pair building exceeded timestep: {:.2}ms > {:.2}ms ({} pairs)",
                pair_build_duration.as_secs_f64() * 1000.0,
                sim_timestep.as_secs_f64() * 1000.0,
                found_count
            );
        }

        let inject_start = std::time::Instant::now();
        if !neuron_potential_pairs.is_empty() {
            let _span = tracing::span!(
                tracing::Level::DEBUG,
                "inject_sensory_with_potentials",
                num_pairs = neuron_potential_pairs.len()
            )
            .entered();
            self.inject_sensory_with_potentials(&neuron_potential_pairs);
        }
        let inject_duration = inject_start.elapsed();
        if inject_duration.as_millis() > 10 {
            tracing::warn!(
                "[NPU-INJECT] ⚠️ Slow injection: {:.2}ms for {} pairs",
                inject_duration.as_secs_f64() * 1000.0,
                found_count
            );
        }

        // Log total timing breakdown for large injections
        let total_duration = coord_lookup_start.elapsed();
        if total_duration.as_millis() > 100 {
            tracing::debug!(
                "[NPU-INJECT] Timing breakdown: total={:.2}ms | coord_lookup={:.2}ms ({:.1}%) | pair_build={:.2}ms ({:.1}%) | inject={:.2}ms ({:.1}%) | neurons={}",
                total_duration.as_secs_f64() * 1000.0,
                coord_lookup_duration.as_secs_f64() * 1000.0,
                (coord_lookup_duration.as_secs_f64() / total_duration.as_secs_f64()) * 100.0,
                pair_build_duration.as_secs_f64() * 1000.0,
                (pair_build_duration.as_secs_f64() / total_duration.as_secs_f64()) * 100.0,
                inject_duration.as_secs_f64() * 1000.0,
                (inject_duration.as_secs_f64() / total_duration.as_secs_f64()) * 100.0,
                found_count
            );
        }

        found_count
    }

    /// Clear any staged (pending) sensory injections.
    ///
    /// @architecture:acceptable - real-time semantics
    /// Under high-rate sensory streams (e.g., vision), multiple incoming messages can arrive
    /// between burst boundaries. Keeping all of them causes drift that looks like buffering.
    /// This clears the staged queue so only the newest sensory message is applied on the next burst.
    pub fn clear_pending_sensory_injections(&mut self) {
        let mut fire_structures = self.fire_structures.lock().unwrap();
        fire_structures.pending_sensory_injections.clear();
    }

    /// Inject sensory neurons using cortical area name (backward compatibility)
    /// For hot paths, use inject_sensory_xyzp_by_id() to avoid string allocations
    pub fn inject_sensory_xyzp(
        &mut self,
        cortical_name: &str,
        xyzp_data: &[(u32, u32, u32, f32)],
    ) -> usize {
        // Find cortical area ID
        let cortical_area = match self.get_cortical_area_id(cortical_name) {
            Some(id) => id,
            None => {
                error!("[NPU] ❌ Unknown cortical area: '{}'", cortical_name);
                error!(
                    "[NPU] ❌ Available cortical areas: {:?}",
                    self.area_id_to_name
                        .read()
                        .unwrap()
                        .values()
                        .collect::<Vec<_>>()
                );
                error!(
                    "[NPU] ❌ Total registered: {}",
                    self.area_id_to_name.read().unwrap().len()
                );
                return 0;
            }
        };

        // Same logic as inject_sensory_xyzp_by_id but converted from string
        let coords: Vec<(u32, u32, u32)> =
            xyzp_data.iter().map(|(x, y, z, _p)| (*x, *y, *z)).collect();
        let neuron_ids = self
            .neuron_storage
            .read()
            .unwrap()
            .batch_coordinate_lookup(cortical_area, &coords);
        let mut neuron_potential_pairs = Vec::with_capacity(neuron_ids.len());
        for (opt_idx, (_x, _y, _z, potential)) in neuron_ids.iter().zip(xyzp_data.iter()) {
            if let Some(idx) = opt_idx {
                neuron_potential_pairs.push((NeuronId(*idx as u32), *potential));
            }
        }
        let found_count = neuron_potential_pairs.len();
        if !neuron_potential_pairs.is_empty() {
            self.inject_sensory_with_potentials(&neuron_potential_pairs);
        }
        found_count
    }

    /// Export connectome snapshot (for saving to file)
    ///
    /// This captures the complete NPU state including all neurons, synapses,
    /// and runtime state for serialization.
    #[cfg(feature = "connectome-io")]
    pub fn export_connectome(&self) -> feagi_npu_neural::types::connectome::ConnectomeSnapshot {
        use feagi_npu_neural::types::connectome::{
            ConnectomeMetadata, ConnectomeSnapshot, SerializableNeuronArray,
            SerializableSynapseArray,
        };

        // Convert neuron array (lock once and clone all fields)
        let neuron_storage = self.neuron_storage.read().unwrap();
        let neurons = SerializableNeuronArray {
            count: neuron_storage.count(),
            capacity: neuron_storage.capacity(),
            // Convert T to f32 for serialization
            membrane_potentials: neuron_storage
                .membrane_potentials()
                .iter()
                .map(|&v| v.to_f32())
                .collect(),
            thresholds: neuron_storage
                .thresholds()
                .iter()
                .map(|&v| v.to_f32())
                .collect(),
            leak_coefficients: neuron_storage.leak_coefficients().to_vec(),
            resting_potentials: neuron_storage
                .resting_potentials()
                .iter()
                .map(|&v| v.to_f32())
                .collect(),
            neuron_types: neuron_storage.neuron_types().to_vec(),
            refractory_periods: neuron_storage.refractory_periods().to_vec(),
            refractory_countdowns: neuron_storage.refractory_countdowns().to_vec(),
            excitabilities: neuron_storage.excitabilities().to_vec(),
            cortical_areas: neuron_storage.cortical_areas().to_vec(),
            coordinates: neuron_storage.coordinates().to_vec(),
            valid_mask: neuron_storage.valid_mask().to_vec(),
        };
        drop(neuron_storage); // Release lock

        // Convert synapse array (lock once and clone all fields)
        let synapse_storage = self.synapse_storage.read().unwrap();
        let source_neurons = synapse_storage.source_neurons().to_vec();

        // Build source_index from source_neurons (for fast lookup)
        let mut source_index = ahash::AHashMap::new();
        for (idx, &source) in source_neurons.iter().enumerate() {
            source_index
                .entry(source)
                .or_insert_with(Vec::new)
                .push(idx);
        }

        let synapses = SerializableSynapseArray {
            count: synapse_storage.count(),
            capacity: synapse_storage.capacity(),
            source_neurons,
            target_neurons: synapse_storage.target_neurons().to_vec(),
            weights: synapse_storage.weights().to_vec(),
            conductances: synapse_storage.postsynaptic_potentials().to_vec(),
            types: synapse_storage.types().to_vec(),
            valid_mask: synapse_storage.valid_mask().to_vec(),
            source_index,
        };
        drop(synapse_storage); // Release lock

        ConnectomeSnapshot {
            version: 1,
            neurons,
            synapses,
            cortical_area_names: self.area_id_to_name.read().unwrap().clone(),
            burst_count: self.get_burst_count(),
            power_amount: self.get_power_amount(),
            fire_ledger_window: 20, // Default value (fire_ledger doesn't expose window)
            metadata: ConnectomeMetadata::default(),
        }
    }

    // TODO: import_connectome needs refactoring for trait-based storage
    // Currently commented out - requires bulk load API in Storage traits
    // See issue: Direct field assignment doesn't work with trait-based storage

    /*
    /// Import connectome snapshot (for loading from file)
    ///
    /// This replaces the entire NPU state with data from a saved connectome.
    /// Import a connectome from a snapshot
    ///
    /// # Arguments
    /// * `snapshot` - The connectome snapshot to import
    ///
    /// # Note
    /// This method uses CPU backend by default for backward compatibility.
    /// Use `import_connectome_with_config()` to specify GPU configuration.
    #[cfg(feature = "connectome-io")]
    pub fn import_connectome(
        runtime: R,
        snapshot: feagi_npu_neural::types::connectome::ConnectomeSnapshot
    ) -> Result<Self, FeagiError> {
        Self::import_connectome_with_config(runtime, snapshot, None)
    }
    */

    // TODO: import_connectome_with_config needs refactoring for trait-based storage
    // Currently commented out pending bulk load API in Storage traits
    /*
    /// Import a connectome from a snapshot with optional GPU configuration
    ///
    /// # Arguments
    /// * `runtime` - Runtime implementation providing storage
    /// * `snapshot` - The connectome snapshot to import
    /// * `gpu_config` - Optional GPU configuration (None = default to CPU)
    #[cfg(feature = "connectome-io")]
    pub fn import_connectome_with_config(
        runtime: R,
        snapshot: feagi_npu_neural::types::connectome::ConnectomeSnapshot,
        gpu_config: Option<&crate::backend::GpuConfig>,
    ) -> Result<Self, FeagiError> {
        use tracing::info;

        // Convert neuron array using runtime
        let mut neuron_storage = runtime.create_neuron_storage(snapshot.neurons.capacity())
            .map_err(|e| FeagiError::RuntimeError(format!("Failed to create neuron storage: {:?}", e)))?;
        // Convert f32 from serialized data to T
        neuron_storage.membrane_potentials() = snapshot.neurons.membrane_potentials.iter().map(|&v| T::from_f32(v)).collect();
        neuron_storage.thresholds() = snapshot.neurons.thresholds.iter().map(|&v| T::from_f32(v)).collect();
        neuron_storage.leak_coefficients() = snapshot.neurons.leak_coefficients;
        neuron_storage.resting_potentials() = snapshot.neurons.resting_potentials.iter().map(|&v| T::from_f32(v)).collect();
        neuron_storage.neuron_types() = snapshot.neurons.neuron_types;
        neuron_storage.refractory_periods() = snapshot.neurons.refractory_periods;
        neuron_storage.refractory_countdowns() = snapshot.neurons.refractory_countdowns;
        neuron_storage.excitabilities() = snapshot.neurons.excitabilities;
        neuron_storage.cortical_areas() = snapshot.neurons.cortical_areas;
        neuron_storage.coordinates() = snapshot.neurons.coordinates;
        neuron_storage.valid_mask() = snapshot.neurons.valid_mask;

        // Convert synapse array using runtime
        let mut synapse_storage = runtime.create_synapse_storage(snapshot.synapses.capacity())
            .map_err(|e| FeagiError::RuntimeError(format!("Failed to create synapse storage: {:?}", e)))?;
        synapse_storage.source_neurons() = snapshot.synapses.source_neurons;
        synapse_storage.target_neurons() = snapshot.synapses.target_neurons;
        synapse_storage.weights() = snapshot.synapses.weights;
        synapse_storage.postsynaptic_potentials() = snapshot.synapses.conductances;  // TODO: Rename field in snapshot
        synapse_storage.types() = snapshot.synapses.types;
        synapse_storage.valid_mask() = snapshot.synapses.valid_mask;
        synapse_storage.source_index = snapshot.synapses.source_index;

        // Create backend based on GPU config and actual genome size
        let (backend_type, backend_config) = if let Some(config) = gpu_config {
            info!("🎮 Imported Connectome GPU Configuration:");
            info!("   Neurons: {}, Synapses: {}", neuron_storage.count(), synapse_storage.count());
            info!("   GPU enabled: {}", config.use_gpu);
            info!("   Hybrid mode: {}", config.hybrid_enabled);
            if config.hybrid_enabled {
                info!("   GPU threshold: {} synapses", config.gpu_threshold);
                if synapse_storage.count() >= config.gpu_threshold {
                    info!("   → Genome ABOVE threshold, GPU will be considered");
                } else {
                    info!("   → Genome BELOW threshold, CPU will be used");
                }
            }
            config.to_backend_selection()
        } else {
            (crate::backend::BackendType::CPU, crate::backend::BackendConfig::default())
        };

        // Create backend
        let backend = crate::backend::create_backend(
            backend_type,
            snapshot.neurons.capacity(),
            snapshot.synapses.capacity(),
            &backend_config,
        ).expect("Failed to create compute backend");

        info!("   ✓ Backend created: {}", backend.backend_name());

        Ok(Self {
            runtime: std::sync::Arc::new(runtime),
            neuron_storage: std::sync::RwLock::new(neuron_storage),
            synapse_storage: std::sync::RwLock::new(synapse_storage),
            fire_structures: std::sync::Mutex::new(FireStructures {
                fire_candidate_list: FireCandidateList::new(),
                current_fire_queue: FireQueue::new(),
                previous_fire_queue: FireQueue::new(),
                fire_ledger: FireLedger::new(snapshot.fire_ledger_window),
                fq_sampler: FQSampler::new(1000.0, SamplingMode::Unified),
                pending_sensory_injections: Vec::with_capacity(10000),
                last_fcl_snapshot: Vec::new(),
            }),
            area_id_to_name: std::sync::RwLock::new(snapshot.cortical_area_names),
            propagation_engine: std::sync::RwLock::new(SynapticPropagationEngine::new()),
            stdp_mappings: std::sync::RwLock::new(AHashMap::new()),
            stdp_mapping_index: std::sync::RwLock::new(AHashMap::new()),
            backend: std::sync::Mutex::new(backend),
            burst_count: std::sync::atomic::AtomicU64::new(snapshot.burst_count),
            power_amount: std::sync::atomic::AtomicU32::new(snapshot.power_amount.to_bits()),
        }
    }
    END COMMENTED OUT */

    /// Get all neuron positions for a cortical area (for fast batch lookups)
    /// Returns Vec<(neuron_id, x, y, z)>
    pub fn get_neuron_positions_in_cortical_area(
        &self,
        cortical_area: u32,
    ) -> Vec<(u32, u32, u32, u32)> {
        let mut positions = Vec::new();

        for neuron_id in 0..self.neuron_storage.read().unwrap().count() {
            if self.neuron_storage.read().unwrap().valid_mask()[neuron_id]
                && self.neuron_storage.read().unwrap().cortical_areas()[neuron_id] == cortical_area
            {
                // Coordinates stored as flat array: [x0, y0, z0, x1, y1, z1, ...]
                let coord_idx = neuron_id * 3;
                positions.push((
                    neuron_id as u32,
                    self.neuron_storage.read().unwrap().coordinates()[coord_idx],
                    self.neuron_storage.read().unwrap().coordinates()[coord_idx + 1],
                    self.neuron_storage.read().unwrap().coordinates()[coord_idx + 2],
                ));
            }
        }

        positions
    }

    /// Update excitability for a single neuron (for live parameter changes)
    /// Returns true if successful, false if neuron doesn't exist
    pub fn update_neuron_excitability(&mut self, neuron_id: u32, excitability: f32) -> bool {
        let idx = neuron_id as usize;
        if idx >= self.neuron_storage.read().unwrap().count()
            || !self.neuron_storage.read().unwrap().valid_mask()[idx]
        {
            return false;
        }

        self.neuron_storage.write().unwrap().excitabilities_mut()[idx] =
            excitability.clamp(0.0, 1.0);
        true
    }

    /// Update firing threshold for a specific neuron
    /// Returns true if successful, false if neuron doesn't exist
    pub fn update_neuron_threshold(&mut self, neuron_id: u32, threshold: T) -> bool {
        let idx = neuron_id as usize;
        if idx >= self.neuron_storage.read().unwrap().count()
            || !self.neuron_storage.read().unwrap().valid_mask()[idx]
        {
            return false;
        }

        self.neuron_storage.write().unwrap().thresholds_mut()[idx] = threshold;
        true
    }

    /// Update leak coefficient for a specific neuron
    /// Returns true if successful, false if neuron doesn't exist
    pub fn update_neuron_leak(&mut self, neuron_id: u32, leak_coefficient: f32) -> bool {
        let idx = neuron_id as usize;
        if idx >= self.neuron_storage.read().unwrap().count()
            || !self.neuron_storage.read().unwrap().valid_mask()[idx]
        {
            return false;
        }

        self.neuron_storage.write().unwrap().leak_coefficients_mut()[idx] =
            leak_coefficient.clamp(0.0, 1.0);
        true
    }

    /// Update resting potential for a specific neuron
    /// Returns true if successful, false if neuron doesn't exist
    pub fn update_neuron_resting_potential(
        &mut self,
        neuron_id: u32,
        resting_potential: T,
    ) -> bool {
        let idx = neuron_id as usize;
        if idx >= self.neuron_storage.read().unwrap().count()
            || !self.neuron_storage.read().unwrap().valid_mask()[idx]
        {
            return false;
        }

        self.neuron_storage
            .write()
            .unwrap()
            .resting_potentials_mut()[idx] = resting_potential;
        true
    }

    /// Update excitability for all neurons in a cortical area (for bulk parameter changes)
    /// Returns number of neurons updated
    pub fn update_cortical_area_excitability(
        &mut self,
        cortical_area: u32,
        excitability: f32,
    ) -> usize {
        let clamped_excitability = excitability.clamp(0.0, 1.0);
        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.excitabilities_mut()[idx] = clamped_excitability;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update refractory period for all neurons in a cortical area (bulk parameter change)
    pub fn update_cortical_area_refractory_period(
        &mut self,
        cortical_area: u32,
        refractory_period: u16,
    ) -> usize {
        info!("[RUST-UPDATE] update_cortical_area_refractory_period: cortical_area={}, refractory_period={}",
                 cortical_area, refractory_period);

        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                // Get the actual neuron_id for this array index
                let neuron_id = idx as u32;

                // Update base refractory period (used when neuron fires)
                neuron_storage_write.refractory_periods_mut()[idx] = refractory_period;

                // CRITICAL FIX: Do NOT set countdown here!
                // The countdown should only be set AFTER a neuron fires.
                // Setting it now would block the neuron immediately, which is backward.

                // Only clear countdown if setting refractory to 0 (allow immediate firing)
                if refractory_period == 0 {
                    neuron_storage_write.refractory_countdowns_mut()[idx] = 0;
                }

                // Reset consecutive fire count when applying a new period to avoid
                // stale state causing unexpected immediate extended refractory.
                neuron_storage_write.consecutive_fire_counts_mut()[idx] = 0;

                updated_count += 1;

                // Log first few neurons (show actual neuron_id, not array index!)
                if updated_count <= 3 {
                    info!(
                        "[RUST-BATCH-UPDATE]   Neuron {}: refractory_period={}, countdown={}",
                        neuron_id,
                        refractory_period,
                        neuron_storage_write.refractory_countdowns()[idx]
                    );
                }
            }
        }

        updated_count
    }

    /// Update threshold for all neurons in a cortical area (bulk parameter change)
    pub fn update_cortical_area_threshold(&mut self, cortical_area: u32, threshold: f32) -> usize {
        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.thresholds_mut()[idx] = T::from_f32(threshold);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update firing threshold limit (upper bound) for all neurons in a cortical area.
    ///
    /// Semantics:
    /// - limit == 0.0: no upper bound (neuron fires if MP >= threshold)
    /// - limit  > 0.0: neuron fires only if threshold <= MP <= limit
    ///
    /// This defines the firing window for neurons in the area.
    pub fn update_cortical_area_threshold_limit(
        &mut self,
        cortical_area: u32,
        limit: f32,
    ) -> usize {
        let mut updated_count = 0;
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // SIMD-friendly encoding: 0.0 means "no upper bound".
        // Internally, "no upper bound" is represented as T::max_value().
        let encoded_limit = if limit == 0.0 {
            T::max_value()
        } else {
            T::from_f32(limit)
        };

        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.threshold_limits_mut()[idx] = encoded_limit;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update thresholds with spatial gradient for all neurons in a cortical area.
    ///
    /// Applies position-based threshold calculation WITHOUT rebuilding neurons:
    /// `threshold = base + (x * inc_x) + (y * inc_y) + (z * inc_z)`
    ///
    /// This allows dynamic adjustment of spatial gradients without neuron recreation.
    /// Uses each neuron's stored (x, y, z) position for calculation.
    ///
    /// # Arguments
    /// * `cortical_area` - Cortical area index
    /// * `base_threshold` - Base firing threshold
    /// * `increment_x/y/z` - Spatial gradient increments
    ///
    /// # Returns
    /// Number of neurons updated
    pub fn update_cortical_area_threshold_with_gradient(
        &mut self,
        cortical_area: u32,
        base_threshold: f32,
        increment_x: f32,
        increment_y: f32,
        increment_z: f32,
    ) -> usize {
        let mut updated_count = 0;

        // Fast path: if all increments are 0, just set uniform threshold
        if increment_x == 0.0 && increment_y == 0.0 && increment_z == 0.0 {
            return self.update_cortical_area_threshold(cortical_area, base_threshold);
        }

        // Acquire write lock once for all updates
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                // Read neuron's actual stored position (interleaved: x,y,z,x,y,z,...)
                let coord_idx = idx * 3;
                let coords = neuron_storage_write.coordinates();
                let x = coords[coord_idx];
                let y = coords[coord_idx + 1];
                let z = coords[coord_idx + 2];

                // Calculate position-based threshold
                let threshold = base_threshold
                    + (x as f32 * increment_x)
                    + (y as f32 * increment_y)
                    + (z as f32 * increment_z);

                // Update threshold (coordinates borrow is dropped here)
                neuron_storage_write.thresholds_mut()[idx] = T::from_f32(threshold);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update leak coefficient for all neurons in a cortical area (bulk parameter change)
    pub fn update_cortical_area_leak(&mut self, cortical_area: u32, leak: f32) -> usize {
        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.leak_coefficients_mut()[idx] = leak;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update consecutive fire limit for all neurons in a cortical area (bulk parameter change)
    pub fn update_cortical_area_consecutive_fire_limit(
        &mut self,
        cortical_area: u32,
        limit: u16,
    ) -> usize {
        let mut updated_count = 0;
        // Semantics: 0 disables the limiter (unlimited). Internally we use MAX encoding.
        let encoded_limit = if limit == 0 { u16::MAX } else { limit };

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.consecutive_fire_limits_mut()[idx] = encoded_limit;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update snooze period (extended refractory) for all neurons in a cortical area (bulk parameter change)
    pub fn update_cortical_area_snooze_period(
        &mut self,
        cortical_area: u32,
        snooze_period: u16,
    ) -> usize {
        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.snooze_periods_mut()[idx] = snooze_period;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update refractory period for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_refractory_period(&mut self, neuron_ids: &[u32], values: &[u16]) -> usize {
        info!(
            "[RUST-BATCH-UPDATE] batch_update_refractory_period: {} neurons",
            neuron_ids.len()
        );

        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                // Update base period
                self.neuron_storage
                    .write()
                    .unwrap()
                    .refractory_periods_mut()[idx] = *value;
                // Enforce immediately: set countdown to new period (or 0)
                if *value > 0 {
                    self.neuron_storage
                        .write()
                        .unwrap()
                        .refractory_countdowns_mut()[idx] = *value;
                } else {
                    self.neuron_storage
                        .write()
                        .unwrap()
                        .refractory_countdowns_mut()[idx] = 0;
                }
                // Reset consecutive fire count to avoid stale extended refractory state
                self.neuron_storage
                    .write()
                    .unwrap()
                    .consecutive_fire_counts_mut()[idx] = 0;
                updated_count += 1;

                // Log first few neurons and any that match our monitored neuron 16438
                if updated_count <= 3 || *neuron_id == 16438 {
                    info!(
                        "[RUST-BATCH-UPDATE]   Neuron {}: refractory_period={}, countdown={}",
                        neuron_id,
                        value,
                        self.neuron_storage.read().unwrap().refractory_countdowns()[idx]
                    );
                }
            }
        }

        updated_count
    }

    /// Batch update threshold for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_threshold(&mut self, neuron_ids: &[u32], values: &[f32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage.write().unwrap().thresholds_mut()[idx] = T::from_f32(*value);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update leak coefficient for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_leak_coefficient(&mut self, neuron_ids: &[u32], values: &[f32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage.write().unwrap().leak_coefficients_mut()[idx] =
                    value.clamp(0.0, 1.0);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update consecutive fire limit for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_consecutive_fire_limit(
        &mut self,
        neuron_ids: &[u32],
        values: &[u16],
    ) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            // Semantics: 0 disables the limiter (unlimited). Internally we use MAX encoding.
            let encoded_limit = if *value == 0 { u16::MAX } else { *value };
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage
                    .write()
                    .unwrap()
                    .consecutive_fire_limits_mut()[idx] = encoded_limit;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update snooze period (extended refractory) for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_snooze_period(&mut self, neuron_ids: &[u32], values: &[u16]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage.write().unwrap().snooze_periods_mut()[idx] = *value;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update membrane potential for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_membrane_potential(&mut self, neuron_ids: &[u32], values: &[f32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage
                    .write()
                    .unwrap()
                    .membrane_potentials_mut()[idx] = T::from_f32(*value);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update resting potential for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_resting_potential(&mut self, neuron_ids: &[u32], values: &[f32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage
                    .write()
                    .unwrap()
                    .resting_potentials_mut()[idx] = T::from_f32(*value);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update excitability for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_excitability(&mut self, neuron_ids: &[u32], values: &[f32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage.write().unwrap().excitabilities_mut()[idx] =
                    value.clamp(0.0, 1.0);
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update neuron type for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_neuron_type(&mut self, neuron_ids: &[u32], values: &[i32]) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage.write().unwrap().neuron_types_mut()[idx] = *value;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Batch update MP charge accumulation for multiple neurons
    /// Returns number of neurons updated
    pub fn batch_update_mp_charge_accumulation(
        &mut self,
        neuron_ids: &[u32],
        values: &[bool],
    ) -> usize {
        if neuron_ids.len() != values.len() {
            return 0;
        }

        let mut updated_count = 0;
        for (neuron_id, value) in neuron_ids.iter().zip(values.iter()) {
            let idx = *neuron_id as usize;
            if idx < self.neuron_storage.read().unwrap().count()
                && self.neuron_storage.read().unwrap().valid_mask()[idx]
            {
                self.neuron_storage
                    .write()
                    .unwrap()
                    .mp_charge_accumulation_mut()[idx] = *value;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update MP charge accumulation for all neurons in a cortical area
    /// Returns number of neurons updated
    pub fn update_cortical_area_mp_charge_accumulation(
        &mut self,
        cortical_area: u32,
        mp_charge_accumulation: bool,
    ) -> usize {
        let mut updated_count = 0;

        // CRITICAL: Acquire write lock ONCE, not per-neuron (huge performance gain)
        let mut neuron_storage_write = self.neuron_storage.write().unwrap();

        // CRITICAL: Iterate by ARRAY INDEX (not neuron_id!)
        for idx in 0..neuron_storage_write.count() {
            if neuron_storage_write.valid_mask()[idx]
                && neuron_storage_write.cortical_areas()[idx] == cortical_area
            {
                neuron_storage_write.mp_charge_accumulation_mut()[idx] = mp_charge_accumulation;
                updated_count += 1;
            }
        }

        updated_count
    }

    /// Update postsynaptic potential (PSP / conductance) for all **existing outgoing synapses**
    /// from neurons in the specified cortical area.
    ///
    /// @cursor:critical-path
    ///
    /// ## Why this exists
    /// Synaptic propagation uses the PSP stored per-synapse in `SynapseStorage::postsynaptic_potentials`.
    /// When a cortical area's `postsynaptic_current` is changed at runtime, the NPU must update the
    /// existing synapses' PSP values to ensure firing influences downstream neurons deterministically.
    ///
    /// ## Returns
    /// Number of synapses updated.
    pub fn update_cortical_area_postsynaptic_current(
        &mut self,
        cortical_area: u32,
        postsynaptic_potential: u8,
    ) -> usize {
        // Phase 1: Gather source neuron IDs for this cortical area.
        // NeuronId == array index by design in this NPU (see process_single_neuron).
        let source_neuron_ids: Vec<u32> = {
            let neuron_storage = self.neuron_storage.read().unwrap();
            let mut sources = Vec::new();
            for idx in 0..neuron_storage.count() {
                if neuron_storage.valid_mask()[idx]
                    && neuron_storage.cortical_areas()[idx] == cortical_area
                {
                    sources.push(idx as u32);
                }
            }
            sources
        };

        if source_neuron_ids.is_empty() {
            return 0;
        }

        // Phase 2: Update all synapses reachable from these sources using the propagation engine's index.
        //
        // Lock order: synapse_storage -> propagation_engine (matches other NPU paths to avoid deadlocks).
        let mut synapse_storage = self.synapse_storage.write().unwrap();
        let prop_engine = self.propagation_engine.read().unwrap();

        let mut updated = 0usize;
        for source_id in source_neuron_ids {
            let src = NeuronId(source_id);
            if let Some(indices) = prop_engine.synapse_index.get(&src) {
                for &syn_idx in indices {
                    // IMPORTANT: Avoid simultaneous mutable + immutable borrows from synapse_storage.
                    // Take a short-lived immutable borrow to check validity, then (if valid) mutate PSP.
                    let is_valid = {
                        let valid = synapse_storage.valid_mask();
                        syn_idx < valid.len() && valid[syn_idx]
                    };

                    if is_valid {
                        let psps = synapse_storage.postsynaptic_potentials_mut();
                        // syn_idx bounds already checked against valid.len(); lengths match by contract.
                        psps[syn_idx] = postsynaptic_potential;
                        updated += 1;
                    }
                }
            }
        }

        updated
    }

    /// Delete a neuron (mark as invalid)
    /// Returns true if successful, false if neuron out of bounds
    pub fn delete_neuron(&mut self, neuron_id: u32) -> bool {
        let idx = neuron_id as usize;
        let neuron_storage = self.neuron_storage.read().unwrap();
        if idx >= neuron_storage.count() {
            return false;
        }

        // Check if this is a power neuron before deleting
        let is_power =
            neuron_storage.valid_mask()[idx] && neuron_storage.cortical_areas()[idx] == 1;
        drop(neuron_storage);

        self.neuron_storage.write().unwrap().valid_mask_mut()[idx] = false;

        // CRITICAL PERFORMANCE: Remove from power neuron cache if it was a power neuron
        if is_power {
            // No cache needed - power neuron is always neuron ID 1 (deterministic)
        }

        true
    }

    /// Check if a neuron exists and is valid (not deleted)
    pub fn is_neuron_valid(&self, neuron_id: u32) -> bool {
        let idx = neuron_id as usize;
        let neuron_storage = self.neuron_storage.read().unwrap();
        idx < neuron_storage.count() && neuron_storage.valid_mask()[idx]
    }

    /// Check if power neuron exists (using deterministic ID: neuron 1)
    ///
    /// This is O(1) - no scanning needed since power neuron is always neuron ID 1
    pub fn check_power_neuron_exists(&self) -> bool {
        let neuron_storage = self.neuron_storage.read().unwrap();
        let count = neuron_storage.count();

        // Direct O(1) access to neuron 1 (deterministic ID)
        if 1 < count {
            let is_valid = neuron_storage.valid_mask()[1];
            let cortical_area = neuron_storage.cortical_areas()[1];
            is_valid && cortical_area == 1
        } else {
            false
        }
    }

    /// Rebuild power neuron cache (no-op - kept for API compatibility)
    ///
    /// ARCHITECTURE: Core areas have deterministic neuron IDs:
    /// - Area 0 (_death) → neuron ID 0
    /// - Area 1 (_power) → neuron ID 1
    /// - Area 2 (_fatigue) → neuron ID 2
    ///
    /// This eliminates the need to scan all neurons (O(1) instead of O(n))
    /// Power injection now uses direct O(1) access to neuron 1 - no cache needed!
    pub fn rebuild_power_neuron_cache(&mut self) {
        // No-op: Power neuron is always neuron ID 1 (deterministic), no cache needed
        // This function is kept for API compatibility but does nothing
        // Power injection now uses direct O(1) access to neuron 1 in phase1_injection_with_synapses
    }

    /// Get neuron coordinates (x, y, z)
    pub fn get_neuron_coordinates(&self, neuron_id: u32) -> Option<(u32, u32, u32)> {
        self.neuron_storage
            .read()
            .unwrap()
            .get_coordinates(neuron_id as usize)
    }

    /// Get cortical area for a neuron
    pub fn get_neuron_cortical_area(&self, neuron_id: u32) -> u32 {
        self.neuron_storage
            .read()
            .unwrap()
            .get_cortical_area(neuron_id as usize)
            .unwrap_or_else(|| {
                panic!(
                    "Invariant violation: cortical area missing for neuron_id={}. This previously fell back to cortical_idx=0 (_death), which is forbidden.",
                    neuron_id
                )
            })
    }

    /// Get all neuron IDs in a specific cortical area
    pub fn get_neurons_in_cortical_area(&self, cortical_idx: u32) -> Vec<u32> {
        self.neuron_storage
            .read()
            .unwrap()
            .get_neurons_in_cortical_area(cortical_idx)
            .into_iter()
            .map(|idx| idx as u32)
            .collect()
    }

    /// Pre-populate the cortical area neuron index cache for all areas
    ///
    /// This eliminates expensive O(n) scans on first access to get_neurons_in_cortical_area.
    /// NOTE: Currently cache is built lazily on first access. To optimize, add prepopulate
    /// method to NeuronStorage trait and call it here after neurons are loaded.
    ///
    /// Get number of neurons in a specific cortical area
    pub fn get_cortical_area_neuron_count(&self, cortical_area: u32) -> usize {
        self.neuron_storage
            .read()
            .unwrap()
            .get_neuron_count(cortical_area)
    }

    /// Get total number of active neurons
    pub fn get_neuron_count(&self) -> usize {
        self.neuron_storage.read().unwrap().count()
    }

    /// Get synapse count (valid only)
    pub fn get_synapse_count(&self) -> usize {
        self.synapse_storage.read().unwrap().valid_count()
    }

    /// Get all outgoing synapses from a source neuron
    /// Returns Vec of (target_neuron_id, weight)
    pub fn get_outgoing_synapses(&self, source_neuron_id: u32) -> Vec<(u32, u8, u8, u8)> {
        let source = NeuronId(source_neuron_id);

        // Look up synapse indices for this source neuron
        let prop_engine = self.propagation_engine.read().unwrap();
        let synapse_indices = match prop_engine.synapse_index.get(&source) {
            Some(indices) => indices,
            None => return Vec::new(), // No synapses from this neuron
        };

        // Collect all valid synapses with full properties
        let mut outgoing = Vec::new();
        for &syn_idx in synapse_indices {
            if syn_idx < self.synapse_storage.read().unwrap().count()
                && self.synapse_storage.read().unwrap().valid_mask()[syn_idx]
            {
                let target = self.synapse_storage.read().unwrap().target_neurons()[syn_idx];
                let weight = self.synapse_storage.read().unwrap().weights()[syn_idx];
                let psp = self
                    .synapse_storage
                    .read()
                    .unwrap()
                    .postsynaptic_potentials()[syn_idx];
                let synapse_type = self.synapse_storage.read().unwrap().types()[syn_idx];
                outgoing.push((target, weight, psp, synapse_type));
            }
        }

        outgoing
    }

    /// Get incoming synapses for a neuron (neuron is the target)
    /// Returns Vec<(source_neuron_id, weight, conductance, synapse_type)>
    pub fn get_incoming_synapses(&self, target_neuron_id: u32) -> Vec<(u32, u8, u8, u8)> {
        let mut synapses = Vec::new();

        // Iterate through all synapses to find ones targeting this neuron
        // Note: This is O(n) - we could optimize with a target_index HashMap if needed
        for i in 0..self.synapse_storage.read().unwrap().count() {
            if self.synapse_storage.read().unwrap().valid_mask()[i]
                && self.synapse_storage.read().unwrap().target_neurons()[i] == target_neuron_id
            {
                synapses.push((
                    self.synapse_storage.read().unwrap().source_neurons()[i],
                    self.synapse_storage.read().unwrap().weights()[i],
                    self.synapse_storage
                        .read()
                        .unwrap()
                        .postsynaptic_potentials()[i],
                    self.synapse_storage.read().unwrap().types()[i],
                ));
            }
        }

        synapses
    }

    /// Rebuild the synapse index in the propagation engine
    ///
    /// CRITICAL: This MUST be called after adding/removing synapses to update the
    /// internal index used by get_outgoing_synapses() and synaptic propagation.
    ///
    /// Without calling this, newly created synapses will be invisible to queries!
    pub fn rebuild_synapse_index(&mut self) {
        let synapse_storage = self.synapse_storage.read().unwrap();
        let mut prop_engine = self.propagation_engine.write().unwrap();
        prop_engine.build_synapse_index(&*synapse_storage);
        drop(prop_engine);
        drop(synapse_storage);

        // Keep STDP mapping index in sync with synapse topology.
        self.rebuild_stdp_mapping_index();
    }

    /// Register or update STDP parameters for a plastic cortical mapping A→B.
    pub fn register_stdp_mapping(
        &mut self,
        src_cortical_idx: u32,
        dst_cortical_idx: u32,
        params: StdpMappingParams,
    ) -> Result<()> {
        if params.plasticity_window == 0 {
            return Err(FeagiError::RuntimeError(
                "plasticity_window must be > 0".to_string(),
            ));
        }
        let key: CorticalMappingKey = (src_cortical_idx, dst_cortical_idx);
        self.stdp_mappings.write().unwrap().insert(key, params);
        Ok(())
    }

    /// Unregister STDP parameters for a cortical mapping A→B.
    pub fn unregister_stdp_mapping(
        &mut self,
        src_cortical_idx: u32,
        dst_cortical_idx: u32,
    ) -> bool {
        let key: CorticalMappingKey = (src_cortical_idx, dst_cortical_idx);
        self.stdp_mappings.write().unwrap().remove(&key).is_some()
    }

    fn rebuild_stdp_mapping_index(&mut self) {
        let mappings = self.stdp_mappings.read().unwrap().clone();
        if mappings.is_empty() {
            *self.stdp_mapping_index.write().unwrap() = AHashMap::new();
            return;
        }

        let synapse_storage = self.synapse_storage.read().unwrap();
        let neuron_storage = self.neuron_storage.read().unwrap();

        let mut index: AHashMap<CorticalMappingKey, Vec<usize>> =
            AHashMap::with_capacity(mappings.len());

        for syn_idx in 0..synapse_storage.count() {
            if !synapse_storage.valid_mask()[syn_idx] {
                continue;
            }

            let src_neuron = synapse_storage.source_neurons()[syn_idx] as usize;
            let dst_neuron = synapse_storage.target_neurons()[syn_idx] as usize;
            if src_neuron >= neuron_storage.count() || dst_neuron >= neuron_storage.count() {
                continue;
            }
            if !neuron_storage.valid_mask()[src_neuron] || !neuron_storage.valid_mask()[dst_neuron]
            {
                continue;
            }

            let src_area = neuron_storage.cortical_areas()[src_neuron];
            let dst_area = neuron_storage.cortical_areas()[dst_neuron];
            let key: CorticalMappingKey = (src_area, dst_area);

            if mappings.contains_key(&key) {
                index.entry(key).or_default().push(syn_idx);
            }
        }

        *self.stdp_mapping_index.write().unwrap() = index;
    }

    fn apply_stdp_updates_for_burst(
        &self,
        burst_timestep: u64,
        fire_ledger: &crate::fire_ledger::FireLedger,
    ) -> Result<()> {
        let mappings = self.stdp_mappings.read().unwrap().clone();
        if mappings.is_empty() {
            return Ok(());
        }

        let mapping_index = self.stdp_mapping_index.read().unwrap().clone();
        if mapping_index.is_empty() {
            return Ok(());
        }

        // Precompute window activity sets per mapping.
        let mut activity_sets: AHashMap<CorticalMappingKey, (RoaringBitmap, RoaringBitmap)> =
            AHashMap::with_capacity(mappings.len());

        for (key @ (src_area, dst_area), params) in &mappings {
            let depth = params.plasticity_window;
            let src_window =
                match fire_ledger.get_dense_window_bitmaps(*src_area, burst_timestep, depth) {
                    Ok(w) => w,
                    Err(_) => continue, // insufficient history / not tracked yet
                };
            let dst_window =
                match fire_ledger.get_dense_window_bitmaps(*dst_area, burst_timestep, depth) {
                    Ok(w) => w,
                    Err(_) => continue,
                };

            let mut src_active = RoaringBitmap::new();
            for (_t, bm) in src_window {
                src_active |= bm;
            }
            let mut dst_active = RoaringBitmap::new();
            for (_t, bm) in dst_window {
                dst_active |= bm;
            }

            activity_sets.insert(*key, (src_active, dst_active));
        }

        if activity_sets.is_empty() {
            return Ok(());
        }

        // Apply weight updates.
        let mut synapse_storage = self.synapse_storage.write().unwrap();

        for (key, params) in mappings {
            let Some((src_active, dst_active)) = activity_sets.get(&key) else {
                continue;
            };
            let Some(syn_indices) = mapping_index.get(&key) else {
                continue;
            };

            let delta_plus = params.delta_plus_u8();
            let delta_minus = params.delta_minus_u8();
            if delta_plus == 0 && delta_minus == 0 {
                continue;
            }

            for &syn_idx in syn_indices {
                if syn_idx >= synapse_storage.count() || !synapse_storage.valid_mask()[syn_idx] {
                    continue;
                }

                let src_neuron = synapse_storage.source_neurons()[syn_idx];
                let dst_neuron = synapse_storage.target_neurons()[syn_idx];

                let src_present = src_active.contains(src_neuron);
                let dst_present = dst_active.contains(dst_neuron);

                if src_present && dst_present {
                    let old = synapse_storage.weights()[syn_idx];
                    let new_w = old.saturating_add(delta_plus);
                    synapse_storage.weights_mut()[syn_idx] = new_w;
                } else if src_present ^ dst_present {
                    let old = synapse_storage.weights()[syn_idx];
                    let new_w = old.saturating_sub(delta_minus);
                    synapse_storage.weights_mut()[syn_idx] = new_w;
                }
            }
        }

        Ok(())
    }

    /// Get neuron state for diagnostics (CFC, extended refractory, potential, etc.)
    /// Returns (cfc, cfc_limit, extended_refrac_period, potential, threshold, refrac_countdown)
    pub fn get_neuron_state(&self, neuron_id: NeuronId) -> Option<(u16, u16, u16, f32, f32, u16)> {
        // neuron_id == array index (direct access)
        let idx = neuron_id.0 as usize;
        if idx >= self.neuron_storage.read().unwrap().count()
            || !self.neuron_storage.read().unwrap().valid_mask()[idx]
        {
            return None;
        }

        Some((
            self.neuron_storage
                .read()
                .unwrap()
                .consecutive_fire_counts()[idx],
            self.neuron_storage
                .read()
                .unwrap()
                .consecutive_fire_limits()[idx],
            self.neuron_storage.read().unwrap().snooze_periods()[idx], // Extended refractory period (additive)
            self.neuron_storage.read().unwrap().membrane_potentials()[idx].to_f32(),
            self.neuron_storage.read().unwrap().thresholds()[idx].to_f32(),
            self.neuron_storage.read().unwrap().refractory_countdowns()[idx],
        ))
    }
}

/// Phase 1 injection result
///
/// Migration status: Metrics struct for burst processing. Will be used for monitoring
/// and debugging once telemetry system is migrated from Python.
/// Warning about unused struct is expected during migration.
#[derive(Debug)]
#[allow(dead_code)] // In development - used for monitoring/debugging
struct InjectionResult {
    power_injections: usize,
    fatigue_injections: usize,
    synaptic_injections: usize,
    sensory_injections: usize,
}

/// Phase 1 injection with automatic power neuron discovery
///
/// 🔋 Power neurons are identified by cortical_idx = 1 (_power area)
/// No separate list - scans neuron array directly!
#[allow(clippy::too_many_arguments)]
fn phase1_injection_with_synapses<
    T: NeuralValue,
    N: NeuronStorage<Value = T>,
    S: SynapseStorage,
>(
    fcl: &mut FireCandidateList,
    neuron_storage: &mut N,
    propagation_engine: &mut SynapticPropagationEngine,
    previous_fire_queue: &FireQueue,
    power_amount: f32,
    synapse_storage: &S,
    pending_sensory: &std::sync::Mutex<Vec<(NeuronId, f32)>>,
    fatigue_active: bool,
) -> Result<InjectionResult> {
    // Clear FCL from previous burst
    fcl.clear();

    let sim_timestep = crate::sim_timestep();

    let mut power_count = 0;
    let mut synaptic_count = 0;
    let mut sensory_count = 0;

    // 0. Drain pending sensory injections (AFTER clear, BEFORE power/synapses)
    let sensory_start = std::time::Instant::now();
    if let Ok(mut pending) = pending_sensory.lock() {
        if !pending.is_empty() {
            // 🔍 DEBUG: Log first sensory injection
            static FIRST_SENSORY_LOG: std::sync::Once = std::sync::Once::new();
            FIRST_SENSORY_LOG.call_once(|| {
                info!("╔══════════════════════════════════════════════════════════════");
                info!("║ [SENSORY-INJECTION] 🎬 DRAINING STAGED SENSORY DATA");
                info!(
                    "║ Injecting {} neurons AFTER FCL clear (prevents race)",
                    pending.len()
                );
                info!("╚══════════════════════════════════════════════════════════════");
            });

            for (neuron_id, potential) in pending.drain(..) {
                fcl.add_candidate(neuron_id, potential);
                sensory_count += 1;
            }
        }
    }
    let sensory_duration = sensory_start.elapsed();

    // Warn when injection exceeds the configured simulation timestep.
    // This accounts for all use-cases (real-time, batch, etc.) because timestep is runtime-configured.
    if sensory_count > 0
        && sim_timestep > std::time::Duration::from_nanos(0)
        && sensory_duration > sim_timestep
    {
        warn!(
            "[PHASE1-SENSORY] Injection exceeded timestep: {:.2}ms > {:.2}ms ({} neurons)",
            sensory_duration.as_secs_f64() * 1000.0,
            sim_timestep.as_secs_f64() * 1000.0,
            sensory_count
        );
    }

    // 1. Power Injection + Membrane Potential Reset - OPTIMIZED FOR LARGE COUNTS
    // CRITICAL PERFORMANCE FIX: Direct O(1) access to power neuron (neuron ID 1, deterministic)
    // No cache needed - power neuron is always neuron ID 1
    static FIRST_LOG: std::sync::Once = std::sync::Once::new();
    FIRST_LOG.call_once(|| {
        info!("╔══════════════════════════════════════════════════════════════");
        info!("║ [POWER-INJECTION] 🔋 DIRECT ACCESS TO NEURON 1 (DETERMINISTIC ID)");
        info!("║ Power neuron is always neuron ID 1 - O(1) access, no cache needed!");
        info!("╚══════════════════════════════════════════════════════════════");
    });

    // PERFORMANCE OPTIMIZATION: For large neuron counts, only reset neurons from fire queue
    // This avoids scanning all 3M+ neurons when only a small fraction need resetting
    let neuron_count = neuron_storage.count();
    let should_do_full_scan = neuron_count < 1_000_000;

    let power_start = std::time::Instant::now();

    if should_do_full_scan {
        // Small neuron count: Direct O(1) access to power neuron (neuron ID 1, deterministic)
        if 1 < neuron_count
            && neuron_storage.valid_mask()[1]
            && neuron_storage.cortical_areas()[1] == 1
        {
            fcl.add_candidate(NeuronId(1), power_amount);
            power_count += 1;
        }

        // Reset membrane potential for non-accumulating neurons
        for idx in 0..neuron_count {
            if neuron_storage.valid_mask()[idx] && !neuron_storage.mp_charge_accumulation()[idx] {
                neuron_storage.membrane_potentials_mut()[idx] = T::zero();
            }
        }
    } else {
        // Large neuron count (3M+): Optimized approach
        // 1. Reset only neurons that fired in previous burst (from fire queue)
        for (_cortical_idx, neurons) in &previous_fire_queue.neurons_by_area {
            for neuron in neurons {
                let idx = neuron.neuron_id.0 as usize;
                if idx < neuron_count
                    && neuron_storage.valid_mask()[idx]
                    && !neuron_storage.mp_charge_accumulation()[idx]
                {
                    neuron_storage.membrane_potentials_mut()[idx] = T::zero();
                }
            }
        }

        // 2. Direct access to power neuron (neuron ID 1, deterministic) - O(1)!
        // No cache needed - power neuron is always neuron ID 1
        if 1 < neuron_count
            && neuron_storage.valid_mask()[1]
            && neuron_storage.cortical_areas()[1] == 1
        {
            fcl.add_candidate(NeuronId(1), power_amount);
            power_count += 1;
        }
    }

    let power_duration = power_start.elapsed();

    // Warn when injection exceeds the configured simulation timestep.
    if power_count > 0
        && sim_timestep > std::time::Duration::from_nanos(0)
        && power_duration > sim_timestep
    {
        warn!(
            "[PHASE1-POWER] Injection exceeded timestep: {:.2}ms > {:.2}ms ({} neurons, total: {})",
            power_duration.as_secs_f64() * 1000.0,
            sim_timestep.as_secs_f64() * 1000.0,
            power_count,
            neuron_count
        );
    }

    // Log first injection and track power neuron count changes
    use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
    static FIRST_INJECTION: AtomicBool = AtomicBool::new(false);
    static LAST_POWER_COUNT: AtomicUsize = AtomicUsize::new(0);

    let last_count = LAST_POWER_COUNT.load(Ordering::Relaxed);

    if !FIRST_INJECTION.load(Ordering::Relaxed) && power_count > 0 {
        // First burst with power neurons found
        info!(
            "[POWER-INJECTION] ✅ Injected {} power neurons into FCL",
            power_count
        );
        FIRST_INJECTION.store(true, Ordering::Relaxed);
        LAST_POWER_COUNT.store(power_count, Ordering::Relaxed);
    } else if power_count == 0 && FIRST_INJECTION.load(Ordering::Relaxed) && last_count > 0 {
        // Power neurons disappeared after working
        error!(
            "[POWER-INJECTION] ❌ ERROR: Power neurons DISAPPEARED! (was {}, now 0)",
            last_count
        );
        LAST_POWER_COUNT.store(0, Ordering::Relaxed);
    } else if power_count == 0 && !FIRST_INJECTION.load(Ordering::Relaxed) {
        // First burst with no power neurons (pre-embryogenesis)
        warn!("[POWER-INJECTION] ⚠️ No power neurons found yet (cortical_idx=1 '_power' area not created or empty) - will auto-discover after genome load");
        FIRST_INJECTION.store(true, Ordering::Relaxed);
        LAST_POWER_COUNT.store(0, Ordering::Relaxed);
    } else if power_count > 0 && last_count == 0 {
        // Power neurons APPEARED after being absent (0→N transition) - CRITICAL LOG
        info!(
            "[POWER-INJECTION] ✅ Power neurons NOW ACTIVE! Injected {} neurons into FCL (was 0, genome loaded successfully)",
            power_count
        );
        LAST_POWER_COUNT.store(power_count, Ordering::Relaxed);
    } else if power_count != last_count && power_count > 0 && last_count > 0 {
        // Power neuron count changed (N→M transition where both are non-zero)
        info!(
            "[POWER-INJECTION] ℹ️  Power neuron count changed: {} → {} neurons",
            last_count, power_count
        );
        LAST_POWER_COUNT.store(power_count, Ordering::Relaxed);
    }

    // 1.5. Fatigue Injection (conditional, extremely light)
    // Check fatigue_active boolean - if true, inject fatigue neurons (cortical_area = 2)
    let mut fatigue_count = 0;

    if fatigue_active {
        // Scan all neurons for _fatigue cortical area (cortical_idx = 2)
        for array_idx in 0..neuron_storage.count() {
            let neuron_id = array_idx as u32;
            if array_idx < neuron_storage.count() && neuron_storage.valid_mask()[array_idx] {
                let cortical_area = neuron_storage.cortical_areas()[array_idx];

                // Check if this is a fatigue neuron (cortical_area = 2)
                if cortical_area == 2 {
                    fcl.add_candidate(NeuronId(neuron_id), power_amount);
                    fatigue_count += 1;
                }
            }
        }

        if fatigue_count > 0 {
            trace!(
                "[FATIGUE-INJECTION] ⚠️  Fatigue detected! Injected {} fatigue neurons into FCL",
                fatigue_count
            );
        }
    }

    // 2. Synaptic Propagation
    let synaptic_start = std::time::Instant::now();
    if !previous_fire_queue.is_empty() {
        let fired_ids = previous_fire_queue.get_all_neuron_ids();

        // Build membrane potential map for fired neurons (needed for mp_driven_psp feature)
        //
        // IMPORTANT:
        // - We must use the *firing-time* membrane potential, not the current neuron_storage value.
        //   Neurons reset their membrane potential after firing, so neuron_storage will typically
        //   contain 0 by the time we propagate (which breaks mp_driven_psp).
        // - FireQueue stores the membrane potential captured at the moment of firing.
        //
        // Conversion contract:
        // - Synaptic propagation expects a u8 conductance (0..=255).
        //
        // IMPORTANT:
        // This conversion must be monotonic. A previous heuristic attempted to "downscale"
        // values above 255 by dividing by 255, which introduced a discontinuity:
        //   MP=255   -> 255
        //   MP=256   -> ~1
        // This can make downstream neurons appear to "stop responding" as upstream PSP increases.
        //
        // We therefore clamp deterministically into the representable u8 range.
        let mp_build_start = std::time::Instant::now();
        let mut neuron_mps: ahash::AHashMap<NeuronId, u8> = ahash::AHashMap::new();
        let u8_max_f32 = u8::MAX as f32;
        for neurons in previous_fire_queue.neurons_by_area.values() {
            for neuron in neurons {
                let mp_f32 = neuron.membrane_potential;
                let mp_u8 = mp_f32.clamp(0.0, u8_max_f32).round() as u8;
                neuron_mps.insert(neuron.neuron_id, mp_u8);
            }
        }
        let mp_build_duration = mp_build_start.elapsed();

        // Call synaptic propagation engine (ZERO-COPY: pass synapse_storage by reference)
        let propagate_start = std::time::Instant::now();
        let propagation_result =
            propagation_engine.propagate(&fired_ids, synapse_storage, &neuron_mps)?;
        let propagate_duration = propagate_start.elapsed();

        // Inject propagated potentials into FCL (OPTIMIZED: pre-allocate + direct insertion)
        let inject_start = std::time::Instant::now();

        // Count total candidates for pre-allocation
        let total_candidates: usize = propagation_result
            .values()
            .map(|targets| targets.len())
            .sum();

        // Pre-allocate FCL HashMap (critical for performance with millions of insertions)
        // Heuristic: ~10% unique neurons (many synapses target same neurons)
        if total_candidates > 100_000 {
            let estimated_unique = total_candidates / 10;
            fcl.reserve(estimated_unique);
        }

        // Direct insertion (faster than flattening first)
        for (_cortical_area, targets) in propagation_result {
            for &(target_neuron_id, contribution) in &targets {
                fcl.add_candidate(target_neuron_id, contribution.0);
                synaptic_count += 1;
            }
        }

        let inject_duration = inject_start.elapsed();
        let synaptic_duration = synaptic_start.elapsed();

        // Synaptic propagation telemetry - only checked when debug logging is enabled (zero runtime overhead otherwise)
        if tracing::enabled!(tracing::Level::DEBUG) && synaptic_duration.as_millis() > 10 {
            debug!(
                "[PHASE1-SYNAPTIC] Slow synaptic propagation: total={:.2}ms | mp_build={:.2}ms | propagate={:.2}ms | inject={:.2}ms | fired={} | synapses={}",
                synaptic_duration.as_secs_f64() * 1000.0,
                mp_build_duration.as_secs_f64() * 1000.0,
                propagate_duration.as_secs_f64() * 1000.0,
                inject_duration.as_secs_f64() * 1000.0,
                fired_ids.len(),
                synaptic_count
            );

            // Fine-grained breakdown from the propagation engine (populated per-call).
            if let Some(profile) = propagation_engine.last_profile() {
                debug!(
                    "[PHASE1-SYNAPTIC-PROFILE] fired={} synapse_indices={} unique_sources={} contributions={} | gather={:.2}ms metadata={:.2}ms compute={:.2}ms group={:.2}ms total={:.2}ms | rayon_threads={}",
                    profile.fired_neurons,
                    profile.synapse_indices,
                    profile.unique_sources,
                    profile.contributions,
                    profile.gather_ms,
                    profile.metadata_ms,
                    profile.compute_ms,
                    profile.group_ms,
                    profile.total_ms,
                    profile.rayon_threads
                );
            }
        }
    }

    Ok(InjectionResult {
        power_injections: power_count,
        fatigue_injections: fatigue_count,
        synaptic_injections: synaptic_count,
        sensory_injections: sensory_count,
    })
}

// ═══════════════════════════════════════════════════════════
// Fire Ledger API (Extension of RustNPU impl)
// ═══════════════════════════════════════════════════════════
impl<
        R: Runtime,
        T: NeuralValue,
        B: crate::backend::ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>,
    > RustNPU<R, T, B>
{
    /// Get a dense, burst-aligned window of firing history as RoaringBitmaps.
    ///
    /// Returns exactly `depth` frames covering `[end_timestep - depth + 1 .. end_timestep]`.
    pub fn get_fire_ledger_dense_window_bitmaps(
        &self,
        cortical_idx: u32,
        end_timestep: u64,
        depth: usize,
    ) -> Result<Vec<(u64, RoaringBitmap)>> {
        self.fire_structures
            .lock()
            .unwrap()
            .fire_ledger
            .get_dense_window_bitmaps(cortical_idx, end_timestep, depth)
            .map_err(|e| FeagiError::RuntimeError(format!("FireLedger query failed: {e}")))
    }

    /// Get the tracked FireLedger window size for a cortical area.
    pub fn get_fire_ledger_window_size(&self, cortical_idx: u32) -> Result<usize> {
        self.fire_structures
            .lock()
            .unwrap()
            .fire_ledger
            .get_tracked_window(cortical_idx)
            .map_err(|e| FeagiError::RuntimeError(format!("FireLedger query failed: {e}")))
    }

    /// Track a cortical area in the FireLedger with an explicit window size.
    pub fn configure_fire_ledger_window(
        &mut self,
        cortical_idx: u32,
        window_size: usize,
    ) -> Result<()> {
        self.fire_structures
            .lock()
            .unwrap()
            .fire_ledger
            .track_area(cortical_idx, window_size)
            .map_err(|e| FeagiError::RuntimeError(format!("FireLedger track failed: {e}")))?;
        Ok(())
    }

    /// Get all tracked FireLedger window sizes (tracked areas only).
    pub fn get_all_fire_ledger_configs(&self) -> Vec<(u32, usize)> {
        self.fire_structures
            .lock()
            .unwrap()
            .fire_ledger
            .get_tracked_windows()
    }
}

// ═══════════════════════════════════════════════════════════
// FQ Sampler API (Entry Point #2: Motor/Visualization Output)
// ═══════════════════════════════════════════════════════════
impl<
        R: Runtime,
        T: NeuralValue,
        B: crate::backend::ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>,
    > RustNPU<R, T, B>
{
    /// Sample the current Fire Queue for visualization/motor output
    ///
    /// Returns None if:
    /// - Rate limit not met
    /// - Fire Queue is empty
    /// - Burst already sampled (deduplication)
    ///
    /// Returns HashMap of cortical_idx -> area data
    ///
    /// ⚠️ DEPRECATED: This method triggers deduplication and may return None if burst already sampled.
    /// Use `get_latest_fire_queue_sample()` instead for non-consuming reads.
    pub fn sample_fire_queue(&mut self) -> Option<FireQueueSample> {
        let mut fire_structures = self.fire_structures.lock().unwrap();
        let current_fq_clone = fire_structures.current_fire_queue.clone();
        let sample_result = fire_structures.fq_sampler.sample(&current_fq_clone)?;
        drop(fire_structures);

        // Convert to Python-friendly format
        let mut result = AHashMap::new();
        for (cortical_idx, area_data) in sample_result.areas {
            result.insert(
                cortical_idx,
                (
                    area_data.neuron_ids,
                    area_data.coordinates_x,
                    area_data.coordinates_y,
                    area_data.coordinates_z,
                    area_data.potentials,
                ),
            );
        }

        Some(result)
    }

    /// Get the latest cached Fire Queue sample (non-consuming read)
    ///
    /// This returns the most recent sample WITHOUT triggering rate limiting or deduplication.
    /// Perfect for Python wrappers and SHM writers that need to read the same burst multiple times.
    ///
    /// Returns None if no sample has been taken yet (no bursts processed).
    pub fn get_latest_fire_queue_sample(&self) -> Option<FireQueueSample> {
        let fire_structures = self.fire_structures.lock().unwrap();
        let sample_result = fire_structures.fq_sampler.get_latest_sample()?;

        // Convert to Python-friendly format
        let mut result = AHashMap::new();
        for (cortical_idx, area_data) in &sample_result.areas {
            result.insert(
                *cortical_idx,
                (
                    area_data.neuron_ids.clone(),
                    area_data.coordinates_x.clone(),
                    area_data.coordinates_y.clone(),
                    area_data.coordinates_z.clone(),
                    area_data.potentials.clone(),
                ),
            );
        }

        Some(result)
    }

    /// Force sample the Fire Queue (for burst loop, bypasses rate limiting)
    ///
    /// This is used by the burst loop to sample on every burst, regardless of the FQ sampler's
    /// configured rate limit. The rate limiting is meant for external consumers, not the burst loop itself.
    pub fn force_sample_fire_queue(&mut self) -> Option<FireQueueSample> {
        // FIXED: Use get_current_fire_queue() instead of accessing private fields
        Some(self.get_current_fire_queue())
    }

    /// Get current Fire Queue directly (bypasses FQ Sampler rate limiting)
    /// Used by FCL endpoint to get real-time firing data without sampling delays
    pub fn get_current_fire_queue(&self) -> FireQueueSample {
        let mut result = AHashMap::new();

        // Convert current Fire Queue to the same format as sample_fire_queue
        for (cortical_idx, neurons) in &self
            .fire_structures
            .lock()
            .unwrap()
            .current_fire_queue
            .neurons_by_area
        {
            let mut neuron_ids = Vec::with_capacity(neurons.len());
            let mut coords_x = Vec::with_capacity(neurons.len());
            let mut coords_y = Vec::with_capacity(neurons.len());
            let mut coords_z = Vec::with_capacity(neurons.len());
            let mut potentials = Vec::with_capacity(neurons.len());

            for neuron in neurons {
                neuron_ids.push(neuron.neuron_id.0);
                coords_x.push(neuron.x);
                coords_y.push(neuron.y);
                coords_z.push(neuron.z);
                potentials.push(neuron.membrane_potential);
            }

            result.insert(
                *cortical_idx,
                (neuron_ids, coords_x, coords_y, coords_z, potentials),
            );
        }

        result
    }

    /// Set FQ Sampler frequency (Hz)
    pub fn set_fq_sampler_frequency(&mut self, frequency_hz: f64) {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .set_sample_frequency(frequency_hz);
    }

    /// Get FQ Sampler frequency (Hz)
    pub fn get_fq_sampler_frequency(&self) -> f64 {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .get_sample_frequency()
    }

    /// Set visualization subscriber state
    pub fn set_visualization_subscribers(&mut self, has_subscribers: bool) {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .set_visualization_subscribers(has_subscribers);
    }

    /// Check if visualization subscribers are connected
    pub fn has_visualization_subscribers(&self) -> bool {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .has_visualization_subscribers()
    }

    /// Set motor subscriber state
    pub fn set_motor_subscribers(&mut self, has_subscribers: bool) {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .set_motor_subscribers(has_subscribers);
    }

    /// Check if motor subscribers are connected
    pub fn has_motor_subscribers(&self) -> bool {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .has_motor_subscribers()
    }

    /// Get total FQ Sampler samples taken
    pub fn get_fq_sampler_samples_taken(&self) -> u64 {
        self.fire_structures
            .lock()
            .unwrap()
            .fq_sampler
            .get_samples_taken()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use feagi_structures::genomic::cortical_area::CoreCorticalType;

    // ═══════════════════════════════════════════════════════════
    // Core NPU Creation & Initialization
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_npu_creation() {
        let npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        assert_eq!(npu.get_neuron_count(), 0);
        assert_eq!(npu.get_synapse_count(), 0);
        assert_eq!(npu.get_burst_count(), 0);
    }

    #[test]
    fn test_npu_creation_with_zero_capacity() {
        let npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                0, 0, 0,
            );
        assert_eq!(npu.get_neuron_count(), 0);
        assert_eq!(npu.get_synapse_count(), 0);
    }

    #[test]
    fn test_npu_creation_with_large_capacity() {
        let npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1_000_000, 10_000_000, 100,
            );
        assert_eq!(npu.get_neuron_count(), 0);
    }

    // ═══════════════════════════════════════════════════════════
    // Neuron Management
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_add_neurons() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        // Use a non-core cortical_idx to avoid auto-creating core neurons (0=_death, 1=_power, 2=_fatigue)
        // which would shift neuron IDs and counts in this test.
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let id1 = npu
            .add_neuron(
                1.0,
                f32::MAX,
                0.1,
                0.0,
                0,
                5,
                1.0,
                u16::MAX,
                0,
                true,
                3,
                0,
                0,
                0,
            )
            .unwrap();
        let id2 = npu
            .add_neuron(
                1.0,
                f32::MAX,
                0.1,
                0.0,
                0,
                5,
                1.0,
                u16::MAX,
                0,
                true,
                3,
                1,
                0,
                0,
            )
            .unwrap();

        assert_eq!(id1.0, 0);
        assert_eq!(id2.0, 1);
        assert_eq!(npu.get_neuron_count(), 2);
    }

    #[test]
    fn test_add_neuron_sequential_ids() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());

        for i in 0..10 {
            let id = npu
                .add_neuron(
                    1.0,
                    f32::MAX,
                    0.1,
                    0.0,
                    0,
                    5,
                    1.0,
                    u16::MAX,
                    0,
                    true,
                    3,
                    i,
                    0,
                    0,
                )
                .unwrap();
            assert_eq!(id.0, i);
        }

        assert_eq!(npu.get_neuron_count(), 10);
    }

    #[test]
    fn test_add_neuron_different_parameters() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // High threshold
        let _n1 = npu
            .add_neuron(
                10.0,
                f32::MAX,
                0.0,
                0.0,
                0,
                0,
                1.0,
                u16::MAX,
                0,
                true,
                3,
                0,
                0,
                0,
            )
            .unwrap();

        // High leak
        let _n2 = npu
            .add_neuron(1.0, 0.0, 0.9, 0.0, 0, 0, 1.0, 0, 0, true, 3, 1, 0, 0)
            .unwrap();

        // Long refractory period
        let _n3 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 100, 1.0, 0, 0, true, 3, 2, 0, 0)
            .unwrap();

        // Low excitability
        let _n4 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 0.1, 0, 0, true, 3, 3, 0, 0)
            .unwrap();

        assert_eq!(npu.get_neuron_count(), 4);
    }

    #[test]
    fn test_add_neuron_different_cortical_areas() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        // Use non-core area IDs to avoid implicit core neuron creation (0..=2).
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(4, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(5, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let _power = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 3, 0, 0, 0)
            .unwrap();
        let _area2 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 4, 0, 0, 0)
            .unwrap();
        let _area3 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 5, 0, 0, 0)
            .unwrap();

        assert_eq!(npu.get_neuron_count(), 3);
    }

    #[test]
    fn test_add_neuron_3d_coordinates() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let _n1 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 3, 5, 10, 15)
            .unwrap();

        assert_eq!(npu.get_neuron_count(), 1);
    }

    // ═══════════════════════════════════════════════════════════
    // Synapse Management
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_add_synapses() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 1, 0, 0)
            .unwrap();

        npu.add_synapse(
            n1,
            n2,
            SynapticWeight(128),
            SynapticConductance(255),
            SynapseType::Excitatory,
        )
        .unwrap();

        assert_eq!(npu.get_synapse_count(), 1);
    }

    #[test]
    fn test_add_multiple_synapses() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 1, 0, 0)
            .unwrap();
        let n3 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 2, 0, 0)
            .unwrap();

        npu.add_synapse(
            n1,
            n2,
            SynapticWeight(128),
            SynapticConductance(255),
            SynapseType::Excitatory,
        )
        .unwrap();
        npu.add_synapse(
            n1,
            n3,
            SynapticWeight(64),
            SynapticConductance(128),
            SynapseType::Excitatory,
        )
        .unwrap();
        npu.add_synapse(
            n2,
            n3,
            SynapticWeight(32),
            SynapticConductance(64),
            SynapseType::Inhibitory,
        )
        .unwrap();

        assert_eq!(npu.get_synapse_count(), 3);
    }

    #[test]
    fn test_add_inhibitory_synapse() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 1, 0, 0)
            .unwrap();

        npu.add_synapse(
            n1,
            n2,
            SynapticWeight(128),
            SynapticConductance(255),
            SynapseType::Inhibitory,
        )
        .unwrap();

        assert_eq!(npu.get_synapse_count(), 1);
    }

    #[test]
    fn test_synapse_removal() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.1, 0.0, 0, 5, 1.0, 0, 0, true, 1, 1, 0, 0)
            .unwrap();

        npu.add_synapse(
            n1,
            n2,
            SynapticWeight(128),
            SynapticConductance(255),
            SynapseType::Excitatory,
        )
        .unwrap();
        assert_eq!(npu.get_synapse_count(), 1);

        assert!(npu.remove_synapse(n1, n2));
        assert_eq!(npu.get_synapse_count(), 0);
    }

    #[test]
    fn test_remove_nonexistent_synapse() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 1, 0, 0)
            .unwrap();

        assert!(!npu.remove_synapse(n1, n2));
    }

    // ═══════════════════════════════════════════════════════════
    // Burst Processing & Power Injection
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_burst_processing() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 20,
            );
        // Register core areas in order so the deterministic core neurons land at IDs 0/1/2.
        npu.register_cortical_area(0, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        // Ensure power injection exceeds threshold (>= vs > comparisons can differ by backend/encoding).
        npu.set_power_amount(2.0);

        // Process burst with power injection
        let result = npu.process_burst().unwrap();

        assert_eq!(result.burst, 1);
        assert_eq!(result.power_injections, 1);
        assert_eq!(result.neuron_count, 1);
    }

    #[test]
    fn test_burst_counter_increments() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        for i in 1..=10 {
            let result = npu.process_burst().unwrap();
            assert_eq!(result.burst, i as u64);
            assert_eq!(npu.get_burst_count(), i as u64);
        }
    }

    #[test]
    fn test_power_injection_auto_discovery() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        // Register core areas in order so neuron ID 1 is the deterministic power neuron.
        npu.register_cortical_area(0, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // Add 5 power neurons (cortical_area=1)
        for i in 0..5 {
            npu.add_neuron(
                1.0,
                f32::MAX,
                0.0,
                0.0,
                0,
                5,
                1.0,
                u16::MAX,
                0,
                true,
                1,
                i,
                0,
                0,
            )
            .unwrap();
        }

        // Add 5 regular neurons (cortical_area=2)
        for i in 0..5 {
            npu.add_neuron(
                1.0,
                f32::MAX,
                0.0,
                0.0,
                0,
                5,
                1.0,
                u16::MAX,
                0,
                true,
                2,
                i,
                0,
                0,
            )
            .unwrap();
        }

        let result = npu.process_burst().unwrap();

        // Power injection is an O(1) deterministic injection into neuron ID 1 (the core _power neuron).
        assert_eq!(result.power_injections, 1);
    }

    #[test]
    fn test_set_power_amount() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(0, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        // Set high power amount
        npu.set_power_amount(10.0);

        // Should fire immediately (10.0 > 5.0 threshold)
        let result = npu.process_burst().unwrap();
        assert_eq!(result.neuron_count, 1);
    }

    #[test]
    fn test_mp_charge_accumulation_false_respects_threshold() {
        use feagi_npu_neural::{SynapseType, SynapticConductance, SynapticWeight};

        // REGRESSION TEST: Verify that neurons with mp_charge_accumulation=false
        // do NOT fire when PSP < threshold, even if they had residual potential
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );

        // Use non-core area IDs to avoid implicit core neuron creation (0..=2).
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(4, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // Area A: 1x1x1 with threshold 1.0, mp_acc=false
        let neuron_a = npu
            .add_neuron(
                1.0,      // threshold
                f32::MAX, // threshold_limit (MAX = no limit, SIMD-friendly encoding)
                0.0,      // leak_coefficient (no leak)
                0.0,      // resting_potential
                0,        // neuron_type
                0,        // refractory_period (fire every burst)
                1.0,      // excitability (always fire)
                0,        // consecutive_fire_limit (unlimited)
                0,        // snooze_period
                false,    // mp_charge_accumulation=FALSE (reset each burst)
                3,        // cortical_area
                0,
                0,
                0,
            )
            .unwrap();

        // Area B: 1x1x1 with threshold 1.1, mp_acc=false
        let neuron_b = npu
            .add_neuron(
                1.1,      // threshold (HIGHER than PSP)
                f32::MAX, // threshold_limit (MAX = no limit, SIMD-friendly encoding)
                0.0,      // leak_coefficient (no leak)
                0.0,      // resting_potential
                0,        // neuron_type
                0,        // refractory_period
                1.0,      // excitability (always fire if threshold met)
                0,        // consecutive_fire_limit
                0,        // snooze_period
                false,    // mp_charge_accumulation=FALSE (reset each burst)
                4,        // cortical_area
                0,
                0,
                0,
            )
            .unwrap();

        // Connect A → B with conductance=1.0 (PSP)
        // CRITICAL: Using weight=1, conductance=1 → PSP = 1×1 = 1.0
        npu.add_synapse(
            neuron_a,
            neuron_b,
            SynapticWeight(1),       // weight = 1
            SynapticConductance(1),  // conductance = 1 → PSP = 1×1 = 1.0
            SynapseType::Excitatory, // synapse_type (excitatory)
        )
        .unwrap();
        npu.rebuild_synapse_index();

        // Burst 1: Inject neuron_a manually to make it fire
        // NOTE: Synaptic propagation is applied on burst t+1 (Phase 1 uses previous burst's fire queue).
        npu.inject_sensory_with_potentials(&[(neuron_a, 1.5)]);
        let result1 = npu.process_burst().unwrap();

        println!("Burst 1 Results:");
        println!("  - Fired neurons: {:?}", result1.fired_neurons);
        println!("  - Synaptic injections: {}", result1.synaptic_injections);
        println!("  - Neurons processed: {}", result1.neurons_processed);

        // Neuron A should fire (1.5 > 1.0 threshold)
        assert!(
            result1.fired_neurons.contains(&neuron_a),
            "Neuron A should fire with 1.5 input"
        );

        // Burst 1 should NOT include synaptic propagation yet (previous fire queue was empty).
        assert_eq!(
            result1.synaptic_injections, 0,
            "No synaptic propagation should occur on the first burst"
        );

        // Burst 2: Fire neuron A again.
        // This burst should receive synaptic input to neuron B from burst 1.
        npu.inject_sensory_with_potentials(&[(neuron_a, 1.5)]);
        let result2 = npu.process_burst().unwrap();

        println!("Burst 2: Fired neurons: {:?}", result2.fired_neurons);

        // Check if synaptic propagation happened (from burst 1 → burst 2).
        assert!(
            result2.synaptic_injections > 0,
            "Synapse from A to B should have propagated on burst 2 (t+1)"
        );

        // Neuron A should fire again
        assert!(result2.fired_neurons.contains(&neuron_a));

        // Neuron B should NOT fire (PSP=1.0 < 1.1 threshold) on burst 2
        assert!(
            !result2.fired_neurons.contains(&neuron_b),
            "BUG: Neuron B fired with PSP=1.0 but threshold=1.1! mp_charge_accumulation=false not respected"
        );

        // Verify membrane potentials after burst 2
        {
            let neuron_storage = npu.neuron_storage.read().unwrap();
            let mp_a = neuron_storage.membrane_potentials()[neuron_a.0 as usize].to_f32();
            let mp_b = neuron_storage.membrane_potentials()[neuron_b.0 as usize].to_f32();

            // Neuron A fired, so it should be reset to 0
            assert_eq!(
                mp_a, 0.0,
                "Neuron A should have 0 membrane potential after firing"
            );

            // Neuron B should have received ~1.0 PSP on burst 2 (from burst 1 firing of A)
            println!(
                "Neuron B membrane potential after burst 2: {} (expected: 1.0 from PSP)",
                mp_b
            );
            assert!(
                (mp_b - 1.0).abs() < 0.01,
                "Neuron B should have ~1.0 potential from PSP"
            );
        } // Drop the read lock here

        // Burst 3: Fire neuron A again (B receives another PSP from burst 2).
        npu.inject_sensory_with_potentials(&[(neuron_a, 1.5)]);
        let result3 = npu.process_burst().unwrap();

        // Neuron A should fire again
        assert!(result3.fired_neurons.contains(&neuron_a));

        // Neuron B should STILL NOT fire (mp_charge_accumulation=false should prevent carry-over)
        assert!(
            !result3.fired_neurons.contains(&neuron_b),
            "BUG: Neuron B fired on burst 3! Membrane potential not reset despite mp_charge_accumulation=false"
        );
    }

    #[test]
    fn test_mp_driven_psp_uses_firing_time_membrane_potential() {
        use feagi_npu_neural::{SynapseType, SynapticConductance, SynapticWeight};

        // REGRESSION TEST:
        // mp_driven_psp must use the firing-time MP captured in FireQueue, NOT neuron_storage MP
        // (neuron_storage MP is reset to 0 after firing).
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );

        // Avoid core areas (0..=2) so the test doesn't rely on implicit deterministic core neurons.
        let cortical_id = CoreCorticalType::Death.to_cortical_id();
        npu.register_cortical_area(3, cortical_id.as_base_64());
        npu.register_cortical_area(4, cortical_id.as_base_64());

        // Source neuron in cortical_area=3 (will be the mp_driven_psp source area).
        let neuron_src = npu
            .add_neuron(
                1.0,      // threshold
                f32::MAX, // threshold_limit (MAX = no limit, SIMD-friendly encoding)
                0.0,      // leak_coefficient
                0.0,      // resting_potential
                0,        // neuron_type
                0,        // refractory_period
                1.0,      // excitability
                0,        // consecutive_fire_limit
                0,        // snooze_period
                true,     // mp_charge_accumulation
                3,        // cortical_area
                0,
                0,
                0,
            )
            .unwrap();

        // Target neuron in cortical_area=4. Threshold=100 so it will fire only if it receives
        // a conductance roughly >= 100 from mp_driven_psp propagation.
        let neuron_dst = npu
            .add_neuron(
                100.0,    // threshold
                f32::MAX, // threshold_limit (MAX = no limit, SIMD-friendly encoding)
                0.0,      // leak_coefficient
                0.0,      // resting_potential
                0,        // neuron_type
                0,        // refractory_period
                1.0,      // excitability
                0,        // consecutive_fire_limit
                0,        // snooze_period
                true,     // mp_charge_accumulation
                4,        // cortical_area
                0,
                0,
                0,
            )
            .unwrap();

        // Connect src → dst. With mp_driven_psp enabled, the synapse conductance is ignored
        // and replaced by the source neuron's MP (converted to u8).
        npu.add_synapse(
            neuron_src,
            neuron_dst,
            SynapticWeight(1),
            SynapticConductance(1),
            SynapseType::Excitatory,
        )
        .unwrap();
        npu.rebuild_synapse_index();

        // Enable mp_driven_psp on the source cortical ID.
        let mut flags = ahash::AHashMap::new();
        flags.insert(cortical_id, true);
        npu.set_mp_driven_psp_flags(flags);

        // Burst 1: force src to fire with MP=128.0 (captured in FireQueue before reset).
        // IMPORTANT: Use staged sensory injection so it survives Phase-1 FCL clear.
        npu.inject_sensory_with_potentials(&[(neuron_src, 128.0)]);
        let result1 = npu.process_burst().unwrap();
        assert!(
            result1.fired_neurons.contains(&neuron_src),
            "Source neuron should fire with injected MP=128.0"
        );

        // Burst 2: no injection. Propagation should use the firing-time MP from burst 1 and
        // stimulate dst enough to cross its threshold.
        let result2 = npu.process_burst().unwrap();
        assert!(
            result2.synaptic_injections > 0,
            "Expected synaptic propagation on burst 2"
        );
        assert!(
            result2.fired_neurons.contains(&neuron_dst),
            "Target neuron should fire when mp_driven_psp uses src firing-time MP"
        );
    }

    #[test]
    fn test_empty_burst_no_power() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // Add only regular neurons (no power area)
        npu.add_neuron(
            1.0,
            f32::MAX,
            0.0,
            0.0,
            0,
            5,
            1.0,
            u16::MAX,
            0,
            true,
            2,
            0,
            0,
            0,
        )
        .unwrap();

        let result = npu.process_burst().unwrap();

        assert_eq!(result.power_injections, 0);
    }

    #[test]
    fn test_power_injection_zero_to_n_transition() {
        // Test the startup race condition: burst loop starts before genome load
        // This simulates what happens in production when burst engine starts before embryogenesis

        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        // Register core areas in order so neuron ID 1 is the deterministic power neuron.
        npu.register_cortical_area(0, CoreCorticalType::Death.to_cortical_id().as_base_64());
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.set_power_amount(0.5);

        // Burst 1: Core power neuron exists immediately (deterministic neuron ID 1)
        let result1 = npu.process_burst().unwrap();
        assert_eq!(
            result1.power_injections, 1,
            "Core power neuron should be injected deterministically"
        );

        // Simulate genome load: Add power neurons
        for i in 0..10 {
            npu.add_neuron(
                0.5,
                f32::MAX,
                0.0,
                0.0,
                0,
                5,
                1.0,
                u16::MAX,
                0,
                true,
                1,
                i,
                0,
                0,
            )
            .unwrap();
        }

        // Burst 2: Additional power neurons do not change the deterministic injection count.
        let result2 = npu.process_burst().unwrap();
        assert_eq!(
            result2.power_injections, 1,
            "Power injection should remain deterministic (neuron ID 1)"
        );

        // Burst 3: Should still inject power neurons consistently
        let result3 = npu.process_burst().unwrap();
        assert_eq!(
            result3.power_injections, 1,
            "Power injection should remain deterministic (neuron ID 1)"
        );
    }

    // ═══════════════════════════════════════════════════════════
    // Sensory Input Injection
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_inject_sensory_input() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let neuron = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, 0, 0, 0)
            .unwrap();

        npu.inject_sensory_with_potentials(&[(neuron, 0.5)]);

        // Sensory input is staged until next burst
        let _result = npu.process_burst().unwrap();
    }

    #[test]
    fn test_inject_multiple_sensory_inputs() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, 0, 0, 0)
            .unwrap();
        let n2 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, 1, 0, 0)
            .unwrap();
        let n3 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, 2, 0, 0)
            .unwrap();

        npu.inject_sensory_with_potentials(&[(n1, 0.5), (n2, 0.3), (n3, 0.8)]);

        let _result = npu.process_burst().unwrap();
    }

    #[test]
    fn test_sensory_accumulation_on_same_neuron() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        let neuron = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, 0, 0, 0)
            .unwrap();

        npu.inject_sensory_with_potentials(&[(neuron, 0.3)]);
        npu.inject_sensory_with_potentials(&[(neuron, 0.3)]);
        npu.inject_sensory_with_potentials(&[(neuron, 0.3)]);

        let _result = npu.process_burst().unwrap();
        // Should accumulate 0.9 potential
    }

    // ═══════════════════════════════════════════════════════════
    // Fire Ledger Tests
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_fire_ledger_recording() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.configure_fire_ledger_window(1, 10).unwrap();

        let _neuron = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();

        // Process burst
        npu.process_burst().unwrap();

        // Check fire ledger (dense, tracked)
        let window = npu.get_fire_ledger_dense_window_bitmaps(1, 1, 1).unwrap();
        assert_eq!(window.len(), 1);
    }

    #[test]
    fn test_fire_ledger_window_configuration() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        npu.configure_fire_ledger_window(1, 50).unwrap();

        let window_size = npu.get_fire_ledger_window_size(1).unwrap();
        assert_eq!(window_size, 50);
    }

    // ═══════════════════════════════════════════════════════════
    // FQ Sampler Tests
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_fq_sampler_rate_limiting() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        npu.add_neuron(
            1.0,
            f32::MAX,
            0.0,
            0.0,
            0,
            5,
            1.0,
            u16::MAX,
            0,
            true,
            1,
            0,
            0,
            0,
        )
        .unwrap();

        npu.set_visualization_subscribers(true);

        npu.process_burst().unwrap();

        // Should be able to sample
        let _sample = npu.sample_fire_queue();
        // Rate limiting may prevent sampling
    }

    #[test]
    fn test_fq_sampler_motor_subscribers() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        assert!(!npu.has_motor_subscribers());

        npu.set_motor_subscribers(true);
        assert!(npu.has_motor_subscribers());

        npu.set_motor_subscribers(false);
        assert!(!npu.has_motor_subscribers());
    }

    #[test]
    fn test_fq_sampler_viz_subscribers() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        assert!(!npu.has_visualization_subscribers());

        npu.set_visualization_subscribers(true);
        assert!(npu.has_visualization_subscribers());

        npu.set_visualization_subscribers(false);
        assert!(!npu.has_visualization_subscribers());
    }

    #[test]
    fn test_get_latest_fire_queue_sample() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        npu.add_neuron(
            1.0,
            f32::MAX,
            0.0,
            0.0,
            0,
            5,
            1.0,
            u16::MAX,
            0,
            true,
            1,
            0,
            0,
            0,
        )
        .unwrap();

        // Before any burst
        assert!(npu.get_latest_fire_queue_sample().is_none());

        npu.process_burst().unwrap();

        // After burst, may have sample
        let _sample = npu.get_latest_fire_queue_sample();
    }

    // ═══════════════════════════════════════════════════════════
    // Area Name Mapping
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_register_cortical_area_name() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        // Names are registered successfully
    }

    // ═══════════════════════════════════════════════════════════
    // Edge Cases & Error Handling
    // ═══════════════════════════════════════════════════════════

    #[test]
    fn test_add_synapse_to_nonexistent_neuron() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let n1 = npu
            .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 1, 0, 0, 0)
            .unwrap();
        let nonexistent = NeuronId(999);

        // Note: add_synapse does NOT validate neuron existence for performance
        // Synapses to nonexistent neurons are silently ignored during propagation
        let result = npu.add_synapse(
            n1,
            nonexistent,
            SynapticWeight(128),
            SynapticConductance(255),
            SynapseType::Excitatory,
        );

        assert!(result.is_ok()); // No validation for performance
        assert_eq!(npu.get_synapse_count(), 1);
    }

    #[test]
    fn test_burst_with_empty_npu() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());

        let result = npu.process_burst().unwrap();

        assert_eq!(result.burst, 1);
        assert_eq!(result.neuron_count, 0);
        assert_eq!(result.power_injections, 0);
    }

    #[test]
    fn test_large_sensory_batch() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                1000, 10000, 10,
            );
        npu.register_cortical_area(1, CoreCorticalType::Power.to_cortical_id().as_base_64());
        npu.register_cortical_area(2, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // Add 100 neurons
        let mut neurons = Vec::new();
        for i in 0..100 {
            let neuron = npu
                .add_neuron(1.0, 0.0, 0.0, 0.0, 0, 5, 1.0, 0, 0, true, 2, i, 0, 0)
                .unwrap();
            neurons.push((neuron, 0.5));
        }

        npu.inject_sensory_with_potentials(&neurons);

        let _result = npu.process_burst().unwrap();
    }

    #[test]
    fn test_update_threshold_limit_zero_means_unlimited() {
        let mut npu =
            <RustNPU<feagi_npu_runtime::StdRuntime, f32, crate::backend::CPUBackend>>::new_cpu_only(
                100, 1000, 10,
            );

        // Use a non-core, non-power area to avoid implicit core neuron creation (0..=2).
        npu.register_cortical_area(3, CoreCorticalType::Death.to_cortical_id().as_base_64());

        // Add a neuron with a bounded threshold limit first (so the update actually changes it).
        let neuron_id = npu
            .add_neuron(
                1.0,
                10.0,
                0.0,
                0.0,
                0,
                0,
                1.0,
                u16::MAX,
                0,
                true,
                3,
                0,
                0,
                0,
            )
            .unwrap();

        // Live-update: 0.0 must be encoded as "no upper bound".
        let updated = npu.update_cortical_area_threshold_limit(3, 0.0);
        assert_eq!(updated, 1);

        // Verify internal encoding.
        let neuron_storage = npu.neuron_storage.read().unwrap();
        let limit = neuron_storage.threshold_limits()[neuron_id.0 as usize].to_f32();
        assert_eq!(limit, f32::MAX);
    }
}

// ═══════════════════════════════════════════════════════════
// Type Aliases for Convenience
// ═══════════════════════════════════════════════════════════
//
// Type aliases removed - use RustNPU::new_std_cpu() constructor instead
// The full generic signature is complex:
// RustNPU<R: Runtime, T: NeuralValue, B: ComputeBackend<T, R::NeuronStorage<T>, R::SynapseStorage>>
//
// Example usage:
//   let npu = RustNPU::new_std_cpu(capacity, syn_capacity, burst_size);  // Returns RustNPU with all generics
//
// INT8 NPU - use RustNPU::new_std_cpu_int8() if we add that constructor
// Future: pub type RustNPUF16 = RustNPU<f16>;

// ═══════════════════════════════════════════════════════════
// Runtime Type Dispatch
// ═══════════════════════════════════════════════════════════

// Dynamic NPU that can hold either F32 or INT8 precision at runtime
//
// This enum enables runtime dispatch based on genome's quantization_precision.
// The system parses the genome, determines the precision, and creates the
// appropriate variant. All operations are then dispatched via pattern matching.
//
// # Architecture
// - **Compile-time generics**: Both RustNPU<f32> and RustNPU<INT8Value> are
//   monomorphized at compile time for maximum performance
// - **Zero-cost abstraction**: No vtables or dynamic dispatch overhead
// - **Type safety**: Impossible to mix precisions accidentally
//
// # Example
// ```rust,ignore
// let precision = parse_genome_precision(&genome)?;
// let npu = match precision {
//     Precision::FP32 => DynamicNPU::F32(RustNPU::<f32>::new(...)?),
//     Precision::INT8 => DynamicNPU::INT8(RustNPU::<INT8Value>::new(...)?),
//     _ => return Err("Unsupported precision"),
// };
// ```
// TODO: DynamicNPU and dispatch macros removed
// With new generic signature, use concrete type aliases instead:
//   type F32NPU = RustNPU<StdRuntime, f32, CPUBackend>;
//   type INT8NPU = RustNPU<StdRuntime, INT8Value, CPUBackend>;

/* COMMENTED OUT - DynamicNPU removed for generic architecture
macro_rules! dispatch {
//     // Immutable method
//     ($self:expr, $method:ident($($arg:expr),*)) => {
//         match $self {
//             DynamicNPU::F32(npu) => npu.$method($($arg),*),
//             DynamicNPU::INT8(npu) => npu.$method($($arg),*),
//         }
//     };
// }
//
// // Macros and impl removed with DynamicNPU
// macro_rules! dispatch_mut {
//     ($self:expr, $method:ident($($arg:expr),*)) => {
//         match $self {
//             DynamicNPU::F32(npu) => npu.$method($($arg),*),
//             DynamicNPU::INT8(npu) => npu.$method($($arg),*),
//         }
//     };
// }
//
// impl DynamicNPU {
//     /// Get the precision type as a string (for logging/debugging)
//     pub fn precision_name(&self) -> &'static str {
//         match self {
//             DynamicNPU::F32(_) => "FP32",
//             DynamicNPU::INT8(_) => "INT8",
//         }
//     }
//
//     /// Get direct access to F32 NPU (panics if not F32)
//     pub fn as_f32(&self) -> &RustNPU<f32> {
//         match self {
//             DynamicNPU::F32(npu) => npu,
//             _ => panic!("NPU is not F32 variant"),
//         }
//     }
//
//     /// Get direct mutable access to F32 NPU (panics if not F32)
//     pub fn as_f32_mut(&mut self) -> &mut RustNPU<f32> {
//         match self {
//             DynamicNPU::F32(npu) => npu,
//             _ => panic!("NPU is not F32 variant"),
//         }
//     }
//
//     /// Get direct access to INT8 NPU (panics if not INT8)
//     pub fn as_int8(&self) -> &RustNPU<INT8Value> {
//         match self {
//             DynamicNPU::INT8(npu) => npu,
//             _ => panic!("NPU is not INT8 variant"),
//         }
//     }
//
//     /// Get direct mutable access to INT8 NPU (panics if not INT8)
//     pub fn as_int8_mut(&mut self) -> &mut RustNPU<INT8Value> {
//         match self {
//             DynamicNPU::INT8(npu) => npu,
//             _ => panic!("NPU is not INT8 variant"),
//         }
//     }
//
//     // ========================================
//     // Core NPU Operations (Dispatched)
//     // ========================================
//
//     /// Get current neuron count
//     pub fn neuron_count(&self) -> usize {
//         match self {
//             DynamicNPU::F32(npu) => npu.neuron_storage.read().unwrap().count,
//             DynamicNPU::INT8(npu) => npu.neuron_storage.read().unwrap().count,
//         }
//     }
//
//     /// Get neuron capacity (MAXIMUM allocated capacity)
//     pub fn get_neuron_capacity(&self) -> usize {
//         match self {
//             DynamicNPU::F32(npu) => npu.neuron_storage.read().unwrap().capacity,
//             DynamicNPU::INT8(npu) => npu.neuron_storage.read().unwrap().capacity,
//         }
//     }
//
//     /// Get current synapse count
//     pub fn synapse_count(&self) -> usize {
//         match self {
//             DynamicNPU::F32(npu) => npu.synapse_storage.read().unwrap().count,
//             DynamicNPU::INT8(npu) => npu.synapse_storage.read().unwrap().count,
//         }
//     }
//
//     /// Get synapse capacity (MAXIMUM allocated capacity)
//     pub fn get_synapse_capacity(&self) -> usize {
//         match self {
//             DynamicNPU::F32(npu) => npu.synapse_storage.read().unwrap().capacity,
//             DynamicNPU::INT8(npu) => npu.synapse_storage.read().unwrap().capacity,
//         }
//     }
//
//     /// Get burst count
//     pub fn get_burst_count(&self) -> u64 {
//         dispatch!(self, get_burst_count())
//     }
//
//     /// Set power amount
//     pub fn set_power_amount(&self, amount: f32) {
//         dispatch!(self, set_power_amount(amount))
//     }
//
//     /// Get power amount
//     pub fn get_power_amount(&self) -> f32 {
//         dispatch!(self, get_power_amount())
//     }
//
//     /// Process burst
//     pub fn process_burst(&self) -> Result<BurstResult> {
//         dispatch!(self, process_burst())
//     }
//
//     /// Inject sensory batch
//     pub fn inject_sensory_batch(&mut self, neuron_ids: &[NeuronId], potential: f32) {
//         dispatch!(self, inject_sensory_batch(neuron_ids, potential))
//     }
//
//     /// Inject sensory with individual potentials
//     pub fn inject_sensory_with_potentials(&mut self, neurons: &[(NeuronId, f32)]) {
//         dispatch!(self, inject_sensory_with_potentials(neurons))
//     }
//
//     /// Register cortical area
//     pub fn register_cortical_area(&mut self, area_id: u32, cortical_name: String) {
//         dispatch!(self, register_cortical_area(area_id, cortical_name))
//     }
//
//     /// Get cortical area name
//     pub fn get_cortical_area_name(&self, area_id: u32) -> Option<String> {
//         dispatch!(self, get_cortical_area_name(area_id))
//     }
//
//     /// Check if genome is loaded
//     pub fn is_genome_loaded(&self) -> bool {
//         dispatch!(self, is_genome_loaded())
//     }
//
//     /// Get all cortical areas
//     pub fn get_all_cortical_areas(&self) -> Vec<(u32, String)> {
//         dispatch!(self, get_all_cortical_areas())
//     }
//
//     /// Get registered cortical area count
//     pub fn get_registered_cortical_area_count(&self) -> usize {
//         dispatch!(self, get_registered_cortical_area_count())
//     }
//
//     /// Rebuild indexes (synapses, etc.)
//     pub fn rebuild_indexes(&mut self) {
//         dispatch!(self, rebuild_indexes())
//     }
//
//     /// Get FCL clone
//     pub fn get_fcl_clone(&self) -> FireCandidateList {
//         dispatch!(self, get_fcl_clone())
//     }
//
//     /// Get last FCL snapshot
//     pub fn get_last_fcl_snapshot(&self) -> Vec<(NeuronId, f32)> {
//         dispatch!(self, get_last_fcl_snapshot())
//     }
//
//     /// Sample fire queue (for visualization)
//     pub fn sample_fire_queue(&mut self) -> Option<ahash::AHashMap<u32, (Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<f32>)>> {
//         dispatch_mut!(self, sample_fire_queue())
//     }
//
//     /// Get batch neuron IDs from coordinates (for sensory injection)
//     pub fn batch_get_neuron_ids_from_coordinates(
//         &self,
//         area: u32,
//         coords: &[(u32, u32, u32)],
//     ) -> Vec<NeuronId> {
//         dispatch!(self, batch_get_neuron_ids_from_coordinates(area, coords))
//     }
//
//     /// Get all fire ledger configs
//     pub fn get_all_fire_ledger_configs(&self) -> Vec<(u32, usize)> {
//         dispatch!(self, get_all_fire_ledger_configs())
//     }
//
//     /// Configure fire ledger window
//     pub fn configure_fire_ledger_window(&mut self, cortical_idx: u32, window_size: usize) {
//         dispatch_mut!(self, configure_fire_ledger_window(cortical_idx, window_size))
//     }
//
//     /// Force sample fire queue (bypasses sampling logic)
//     pub fn force_sample_fire_queue(&mut self) -> Option<ahash::AHashMap<u32, (Vec<u32>, Vec<u32>, Vec<u32>, Vec<u32>, Vec<f32>)>> {
//         dispatch_mut!(self, force_sample_fire_queue())
//     }
//
//     /// Update cortical area threshold (returns count of neurons updated)
//     pub fn update_cortical_area_threshold(&mut self, cortical_idx: u32, threshold: f32) -> usize {
//         dispatch_mut!(self, update_cortical_area_threshold(cortical_idx, threshold))
//     }
//
//     /// Update cortical area refractory period (returns count of neurons updated)
//     pub fn update_cortical_area_refractory_period(&mut self, cortical_idx: u32, period: u16) -> usize {
//         dispatch_mut!(self, update_cortical_area_refractory_period(cortical_idx, period))
//     }
//
//     /// Update cortical area leak coefficient (returns count of neurons updated)
//     pub fn update_cortical_area_leak(&mut self, cortical_idx: u32, leak: f32) -> usize {
//         dispatch_mut!(self, update_cortical_area_leak(cortical_idx, leak))
//     }
//
//     /// Update cortical area consecutive fire limit (returns count of neurons updated)
//     pub fn update_cortical_area_consecutive_fire_limit(&mut self, cortical_idx: u32, limit: u16) -> usize {
//         dispatch_mut!(self, update_cortical_area_consecutive_fire_limit(cortical_idx, limit))
//     }
//
//     /// Update cortical area snooze period (returns count of neurons updated)
//     pub fn update_cortical_area_snooze_period(&mut self, cortical_idx: u32, snooze: u16) -> usize {
//         dispatch_mut!(self, update_cortical_area_snooze_period(cortical_idx, snooze))
//     }
//
//     /// Update cortical area excitability (returns count of neurons updated)
//     pub fn update_cortical_area_excitability(&mut self, cortical_idx: u32, excitability: f32) -> usize {
//         dispatch_mut!(self, update_cortical_area_excitability(cortical_idx, excitability))
//     }
//
//     /// Update cortical area MP charge accumulation (returns count of neurons updated)
//     pub fn update_cortical_area_mp_charge_accumulation(&mut self, cortical_idx: u32, accumulation: bool) -> usize {
//         dispatch_mut!(self, update_cortical_area_mp_charge_accumulation(cortical_idx, accumulation))
//     }
//
//     /// Rebuild synapse index
//     pub fn rebuild_synapse_index(&mut self) {
//         dispatch_mut!(self, rebuild_synapse_index())
//     }
//
//     /// Create cortical area neurons (3D grid with defaults)
//     pub fn create_cortical_area_neurons(
//         &mut self,
//         cortical_idx: u32,
//         width: u32,
//         height: u32,
//         depth: u32,
//         neurons_per_voxel: u32,
//         default_threshold: f32,
//         default_leak_coefficient: f32,
//         default_resting_potential: f32,
//         default_neuron_type: i32,
//         default_refractory_period: u16,
//         default_excitability: f32,
//         default_consecutive_fire_limit: u16,
//         default_snooze_period: u16,
//         default_mp_charge_accumulation: bool,
//     ) -> Result<u32> {
//         dispatch_mut!(self, create_cortical_area_neurons(
//             cortical_idx, width, height, depth, neurons_per_voxel,
//             default_threshold, default_leak_coefficient, default_resting_potential,
//             default_neuron_type, default_refractory_period, default_excitability,
//             default_consecutive_fire_limit, default_snooze_period, default_mp_charge_accumulation
//         ))
//     }
//
//     /// Add single neuron (accepts f32, converts internally)
//     pub fn add_neuron(
//         &mut self,
//         threshold: f32,
//         leak_coefficient: f32,
//         resting_potential: f32,
//         neuron_type: i32,
//         refractory_period: u16,
//         excitability: f32,
//         consecutive_fire_limit: u16,
//         snooze_period: u16,
//         mp_charge_accumulation: bool,
//         cortical_area: u32,
//         x: u32,
//         y: u32,
//         z: u32,
//     ) -> Result<NeuronId> {
//         match self {
//             DynamicNPU::F32(npu) => npu.add_neuron(//                 <f32 as feagi_types::NeuralValue>::from_f32(threshold),
//                 leak_coefficient,
//                 <f32 as feagi_types::NeuralValue>::from_f32(resting_potential),
//                 neuron_type,
//                 refractory_period,
//                 excitability,
//                 consecutive_fire_limit,
//                 snooze_period,
//                 mp_charge_accumulation,
//                 cortical_area,
//                 x, y, z,
//             ),
//             DynamicNPU::INT8(npu) => npu.add_neuron(//                 <feagi_types::INT8Value as feagi_types::NeuralValue>::from_f32(threshold),
//                 leak_coefficient,
//                 <feagi_types::INT8Value as feagi_types::NeuralValue>::from_f32(resting_potential),
//                 neuron_type,
//                 refractory_period,
//                 excitability,
//                 consecutive_fire_limit,
//                 snooze_period,
//                 mp_charge_accumulation,
//                 cortical_area,
//                 x, y, z,
//             ),
//         }
//     }
//
//     /// Delete neuron
//     pub fn delete_neuron(&mut self, neuron_id: u32) -> bool {
//         dispatch_mut!(self, delete_neuron(neuron_id))
//     }
//
//     /// Check if neuron is valid
//     pub fn is_neuron_valid(&self, neuron_id: u32) -> bool {
//         dispatch!(self, is_neuron_valid(neuron_id))
//     }
//
//     /// Get neuron count
//     pub fn get_neuron_count(&self) -> usize {
//         self.neuron_count()
//     }
//
//     /// Get synapse count
//     pub fn get_synapse_count(&self) -> usize {
//         self.synapse_count()
//     }
//
//     /// Get neuron coordinates (returns cortical_area, x, y, z)
//     pub fn get_neuron_coordinates(&self, neuron_id: u32) -> (u32, u32, u32) {
//         dispatch!(self, get_neuron_coordinates(neuron_id))
//     }
//
//     /// Add neurons batch (accepts f32, converts internally based on precision)
//     pub fn add_neurons_batch(
//         &mut self,
//         thresholds: Vec<f32>,
//         leak_coefficients: Vec<f32>,
//         resting_potentials: Vec<f32>,
//         neuron_types: Vec<i32>,
//         refractory_periods: Vec<u16>,
//         excitabilities: Vec<f32>,
//         consecutive_fire_limits: Vec<u16>,
//         snooze_periods: Vec<u16>,
//         mp_charge_accumulations: Vec<bool>,
//         cortical_areas: Vec<u32>,
//         x_coords: Vec<u32>,
//         y_coords: Vec<u32>,
//         z_coords: Vec<u32>,
//     ) -> (u32, Vec<usize>) {
//         match self {
//             DynamicNPU::F32(npu) => {
//                 let thresholds_t: Vec<f32> = thresholds;
//                 let resting_t: Vec<f32> = resting_potentials;
//                 npu.add_neurons_batch(thresholds_t, leak_coefficients, resting_t, neuron_types,
//                     refractory_periods, excitabilities, consecutive_fire_limits, snooze_periods,
//                     mp_charge_accumulations, cortical_areas, x_coords, y_coords, z_coords)
//             }
//             DynamicNPU::INT8(npu) => {
//                 let thresholds_t: Vec<feagi_types::INT8Value> = thresholds.into_iter()
//                     .map(feagi_types::NeuralValue::from_f32).collect();
//                 let resting_t: Vec<feagi_types::INT8Value> = resting_potentials.into_iter()
//                     .map(feagi_types::NeuralValue::from_f32).collect();
//                 npu.add_neurons_batch(thresholds_t, leak_coefficients, resting_t, neuron_types,
//                     refractory_periods, excitabilities, consecutive_fire_limits, snooze_periods,
//                     mp_charge_accumulations, cortical_areas, x_coords, y_coords, z_coords)
//             }
//         }
//     }
//
//     /// Add synapse (returns Result<usize> - synapse count after add)
//     pub fn add_synapse(
//         &mut self,
//         source: NeuronId,
//         target: NeuronId,
//         weight: feagi_types::SynapticWeight,
//         postsynaptic_potential: feagi_types::SynapticConductance,
//         synapse_type: feagi_types::SynapseType,
//     ) -> Result<usize> {
//         dispatch_mut!(self, add_synapse(source, target, weight, postsynaptic_potential, synapse_type))
//     }
//
//     /// Add synapses batch (returns count, errors)
//     pub fn add_synapses_batch(
//         &mut self,
//         source_neurons: Vec<NeuronId>,
//         target_neurons: Vec<NeuronId>,
//         weights: Vec<feagi_types::SynapticWeight>,
//         postsynaptic_potentials: Vec<feagi_types::SynapticConductance>,
//         synapse_types: Vec<feagi_types::SynapseType>,
//     ) -> (usize, Vec<usize>) {
//         dispatch_mut!(self, add_synapses_batch(source_neurons, target_neurons, weights, postsynaptic_potentials, synapse_types))
//     }
//
//     /// Set neuron mapping
//     pub fn set_neuron_mapping(&mut self, mapping: ahash::AHashMap<NeuronId, feagi_types::CorticalID>) {
//         dispatch_mut!(self, set_neuron_mapping(mapping))
//     }
//
//     /// Get neuron ID at coordinate
//     pub fn get_neuron_id_at_coordinate(&self, cortical_area: u32, x: u32, y: u32, z: u32) -> Option<u32> {
//         dispatch!(self, get_neuron_id_at_coordinate(cortical_area, x, y, z))
//     }
//
//     /// Update neuron threshold (accepts f32, converts internally)
//     pub fn update_neuron_threshold(&mut self, neuron_id: u32, threshold: f32) -> bool {
//         match self {
//             DynamicNPU::F32(npu) => npu.update_neuron_threshold(neuron_id, threshold),
//             DynamicNPU::INT8(npu) => {
//                 let threshold_int8 = <feagi_types::INT8Value as feagi_types::NeuralValue>::from_f32(threshold);
//                 npu.update_neuron_threshold(neuron_id, threshold_int8)
//             },
//         }
//     }
//
//     /// Update neuron resting potential (accepts f32, converts internally)
//     pub fn update_neuron_resting_potential(&mut self, neuron_id: u32, resting_potential: f32) -> bool {
//         match self {
//             DynamicNPU::F32(npu) => npu.update_neuron_resting_potential(neuron_id, resting_potential),
//             DynamicNPU::INT8(npu) => {
//                 let resting_int8 = <feagi_types::INT8Value as feagi_types::NeuralValue>::from_f32(resting_potential);
//                 npu.update_neuron_resting_potential(neuron_id, resting_int8)
//             },
//         }
//     }
//
//     /// Update neuron leak
//     pub fn update_neuron_leak(&mut self, neuron_id: u32, leak: f32) -> bool {
//         dispatch_mut!(self, update_neuron_leak(neuron_id, leak))
//     }
//
//     /// Update neuron excitability
//     pub fn update_neuron_excitability(&mut self, neuron_id: u32, excitability: f32) -> bool {
//         dispatch_mut!(self, update_neuron_excitability(neuron_id, excitability))
//     }
//
//     /// Remove synapse
//     pub fn remove_synapse(&mut self, source: NeuronId, target: NeuronId) -> bool {
//         dispatch_mut!(self, remove_synapse(source, target))
//     }
//
//     /// Update synapse weight
//     pub fn update_synapse_weight(&mut self, source: NeuronId, target: NeuronId, weight: feagi_types::SynapticWeight) -> bool {
//         dispatch_mut!(self, update_synapse_weight(source, target, weight))
//     }
//
//     /// Get incoming synapses
//     pub fn get_incoming_synapses(&self, target_neuron_id: u32) -> Vec<(u32, u8, u8, u8)> {
//         dispatch!(self, get_incoming_synapses(target_neuron_id))
//     }
//
//     /// Get outgoing synapses
//     pub fn get_outgoing_synapses(&self, source_neuron_id: u32) -> Vec<(u32, u8, u8, u8)> {
//         dispatch!(self, get_outgoing_synapses(source_neuron_id))
//     }
//
//     /// Get neurons in cortical area
//     pub fn get_neurons_in_cortical_area(&self, cortical_area_id: u32) -> Vec<u32> {
//         dispatch!(self, get_neurons_in_cortical_area(cortical_area_id))
//     }
//
//     /// Get neuron cortical area
//     pub fn get_neuron_cortical_area(&self, neuron_id: u32) -> u32 {
//         dispatch!(self, get_neuron_cortical_area(neuron_id))
//     }
//
//     /// Get neuron property by index
//     pub fn get_neuron_property_by_index(&self, idx: usize, property: &str) -> Option<f32> {
//         dispatch!(self, get_neuron_property_by_index(idx, property))
//     }
//
//     /// Get neuron property u16 by index
//     pub fn get_neuron_property_u16_by_index(&self, idx: usize, property: &str) -> Option<u16> {
//         dispatch!(self, get_neuron_property_u16_by_index(idx, property))
//     }
//
//     /// Get neuron state (for diagnostics)
//     pub fn get_neuron_state(&self, neuron_id: NeuronId) -> Option<(u16, u16, u16, f32, f32, u16)> {
//         dispatch!(self, get_neuron_state(neuron_id))
//     }
//
//     /// Inject sensory XYZP data by CorticalID (for PNS - hot path optimized)
//     pub fn inject_sensory_xyzp_by_id(
//         &mut self,
//         cortical_id: &CorticalID,
//         xyzp_data: &[(u32, u32, u32, f32)],
//     ) -> usize {
//         dispatch_mut!(self, inject_sensory_xyzp_by_id(cortical_id, xyzp_data))
//     }
//
//     /// Inject sensory XYZP data by name (for backward compatibility)
//     pub fn inject_sensory_xyzp(
//         &mut self,
//         cortical_area: &str,
//         xyzp_data: &[(u32, u32, u32, f32)],
//     ) -> usize {
//         dispatch_mut!(self, inject_sensory_xyzp(cortical_area, xyzp_data))
//     }
// }
*/
