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