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>
This commit is contained in:
commit
06df9df339
7 changed files with 804 additions and 0 deletions
180
spec/data-model.md
Normal file
180
spec/data-model.md
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# 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 | 0–12 |
|
||||
| Teen | 12–16 |
|
||||
| Young Adult | 16–32 |
|
||||
| Adult | 32–50 |
|
||||
| Senior | 50–X (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
|
||||
}
|
||||
```
|
||||
Loading…
Add table
Add a link
Reference in a new issue