Skip to main content

sigil_parser/plurality/
runtime.rs

1//! # Runtime Types for Plurality
2//!
3//! Runtime representations of plurality constructs for the DAEMONIORUM game engine.
4//! These types represent the in-game state of the plural system.
5
6use std::collections::{HashMap, HashSet};
7
8// ============================================================================
9// ANIMA STATE (PAD Model)
10// ============================================================================
11
12/// Anima state represents the emotional/psychological state of an alter or the system.
13/// Based on the PAD (Pleasure-Arousal-Dominance) model of emotional states.
14///
15/// Each dimension ranges from -1.0 to 1.0:
16/// - Pleasure: unhappy (-1) to happy (+1)
17/// - Arousal: calm (-1) to excited (+1)
18/// - Dominance: submissive (-1) to dominant (+1)
19#[derive(Debug, Clone, PartialEq)]
20pub struct AnimaState {
21    /// Pleasure dimension: -1.0 (unhappy) to 1.0 (happy)
22    pub pleasure: f32,
23    /// Arousal dimension: -1.0 (calm) to 1.0 (excited)
24    pub arousal: f32,
25    /// Dominance dimension: -1.0 (submissive) to 1.0 (dominant)
26    pub dominance: f32,
27    /// Expressiveness: how visibly the emotion is displayed (0.0 to 1.0)
28    pub expressiveness: f32,
29    /// Stability: how stable the current emotional state is (0.0 to 1.0)
30    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    /// Create a new AnimaState with the given PAD values
47    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    /// Create an anxious state (low pleasure, high arousal, low dominance)
58    pub fn anxious() -> Self {
59        Self::new(-0.5, 0.7, -0.4)
60    }
61
62    /// Create an angry state (low pleasure, high arousal, high dominance)
63    pub fn angry() -> Self {
64        Self::new(-0.7, 0.8, 0.6)
65    }
66
67    /// Create a calm state (neutral pleasure, low arousal, neutral dominance)
68    pub fn calm() -> Self {
69        Self::new(0.3, -0.6, 0.0)
70    }
71
72    /// Create a dissociated state (flat affect)
73    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    /// Apply trauma response modifier
84    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    /// Blend two AnimaStates together
91    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    /// Calculate the intensity/magnitude of the emotional state
104    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// ============================================================================
110// ALTER STATE
111// ============================================================================
112
113/// Runtime state of an alter
114#[derive(Debug, Clone, PartialEq)]
115pub enum AlterPresenceState {
116    /// Alter is completely inactive
117    Dormant,
118    /// Alter is beginning to wake/activate
119    Stirring,
120    /// Alter is present but not fronting
121    CoConscious,
122    /// Alter is transitioning to front
123    Emerging,
124    /// Alter is currently in control
125    Fronting,
126    /// Alter is transitioning away from front
127    Receding,
128    /// Alter is in trauma response
129    Triggered,
130    /// Alter is disconnecting/going passive
131    Dissociating,
132}
133
134/// Runtime representation of an alter
135#[derive(Debug, Clone)]
136pub struct Alter {
137    /// Unique identifier for the alter
138    pub id: String,
139    /// Display name
140    pub name: String,
141    /// Category (Council, Servant, Fragment, etc.)
142    pub category: AlterCategory,
143    /// Current presence state
144    pub state: AlterPresenceState,
145    /// Current anima (emotional) state
146    pub anima: AnimaState,
147    /// Base arousal level (personality trait)
148    pub base_arousal: f32,
149    /// Base dominance level (personality trait)
150    pub base_dominance: f32,
151    /// Time since last fronting (in game time units)
152    pub time_since_front: u64,
153    /// Triggers that can activate this alter
154    pub triggers: Vec<TriggerId>,
155    /// Abilities unique to this alter
156    pub abilities: HashSet<String>,
157    /// Preferred reality layer
158    pub preferred_reality: RealityLayer,
159    /// Memory access level for this alter
160    pub memory_access: MemoryAccess,
161}
162
163/// Alter category from the Council system
164#[derive(Debug, Clone, PartialEq, Eq, Hash)]
165pub enum AlterCategory {
166    /// Core system member, full agency
167    Council,
168    /// Helper alter, limited scope
169    Servant,
170    /// Incomplete alter, specific function
171    Fragment,
172    /// External introject
173    Introject,
174    /// Persecutor alter
175    Persecutor,
176    /// Trauma holder
177    TraumaHolder,
178    /// Custom category
179    Custom(String),
180}
181
182/// Memory access level
183#[derive(Debug, Clone, PartialEq)]
184pub enum MemoryAccess {
185    /// Full access to all system memories
186    Full,
187    /// Partial access (specific memory sets)
188    Partial(Vec<String>),
189    /// Limited to own memories only
190    Own,
191    /// Amnesiac - no memory access
192    None,
193}
194
195// ============================================================================
196// PLURAL SYSTEM
197// ============================================================================
198
199/// The plural system as a whole
200#[derive(Debug, Clone)]
201pub struct PluralSystem {
202    /// System name (if any)
203    pub name: Option<String>,
204    /// All alters in the system
205    pub alters: HashMap<String, Alter>,
206    /// Currently fronting alter(s)
207    pub fronting: FrontingState,
208    /// System-level anima state (blended from active alters)
209    pub anima: AnimaState,
210    /// Current reality perception layer
211    pub reality_layer: RealityLayer,
212    /// Active triggers being processed
213    pub active_triggers: Vec<Trigger>,
214    /// Headspace state
215    pub headspace: HeadspaceState,
216    /// Dissociation level (0.0 to 1.0)
217    pub dissociation: f32,
218    /// System stability (0.0 to 1.0)
219    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    /// Create a new plural system with the given name
240    pub fn new(name: impl Into<String>) -> Self {
241        Self {
242            name: Some(name.into()),
243            ..Default::default()
244        }
245    }
246
247    /// Add an alter to the system
248    pub fn add_alter(&mut self, alter: Alter) {
249        self.alters.insert(alter.id.clone(), alter);
250    }
251
252    /// Get the currently fronting alter (if single fronter)
253    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    /// Request a switch to a different alter
262    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        // Check if switch is possible based on system state
268        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        // Calculate switch difficulty
277        let current_alter = self.current_fronter();
278        let resistance = if let Some(current) = current_alter {
279            // More dominant alters are harder to switch away from
280            (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        // Perform the switch
290        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    /// Update the system's blended anima state from active alters
309    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; // Average expressiveness
336            blended.stability = self.stability;
337        }
338
339        self.anima = blended;
340    }
341
342    /// Process a trigger event
343    pub fn process_trigger(&mut self, trigger: Trigger) -> TriggerResult {
344        self.active_triggers.push(trigger.clone());
345
346        // Find alters that respond to this trigger
347        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        // Calculate response intensity
357        let intensity = trigger.intensity * (1.0 + self.dissociation);
358
359        // Update responding alters
360        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        // High intensity triggers can cause forced switches
370        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    /// Shift reality perception layer
380    pub fn shift_reality(&mut self, target: RealityLayer, perception_level: f32) {
381        // Reality shifts are influenced by dissociation and trigger state
382        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// ============================================================================
397// FRONTING STATE
398// ============================================================================
399
400/// Represents who is currently fronting
401#[derive(Debug, Clone, PartialEq)]
402pub enum FrontingState {
403    /// No one is fronting (passive, autopilot)
404    None,
405    /// Single alter fronting
406    Single(String),
407    /// Multiple alters co-fronting
408    Blended(Vec<String>),
409    /// Rapid switching between alters
410    Rapid(Vec<String>),
411    /// Unknown/unclear who is fronting
412    Unknown,
413}
414
415// ============================================================================
416// REALITY LAYERS
417// ============================================================================
418
419/// Reality perception layer
420#[derive(Debug, Clone, PartialEq, Eq, Hash)]
421pub enum RealityLayer {
422    /// Normal perception, grounded in consensus reality
423    Grounded,
424    /// Fractured perception - distorted, symbolic overlays
425    Fractured,
426    /// Completely shattered - full symbolic/hallucinatory experience
427    Shattered,
428    /// Custom reality layer
429    Custom(String),
430}
431
432// ============================================================================
433// TRIGGERS
434// ============================================================================
435
436/// Unique identifier for a trigger type
437pub type TriggerId = String;
438
439/// A trigger event that can affect the system
440#[derive(Debug, Clone)]
441pub struct Trigger {
442    /// Unique identifier for this trigger type
443    pub id: TriggerId,
444    /// Display name
445    pub name: String,
446    /// Trigger category
447    pub category: TriggerCategory,
448    /// Intensity of the trigger (0.0 to 1.0)
449    pub intensity: f32,
450    /// Additional context/data
451    pub context: HashMap<String, String>,
452}
453
454/// Categories of triggers
455#[derive(Debug, Clone, PartialEq, Eq)]
456pub enum TriggerCategory {
457    /// Environmental trigger (sound, smell, place)
458    Environmental,
459    /// Social trigger (person, interaction type)
460    Social,
461    /// Internal trigger (thought, memory, emotion)
462    Internal,
463    /// Physical trigger (sensation, pain, touch)
464    Physical,
465    /// Temporal trigger (time of day, anniversary)
466    Temporal,
467    /// Combat trigger (threat, violence)
468    Combat,
469    /// Custom category
470    Custom(String),
471}
472
473/// Result of processing a trigger
474#[derive(Debug, Clone)]
475pub enum TriggerResult {
476    /// No alters responded to the trigger
477    NoResponse,
478    /// Trigger activated one or more alters
479    Activation(Vec<String>),
480    /// Trigger caused a forced switch
481    ForcedSwitch(String),
482    /// System dissociated in response
483    Dissociation,
484}
485
486// ============================================================================
487// SWITCH RESULT
488// ============================================================================
489
490/// Result of a switch attempt
491#[derive(Debug, Clone)]
492pub enum SwitchResult {
493    /// Switch succeeded
494    Success,
495    /// Switch was resisted
496    Resisted { resistance: f32 },
497    /// Switch failed
498    Failed(SwitchFailReason),
499    /// Switch is in progress (async)
500    InProgress { eta: u64 },
501}
502
503/// Reasons a switch can fail
504#[derive(Debug, Clone, PartialEq)]
505pub enum SwitchFailReason {
506    /// Target alter doesn't exist
507    UnknownAlter,
508    /// System is too dissociated
509    TooDisassociated,
510    /// System is unstable
511    SystemUnstable,
512    /// Current fronter is refusing
513    CurrentRefused,
514    /// Target alter is unavailable
515    TargetUnavailable,
516    /// External barrier (game mechanic)
517    Blocked(String),
518}
519
520// ============================================================================
521// HEADSPACE
522// ============================================================================
523
524/// State of the internal headspace/inner world
525#[derive(Debug, Clone, Default)]
526pub struct HeadspaceState {
527    /// Current active location
528    pub current_location: Option<String>,
529    /// Alters present at current location
530    pub present_alters: Vec<String>,
531    /// Active navigation path
532    pub navigation_path: Vec<String>,
533    /// Hazards in the current area
534    pub active_hazards: Vec<String>,
535    /// Weather/atmosphere
536    pub atmosphere: HeadspaceAtmosphere,
537}
538
539/// Atmospheric conditions in the headspace
540#[derive(Debug, Clone, Default)]
541pub struct HeadspaceAtmosphere {
542    /// Clarity (0.0 foggy to 1.0 clear)
543    pub clarity: f32,
544    /// Stability (0.0 chaotic to 1.0 stable)
545    pub stability: f32,
546    /// Lighting (0.0 dark to 1.0 bright)
547    pub lighting: f32,
548    /// Custom atmosphere effects
549    pub effects: Vec<String>,
550}
551
552// ============================================================================
553// CO-CONSCIOUSNESS CHANNEL
554// ============================================================================
555
556/// A communication channel between co-conscious alters
557#[derive(Debug, Clone)]
558pub struct CoConChannel {
559    /// Participating alters
560    pub participants: Vec<String>,
561    /// Channel quality (0.0 to 1.0)
562    pub quality: f32,
563    /// Messages in the channel
564    pub messages: Vec<CoConMessage>,
565    /// Whether the channel is currently active
566    pub active: bool,
567}
568
569/// A message in a co-con channel
570#[derive(Debug, Clone)]
571pub struct CoConMessage {
572    /// Sender alter id
573    pub from: String,
574    /// Message content
575    pub content: String,
576    /// Certainty of the message (evidentiality)
577    pub certainty: f32,
578    /// Timestamp (game time)
579    pub timestamp: u64,
580}
581
582// ============================================================================
583// TESTS
584// ============================================================================
585
586#[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}