Skip to main content

proof_engine/narrative/
drama.rs

1//! Drama management — pacing system using narrative arc templates.
2
3/// Narrative tension state.
4#[derive(Debug, Clone)]
5pub struct DramaState {
6    pub tension: f32,
7    pub target_tension: f32,
8    pub arc_position: f32,
9    pub beats_since_action: u32,
10    pub beats_since_quiet: u32,
11}
12
13impl DramaState {
14    pub fn new() -> Self {
15        Self { tension: 0.2, target_tension: 0.2, arc_position: 0.0, beats_since_action: 0, beats_since_quiet: 0 }
16    }
17
18    /// Update tension toward target, following a narrative arc.
19    pub fn tick(&mut self, dt: f32) {
20        self.arc_position = (self.arc_position + dt * 0.01).min(1.0);
21        self.target_tension = narrative_arc(self.arc_position);
22        let rate = if self.tension < self.target_tension { 0.3 } else { 0.15 };
23        self.tension += (self.target_tension - self.tension) * rate * dt;
24        self.tension = self.tension.clamp(0.0, 1.0);
25    }
26
27    /// Should we inject an action beat?
28    pub fn needs_action(&self) -> bool {
29        self.beats_since_action > 5 && self.tension < self.target_tension
30    }
31
32    /// Should we inject a quiet moment?
33    pub fn needs_quiet(&self) -> bool {
34        self.beats_since_quiet > 3 && self.tension > self.target_tension + 0.2
35    }
36
37    pub fn on_action(&mut self) { self.beats_since_action = 0; self.beats_since_quiet += 1; }
38    pub fn on_quiet(&mut self) { self.beats_since_quiet = 0; self.beats_since_action += 1; }
39}
40
41/// Standard three-act narrative arc: setup (low) → rising → climax → falling → resolution.
42fn narrative_arc(t: f32) -> f32 {
43    if t < 0.15 { t * 2.0 }
44    else if t < 0.6 { 0.3 + (t - 0.15) * 1.2 }
45    else if t < 0.75 { 0.84 + (t - 0.6) * 1.0 }
46    else { 1.0 - (t - 0.75) * 3.0 }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn test_arc_shape() {
55        assert!(narrative_arc(0.0) < narrative_arc(0.5));
56        assert!(narrative_arc(0.7) > narrative_arc(0.0));
57        assert!(narrative_arc(1.0) < narrative_arc(0.7));
58    }
59
60    #[test]
61    fn test_drama_tick() {
62        let mut d = DramaState::new();
63        for _ in 0..100 { d.tick(1.0); }
64        assert!(d.tension > 0.0);
65    }
66}