#![allow(clippy::significant_drop_tightening)]
use crate::Gana;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GanaRegistry {
co_usage: HashMap<String, u64>,
#[serde(skip)]
co_usage_pairs: HashMap<(u8, u8), String>,
usage_counts: HashMap<u8, u64>,
success_rates: HashMap<u8, f32>,
drift_threshold: u64,
drift_enabled: bool,
suggested_merges: Vec<GanaMerge>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GanaMerge {
pub gana_a: u8,
pub gana_b: u8,
pub co_usage_count: u64,
pub confidence: f32,
}
impl GanaRegistry {
#[must_use]
pub fn new() -> Self {
Self {
co_usage: HashMap::new(),
co_usage_pairs: HashMap::new(),
usage_counts: HashMap::new(),
success_rates: HashMap::new(),
drift_threshold: 100,
drift_enabled: true,
suggested_merges: Vec::new(),
}
}
#[must_use]
pub fn with_threshold(drift_threshold: u64) -> Self {
Self {
co_usage: HashMap::new(),
co_usage_pairs: HashMap::new(),
usage_counts: HashMap::new(),
success_rates: HashMap::new(),
drift_threshold,
drift_enabled: true,
suggested_merges: Vec::new(),
}
}
pub fn record_usage(&mut self, gana: Gana, success: bool) {
let idx = gana as u8;
*self.usage_counts.entry(idx).or_insert(0) += 1;
let current = self.success_rates.get(&idx).copied().unwrap_or(0.5);
let count = self.usage_counts[&idx];
let new_rate = if success {
current + (1.0 - current) / count as f32
} else {
current * (1.0 - 1.0 / count as f32)
};
self.success_rates.insert(idx, new_rate);
}
pub fn record_co_usage(&mut self, gana_a: Gana, gana_b: Gana) {
let (a, b) = if gana_a as u8 <= gana_b as u8 {
(gana_a as u8, gana_b as u8)
} else {
(gana_b as u8, gana_a as u8)
};
let key = format!("{a}:{b}");
self.co_usage_pairs.insert((a, b), key.clone());
*self.co_usage.entry(key).or_insert(0) += 1;
if self.drift_enabled {
let count = self.co_usage[&format!("{a}:{b}")];
if count == self.drift_threshold {
self.suggested_merges.push(GanaMerge {
gana_a: a,
gana_b: b,
co_usage_count: count,
confidence: 0.5,
});
} else if count > self.drift_threshold && count % self.drift_threshold == 0 {
if let Some(merge) = self
.suggested_merges
.iter_mut()
.find(|m| m.gana_a == a && m.gana_b == b)
{
merge.co_usage_count = count;
merge.confidence = (merge.confidence + 0.1).min(1.0);
}
}
}
}
#[must_use]
pub fn success_rate(&self, gana: Gana) -> f32 {
self.success_rates
.get(&(gana as u8))
.copied()
.unwrap_or(0.5)
}
#[must_use]
pub fn usage_count(&self, gana: Gana) -> u64 {
self.usage_counts.get(&(gana as u8)).copied().unwrap_or(0)
}
#[must_use]
pub const fn usage_counts(&self) -> &HashMap<u8, u64> {
&self.usage_counts
}
#[must_use]
pub const fn co_usage(&self) -> &HashMap<String, u64> {
&self.co_usage
}
#[must_use]
pub fn co_usage_count(&self, gana_a: Gana, gana_b: Gana) -> u64 {
let (a, b) = if gana_a as u8 <= gana_b as u8 {
(gana_a as u8, gana_b as u8)
} else {
(gana_b as u8, gana_a as u8)
};
self.co_usage_pairs
.get(&(a, b))
.and_then(|key| self.co_usage.get(key))
.copied()
.unwrap_or(0)
}
#[must_use]
pub fn suggested_merges(&self) -> &[GanaMerge] {
&self.suggested_merges
}
#[must_use]
pub fn analyze_drift(&self, top_n: usize) -> Vec<GanaMerge> {
let mut merges = self.suggested_merges.clone();
merges.sort_by_key(|x| std::cmp::Reverse(x.co_usage_count));
merges.truncate(top_n);
merges
}
pub fn rebuild_pairs(&mut self) {
self.co_usage_pairs.clear();
for key in self.co_usage.keys() {
let parts: Vec<&str> = key.split(':').collect();
if parts.len() == 2 {
if let (Ok(a), Ok(b)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>()) {
self.co_usage_pairs.insert((a, b), key.clone());
}
}
}
}
pub fn clear(&mut self) {
self.co_usage.clear();
self.co_usage_pairs.clear();
self.usage_counts.clear();
self.success_rates.clear();
self.suggested_merges.clear();
}
#[must_use]
pub fn snapshot(&self) -> serde_json::Value {
serde_json::json!({
"total_ganas_tracked": self.usage_counts.len(),
"total_co_usage_pairs": self.co_usage.len(),
"suggested_merges": self.suggested_merges.len(),
"drift_threshold": self.drift_threshold,
"drift_enabled": self.drift_enabled,
})
}
}
impl Default for GanaRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicGalaxy {
pub id: String,
pub name: String,
pub description: String,
pub cluster_tags: Vec<String>,
pub memory_count: usize,
pub created_at: u64,
pub effectiveness: f32,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DynamicGalaxyRegistry {
galaxies: HashMap<String, DynamicGalaxy>,
min_cluster_size: usize,
max_galaxies: usize,
prune_threshold: f32,
}
impl DynamicGalaxyRegistry {
#[must_use]
pub fn new() -> Self {
Self {
galaxies: HashMap::new(),
min_cluster_size: 10,
max_galaxies: 20,
prune_threshold: 0.1,
}
}
#[must_use]
pub fn with_config(min_cluster_size: usize, max_galaxies: usize, prune_threshold: f32) -> Self {
Self {
galaxies: HashMap::new(),
min_cluster_size,
max_galaxies,
prune_threshold,
}
}
pub fn try_create(
&mut self,
name: &str,
description: &str,
cluster_tags: Vec<String>,
memory_count: usize,
) -> Option<&DynamicGalaxy> {
if memory_count < self.min_cluster_size {
return None;
}
if self.galaxies.len() >= self.max_galaxies {
self.prune();
if self.galaxies.len() >= self.max_galaxies {
return None;
}
}
let id = format!("dyn_{}", name.to_lowercase().replace(' ', "_"));
if self.galaxies.contains_key(&id) {
if let Some(g) = self.galaxies.get_mut(&id) {
g.memory_count = memory_count;
}
return self.galaxies.get(&id);
}
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs());
let galaxy = DynamicGalaxy {
id: id.clone(),
name: name.to_string(),
description: description.to_string(),
cluster_tags,
memory_count,
created_at: timestamp,
effectiveness: 0.5,
};
self.galaxies.insert(id.clone(), galaxy);
self.galaxies.get(&id)
}
#[must_use]
pub fn get(&self, id: &str) -> Option<&DynamicGalaxy> {
self.galaxies.get(id)
}
#[must_use]
pub fn all(&self) -> Vec<&DynamicGalaxy> {
self.galaxies.values().collect()
}
pub fn update_effectiveness(&mut self, id: &str, effectiveness: f32) {
if let Some(g) = self.galaxies.get_mut(id) {
g.effectiveness = effectiveness;
}
}
pub fn prune(&mut self) -> usize {
let before = self.galaxies.len();
self.galaxies
.retain(|_, g| g.effectiveness >= self.prune_threshold);
before - self.galaxies.len()
}
#[must_use]
pub fn len(&self) -> usize {
self.galaxies.len()
}
#[must_use]
pub fn galaxy_count(&self) -> usize {
self.galaxies.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.galaxies.is_empty()
}
}
impl Default for DynamicGalaxyRegistry {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PhaseEffectiveness {
pub runs: u64,
pub useful_results: u64,
pub avg_improvement: f32,
pub avg_duration_ms: u64,
}
impl PhaseEffectiveness {
#[must_use]
pub const fn new() -> Self {
Self {
runs: 0,
useful_results: 0,
avg_improvement: 0.0,
avg_duration_ms: 0,
}
}
#[must_use]
pub fn score(&self) -> f32 {
if self.runs == 0 {
return 0.5;
}
let success_rate = self.useful_results as f32 / self.runs as f32;
success_rate.midpoint(self.avg_improvement)
}
pub fn record(&mut self, useful: bool, improvement: f32, duration_ms: u64) {
let n = self.runs as f32;
self.avg_improvement = self.avg_improvement.mul_add(n, improvement) / (n + 1.0);
self.avg_duration_ms =
((self.avg_duration_ms as f32).mul_add(n, duration_ms as f32) / (n + 1.0)) as u64;
self.runs += 1;
if useful {
self.useful_results += 1;
}
}
}
impl Default for PhaseEffectiveness {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearnedDreamCycle {
phase_effectiveness: HashMap<u8, PhaseEffectiveness>,
min_effectiveness: f32,
min_runs: u64,
learning_enabled: bool,
phase_order: Vec<u8>,
}
impl LearnedDreamCycle {
#[must_use]
pub fn new() -> Self {
let default_order: Vec<u8> = (0..12u8).collect();
Self {
phase_effectiveness: HashMap::new(),
min_effectiveness: 0.2,
min_runs: 5,
learning_enabled: true,
phase_order: default_order,
}
}
#[must_use]
pub fn with_config(min_effectiveness: f32, min_runs: u64, learning_enabled: bool) -> Self {
Self {
phase_effectiveness: HashMap::new(),
min_effectiveness,
min_runs,
learning_enabled,
phase_order: (0..12u8).collect(),
}
}
pub fn record_phase(
&mut self,
phase_idx: u8,
useful: bool,
improvement: f32,
duration_ms: u64,
) {
let record = self.phase_effectiveness.entry(phase_idx).or_default();
record.record(useful, improvement, duration_ms);
if self.learning_enabled {
self.update_phase_order();
}
}
#[must_use]
pub fn phase_order(&self) -> &[u8] {
&self.phase_order
}
#[must_use]
pub fn phases_to_run(&self) -> Vec<u8> {
self.phase_order
.iter()
.filter(|&&idx| {
if let Some(eff) = self.phase_effectiveness.get(&idx) {
if eff.runs >= self.min_runs {
return eff.score() >= self.min_effectiveness;
}
}
true })
.copied()
.collect()
}
#[must_use]
pub fn effectiveness(&self, phase_idx: u8) -> Option<&PhaseEffectiveness> {
self.phase_effectiveness.get(&phase_idx)
}
fn update_phase_order(&mut self) {
let mut scored: Vec<(u8, f32)> = (0..12u8)
.map(|idx| {
let score = self
.phase_effectiveness
.get(&idx)
.map_or(0.5, PhaseEffectiveness::score);
(idx, score)
})
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
self.phase_order = scored.into_iter().map(|(idx, _)| idx).collect();
}
#[must_use]
pub fn snapshot(&self) -> serde_json::Value {
let phases: Vec<serde_json::Value> = (0..12u8)
.map(|idx| {
if let Some(eff) = self.phase_effectiveness.get(&idx) {
serde_json::json!({
"phase": idx,
"runs": eff.runs,
"useful": eff.useful_results,
"score": eff.score(),
"avg_improvement": eff.avg_improvement,
"avg_duration_ms": eff.avg_duration_ms,
})
} else {
serde_json::json!({"phase": idx, "runs": 0})
}
})
.collect();
serde_json::json!({
"phases": phases,
"phase_order": self.phase_order,
"phases_to_run": self.phases_to_run(),
"min_effectiveness": self.min_effectiveness,
"learning_enabled": self.learning_enabled,
})
}
}
impl Default for LearnedDreamCycle {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum CycleStrategy {
FixedOrder,
PriorityBased,
BestOnly,
Adaptive,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CycleEffectiveness {
pub runs: u64,
pub proposals_generated: u64,
pub avg_usefulness: f32,
pub avg_duration_ms: u64,
}
impl CycleEffectiveness {
#[must_use]
pub const fn new() -> Self {
Self {
runs: 0,
proposals_generated: 0,
avg_usefulness: 0.0,
avg_duration_ms: 0,
}
}
#[must_use]
pub fn score(&self) -> f32 {
if self.runs == 0 {
return 0.5;
}
let proposal_rate = self.proposals_generated as f32 / self.runs as f32;
proposal_rate.midpoint(self.avg_usefulness)
}
pub fn record(&mut self, proposals: u64, usefulness: f32, duration_ms: u64) {
let n = self.runs as f32;
self.avg_usefulness = self.avg_usefulness.mul_add(n, usefulness) / (n + 1.0);
self.avg_duration_ms =
((self.avg_duration_ms as f32).mul_add(n, duration_ms as f32) / (n + 1.0)) as u64;
self.runs += 1;
self.proposals_generated += proposals;
}
}
impl Default for CycleEffectiveness {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct LearnedCycleStrategy {
cycle_effectiveness: HashMap<u8, CycleEffectiveness>,
strategy: CycleStrategy,
exploration_rate: f32,
min_runs: u64,
priority_order: Vec<u8>,
}
impl LearnedCycleStrategy {
#[must_use]
pub fn new() -> Self {
Self {
cycle_effectiveness: HashMap::new(),
strategy: CycleStrategy::FixedOrder,
exploration_rate: 0.1,
min_runs: 10,
priority_order: (0..8u8).collect(),
}
}
#[must_use]
pub fn with_strategy(strategy: CycleStrategy) -> Self {
Self {
cycle_effectiveness: HashMap::new(),
strategy,
exploration_rate: 0.1,
min_runs: 10,
priority_order: (0..8u8).collect(),
}
}
pub fn record_cycle(
&mut self,
cycle_type_idx: u8,
proposals: u64,
usefulness: f32,
duration_ms: u64,
) {
let record = self.cycle_effectiveness.entry(cycle_type_idx).or_default();
record.record(proposals, usefulness, duration_ms);
if self.strategy == CycleStrategy::FixedOrder {
let total_runs: u64 = self.cycle_effectiveness.values().map(|e| e.runs).sum();
if total_runs >= self.min_runs {
self.strategy = CycleStrategy::PriorityBased;
self.update_priority_order();
}
} else if matches!(
self.strategy,
CycleStrategy::PriorityBased | CycleStrategy::Adaptive
) {
self.update_priority_order();
}
}
#[must_use]
pub const fn strategy(&self) -> CycleStrategy {
self.strategy
}
#[must_use]
pub fn priority_order(&self) -> &[u8] {
&self.priority_order
}
#[must_use]
pub fn cycles_to_run(&self) -> Vec<u8> {
match self.strategy {
CycleStrategy::FixedOrder => (0..8u8).collect(),
CycleStrategy::PriorityBased | CycleStrategy::Adaptive => {
if self.strategy == CycleStrategy::Adaptive {
}
self.priority_order.clone()
}
CycleStrategy::BestOnly => self.priority_order.first().copied().into_iter().collect(),
}
}
#[must_use]
pub fn effectiveness(&self, cycle_type_idx: u8) -> Option<&CycleEffectiveness> {
self.cycle_effectiveness.get(&cycle_type_idx)
}
pub fn set_strategy(&mut self, strategy: CycleStrategy) {
self.strategy = strategy;
if matches!(
strategy,
CycleStrategy::PriorityBased | CycleStrategy::Adaptive
) {
self.update_priority_order();
}
}
fn update_priority_order(&mut self) {
let mut scored: Vec<(u8, f32)> = (0..8u8)
.map(|idx| {
let score = self
.cycle_effectiveness
.get(&idx)
.map_or(0.5, CycleEffectiveness::score);
(idx, score)
})
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
self.priority_order = scored.into_iter().map(|(idx, _)| idx).collect();
}
#[must_use]
pub fn snapshot(&self) -> serde_json::Value {
let cycles: Vec<serde_json::Value> = (0..8u8)
.map(|idx| {
if let Some(eff) = self.cycle_effectiveness.get(&idx) {
serde_json::json!({
"cycle_type": idx,
"runs": eff.runs,
"proposals": eff.proposals_generated,
"score": eff.score(),
"avg_usefulness": eff.avg_usefulness,
})
} else {
serde_json::json!({"cycle_type": idx, "runs": 0})
}
})
.collect();
serde_json::json!({
"strategy": format!("{:?}", self.strategy),
"cycles": cycles,
"priority_order": self.priority_order,
"cycles_to_run": self.cycles_to_run(),
"exploration_rate": self.exploration_rate,
})
}
}
impl Default for LearnedCycleStrategy {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gana_registry_record_usage() {
let mut registry = GanaRegistry::new();
registry.record_usage(Gana::Horn, true);
registry.record_usage(Gana::Horn, true);
registry.record_usage(Gana::Horn, false);
assert_eq!(registry.usage_count(Gana::Horn), 3);
let rate = registry.success_rate(Gana::Horn);
assert!(rate > 0.5); }
#[test]
fn gana_registry_co_usage() {
let mut registry = GanaRegistry::new();
registry.record_co_usage(Gana::Horn, Gana::Encampment);
registry.record_co_usage(Gana::Horn, Gana::Encampment);
registry.record_co_usage(Gana::Horn, Gana::Encampment);
assert_eq!(registry.co_usage_count(Gana::Horn, Gana::Encampment), 3);
assert_eq!(registry.co_usage_count(Gana::Encampment, Gana::Horn), 3);
}
#[test]
fn gana_registry_drift_suggestion() {
let mut registry = GanaRegistry::with_threshold(5);
for _ in 0..5 {
registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
}
let merges = registry.suggested_merges();
assert_eq!(merges.len(), 1);
assert_eq!(merges[0].gana_a, Gana::Horn as u8);
assert_eq!(merges[0].gana_b, Gana::WinnowingBasket as u8);
}
#[test]
fn gana_registry_drift_confidence_increases() {
let mut registry = GanaRegistry::with_threshold(5);
for _ in 0..10 {
registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
}
let merges = registry.suggested_merges();
assert_eq!(merges.len(), 1);
assert!(merges[0].confidence > 0.5);
}
#[test]
fn gana_registry_analyze_drift() {
let mut registry = GanaRegistry::with_threshold(3);
for _ in 0..5 {
registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
}
for _ in 0..3 {
registry.record_co_usage(Gana::Ghost, Gana::Star);
}
let top = registry.analyze_drift(2);
assert_eq!(top.len(), 2);
assert!(top[0].co_usage_count >= top[1].co_usage_count);
}
#[test]
fn gana_registry_clear() {
let mut registry = GanaRegistry::new();
registry.record_usage(Gana::Horn, true);
registry.record_co_usage(Gana::Horn, Gana::Neck);
registry.clear();
assert_eq!(registry.usage_count(Gana::Horn), 0);
assert_eq!(registry.co_usage_count(Gana::Horn, Gana::Neck), 0);
}
#[test]
fn gana_registry_snapshot() {
let mut registry = GanaRegistry::new();
registry.record_usage(Gana::Horn, true);
let snap = registry.snapshot();
assert!(snap.get("total_ganas_tracked").is_some());
}
#[test]
fn dynamic_galaxy_create() {
let mut registry = DynamicGalaxyRegistry::with_config(5, 10, 0.1);
let galaxy = registry.try_create(
"Rust Patterns",
"Memories about Rust design patterns",
vec!["rust".to_string(), "patterns".to_string()],
15,
);
assert!(galaxy.is_some());
assert_eq!(galaxy.unwrap().name, "Rust Patterns");
assert_eq!(registry.len(), 1);
}
#[test]
fn dynamic_galaxy_too_small() {
let mut registry = DynamicGalaxyRegistry::with_config(10, 5, 0.1);
let galaxy = registry.try_create("Small", "Too small", vec![], 3);
assert!(galaxy.is_none());
assert!(registry.is_empty());
}
#[test]
fn dynamic_galaxy_max_limit() {
let mut registry = DynamicGalaxyRegistry::with_config(1, 2, 0.0);
registry.try_create("G1", "desc", vec![], 5);
registry.try_create("G2", "desc", vec![], 5);
registry.try_create("G3", "desc", vec![], 5);
assert_eq!(registry.len(), 2); }
#[test]
fn dynamic_galaxy_prune() {
let mut registry = DynamicGalaxyRegistry::with_config(1, 10, 0.5);
registry.try_create("G1", "desc", vec![], 5);
registry.try_create("G2", "desc", vec![], 5);
registry.update_effectiveness("dyn_g1", 0.1); registry.update_effectiveness("dyn_g2", 0.8);
let pruned = registry.prune();
assert_eq!(pruned, 1);
assert_eq!(registry.len(), 1);
assert!(registry.get("dyn_g2").is_some());
}
#[test]
fn dynamic_galaxy_update_existing() {
let mut registry = DynamicGalaxyRegistry::with_config(1, 10, 0.0);
registry.try_create("Test", "desc", vec![], 5);
registry.try_create("Test", "desc", vec![], 10);
let g = registry.get("dyn_test").unwrap();
assert_eq!(g.memory_count, 10);
assert_eq!(registry.len(), 1);
}
#[test]
fn learned_dream_default_order() {
let cycle = LearnedDreamCycle::new();
assert_eq!(cycle.phase_order(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
}
#[test]
fn learned_dream_record_phase() {
let mut cycle = LearnedDreamCycle::new();
cycle.record_phase(0, true, 0.8, 100);
cycle.record_phase(0, true, 0.9, 120);
let eff = cycle.effectiveness(0).unwrap();
assert_eq!(eff.runs, 2);
assert_eq!(eff.useful_results, 2);
assert!((eff.avg_improvement - 0.85).abs() < 0.01);
}
#[test]
fn learned_dream_reorders_by_effectiveness() {
let mut cycle = LearnedDreamCycle::new();
for _ in 0..10 {
cycle.record_phase(5, true, 0.9, 50);
}
for _ in 0..10 {
cycle.record_phase(0, false, 0.1, 200);
}
let order = cycle.phase_order();
let pos5 = order.iter().position(|&x| x == 5).unwrap();
let pos0 = order.iter().position(|&x| x == 0).unwrap();
assert!(pos5 < pos0);
}
#[test]
fn learned_dream_filters_ineffective() {
let mut cycle = LearnedDreamCycle::with_config(0.5, 5, true);
for _ in 0..10 {
cycle.record_phase(3, false, 0.1, 200);
}
let to_run = cycle.phases_to_run();
assert!(!to_run.contains(&3));
}
#[test]
fn learned_dream_keeps_phases_without_data() {
let cycle = LearnedDreamCycle::new();
let to_run = cycle.phases_to_run();
assert_eq!(to_run.len(), 12);
}
#[test]
fn learned_dream_snapshot() {
let mut cycle = LearnedDreamCycle::new();
cycle.record_phase(0, true, 0.8, 100);
let snap = cycle.snapshot();
assert!(snap.get("phases").is_some());
}
#[test]
fn phase_effectiveness_score() {
let mut eff = PhaseEffectiveness::new();
assert!((eff.score() - 0.5).abs() < 0.01);
eff.record(true, 0.8, 100);
eff.record(true, 0.9, 120);
eff.record(false, 0.1, 50);
let score = eff.score();
assert!(score > 0.5);
}
#[test]
fn cycle_strategy_default_is_fixed() {
let strategy = LearnedCycleStrategy::new();
assert_eq!(strategy.strategy(), CycleStrategy::FixedOrder);
assert_eq!(strategy.priority_order().len(), 8);
}
#[test]
fn cycle_strategy_transitions_to_priority() {
let mut strategy = LearnedCycleStrategy::new();
for _ in 0..15 {
strategy.record_cycle(0, 2, 0.8, 100);
}
assert_eq!(strategy.strategy(), CycleStrategy::PriorityBased);
}
#[test]
fn cycle_strategy_priority_order() {
let mut strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::PriorityBased);
for _ in 0..10 {
strategy.record_cycle(3, 5, 0.9, 100);
}
for _ in 0..10 {
strategy.record_cycle(0, 0, 0.1, 200);
}
let order = strategy.priority_order();
assert_eq!(order[0], 3); }
#[test]
fn cycle_strategy_best_only() {
let mut strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::BestOnly);
for _ in 0..5 {
strategy.record_cycle(2, 3, 0.8, 100);
}
for _ in 0..5 {
strategy.record_cycle(5, 1, 0.3, 100);
}
strategy.set_strategy(CycleStrategy::Adaptive);
strategy.set_strategy(CycleStrategy::BestOnly);
let to_run = strategy.cycles_to_run();
assert_eq!(to_run.len(), 1);
assert_eq!(to_run[0], 2); }
#[test]
fn cycle_strategy_fixed_order_returns_all() {
let strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::FixedOrder);
let to_run = strategy.cycles_to_run();
assert_eq!(to_run.len(), 8);
}
#[test]
fn cycle_strategy_set_strategy() {
let mut strategy = LearnedCycleStrategy::new();
strategy.set_strategy(CycleStrategy::Adaptive);
assert_eq!(strategy.strategy(), CycleStrategy::Adaptive);
}
#[test]
fn cycle_strategy_snapshot() {
let mut strategy = LearnedCycleStrategy::new();
strategy.record_cycle(0, 2, 0.8, 100);
let snap = strategy.snapshot();
assert!(snap.get("strategy").is_some());
}
#[test]
fn cycle_effectiveness_score() {
let mut eff = CycleEffectiveness::new();
assert!((eff.score() - 0.5).abs() < 0.01);
eff.record(3, 0.8, 100);
eff.record(0, 0.2, 200);
let score = eff.score();
assert!(score > 0.5);
}
#[test]
fn gana_registry_serialization() {
let mut registry = GanaRegistry::new();
registry.record_usage(Gana::Horn, true);
registry.record_co_usage(Gana::Horn, Gana::Neck);
let json = serde_json::to_string(®istry).unwrap();
let mut back: GanaRegistry = serde_json::from_str(&json).unwrap();
back.rebuild_pairs();
assert_eq!(back.usage_count(Gana::Horn), 1);
assert_eq!(back.co_usage_count(Gana::Horn, Gana::Neck), 1);
}
#[test]
fn dynamic_galaxy_registry_serialization() {
let mut registry = DynamicGalaxyRegistry::new();
registry.try_create("Test", "desc", vec!["tag".to_string()], 15);
let json = serde_json::to_string(®istry).unwrap();
let back: DynamicGalaxyRegistry = serde_json::from_str(&json).unwrap();
assert_eq!(back.len(), 1);
}
#[test]
fn learned_dream_cycle_serialization() {
let mut cycle = LearnedDreamCycle::new();
cycle.record_phase(0, true, 0.8, 100);
let json = serde_json::to_string(&cycle).unwrap();
let back: LearnedDreamCycle = serde_json::from_str(&json).unwrap();
assert_eq!(back.effectiveness(0).unwrap().runs, 1);
}
#[test]
fn learned_cycle_strategy_serialization() {
let mut strategy = LearnedCycleStrategy::new();
strategy.record_cycle(0, 2, 0.8, 100);
let json = serde_json::to_string(&strategy).unwrap();
let back: LearnedCycleStrategy = serde_json::from_str(&json).unwrap();
assert_eq!(back.effectiveness(0).unwrap().runs, 1);
}
}