1use std::collections::{HashMap, HashSet};
7
8#[derive(Debug, Clone, PartialEq)]
20pub struct AnimaState {
21 pub pleasure: f32,
23 pub arousal: f32,
25 pub dominance: f32,
27 pub expressiveness: f32,
29 pub stability: f32,
31}
32
33impl Default for AnimaState {
34 fn default() -> Self {
35 Self {
36 pleasure: 0.0,
37 arousal: 0.0,
38 dominance: 0.0,
39 expressiveness: 0.5,
40 stability: 0.7,
41 }
42 }
43}
44
45impl AnimaState {
46 pub fn new(pleasure: f32, arousal: f32, dominance: f32) -> Self {
48 Self {
49 pleasure: pleasure.clamp(-1.0, 1.0),
50 arousal: arousal.clamp(-1.0, 1.0),
51 dominance: dominance.clamp(-1.0, 1.0),
52 expressiveness: 0.5,
53 stability: 0.7,
54 }
55 }
56
57 pub fn anxious() -> Self {
59 Self::new(-0.5, 0.7, -0.4)
60 }
61
62 pub fn angry() -> Self {
64 Self::new(-0.7, 0.8, 0.6)
65 }
66
67 pub fn calm() -> Self {
69 Self::new(0.3, -0.6, 0.0)
70 }
71
72 pub fn dissociated() -> Self {
74 Self {
75 pleasure: 0.0,
76 arousal: -0.3,
77 dominance: -0.5,
78 expressiveness: 0.1,
79 stability: 0.3,
80 }
81 }
82
83 pub fn apply_trauma_response(&mut self, intensity: f32) {
85 self.arousal = (self.arousal + intensity * 0.5).clamp(-1.0, 1.0);
86 self.stability -= intensity * 0.3;
87 self.stability = self.stability.clamp(0.0, 1.0);
88 }
89
90 pub fn blend(&self, other: &AnimaState, ratio: f32) -> AnimaState {
92 let ratio = ratio.clamp(0.0, 1.0);
93 let inv = 1.0 - ratio;
94 AnimaState {
95 pleasure: self.pleasure * inv + other.pleasure * ratio,
96 arousal: self.arousal * inv + other.arousal * ratio,
97 dominance: self.dominance * inv + other.dominance * ratio,
98 expressiveness: self.expressiveness * inv + other.expressiveness * ratio,
99 stability: (self.stability * inv + other.stability * ratio)
100 .min(self.stability)
101 .min(other.stability),
102 }
103 }
104
105 pub fn intensity(&self) -> f32 {
107 (self.pleasure.powi(2) + self.arousal.powi(2) + self.dominance.powi(2)).sqrt() / 1.732
108 }
109}
110
111#[derive(Debug, Clone, PartialEq)]
117pub enum AlterPresenceState {
118 Dormant,
120 Stirring,
122 CoConscious,
124 Emerging,
126 Fronting,
128 Receding,
130 Triggered,
132 Dissociating,
134}
135
136#[derive(Debug, Clone)]
138pub struct Alter {
139 pub id: String,
141 pub name: String,
143 pub category: AlterCategory,
145 pub state: AlterPresenceState,
147 pub anima: AnimaState,
149 pub base_arousal: f32,
151 pub base_dominance: f32,
153 pub time_since_front: u64,
155 pub triggers: Vec<TriggerId>,
157 pub abilities: HashSet<String>,
159 pub preferred_reality: RealityLayer,
161 pub memory_access: MemoryAccess,
163}
164
165#[derive(Debug, Clone, PartialEq, Eq, Hash)]
167pub enum AlterCategory {
168 Council,
170 Servant,
172 Fragment,
174 Introject,
176 Persecutor,
178 TraumaHolder,
180 Custom(String),
182}
183
184#[derive(Debug, Clone, PartialEq)]
186pub enum MemoryAccess {
187 Full,
189 Partial(Vec<String>),
191 Own,
193 None,
195}
196
197#[derive(Debug, Clone)]
203pub struct PluralSystem {
204 pub name: Option<String>,
206 pub alters: HashMap<String, Alter>,
208 pub fronting: FrontingState,
210 pub anima: AnimaState,
212 pub reality_layer: RealityLayer,
214 pub active_triggers: Vec<Trigger>,
216 pub headspace: HeadspaceState,
218 pub dissociation: f32,
220 pub stability: f32,
222}
223
224impl Default for PluralSystem {
225 fn default() -> Self {
226 Self {
227 name: None,
228 alters: HashMap::new(),
229 fronting: FrontingState::None,
230 anima: AnimaState::default(),
231 reality_layer: RealityLayer::Grounded,
232 active_triggers: Vec::new(),
233 headspace: HeadspaceState::default(),
234 dissociation: 0.0,
235 stability: 1.0,
236 }
237 }
238}
239
240impl PluralSystem {
241 pub fn new(name: impl Into<String>) -> Self {
243 Self {
244 name: Some(name.into()),
245 ..Default::default()
246 }
247 }
248
249 pub fn add_alter(&mut self, alter: Alter) {
251 self.alters.insert(alter.id.clone(), alter);
252 }
253
254 pub fn current_fronter(&self) -> Option<&Alter> {
256 match &self.fronting {
257 FrontingState::Single(id) => self.alters.get(id),
258 FrontingState::Blended(ids) if ids.len() == 1 => self.alters.get(&ids[0]),
259 _ => None,
260 }
261 }
262
263 pub fn request_switch(&mut self, target_id: &str, urgency: f32, forced: bool) -> SwitchResult {
265 if !self.alters.contains_key(target_id) {
266 return SwitchResult::Failed(SwitchFailReason::UnknownAlter);
267 }
268
269 if self.dissociation > 0.8 && !forced {
271 return SwitchResult::Failed(SwitchFailReason::TooDisassociated);
272 }
273
274 if self.stability < 0.2 && !forced {
275 return SwitchResult::Failed(SwitchFailReason::SystemUnstable);
276 }
277
278 let current_alter = self.current_fronter();
280 let resistance = if let Some(current) = current_alter {
281 (current.anima.dominance + 1.0) / 2.0 * (1.0 - urgency)
283 } else {
284 0.0
285 };
286
287 if resistance > 0.7 && !forced {
288 return SwitchResult::Resisted { resistance };
289 }
290
291 if let Some(prev) = current_alter {
293 let prev_id = prev.id.clone();
294 if let Some(alter) = self.alters.get_mut(&prev_id) {
295 alter.state = AlterPresenceState::Receding;
296 }
297 }
298
299 if let Some(alter) = self.alters.get_mut(target_id) {
300 alter.state = AlterPresenceState::Fronting;
301 alter.time_since_front = 0;
302 }
303
304 self.fronting = FrontingState::Single(target_id.to_string());
305 self.update_blended_anima();
306
307 SwitchResult::Success
308 }
309
310 pub fn update_blended_anima(&mut self) {
312 let mut total_influence = 0.0;
313 let mut blended = AnimaState::default();
314
315 for alter in self.alters.values() {
316 let influence = match alter.state {
317 AlterPresenceState::Fronting => 1.0,
318 AlterPresenceState::CoConscious => 0.3,
319 AlterPresenceState::Emerging => 0.5,
320 AlterPresenceState::Receding => 0.2,
321 AlterPresenceState::Triggered => 0.7,
322 _ => 0.0,
323 };
324
325 if influence > 0.0 {
326 blended.pleasure += alter.anima.pleasure * influence;
327 blended.arousal += alter.anima.arousal * influence;
328 blended.dominance += alter.anima.dominance * influence;
329 total_influence += influence;
330 }
331 }
332
333 if total_influence > 0.0 {
334 blended.pleasure /= total_influence;
335 blended.arousal /= total_influence;
336 blended.dominance /= total_influence;
337 blended.expressiveness = 0.5; blended.stability = self.stability;
339 }
340
341 self.anima = blended;
342 }
343
344 pub fn process_trigger(&mut self, trigger: Trigger) -> TriggerResult {
346 self.active_triggers.push(trigger.clone());
347
348 let responding_alters: Vec<String> = self
350 .alters
351 .values()
352 .filter(|a| a.triggers.contains(&trigger.id))
353 .map(|a| a.id.clone())
354 .collect();
355
356 if responding_alters.is_empty() {
357 return TriggerResult::NoResponse;
358 }
359
360 let intensity = trigger.intensity * (1.0 + self.dissociation);
362
363 for alter_id in &responding_alters {
365 if let Some(alter) = self.alters.get_mut(alter_id) {
366 if matches!(
367 alter.state,
368 AlterPresenceState::Dormant | AlterPresenceState::Stirring
369 ) {
370 alter.state = AlterPresenceState::Stirring;
371 alter.anima.apply_trauma_response(intensity);
372 }
373 }
374 }
375
376 if intensity > 0.8 {
378 if let Some(strongest) = responding_alters.first() {
379 return TriggerResult::ForcedSwitch(strongest.clone());
380 }
381 }
382
383 TriggerResult::Activation(responding_alters)
384 }
385
386 pub fn shift_reality(&mut self, target: RealityLayer, perception_level: f32) {
388 let shift_threshold = match (&self.reality_layer, &target) {
390 (RealityLayer::Grounded, RealityLayer::Fractured) => 0.3,
391 (RealityLayer::Fractured, RealityLayer::Shattered) => 0.6,
392 (RealityLayer::Shattered, RealityLayer::Fractured) => 0.4,
393 (RealityLayer::Fractured, RealityLayer::Grounded) => 0.5,
394 _ => 0.5,
395 };
396
397 if perception_level >= shift_threshold || self.dissociation > 0.7 {
398 self.reality_layer = target;
399 }
400 }
401}
402
403#[derive(Debug, Clone, PartialEq)]
409pub enum FrontingState {
410 None,
412 Single(String),
414 Blended(Vec<String>),
416 Rapid(Vec<String>),
418 Unknown,
420}
421
422#[derive(Debug, Clone, PartialEq, Eq, Hash)]
428pub enum RealityLayer {
429 Grounded,
431 Fractured,
433 Shattered,
435 Custom(String),
437}
438
439pub type TriggerId = String;
445
446#[derive(Debug, Clone)]
448pub struct Trigger {
449 pub id: TriggerId,
451 pub name: String,
453 pub category: TriggerCategory,
455 pub intensity: f32,
457 pub context: HashMap<String, String>,
459}
460
461#[derive(Debug, Clone, PartialEq, Eq)]
463pub enum TriggerCategory {
464 Environmental,
466 Social,
468 Internal,
470 Physical,
472 Temporal,
474 Combat,
476 Custom(String),
478}
479
480#[derive(Debug, Clone)]
482pub enum TriggerResult {
483 NoResponse,
485 Activation(Vec<String>),
487 ForcedSwitch(String),
489 Dissociation,
491}
492
493#[derive(Debug, Clone)]
499pub enum SwitchResult {
500 Success,
502 Resisted { resistance: f32 },
504 Failed(SwitchFailReason),
506 InProgress { eta: u64 },
508}
509
510#[derive(Debug, Clone, PartialEq)]
512pub enum SwitchFailReason {
513 UnknownAlter,
515 TooDisassociated,
517 SystemUnstable,
519 CurrentRefused,
521 TargetUnavailable,
523 Blocked(String),
525}
526
527#[derive(Debug, Clone, Default)]
533pub struct HeadspaceState {
534 pub current_location: Option<String>,
536 pub present_alters: Vec<String>,
538 pub navigation_path: Vec<String>,
540 pub active_hazards: Vec<String>,
542 pub atmosphere: HeadspaceAtmosphere,
544}
545
546#[derive(Debug, Clone, Default)]
548pub struct HeadspaceAtmosphere {
549 pub clarity: f32,
551 pub stability: f32,
553 pub lighting: f32,
555 pub effects: Vec<String>,
557}
558
559#[derive(Debug, Clone)]
565pub struct CoConChannel {
566 pub participants: Vec<String>,
568 pub quality: f32,
570 pub messages: Vec<CoConMessage>,
572 pub active: bool,
574}
575
576#[derive(Debug, Clone)]
578pub struct CoConMessage {
579 pub from: String,
581 pub content: String,
583 pub certainty: f32,
585 pub timestamp: u64,
587}
588
589#[cfg(test)]
594mod tests {
595 use super::*;
596
597 #[test]
598 fn test_anima_state_blend() {
599 let anxious = AnimaState::anxious();
600 let calm = AnimaState::calm();
601 let blended = anxious.blend(&calm, 0.5);
602
603 assert!(blended.pleasure > anxious.pleasure);
604 assert!(blended.arousal < anxious.arousal);
605 }
606
607 #[test]
608 fn test_plural_system_add_alter() {
609 let mut system = PluralSystem::new("Test System");
610
611 let alter = Alter {
612 id: "abaddon".to_string(),
613 name: "Abaddon".to_string(),
614 category: AlterCategory::Council,
615 state: AlterPresenceState::Dormant,
616 anima: AnimaState::default(),
617 base_arousal: 0.3,
618 base_dominance: 0.6,
619 time_since_front: 0,
620 triggers: vec!["threat".to_string()],
621 abilities: HashSet::from(["combat".to_string()]),
622 preferred_reality: RealityLayer::Fractured,
623 memory_access: MemoryAccess::Full,
624 };
625
626 system.add_alter(alter);
627 assert!(system.alters.contains_key("abaddon"));
628 }
629
630 #[test]
631 fn test_switch_request() {
632 let mut system = PluralSystem::new("Test System");
633
634 let alter1 = Alter {
635 id: "host".to_string(),
636 name: "Host".to_string(),
637 category: AlterCategory::Council,
638 state: AlterPresenceState::Fronting,
639 anima: AnimaState::default(),
640 base_arousal: 0.0,
641 base_dominance: 0.0,
642 time_since_front: 0,
643 triggers: vec![],
644 abilities: HashSet::new(),
645 preferred_reality: RealityLayer::Grounded,
646 memory_access: MemoryAccess::Full,
647 };
648
649 let alter2 = Alter {
650 id: "protector".to_string(),
651 name: "Protector".to_string(),
652 category: AlterCategory::Council,
653 state: AlterPresenceState::Dormant,
654 anima: AnimaState::default(),
655 base_arousal: 0.5,
656 base_dominance: 0.7,
657 time_since_front: 100,
658 triggers: vec!["threat".to_string()],
659 abilities: HashSet::from(["combat".to_string()]),
660 preferred_reality: RealityLayer::Grounded,
661 memory_access: MemoryAccess::Full,
662 };
663
664 system.add_alter(alter1);
665 system.add_alter(alter2);
666 system.fronting = FrontingState::Single("host".to_string());
667
668 let result = system.request_switch("protector", 0.8, false);
669 assert!(matches!(result, SwitchResult::Success));
670 assert_eq!(
671 system.fronting,
672 FrontingState::Single("protector".to_string())
673 );
674 }
675}