use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[repr(u8)]
pub enum OpCategory {
Structural = 0,
Environmental = 1,
Biological = 2,
Informational = 3,
Temporal = 4,
Acoustic = 5,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[repr(u8)]
pub enum EmergenceRarity {
Common = 0,
Uncommon = 1,
Rare = 2,
Legendary = 3,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EmergenceResult {
pub op_a: u32,
pub op_b: u32,
pub rarity: EmergenceRarity,
pub behavior_index: u8,
pub visual_override: [f32; 3],
}
const OP_CATEGORIES: [OpCategory; 55] = [
OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
OpCategory::Structural, OpCategory::Structural, OpCategory::Structural,
OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
OpCategory::Environmental, OpCategory::Environmental, OpCategory::Environmental,
OpCategory::Environmental,
OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
OpCategory::Biological, OpCategory::Biological, OpCategory::Biological,
OpCategory::Informational, OpCategory::Informational, OpCategory::Informational,
OpCategory::Informational, OpCategory::Informational, OpCategory::Informational,
OpCategory::Informational, OpCategory::Informational,
OpCategory::Temporal, OpCategory::Temporal, OpCategory::Temporal,
OpCategory::Temporal, OpCategory::Temporal, OpCategory::Temporal,
OpCategory::Temporal, OpCategory::Temporal,
OpCategory::Acoustic, OpCategory::Acoustic, OpCategory::Acoustic,
OpCategory::Acoustic, OpCategory::Acoustic, OpCategory::Acoustic,
OpCategory::Acoustic, OpCategory::Acoustic,
];
pub fn op_category(op_id: u32) -> OpCategory {
if (op_id as usize) < OP_CATEGORIES.len() {
OP_CATEGORIES[op_id as usize]
} else {
OpCategory::Structural }
}
fn category_distance(a: OpCategory, b: OpCategory) -> u32 {
if (a == OpCategory::Temporal && b == OpCategory::Acoustic)
|| (a == OpCategory::Acoustic && b == OpCategory::Temporal)
{
return 4;
}
let ai = a as i32;
let bi = b as i32;
(ai - bi).unsigned_abs()
}
fn distance_to_rarity(distance: u32) -> EmergenceRarity {
match distance {
0 => EmergenceRarity::Common,
1 => EmergenceRarity::Uncommon,
2..=3 => EmergenceRarity::Rare,
_ => EmergenceRarity::Legendary,
}
}
pub fn compose_ops(op_a: u32, op_b: u32) -> EmergenceResult {
let (lo, hi) = if op_a <= op_b { (op_a, op_b) } else { (op_b, op_a) };
let cat_a = op_category(lo);
let cat_b = op_category(hi);
let distance = category_distance(cat_a, cat_b);
let rarity = distance_to_rarity(distance);
let pair_hash = lo.wrapping_mul(7919) ^ hi.wrapping_mul(104729);
let behavior_index = (pair_hash % 16) as u8;
let visual_override = match rarity {
EmergenceRarity::Common => [0.0, 0.1, 0.2],
EmergenceRarity::Uncommon => [0.15, 0.2, 0.4],
EmergenceRarity::Rare => [0.3, 0.4, 0.7],
EmergenceRarity::Legendary => [0.5, 0.6, 1.0],
};
EmergenceResult {
op_a: lo,
op_b: hi,
rarity,
behavior_index,
visual_override,
}
}
pub fn best_emergence(active_ops: u64) -> Option<EmergenceResult> {
let ops: Vec<u32> = (0u32..55).filter(|&i| active_ops & (1u64 << i) != 0).collect();
if ops.len() < 2 {
return None;
}
let mut best: Option<EmergenceResult> = None;
for i in 0..ops.len() {
for j in (i + 1)..ops.len() {
let result = compose_ops(ops[i], ops[j]);
if best.as_ref().is_none_or(|b| result.rarity > b.rarity) {
best = Some(result);
}
}
}
best
}
pub fn emergence_to_json(result: &EmergenceResult) -> String {
serde_json::to_string(result).unwrap_or_default()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_same_category_common() {
let result = compose_ops(0, 1);
assert_eq!(result.rarity, EmergenceRarity::Common);
}
#[test]
fn test_adjacent_category_uncommon() {
let result = compose_ops(11, 12);
assert_eq!(result.rarity, EmergenceRarity::Uncommon);
}
#[test]
fn test_distant_category_rare() {
let result = compose_ops(0, 31);
assert_eq!(result.rarity, EmergenceRarity::Rare);
}
#[test]
fn test_temporal_acoustic_legendary() {
let result = compose_ops(39, 47);
assert_eq!(result.rarity, EmergenceRarity::Legendary);
}
#[test]
fn test_compose_deterministic() {
let r1 = compose_ops(5, 20);
let r2 = compose_ops(5, 20);
assert_eq!(r1.rarity, r2.rarity);
assert_eq!(r1.behavior_index, r2.behavior_index);
}
#[test]
fn test_compose_symmetric() {
let r1 = compose_ops(5, 20);
let r2 = compose_ops(20, 5);
assert_eq!(r1.rarity, r2.rarity);
assert_eq!(r1.op_a, r2.op_a);
assert_eq!(r1.op_b, r2.op_b);
}
#[test]
fn test_best_emergence_no_ops() {
assert!(best_emergence(0).is_none());
}
#[test]
fn test_best_emergence_single_op() {
assert!(best_emergence(1 << 5).is_none());
}
#[test]
fn test_best_emergence_multiple() {
let bits = (1u64 << 0) | (1u64 << 39) | (1u64 << 47);
let best = best_emergence(bits).unwrap();
assert_eq!(best.rarity, EmergenceRarity::Legendary); }
#[test]
fn test_emergence_to_json() {
let result = compose_ops(0, 47);
let json = emergence_to_json(&result);
assert!(json.contains("rarity"));
assert!(json.contains("visual_override"));
}
#[test]
fn test_op_category_bounds() {
assert_eq!(op_category(0), OpCategory::Structural);
assert_eq!(op_category(54), OpCategory::Acoustic);
assert_eq!(op_category(100), OpCategory::Structural); }
}