# VibeEngine - Core Design Document ## Table of Contents - [1. Core Components](#1-core-components) - [2. Character AI System](#2-character-ai-system) - [3. Game Definition Format](#3-game-definition-format) - [4. AI Model Deployment](#4-ai-model-deployment) - [5. Preventing LLM Hallucination](#5-preventing-llm-hallucination) - [6. Managing Context Windows](#6-managing-context-windows) - [7. Map System](#7-map-system) - [8. Rust Implementation](#8-rust-implementation) - [9. Advanced Systems](#9-advanced-systems) - [10. Example Implementations](#10-example-implementations) ## 1. Core Components VibeEngine is built on several key components that work together to create dynamic, AI-driven gameplay experiences. - **World State Manager**: Tracks game world, objects, characters, and their properties - **Event System**: Handles player actions, AI decisions, and world changes - **AI Integration Layer**: Manages communication with your AI model(s) - **Game Logic Engine**: Processes rules, validates actions, manages turn flow - **Narrative System**: Handles text generation, story progression, and dialogue ## 2. Character AI System The Character AI System powers dynamic NPC interactions with: - Personality profiles and goals for each NPC - Memory of past interactions and relationships - Context-aware AI queries including: - Character context - Current situation - Available actions - Advanced response parsing for natural dialogue and decisions ## 3. Game Definition Format A flexible JSON or YAML-based format is used for game definitions. ### 3.1 World Definition - Locations with descriptions, connections, and properties - Objects with interactive capabilities - Character templates with personality traits, goals, and behavioral parameters ### 3.2 Rule Systems - Action definitions (what players/NPCs can do) - Condition checks (requirements for actions) - Consequence chains (what happens after actions) ### 3.3 Dynamic Elements - Template-based text generation - Conditional story branches - Procedural event triggers This allows game creators to define the framework while letting AI fill in character behaviors and dynamic responses. ## 4. AI Model Deployment ### 4.1 Self-Hosted Options - **Ollama**: Easiest setup, runs models like Llama locally with simple API - **LM Studio**: User-friendly GUI for running various open-source models - **Text Generation WebUI**: More advanced interface with extensive model support - **vLLM**: Higher performance option for production use ### 4.2 Model Recommendations - **For character dialogue**: Llama 3.1 8B or Mistral 7B (good balance of capability and resource usage) - **For game logic**: Smaller, faster models might suffice - **Fine-tuning**: Consider fine-tuning on game-specific data for better character consistency ### 4.3 Integration Approach - REST API calls to your local model server - Structured prompts that include character context and game state - Response parsing to extract actionable decisions from AI output The key is designing prompts that give the AI enough context about the character's personality, current situation, and available actions to make believable decisions while maintaining game flow and narrative coherence. ## 5. Preventing LLM Hallucination ### 5.1 World Knowledge Grounding #### 5.1.1 Provide Explicit Context Instead of letting the AI invent details, feed it comprehensive world information with each request. **Example Context for an RPG town:** ```yaml # world/millbrook.yaml name: "Millbrook" description: "A small farming community known for its grain production" population: 500 locations: mill: name: "The Old Mill" owner: "Elder Thorne" description: "A large water mill by the river, currently having wheel issues" tavern: name: "The Grain & Grapes" owner: "Rosa" description: "A cozy tavern serving local ale and simple meals" market_square: name: "Market Square" description: "The bustling center of town where merchants gather" events: upcoming: - name: "Harvest Festival" date: "next week" description: "Annual celebration of the harvest" recent: - description: "Bandits spotted near north road" impact: 3 # Scale of 1-5 affects: ["trade", "safety"] current_issues: - issue: "Mill wheel broken" severity: "high" affects: ["economy", "food supply"] - issue: "Merchant caravan late" severity: "medium" affects: ["supplies", "news from other towns"] npcs: - id: "miller_gareth" name: "Gareth" role: "Miller" location: "mill" - id: "tavern_rosa" name: "Rosa" role: "Tavern Owner" location: "tavern" - id: "guard_marcus" name: "Marcus" role: "Guard Captain" location: "market_square" ``` #### 5.1.2 Character-Specific Information ```yaml # characters/miller_gareth.yaml id: "miller_gareth" name: "Gareth" role: "Miller" location: "mill" descriptors: - "middle-aged" - "flour-dusted clothes" - "calloused hands" - "perpetually worried expression" personality: traits: - "hardworking" - "worrier" - "talkative" - "knowledgeable about grain" - "community-minded" speech_patterns: - "tends to ramble about grain quality" - "frequently mentions the weather's impact on crops" - "uses farming metaphors" current_concerns: - "mill_wheel_broken": description: "The mill wheel is broken, affecting production" priority: "high" related_to: ["economy", "livelihood"] - "upcoming_harvest": description: "The annual harvest is approaching" priority: "medium" related_to: ["workload", "income"] knowledge_base: known_topics: - "grain_types" - "mill_operations" - "local_farming_techniques" - "town_gossip" - "weather_patterns" unknown_topics: - "military_strategy" - "arcane_magic" - "distant_kingdoms" - "advanced_mathematics" relationships: player: current: 50 # 0-100 scale history: [] elder_thorne: type: "employer" current: 80 tavern_rosa: type: "friend" current: 70 guard_marcus: type: "acquaintance" current: 40 inventory: - "flour_sack" - "mill_key" - "wheat_samples" schedule: "06:00-18:00": "working at the mill" "18:00-20:00": "at the tavern" "20:00-06:00": "sleeping at home" dialogue_hooks: - "mill_repair": "The mill wheel won't last much longer..." - "harvest_time": "The harvest is coming early this year." - "bandit_trouble": "Heard about them bandits up north?" ``` ### 5.2 Structured Prompting #### 5.2.1 Use Explicit Constraints ```text - "You can only reference information provided in the world context" - "If asked about something not in your knowledge, say the character doesn't know" - "Stick to the character's personality and knowledge limitations" ``` #### 5.2.2 Knowledge Boundaries - Define what each character type would realistically know - Merchants know trade routes and prices, not military secrets - Guards know local security, not magical theory - Farmers know crops and weather, not court politics ### 5.3 Response Format Control #### 5.3.1 Structured Output Requirements ```json { "dialogue": "Oh, well the harvest festival is coming up next week! Though I am a bit worried about these bandits they spotted up north...", "action": "scratches head and looks concerned", "internal_state": "concerned about bandits, excited for festival", "world_query": "" } ``` #### 5.3.2 Response Validation - Parse the structured output before displaying - Check if dialogue references world elements not in the provided context - Reject responses that invent new locations, characters, or events ### 5.4 Example Implementation Strategy **Bad Approach (Prone to Hallucination):** "Play as a miller in a fantasy town. The player asks about recent news." **Good Approach:** ```yaml WORLD_CONTEXT: - Detailed town information - Current events - Character knowledge base CHARACTER: name: "Miller Gareth" personality: "Worried, hardworking, talkative about grain" knowledge: ["local farming", "mill operations", "town gossip"] relationships: player: "neutral" CONVERSATION_HISTORY: [] CURRENT_SITUATION: "Player just entered the mill" INSTRUCTIONS: | - Respond naturally as Miller Gareth - Only reference known information - If unsure, say you don't know - Format response as JSON with: - dialogue: string - action: string (optional) - internal_state: string (optional) Player: "What's the news around town?" ``` # Additional Techniques ## Fact Checking Layer - Cross-reference AI responses against your world database - Flag or reject responses that introduce new "facts" - Maintain a consistency log of what's been established ## Layered AI Approach - One AI for dialogue generation (with strict constraints) - Another AI for world consistency checking - Game engine validates both outputs against world state ## Dynamic Context Updates - Update world context as events occur - Remove outdated information to prevent confusion - Add new confirmed facts from validated interactions ## Fallback Responses - Pre-written responses for when AI output fails validation - Character-appropriate ways to deflect unknown topics - "I'm not sure about that" responses that fit each character's personality This approach treats the AI as a dialogue generator within strict boundaries rather than a world creator, significantly reducing hallucination while maintaining engaging character interactions. ## 6. Managing Context Windows Smaller models (7B-13B parameters) struggle significantly with large context windows, leading to: - Information bleeding between different parts of the context - Attention degradation where the model loses focus on relevant details - Increased hallucination as the model gets overwhelmed - Inconsistent responses based on context position effects # Common Approaches for Managing Large Context ### 6.1 Hierarchical Context Management #### 6.1.1 Recent + Summary Approach - Keep last 10-20 messages in full detail - Summarize older interactions into key facts - Maintain separate "character memory" summaries **Example:** ```text RECENT CONVERSATION: [last 5 exchanges] CHARACTER MEMORY: - Player helped with mill repair - Friendly relationship established - Player mentioned being from the capital WORLD EVENTS SUMMARY: - Harvest festival completed - Bandits captured - New merchant arrived ``` ### 6.2 Retrieval-Augmented Generation (RAG) ### Context Retrieval - Store all game events/dialogue in a searchable database - Use semantic search to find relevant context for current situation - Only inject the most relevant 3-5 pieces of information **Example Query:** - Player asks about the festival - Retrieved Context: Only festival-related events, not unrelated conversations ### 6.3 State-Based Context Reduction ### Categorical Organization - Separate character relationships, world events, inventory, locations - Only include relevant categories for current interaction - Use different context "views" for different interaction types ### 6.4 Rolling Context Windows Sliding Window Approach: Maintain fixed-size context (e.g., 2000 tokens) Compress older information into structured summaries Keep full detail only for recent interactions # 5. Multi-Pass Processing Staged Approach: Context Selection Pass: Determine what information is relevant Response Generation Pass: Generate dialogue with selected context Consistency Check Pass: Verify response against world state # 6. Structured Memory Systems Explicit Memory Management: CHARACTER_FACTS: { "player_relationship": "friendly", "last_interaction": "helped with mill", "known_about_player": ["from capital", "skilled fighter"] } WORLD_STATE: { "mill_status": "repaired", "current_events": ["festival over", "new merchant"] } # 7. Context Compression Techniques Information Density: Use abbreviated formats for historical events Compress repeated information into single statements Focus on actionable/relevant details only Example Compression: Instead of: "The player said hello. The miller responded with a greeting. They discussed the weather..." Use: "Previous interaction: Friendly greeting, discussed weather concerns affecting crops" Practical Implementation Strategy For Your Game Engine: Dynamic Context Building: Analyze current player action/location Retrieve only relevant world state and character memories Include recent conversation history (last 3-5 exchanges) Memory Persistence: Store character impressions and key facts separately Update character memory after each significant interaction Maintain world event timeline with importance ratings Context Prioritization: Recent events > Historical events Character-specific knowledge > General world knowledge Player-relevant information > Background details Fallback Mechanisms: If context becomes too large, prioritize recent + character-specific information Use "the character doesn't recall" for information that had to be excluded Implement context overflow handling gracefully This approach lets you maintain rich, consistent game worlds while keeping the AI model's context manageable and focused, significantly reducing hallucination risks even with smaller models. ## 7. Map System ### 7.1 Core Map Structure Areas with Connections: Each area defines its connections to other areas, along with conditions that must be met to traverse them. Condition Types: Item requirements (need torch) Character stats (strength, magic level, health) World state (events completed, time of day, weather) Character knowledge (learned location, met someone) Story progression (quest completed, dialogue triggered) Example Game File json ```json { "game_info": { "name": "Village of Shadows", "version": "1.0", "description": "A mysterious village with dark secrets" }, "world_state": { "time_of_day": "afternoon", "weather": "cloudy", "events": { "mill_repaired": false, "bandits_defeated": false, "mayor_met": false, "secret_passage_discovered": false } }, "areas": { "village_center": { "name": "Village Center", "description": "A bustling town square with a stone fountain. Villagers go about their daily business.", "connections": { "millers_house": { "direction": "north", "description": "A path leads north to the miller's house", "conditions": [] }, "tavern": { "direction": "east", "description": "The warm glow of the tavern beckons from the east", "conditions": [] }, "mayors_hall": { "direction": "west", "description": "The imposing mayor's hall stands to the west", "conditions": [ { "type": "world_event", "event": "mayor_met", "value": true, "failure_message": "The guards won't let you pass without an appointment" } ] }, "forest_path": { "direction": "south", "description": "A dark forest path leads south", "conditions": [ { "type": "item", "item": "torch", "failure_message": "The forest is too dark to navigate without light" }, { "type": "time", "allowed_times": ["morning", "afternoon"], "failure_message": "The forest is too dangerous at night" } ] } }, "items": ["rusty_coin", "old_newspaper"], "npcs": ["town_crier", "fruit_vendor"] }, "millers_house": { "name": "Miller's House", "description": "A modest wooden house with a large water wheel beside it.", "connections": { "village_center": { "direction": "south", "description": "Return to the village center", "conditions": [] }, "mill_basement": { "direction": "down", "description": "Wooden stairs lead down to the mill's basement", "conditions": [ { "type": "character_relationship", "character": "miller_gareth", "relationship_level": "trusted", "failure_message": "The miller doesn't trust you enough to let you into his basement" } ] } }, "items": ["flour_sack"], "npcs": ["miller_gareth"] }, "tavern": { "name": "The Grain & Grapes Tavern", "description": "A cozy tavern filled with the aroma of ale and roasted meat.", "connections": { "village_center": { "direction": "west", "description": "Exit to the village center", "conditions": [] }, "tavern_upstairs": { "direction": "up", "description": "Stairs lead to the guest rooms above", "conditions": [ { "type": "item", "item": "room_key", "failure_message": "You need a room key to go upstairs" } ] }, "secret_tunnel": { "direction": "hidden", "description": "A hidden passage behind the wine barrels", "conditions": [ { "type": "world_event", "event": "secret_passage_discovered", "value": true, "failure_message": "You don't know about any secret passages" }, { "type": "item", "item": "crowbar", "failure_message": "You need something to pry open the passage" } ] } }, "items": ["ale_mug", "mysterious_note"], "npcs": ["tavern_owner_rosa", "drunk_patron"] }, "forest_path": { "name": "Dark Forest Path", "description": "Ancient trees tower overhead, their branches blocking most sunlight.", "connections": { "village_center": { "direction": "north", "description": "Return to the village", "conditions": [] }, "bandit_camp": { "direction": "east", "description": "Trampled underbrush leads deeper into the forest", "conditions": [ { "type": "character_stat", "stat": "stealth", "minimum_value": 3, "failure_message": "You're making too much noise - the bandits will hear you" } ] }, "ancient_ruins": { "direction": "west", "description": "Stone ruins are barely visible through the trees", "conditions": [ { "type": "multiple", "operator": "AND", "conditions": [ { "type": "character_knowledge", "knowledge": "ruins_location", "failure_message": "You don't know about any ruins in this area" }, { "type": "character_stat", "stat": "magic_level", "minimum_value": 2, "failure_message": "The magical barrier prevents you from approaching" } ] } ] } }, "items": ["healing_herb", "broken_sword"], "npcs": ["forest_spirit"] } }, "condition_types": { "item": "Player must possess specific item", "character_stat": "Player stat must meet minimum requirement", "world_event": "Specific world event must be true/false", "time": "Must be specific time of day", "character_relationship": "Must have specific relationship level with NPC", "character_knowledge": "Player must have learned specific information", "multiple": "Combine multiple conditions with AND/OR logic" } } ``` ### 7.2 Key Design Features #### 7.2.1 Flexible Conditions Simple single requirements (need torch) Complex multi-part conditions (need item AND high stat) Dynamic conditions based on world state Directional Movement: Traditional compass directions Special directions (up, down, hidden) Descriptive text for each connection Failure Handling: Custom messages for each blocked path Clear feedback about what's needed Progressive Unlocking: Areas become accessible as story progresses Relationships with NPCs open new areas Player character growth enables new paths This structure allows for rich, interconnected worlds where exploration feels natural and progression is gated by meaningful story and character development rather than arbitrary barriers. JSON vs YAML for Game Files YAML is better for your use case: YAML Advantages: Human readable/writable - Game designers can easily author content Comments support - Essential for documenting complex game logic Multi-line strings - Perfect for character dialogue and descriptions Less verbose - Cleaner structure for nested data Better for version control - Easier to see diffs and merge changes JSON Advantages: Parsing performance - Slightly faster, but negligible for game files Ubiquitous support - But YAML parsers are common in Rust Example comparison: yaml # YAML - much cleaner for game content areas: village_center: name: Village Center description: | A bustling town square with a stone fountain. Villagers go about their daily business, preparing for the upcoming harvest festival. connections: tavern: direction: east conditions: - type: time allowed_times: [morning, afternoon] failure_message: "The tavern is closed at night" vs the equivalent JSON with escaped strings and no comments. Dependency System Modular Structure: yaml # main_game.yaml game_info: name: "Village of Shadows" dependencies: - "maps/village_map.yaml" - "characters/villagers.yaml" - "items/village_items.yaml" - "events/harvest_festival.yaml" # characters/villagers.yaml characters: miller_gareth: import_from: "archetypes/worried_craftsman.yaml" overrides: profession: "miller" specific_concerns: ["mill_wheel", "grain_quality"] # archetypes/worried_craftsman.yaml personality_template: traits: ["anxious", "hardworking", "detail_oriented"] speech_patterns: ["tends to ramble", "mentions work frequently"] Dependency Types: Maps - Area definitions and connections Characters - NPC definitions and dialogue trees Items - Object definitions and interactions Events - Story progression and world state changes Archetypes - Reusable character/item templates Character System Structure yaml ```yaml characters: miller_gareth: # Basic Info name: "Gareth the Miller" profession: "Miller" location: "millers_house" # Personality Core personality: traits: ["anxious", "hardworking", "talkative", "kind"] values: ["honest_work", "community", "tradition"] fears: ["mill_breaking", "poor_harvest", "letting_people_down"] goals: ["keep_mill_running", "help_village", "save_money"] # Speech Patterns speech: style: "rambling" common_phrases: - "Well, you see..." - "Back in my day..." - "The grain tells me..." topics_of_interest: ["milling", "grain_quality", "weather", "village_gossip"] vocabulary_level: "simple" accent: "rural" # Knowledge & Relationships knowledge: areas: ["village_center", "millers_house", "grain_fields"] secrets: ["mill_basement_contents", "old_mayors_corruption"] skills: ["milling", "grain_assessment", "basic_repairs"] relationships: player: initial_disposition: "cautious" relationship_stages: stranger: "polite but distant" acquaintance: "friendly but reserved" trusted: "opens up about problems" friend: "shares secrets and asks for help" tavern_owner_rosa: status: "friend" history: "childhood_friends" current_dynamic: "trade_grain_for_ale" # Memory System memory: short_term: [] # Recent conversations long_term: [] # Important events with player character_impression: "unknown" # Updated based on interactions # Dialogue System dialogue: greeting: stranger: | *wipes flour-covered hands on apron* Oh! A visitor! Don't get many of those these days. You wouldn't be interested in some fine flour, would you? acquaintance: | Ah, good to see you again! How've you been? The mill's been keeping me busy as always. trusted: | *sighs heavily* I'm glad you're here. Been having some troubles with the mill wheel lately, and I could use someone I can trust to talk to. topics: mill_problems: conditions: - relationship: "trusted" responses: - trigger: "first_time" text: | *looks around nervously* Well, you see, the wheel's been making strange noises. Not the usual creaking - something... different. And there's been odd scratching sounds from the basement at night. village_gossip: conditions: [] responses: - trigger: "default" text: | *leans in conspiratorially* Rosa at the tavern's been asking about strangers lately. Says some folk have been asking odd questions about the old days. reactions: player_helps_with_mill: immediate: "You're a lifesaver! I don't know what I'd have done without you." relationship_change: "+2" memory_addition: "Player helped repair the mill wheel" player_asks_about_basement: conditions: - relationship: "below_trusted" response: "*shifts uncomfortably* Oh, just old milling equipment down there." # Dynamic Behavior behaviors: default_actions: ["mill_maintenance", "check_grain_quality", "worry_about_weather"] conditional_behaviors: - condition: "mill_wheel_broken" actions: ["pace_nervously", "mutter_about_repairs"] - condition: "player_relationship_trusted" actions: ["confide_problems", "ask_for_help"] # Context for AI ai_context: personality_prompt: | You are Gareth, a worried but kind miller. You tend to ramble about grain and milling processes. You're anxious about your mill breaking down and letting the village down. You speak in a rural dialect and often mention your work. knowledge_limits: | You know about milling, local farming, village gossip, and basic repairs. You don't know about magic, distant lands, or court politics. conversation_style: | You tend to go off on tangents about grain quality or mill maintenance. You're friendly but worry about your problems. You often wipe your hands on your apron when nervous. ``` This structure provides: - Rich personality definition for consistent AI behavior - Relationship progression that affects dialogue options - Memory system for persistent character development - Conditional content based on world state and relationships - Clear AI context to prevent hallucination while maintaining personality Yes, YAML is very easy to parse in Rust with excellent serde support! ## Primary YAML Crate serde_yaml is the standard choice: toml [dependencies] serde = { version = "1.0", features = ["derive"] } serde_yaml = "0.9" ## Basic Usage ### Deserializing YAML ```rust use serde::{Deserialize, Serialize}; #[derive(Debug, Deserialize, Serialize)] struct GameArea { name: String, description: String, connections: HashMap, items: Vec, npcs: Vec, } #[derive(Debug, Deserialize, Serialize)] struct Connection { direction: String, description: String, conditions: Vec, } // Parse YAML file let yaml_content = std::fs::read_to_string("game_world.yaml")?; let areas: HashMap = serde_yaml::from_str(&yaml_content)?; ``` ### Advantages of YAML + Serde Automatic Deserialization: - Serde handles all the parsing automatically - Type-safe deserialization into Rust structs - Excellent error messages for malformed YAML Flexible Data Types: - Maps to Rust's HashMap, Vec, Option, String, etc. - Supports enums, nested structs, and custom types - Handles optional fields gracefully Comments Preserved: - YAML comments are ignored during parsing (which is what you want) - No need to strip comments manually ## Example for Your Game Engine ```rust #[derive(Debug, Deserialize, Serialize)] struct GameWorld { game_info: GameInfo, world_state: WorldState, areas: HashMap, characters: HashMap, } #[derive(Debug, Deserialize, Serialize)] struct Character { name: String, profession: String, location: String, personality: Personality, speech: SpeechPattern, knowledge: Knowledge, relationships: HashMap, dialogue: HashMap, } #[derive(Debug, Deserialize, Serialize)] struct Condition { #[serde(rename = "type")] condition_type: String, // Flatten all other fields into this map #[serde(flatten)] parameters: HashMap, } // Load multiple files let world: GameWorld = serde_yaml::from_str(&fs::read_to_string("main_game.yaml")?)?; let characters: HashMap = serde_yaml::from_str(&fs::read_to_string("characters.yaml")?)?; ``` ## Performance & Considerations ### Performance YAML parsing is slightly slower than JSON, but negligible for game files Typically happens only at game startup Very fast for the file sizes you'd be working with ### Error Handling - Serde provides detailed error messages with line numbers Easy to catch and display meaningful errors to game designers Alternative: If you need maximum performance and don't mind losing comments, you could use JSON for production and YAML for development, converting between them as needed. Verdict: YAML + serde is an excellent choice for your game engine. The developer experience benefits far outweigh the minor performance trade-off, and serde makes it nearly as easy to work with as JSON while being much more human-friendly for content creators. For save states, I'd recommend JSON over YAML despite using YAML for game content. Here's why: Save States: JSON vs YAML Use JSON for saves: - Performance - Faster parsing/writing during frequent saves - Compact size - Smaller file sizes for save data - No comments needed - Save data is machine-generated - Standard format - Expected format for save files - Atomic writes - Easier to ensure save integrity Keep YAML for: - Game content files (characters, maps, dialogue) - Configuration files - Anything humans need to edit Save State Structure Main save file (JSON): ```json { "save_info": { "version": "1.0", "created_at": "2025-07-08T14:30:00Z", "game_time": 1547, "player_location": "village_center", "save_name": "Village Investigation", "playtime_minutes": 127 }, "dependencies": { "world_state": "saves/world_state.json", "character_states": "saves/character_states.json", "player_data": "saves/player_data.json", "conversation_history": "saves/conversation_history.json", "inventory": "saves/inventory.json" } } ``` ### World State (world_state.json) ```json { "current_state": { "time_of_day": "evening", "weather": "rainy", "season": "autumn" }, "events": { "mill_repaired": true, "bandits_defeated": false, "mayor_met": true, "secret_passage_discovered": true, "harvest_festival_completed": true, "ancient_ruins_explored": false }, "area_states": { "village_center": { "items_present": ["old_newspaper"], "items_taken": ["rusty_coin"], "visited_count": 15, "last_visited": 1543 }, "millers_house": { "items_present": [], "items_taken": ["flour_sack"], "visited_count": 8, "last_visited": 1547, "custom_state": { "mill_wheel_condition": "repaired", "basement_door_status": "unlocked" } }, "tavern": { "items_present": ["mysterious_note"], "items_taken": ["ale_mug"], "visited_count": 12, "last_visited": 1540, "custom_state": { "secret_passage_opened": true, "room_rented": false } } }, "global_variables": { "festival_preparations": 100, "village_trust_level": 75, "bandit_threat_level": 60 } } ``` ### Character States (character_states.json) ```json { "miller_gareth": { "current_location": "millers_house", "relationship_with_player": "trusted", "relationship_value": 85, "current_mood": "relieved", "health": 100, "memory": { "short_term": [ { "event": "player_helped_repair_mill", "timestamp": 1545, "importance": "high" }, { "event": "discussed_basement_noises", "timestamp": 1547, "importance": "medium" } ], "long_term": [ { "fact": "Player is trustworthy and helpful", "established": 1545, "confidence": "high" }, { "fact": "Player knows about basement secret", "established": 1547, "confidence": "high" } ], "player_impression": "A capable and trustworthy person who helped me in my time of need" }, "dialogue_flags": { "basement_revealed": true, "mill_problem_solved": true, "personal_story_shared": false }, "custom_state": { "stress_level": 20, "work_efficiency": 90, "last_maintenance_check": 1546 } }, "tavern_owner_rosa": { "current_location": "tavern", "relationship_with_player": "acquaintance", "relationship_value": 45, "current_mood": "curious", "health": 100, "memory": { "short_term": [ { "event": "player_asked_about_strangers", "timestamp": 1540, "importance": "medium" } ], "long_term": [ { "fact": "Player asks good questions", "established": 1540, "confidence": "medium" } ], "player_impression": "Seems like someone who pays attention to details" }, "dialogue_flags": { "stranger_topic_introduced": true, "secret_passage_mentioned": false }, "custom_state": { "suspicion_level": 30, "business_mood": "good" } } } ``` ### Player Data (player_data.json) ```json { "character": { "name": "Traveler", "level": 3, "experience": 350, "health": 85, "max_health": 100, "stats": { "strength": 4, "stealth": 3, "magic_level": 2, "charisma": 5 } }, "knowledge": { "areas_discovered": [ "village_center", "millers_house", "tavern", "forest_path" ], "secrets_learned": [ "ruins_location", "mill_basement_contents", "secret_passage_location" ], "skills_acquired": [ "basic_repair", "local_knowledge" ] }, "quests": { "active": [ { "id": "investigate_bandits", "title": "Investigate Bandit Activity", "description": "Look into the bandit sightings near the forest", "progress": 60, "objectives": [ { "description": "Talk to villagers about bandit sightings", "completed": true }, { "description": "Investigate the forest path", "completed": false } ] } ], "completed": [ { "id": "help_miller", "title": "Help the Miller", "completed_at": 1545, "reward_given": true } ] } } ``` ### Conversation History (conversation_history.json) ```json { "conversations": [ { "id": "conv_001", "character": "miller_gareth", "location": "millers_house", "timestamp": 1547, "messages": [ { "speaker": "player", "text": "I heard strange noises coming from your basement", "timestamp": 1547 }, { "speaker": "miller_gareth", "text": "You did? Well, I... I suppose I should tell you. There's been scratching sounds at night. I'm too scared to investigate myself.", "timestamp": 1547, "mood": "nervous", "relationship_change": 5 } ] } ], "summary": { "total_conversations": 23, "characters_met": ["miller_gareth", "tavern_owner_rosa", "town_crier"], "most_talked_to": "miller_gareth", "conversation_themes": ["mill_problems", "village_gossip", "bandit_concerns"] } } ``` ### Inventory (inventory.json) ```json { "items": [ { "id": "torch", "name": "Torch", "description": "A sturdy torch that provides light", "quantity": 2, "equipped": false }, { "id": "room_key", "name": "Tavern Room Key", "description": "A brass key for the tavern's guest room", "quantity": 1, "equipped": false }, { "id": "crowbar", "name": "Crowbar", "description": "A heavy iron crowbar useful for prying things open", "quantity": 1, "equipped": true } ], "capacity": { "current": 3, "maximum": 10 } } ``` ## Benefits of This Structure Performance: Fast loading/saving with JSON Only load relevant files when needed Separate files allow partial saves Maintainability: Clear separation of concerns Easy to debug specific save issues Modular structure matches your game engine Flexibility: Can compress individual files if needed Easy to add new save categories Version control friendly for development Save Management: Multiple save slots just use different directories Can validate save integrity per component Easy to implement save/load progress indicators This gives you the best of both worlds: human-readable YAML for content creation and efficient JSON for runtime save data. You'll need several additional systems beyond area connections to support complex game mechanics: ### 9.1 Combat System Enemy Definitions: ```yaml # enemies/forest_enemies.yaml enemies: forest_bandit: name: "Forest Bandit" stats: health: 45 max_health: 45 attack: 8 defense: 3 speed: 6 behaviors: - type: "aggressive" trigger: "player_enters_area" actions: ["attack", "call_for_help"] - type: "flee" trigger: "health_below_25_percent" actions: ["run_to_bandit_camp"] loot_table: - item: "bandit_sword" chance: 60 - item: "gold_coins" quantity: [3, 8] chance: 100 ai_context: | You're a desperate bandit who attacks travelers for money. You're not particularly brave and will flee when badly injured. ``` ### 9.2 Advanced Conversation System Dialogue Trees with Complex Triggers: ```yaml # characters/miller_gareth.yaml dialogue: topics: basement_investigation: unlock_conditions: - type: "relationship" level: "trusted" - type: "world_event" event: "mill_repaired" value: true - type: "NOT" condition: type: "dialogue_flag" flag: "basement_revealed" conversation_flow: entry: npc_text: | *shifts nervously and glances toward the basement door* Well, since you helped with the mill... I suppose I can trust you. There's been strange sounds coming from down there at night. player_options: - text: "What kind of sounds?" leads_to: "describe_sounds" requirements: [] - text: "Have you investigated?" leads_to: "investigation_attempt" requirements: [] - text: "I could take a look for you" leads_to: "offer_help" requirements: - type: "character_stat" stat: "courage" minimum: 3 describe_sounds: npc_text: | Scratching, mostly. Like claws on wood. And sometimes... *whispers* sometimes I hear whispers. Not human whispers. effects: - type: "add_knowledge" knowledge: "basement_has_creature" - type: "increase_fear" character: "miller_gareth" amount: 10 player_options: - text: "That sounds supernatural" leads_to: "supernatural_discussion" requirements: - type: "character_knowledge" knowledge: "magic_exists" - text: "Probably just rats" leads_to: "dismiss_concern" effects: - type: "change_relationship" amount: -5 reason: "dismissed_concerns" ``` ### 9.3 Quest System Complex Quest Logic: ```yaml # quests/investigate_bandits.yaml quests: investigate_bandits: name: "Investigate Bandit Activity" description: "The villagers are concerned about bandit activity in the forest" start_conditions: - type: "talked_to_character" character: "town_crier" topic: "bandit_concerns" - type: "world_event" event: "bandit_sightings_reported" value: true objectives: gather_information: description: "Talk to villagers about bandit sightings" type: "collection" target_count: 3 progress_tracking: - character: "miller_gareth" topic: "bandit_concerns" weight: 1 - character: "tavern_owner_rosa" topic: "stranger_sightings" weight: 1 - character: "guard_captain_marcus" topic: "patrol_reports" weight: 1 investigate_forest: description: "Search the forest for bandit signs" type: "exploration" unlock_conditions: - type: "objective_completed" objective: "gather_information" requirements: - type: "visit_area" area: "forest_path" - type: "find_item" item: "bandit_tracks" location: "forest_path" confront_bandits: description: "Deal with the bandit threat" type: "choice" unlock_conditions: - type: "objective_completed" objective: "investigate_forest" options: - choice: "attack_bandit_camp" requirements: - type: "character_stat" stat: "strength" minimum: 5 effects: - type: "start_combat" enemy_group: "bandit_patrol" - type: "set_world_event" event: "chose_violent_approach" value: true - choice: "negotiate_with_bandits" requirements: - type: "character_stat" stat: "charisma" minimum: 6 effects: - type: "start_dialogue" character: "bandit_leader" topic: "negotiation" ``` ### 9.4 Event System Scripted Events and Triggers: ```yaml # events/harvest_festival.yaml events: harvest_festival: name: "Harvest Festival" description: "The annual village celebration" start_triggers: - type: "time_based" game_time: 2000 conditions: - type: "world_event" event: "mill_repaired" value: true - type: "quest_status" quest: "investigate_bandits" status: "completed" phases: preparation: duration: 200 description: "Villagers prepare for the festival" effects: - type: "modify_character_behavior" character: "all_villagers" new_behavior: "festival_preparation" - type: "add_temporary_areas" areas: ["festival_grounds", "merchant_stalls"] celebration: duration: 300 description: "The festival is in full swing" effects: - type: "spawn_temporary_npcs" npcs: ["traveling_merchant", "festival_musician"] - type: "modify_dialogue" characters: "all_villagers" add_topics: ["festival_joy", "harvest_success"] random_events: - event: "pickpocket_attempt" chance: 15 conditions: - type: "player_has_valuable_items" minimum_value: 50 - event: "mysterious_stranger_appears" chance: 25 conditions: - type: "world_event" event: "ancient_ruins_explored" value: true completion_effects: - type: "set_world_event" event: "harvest_festival_completed" value: true - type: "increase_relationship" character: "all_villagers" amount: 10 reason: "festival_participation" ``` ### 9.5 Conditional Logic System Complex Condition Engine: ```yaml # logic/conditions.yaml condition_templates: multiple_and: type: "multiple" operator: "AND" conditions: [] multiple_or: type: "multiple" operator: "OR" conditions: [] conditional_chain: type: "chain" conditions: - if: type: "character_stat" stat: "stealth" minimum: 4 then: type: "set_world_event" event: "sneaked_past_guards" value: true else: type: "start_combat" enemy_group: "guard_patrol" time_and_weather: type: "multiple" operator: "AND" conditions: - type: "time_of_day" time: "night" - type: "weather" weather: "stormy" failure_message: "You need the cover of a stormy night" ``` ### 9.6 Dynamic World State World Reactions to Player Actions: ```yaml # world/dynamic_systems.yaml world_reactions: bandit_defeat_consequences: trigger: type: "world_event" event: "bandits_defeated" value: true effects: - type: "modify_area" area: "forest_path" changes: safety_level: "safe" new_connections: merchant_road: direction: "northwest" description: "A now-safe merchant road" conditions: [] - type: "modify_all_characters" filter: type: "location" locations: ["village_center", "tavern"] changes: add_dialogue_topic: "bandit_victory_celebration" relationship_bonus: 15 - type: "spawn_events" events: ["merchant_caravan_arrives", "celebration_feast"] ``` ### 9.7 AI Behavior System Dynamic NPC Behaviors: ```yaml # ai/behaviors.yaml ai_behaviors: worried_villager: base_personality: "anxious" triggers: - condition: type: "world_event" event: "bandits_active" value: true behavior_changes: dialogue_topics_add: ["safety_concerns", "bandit_fears"] movement_restrictions: ["avoid_forest_areas"] ai_context_append: "You're worried about bandit attacks and reluctant to travel alone." festival_celebrant: base_personality: "joyful" triggers: - condition: type: "event_active" event: "harvest_festival" behavior_changes: dialogue_topics_add: ["festival_joy", "harvest_success"] movement_pattern: "festival_participation" ai_context_override: "You're in a festive mood, celebrating the harvest with the community." ``` This system provides: Complex combat with AI-driven enemies Branching dialogue with multiple unlock conditions Multi-objective quests with different completion paths Dynamic world events that respond to player actions Conditional logic that can handle complex scenarios Behavior modification for NPCs based on world state Each system can reference and modify the others, creating emergent gameplay where player choices have cascading effects throughout the game world. ## 8. Rust Implementation ### 8.1 Core Data Structures ```rust use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::any::Any; // Generic container for dynamic data #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(untagged)] pub enum GameValue { String(String), Number(f64), Bool(bool), Array(Vec), Object(HashMap), } // Generic condition system #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Condition { #[serde(rename = "type")] pub condition_type: String, // Flatten all other fields into this map #[serde(flatten)] pub parameters: HashMap, } // Generic effect system #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Effect { #[serde(rename = "type")] pub effect_type: String, #[serde(flatten)] pub parameters: HashMap, } // Generic trait system for inheritable behaviors #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Trait { pub name: String, pub description: Option, pub modifiers: HashMap, pub behaviors: Vec, pub conditions: Vec, } ``` Game World Structure ```rust #[derive(Debug, Deserialize, Serialize)] pub struct GameWorld { pub game_info: GameInfo, pub world_state: WorldState, pub areas: HashMap, pub characters: HashMap, pub items: HashMap, pub traits: HashMap, pub templates: HashMap, } #[derive(Debug, Deserialize, Serialize)] pub struct GameInfo { pub name: String, pub version: String, pub description: String, pub dependencies: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct WorldState { pub current_time: u64, pub weather: String, pub events: HashMap, pub global_variables: HashMap, } ``` Area System ```rust #[derive(Debug, Deserialize, Serialize)] pub struct GameArea { pub name: String, pub description: String, pub connections: HashMap, pub items: Vec, pub npcs: Vec, pub properties: HashMap, pub events: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct Connection { pub direction: String, pub description: String, pub conditions: Vec, pub effects: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct AreaEvent { pub name: String, pub triggers: Vec, pub effects: Vec, pub chance: Option, } ``` Inheritable Character System ```rust #[derive(Debug, Deserialize, Serialize)] pub struct Character { pub name: String, pub description: String, // Inheritance system pub inherits_from: Option, pub traits: Vec, // Core properties pub stats: HashMap, pub location: String, pub properties: HashMap, // Behavior system pub behaviors: Vec, pub dialogue: DialogueSystem, pub relationships: HashMap, // AI context pub ai_context: Option, pub personality_prompt: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct DialogueSystem { pub topics: HashMap, pub greetings: HashMap, pub reactions: HashMap, } #[derive(Debug, Deserialize, Serialize)] pub struct DialogueTopic { pub unlock_conditions: Vec, pub responses: Vec, pub effects: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct DialogueResponse { pub text: String, pub conditions: Vec, pub effects: Vec, pub next_topic: Option, } #[derive(Debug, Deserialize, Serialize)] pub struct DialogueReaction { pub trigger: String, pub response: String, pub effects: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct Relationship { pub level: String, pub value: f64, pub history: Vec, } ``` Inheritable Item System ```rust #[derive(Debug, Deserialize, Serialize)] ```rust pub struct Item { pub name: String, pub description: String, // Inheritance pub inherits_from: Option, pub traits: Vec, // Properties pub properties: HashMap, pub stats: HashMap, // Behavior pub actions: Vec, pub effects: Vec, pub conditions: Vec, } #[derive(Debug, Deserialize, Serialize)] pub struct ItemAction { pub name: String, pub description: String, pub conditions: Vec, pub effects: Vec, } Template System ```rust #[derive(Debug, Deserialize, Serialize)] pub struct Template { pub name: String, pub template_type: String, // "character", "item", "area", etc. pub properties: HashMap, pub traits: Vec, pub behaviors: Vec, } ``` Runtime System for Inheritance ```rust pub struct GameEngine { pub world: GameWorld, pub loaded_files: HashMap, pub resolved_cache: HashMap>, } impl GameEngine { pub fn resolve_character(&mut self, character_id: &str) -> Character { // Check cache first if let Some(cached) = self.resolved_cache.get(character_id) { if let Some(character) = cached.downcast_ref::() { return character.clone(); } } let mut character = self.world.characters[character_id].clone(); // Apply inheritance if let Some(parent_id) = &character.inherits_from { let parent = self.resolve_character(parent_id); character = self.merge_character(parent, character); } // Apply traits for trait_name in &character.traits.clone() { if let Some(trait_def) = self.world.traits.get(trait_name) { character = self.apply_trait_to_character(character, trait_def); } } // Cache result self.resolved_cache.insert( character_id.to_string(), Box::new(character.clone()) ); character } fn merge_character(&self, parent: Character, child: Character) -> Character { let mut merged = parent; // Override with child properties merged.name = child.name; merged.description = child.description; // Merge stats (child overrides parent) for (key, value) in child.stats { merged.stats.insert(key, value); } // Merge properties for (key, value) in child.properties { merged.properties.insert(key, value); } // Combine traits merged.traits.extend(child.traits); merged.traits.dedup(); // Merge dialogue topics for (topic, dialogue) in child.dialogue.topics { merged.dialogue.topics.insert(topic, dialogue); } merged } fn apply_trait_to_character(&self, mut character: Character, trait_def: &Trait) -> Character { // Apply trait modifiers for (key, modifier) in &trait_def.modifiers { match modifier { GameValue::Number(n) => { // Add to existing stat or create new one if let Some(GameValue::Number(existing)) = character.stats.get(key) { character.stats.insert(key.clone(), GameValue::Number(existing + n)); } else { character.stats.insert(key.clone(), GameValue::Number(*n)); } } _ => { character.properties.insert(key.clone(), modifier.clone()); } } } // Add trait behaviors character.behaviors.extend(trait_def.behaviors.clone()); character } } ``` Condition/Effect Evaluation System ```rust impl GameEngine { pub fn evaluate_condition(&self, condition: &Condition, context: &GameContext) -> bool { match condition.condition_type.as_str() { "character_stat" => { let character_id = self.get_string_param(&condition.parameters, "character") .unwrap_or(&context.current_character); let stat_name = self.get_string_param(&condition.parameters, "stat").unwrap(); let minimum = self.get_number_param(&condition.parameters, "minimum").unwrap_or(0.0); if let Some(character) = self.world.characters.get(character_id) { if let Some(GameValue::Number(stat_value)) = character.stats.get(stat_name) { return *stat_value >= minimum; } } false } "world_event" => { let event_name = self.get_string_param(&condition.parameters, "event").unwrap(); let expected_value = self.get_param(&condition.parameters, "value").unwrap(); if let Some(actual_value) = self.world.world_state.events.get(event_name) { return self.values_match(actual_value, expected_value); } false } "multiple" => { let operator = self.get_string_param(&condition.parameters, "operator").unwrap(); let conditions = self.get_array_param(&condition.parameters, "conditions").unwrap(); match operator { "AND" => conditions.iter().all(|c| self.evaluate_condition_value(c, context)), "OR" => conditions.iter().any(|c| self.evaluate_condition_value(c, context)), _ => false } } _ => { // Handle unknown condition types gracefully eprintln!("Unknown condition type: {}", condition.condition_type); false } } } // Helper methods for parameter extraction fn get_string_param(&self, params: &HashMap, key: &str) -> Option<&String> { if let Some(GameValue::String(s)) = params.get(key) { Some(s) } else { None } } fn get_number_param(&self, params: &HashMap, key: &str) -> Option { if let Some(GameValue::Number(n)) = params.get(key) { Some(*n) } else { None } } } #[derive(Debug)] pub struct GameContext { pub current_character: String, pub current_area: String, pub player_id: String, } ``` Usage Example ```rust fn main() -> Result<(), Box> { let yaml_content = std::fs::read_to_string("game_world.yaml")?; let world: GameWorld = serde_yaml::from_str(&yaml_content)?; let mut engine = GameEngine { world, loaded_files: HashMap::new(), resolved_cache: HashMap::new(), }; // Resolve a character with inheritance let miller = engine.resolve_character("miller_gareth"); println!("Miller stats: {:?}", miller.stats); // Evaluate conditions let context = GameContext { current_character: "miller_gareth".to_string(), current_area: "millers_house".to_string(), player_id: "player".to_string(), }; let condition = Condition { condition_type: "character_stat".to_string(), parameters: [ ("character".to_string(), GameValue::String("miller_gareth".to_string())), ("stat".to_string(), GameValue::String("trust_level".to_string())), ("minimum".to_string(), GameValue::Number(50.0)), ].iter().cloned().collect(), }; let result = engine.evaluate_condition(&condition, &context); println!("Condition result: {}", result); Ok(()) } ``` This approach gives you: Flexible YAML parsing without complex struct hierarchies Runtime inheritance for characters and items Generic condition/effect system that can handle any parameters Caching for performance Extensibility - easy to add new condition/effect types Type safety where it matters, flexibility where it doesn't The key insight is using `HashMap` for dynamic data and resolving inheritance at runtime rather than trying to model everything statically in the type system.