chronicle/model.rs
1//! Core data model: entity types, relationships, and graph structure.
2
3use serde::{Deserialize, Serialize};
4
5// ── Entity ID ──────────────────────────────────────────────
6
7/// Stable string identifier for any entity in the graph.
8pub type EntityId = String;
9
10// ── Time ───────────────────────────────────────────────────
11
12/// A span of time. Both bounds are inclusive integer years.
13/// For point events, start == end.
14#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
15pub struct TimeSpan {
16 /// First year of the span (inclusive).
17 pub start: i32,
18 /// Last year of the span (inclusive).
19 pub end: i32,
20}
21
22// ── Status ─────────────────────────────────────────────────
23
24/// Current state of an entity, potentially changed by events.
25#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
26pub enum Status {
27 /// Entity is alive / operational / intact.
28 Active,
29 /// Character has died (terminal by default).
30 Dead,
31 /// Place has been destroyed (terminal by default).
32 Destroyed,
33 /// Faction or organization has been dissolved (terminal by default).
34 Dissolved,
35 /// Place has been evacuated but still exists.
36 Evacuated,
37 /// Entity has been captured by another force.
38 Captured,
39 /// Entity has fundamentally changed form.
40 Transformed,
41 /// Entity has been corrupted or tainted.
42 Corrupted,
43 /// Status is not yet established.
44 Unknown,
45}
46
47// ── Sentiment ──────────────────────────────────────────────
48
49/// How a participant feels about an event they were involved in.
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
51pub enum Sentiment {
52 /// Victorious elation.
53 Triumphant,
54 /// Justified by the outcome.
55 Vindicated,
56 /// Fulfilled obligation without strong emotion.
57 Dutiful,
58 /// Resolved to continue despite difficulty.
59 Determined,
60 /// Morally certain of the cause.
61 Righteous,
62 /// Fundamentally changed by the experience.
63 Transformative,
64 /// No strong feeling either way.
65 Neutral,
66 /// Grief or mourning.
67 Sorrowful,
68 /// Overcome by fear.
69 Fearful,
70 /// Acting out of last-resort urgency.
71 Desperate,
72 /// Resisting despite the odds.
73 Defiant,
74 /// Emotionally shattered by the event.
75 Devastating,
76 /// Bitter anger toward the outcome or participants.
77 Resentful,
78 /// Internally divided or broken by the event.
79 Fractured,
80}
81
82// ── Participant Role ───────────────────────────────────────
83
84/// The role an actor plays in an event.
85#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
86pub enum Role {
87 /// Initiated or led an offensive action.
88 Attacker,
89 /// Resisted or protected against an action.
90 Defender,
91 /// Commanded or directed the event.
92 Leader,
93 /// Took part without a specialized role.
94 Participant,
95 /// Observed without direct involvement.
96 Witness,
97 /// Made a discovery or revelation.
98 Discoverer,
99 /// Provoked or triggered the event.
100 Instigator,
101 /// Suffered harm as a result of the event.
102 Victim,
103 /// Attempted to resolve conflict between parties.
104 Mediator,
105}
106
107// ── Fidelity ───────────────────────────────────────────────
108
109/// How closely a subjective account matches the objective graph.
110#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
111pub enum Fidelity {
112 /// Matches objective graph exactly (e.g., archive-drone with intact records).
113 Canonical,
114 /// Omissions but no fabrication (e.g., eyewitness with incomplete view).
115 Partial,
116 /// Genuine misremembering (e.g., elderly NPC).
117 Distorted,
118 /// Deliberate spin, selective truth (e.g., faction propagandist).
119 Biased,
120 /// Intentional lies (e.g., trickster NPC, corrupted record).
121 Fabricated,
122 /// Damaged/degraded record (e.g., storm-damaged archive-drone).
123 Corrupted,
124}
125
126// ── State Change ───────────────────────────────────────────
127
128/// A change to an entity's state caused by an event.
129#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
130pub enum StateChange {
131 /// Entity transitions to a new [`Status`].
132 StatusChange(Status),
133 /// Entity is newly created or established.
134 Founded,
135 /// Entity gains an affiliation with the referenced entity.
136 AffiliationAdded(EntityId),
137 /// Entity loses an affiliation with the referenced entity.
138 AffiliationRemoved(EntityId),
139}
140
141// ── Event Participant ──────────────────────────────────────
142
143/// An actor's participation in an event, with role and emotional response.
144#[derive(Debug, Clone, Serialize, Deserialize)]
145pub struct Participant {
146 /// The actor who participated.
147 pub actor: EntityId,
148 /// What role the actor played.
149 pub role: Role,
150 /// How the actor felt about the event.
151 pub sentiment: Sentiment,
152}
153
154// ── Event State Change Record ──────────────────────────────
155
156/// Links a [`StateChange`] to the entity it affects within an event.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct EventStateChange {
159 /// The entity whose state changed.
160 pub entity: EntityId,
161 /// The change that occurred.
162 pub change: StateChange,
163}
164
165// ── Entity Types ───────────────────────────────────────────
166
167/// Classification of an [`Actor`].
168#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
169pub enum ActorType {
170 /// An individual person or named character.
171 Character,
172 /// A political, military, or ideological group.
173 Faction,
174 /// A divine or supernatural being.
175 Deity,
176 /// A structured group (guild, company, order).
177 Organization,
178 /// A non-humanoid living entity.
179 Creature,
180}
181
182/// Classification of a [`Place`].
183#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
184pub enum PlaceType {
185 /// A city, town, or village.
186 Settlement,
187 /// A broad geographic area containing other places.
188 Region,
189 /// An underground or enclosed adventure site.
190 Dungeon,
191 /// A notable geographic or constructed feature.
192 Landmark,
193 /// A destroyed or abandoned former settlement.
194 Ruin,
195}
196
197/// Classification of an [`Event`].
198#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
199pub enum EventType {
200 /// Open armed conflict.
201 Battle,
202 /// Prolonged encirclement or assault on a fortified place.
203 Siege,
204 /// Revelation of new knowledge or territory.
205 Discovery,
206 /// Governance, diplomacy, or power-structure change.
207 Political,
208 /// Large-scale movement of people.
209 Migration,
210 /// The death of a significant entity.
211 Death,
212 /// Establishment of a new settlement, faction, or institution.
213 Founding,
214 /// A large-scale disaster (natural or otherwise).
215 Catastrophe,
216 /// A magical, religious, or ceremonial event.
217 Ritual,
218}
219
220/// Classification of a [`Concept`].
221#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
222pub enum ConceptType {
223 /// A belief system or faith.
224 Religion,
225 /// A technique, invention, or body of knowledge.
226 Technology,
227 /// A unique or significant object.
228 Artifact,
229 /// A codified rule or legal framework.
230 Law,
231 /// A cultural practice or custom.
232 Tradition,
233}
234
235// ── Entities (RON-deserializable) ──────────────────────────
236
237/// A character, faction, deity, organization, or creature.
238#[derive(Debug, Clone, Serialize, Deserialize)]
239pub struct Actor {
240 /// Unique identifier for this actor.
241 pub id: EntityId,
242 /// Display name.
243 pub name: String,
244 /// What kind of actor this is.
245 #[serde(rename = "type")]
246 pub actor_type: ActorType,
247 /// Current status (defaults to [`Status::Active`]).
248 #[serde(default = "default_status")]
249 pub status: Status,
250 /// The event that caused the current status, if any.
251 #[serde(default)]
252 pub status_since_event: Option<EntityId>,
253 /// Factions or organizations this actor belongs to.
254 #[serde(default)]
255 pub affiliations: Vec<EntityId>,
256 /// Birth-to-death (or founding-to-dissolution) time span.
257 #[serde(default)]
258 pub lifespan: Option<TimeSpan>,
259 /// Free-text description.
260 #[serde(default)]
261 pub description: String,
262}
263
264/// A settlement, region, dungeon, landmark, or ruin.
265#[derive(Debug, Clone, Serialize, Deserialize)]
266pub struct Place {
267 /// Unique identifier for this place.
268 pub id: EntityId,
269 /// Display name.
270 pub name: String,
271 /// What kind of place this is.
272 #[serde(rename = "type")]
273 pub place_type: PlaceType,
274 /// Current status (defaults to [`Status::Active`]).
275 #[serde(default = "default_status")]
276 pub status: Status,
277 /// The event that caused the current status, if any.
278 #[serde(default)]
279 pub status_since_event: Option<EntityId>,
280 /// Parent region this place is located within.
281 #[serde(default)]
282 pub region: Option<EntityId>,
283 /// Free-text description.
284 #[serde(default)]
285 pub description: String,
286}
287
288/// Something that happened: a battle, discovery, political shift, etc.
289#[derive(Debug, Clone, Serialize, Deserialize)]
290pub struct Event {
291 /// Unique identifier for this event.
292 pub id: EntityId,
293 /// Display name.
294 pub name: String,
295 /// What kind of event this is.
296 #[serde(rename = "type")]
297 pub event_type: EventType,
298 /// When the event occurred (inclusive year range).
299 pub time_span: TimeSpan,
300 /// Where the event took place, if applicable.
301 #[serde(default)]
302 pub location: Option<EntityId>,
303 /// Events that caused or led to this one.
304 #[serde(default)]
305 pub caused_by: Vec<EntityId>,
306 /// Actors involved and their roles.
307 #[serde(default)]
308 pub participants: Vec<Participant>,
309 /// State changes this event inflicted on entities.
310 #[serde(default)]
311 pub state_changes: Vec<EventStateChange>,
312 /// Free-text description.
313 #[serde(default)]
314 pub description: String,
315}
316
317/// An abstract idea: religion, technology, artifact, law, or tradition.
318#[derive(Debug, Clone, Serialize, Deserialize)]
319pub struct Concept {
320 /// Unique identifier for this concept.
321 pub id: EntityId,
322 /// Display name.
323 pub name: String,
324 /// What kind of concept this is.
325 #[serde(rename = "type")]
326 pub concept_type: ConceptType,
327 /// The event that introduced this concept, if any.
328 #[serde(default)]
329 pub origin_event: Option<EntityId>,
330 /// Free-text description.
331 #[serde(default)]
332 pub description: String,
333}
334
335/// A subjective narrative fragment attributed to a source actor.
336#[derive(Debug, Clone, Serialize, Deserialize)]
337pub struct Account {
338 /// Unique identifier for this account.
339 pub id: EntityId,
340 /// The actor who authored or narrated this account.
341 pub source: EntityId,
342 /// How reliable this account is relative to the objective graph.
343 pub fidelity: Fidelity,
344 /// Events this account describes or references.
345 #[serde(default)]
346 pub event_refs: Vec<EntityId>,
347 /// The narrative text, potentially containing `{entity_id}` references.
348 pub text: String,
349}
350
351fn default_status() -> Status {
352 Status::Active
353}
354
355// ── Validation Config ──────────────────────────────────────
356
357/// Policy configuration for validation rules.
358#[derive(Debug, Clone)]
359pub struct ValidationConfig {
360 /// Statuses that prevent an entity from participating in future events.
361 pub terminal_statuses: std::collections::HashSet<Status>,
362}
363
364impl Default for ValidationConfig {
365 fn default() -> Self {
366 Self {
367 terminal_statuses: [Status::Dead, Status::Destroyed, Status::Dissolved].into(),
368 }
369 }
370}
371
372// ── Graph Node / Edge Enums ────────────────────────────────
373
374/// A node in the chronicle graph.
375#[derive(Debug, Clone, Serialize, Deserialize)]
376pub enum Entity {
377 /// An actor node (character, faction, etc.).
378 Actor(Actor),
379 /// A place node (settlement, region, etc.).
380 Place(Place),
381 /// An event node (battle, discovery, etc.).
382 Event(Event),
383 /// A concept node (religion, artifact, etc.).
384 Concept(Concept),
385 /// A subjective account node.
386 Account(Account),
387}
388
389impl Entity {
390 /// Returns the [`EntityId`] of the wrapped entity.
391 pub fn id(&self) -> &str {
392 match self {
393 Entity::Actor(a) => &a.id,
394 Entity::Place(p) => &p.id,
395 Entity::Event(e) => &e.id,
396 Entity::Concept(c) => &c.id,
397 Entity::Account(a) => &a.id,
398 }
399 }
400}
401
402/// An edge in the chronicle graph.
403#[derive(Debug, Clone, Serialize, Deserialize)]
404pub enum Relationship {
405 /// Actor → Event
406 ParticipatedIn {
407 /// The role the actor played in the event.
408 role: Role,
409 /// How the actor felt about the event.
410 sentiment: Sentiment,
411 },
412 /// Event → Place
413 OccurredAt,
414 /// Event → Event (effect → cause)
415 CausedBy,
416 /// Event → affected Entity
417 HasStateChange {
418 /// The state change that was applied.
419 change: StateChange,
420 },
421 /// Actor → Actor (member → faction)
422 AffiliatedWith,
423 /// Concept → Event
424 OriginatedFrom,
425 /// Account → Event
426 AccountOf,
427 /// Account → Actor (source)
428 AuthoredBy,
429 /// Account → any Entity (from {entity_id} refs in text)
430 Mentions,
431 /// Place → Place (child → parent region)
432 LocatedIn,
433}