Skip to main content

proof_engine/worldgen/
mythology.rs

1//! Mythology generation — narrative grammars producing creation myths,
2//! hero stories, and prophecies from civilization and language data.
3
4use super::Rng;
5use super::history::{Civilization, EventType};
6use super::language::Language;
7
8/// Myth category.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum MythType {
11    Creation, Flood, HeroJourney, Prophecy, Trickster, War,
12    Origin, Apocalypse, Romance, Curse, Gift, Transformation,
13}
14
15/// A generated myth.
16#[derive(Debug, Clone)]
17pub struct Myth {
18    pub id: u32,
19    pub myth_type: MythType,
20    pub title: String,
21    pub narrative: String,
22    pub source_civ: u32,
23    pub characters: Vec<MythCharacter>,
24    pub moral: String,
25    pub related_events: Vec<i32>,
26}
27
28/// A character in a myth.
29#[derive(Debug, Clone)]
30pub struct MythCharacter {
31    pub name: String,
32    pub role: MythRole,
33    pub epithet: String,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub enum MythRole {
38    Creator, Destroyer, Hero, Trickster, Monster, Lover, Sage, Martyr, King, Prophet,
39}
40
41/// Generate myths from civilizations and languages.
42pub fn generate(civs: &[Civilization], languages: &[Language], rng: &mut Rng) -> Vec<Myth> {
43    let mut myths = Vec::new();
44    let mut next_id = 0u32;
45
46    for civ in civs {
47        let lang = languages.iter().find(|l| l.owner_civ == civ.id)
48            .or_else(|| languages.first());
49
50        // Every civilization gets a creation myth
51        myths.push(generate_creation_myth(next_id, civ, lang, rng));
52        next_id += 1;
53
54        // Hero myths from major events
55        for event in &civ.historical_events {
56            if matches!(event.event_type, EventType::War | EventType::HeroRise | EventType::GoldenAge) {
57                if rng.coin(0.4) {
58                    myths.push(generate_hero_myth(next_id, civ, event, lang, rng));
59                    next_id += 1;
60                }
61            }
62        }
63
64        // Prophecy
65        if rng.coin(0.6) {
66            myths.push(generate_prophecy(next_id, civ, lang, rng));
67            next_id += 1;
68        }
69    }
70
71    myths
72}
73
74fn generate_creation_myth(id: u32, civ: &Civilization, lang: Option<&Language>, rng: &mut Rng) -> Myth {
75    let creator_name = myth_name(lang, rng);
76    let world_name = myth_name(lang, rng);
77
78    let templates = [
79        format!("In the beginning, {} shaped {} from the void. From chaos came order, and from order came life.", creator_name, world_name),
80        format!("{} dreamed, and the dream became {}. The mountains are {} sleeping thoughts, and the rivers are tears of joy.", creator_name, world_name, creator_name),
81        format!("Two forces clashed: {} the maker and the void. Where they met, {} was born — imperfect but alive.", creator_name, world_name),
82        format!("{} spoke a single word, and {} erupted from silence. Each syllable became a mountain, each pause a valley.", creator_name, world_name),
83        format!("From the death of the old world, {} gathered the fragments and forged {}. We are the children of destruction reborn.", creator_name, world_name),
84    ];
85
86    let narrative = templates[rng.next_u64() as usize % templates.len()].clone();
87
88    Myth {
89        id,
90        myth_type: MythType::Creation,
91        title: format!("The Making of {}", world_name),
92        narrative,
93        source_civ: civ.id,
94        characters: vec![MythCharacter {
95            name: creator_name,
96            role: MythRole::Creator,
97            epithet: "the First".to_string(),
98        }],
99        moral: "From nothing, all things come.".to_string(),
100        related_events: Vec::new(),
101    }
102}
103
104fn generate_hero_myth(id: u32, civ: &Civilization, event: &super::history::HistoricalEvent, lang: Option<&Language>, rng: &mut Rng) -> Myth {
105    let hero_name = myth_name(lang, rng);
106    let villain_name = myth_name(lang, rng);
107
108    let narrative = format!(
109        "In the year of {}, {} rose from humble origins. Armed with nothing but courage, \
110         {} faced the terrible {} and prevailed through {}. The people of {} remember this deed in song.",
111        event.year, hero_name, hero_name, villain_name,
112        if rng.coin(0.5) { "wisdom" } else { "strength" },
113        civ.name
114    );
115
116    Myth {
117        id,
118        myth_type: MythType::HeroJourney,
119        title: format!("The Saga of {}", hero_name),
120        narrative,
121        source_civ: civ.id,
122        characters: vec![
123            MythCharacter { name: hero_name, role: MythRole::Hero, epithet: "the Brave".to_string() },
124            MythCharacter { name: villain_name, role: MythRole::Monster, epithet: "the Terrible".to_string() },
125        ],
126        moral: "Even the smallest can change the fate of the world.".to_string(),
127        related_events: vec![event.year],
128    }
129}
130
131fn generate_prophecy(id: u32, civ: &Civilization, lang: Option<&Language>, rng: &mut Rng) -> Myth {
132    let prophet_name = myth_name(lang, rng);
133    let harbinger = myth_name(lang, rng);
134
135    let templates = [
136        format!("When {} walks the earth again, the old order shall crumble and a new age shall dawn.", harbinger),
137        format!("{} foretold: 'Three signs shall mark the end — a star that bleeds, a king who weeps, and a door that opens into nothing.'", prophet_name),
138        format!("The scrolls of {} speak of a convergence, when all rivers flow backward and the dead remember their names.", prophet_name),
139    ];
140
141    let narrative = templates[rng.next_u64() as usize % templates.len()].clone();
142
143    Myth {
144        id,
145        myth_type: MythType::Prophecy,
146        title: format!("The Prophecy of {}", prophet_name),
147        narrative,
148        source_civ: civ.id,
149        characters: vec![
150            MythCharacter { name: prophet_name, role: MythRole::Prophet, epithet: "the Seer".to_string() },
151        ],
152        moral: "The future is written but never read clearly.".to_string(),
153        related_events: Vec::new(),
154    }
155}
156
157fn myth_name(lang: Option<&Language>, rng: &mut Rng) -> String {
158    if let Some(lang) = lang {
159        let syllables = rng.range_usize(2, 4);
160        let word = lang.generate_word(rng, syllables);
161        capitalize(&word)
162    } else {
163        let syllables = ["Zar", "Keth", "Mor", "Ael", "Vor", "Thi", "Dra", "Nym"];
164        let s1 = syllables[rng.next_u64() as usize % syllables.len()];
165        let s2 = syllables[rng.next_u64() as usize % syllables.len()];
166        format!("{}{}", s1, s2.to_lowercase())
167    }
168}
169
170fn capitalize(s: &str) -> String {
171    let mut c = s.chars();
172    match c.next() {
173        None => String::new(),
174        Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use super::*;
181
182    #[test]
183    fn test_generate_myths() {
184        let mut rng = Rng::new(42);
185        let civs = vec![Civilization {
186            id: 0, name: "TestCiv".to_string(), founding_year: -1000,
187            collapse_year: None, capital_settlement: 0, settlements: vec![0],
188            population: 10000, technology_level: 0.5, military_strength: 0.3,
189            culture_score: 0.5, trade_score: 0.3,
190            government: super::super::history::GovernmentType::Monarchy,
191            religion: super::super::history::ReligionType::Polytheism,
192            relations: Vec::new(),
193            historical_events: vec![super::super::history::HistoricalEvent {
194                year: -500, event_type: EventType::War,
195                description: "War".to_string(), participants: vec![0],
196            }],
197            traits: Vec::new(),
198        }];
199        let langs = super::super::language::generate(1, &civs, &mut rng);
200        let myths = generate(&civs, &langs, &mut rng);
201        assert!(!myths.is_empty());
202        assert!(myths.iter().any(|m| m.myth_type == MythType::Creation));
203    }
204}