lean_ctx/core/buddy/
mod.rs1mod format;
2mod rpg;
3mod sprite;
4mod types;
5
6pub use format::{format_buddy_block, format_buddy_block_at, format_buddy_full};
7pub use sprite::render_sprite;
8pub use types::{BuddyState, BuddyStats, CreatureTraits, Mood, Rarity, Species};
9
10#[cfg(test)]
11mod tests {
12 use super::*;
13 use std::collections::HashMap;
14
15 #[test]
16 fn species_from_cargo_commands() {
17 let mut cmds = HashMap::new();
18 cmds.insert(
19 "cargo build".to_string(),
20 super::super::stats::CommandStats {
21 count: 50,
22 input_tokens: 1000,
23 output_tokens: 500,
24 },
25 );
26 assert_eq!(Species::from_commands(&cmds), Species::Crab);
27 }
28
29 #[test]
30 fn species_mixed_is_dragon() {
31 let mut cmds = HashMap::new();
32 cmds.insert(
33 "cargo build".to_string(),
34 super::super::stats::CommandStats {
35 count: 10,
36 input_tokens: 0,
37 output_tokens: 0,
38 },
39 );
40 cmds.insert(
41 "npm install".to_string(),
42 super::super::stats::CommandStats {
43 count: 10,
44 input_tokens: 0,
45 output_tokens: 0,
46 },
47 );
48 cmds.insert(
49 "python app.py".to_string(),
50 super::super::stats::CommandStats {
51 count: 10,
52 input_tokens: 0,
53 output_tokens: 0,
54 },
55 );
56 assert_eq!(Species::from_commands(&cmds), Species::Dragon);
57 }
58
59 #[test]
60 fn species_empty_is_egg() {
61 let cmds = HashMap::new();
62 assert_eq!(Species::from_commands(&cmds), Species::Egg);
63 }
64
65 #[test]
66 fn rarity_levels() {
67 assert_eq!(Rarity::from_tokens_saved(0), Rarity::Egg);
68 assert_eq!(Rarity::from_tokens_saved(5_000), Rarity::Egg);
69 assert_eq!(Rarity::from_tokens_saved(50_000), Rarity::Common);
70 assert_eq!(Rarity::from_tokens_saved(500_000), Rarity::Uncommon);
71 assert_eq!(Rarity::from_tokens_saved(5_000_000), Rarity::Rare);
72 assert_eq!(Rarity::from_tokens_saved(50_000_000), Rarity::Epic);
73 assert_eq!(Rarity::from_tokens_saved(500_000_000), Rarity::Legendary);
74 }
75
76 #[test]
77 fn name_is_deterministic() {
78 let s = types::user_seed();
79 let n1 = rpg::generate_name(s);
80 let n2 = rpg::generate_name(s);
81 assert_eq!(n1, n2);
82 }
83
84 #[test]
85 fn format_compact_values() {
86 assert_eq!(rpg::format_compact(500), "500");
87 assert_eq!(rpg::format_compact(1_500), "1.5K");
88 assert_eq!(rpg::format_compact(2_500_000), "2.5M");
89 assert_eq!(rpg::format_compact(3_000_000_000), "3.0B");
90 }
91
92 #[test]
93 fn procedural_sprite_returns_7_lines() {
94 for seed in [0u64, 1, 42, 999, 12345, 69_119_999, u64::MAX] {
95 let traits = CreatureTraits::from_seed(seed);
96 for mood in &[
97 Mood::Ecstatic,
98 Mood::Happy,
99 Mood::Content,
100 Mood::Worried,
101 Mood::Sleeping,
102 ] {
103 let sp = render_sprite(&traits, mood);
104 assert_eq!(sp.len(), 7, "sprite for seed={seed}, mood={mood:?}");
105 }
106 }
107 }
108
109 #[test]
110 fn creature_traits_are_deterministic() {
111 let t1 = CreatureTraits::from_seed(42);
112 let t2 = CreatureTraits::from_seed(42);
113 assert_eq!(t1.head, t2.head);
114 assert_eq!(t1.eyes, t2.eyes);
115 assert_eq!(t1.mouth, t2.mouth);
116 assert_eq!(t1.ears, t2.ears);
117 assert_eq!(t1.body, t2.body);
118 assert_eq!(t1.legs, t2.legs);
119 assert_eq!(t1.tail, t2.tail);
120 assert_eq!(t1.markings, t2.markings);
121 }
122
123 #[test]
124 fn different_seeds_produce_different_traits() {
125 let t1 = CreatureTraits::from_seed(1);
126 let t2 = CreatureTraits::from_seed(9999);
127 let same = t1.head == t2.head
128 && t1.eyes == t2.eyes
129 && t1.mouth == t2.mouth
130 && t1.ears == t2.ears
131 && t1.body == t2.body
132 && t1.legs == t2.legs
133 && t1.tail == t2.tail
134 && t1.markings == t2.markings;
135 assert!(
136 !same,
137 "seeds 1 and 9999 should differ in at least one trait"
138 );
139 }
140
141 #[test]
142 fn total_combinations_is_69m() {
143 assert_eq!(12u64 * 10 * 10 * 12 * 10 * 10 * 8 * 6, 69_120_000);
144 }
145
146 #[test]
147 fn xp_next_level_increases() {
148 let lv1 = (1u64 + 1) * (1 + 1) * 50;
149 let lv10 = (10u64 + 1) * (10 + 1) * 50;
150 assert!(lv10 > lv1);
151 }
152}