Skip to main content

heatsync_core/
mock.rs

1//! deterministic mock feed — proves the aesthetic before the relay WS exists.
2//! no rand crate, no real clock: a seeded xorshift + a synthetic ms clock
3//! advanced each tick. heat emerges from the same decaying-counter model the
4//! live feed uses, so the mock and the real thing behave identically.
5
6use crate::{Channel, Message, Platform};
7
8struct Rng(u64);
9impl Rng {
10    fn next(&mut self) -> u64 {
11        let mut x = self.0;
12        x ^= x << 13;
13        x ^= x >> 7;
14        x ^= x << 17;
15        self.0 = x;
16        x
17    }
18    fn below(&mut self, n: u64) -> u64 {
19        self.next() % n.max(1)
20    }
21    fn pick<'a, T>(&mut self, xs: &'a [T]) -> &'a T {
22        &xs[self.below(xs.len() as u64) as usize]
23    }
24    fn unit(&mut self) -> f64 {
25        (self.next() >> 11) as f64 / (1u64 << 53) as f64
26    }
27}
28
29const USERS: &[&str] = &[
30    "forsen",
31    "nymn",
32    "pajlada",
33    "supibot",
34    "mel",
35    "kkona",
36    "wide",
37    "peeposit",
38    "gigachad",
39    "monkas",
40    "widepeepo",
41    "hasanabi",
42    "vedal",
43    "neuro",
44    "anon",
45];
46
47const WORDS: &[&str] = &[
48    "GAMBA",
49    "OMEGALUL",
50    "Sadge",
51    "PepeLaugh",
52    "gigachad moment",
53    "actual clip",
54    "no way",
55    "chat is this real",
56    "COPIUM",
57    "based",
58    "ratio",
59    "Nerd",
60    "KEKW",
61    "this is peak",
62    "mods asleep",
63    "PogU",
64    "sptvJAM",
65    "Aware",
66];
67
68/// per-channel baseline messages/tick — spreads channels across the ramp.
69struct Seed {
70    name: &'static str,
71    platform: Platform,
72    activity: f64,
73}
74
75const SEEDS: &[Seed] = &[
76    Seed {
77        name: "xqc",
78        platform: Platform::Twitch,
79        activity: 7.0,
80    },
81    Seed {
82        name: "forsen",
83        platform: Platform::Twitch,
84        activity: 2.5,
85    },
86    Seed {
87        name: "hasanabi",
88        platform: Platform::Twitch,
89        activity: 1.0,
90    },
91    Seed {
92        name: "trainwreckstv",
93        platform: Platform::Kick,
94        activity: 11.0,
95    },
96    Seed {
97        name: "adin",
98        platform: Platform::Kick,
99        activity: 0.4,
100    },
101];
102
103/// the demo channel set — same shape the live feed produces.
104pub fn channels() -> Vec<Channel> {
105    SEEDS
106        .iter()
107        .map(|s| Channel::new(s.name, s.platform, 200))
108        .collect()
109}
110
111/// drives the demo channels with a synthetic clock — no real time, no rand crate.
112pub struct Driver {
113    rng: Rng,
114    clock_ms: u64,
115    activity: Vec<f64>,
116}
117
118impl Default for Driver {
119    fn default() -> Driver {
120        Driver::new()
121    }
122}
123
124impl Driver {
125    pub fn new() -> Driver {
126        Driver {
127            rng: Rng(0x9E3779B97F4A7C15),
128            clock_ms: 1,
129            activity: SEEDS.iter().map(|s| s.activity).collect(),
130        }
131    }
132
133    /// one 350ms tick: advance the clock, cool every channel, then emit a
134    /// poisson-ish burst per channel scaled by its activity (rare 6× spikes).
135    pub fn tick(&mut self, channels: &mut [Channel]) {
136        self.clock_ms += 350;
137        let now = self.clock_ms;
138        for (i, ch) in channels.iter_mut().enumerate() {
139            ch.cool(now);
140            let spike = self.rng.unit() < 0.03;
141            let mult = if spike { 6.0 } else { 1.0 };
142            let mut budget = self.activity.get(i).copied().unwrap_or(1.0) * mult;
143            while budget > 0.0 {
144                if self.rng.unit() < budget.min(1.0) {
145                    let user = self.rng.pick(USERS).to_string();
146                    let text = self.rng.pick(WORDS).to_string();
147                    ch.record(
148                        Message {
149                            user,
150                            text,
151                            color: None,
152                            heat: 0.0,
153                        },
154                        now,
155                    );
156                }
157                budget -= 1.0;
158            }
159        }
160    }
161}