1#![allow(clippy::significant_drop_tightening)]
11
12use crate::Gana;
13use serde::{Deserialize, Serialize};
14use std::collections::HashMap;
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
27pub struct GanaRegistry {
28 co_usage: HashMap<String, u64>,
30 #[serde(skip)]
32 co_usage_pairs: HashMap<(u8, u8), String>,
33 usage_counts: HashMap<u8, u64>,
35 success_rates: HashMap<u8, f32>,
37 drift_threshold: u64,
39 drift_enabled: bool,
41 suggested_merges: Vec<GanaMerge>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct GanaMerge {
48 pub gana_a: u8,
50 pub gana_b: u8,
52 pub co_usage_count: u64,
54 pub confidence: f32,
56}
57
58impl GanaRegistry {
59 #[must_use]
61 pub fn new() -> Self {
62 Self {
63 co_usage: HashMap::new(),
64 co_usage_pairs: HashMap::new(),
65 usage_counts: HashMap::new(),
66 success_rates: HashMap::new(),
67 drift_threshold: 100,
68 drift_enabled: true,
69 suggested_merges: Vec::new(),
70 }
71 }
72
73 #[must_use]
75 pub fn with_threshold(drift_threshold: u64) -> Self {
76 Self {
77 co_usage: HashMap::new(),
78 co_usage_pairs: HashMap::new(),
79 usage_counts: HashMap::new(),
80 success_rates: HashMap::new(),
81 drift_threshold,
82 drift_enabled: true,
83 suggested_merges: Vec::new(),
84 }
85 }
86
87 pub fn record_usage(&mut self, gana: Gana, success: bool) {
89 let idx = gana as u8;
90 *self.usage_counts.entry(idx).or_insert(0) += 1;
91
92 let current = self.success_rates.get(&idx).copied().unwrap_or(0.5);
94 let count = self.usage_counts[&idx];
95 let new_rate = if success {
96 current + (1.0 - current) / count as f32
97 } else {
98 current * (1.0 - 1.0 / count as f32)
99 };
100 self.success_rates.insert(idx, new_rate);
101 }
102
103 pub fn record_co_usage(&mut self, gana_a: Gana, gana_b: Gana) {
105 let (a, b) = if gana_a as u8 <= gana_b as u8 {
106 (gana_a as u8, gana_b as u8)
107 } else {
108 (gana_b as u8, gana_a as u8)
109 };
110 let key = format!("{a}:{b}");
111 self.co_usage_pairs.insert((a, b), key.clone());
112 *self.co_usage.entry(key).or_insert(0) += 1;
113
114 if self.drift_enabled {
116 let count = self.co_usage[&format!("{a}:{b}")];
117 if count == self.drift_threshold {
118 self.suggested_merges.push(GanaMerge {
119 gana_a: a,
120 gana_b: b,
121 co_usage_count: count,
122 confidence: 0.5,
123 });
124 } else if count > self.drift_threshold && count % self.drift_threshold == 0 {
125 if let Some(merge) = self
127 .suggested_merges
128 .iter_mut()
129 .find(|m| m.gana_a == a && m.gana_b == b)
130 {
131 merge.co_usage_count = count;
132 merge.confidence = (merge.confidence + 0.1).min(1.0);
133 }
134 }
135 }
136 }
137
138 #[must_use]
140 pub fn success_rate(&self, gana: Gana) -> f32 {
141 self.success_rates
142 .get(&(gana as u8))
143 .copied()
144 .unwrap_or(0.5)
145 }
146
147 #[must_use]
149 pub fn usage_count(&self, gana: Gana) -> u64 {
150 self.usage_counts.get(&(gana as u8)).copied().unwrap_or(0)
151 }
152
153 #[must_use]
155 pub const fn usage_counts(&self) -> &HashMap<u8, u64> {
156 &self.usage_counts
157 }
158
159 #[must_use]
161 pub const fn co_usage(&self) -> &HashMap<String, u64> {
162 &self.co_usage
163 }
164
165 #[must_use]
167 pub fn co_usage_count(&self, gana_a: Gana, gana_b: Gana) -> u64 {
168 let (a, b) = if gana_a as u8 <= gana_b as u8 {
169 (gana_a as u8, gana_b as u8)
170 } else {
171 (gana_b as u8, gana_a as u8)
172 };
173 self.co_usage_pairs
174 .get(&(a, b))
175 .and_then(|key| self.co_usage.get(key))
176 .copied()
177 .unwrap_or(0)
178 }
179
180 #[must_use]
182 pub fn suggested_merges(&self) -> &[GanaMerge] {
183 &self.suggested_merges
184 }
185
186 #[must_use]
188 pub fn analyze_drift(&self, top_n: usize) -> Vec<GanaMerge> {
189 let mut merges = self.suggested_merges.clone();
190 merges.sort_by_key(|x| std::cmp::Reverse(x.co_usage_count));
191 merges.truncate(top_n);
192 merges
193 }
194
195 pub fn rebuild_pairs(&mut self) {
200 self.co_usage_pairs.clear();
201 for key in self.co_usage.keys() {
202 let parts: Vec<&str> = key.split(':').collect();
203 if parts.len() == 2 {
204 if let (Ok(a), Ok(b)) = (parts[0].parse::<u8>(), parts[1].parse::<u8>()) {
205 self.co_usage_pairs.insert((a, b), key.clone());
206 }
207 }
208 }
209 }
210
211 pub fn clear(&mut self) {
213 self.co_usage.clear();
214 self.co_usage_pairs.clear();
215 self.usage_counts.clear();
216 self.success_rates.clear();
217 self.suggested_merges.clear();
218 }
219
220 #[must_use]
222 pub fn snapshot(&self) -> serde_json::Value {
223 serde_json::json!({
224 "total_ganas_tracked": self.usage_counts.len(),
225 "total_co_usage_pairs": self.co_usage.len(),
226 "suggested_merges": self.suggested_merges.len(),
227 "drift_threshold": self.drift_threshold,
228 "drift_enabled": self.drift_enabled,
229 })
230 }
231}
232
233impl Default for GanaRegistry {
234 fn default() -> Self {
235 Self::new()
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize)]
243pub struct DynamicGalaxy {
244 pub id: String,
246 pub name: String,
248 pub description: String,
250 pub cluster_tags: Vec<String>,
252 pub memory_count: usize,
254 pub created_at: u64,
256 pub effectiveness: f32,
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct DynamicGalaxyRegistry {
267 galaxies: HashMap<String, DynamicGalaxy>,
269 min_cluster_size: usize,
271 max_galaxies: usize,
273 prune_threshold: f32,
275}
276
277impl DynamicGalaxyRegistry {
278 #[must_use]
280 pub fn new() -> Self {
281 Self {
282 galaxies: HashMap::new(),
283 min_cluster_size: 10,
284 max_galaxies: 20,
285 prune_threshold: 0.1,
286 }
287 }
288
289 #[must_use]
291 pub fn with_config(min_cluster_size: usize, max_galaxies: usize, prune_threshold: f32) -> Self {
292 Self {
293 galaxies: HashMap::new(),
294 min_cluster_size,
295 max_galaxies,
296 prune_threshold,
297 }
298 }
299
300 pub fn try_create(
305 &mut self,
306 name: &str,
307 description: &str,
308 cluster_tags: Vec<String>,
309 memory_count: usize,
310 ) -> Option<&DynamicGalaxy> {
311 if memory_count < self.min_cluster_size {
312 return None;
313 }
314
315 if self.galaxies.len() >= self.max_galaxies {
316 self.prune();
318 if self.galaxies.len() >= self.max_galaxies {
319 return None;
320 }
321 }
322
323 let id = format!("dyn_{}", name.to_lowercase().replace(' ', "_"));
324 if self.galaxies.contains_key(&id) {
325 if let Some(g) = self.galaxies.get_mut(&id) {
327 g.memory_count = memory_count;
328 }
329 return self.galaxies.get(&id);
330 }
331
332 let timestamp = std::time::SystemTime::now()
333 .duration_since(std::time::UNIX_EPOCH)
334 .map_or(0, |d| d.as_secs());
335
336 let galaxy = DynamicGalaxy {
337 id: id.clone(),
338 name: name.to_string(),
339 description: description.to_string(),
340 cluster_tags,
341 memory_count,
342 created_at: timestamp,
343 effectiveness: 0.5,
344 };
345
346 self.galaxies.insert(id.clone(), galaxy);
347 self.galaxies.get(&id)
348 }
349
350 #[must_use]
352 pub fn get(&self, id: &str) -> Option<&DynamicGalaxy> {
353 self.galaxies.get(id)
354 }
355
356 #[must_use]
358 pub fn all(&self) -> Vec<&DynamicGalaxy> {
359 self.galaxies.values().collect()
360 }
361
362 pub fn update_effectiveness(&mut self, id: &str, effectiveness: f32) {
364 if let Some(g) = self.galaxies.get_mut(id) {
365 g.effectiveness = effectiveness;
366 }
367 }
368
369 pub fn prune(&mut self) -> usize {
373 let before = self.galaxies.len();
374 self.galaxies
375 .retain(|_, g| g.effectiveness >= self.prune_threshold);
376 before - self.galaxies.len()
377 }
378
379 #[must_use]
381 pub fn len(&self) -> usize {
382 self.galaxies.len()
383 }
384
385 #[must_use]
387 pub fn galaxy_count(&self) -> usize {
388 self.galaxies.len()
389 }
390
391 #[must_use]
393 pub fn is_empty(&self) -> bool {
394 self.galaxies.is_empty()
395 }
396}
397
398impl Default for DynamicGalaxyRegistry {
399 fn default() -> Self {
400 Self::new()
401 }
402}
403
404#[derive(Debug, Clone, Serialize, Deserialize)]
408pub struct PhaseEffectiveness {
409 pub runs: u64,
411 pub useful_results: u64,
413 pub avg_improvement: f32,
415 pub avg_duration_ms: u64,
417}
418
419impl PhaseEffectiveness {
420 #[must_use]
422 pub const fn new() -> Self {
423 Self {
424 runs: 0,
425 useful_results: 0,
426 avg_improvement: 0.0,
427 avg_duration_ms: 0,
428 }
429 }
430
431 #[must_use]
433 pub fn score(&self) -> f32 {
434 if self.runs == 0 {
435 return 0.5;
436 }
437 let success_rate = self.useful_results as f32 / self.runs as f32;
438 success_rate.midpoint(self.avg_improvement)
439 }
440
441 pub fn record(&mut self, useful: bool, improvement: f32, duration_ms: u64) {
443 let n = self.runs as f32;
444 self.avg_improvement = self.avg_improvement.mul_add(n, improvement) / (n + 1.0);
445 self.avg_duration_ms =
446 ((self.avg_duration_ms as f32).mul_add(n, duration_ms as f32) / (n + 1.0)) as u64;
447 self.runs += 1;
448 if useful {
449 self.useful_results += 1;
450 }
451 }
452}
453
454impl Default for PhaseEffectiveness {
455 fn default() -> Self {
456 Self::new()
457 }
458}
459
460#[derive(Debug, Clone, Serialize, Deserialize)]
468pub struct LearnedDreamCycle {
469 phase_effectiveness: HashMap<u8, PhaseEffectiveness>,
471 min_effectiveness: f32,
473 min_runs: u64,
475 learning_enabled: bool,
477 phase_order: Vec<u8>,
479}
480
481impl LearnedDreamCycle {
482 #[must_use]
484 pub fn new() -> Self {
485 let default_order: Vec<u8> = (0..12u8).collect();
486 Self {
487 phase_effectiveness: HashMap::new(),
488 min_effectiveness: 0.2,
489 min_runs: 5,
490 learning_enabled: true,
491 phase_order: default_order,
492 }
493 }
494
495 #[must_use]
497 pub fn with_config(min_effectiveness: f32, min_runs: u64, learning_enabled: bool) -> Self {
498 Self {
499 phase_effectiveness: HashMap::new(),
500 min_effectiveness,
501 min_runs,
502 learning_enabled,
503 phase_order: (0..12u8).collect(),
504 }
505 }
506
507 pub fn record_phase(
509 &mut self,
510 phase_idx: u8,
511 useful: bool,
512 improvement: f32,
513 duration_ms: u64,
514 ) {
515 let record = self.phase_effectiveness.entry(phase_idx).or_default();
516 record.record(useful, improvement, duration_ms);
517
518 if self.learning_enabled {
520 self.update_phase_order();
521 }
522 }
523
524 #[must_use]
526 pub fn phase_order(&self) -> &[u8] {
527 &self.phase_order
528 }
529
530 #[must_use]
532 pub fn phases_to_run(&self) -> Vec<u8> {
533 self.phase_order
534 .iter()
535 .filter(|&&idx| {
536 if let Some(eff) = self.phase_effectiveness.get(&idx) {
537 if eff.runs >= self.min_runs {
538 return eff.score() >= self.min_effectiveness;
539 }
540 }
541 true })
543 .copied()
544 .collect()
545 }
546
547 #[must_use]
549 pub fn effectiveness(&self, phase_idx: u8) -> Option<&PhaseEffectiveness> {
550 self.phase_effectiveness.get(&phase_idx)
551 }
552
553 fn update_phase_order(&mut self) {
555 let mut scored: Vec<(u8, f32)> = (0..12u8)
556 .map(|idx| {
557 let score = self
558 .phase_effectiveness
559 .get(&idx)
560 .map_or(0.5, PhaseEffectiveness::score);
561 (idx, score)
562 })
563 .collect();
564 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
565 self.phase_order = scored.into_iter().map(|(idx, _)| idx).collect();
566 }
567
568 #[must_use]
570 pub fn snapshot(&self) -> serde_json::Value {
571 let phases: Vec<serde_json::Value> = (0..12u8)
572 .map(|idx| {
573 if let Some(eff) = self.phase_effectiveness.get(&idx) {
574 serde_json::json!({
575 "phase": idx,
576 "runs": eff.runs,
577 "useful": eff.useful_results,
578 "score": eff.score(),
579 "avg_improvement": eff.avg_improvement,
580 "avg_duration_ms": eff.avg_duration_ms,
581 })
582 } else {
583 serde_json::json!({"phase": idx, "runs": 0})
584 }
585 })
586 .collect();
587
588 serde_json::json!({
589 "phases": phases,
590 "phase_order": self.phase_order,
591 "phases_to_run": self.phases_to_run(),
592 "min_effectiveness": self.min_effectiveness,
593 "learning_enabled": self.learning_enabled,
594 })
595 }
596}
597
598impl Default for LearnedDreamCycle {
599 fn default() -> Self {
600 Self::new()
601 }
602}
603
604#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
608pub enum CycleStrategy {
609 FixedOrder,
611 PriorityBased,
613 BestOnly,
615 Adaptive,
617}
618
619#[derive(Debug, Clone, Serialize, Deserialize)]
621pub struct CycleEffectiveness {
622 pub runs: u64,
624 pub proposals_generated: u64,
626 pub avg_usefulness: f32,
628 pub avg_duration_ms: u64,
630}
631
632impl CycleEffectiveness {
633 #[must_use]
635 pub const fn new() -> Self {
636 Self {
637 runs: 0,
638 proposals_generated: 0,
639 avg_usefulness: 0.0,
640 avg_duration_ms: 0,
641 }
642 }
643
644 #[must_use]
646 pub fn score(&self) -> f32 {
647 if self.runs == 0 {
648 return 0.5;
649 }
650 let proposal_rate = self.proposals_generated as f32 / self.runs as f32;
651 proposal_rate.midpoint(self.avg_usefulness)
652 }
653
654 pub fn record(&mut self, proposals: u64, usefulness: f32, duration_ms: u64) {
656 let n = self.runs as f32;
657 self.avg_usefulness = self.avg_usefulness.mul_add(n, usefulness) / (n + 1.0);
658 self.avg_duration_ms =
659 ((self.avg_duration_ms as f32).mul_add(n, duration_ms as f32) / (n + 1.0)) as u64;
660 self.runs += 1;
661 self.proposals_generated += proposals;
662 }
663}
664
665impl Default for CycleEffectiveness {
666 fn default() -> Self {
667 Self::new()
668 }
669}
670
671#[derive(Debug, Clone, Serialize, Deserialize)]
676pub struct LearnedCycleStrategy {
677 cycle_effectiveness: HashMap<u8, CycleEffectiveness>,
679 strategy: CycleStrategy,
681 exploration_rate: f32,
683 min_runs: u64,
685 priority_order: Vec<u8>,
687}
688
689impl LearnedCycleStrategy {
690 #[must_use]
692 pub fn new() -> Self {
693 Self {
694 cycle_effectiveness: HashMap::new(),
695 strategy: CycleStrategy::FixedOrder,
696 exploration_rate: 0.1,
697 min_runs: 10,
698 priority_order: (0..8u8).collect(),
699 }
700 }
701
702 #[must_use]
704 pub fn with_strategy(strategy: CycleStrategy) -> Self {
705 Self {
706 cycle_effectiveness: HashMap::new(),
707 strategy,
708 exploration_rate: 0.1,
709 min_runs: 10,
710 priority_order: (0..8u8).collect(),
711 }
712 }
713
714 pub fn record_cycle(
716 &mut self,
717 cycle_type_idx: u8,
718 proposals: u64,
719 usefulness: f32,
720 duration_ms: u64,
721 ) {
722 let record = self.cycle_effectiveness.entry(cycle_type_idx).or_default();
723 record.record(proposals, usefulness, duration_ms);
724
725 if self.strategy == CycleStrategy::FixedOrder {
727 let total_runs: u64 = self.cycle_effectiveness.values().map(|e| e.runs).sum();
728 if total_runs >= self.min_runs {
729 self.strategy = CycleStrategy::PriorityBased;
730 self.update_priority_order();
731 }
732 } else if matches!(
733 self.strategy,
734 CycleStrategy::PriorityBased | CycleStrategy::Adaptive
735 ) {
736 self.update_priority_order();
737 }
738 }
739
740 #[must_use]
742 pub const fn strategy(&self) -> CycleStrategy {
743 self.strategy
744 }
745
746 #[must_use]
748 pub fn priority_order(&self) -> &[u8] {
749 &self.priority_order
750 }
751
752 #[must_use]
759 pub fn cycles_to_run(&self) -> Vec<u8> {
760 match self.strategy {
761 CycleStrategy::FixedOrder => (0..8u8).collect(),
762 CycleStrategy::PriorityBased | CycleStrategy::Adaptive => {
763 if self.strategy == CycleStrategy::Adaptive {
764 }
767 self.priority_order.clone()
768 }
769 CycleStrategy::BestOnly => self.priority_order.first().copied().into_iter().collect(),
770 }
771 }
772
773 #[must_use]
775 pub fn effectiveness(&self, cycle_type_idx: u8) -> Option<&CycleEffectiveness> {
776 self.cycle_effectiveness.get(&cycle_type_idx)
777 }
778
779 pub fn set_strategy(&mut self, strategy: CycleStrategy) {
781 self.strategy = strategy;
782 if matches!(
783 strategy,
784 CycleStrategy::PriorityBased | CycleStrategy::Adaptive
785 ) {
786 self.update_priority_order();
787 }
788 }
789
790 fn update_priority_order(&mut self) {
792 let mut scored: Vec<(u8, f32)> = (0..8u8)
793 .map(|idx| {
794 let score = self
795 .cycle_effectiveness
796 .get(&idx)
797 .map_or(0.5, CycleEffectiveness::score);
798 (idx, score)
799 })
800 .collect();
801 scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
802 self.priority_order = scored.into_iter().map(|(idx, _)| idx).collect();
803 }
804
805 #[must_use]
807 pub fn snapshot(&self) -> serde_json::Value {
808 let cycles: Vec<serde_json::Value> = (0..8u8)
809 .map(|idx| {
810 if let Some(eff) = self.cycle_effectiveness.get(&idx) {
811 serde_json::json!({
812 "cycle_type": idx,
813 "runs": eff.runs,
814 "proposals": eff.proposals_generated,
815 "score": eff.score(),
816 "avg_usefulness": eff.avg_usefulness,
817 })
818 } else {
819 serde_json::json!({"cycle_type": idx, "runs": 0})
820 }
821 })
822 .collect();
823
824 serde_json::json!({
825 "strategy": format!("{:?}", self.strategy),
826 "cycles": cycles,
827 "priority_order": self.priority_order,
828 "cycles_to_run": self.cycles_to_run(),
829 "exploration_rate": self.exploration_rate,
830 })
831 }
832}
833
834impl Default for LearnedCycleStrategy {
835 fn default() -> Self {
836 Self::new()
837 }
838}
839
840#[cfg(test)]
843mod tests {
844 use super::*;
845
846 #[test]
849 fn gana_registry_record_usage() {
850 let mut registry = GanaRegistry::new();
851 registry.record_usage(Gana::Horn, true);
852 registry.record_usage(Gana::Horn, true);
853 registry.record_usage(Gana::Horn, false);
854
855 assert_eq!(registry.usage_count(Gana::Horn), 3);
856 let rate = registry.success_rate(Gana::Horn);
857 assert!(rate > 0.5); }
859
860 #[test]
861 fn gana_registry_co_usage() {
862 let mut registry = GanaRegistry::new();
863 registry.record_co_usage(Gana::Horn, Gana::Encampment);
864 registry.record_co_usage(Gana::Horn, Gana::Encampment);
865 registry.record_co_usage(Gana::Horn, Gana::Encampment);
866
867 assert_eq!(registry.co_usage_count(Gana::Horn, Gana::Encampment), 3);
868 assert_eq!(registry.co_usage_count(Gana::Encampment, Gana::Horn), 3);
870 }
871
872 #[test]
873 fn gana_registry_drift_suggestion() {
874 let mut registry = GanaRegistry::with_threshold(5);
875 for _ in 0..5 {
876 registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
877 }
878
879 let merges = registry.suggested_merges();
880 assert_eq!(merges.len(), 1);
881 assert_eq!(merges[0].gana_a, Gana::Horn as u8);
882 assert_eq!(merges[0].gana_b, Gana::WinnowingBasket as u8);
883 }
884
885 #[test]
886 fn gana_registry_drift_confidence_increases() {
887 let mut registry = GanaRegistry::with_threshold(5);
888 for _ in 0..10 {
889 registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
890 }
891
892 let merges = registry.suggested_merges();
893 assert_eq!(merges.len(), 1);
894 assert!(merges[0].confidence > 0.5);
895 }
896
897 #[test]
898 fn gana_registry_analyze_drift() {
899 let mut registry = GanaRegistry::with_threshold(3);
900 for _ in 0..5 {
901 registry.record_co_usage(Gana::Horn, Gana::WinnowingBasket);
902 }
903 for _ in 0..3 {
904 registry.record_co_usage(Gana::Ghost, Gana::Star);
905 }
906
907 let top = registry.analyze_drift(2);
908 assert_eq!(top.len(), 2);
909 assert!(top[0].co_usage_count >= top[1].co_usage_count);
911 }
912
913 #[test]
914 fn gana_registry_clear() {
915 let mut registry = GanaRegistry::new();
916 registry.record_usage(Gana::Horn, true);
917 registry.record_co_usage(Gana::Horn, Gana::Neck);
918 registry.clear();
919
920 assert_eq!(registry.usage_count(Gana::Horn), 0);
921 assert_eq!(registry.co_usage_count(Gana::Horn, Gana::Neck), 0);
922 }
923
924 #[test]
925 fn gana_registry_snapshot() {
926 let mut registry = GanaRegistry::new();
927 registry.record_usage(Gana::Horn, true);
928 let snap = registry.snapshot();
929 assert!(snap.get("total_ganas_tracked").is_some());
930 }
931
932 #[test]
935 fn dynamic_galaxy_create() {
936 let mut registry = DynamicGalaxyRegistry::with_config(5, 10, 0.1);
937 let galaxy = registry.try_create(
938 "Rust Patterns",
939 "Memories about Rust design patterns",
940 vec!["rust".to_string(), "patterns".to_string()],
941 15,
942 );
943 assert!(galaxy.is_some());
944 assert_eq!(galaxy.unwrap().name, "Rust Patterns");
945 assert_eq!(registry.len(), 1);
946 }
947
948 #[test]
949 fn dynamic_galaxy_too_small() {
950 let mut registry = DynamicGalaxyRegistry::with_config(10, 5, 0.1);
951 let galaxy = registry.try_create("Small", "Too small", vec![], 3);
952 assert!(galaxy.is_none());
953 assert!(registry.is_empty());
954 }
955
956 #[test]
957 fn dynamic_galaxy_max_limit() {
958 let mut registry = DynamicGalaxyRegistry::with_config(1, 2, 0.0);
959 registry.try_create("G1", "desc", vec![], 5);
960 registry.try_create("G2", "desc", vec![], 5);
961 registry.try_create("G3", "desc", vec![], 5);
962 assert_eq!(registry.len(), 2); }
964
965 #[test]
966 fn dynamic_galaxy_prune() {
967 let mut registry = DynamicGalaxyRegistry::with_config(1, 10, 0.5);
968 registry.try_create("G1", "desc", vec![], 5);
969 registry.try_create("G2", "desc", vec![], 5);
970 registry.update_effectiveness("dyn_g1", 0.1); registry.update_effectiveness("dyn_g2", 0.8); let pruned = registry.prune();
974 assert_eq!(pruned, 1);
975 assert_eq!(registry.len(), 1);
976 assert!(registry.get("dyn_g2").is_some());
977 }
978
979 #[test]
980 fn dynamic_galaxy_update_existing() {
981 let mut registry = DynamicGalaxyRegistry::with_config(1, 10, 0.0);
982 registry.try_create("Test", "desc", vec![], 5);
983 registry.try_create("Test", "desc", vec![], 10);
984 let g = registry.get("dyn_test").unwrap();
985 assert_eq!(g.memory_count, 10);
986 assert_eq!(registry.len(), 1);
987 }
988
989 #[test]
992 fn learned_dream_default_order() {
993 let cycle = LearnedDreamCycle::new();
994 assert_eq!(cycle.phase_order(), &[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]);
995 }
996
997 #[test]
998 fn learned_dream_record_phase() {
999 let mut cycle = LearnedDreamCycle::new();
1000 cycle.record_phase(0, true, 0.8, 100);
1001 cycle.record_phase(0, true, 0.9, 120);
1002
1003 let eff = cycle.effectiveness(0).unwrap();
1004 assert_eq!(eff.runs, 2);
1005 assert_eq!(eff.useful_results, 2);
1006 assert!((eff.avg_improvement - 0.85).abs() < 0.01);
1007 }
1008
1009 #[test]
1010 fn learned_dream_reorders_by_effectiveness() {
1011 let mut cycle = LearnedDreamCycle::new();
1012
1013 for _ in 0..10 {
1015 cycle.record_phase(5, true, 0.9, 50);
1016 }
1017
1018 for _ in 0..10 {
1020 cycle.record_phase(0, false, 0.1, 200);
1021 }
1022
1023 let order = cycle.phase_order();
1024 let pos5 = order.iter().position(|&x| x == 5).unwrap();
1026 let pos0 = order.iter().position(|&x| x == 0).unwrap();
1027 assert!(pos5 < pos0);
1028 }
1029
1030 #[test]
1031 fn learned_dream_filters_ineffective() {
1032 let mut cycle = LearnedDreamCycle::with_config(0.5, 5, true);
1033
1034 for _ in 0..10 {
1036 cycle.record_phase(3, false, 0.1, 200);
1037 }
1038
1039 let to_run = cycle.phases_to_run();
1040 assert!(!to_run.contains(&3));
1042 }
1043
1044 #[test]
1045 fn learned_dream_keeps_phases_without_data() {
1046 let cycle = LearnedDreamCycle::new();
1047 let to_run = cycle.phases_to_run();
1048 assert_eq!(to_run.len(), 12);
1050 }
1051
1052 #[test]
1053 fn learned_dream_snapshot() {
1054 let mut cycle = LearnedDreamCycle::new();
1055 cycle.record_phase(0, true, 0.8, 100);
1056 let snap = cycle.snapshot();
1057 assert!(snap.get("phases").is_some());
1058 }
1059
1060 #[test]
1063 fn phase_effectiveness_score() {
1064 let mut eff = PhaseEffectiveness::new();
1065 assert!((eff.score() - 0.5).abs() < 0.01); eff.record(true, 0.8, 100);
1068 eff.record(true, 0.9, 120);
1069 eff.record(false, 0.1, 50);
1070
1071 let score = eff.score();
1074 assert!(score > 0.5);
1075 }
1076
1077 #[test]
1080 fn cycle_strategy_default_is_fixed() {
1081 let strategy = LearnedCycleStrategy::new();
1082 assert_eq!(strategy.strategy(), CycleStrategy::FixedOrder);
1083 assert_eq!(strategy.priority_order().len(), 8);
1084 }
1085
1086 #[test]
1087 fn cycle_strategy_transitions_to_priority() {
1088 let mut strategy = LearnedCycleStrategy::new();
1089 for _ in 0..15 {
1091 strategy.record_cycle(0, 2, 0.8, 100);
1092 }
1093 assert_eq!(strategy.strategy(), CycleStrategy::PriorityBased);
1094 }
1095
1096 #[test]
1097 fn cycle_strategy_priority_order() {
1098 let mut strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::PriorityBased);
1099
1100 for _ in 0..10 {
1102 strategy.record_cycle(3, 5, 0.9, 100);
1103 }
1104 for _ in 0..10 {
1106 strategy.record_cycle(0, 0, 0.1, 200);
1107 }
1108
1109 let order = strategy.priority_order();
1110 assert_eq!(order[0], 3); }
1112
1113 #[test]
1114 fn cycle_strategy_best_only() {
1115 let mut strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::BestOnly);
1116
1117 for _ in 0..5 {
1118 strategy.record_cycle(2, 3, 0.8, 100);
1119 }
1120 for _ in 0..5 {
1121 strategy.record_cycle(5, 1, 0.3, 100);
1122 }
1123
1124 strategy.set_strategy(CycleStrategy::Adaptive);
1127 strategy.set_strategy(CycleStrategy::BestOnly);
1128
1129 let to_run = strategy.cycles_to_run();
1130 assert_eq!(to_run.len(), 1);
1131 assert_eq!(to_run[0], 2); }
1133
1134 #[test]
1135 fn cycle_strategy_fixed_order_returns_all() {
1136 let strategy = LearnedCycleStrategy::with_strategy(CycleStrategy::FixedOrder);
1137 let to_run = strategy.cycles_to_run();
1138 assert_eq!(to_run.len(), 8);
1139 }
1140
1141 #[test]
1142 fn cycle_strategy_set_strategy() {
1143 let mut strategy = LearnedCycleStrategy::new();
1144 strategy.set_strategy(CycleStrategy::Adaptive);
1145 assert_eq!(strategy.strategy(), CycleStrategy::Adaptive);
1146 }
1147
1148 #[test]
1149 fn cycle_strategy_snapshot() {
1150 let mut strategy = LearnedCycleStrategy::new();
1151 strategy.record_cycle(0, 2, 0.8, 100);
1152 let snap = strategy.snapshot();
1153 assert!(snap.get("strategy").is_some());
1154 }
1155
1156 #[test]
1157 fn cycle_effectiveness_score() {
1158 let mut eff = CycleEffectiveness::new();
1159 assert!((eff.score() - 0.5).abs() < 0.01);
1160
1161 eff.record(3, 0.8, 100);
1162 eff.record(0, 0.2, 200);
1163
1164 let score = eff.score();
1165 assert!(score > 0.5);
1168 }
1169
1170 #[test]
1173 fn gana_registry_serialization() {
1174 let mut registry = GanaRegistry::new();
1175 registry.record_usage(Gana::Horn, true);
1176 registry.record_co_usage(Gana::Horn, Gana::Neck);
1177
1178 let json = serde_json::to_string(®istry).unwrap();
1179 let mut back: GanaRegistry = serde_json::from_str(&json).unwrap();
1180 back.rebuild_pairs();
1181 assert_eq!(back.usage_count(Gana::Horn), 1);
1182 assert_eq!(back.co_usage_count(Gana::Horn, Gana::Neck), 1);
1183 }
1184
1185 #[test]
1186 fn dynamic_galaxy_registry_serialization() {
1187 let mut registry = DynamicGalaxyRegistry::new();
1188 registry.try_create("Test", "desc", vec!["tag".to_string()], 15);
1189
1190 let json = serde_json::to_string(®istry).unwrap();
1191 let back: DynamicGalaxyRegistry = serde_json::from_str(&json).unwrap();
1192 assert_eq!(back.len(), 1);
1193 }
1194
1195 #[test]
1196 fn learned_dream_cycle_serialization() {
1197 let mut cycle = LearnedDreamCycle::new();
1198 cycle.record_phase(0, true, 0.8, 100);
1199
1200 let json = serde_json::to_string(&cycle).unwrap();
1201 let back: LearnedDreamCycle = serde_json::from_str(&json).unwrap();
1202 assert_eq!(back.effectiveness(0).unwrap().runs, 1);
1203 }
1204
1205 #[test]
1206 fn learned_cycle_strategy_serialization() {
1207 let mut strategy = LearnedCycleStrategy::new();
1208 strategy.record_cycle(0, 2, 0.8, 100);
1209
1210 let json = serde_json::to_string(&strategy).unwrap();
1211 let back: LearnedCycleStrategy = serde_json::from_str(&json).unwrap();
1212 assert_eq!(back.effectiveness(0).unwrap().runs, 1);
1213 }
1214}