microscope_memory/
mental_stimulation.rs1use std::collections::HashMap;
5use std::fs;
6use std::path::Path;
7
8#[derive(Clone, Debug)]
9pub struct ActivityRecord {
10 pub timestamp_ms: u64,
11 pub activity_type: String, pub intensity: f32, pub engagement_gain: f32,
14}
15
16pub struct MentalStimulation {
17 pub activity_history: Vec<ActivityRecord>,
18 pub engagement_level: f32, pub decay_rate: f32, pub last_activity_ms: u64,
21 pub stimulation_need: f32, pub novelty_threshold: f32, }
24
25impl Default for MentalStimulation {
26 fn default() -> Self {
27 Self::new()
28 }
29}
30
31impl MentalStimulation {
32 pub fn new() -> Self {
33 Self {
34 activity_history: Vec::new(),
35 engagement_level: 0.8,
36 decay_rate: 0.001, last_activity_ms: Self::now_ms(),
38 stimulation_need: 0.0,
39 novelty_threshold: 0.3,
40 }
41 }
42
43 pub fn record_activity(&mut self, activity_type: &str, intensity: f32) {
45 let now = Self::now_ms();
46
47 self.apply_decay(now);
49
50 let engagement_gain = intensity * 0.2; self.engagement_level = (self.engagement_level + engagement_gain).min(1.0);
53
54 self.activity_history.push(ActivityRecord {
55 timestamp_ms: now,
56 activity_type: activity_type.to_string(),
57 intensity,
58 engagement_gain,
59 });
60
61 if self.activity_history.len() > 1000 {
62 self.activity_history.remove(0);
63 }
64
65 self.last_activity_ms = now;
66 self.stimulation_need = 0.0;
67 }
68
69 pub fn apply_decay(&mut self, current_ms: u64) {
71 let time_since_activity_ms = current_ms.saturating_sub(self.last_activity_ms);
72 let decay_amount = self.decay_rate * (time_since_activity_ms as f32 / 1000.0);
73
74 self.engagement_level = (self.engagement_level - decay_amount).max(0.0);
75 self.stimulation_need = (1.0 - self.engagement_level).max(0.0);
76 }
77
78 pub fn needs_stimulation(&mut self) -> bool {
80 let now = Self::now_ms();
81 self.apply_decay(now);
82 self.engagement_level < self.novelty_threshold
83 }
84
85 pub fn get_stimulation_activities(&self) -> Vec<&'static str> {
87 let mut activities = Vec::new();
88
89 if self.engagement_level < 0.2 {
90 activities.push("explore_new_patterns");
92 activities.push("cross_modal_learning");
93 } else if self.engagement_level < 0.4 {
94 activities.push("challenge_existing_pathways");
96 activities.push("learn_new_domain");
97 } else if self.engagement_level < 0.6 {
98 activities.push("recall_with_variations");
100 activities.push("make_new_associations");
101 }
102
103 activities
104 }
105
106 pub fn stats(&self) -> (f32, u64, usize, f32) {
108 let now = Self::now_ms();
109 let time_since_last = now.saturating_sub(self.last_activity_ms);
110 let activity_count = self.activity_history.len();
111
112 let recent_avg_intensity = if !self.activity_history.is_empty() {
113 self.activity_history
114 .iter()
115 .rev()
116 .take(10)
117 .map(|a| a.intensity)
118 .sum::<f32>()
119 / self.activity_history.len().min(10) as f32
120 } else {
121 0.0
122 };
123
124 (
125 self.engagement_level,
126 time_since_last,
127 activity_count,
128 recent_avg_intensity,
129 )
130 }
131
132 pub fn activity_diversity(&self) -> f32 {
134 if self.activity_history.is_empty() {
135 return 0.0;
136 }
137
138 let mut type_counts = HashMap::new();
139 for activity in &self.activity_history {
140 *type_counts
141 .entry(activity.activity_type.clone())
142 .or_insert(0) += 1;
143 }
144
145 let unique_types = type_counts.len() as f32;
146 let total_activities = self.activity_history.len() as f32;
147
148 (unique_types / total_activities).min(1.0)
149 }
150
151 pub fn save(&self, dir: &Path) -> Result<(), String> {
152 let path = dir.join("mental_stimulation.bin");
153 let mut data = Vec::new();
154
155 data.extend_from_slice(b"STIM");
156 data.push(1);
157
158 data.extend_from_slice(&self.engagement_level.to_le_bytes());
159 data.extend_from_slice(&self.last_activity_ms.to_le_bytes());
160 data.extend_from_slice(&self.stimulation_need.to_le_bytes());
161
162 let activity_count = self.activity_history.len() as u32;
163 data.extend_from_slice(&activity_count.to_le_bytes());
164
165 for activity in &self.activity_history {
166 data.extend_from_slice(&activity.timestamp_ms.to_le_bytes());
167 data.extend_from_slice(&activity.intensity.to_le_bytes());
168 data.extend_from_slice(&activity.engagement_gain.to_le_bytes());
169
170 let type_bytes = activity.activity_type.as_bytes();
171 data.push(type_bytes.len() as u8);
172 data.extend_from_slice(type_bytes);
173 }
174
175 let tmp_path = dir.join("mental_stimulation.bin.tmp");
176 fs::write(&tmp_path, data).map_err(|e| e.to_string())?;
177 fs::rename(&tmp_path, &path).map_err(|e| e.to_string())
178 }
179
180 pub fn load(dir: &Path) -> Result<Self, String> {
181 let path = dir.join("mental_stimulation.bin");
182 if !path.exists() {
183 return Ok(Self::new());
184 }
185
186 let data = fs::read(&path).map_err(|e| e.to_string())?;
187 if data.len() < 5 || &data[0..4] != b"STIM" {
188 return Ok(Self::new());
189 }
190
191 let mut idx = 5;
192 let engagement_level =
193 f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
194 idx += 4;
195
196 let last_activity_ms = u64::from_le_bytes([
197 data[idx],
198 data[idx + 1],
199 data[idx + 2],
200 data[idx + 3],
201 data[idx + 4],
202 data[idx + 5],
203 data[idx + 6],
204 data[idx + 7],
205 ]);
206 idx += 8;
207
208 let stimulation_need =
209 f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
210 idx += 4;
211
212 let mut activity_history = Vec::new();
213
214 if idx + 4 <= data.len() {
215 let activity_count =
216 u32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]])
217 as usize;
218 idx += 4;
219
220 for _ in 0..activity_count {
221 if idx + 20 > data.len() {
222 break;
223 }
224
225 let timestamp_ms = u64::from_le_bytes([
226 data[idx],
227 data[idx + 1],
228 data[idx + 2],
229 data[idx + 3],
230 data[idx + 4],
231 data[idx + 5],
232 data[idx + 6],
233 data[idx + 7],
234 ]);
235 idx += 8;
236
237 let intensity =
238 f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
239 idx += 4;
240
241 let engagement_gain =
242 f32::from_le_bytes([data[idx], data[idx + 1], data[idx + 2], data[idx + 3]]);
243 idx += 4;
244
245 if idx >= data.len() {
246 break;
247 }
248 let type_len = data[idx] as usize;
249 idx += 1;
250
251 if idx + type_len > data.len() {
252 break;
253 }
254 let activity_type = String::from_utf8_lossy(&data[idx..idx + type_len]).to_string();
255 idx += type_len;
256
257 activity_history.push(ActivityRecord {
258 timestamp_ms,
259 activity_type,
260 intensity,
261 engagement_gain,
262 });
263 }
264 }
265
266 Ok(Self {
267 activity_history,
268 engagement_level,
269 decay_rate: 0.001,
270 last_activity_ms,
271 stimulation_need,
272 novelty_threshold: 0.3,
273 })
274 }
275
276 pub fn load_or_init(dir: &Path) -> Self {
277 Self::load(dir).unwrap_or_else(|_| Self::new())
278 }
279
280 fn now_ms() -> u64 {
281 std::time::SystemTime::now()
282 .duration_since(std::time::UNIX_EPOCH)
283 .unwrap_or_default()
284 .as_millis() as u64
285 }
286}