# Adventure Storyteller — Data Model **Status:** Draft v0.1 **Purpose:** Define the persistent information the application must represent, independent of the final fork or database implementation. ## 1. Design Goals The data model must support persistent interactive stories, complete authoritative history, non-destructive branching, checkpoints and rollback, genre-independent narrative state, long-term memory, imported local knowledge, prompt/context provenance, future image/video generation, export/restore, and local-only operation. The same core schema should work for fantasy, science fiction, mystery, horror, historical fiction, westerns, and other narrative genres. ## 2. Core Principles ### 2.1 Application-owned authority The database is the source of truth. The model may propose narration and state changes, but those proposals become authoritative only after application validation. ### 2.2 Non-destructive history Accepted turns are historical records. Going backward should move the active story head or create a branch, not silently delete accepted history. ### 2.3 Historical reconstruction The system must answer both: - What is true now? - What was true at a particular earlier turn on a particular branch? ### 2.4 Genre neutrality Avoid fantasy- or science-fiction-specific core fields. Use generic concepts such as characters, locations, organizations, items, vehicles, facts, relationships, conditions, scenes, and story threads. ## 3. Conceptual Entity Map ```text Campaign ├── Branches │ └── Turns │ ├── Prompt Snapshot │ ├── State Version │ ├── Scene Snapshot │ └── Retrieval Records ├── Checkpoints ├── Narrative Entities ├── Facts ├── Relationships ├── Story Threads ├── Memories ├── Summaries ├── Knowledge Sources │ └── Knowledge Chunks └── Media ├── Media Jobs └── Media Assets ``` The exact SQL schema may differ from this conceptual model. ## 4. Campaign A campaign is the top-level story container. ```yaml campaign: id: uuid title: string created_at: timestamp updated_at: timestamp active_branch_id: uuid status: active | archived story_profile: genre: string subgenre: optional string tone: optional string style: optional string point_of_view: optional string tense: optional string ``` Campaigns also store durable narrator rules and model configuration. Potential model roles: - narrator, - state extractor, - summarizer, - embedding model. For v1, all model roles should use local Ollama-compatible models. ## 5. Branch A branch represents one valid continuation of story history. ```yaml branch: id: uuid campaign_id: uuid name: string created_at: timestamp created_from_branch_id: optional uuid fork_turn_id: optional uuid head_turn_id: optional uuid status: active | archived ``` Rules: - branches may share ancestral turns, - shared history should not be duplicated unnecessarily, - creating a branch must not modify the source branch. Detailed behavior will be defined separately in `STORY-BRANCH-SEMANTICS.md`. ## 6. Turn A turn is one accepted story interaction: ```text user input -> narrator response -> accepted state transition ``` ```yaml turn: id: uuid campaign_id: uuid branch_id: uuid parent_turn_id: optional uuid created_at: timestamp input: mode: action | dialogue | direction | continue text: string output: narration: string generation: provider: ollama model: string generation_settings: object prompt_snapshot_id: uuid state_before_id: uuid state_after_id: uuid scene_snapshot_id: optional uuid status: pending | accepted | failed | superseded ``` The root turn has no parent. An accepted historical turn should not silently disappear if another continuation is chosen. ## 7. Alternate Take The system may need to distinguish: - a different narrator response to the same user action, - a genuinely different story branch. Conceptual form: ```yaml take: id: uuid turn_request_id: uuid output_text: string model_metadata: object selected: boolean ``` Whether `Take` becomes a separate table or sibling turn nodes will be decided after Phase 0B. ## 8. Checkpoint ```yaml checkpoint: id: uuid campaign_id: uuid turn_id: uuid name: string notes: optional string created_at: timestamp ``` A checkpoint is a named pointer to a recoverable story position. It should normally remain tied to the turn where it was created. ## 9. Narrative Entity An entity is a persistent thing or concept in the fictional world. ```yaml entity: id: uuid campaign_id: uuid type: string name: string aliases: [string] description: string status: active | inactive | destroyed | dead | unknown created_turn_id: optional uuid metadata: object ``` Recommended built-in categories: - character, - location, - organization, - item, - vehicle, - creature, - structure, - concept, - other. These are descriptive categories, not separate game systems. ## 10. Character ```yaml character: entity_id: uuid role: optional string description: string current_location_id: optional uuid condition: [string] personality_notes: [string] goals: [string] secrets: [string] ``` Optional visual continuity fields: ```yaml visual_profile: apparent_age: optional string build: optional string hair: optional string eyes: optional string clothing: optional string distinctive_features: [string] continuity_notes: [string] ``` ## 11. Location ```yaml location: entity_id: uuid description: string parent_location_id: optional uuid current_status: optional string visual_profile: architecture: optional string environment: optional string lighting: optional string signature_features: [string] continuity_notes: [string] ``` ## 12. Organization Organizations may represent factions, governments, companies, guilds, military units, religious organizations, or informal groups. ```yaml organization: entity_id: uuid purpose: optional string current_status: optional string ``` Specific characteristics should usually live in facts and relationships. ## 13. Items and Vehicles Items and vehicles remain generic entities. Examples: - silver key, - longsword, - encrypted data crystal, - survey ship, - horse-drawn carriage. Possession and location should normally be represented as relationships or facts: ```text Aldric --possesses--> Silver Key Persephone --docked_at--> Ceres Station ``` ## 14. Fact Facts represent assertions about the story world. ```yaml fact: id: uuid campaign_id: uuid subject_entity_id: optional uuid predicate: string object_entity_id: optional uuid value: optional scalar_or_object authority: string source_type: string source_id: optional uuid created_turn_id: optional uuid invalidated_turn_id: optional uuid status: active | superseded | disputed ``` Examples: - Mara knows Aldric has the silver key. - The Persephone cannot travel faster than light. - Edrin disappeared three weeks before the campaign began. - The eastern bridge collapsed during Turn 47. Minimum authority categories: - campaign_canon, - accepted_story, - current_state, - imported_canon, - reference, - heuristic, - inspiration. Exact context behavior will be defined in `CONTEXT-AND-MEMORY.md`. ## 15. Relationship ```yaml relationship: id: uuid campaign_id: uuid source_entity_id: uuid target_entity_id: uuid type: string status: string description: optional string created_turn_id: optional uuid ended_turn_id: optional uuid ``` Examples: - Aldric -> trusts -> Mara - Mara -> member_of -> Circle of Ash - Silver Key -> belongs_to -> Aldric - Persephone -> docked_at -> Ceres Station Relationship types should remain extensible. ## 16. Story Thread Story threads track unresolved or resolved narrative business. ```yaml story_thread: id: uuid campaign_id: uuid title: string description: string status: open | dormant | resolved | abandoned importance: optional number_or_label opened_turn_id: optional uuid resolved_turn_id: optional uuid ``` These are narrative continuity tools, not RPG quests. ## 17. State Version The system must reconstruct authoritative state at any retained turn. ```yaml state_version: id: uuid campaign_id: uuid turn_id: optional uuid parent_state_version_id: optional uuid created_at: timestamp state_hash: optional string ``` Possible implementation models: ### A. Full snapshots Simple restore, but duplicates data. ### B. Event sourcing Excellent auditability, but requires replay. ### C. Hybrid Validated events plus periodic/current snapshots. **Current preference: Hybrid**, pending Phase 0B. ## 18. State Change Event If the hybrid/event model is selected: ```yaml state_event: id: uuid campaign_id: uuid turn_id: uuid event_type: string payload: object sequence: integer ``` Potential event types: - entity_created, - entity_updated, - fact_added, - fact_invalidated, - relationship_added, - relationship_ended, - thread_opened, - thread_resolved, - location_changed, - possession_changed, - scene_changed. Events must be validated before commit. ## 19. State Proposal The model's extracted state proposal must be distinct from accepted state. ```yaml state_proposal: id: uuid turn_id: uuid model: string raw_output: string parsed_payload: object validation_status: accepted | partially_accepted | rejected | repair_required ``` The model must never write directly to authoritative state tables. ## 20. Scene Snapshot A scene snapshot captures the immediate narrative situation. ```yaml scene: id: uuid campaign_id: uuid branch_id: uuid source_turn_start_id: optional uuid source_turn_end_id: optional uuid location_id: optional uuid time_description: optional string mood: optional string environment: optional string participants: [uuid] significant_objects: [uuid] current_actions: [string] visual_notes: [string] continuity_notes: [string] ``` Scene snapshots support: - current context, - narrative continuity, - future image generation, - future multi-turn video/storyboard generation. ## 21. Summary Summaries compress history but never replace authoritative history. ```yaml summary: id: uuid campaign_id: uuid branch_id: uuid type: campaign | arc | rolling | turn_range source_start_turn_id: uuid source_end_turn_id: uuid text: string created_at: timestamp model: optional string ``` Summaries are derived data. Branch changes must invalidate or lineage-filter incompatible summaries. ## 22. Memory ```yaml memory: id: uuid campaign_id: uuid branch_scope: optional uuid source_turn_id: optional uuid type: string text: string authority: string importance: optional number embedding_ref: optional string created_at: timestamp ``` Potential types: - event, - character, - relationship, - location, - promise, - discovery, - conflict, - heuristic. A retrieved memory does not automatically become canon. ## 23. Knowledge Source A knowledge source is a user-imported local file or manually authored campaign document. ```yaml knowledge_source: id: uuid campaign_id: optional uuid title: string source_type: file | manual classification: canon | reference | inspiration original_filename: optional string content_hash: string enabled: boolean imported_at: timestamp metadata: object ``` Initial supported file types should be `.txt` and `.md`. ## 24. Knowledge Chunk ```yaml knowledge_chunk: id: uuid source_id: uuid sequence: integer text: string heading_path: optional string token_count: optional integer embedding_ref: optional string metadata: object ``` Requirements: - preserve provenance, - preserve chunk order, - permit re-indexing, - never execute imported content. ## 25. Retrieval Record Every turn should record which memories or knowledge chunks were supplied to the narrator. ```yaml retrieval_record: id: uuid turn_id: uuid source_kind: memory | knowledge | summary | fact source_id: uuid retrieval_method: lexical | semantic | hybrid | forced score: optional number rank: integer ``` This lets the prompt inspector answer: > Why did the narrator know this? ## 26. Prompt Snapshot ```yaml prompt_snapshot: id: uuid turn_id: uuid created_at: timestamp system_instructions: text campaign_context: text_or_structured state_context: text_or_structured summary_context: text_or_structured memory_context: text_or_structured knowledge_context: text_or_structured recent_history: text_or_structured user_input: text token_accounting: object ``` The physical representation may be compressed or normalized. Reproducibility and inspection are the requirements. ## 27. Media Job Not required for v1 behavior, but the data model should not prevent it. ```yaml media_job: id: uuid campaign_id: uuid scene_id: optional uuid source_turn_start_id: optional uuid source_turn_end_id: optional uuid type: image | video | audio provider: string model: string status: queued | running | completed | failed request_payload: object created_at: timestamp completed_at: optional timestamp ``` ## 28. Media Asset ```yaml media_asset: id: uuid campaign_id: uuid media_job_id: optional uuid scene_id: optional uuid type: image | video | audio file_path: string metadata: object created_at: timestamp ``` Potential metadata includes prompt, seed, model, workflow, dimensions, duration, character references, and source turn range. ## 29. Export Package A campaign export should be capable of preserving: - campaign configuration, - branches, - turn graph, - checkpoints, - state/events, - entities, - facts, - relationships, - story threads, - summaries, - memories, - scene snapshots, - knowledge-source metadata, - knowledge chunks/source files if selected, - prompt provenance if selected, - media metadata, - media files if selected. Exact format remains open. A ZIP containing a database plus manifest is a strong candidate. ## 30. Deletion vs Archival The system must distinguish: - archive, - detach/disable, - permanent delete. Retry, Undo, Restore, and branch switching must not silently perform permanent deletion. ## 31. Authoritative vs Derived Data ### Authoritative - accepted turns, - branch lineage, - campaign configuration, - accepted facts, - accepted relationships, - accepted state events, - checkpoints. ### Derived - summaries, - embeddings, - semantic indexes, - lexical indexes, - some automatically produced scene descriptions. Derived data should be rebuildable where practical. ## 32. Provenance Important information should answer: > Where did this come from? Potential provenance: - campaign setup, - manual user edit, - accepted narrator turn, - state extraction, - imported canon, - imported reference, - imported inspiration, - derived inference. ## 33. Open Questions for Phase 0B 1. Does AI-DnD already model alternate takes separately from branch nodes in a reusable way? 2. Can its state snapshots hold generic narrative JSON without major redesign? 3. Is its branch lineage compatible with immutable turns? 4. Should checkpoints be branch-independent pointers to turns? 5. Should memories be physically branch-scoped or lineage-filtered at query time? 6. Should knowledge sources be reusable across campaigns in v1? 7. Should media tables physically exist in v1 or only interfaces/types? 8. How should manual edits to canon/state be versioned? 9. Which state needs full historical reconstruction versus only current-state storage? 10. Can ai-adventure's event/replay discipline be adopted without overcomplicating AI-DnD? ## 34. Acceptance Criteria The final v1 data model must support all of these without destructive hacks: - close/restart/resume exact story, - branch from an earlier turn while retaining the original future, - create and restore named checkpoints, - know current characters/locations/relationships/story threads, - reconstruct earlier authoritative state, - retrieve old events outside the active context window, - identify which imported passages informed a turn, - reconstruct what was sent to Ollama, - run fantasy and science-fiction campaigns without schema changes, - attach future image/video assets to scenes or turn ranges. ## 35. Current Recommendation The target conceptual model should be: ```text Immutable Turn Graph | +--> validated state events | +--> state snapshot/cache | +--> scene snapshot | +--> prompt/retrieval provenance ``` This combines the strongest observed concepts from: - AI-DnD's story tree and snapshots, - ai-adventure's append-only event/replay discipline, - Open Dungeon's simple story UX and visual continuity. The physical implementation remains provisional until Phase 0B validates the preferred production base.