Skip to main content

dino_park/
dino_park.rs

1/// Dino Park example — demonstrates the Survival Thriller genre template.
2///
3/// A sequence: routine status → power warning → perimeter breach → escalation →
4///             discovery of damage → critical failure.
5///
6/// Uses radio_operator and narrator_omniscient voices to alternate between
7/// terse status reports and atmospheric narration.
8///
9/// Run with: cargo run --example dino_park
10use narrative_engine::core::grammar::GrammarSet;
11use narrative_engine::core::markov::MarkovTrainer;
12use narrative_engine::core::pipeline::{NarrativeEngine, WorldState};
13use narrative_engine::core::voice::VoiceRegistry;
14use narrative_engine::schema::entity::{Entity, EntityId, Pronouns, VoiceId};
15use narrative_engine::schema::event::{EntityRef, Event, Mood, Stakes};
16use narrative_engine::schema::narrative_fn::NarrativeFunction;
17use std::collections::HashMap;
18
19fn main() {
20    // --- Load Survival Thriller genre template ---
21    let grammars = GrammarSet::load_from_ron(std::path::Path::new(
22        "genre_data/survival_thriller/grammar.ron",
23    ))
24    .expect("Failed to load survival thriller grammar");
25
26    let mut voices = VoiceRegistry::new();
27    voices
28        .load_from_ron(std::path::Path::new(
29            "genre_data/survival_thriller/voices.ron",
30        ))
31        .expect("Failed to load survival thriller voices");
32
33    // --- Train Markov model from survival thriller corpus ---
34    let corpus = std::fs::read_to_string("genre_data/survival_thriller/corpus.txt")
35        .expect("Failed to read survival thriller corpus");
36    let markov_model = MarkovTrainer::train(&corpus, 3);
37
38    let mut markov_models = HashMap::new();
39    markov_models.insert("survival_thriller".to_string(), markov_model);
40
41    let mut engine = NarrativeEngine::builder()
42        .seed(1993)
43        .with_grammars(grammars)
44        .with_voices(voices)
45        .with_markov_models(markov_models)
46        .build()
47        .expect("Failed to build engine");
48
49    // --- Define entities ---
50    let mut entities = HashMap::new();
51
52    // Dr. Grant — paleontologist, survivor instinct
53    entities.insert(
54        EntityId(1),
55        Entity {
56            id: EntityId(1),
57            name: "Dr. Grant".to_string(),
58            pronouns: Pronouns::HeHim,
59            tags: [
60                "scientist".to_string(),
61                "determined".to_string(),
62                "field_expert".to_string(),
63            ]
64            .into_iter()
65            .collect(),
66            relationships: Vec::new(),
67            voice_id: Some(VoiceId(202)), // scientist voice
68            properties: HashMap::new(),
69        },
70    );
71
72    // Dr. Malcolm — chaos theorist, always right at the worst time
73    entities.insert(
74        EntityId(2),
75        Entity {
76            id: EntityId(2),
77            name: "Dr. Malcolm".to_string(),
78            pronouns: Pronouns::HeHim,
79            tags: [
80                "scientist".to_string(),
81                "skeptic".to_string(),
82                "charismatic".to_string(),
83            ]
84            .into_iter()
85            .collect(),
86            relationships: Vec::new(),
87            voice_id: Some(VoiceId(202)), // scientist voice
88            properties: HashMap::new(),
89        },
90    );
91
92    // Muldoon — game warden, knows the danger
93    entities.insert(
94        EntityId(3),
95        Entity {
96            id: EntityId(3),
97            name: "Muldoon".to_string(),
98            pronouns: Pronouns::HeHim,
99            tags: [
100                "hunter".to_string(),
101                "pragmatic".to_string(),
102                "alert".to_string(),
103            ]
104            .into_iter()
105            .collect(),
106            relationships: Vec::new(),
107            voice_id: Some(VoiceId(201)), // survivor voice
108            properties: HashMap::new(),
109        },
110    );
111
112    // Control Room — the nerve center
113    entities.insert(
114        EntityId(10),
115        Entity {
116            id: EntityId(10),
117            name: "Control Room".to_string(),
118            pronouns: Pronouns::ItIts,
119            tags: [
120                "location".to_string(),
121                "technology".to_string(),
122                "enclosed".to_string(),
123            ]
124            .into_iter()
125            .collect(),
126            relationships: Vec::new(),
127            voice_id: None,
128            properties: HashMap::new(),
129        },
130    );
131
132    // Rex Paddock — T. rex enclosure
133    entities.insert(
134        EntityId(11),
135        Entity {
136            id: EntityId(11),
137            name: "Rex Paddock".to_string(),
138            pronouns: Pronouns::ItIts,
139            tags: [
140                "location".to_string(),
141                "dangerous".to_string(),
142                "perimeter".to_string(),
143            ]
144            .into_iter()
145            .collect(),
146            relationships: Vec::new(),
147            voice_id: None,
148            properties: HashMap::new(),
149        },
150    );
151
152    // Raptor Pen — velociraptors
153    entities.insert(
154        EntityId(12),
155        Entity {
156            id: EntityId(12),
157            name: "Raptor Pen".to_string(),
158            pronouns: Pronouns::ItIts,
159            tags: [
160                "location".to_string(),
161                "dangerous".to_string(),
162                "high_security".to_string(),
163            ]
164            .into_iter()
165            .collect(),
166            relationships: Vec::new(),
167            voice_id: None,
168            properties: HashMap::new(),
169        },
170    );
171
172    // Security System — abstract entity
173    entities.insert(
174        EntityId(20),
175        Entity {
176            id: EntityId(20),
177            name: "Security System".to_string(),
178            pronouns: Pronouns::ItIts,
179            tags: ["system".to_string(), "automated".to_string()]
180                .into_iter()
181                .collect(),
182            relationships: Vec::new(),
183            voice_id: None,
184            properties: HashMap::new(),
185        },
186    );
187
188    let world = WorldState {
189        entities: &entities,
190    };
191
192    // --- Title ---
193    println!("========================================");
194    println!("   DINO PARK INCIDENT REPORT");
195    println!("   [CLASSIFIED — Park Security]");
196    println!("========================================");
197    println!();
198
199    // --- Scene 1: Routine Status (StatusChange — neutral, low stakes) ---
200    // radio_operator voice — terse, technical
201    let event1 = Event {
202        event_type: "routine_check".to_string(),
203        participants: vec![EntityRef {
204            entity_id: EntityId(3),
205            role: "subject".to_string(),
206        }],
207        location: Some(EntityRef {
208            entity_id: EntityId(10),
209            role: "location".to_string(),
210        }),
211        mood: Mood::Neutral,
212        stakes: Stakes::Low,
213        outcome: None,
214        narrative_fn: NarrativeFunction::StatusChange,
215        metadata: HashMap::new(),
216    };
217    print_scene(
218        1,
219        "0600 — Morning Status Report",
220        "RADIO OPERATOR",
221        &mut engine,
222        &event1,
223        &world,
224        Some(VoiceId(200)),
225    );
226
227    // --- Scene 2: Power Warning (Foreshadowing — neutral/dread, medium stakes) ---
228    // narrator_omniscient voice — atmospheric
229    let event2 = Event {
230        event_type: "power_fluctuation".to_string(),
231        participants: vec![EntityRef {
232            entity_id: EntityId(1),
233            role: "subject".to_string(),
234        }],
235        location: Some(EntityRef {
236            entity_id: EntityId(10),
237            role: "location".to_string(),
238        }),
239        mood: Mood::Neutral,
240        stakes: Stakes::Medium,
241        outcome: None,
242        narrative_fn: NarrativeFunction::Foreshadowing,
243        metadata: HashMap::new(),
244    };
245    print_scene(
246        2,
247        "1430 — Power Fluctuation Detected",
248        "NARRATOR",
249        &mut engine,
250        &event2,
251        &world,
252        Some(VoiceId(203)),
253    );
254
255    // --- Scene 3: Perimeter Breach (Escalation — dread, high stakes) ---
256    // radio_operator voice
257    let event3 = Event {
258        event_type: "perimeter_breach".to_string(),
259        participants: vec![EntityRef {
260            entity_id: EntityId(3),
261            role: "subject".to_string(),
262        }],
263        location: Some(EntityRef {
264            entity_id: EntityId(11),
265            role: "location".to_string(),
266        }),
267        mood: Mood::Dread,
268        stakes: Stakes::High,
269        outcome: None,
270        narrative_fn: NarrativeFunction::Escalation,
271        metadata: HashMap::new(),
272    };
273    print_scene(
274        3,
275        "2247 — Perimeter Breach: Rex Paddock",
276        "RADIO OPERATOR",
277        &mut engine,
278        &event3,
279        &world,
280        Some(VoiceId(200)),
281    );
282
283    // --- Scene 4: Escalation (Escalation — dread/chaotic, critical) ---
284    // narrator_omniscient — full atmospheric dread
285    let event4 = Event {
286        event_type: "systems_failing".to_string(),
287        participants: vec![
288            EntityRef {
289                entity_id: EntityId(1),
290                role: "subject".to_string(),
291            },
292            EntityRef {
293                entity_id: EntityId(2),
294                role: "object".to_string(),
295            },
296        ],
297        location: Some(EntityRef {
298            entity_id: EntityId(10),
299            role: "location".to_string(),
300        }),
301        mood: Mood::Chaotic,
302        stakes: Stakes::Critical,
303        outcome: None,
304        narrative_fn: NarrativeFunction::Escalation,
305        metadata: HashMap::new(),
306    };
307    print_scene(
308        4,
309        "2253 — Multiple System Failures",
310        "NARRATOR",
311        &mut engine,
312        &event4,
313        &world,
314        Some(VoiceId(203)),
315    );
316
317    // --- Scene 5: Discovery of Damage (Discovery — dread, high stakes) ---
318    // Dr. Grant (scientist voice) discovers the extent
319    let event5 = Event {
320        event_type: "damage_assessment".to_string(),
321        participants: vec![EntityRef {
322            entity_id: EntityId(1),
323            role: "subject".to_string(),
324        }],
325        location: Some(EntityRef {
326            entity_id: EntityId(12),
327            role: "location".to_string(),
328        }),
329        mood: Mood::Dread,
330        stakes: Stakes::High,
331        outcome: None,
332        narrative_fn: NarrativeFunction::Discovery,
333        metadata: HashMap::new(),
334    };
335    print_scene(
336        5,
337        "2301 — Discovery: Raptor Pen Integrity",
338        "DR. GRANT",
339        &mut engine,
340        &event5,
341        &world,
342        None,
343    );
344
345    // --- Scene 6: Loss (Loss — somber, critical) ---
346    // radio_operator — the final status
347    let event6 = Event {
348        event_type: "critical_failure".to_string(),
349        participants: vec![EntityRef {
350            entity_id: EntityId(3),
351            role: "subject".to_string(),
352        }],
353        location: Some(EntityRef {
354            entity_id: EntityId(10),
355            role: "location".to_string(),
356        }),
357        mood: Mood::Somber,
358        stakes: Stakes::Critical,
359        outcome: None,
360        narrative_fn: NarrativeFunction::Loss,
361        metadata: HashMap::new(),
362    };
363    print_scene(
364        6,
365        "2315 — Critical Failure: All Systems",
366        "RADIO OPERATOR",
367        &mut engine,
368        &event6,
369        &world,
370        Some(VoiceId(200)),
371    );
372
373    // --- Scene 7: Final atmospheric beat ---
374    // narrator_omniscient — the island at night
375    let event7 = Event {
376        event_type: "aftermath".to_string(),
377        participants: vec![EntityRef {
378            entity_id: EntityId(2),
379            role: "subject".to_string(),
380        }],
381        location: Some(EntityRef {
382            entity_id: EntityId(11),
383            role: "location".to_string(),
384        }),
385        mood: Mood::Dread,
386        stakes: Stakes::Critical,
387        outcome: None,
388        narrative_fn: NarrativeFunction::Loss,
389        metadata: HashMap::new(),
390    };
391    print_scene(
392        7,
393        "2330 — Final Log Entry",
394        "NARRATOR",
395        &mut engine,
396        &event7,
397        &world,
398        Some(VoiceId(203)),
399    );
400
401    println!("========================================");
402    println!("   [END OF INCIDENT REPORT]");
403    println!("   [STATUS: FACILITY ABANDONED]");
404    println!("========================================");
405}
406
407fn print_scene(
408    _number: u32,
409    title: &str,
410    voice_label: &str,
411    engine: &mut NarrativeEngine,
412    event: &Event,
413    world: &WorldState<'_>,
414    voice_override: Option<VoiceId>,
415) {
416    println!("--- {} ---", title);
417    println!(
418        "[Voice: {} | {} | {}]",
419        voice_label,
420        event.mood.tag().strip_prefix("mood:").unwrap_or("?"),
421        event.stakes.tag().strip_prefix("stakes:").unwrap_or("?"),
422    );
423    println!();
424
425    let result = if let Some(vid) = voice_override {
426        engine.narrate_as(event, vid, world)
427    } else {
428        engine.narrate(event, world)
429    };
430
431    match result {
432        Ok(text) => println!("{}", text),
433        Err(e) => println!("[Generation error: {}]", e),
434    }
435
436    println!();
437    println!();
438}