Files
vibeengine/concept.md
T
2025-07-08 20:14:07 +01:00

61 KiB

VibeEngine - Core Design Document

Table of Contents

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:

# 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

# 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

- "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

{
  "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:

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:

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

{
  "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

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

use serde::{Deserialize, Serialize};

#[derive(Debug, Deserialize, Serialize)]
struct GameArea {
    name: String,
    description: String,
    connections: HashMap<String, Connection>,
    items: Vec<String>,
    npcs: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize)]
struct Connection {
    direction: String,
    description: String,
    conditions: Vec<Condition>,
}

// Parse YAML file
let yaml_content = std::fs::read_to_string("game_world.yaml")?;
let areas: HashMap<String, GameArea> = 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


#[derive(Debug, Deserialize, Serialize)]
struct GameWorld {
    game_info: GameInfo,
    world_state: WorldState,
    areas: HashMap<String, GameArea>,
    characters: HashMap<String, Character>,
}

#[derive(Debug, Deserialize, Serialize)]
struct Character {
    name: String,
    profession: String,
    location: String,
    personality: Personality,
    speech: SpeechPattern,
    knowledge: Knowledge,
    relationships: HashMap<String, Relationship>,
    dialogue: HashMap<String, DialogueOption>,
}

#[derive(Debug, Deserialize, Serialize)]
struct Condition {
    #[serde(rename = "type")]
    condition_type: String,
    
    // Flatten all other fields into this map
    #[serde(flatten)]
    parameters: HashMap<String, serde_yaml::Value>,
}

// Load multiple files
let world: GameWorld = serde_yaml::from_str(&fs::read_to_string("main_game.yaml")?)?;
let characters: HashMap<String, Character> = 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):

{
  "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)

{
  "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)

{
  "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)

{
  "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)

{
  "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)

{
  "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:

# 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:

# 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:


# 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:

# 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:


# 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:

# 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:

# 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

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<GameValue>),
    Object(HashMap<String, GameValue>),
}

// 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<String, GameValue>,
}

// Generic effect system
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Effect {
    #[serde(rename = "type")]
    pub effect_type: String,
    
    #[serde(flatten)]
    pub parameters: HashMap<String, GameValue>,
}

// Generic trait system for inheritable behaviors
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct Trait {
    pub name: String,
    pub description: Option<String>,
    pub modifiers: HashMap<String, GameValue>,
    pub behaviors: Vec<String>,
    pub conditions: Vec<Condition>,
}

Game World Structure

#[derive(Debug, Deserialize, Serialize)]
pub struct GameWorld {
    pub game_info: GameInfo,
    pub world_state: WorldState,
    pub areas: HashMap<String, GameArea>,
    pub characters: HashMap<String, Character>,
    pub items: HashMap<String, Item>,
    pub traits: HashMap<String, Trait>,
    pub templates: HashMap<String, Template>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct GameInfo {
    pub name: String,
    pub version: String,
    pub description: String,
    pub dependencies: Vec<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct WorldState {
    pub current_time: u64,
    pub weather: String,
    pub events: HashMap<String, GameValue>,
    pub global_variables: HashMap<String, GameValue>,
}

Area System

#[derive(Debug, Deserialize, Serialize)]
pub struct GameArea {
    pub name: String,
    pub description: String,
    pub connections: HashMap<String, Connection>,
    pub items: Vec<String>,
    pub npcs: Vec<String>,
    pub properties: HashMap<String, GameValue>,
    pub events: Vec<AreaEvent>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Connection {
    pub direction: String,
    pub description: String,
    pub conditions: Vec<Condition>,
    pub effects: Vec<Effect>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct AreaEvent {
    pub name: String,
    pub triggers: Vec<Condition>,
    pub effects: Vec<Effect>,
    pub chance: Option<f64>,
}

Inheritable Character System

#[derive(Debug, Deserialize, Serialize)]
pub struct Character {
    pub name: String,
    pub description: String,
    
    // Inheritance system
    pub inherits_from: Option<String>,
    pub traits: Vec<String>,
    
    // Core properties
    pub stats: HashMap<String, GameValue>,
    pub location: String,
    pub properties: HashMap<String, GameValue>,
    
    // Behavior system
    pub behaviors: Vec<String>,
    pub dialogue: DialogueSystem,
    pub relationships: HashMap<String, Relationship>,
    
    // AI context
    pub ai_context: Option<String>,
    pub personality_prompt: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct DialogueSystem {
    pub topics: HashMap<String, DialogueTopic>,
    pub greetings: HashMap<String, String>,
    pub reactions: HashMap<String, DialogueReaction>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct DialogueTopic {
    pub unlock_conditions: Vec<Condition>,
    pub responses: Vec<DialogueResponse>,
    pub effects: Vec<Effect>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct DialogueResponse {
    pub text: String,
    pub conditions: Vec<Condition>,
    pub effects: Vec<Effect>,
    pub next_topic: Option<String>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct DialogueReaction {
    pub trigger: String,
    pub response: String,
    pub effects: Vec<Effect>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct Relationship {
    pub level: String,
    pub value: f64,
    pub history: Vec<String>,
}

Inheritable Item System


#[derive(Debug, Deserialize, Serialize)]
```rust
pub struct Item {
    pub name: String,
    pub description: String,    
    
    // Inheritance
    pub inherits_from: Option<String>,
    pub traits: Vec<String>,
    
    // Properties
    pub properties: HashMap<String, GameValue>,
    pub stats: HashMap<String, GameValue>,
    
    // Behavior
    pub actions: Vec<ItemAction>,
    pub effects: Vec<Effect>,
    pub conditions: Vec<Condition>,
}

#[derive(Debug, Deserialize, Serialize)]
pub struct ItemAction {
    pub name: String,
    pub description: String,
    pub conditions: Vec<Condition>,
    pub effects: Vec<Effect>,
}

Template System

```rust
#[derive(Debug, Deserialize, Serialize)]
pub struct Template {
    pub name: String,
    pub template_type: String, // "character", "item", "area", etc.
    pub properties: HashMap<String, GameValue>,
    pub traits: Vec<String>,
    pub behaviors: Vec<String>,
}

Runtime System for Inheritance


pub struct GameEngine {
    pub world: GameWorld,
    pub loaded_files: HashMap<String, GameWorld>,
    pub resolved_cache: HashMap<String, Box<dyn Any>>,
}

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::<Character>() {
                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

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<String, GameValue>, key: &str) -> Option<&String> {
        if let Some(GameValue::String(s)) = params.get(key) {
            Some(s)
        } else {
            None
        }
    }
    
    fn get_number_param(&self, params: &HashMap<String, GameValue>, key: &str) -> Option<f64> {
        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

fn main() -> Result<(), Box<dyn std::error::Error>> {
    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<String, GameValue> for dynamic data and resolving inheritance at runtime rather than trying to model everything statically in the type system.