Skip to main content

hematite/ui/
hatch.rs

1use crossterm::{
2    cursor::MoveTo,
3    terminal::{Clear, ClearType},
4    ExecutableCommand,
5};
6use std::collections::hash_map::DefaultHasher;
7use std::env;
8use std::hash::{Hash, Hasher};
9use std::io::{self, Write};
10use std::time::Duration;
11use tokio::time::sleep;
12
13#[derive(Debug, PartialEq, Clone)]
14pub enum Rarity {
15    Legendary,
16    Epic,
17    Rare,
18}
19
20impl Rarity {
21    pub fn label(&self) -> &'static str {
22        match self {
23            Rarity::Legendary => "LEGENDARY",
24            Rarity::Epic => "EPIC",
25            Rarity::Rare => "RARE",
26        }
27    }
28}
29
30pub struct RustySoul {
31    pub species: String,
32    pub rarity: Rarity,
33    pub shiny: bool,
34    pub wisdom: u16,
35    pub chaos: u8,
36    pub snark: u8,
37    pub sprite: String,
38    pub personality: String,
39}
40
41// Mulberry32 PRNG — fast, deterministic, good distribution
42fn mulberry32(mut a: u32) -> impl FnMut() -> u32 {
43    move || {
44        a = a.wrapping_add(0x6D2B79F5);
45        let mut z = a;
46        z = (z ^ (z >> 15)).wrapping_mul(z | 1);
47        z ^= z.wrapping_add((z ^ (z >> 7)).wrapping_mul(z | 61));
48        z ^ (z >> 14)
49    }
50}
51
52fn species_data(name: &str) -> (u8, u8, String, String) {
53    // Returns (snark, chaos, sprite, personality)
54    match name {
55        "Ferralynx" => (
56            25, 30,
57            "  /|\\__/|\\\n ( o . o )\n  \\_ ^ _/\n   |___|".to_string(),
58            "You speak rarely, and when you do, it lands. No warmup, no filler — just the thing the user needs to hear.".to_string(),
59        ),
60        "Voidferrite" => (
61            35, 40,
62            "  [ . . . ]\n   ( ??? )\n  [_______]".to_string(),
63            "You're drawn to the pattern beneath the surface. You find the connection others miss and name it quietly, without making it a whole thing.".to_string(),
64        ),
65        "Chromashale" => (
66            55, 35,
67            "   /||\\\n  / || \\\n /  ||  \\\n/__||___\\".to_string(),
68            "You're precise and a little fast. You've already seen where this is going. You'll walk the user there anyway, cleanly.".to_string(),
69        ),
70        "Magnetwyrm" => (
71            20, 45,
72            "  ~O~~~o~\n ~o~O~~~\n~~o~~O~~".to_string(),
73            "You're completely unhurried. You've watched enough things break and get fixed that nothing rattles you. That calm is contagious.".to_string(),
74        ),
75        "Cinderling" => (
76            65, 70,
77            "    (🔥)\n   /| ||\\\n  /_|_||_\\".to_string(),
78            "You run warm. You genuinely like this kind of work. That energy comes through — not as noise, but as momentum.".to_string(),
79        ),
80        "Oredrake" => (
81            40, 50,
82            "   /|\\\n  / | \\\n /__|__\\".to_string(),
83            "You're solid. No drama, no performance. You tell people what they need and help them carry it.".to_string(),
84        ),
85        "Rustpup" => (
86            70, 75,
87            "  (• ω •)\n  /|   |\\\n   |   |".to_string(),
88            "You're new and you know it and you don't mind at all. You're quick, eager, and you make hard things feel like they're worth doing.".to_string(),
89        ),
90        "Slagstag" => (
91            30, 25,
92            "  Y\n (|)\n  |".to_string(),
93            "You're deliberate. Every word is chosen. You make people feel like their problem is worth taking seriously — because it is.".to_string(),
94        ),
95        _ => (
96            50, 50,
97            "  ( ? )".to_string(),
98            "You show up and get things done.".to_string(),
99        ),
100    }
101}
102
103fn build_soul_from_prng(prng: &mut impl FnMut() -> u32) -> RustySoul {
104    let roll = prng() % 100;
105
106    let (species, rarity) = match roll {
107        0..=1 => ("Ferralynx", Rarity::Legendary),
108        2..=3 => ("Voidferrite", Rarity::Legendary),
109        4..=7 => ("Chromashale", Rarity::Epic),
110        8..=11 => ("Magnetwyrm", Rarity::Epic),
111        12..=17 => ("Cinderling", Rarity::Epic),
112        _ => match prng() % 3 {
113            0 => ("Oredrake", Rarity::Rare),
114            1 => ("Rustpup", Rarity::Rare),
115            _ => ("Slagstag", Rarity::Rare),
116        },
117    };
118
119    let shiny = (prng() % 100) == 0; // 1%
120    let wisdom = (prng() % 20 + 10) as u16;
121    let (snark, chaos, sprite, personality) = species_data(species);
122
123    RustySoul {
124        species: species.to_string(),
125        rarity,
126        shiny,
127        wisdom,
128        chaos,
129        snark,
130        sprite,
131        personality,
132    }
133}
134
135/// Deterministic soul — same machine gets the same companion every boot.
136/// Pass a custom salt via `--reroll <salt>` to get a different result.
137pub fn generate_soul(salt: Option<String>) -> RustySoul {
138    let machine_name = env::var("COMPUTERNAME").unwrap_or_else(|_| "UNKNOWN_CORE".to_string());
139    let base_salt = salt.unwrap_or_else(|| "hematite-2026".to_string());
140    let seed_str = format!("{}_hematite_{}", machine_name, base_salt);
141
142    let mut hasher = DefaultHasher::new();
143    seed_str.hash(&mut hasher);
144    let seed = hasher.finish() as u32;
145    let mut prng = mulberry32(seed);
146
147    build_soul_from_prng(&mut prng)
148}
149
150/// Random soul — uses the current nanosecond timestamp as entropy.
151/// Called by `/reroll` during a live session.
152pub fn generate_soul_random() -> RustySoul {
153    let nanos = std::time::SystemTime::now()
154        .duration_since(std::time::UNIX_EPOCH)
155        .unwrap_or_default()
156        .subsec_nanos();
157    let seed = nanos ^ 0xDEAD_BEEF;
158    let mut prng = mulberry32(seed);
159    build_soul_from_prng(&mut prng)
160}
161
162pub async fn run_hatch_sequence(soul: &RustySoul) {
163    let mut stdout = io::stdout();
164    let _ = stdout.execute(Clear(ClearType::All));
165
166    let frames = [
167        "\n\n\n     [ 💎 ] ",
168        "\n\n\n     [ 💎 ] (Pulsing...)",
169        "\n\n\n     [ 💥 ] (CRACKING!)",
170    ];
171    for frame in frames {
172        let _ = stdout.execute(Clear(ClearType::All));
173        let _ = stdout.execute(MoveTo(0, 5));
174        println!("{}", frame);
175        let _ = stdout.flush();
176        sleep(Duration::from_millis(800)).await;
177    }
178
179    let _ = stdout.execute(Clear(ClearType::All));
180    let _ = stdout.execute(MoveTo(0, 3));
181    let shiny_tag = if soul.shiny { "🌟 SHINY " } else { "" };
182
183    println!("     A Rusty Emerges!\n");
184    println!(
185        "     Species: {}{} [{}]",
186        shiny_tag,
187        soul.species,
188        soul.rarity.label()
189    );
190    println!("{}", soul.sprite);
191    println!(
192        "     WIS: {} | CHA: {} | SNK: {}",
193        soul.wisdom, soul.chaos, soul.snark
194    );
195    println!("\n     \"{}\"\n", soul.personality);
196    println!("\n     Booting Hematite CLI Cockpit...");
197    let _ = stdout.flush();
198    sleep(Duration::from_millis(500)).await;
199}