sagaphone/spec/data-model.md
Tim 06df9df339 Initial spec for Sagaphone
Pulled from Notion project docs: overview, architecture, data model,
event system, line logic, and implementation notes for the family-tree
generator + TTS narrator.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 22:04:33 +02:00

180 lines
4.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Data Model
All identifiers and code are in English.
## Enums
```python
class Gender(IntEnum):
MALE = 0
FEMALE = 1
NONBINARY = 2
class PartnerType(IntEnum):
MARRIAGE = 0
AFFAIR = 1
POLITICAL = 2
ILLEGITIMATE = 3
class StageType(IntEnum):
CHILD = 0
TEEN = 1
YOUNG_ADULT = 2
ADULT = 3
SENIOR = 4
class Event(IntEnum):
# Category 1 — Death
DEATH_ILLNESS = 10
DEATH_ACCIDENT = 11
DEATH_COMBAT = 12
DEATH_OLD_AGE = 13
DEATH_CHILDBIRTH = 14 # mother dies during birth
# Category 2 — Partnership
PARTNER_MARRIAGE = 20
PARTNER_AFFAIR = 21
PARTNER_ENGAGEMENT = 22
PARTNER_POLITICAL = 23
PARTNER_CHILDHOOD_PROMISE = 24
# Category 3 — Offspring
CHILD_BORN = 30
CHILD_TWINS = 31
CHILD_TRIPLETS = 32
CHILD_STILLBORN = 33
CHILD_ADOPT = 34 # only path to offspring for NONBINARY; from YOUNG_ADULT onwards
# Category 4 — Daily Life
DAILY_FRIENDSHIP = 40
DAILY_FAMILY = 41
DAILY_TEMP_CHAR = 42
# Category 5 — Travel
TRAVEL_NEAR = 50
TRAVEL_FAR = 51
TRAVEL_PILGRIMAGE = 52
# Category 6 — Learning
LEARN_APPRENTICESHIP = 60
LEARN_MENTOR = 61
LEARN_SELF = 62
# Category 7 — Conflict (personal)
CONFLICT_PERSONAL = 70
CONFLICT_FIGHT = 71
CONFLICT_FEUD = 72
# Category 8 — Discovery
DISCOVER_OBJECT = 80
DISCOVER_SPELL = 81
DISCOVER_WONDER = 82
DISCOVER_KNOWLEDGE = 83
# Category 9 — Local Conflict
LOCAL_REBELLION = 90
LOCAL_PLAGUE = 91
LOCAL_DISASTER = 92
LOCAL_CRISIS = 93
# Category 10 — Intrigue
INTRIGUE_POLITICAL = 100
INTRIGUE_EXILE = 101
INTRIGUE_CONSPIRACY = 102
# Category 11 — Illness / Injury
ILLNESS_WEAK = 110
ILLNESS_SEVERE = 111
ILLNESS_CHRONIC = 112
INJURY_ACCIDENT = 113
INJURY_COMBAT = 114
```
`event_type // 10` → category, `event_type % 10` → subtype within category.
## Person
```python
Person {
id: str
name: str
gender: Gender
birth_year: int # absolute, set when person is created via parent event
death_year: int | None # birth_year + age_at_death
alive: bool
appearance: dict
profession: str | None # set by a LEARN event
home: str
is_ancestor: bool # True only for Person 1
parent_id: str | None # previous active node; None = joined from outside
generation: int
partners: PartnerEntry[]
events: str[] # event IDs in chronological order
acquaintances: str[] # IDs from person_pool only (not family tree)
stages: Stage[]
}
PartnerEntry {
person_id: str
type: PartnerType
children_ids: str[] # all children from this union
}
```
## Stage
```python
Stage {
type: StageType
age: { from: int, to: int } # relative to person
events: str[] # event IDs
}
```
### Stage Age Ranges
| Stage | Age |
|-------------|----------------|
| Child | 012 |
| Teen | 1216 |
| Young Adult | 1632 |
| Adult | 3250 |
| Senior | 50X (LLM decides) |
## EventEntry
```python
EventEntry {
id: str
type: Event # e.g. Event.ILLNESS_SEVERE
age: int # age of person at time of event
# absolute_year: @property -> person.birth_year + self.age
location: str | None # None = takes place at person.home
location_is_temp: bool # True = travel destination, False = known location
participants: str[] # person_ids or pool_ids
result: dict # predetermined simulation outcome
context: dict # extra info passed to LLM prompt
follow_bonus: Event[] # event types made more likely after this event
# e.g. after TRAVEL_FAR -> [DISCOVER_OBJECT, DISCOVER_WONDER]
}
```
## FamilyTree & person_pool
```python
FamilyTree {
persons: dict[str, Person] # all family tree persons
person_pool: dict[str, Person] # temporary chars with no tree membership
# e.g. random villager
# distant cousin -> lives in persons, not here
world: World
current_generation: int
active_node: str # person_id of currently narrated person
}
World {
start_year: int # randomly generated, max 4 digits, no calendar system stated
name: str
epoch_flavor: str # setting keywords passed to LLM
}
```