Documentation

Everything you need to bring living characters into your game - from first install to production deployment.

Getting Started

Go from zero to talking NPCs in five steps. VoxLore models run entirely on the player's machine - no cloud, no latency, no ongoing costs.

1

Create your world

Define the setting, lore, factions, and rules that all characters share. This becomes the foundation for consistent, world-aware dialogue.

2

Define your characters

Write character definitions with personality traits, backstory, speech patterns, and knowledge. Use our JSON format or the visual Character Creator.

3

Generate your model

Run the VoxLore CLI to fine-tune a compact LLM on your world and characters. The output is a single .gguf file that ships with your game.

4

Install the SDK

Drop the SDK into your engine of choice - Godot, Unity, Unreal, or Phaser. It handles model loading, inference, and response parsing.

5

Your NPCs are alive

Players talk to characters who respond in-character, with structured output your engine can use to drive animations, expressions, and gameplay.

Terminal
# Install the VoxLore CLI
npm install -g @voxlore/cli

# Or with pip
pip install voxlore
Terminal
# Generate your world model
voxlore generate --world ./my-world/ --output ./models/my_world.gguf
Terminal
# Install the SDK for your engine
npm install @voxlore/sdk        # Phaser / Web
# Unity and Godot: download from voxlore.dev/sdk
# Unreal: available via the Marketplace

World Building

A world is the shared context that all your characters live inside. It defines the setting, rules, factions, and lore so that every NPC speaks consistently about the same reality.

Your world directory contains a world.json manifest and individual character files. When you run voxlore generate, the pipeline reads the entire directory and produces a single model fine-tuned on everything inside.

world.json
{
  "name": "Skybound",
  "description": "A world of floating islands connected by wind currents, where sky-pirates and merchant guilds vie for control of the trade routes.",
  "era": "Industrial Fantasy",
  "rules": [
    "Gravity works normally on islands but is weak between them",
    "Airships are powered by captured storm energy",
    "Magic does not exist - all power comes from technology and weather manipulation"
  ],
  "factions": [
    { "name": "The Free Captains", "role": "Independent sky-pirate crews" },
    { "name": "Meridian Trade Guild", "role": "Controls legitimate commerce" },
    { "name": "The Stormweavers", "role": "Engineers who harvest lightning" }
  ],
  "characters": ["captain_zara.json", "merchant_orin.json", "engineer_val.json"]
}

Tip

The rules array is powerful. Use it to prevent characters from referencing things that don't exist in your world. If there's no magic, say so - the model will respect that boundary.

Character Creation

Each character is defined in a JSON file with personality traits, backstory, dialogue examples, and metadata the model uses to stay in character. The more detail you provide, the more consistent and nuanced the character becomes.

The personality object uses the OCEAN model (Big Five) - each trait is a float from 0.0 to 1.0. The model uses these values to modulate tone, word choice, and emotional range.

captain_zara.json
{
  "name": "Captain Zara",
  "system_prompt": "You are Captain Zara, a fearless sky-pirate captain who commands the airship Stormchaser. You speak with bold confidence, use nautical metaphors, and have a soft spot for underdogs. You never back down from a challenge and always keep your word.",
  "personality": {
    "openness": 0.8,
    "conscientiousness": 0.4,
    "extraversion": 0.9,
    "agreeableness": 0.6,
    "neuroticism": 0.2
  },
  "response_examples": [
    {
      "player": "I need help crossing the Shattered Reach.",
      "npc": "The Shattered Reach? Ha! Most captains won't even look at that stretch of sky. Lucky for you, I'm not most captains. Stormchaser's cut through worse."
    },
    {
      "player": "Why should I trust you?",
      "npc": "Trust is earned on the wind, friend. I could give you a list of references, but they're all either too drunk or too dead to vouch. My ship's still flying - that speaks for itself."
    }
  ],
  "animations": {
    "emotions": ["neutral", "happy", "angry", "sad", "surprised", "amused", "determined", "suspicious"],
    "faces": ["smirk", "wide grin", "furrowed brow", "raised eyebrow", "narrowed eyes", "soft smile"],
    "gestures": ["hand on hip", "arms crossed", "pointing forward", "drawing cutlass", "adjusting hat", "leaning on railing"]
  },
  "inventory": [
    { "item": "Stormchaser Cutlass", "description": "A well-worn blade with a lightning motif etched into the guard" },
    { "item": "Sky Chart", "description": "Detailed map of wind currents across the Shattered Reach" },
    { "item": "Captain's Compass", "description": "Always points toward the nearest storm" }
  ],
  "locations": [
    { "name": "The Stormchaser", "description": "Zara's airship - her home, her pride, and the fastest vessel in the fleet" },
    { "name": "Port Nimbus", "description": "A floating trade hub where Zara resupplies and picks up jobs" },
    { "name": "The Shattered Reach", "description": "A dangerous stretch of fractured sky islands only the bravest pilots navigate" }
  ],
  "quests": [
    { "id": "storm_run", "title": "The Storm Run", "description": "Help Zara navigate the Shattered Reach to deliver critical supplies" },
    { "id": "old_debt", "title": "An Old Debt", "description": "A rival captain from Zara's past has come to collect" }
  ],
  "knowledge": [
    "Knows the wind patterns of every major sky route",
    "Has a rivalry with Captain Vex of the Iron Talon",
    "Lost her first ship, the Breeze Runner, ten years ago",
    "Secretly funds an orphanage in Port Nimbus"
  ]
}
system_prompt

The core identity the model always follows. Write it as a direct instruction.

personality

OCEAN traits (0.0-1.0). High extraversion = bold speech; low agreeableness = blunt responses.

response_examples

Few-shot examples that teach the model the character's voice and speech patterns.

animations

Lists of valid emotions, facial expressions, and gestures the model can reference.

inventory

Items the character has. The model can reference these naturally in dialogue.

knowledge

Facts the character knows. Keeps responses consistent and prevents hallucination.

World Events

World events let you inject things that happen in your game into every NPC's awareness with a single API call. A bridge collapse, a dragon sighting, a festival starting - push the event once and every character reacts through the lens of their own personality.

Events are filtered by each NPC's OCEAN personality traits. A character with high conscientiousness worries about logistics and safety. A character with low agreeableness sees opportunity in chaos. The same event produces completely different reactions across your cast - no extra scripting required.

GDScript
# Push a world event - every NPC gets the news
voxlore.push_world_event("The old bridge collapsed during the storm")

# Events are filtered by each NPC's personality
# A warrior worries about supply routes
# A healer worries about injured travelers
# A thief sees an opportunity in the chaos

Personality-Filtered

Events pass through OCEAN trait filters. High neuroticism NPCs panic about a storm while low neuroticism NPCs shrug it off. Zero scripting per-NPC.

Player-NPC and NPC-to-NPC

Events flow into both conversation types. Players hear about events when they talk to NPCs, and NPCs discuss events with each other in the background.

Recency Weighted

Recent events dominate conversation naturally. Yesterday's dragon sighting gets more airtime than last week's market dispute.

Single API Call

Push once, reach every NPC. No need to manually inject events into each character's context - the SDK handles distribution and filtering.

Tip

Combine world events with NPC-to-NPC conversations for maximum immersion. Push an event like "Bandits raided the eastern farmsteads" and listen to NPCs debate what it means while the player walks through town.

Animation Constraints

Every game has a different animation vocabulary. An indie 2D game might have 5 emotion sprites. A AAA title might have 50 blend shape parameters. The animation constraint system lets you define exactly what flags are available per character, and the model will only output flags from your list.

This means no post-processing, no fuzzy matching, no "the model said melancholic_resignation but I only have sad". The output is machine-readable from the start.

Define your animation vocabulary

character_animations.json
{
  "animations": {
    "emotions": ["neutral", "happy", "angry", "sad", "surprised"],
    "gestures": ["wave", "point", "shrug", "bow", "crossed_arms"],
    "faces": ["smile", "frown", "smirk", "raised_eyebrow", "wide_eyes"],
    "body_poses": ["relaxed", "alert", "defensive", "confident"]
  }
}

Model outputs only your flags

Response JSON
{
  "dialogue": "You again? Last time you cost me three gold.",
  "emotion": "angry",
  "face": "frown",
  "body": "defensive",
  "gesture": "crossed_arms"
}
emotions

Valid emotion labels. The model picks one per response. Map these to animation states, audio tone parameters, or mood indicators.

gestures

Valid gesture labels. Trigger one-shot animation clips or particle effects. The model selects contextually appropriate gestures.

faces

Valid facial expression labels. Map to blend shapes, sprite frames, or portrait swaps. Each label maps to one visual state.

body_poses

Valid body pose labels. Drive your IK system, animation state machine, or character stance. The model picks based on dialogue context.

Why this matters

Without constraints, LLMs generate creative but unparseable descriptions like "a wistful half-smile tinged with regret". With constraints, you get sad - exactly what your animation system needs. Different characters can have different animation sets, so a boss with 20 emotions gets richer output than a background NPC with 3.

Game State Awareness

NPCs can reference the player's current game context - inventory items, active quests, location, time of day, and reputation. This creates natural, contextual dialogue where characters notice what the player is carrying, remember past interactions, and react to the environment.

Push game state updates whenever the player's context changes. The SDK injects this data into the prompt so NPCs can reference it naturally without breaking character.

GDScript
# Set the player's current game context
voxlore.set_game_state("captain_aldric", {
    "inventory": ["broken_sword", "health_potion", "old_map"],
    "active_quest": "find_missing_merchant",
    "location": "market_district",
    "time_of_day": "night",
    "reputation": 0.7
})

# Now when the player talks to Aldric:
# "I see you carry a broken sword. The smith on
#  Irongate Lane could fix that - if he's still alive.
#  The market's not safe at night. Watch your back."

Inventory

"I see you carry a broken sword. The smith on Irongate Lane could fix that."

Active Quests

"Looking for the missing merchant? Last seen heading east past the old mill."

Location & Time

"The market's not safe at night. Watch your back, traveler."

Reputation

"I've heard good things about you. Perhaps we can do business after all."

Context Pipeline

Under the hood, VoxLore assembles a structured system prompt from five layers. Studios configure layers 1 through 4. The model handles the rest. Zero ML expertise required.

This is the complete architecture of what gets sent to the model for every conversation turn. Understanding this helps you get the most out of each layer.

System Prompt Architecture
[CHARACTER]              - Who the NPC is
  system_prompt            Personality, backstory, voice
  personality (OCEAN)      Trait values that shape tone and word choice
  knowledge                Facts the NPC knows (and does not know)
  response_examples        Few-shot examples of the character's voice

[WORLD EVENTS]           - What is happening in the world
  active_events            Filtered by personality relevance
  event_recency            Recent events weighted higher

[GAME STATE]             - The player's current context
  inventory                Items the player carries
  active_quests            What the player is working on
  location / time          Where and when the conversation happens
  reputation               How the NPC feels about the player

[ANIMATION CONSTRAINTS]  - Available animation flags
  emotions                 Valid emotion labels for this character
  gestures                 Valid gesture labels
  faces                    Valid facial expression labels
  body_poses               Valid body pose labels

[FORMAT]                 - JSON output specification
  Enforced schema          dialogue, emotion, face, body, gesture
  Constrained decoding     Model can only output valid flags

CHARACTER

The NPC's identity - personality traits, backstory, speech patterns, knowledge boundaries. This is the foundation that never changes during a conversation.

WORLD EVENTS

Active events filtered by personality relevance and recency. A warrior hears about the battle before the festival; a bard hears about the festival first.

GAME STATE

The player's current context - inventory, quests, location, time, reputation. Updated dynamically as the player progresses through the game.

ANIMATION CONSTRAINTS

The character's available animation flags. Constrains the model to only output valid emotion, gesture, face, and body labels for this specific character.

FORMAT

The JSON output schema enforced via constrained decoding. The model can only produce valid JSON with the correct fields. You never get malformed output.

No ML expertise required

You configure characters, events, and game state through JSON and simple API calls. The SDK assembles the prompt, manages context windows, and handles constrained decoding. You focus on game design - VoxLore handles the LLM engineering.

Structured Output

Every response from the model is a typed JSON object - not raw text. This means your game engine can directly parse the response and use each field to drive different systems simultaneously.

No regex parsing, no string splitting, no guesswork. The model is constrained to always produce this exact schema.

Response JSON
{
  "dialogue": "Ha! Do I look like a coward to you?",
  "emotion": "amused",
  "face": "wide grin, eyes sparkling with defiance",
  "body": "hands on hips, chest out, stance wide",
  "gesture": "drawing cutlass slowly from belt"
}
dialogue
string

The spoken text to display in your dialogue UI. This is what the player reads.

emotion
string

An emotion label from the character's allowed emotions list. Use this to set the animation state or mood indicator.

face
string

A natural-language description of the character's facial expression. Map this to blend shapes, sprite frames, or your portrait system.

body
string

A description of the character's body posture and stance. Drive your IK system or select from a library of body poses.

gesture
string

A specific action or movement the character performs while speaking. Trigger animation clips or particle effects.

Why structured output matters

Traditional NPC dialogue systems give you a string. With structured output, a single model call gives you everything needed to animate the character, set the mood, and trigger gameplay events - all in one inference pass.

Engine Integration

The VoxLore SDK handles model loading, inference, and response parsing. Pick your engine below to see a complete integration example.

GDScript
# voxlore_npc.gd
extends Node

var voxlore: VoxLoreClient

func _ready():
    # Load your world's model (ships with your game)
    voxlore = VoxLoreClient.new()
    voxlore.load_model("res://models/my_world.gguf")

func chat(npc_name: String, player_message: String):
    # Send player input, get structured response
    var response = await voxlore.chat(npc_name, player_message)

    # Parse the structured JSON output
    var dialogue = response["dialogue"]
    var emotion = response["emotion"]
    var face = response["face"]
    var body = response["body"]
    var gesture = response["gesture"]

    # Drive your game systems
    $DialogueUI.show_text(dialogue)
    $AnimationTree.set_emotion(emotion)
    $FaceController.set_expression(face)
    $BodyController.set_pose(body)
    $GesturePlayer.play(gesture)

How it works

  1. 1.Load the model - Call loadModel() once at startup with the path to your .gguf file. The SDK initializes the inference runtime.
  2. 2.Send a chat message - Pass the NPC name and the player's text to chat(). The SDK looks up the character, builds the prompt, and runs inference.
  3. 3.Use the response - The return value is a parsed object with dialogue, emotion, face, body, and gesture. Route each to the appropriate game system.

API Reference

The SDK exposes a minimal, consistent API across all engines. These are the core methods you will use.

loadModel(path: string)returns void

Initialize the inference runtime with a .gguf model file. Call once at startup. The path should be relative to your project's asset directory.

chat(npcName: string, message: string)returns VoxLoreResponse

Send a player message to a named NPC and receive a structured response. The SDK handles prompt construction, context management, and JSON parsing internally.

setContext(npcName: string, context: object)returns void

Inject runtime context into an NPC's conversation - quest state, inventory changes, world events. The model uses this to adapt its responses.

pushWorldEvent(event: string)returns void

Broadcast a world event to all NPCs. The event is filtered through each NPC's OCEAN personality traits, so each character reacts differently. Events persist until cleared.

setGameState(npcName: string, state: GameState)returns void

Update the player's game context for a specific NPC. Includes inventory, active quests, location, time of day, and reputation. NPCs reference this naturally in dialogue.

startNPCConversation(npc1: string, npc2: string)returns void

Start an autonomous conversation between two NPCs. They exchange dialogue on a timer using the LLM, each maintaining their distinct voice. Player requests always take inference priority.

stopNPCConversation(npc1: string, npc2: string)returns void

Stop an ongoing NPC-to-NPC conversation. Use when the player walks away or a scene transition occurs.

setNPCChatInterval(npc1: string, npc2: string, seconds: number)returns void

Set the interval between autonomous NPC-to-NPC dialogue exchanges. Lower values create more frequent chatter. Default is 30 seconds.

resetConversation(npcName: string)returns void

Clear the conversation history for a specific NPC. Use this when the player starts a new scene or the game state changes significantly.

getConversationHistory(npcName: string)returns Message[]

Retrieve the full conversation history for an NPC. Useful for saving game state or displaying a conversation log to the player.

VoxLoreResponse schema

TypeScript
interface VoxLoreResponse {
  dialogue: string;   // The spoken text
  emotion: string;    // Emotion label (from character's emotion list)
  face: string;       // Facial expression description
  body: string;       // Body posture description
  gesture: string;    // Action or movement description
}