Skip to main content

narrative_engine/schema/
relationship.rs

1use rustc_hash::FxHashSet;
2use serde::{Deserialize, Serialize};
3
4use super::entity::EntityId;
5
6/// A typed, directional edge between two entities with a numerical
7/// intensity value. The engine uses these to select appropriate language
8/// without understanding the game's specific relationship semantics.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Relationship {
11    pub source: EntityId,
12    pub target: EntityId,
13    pub rel_type: String,
14    /// Intensity of the relationship, clamped to 0.0..=1.0.
15    pub intensity: f32,
16    pub tags: FxHashSet<String>,
17}
18
19impl Relationship {
20    /// Create a new relationship with intensity clamped to 0.0..=1.0.
21    pub fn new(
22        source: EntityId,
23        target: EntityId,
24        rel_type: String,
25        intensity: f32,
26        tags: FxHashSet<String>,
27    ) -> Self {
28        Self {
29            source,
30            target,
31            rel_type,
32            intensity: intensity.clamp(0.0, 1.0),
33            tags,
34        }
35    }
36}
37
38#[cfg(test)]
39mod tests {
40    use super::*;
41
42    #[test]
43    fn relationship_creation() {
44        let rel = Relationship::new(
45            EntityId(1),
46            EntityId(2),
47            "rival".to_string(),
48            0.8,
49            FxHashSet::default(),
50        );
51        assert_eq!(rel.rel_type, "rival");
52        assert!((rel.intensity - 0.8).abs() < f32::EPSILON);
53    }
54
55    #[test]
56    fn intensity_clamped_high() {
57        let rel = Relationship::new(
58            EntityId(1),
59            EntityId(2),
60            "ally".to_string(),
61            1.5,
62            FxHashSet::default(),
63        );
64        assert!((rel.intensity - 1.0).abs() < f32::EPSILON);
65    }
66
67    #[test]
68    fn intensity_clamped_low() {
69        let rel = Relationship::new(
70            EntityId(1),
71            EntityId(2),
72            "stranger".to_string(),
73            -0.3,
74            FxHashSet::default(),
75        );
76        assert!((rel.intensity - 0.0).abs() < f32::EPSILON);
77    }
78
79    #[test]
80    fn relationship_with_tags() {
81        let mut tags = FxHashSet::default();
82        tags.insert("secret".to_string());
83        tags.insert("deteriorating".to_string());
84        let rel = Relationship::new(EntityId(1), EntityId(2), "lover".to_string(), 0.9, tags);
85        assert!(rel.tags.contains("secret"));
86        assert!(rel.tags.contains("deteriorating"));
87        assert_eq!(rel.tags.len(), 2);
88    }
89}