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).min(self.stability).min(other.stability),
100 }
101 }
102
103 pub fn intensity(&self) -> f32 {
105 (self.pleasure.powi(2) + self.arousal.powi(2) + self.dominance.powi(2)).sqrt() / 1.732
106 }
107}
108
109#[derive(Debug, Clone, PartialEq)]
115pub enum AlterPresenceState {
116 Dormant,
118 Stirring,
120 CoConscious,
122 Emerging,
124 Fronting,
126 Receding,
128 Triggered,
130 Dissociating,
132}
133
134#[derive(Debug, Clone)]
136pub struct Alter {
137 pub id: String,
139 pub name: String,
141 pub category: AlterCategory,
143 pub state: AlterPresenceState,
145 pub anima: AnimaState,
147 pub base_arousal: f32,
149 pub base_dominance: f32,
151 pub time_since_front: u64,
153 pub triggers: Vec<TriggerId>,
155 pub abilities: HashSet<String>,
157 pub preferred_reality: RealityLayer,
159 pub memory_access: MemoryAccess,
161}
162
163#[derive(Debug, Clone, PartialEq, Eq, Hash)]
165pub enum AlterCategory {
166 Council,
168 Servant,
170 Fragment,
172 Introject,
174 Persecutor,
176 TraumaHolder,
178 Custom(String),
180}
181
182#[derive(Debug, Clone, PartialEq)]
184pub enum MemoryAccess {
185 Full,
187 Partial(Vec<String>),
189 Own,
191 None,
193}
194
195#[derive(Debug, Clone)]
201pub struct PluralSystem {
202 pub name: Option<String>,
204 pub alters: HashMap<String, Alter>,
206 pub fronting: FrontingState,
208 pub anima: AnimaState,
210 pub reality_layer: RealityLayer,
212 pub active_triggers: Vec<Trigger>,
214 pub headspace: HeadspaceState,
216 pub dissociation: f32,
218 pub stability: f32,
220}
221
222impl Default for PluralSystem {
223 fn default() -> Self {
224 Self {
225 name: None,
226 alters: HashMap::new(),
227 fronting: FrontingState::None,
228 anima: AnimaState::default(),
229 reality_layer: RealityLayer::Grounded,
230 active_triggers: Vec::new(),
231 headspace: HeadspaceState::default(),
232 dissociation: 0.0,
233 stability: 1.0,
234 }
235 }
236}
237
238impl PluralSystem {
239 pub fn new(name: impl Into<String>) -> Self {
241 Self {
242 name: Some(name.into()),
243 ..Default::default()
244 }
245 }
246
247 pub fn add_alter(&mut self, alter: Alter) {
249 self.alters.insert(alter.id.clone(), alter);
250 }
251
252 pub fn current_fronter(&self) -> Option<&Alter> {
254 match &self.fronting {
255 FrontingState::Single(id) => self.alters.get(id),
256 FrontingState::Blended(ids) if ids.len() == 1 => self.alters.get(&ids[0]),
257 _ => None,
258 }
259 }
260
261 pub fn request_switch(&mut self, target_id: &str, urgency: f32, forced: bool) -> SwitchResult {
263 if !self.alters.contains_key(target_id) {
264 return SwitchResult::Failed(SwitchFailReason::UnknownAlter);
265 }
266
267 if self.dissociation > 0.8 && !forced {
269 return SwitchResult::Failed(SwitchFailReason::TooDisassociated);
270 }
271
272 if self.stability < 0.2 && !forced {
273 return SwitchResult::Failed(SwitchFailReason::SystemUnstable);
274 }
275
276 let current_alter = self.current_fronter();
278 let resistance = if let Some(current) = current_alter {
279 (current.anima.dominance + 1.0) / 2.0 * (1.0 - urgency)
281 } else {
282 0.0
283 };
284
285 if resistance > 0.7 && !forced {
286 return SwitchResult::Resisted { resistance };
287 }
288
289 if let Some(prev) = current_alter {
291 let prev_id = prev.id.clone();
292 if let Some(alter) = self.alters.get_mut(&prev_id) {
293 alter.state = AlterPresenceState::Receding;
294 }
295 }
296
297 if let Some(alter) = self.alters.get_mut(target_id) {
298 alter.state = AlterPresenceState::Fronting;
299 alter.time_since_front = 0;
300 }
301
302 self.fronting = FrontingState::Single(target_id.to_string());
303 self.update_blended_anima();
304
305 SwitchResult::Success
306 }
307
308 pub fn update_blended_anima(&mut self) {
310 let mut total_influence = 0.0;
311 let mut blended = AnimaState::default();
312
313 for alter in self.alters.values() {
314 let influence = match alter.state {
315 AlterPresenceState::Fronting => 1.0,
316 AlterPresenceState::CoConscious => 0.3,
317 AlterPresenceState::Emerging => 0.5,
318 AlterPresenceState::Receding => 0.2,
319 AlterPresenceState::Triggered => 0.7,
320 _ => 0.0,
321 };
322
323 if influence > 0.0 {
324 blended.pleasure += alter.anima.pleasure * influence;
325 blended.arousal += alter.anima.arousal * influence;
326 blended.dominance += alter.anima.dominance * influence;
327 total_influence += influence;
328 }
329 }
330
331 if total_influence > 0.0 {
332 blended.pleasure /= total_influence;
333 blended.arousal /= total_influence;
334 blended.dominance /= total_influence;
335 blended.expressiveness = 0.5; blended.stability = self.stability;
337 }
338
339 self.anima = blended;
340 }
341
342 pub fn process_trigger(&mut self, trigger: Trigger) -> TriggerResult {
344 self.active_triggers.push(trigger.clone());
345
346 let responding_alters: Vec<String> = self.alters.values()
348 .filter(|a| a.triggers.contains(&trigger.id))
349 .map(|a| a.id.clone())
350 .collect();
351
352 if responding_alters.is_empty() {
353 return TriggerResult::NoResponse;
354 }
355
356 let intensity = trigger.intensity * (1.0 + self.dissociation);
358
359 for alter_id in &responding_alters {
361 if let Some(alter) = self.alters.get_mut(alter_id) {
362 if matches!(alter.state, AlterPresenceState::Dormant | AlterPresenceState::Stirring) {
363 alter.state = AlterPresenceState::Stirring;
364 alter.anima.apply_trauma_response(intensity);
365 }
366 }
367 }
368
369 if intensity > 0.8 {
371 if let Some(strongest) = responding_alters.first() {
372 return TriggerResult::ForcedSwitch(strongest.clone());
373 }
374 }
375
376 TriggerResult::Activation(responding_alters)
377 }
378
379 pub fn shift_reality(&mut self, target: RealityLayer, perception_level: f32) {
381 let shift_threshold = match (&self.reality_layer, &target) {
383 (RealityLayer::Grounded, RealityLayer::Fractured) => 0.3,
384 (RealityLayer::Fractured, RealityLayer::Shattered) => 0.6,
385 (RealityLayer::Shattered, RealityLayer::Fractured) => 0.4,
386 (RealityLayer::Fractured, RealityLayer::Grounded) => 0.5,
387 _ => 0.5,
388 };
389
390 if perception_level >= shift_threshold || self.dissociation > 0.7 {
391 self.reality_layer = target;
392 }
393 }
394}
395
396#[derive(Debug, Clone, PartialEq)]
402pub enum FrontingState {
403 None,
405 Single(String),
407 Blended(Vec<String>),
409 Rapid(Vec<String>),
411 Unknown,
413}
414
415#[derive(Debug, Clone, PartialEq, Eq, Hash)]
421pub enum RealityLayer {
422 Grounded,
424 Fractured,
426 Shattered,
428 Custom(String),
430}
431
432pub type TriggerId = String;
438
439#[derive(Debug, Clone)]
441pub struct Trigger {
442 pub id: TriggerId,
444 pub name: String,
446 pub category: TriggerCategory,
448 pub intensity: f32,
450 pub context: HashMap<String, String>,
452}
453
454#[derive(Debug, Clone, PartialEq, Eq)]
456pub enum TriggerCategory {
457 Environmental,
459 Social,
461 Internal,
463 Physical,
465 Temporal,
467 Combat,
469 Custom(String),
471}
472
473#[derive(Debug, Clone)]
475pub enum TriggerResult {
476 NoResponse,
478 Activation(Vec<String>),
480 ForcedSwitch(String),
482 Dissociation,
484}
485
486#[derive(Debug, Clone)]
492pub enum SwitchResult {
493 Success,
495 Resisted { resistance: f32 },
497 Failed(SwitchFailReason),
499 InProgress { eta: u64 },
501}
502
503#[derive(Debug, Clone, PartialEq)]
505pub enum SwitchFailReason {
506 UnknownAlter,
508 TooDisassociated,
510 SystemUnstable,
512 CurrentRefused,
514 TargetUnavailable,
516 Blocked(String),
518}
519
520#[derive(Debug, Clone, Default)]
526pub struct HeadspaceState {
527 pub current_location: Option<String>,
529 pub present_alters: Vec<String>,
531 pub navigation_path: Vec<String>,
533 pub active_hazards: Vec<String>,
535 pub atmosphere: HeadspaceAtmosphere,
537}
538
539#[derive(Debug, Clone, Default)]
541pub struct HeadspaceAtmosphere {
542 pub clarity: f32,
544 pub stability: f32,
546 pub lighting: f32,
548 pub effects: Vec<String>,
550}
551
552#[derive(Debug, Clone)]
558pub struct CoConChannel {
559 pub participants: Vec<String>,
561 pub quality: f32,
563 pub messages: Vec<CoConMessage>,
565 pub active: bool,
567}
568
569#[derive(Debug, Clone)]
571pub struct CoConMessage {
572 pub from: String,
574 pub content: String,
576 pub certainty: f32,
578 pub timestamp: u64,
580}
581
582#[cfg(test)]
587mod tests {
588 use super::*;
589
590 #[test]
591 fn test_anima_state_blend() {
592 let anxious = AnimaState::anxious();
593 let calm = AnimaState::calm();
594 let blended = anxious.blend(&calm, 0.5);
595
596 assert!(blended.pleasure > anxious.pleasure);
597 assert!(blended.arousal < anxious.arousal);
598 }
599
600 #[test]
601 fn test_plural_system_add_alter() {
602 let mut system = PluralSystem::new("Test System");
603
604 let alter = Alter {
605 id: "abaddon".to_string(),
606 name: "Abaddon".to_string(),
607 category: AlterCategory::Council,
608 state: AlterPresenceState::Dormant,
609 anima: AnimaState::default(),
610 base_arousal: 0.3,
611 base_dominance: 0.6,
612 time_since_front: 0,
613 triggers: vec!["threat".to_string()],
614 abilities: HashSet::from(["combat".to_string()]),
615 preferred_reality: RealityLayer::Fractured,
616 memory_access: MemoryAccess::Full,
617 };
618
619 system.add_alter(alter);
620 assert!(system.alters.contains_key("abaddon"));
621 }
622
623 #[test]
624 fn test_switch_request() {
625 let mut system = PluralSystem::new("Test System");
626
627 let alter1 = Alter {
628 id: "host".to_string(),
629 name: "Host".to_string(),
630 category: AlterCategory::Council,
631 state: AlterPresenceState::Fronting,
632 anima: AnimaState::default(),
633 base_arousal: 0.0,
634 base_dominance: 0.0,
635 time_since_front: 0,
636 triggers: vec![],
637 abilities: HashSet::new(),
638 preferred_reality: RealityLayer::Grounded,
639 memory_access: MemoryAccess::Full,
640 };
641
642 let alter2 = Alter {
643 id: "protector".to_string(),
644 name: "Protector".to_string(),
645 category: AlterCategory::Council,
646 state: AlterPresenceState::Dormant,
647 anima: AnimaState::default(),
648 base_arousal: 0.5,
649 base_dominance: 0.7,
650 time_since_front: 100,
651 triggers: vec!["threat".to_string()],
652 abilities: HashSet::from(["combat".to_string()]),
653 preferred_reality: RealityLayer::Grounded,
654 memory_access: MemoryAccess::Full,
655 };
656
657 system.add_alter(alter1);
658 system.add_alter(alter2);
659 system.fronting = FrontingState::Single("host".to_string());
660
661 let result = system.request_switch("protector", 0.8, false);
662 assert!(matches!(result, SwitchResult::Success));
663 assert_eq!(system.fronting, FrontingState::Single("protector".to_string()));
664 }
665}