use crate::{EvoError, EvoResult};
use serde_json::Value;
use std::collections::HashMap;
use super::parser::string_to_cortical_id;
fn is_legacy_io_shorthand(id: &str) -> bool {
id.len() == 6 && (id.starts_with('i') || id.starts_with('o'))
}
#[derive(Debug, Clone)]
pub struct MigrationResult {
pub genome: Value,
pub cortical_ids_migrated: usize,
pub id_mapping: HashMap<String, String>,
pub warnings: Vec<String>,
}
pub fn migrate_genome(genome_json: &Value) -> EvoResult<MigrationResult> {
let mut result = MigrationResult {
genome: genome_json.clone(),
cortical_ids_migrated: 0,
id_mapping: HashMap::new(),
warnings: Vec::new(),
};
build_id_mapping(genome_json, &mut result)?;
migrate_blueprint(&mut result)?;
migrate_brain_regions(&mut result)?;
migrate_cortical_mappings(&mut result)?;
migrate_morphology_ids(&mut result)?;
Ok(result)
}
fn migrate_morphology_ids(result: &mut MigrationResult) -> EvoResult<()> {
let replacements: HashMap<&str, &str> = HashMap::from([
("memory", "episodic_memory"),
("bi_directional_stdp", "associative_memory"),
]);
let mut replaced_count: usize = 0;
let genome = result
.genome
.as_object_mut()
.ok_or_else(|| EvoError::InvalidGenome("Genome is not an object".to_string()))?;
for section_key in ["morphologies", "neuron_morphologies"] {
if let Some(Value::Object(section)) = genome.get_mut(section_key) {
for (old_id, new_id) in &replacements {
if let Some(value) = section.remove(*old_id) {
section.insert((*new_id).to_string(), value);
replaced_count += 1;
}
}
}
}
fn update_morphology_id_fields(
value: &mut Value,
replacements: &HashMap<&str, &str>,
replaced_count: &mut usize,
) {
match value {
Value::Object(obj) => {
if let Some(morphology_id) = obj.get_mut("morphology_id") {
if let Some(old_id) = morphology_id.as_str() {
if let Some(new_id) = replacements.get(old_id) {
*morphology_id = Value::String((*new_id).to_string());
*replaced_count += 1;
}
}
}
for child in obj.values_mut() {
update_morphology_id_fields(child, replacements, replaced_count);
}
}
Value::Array(arr) => {
for child in arr.iter_mut() {
update_morphology_id_fields(child, replacements, replaced_count);
}
}
_ => {}
}
}
update_morphology_id_fields(&mut result.genome, &replacements, &mut replaced_count);
if replaced_count > 0 {
result.warnings.push(format!(
"Migrated {} legacy morphology ID reference(s) to episodic/associative naming",
replaced_count
));
}
Ok(())
}
fn build_id_mapping(genome_json: &Value, result: &mut MigrationResult) -> EvoResult<()> {
let blueprint = genome_json
.get("blueprint")
.and_then(|v| v.as_object())
.ok_or_else(|| EvoError::InvalidGenome("Missing or invalid blueprint".to_string()))?;
let is_flat = blueprint.keys().any(|k| k.starts_with("_____10c-"));
use std::collections::{BTreeSet, HashSet};
let mut seen_ids: HashSet<String> = HashSet::new();
let mut cortical_ids: BTreeSet<String> = BTreeSet::new();
if is_flat {
for flat_key in blueprint.keys() {
if let Some(cortical_id) = extract_cortical_id_from_flat_key(flat_key) {
if seen_ids.insert(cortical_id.clone()) {
cortical_ids.insert(cortical_id);
}
}
}
} else {
for old_id in blueprint.keys() {
cortical_ids.insert(old_id.clone());
}
}
if let Some(brain_regions) = genome_json.get("brain_regions").and_then(|v| v.as_object()) {
for region in brain_regions.values() {
if let Some(region_obj) = region.as_object() {
for arr_key in [
"areas",
"cortical_areas",
"inputs",
"outputs",
"designated_inputs",
"designated_outputs",
] {
if let Some(Value::Array(arr)) = region_obj.get(arr_key) {
for item in arr {
if let Some(id) = item.as_str() {
cortical_ids.insert(id.to_string());
}
}
}
}
if let Some(Value::Object(props)) = region_obj.get("properties") {
for arr_key in [
"inputs",
"outputs",
"designated_inputs",
"designated_outputs",
] {
if let Some(Value::Array(arr)) = props.get(arr_key) {
for item in arr {
if let Some(id) = item.as_str() {
cortical_ids.insert(id.to_string());
}
}
}
}
}
}
}
}
for area_data in blueprint.values() {
if let Some(area_obj) = area_data.as_object() {
if let Some(Value::Object(dstmap)) = area_obj.get("cortical_mapping_dst") {
for dst_id in dstmap.keys() {
cortical_ids.insert(dst_id.clone());
}
}
}
}
let mut used_base64: HashSet<String> = HashSet::new();
for id in cortical_ids.iter() {
if feagi_structures::genomic::cortical_area::CorticalID::try_from_base_64(id).is_ok() {
used_base64.insert(id.clone());
}
}
let mut legacy_io_shorthands: Vec<String> = Vec::new();
for id in cortical_ids.iter() {
{
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::FrameChangeHandling;
use feagi_structures::genomic::cortical_area::CorticalID;
use feagi_structures::genomic::SensoryCorticalUnit;
let name_opt: Option<&str> = if is_flat {
let name_key = format!("_____10c-{}-cx-__name-t", id);
blueprint.get(&name_key).and_then(|v| v.as_str())
} else {
blueprint
.get(id)
.and_then(|v| v.as_object())
.and_then(|o| o.get("name"))
.and_then(|v| v.as_str())
};
if let (Ok(cid), Some(name)) = (CorticalID::try_from_base_64(id), name_opt) {
if cid.extract_subtype().as_deref() == Some("mis") {
let tile_idx: Option<usize> = match name {
"vision_LL" => Some(0),
"vision_LM" => Some(1),
"vision_LR" => Some(2),
"vision_ML" => Some(3),
"vision_C" => Some(4),
"vision_MR" => Some(5),
"vision_TL" => Some(6),
"vision_TM" => Some(7),
"vision_TR" => Some(8),
_ => None,
};
if let Some(idx) = tile_idx {
let group_index: CorticalUnitIndex = 0.into();
let segmented =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
FrameChangeHandling::Absolute,
group_index,
);
if idx < segmented.len() {
let new_id = segmented[idx].as_base_64();
if !used_base64.contains(&new_id) {
used_base64.insert(new_id.clone());
result.id_mapping.insert(id.clone(), new_id.clone());
result.cortical_ids_migrated += 1;
result.warnings.push(format!(
"Legacy base64 vision cortical ID '{}' (subtype=mis, name='{}') migrated to SegmentedVision(tile_index={}, group=0) → '{}'",
id, name, idx, new_id
));
continue;
}
result.warnings.push(format!(
"Legacy base64 vision cortical ID '{}' (subtype=mis, name='{}') could not be migrated to SegmentedVision(tile_index={}, group=0) because target ID '{}' already exists in the genome",
id, name, idx, new_id
));
}
}
if name == "vision_ipu" {
for group_u16 in 0u16..=u8::MAX as u16 {
let group_u8 = group_u16 as u8;
let group_index: CorticalUnitIndex = group_u8.into();
let new_id = SensoryCorticalUnit::get_cortical_ids_array_for_misc_data_with_parameters(
FrameChangeHandling::Absolute,
group_index,
)[0]
.as_base_64();
if used_base64.contains(&new_id) {
continue;
}
used_base64.insert(new_id.clone());
result.id_mapping.insert(id.clone(), new_id.clone());
result.cortical_ids_migrated += 1;
result.warnings.push(format!(
"Legacy base64 vision cortical ID '{}' (subtype=mis, name='{}') migrated to MiscData IPU(group={}) → '{}'",
id, name, group_u8, new_id
));
break;
}
if !result.id_mapping.contains_key(id) {
return Err(EvoError::InvalidGenome(
"Unable to allocate unique MiscData IPU group ID for legacy base64 vision cortical IDs".to_string(),
));
}
continue;
}
}
}
}
if !needs_migration(id) {
if !result.id_mapping.contains_key(id) {
if let Ok(cid) = string_to_cortical_id(id) {
let new_id = cid.as_base_64();
if !used_base64.contains(&new_id) {
used_base64.insert(new_id.clone());
result.id_mapping.insert(id.clone(), new_id);
result.cortical_ids_migrated += 1;
}
}
}
continue;
}
if let Some(new_id) = map_old_id_to_new(id) {
tracing::debug!("🔄 [MIGRATION] '{}' → '{}'", id, new_id);
used_base64.insert(new_id.clone());
result.id_mapping.insert(id.clone(), new_id);
result.cortical_ids_migrated += 1;
continue;
}
if is_legacy_io_shorthand(id) {
legacy_io_shorthands.push(id.clone());
continue;
}
if let Ok(cid) = string_to_cortical_id(id) {
let new_id = cid.as_base_64();
if !used_base64.contains(&new_id) {
used_base64.insert(new_id.clone());
result.id_mapping.insert(id.clone(), new_id);
result.cortical_ids_migrated += 1;
continue;
}
}
result.warnings.push(format!(
"Cannot auto-migrate cortical ID: '{}' - no mapping defined",
id
));
}
if !legacy_io_shorthands.is_empty() {
apply_legacy_io_shorthand_migration(&legacy_io_shorthands, &mut used_base64, result)?;
}
Ok(())
}
fn extract_legacy_io_subtype(id: &str) -> Option<&str> {
if id.len() == 6 {
id.get(3..6)
} else {
None
}
}
fn legacy_io_to_custom_base64(old_id: &str) -> EvoResult<String> {
use feagi_structures::genomic::cortical_area::CorticalID;
let custom_str = if let (Some(first), Some(rest)) = (old_id.chars().next(), old_id.get(1..)) {
if first == 'i' || first == 'o' {
format!("c{}{}", first, rest)
} else {
old_id.to_string()
}
} else {
old_id.to_string()
};
let cid = CorticalID::try_from_legacy_ascii(&custom_str).map_err(|e| {
EvoError::InvalidGenome(format!(
"Failed to convert legacy IO '{}' to custom: {}",
old_id, e
))
})?;
Ok(cid.as_base_64())
}
fn apply_legacy_io_shorthand_migration(
legacy_ids: &[String],
used_base64: &mut std::collections::HashSet<String>,
result: &mut MigrationResult,
) -> EvoResult<()> {
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::FrameChangeHandling;
use feagi_structures::genomic::{MotorCorticalUnit, SensoryCorticalUnit};
let frame_handling = FrameChangeHandling::Absolute;
let mut exceptions: Vec<String> = Vec::new();
for old_id in legacy_ids.iter() {
if old_id.starts_with("iv00") && old_id.len() == 6 {
let suffix = &old_id[4..6];
let tile_idx: Option<usize> = match suffix {
"_C" => Some(4),
"BL" => Some(0),
"BM" => Some(1),
"BR" => Some(2),
"ML" => Some(3),
"MR" => Some(5),
"TL" => Some(6),
"TM" => Some(7),
"TR" => Some(8),
_ => None,
};
if let Some(idx) = tile_idx {
let group_index: CorticalUnitIndex = 0.into();
let segmented =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
);
if idx < segmented.len() {
let new_id = segmented[idx].as_base_64();
used_base64.insert(new_id.clone());
result.id_mapping.insert(old_id.clone(), new_id.clone());
result.cortical_ids_migrated += 1;
exceptions.push(format!(
"Legacy segmented-vision shorthand '{}' mapped to SegmentedVision(tile_index={}) (group=0) → '{}'",
old_id, idx, new_id
));
continue;
}
}
}
let is_input = old_id.starts_with('i');
let supported_match = extract_legacy_io_subtype(old_id).and_then(|subtype| {
if is_input {
SensoryCorticalUnit::try_from_legacy_subtype(subtype)
} else {
MotorCorticalUnit::try_from_legacy_subtype(subtype)
}
.map(|cid| cid.as_base_64())
});
if let Some(ref new_id) = supported_match {
if !used_base64.contains(new_id) {
used_base64.insert(new_id.clone());
result.id_mapping.insert(old_id.clone(), new_id.clone());
result.cortical_ids_migrated += 1;
exceptions.push(format!(
"Legacy {} shorthand '{}' matched supported IO type → '{}'",
if is_input { "IPU" } else { "OPU" },
old_id,
new_id
));
continue;
}
}
match legacy_io_to_custom_base64(old_id) {
Ok(new_id) => {
if !used_base64.contains(&new_id) {
used_base64.insert(new_id.clone());
result.id_mapping.insert(old_id.clone(), new_id.clone());
result.cortical_ids_migrated += 1;
exceptions.push(format!(
"Legacy {} shorthand '{}' not in supported IO types; mapped to custom → '{}'",
if is_input { "IPU" } else { "OPU" },
old_id,
new_id
));
} else {
exceptions.push(format!(
"Legacy {} shorthand '{}' not in supported IO types; custom ID collision, skipped",
if is_input { "IPU" } else { "OPU" },
old_id
));
}
}
Err(e) => {
result.warnings.push(format!(
"Legacy {} shorthand '{}' could not be migrated: {}",
if is_input { "IPU" } else { "OPU" },
old_id,
e
));
}
}
}
if !exceptions.is_empty() {
tracing::warn!(
target: "feagi-evo",
"⚠️ [MIGRATION] Applied legacy IO shorthand migration rules ({}): {}",
exceptions.len(),
exceptions.join(" | ")
);
result.warnings.extend(exceptions);
}
Ok(())
}
fn extract_cortical_id_from_flat_key(key: &str) -> Option<String> {
if !key.starts_with("_____10c-") {
return None;
}
let parts: Vec<&str> = key.split('-').collect();
if parts.len() >= 2 {
Some(parts[1].to_string())
} else {
None
}
}
fn needs_migration(id: &str) -> bool {
if id.starts_with("iic") {
return true;
}
if id.starts_with("omot") || id.starts_with("ogaz") {
return true;
}
if id.starts_with('_') && id.len() < 8 {
return true;
}
if id == "___pwr__" {
return true;
}
if is_legacy_io_shorthand(id) {
return true;
}
false
}
pub fn map_old_id_to_new(old_id: &str) -> Option<String> {
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::{
FrameChangeHandling, PercentageNeuronPositioning,
};
use feagi_structures::genomic::SensoryCorticalUnit;
if old_id.starts_with("iic") && old_id.len() >= 6 {
if let Some(index_char) = old_id.chars().nth(3) {
if index_char.is_ascii_digit() {
let unit_index = index_char as u8 - b'0';
if unit_index <= 8 {
let frame_handling = FrameChangeHandling::Absolute;
let group_index: CorticalUnitIndex = 0.into();
let cortical_ids =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
);
if (unit_index as usize) < cortical_ids.len() {
let new_id = cortical_ids[unit_index as usize].as_base_64();
tracing::debug!("🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64, Absolute+Linear)", old_id, new_id);
return Some(new_id);
}
}
}
}
}
use feagi_structures::genomic::MotorCorticalUnit;
if old_id.starts_with("omot") && old_id.len() >= 6 {
if let Some(index_chars) = old_id.get(4..6) {
if let Ok(unit_index) = index_chars.parse::<u8>() {
let frame_handling = FrameChangeHandling::Absolute;
let positioning = PercentageNeuronPositioning::Linear;
let group_index: CorticalUnitIndex = 0.into();
let cortical_ids =
MotorCorticalUnit::get_cortical_ids_array_for_rotary_motor_with_parameters(
frame_handling,
positioning,
group_index,
);
if unit_index == 0 && !cortical_ids.is_empty() {
let new_id = cortical_ids[0].as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64, Absolute+Linear)",
old_id,
new_id
);
return Some(new_id);
}
}
}
}
if old_id.starts_with("ogaz") && old_id.len() >= 6 {
if let Some(index_chars) = old_id.get(4..6) {
if let Ok(unit_index) = index_chars.parse::<u8>() {
let frame_handling = FrameChangeHandling::Absolute;
let positioning = PercentageNeuronPositioning::Linear;
let group_index: CorticalUnitIndex = 0.into();
let cortical_ids =
MotorCorticalUnit::get_cortical_ids_array_for_gaze_with_parameters(
frame_handling,
positioning,
group_index,
);
if (unit_index as usize) < cortical_ids.len() {
let new_id = cortical_ids[unit_index as usize].as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64, Absolute+Linear)",
old_id,
new_id
);
return Some(new_id);
}
}
}
}
use feagi_structures::genomic::cortical_area::CoreCorticalType;
if old_id == "_power" {
let new_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64)",
old_id,
new_id
);
return Some(new_id);
}
if old_id == "___pwr" {
let new_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64)",
old_id,
new_id
);
return Some(new_id);
}
if old_id == "___pwr__" {
let new_id = CoreCorticalType::Power.to_cortical_id().as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64)",
old_id,
new_id
);
return Some(new_id);
}
if old_id == "_death" {
let new_id = CoreCorticalType::Death.to_cortical_id().as_base_64();
tracing::debug!(
"🔄 [MIGRATION] Converting old ID '{}' → '{}' (base64)",
old_id,
new_id
);
return Some(new_id);
}
None
}
fn migrate_blueprint(result: &mut MigrationResult) -> EvoResult<()> {
let genome = result
.genome
.as_object_mut()
.ok_or_else(|| EvoError::InvalidGenome("Genome is not an object".to_string()))?;
let old_blueprint = genome
.get("blueprint")
.and_then(|v| v.as_object())
.ok_or_else(|| EvoError::InvalidGenome("Missing or invalid blueprint".to_string()))?
.clone();
let is_flat = old_blueprint.keys().any(|k| k.starts_with("_____10c-"));
let mut new_blueprint = serde_json::Map::new();
if is_flat {
for (old_key, value) in old_blueprint.iter() {
if let Some(cortical_id) = extract_cortical_id_from_flat_key(old_key) {
if let Some(new_id) = result.id_mapping.get(&cortical_id) {
let new_key =
old_key.replace(&format!("-{}-", cortical_id), &format!("-{}-", new_id));
new_blueprint.insert(new_key, value.clone());
} else {
new_blueprint.insert(old_key.clone(), value.clone());
}
} else {
new_blueprint.insert(old_key.clone(), value.clone());
}
}
} else {
for (old_id, area_data) in old_blueprint.iter() {
let new_id = result.id_mapping.get(old_id).unwrap_or(old_id);
new_blueprint.insert(new_id.clone(), area_data.clone());
}
}
genome.insert("blueprint".to_string(), Value::Object(new_blueprint));
Ok(())
}
fn migrate_brain_regions(result: &mut MigrationResult) -> EvoResult<()> {
let genome = result
.genome
.as_object_mut()
.ok_or_else(|| EvoError::InvalidGenome("Genome is not an object".to_string()))?;
if let Some(brain_regions_value) = genome.get_mut("brain_regions") {
if brain_regions_value.is_null() {
*brain_regions_value = Value::Object(serde_json::Map::new());
}
if let Some(brain_regions) = brain_regions_value.as_object_mut() {
for region in brain_regions.values_mut() {
if let Some(region_obj) = region.as_object_mut() {
for areas_key in ["areas", "cortical_areas"] {
if let Some(areas_value) = region_obj.get_mut(areas_key) {
if let Some(areas) = areas_value.as_array_mut() {
for area_id in areas.iter_mut() {
if let Some(old_id) = area_id.as_str() {
if let Some(new_id) = result.id_mapping.get(old_id) {
*area_id = Value::String(new_id.clone());
}
}
}
}
}
}
if let Some(inputs_value) = region_obj.get_mut("inputs") {
if let Some(inputs) = inputs_value.as_array_mut() {
for input_id in inputs.iter_mut() {
if let Some(old_id) = input_id.as_str() {
if let Some(new_id) = result.id_mapping.get(old_id) {
*input_id = Value::String(new_id.clone());
}
}
}
}
}
if let Some(outputs_value) = region_obj.get_mut("outputs") {
if let Some(outputs) = outputs_value.as_array_mut() {
for output_id in outputs.iter_mut() {
if let Some(old_id) = output_id.as_str() {
if let Some(new_id) = result.id_mapping.get(old_id) {
*output_id = Value::String(new_id.clone());
}
}
}
}
}
for key in ["designated_inputs", "designated_outputs"] {
if let Some(val) = region_obj.get_mut(key) {
if let Some(arr) = val.as_array_mut() {
for entry in arr.iter_mut() {
if let Some(old_id) = entry.as_str() {
if let Some(new_id) = result.id_mapping.get(old_id) {
*entry = Value::String(new_id.clone());
}
}
}
}
}
}
if let Some(Value::Object(props)) = region_obj.get_mut("properties") {
for key in [
"inputs",
"outputs",
"designated_inputs",
"designated_outputs",
] {
if let Some(val) = props.get_mut(key) {
if let Some(arr) = val.as_array_mut() {
for entry in arr.iter_mut() {
if let Some(old_id) = entry.as_str() {
if let Some(new_id) = result.id_mapping.get(old_id) {
*entry = Value::String(new_id.clone());
}
}
}
}
}
}
}
}
}
}
}
Ok(())
}
fn migrate_cortical_mappings(result: &mut MigrationResult) -> EvoResult<()> {
let genome = result
.genome
.as_object_mut()
.ok_or_else(|| EvoError::InvalidGenome("Genome is not an object".to_string()))?;
if let Some(blueprint_value) = genome.get_mut("blueprint") {
if let Some(blueprint) = blueprint_value.as_object_mut() {
for area_data in blueprint.values_mut() {
if let Some(area_obj) = area_data.as_object_mut() {
if let Some(dstmap_value) = area_obj.get("cortical_mapping_dst") {
if let Some(old_dstmap) = dstmap_value.as_object() {
let mut new_dstmap = serde_json::Map::new();
for (old_dst_id, mapping_rules) in old_dstmap.iter() {
let new_dst_id =
result.id_mapping.get(old_dst_id).unwrap_or(old_dst_id);
new_dstmap.insert(new_dst_id.clone(), mapping_rules.clone());
}
area_obj.insert(
"cortical_mapping_dst".to_string(),
Value::Object(new_dstmap),
);
}
}
}
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn test_map_old_id_to_new() {
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::FrameChangeHandling;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::PercentageNeuronPositioning;
use feagi_structures::genomic::cortical_area::CoreCorticalType;
use feagi_structures::genomic::MotorCorticalUnit;
use feagi_structures::genomic::SensoryCorticalUnit;
let group_index: CorticalUnitIndex = 0.into();
let frame_handling = FrameChangeHandling::Absolute;
let expected_svi0 =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
)[0]
.as_base_64();
let expected_svi1 =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
)[1]
.as_base_64();
let expected_svi4 =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
)[4]
.as_base_64();
let expected_svi8 =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
frame_handling,
group_index,
)[8]
.as_base_64();
assert_eq!(map_old_id_to_new("iic000"), Some(expected_svi0));
assert_eq!(map_old_id_to_new("iic100"), Some(expected_svi1));
assert_eq!(map_old_id_to_new("iic400"), Some(expected_svi4));
assert_eq!(map_old_id_to_new("iic800"), Some(expected_svi8));
let positioning = PercentageNeuronPositioning::Linear;
let expected_mot0 =
MotorCorticalUnit::get_cortical_ids_array_for_rotary_motor_with_parameters(
frame_handling,
positioning,
group_index,
)[0]
.as_base_64();
let expected_gaz0 = MotorCorticalUnit::get_cortical_ids_array_for_gaze_with_parameters(
frame_handling,
positioning,
group_index,
)[0]
.as_base_64();
assert_eq!(map_old_id_to_new("omot00"), Some(expected_mot0));
assert_eq!(map_old_id_to_new("ogaz00"), Some(expected_gaz0));
assert_eq!(
map_old_id_to_new("_power"),
Some(CoreCorticalType::Power.to_cortical_id().as_base_64())
);
assert_eq!(
map_old_id_to_new("___pwr"),
Some(CoreCorticalType::Power.to_cortical_id().as_base_64())
);
assert_eq!(
map_old_id_to_new("___pwr__"),
Some(CoreCorticalType::Power.to_cortical_id().as_base_64())
);
assert_eq!(
map_old_id_to_new("_death"),
Some(CoreCorticalType::Death.to_cortical_id().as_base_64())
);
assert_eq!(map_old_id_to_new("svi0____"), None);
assert_eq!(
map_old_id_to_new(&CoreCorticalType::Power.to_cortical_id().as_base_64()),
None
);
}
#[test]
fn test_needs_migration() {
use feagi_structures::genomic::cortical_area::CoreCorticalType;
assert!(needs_migration("iic000"));
assert!(needs_migration("omot00"));
assert!(needs_migration("_power"));
assert!(needs_migration("___pwr__"));
assert!(!needs_migration("svi0____"));
assert!(!needs_migration("mot0____"));
assert!(!needs_migration(
&CoreCorticalType::Power.to_cortical_id().to_string()
));
assert!(!needs_migration("custom01"));
}
#[test]
fn test_migrate_brain_regions_null_becomes_empty_object() {
let genome = json!({
"version": "2.0",
"blueprint": {
"_____10c-iv00_C-cx-__name-t": "Central vision sensor",
},
"brain_regions": null,
"neuron_morphologies": {},
"physiology": {}
});
let result = migrate_genome(&genome).expect("Migration failed");
let br = result
.genome
.get("brain_regions")
.expect("brain_regions key present after migration");
assert!(
br.is_object(),
"brain_regions should be an object after migration, got {:?}",
br
);
}
#[test]
fn test_migrate_six_char_catch_all() {
let genome = json!({
"genome_id": "test",
"version": "2.1",
"blueprint": {
"custom": {
"cortical_name": "Custom Area",
"block_boundaries": [1, 1, 1],
"relative_coordinate": [0, 0, 0],
"cortical_type": "CUSTOM"
}
},
"brain_regions": {}
});
let result = migrate_genome(&genome).expect("Migration failed");
assert_eq!(result.cortical_ids_migrated, 1);
let new_id = result
.id_mapping
.get("custom")
.expect("custom should be mapped");
assert!(
string_to_cortical_id(new_id).is_ok(),
"new_id '{}' must be valid base64 CorticalID",
new_id
);
assert_ne!(new_id, "custom__", "must use base64, not string padding");
let new_blueprint = result
.genome
.get("blueprint")
.and_then(|v| v.as_object())
.expect("Blueprint missing");
assert!(new_blueprint.contains_key(new_id));
assert!(!new_blueprint.contains_key("custom"));
}
#[test]
fn test_migrate_simple_genome() {
use feagi_structures::genomic::cortical_area::CoreCorticalType;
let genome = json!({
"genome_id": "test",
"version": "2.1",
"blueprint": {
"iic000": {
"cortical_name": "Vision 0",
"cortical_type": "IPU"
},
"_power": {
"cortical_name": "Power",
"cortical_type": "CORE"
}
},
"brain_regions": {
"root": {
"areas": ["iic000", "_power"],
"inputs": ["iic000"],
"outputs": []
}
}
});
let result = migrate_genome(&genome).expect("Migration failed");
let expected_power_id = CoreCorticalType::Power.to_cortical_id().to_string();
assert_eq!(result.cortical_ids_migrated, 2);
assert!(
result.id_mapping.contains_key("iic000"),
"iic000 should be migrated"
);
assert!(
result.id_mapping.contains_key("_power"),
"_power should be migrated"
);
assert_eq!(result.id_mapping.get("_power"), Some(&expected_power_id));
let new_blueprint = result
.genome
.get("blueprint")
.and_then(|v| v.as_object())
.expect("Blueprint missing");
assert!(
new_blueprint.contains_key(&expected_power_id),
"Power ID should be in blueprint"
);
assert!(
!new_blueprint.contains_key("iic000"),
"Old iic000 should be removed"
);
assert!(
!new_blueprint.contains_key("_power"),
"Old _power should be removed"
);
let regions = result
.genome
.get("brain_regions")
.and_then(|v| v.as_object())
.expect("brain_regions missing");
let root = regions
.get("root")
.and_then(|v| v.as_object())
.expect("root region missing");
let areas = root
.get("areas")
.and_then(|v| v.as_array())
.expect("areas array missing");
let migrated_vision_id = result
.id_mapping
.get("iic000")
.expect("iic000 should be mapped");
assert_eq!(
areas[0].as_str(),
Some(migrated_vision_id.as_str()),
"Vision ID should be migrated"
);
assert_eq!(
areas[1].as_str(),
Some(expected_power_id.as_str()),
"Power ID should be migrated"
);
}
#[test]
fn test_migrate_legacy_io_shorthands_to_segmented_center_and_misc() {
let genome = json!({
"version": "2.0",
"blueprint": {
"_____10c-iv00_C-cx-__name-t": "Central vision sensor",
"_____10c-i___id-cx-__name-t": "ID Trainer",
"_____10c-o___id-cx-__name-t": "ID Recognition",
},
"brain_regions": null,
"neuron_morphologies": {},
"physiology": {}
});
let result = migrate_genome(&genome).unwrap();
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::FrameChangeHandling;
use feagi_structures::genomic::SensoryCorticalUnit;
let expected_center =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
FrameChangeHandling::Absolute,
CorticalUnitIndex::from(0u8),
)[4]
.as_base_64();
assert_eq!(result.id_mapping.get("iv00_C").unwrap(), &expected_center);
let i_mapped = result.id_mapping.get("i___id").expect("i___id mapped");
let o_mapped = result.id_mapping.get("o___id").expect("o___id mapped");
assert_ne!(i_mapped, o_mapped);
assert!(
result
.warnings
.iter()
.any(|w| w.contains("Legacy") && w.contains("mapped")),
"Expected migration warnings report for legacy IO shorthands"
);
}
#[test]
fn test_migrate_legacy_segmented_vision_tl_to_subunit_6() {
let genome = json!({
"version": "2.0",
"blueprint": {
"_____10c-iv00TL-cx-__name-t": "Vision Top Left",
},
"brain_regions": null,
"neuron_morphologies": {},
"physiology": {}
});
let result = migrate_genome(&genome).unwrap();
use feagi_structures::genomic::cortical_area::descriptors::CorticalUnitIndex;
use feagi_structures::genomic::cortical_area::io_cortical_area_configuration_flag::FrameChangeHandling;
use feagi_structures::genomic::SensoryCorticalUnit;
let expected =
SensoryCorticalUnit::get_cortical_ids_array_for_segmented_vision_with_parameters(
FrameChangeHandling::Absolute,
CorticalUnitIndex::from(0u8),
)[6]
.as_base_64();
assert_eq!(result.id_mapping.get("iv00TL").unwrap(), &expected);
}
}