companion.json
Local metadata that drives the companion runtime — schema, animations, interactions, personality, and capabilities.
Architecture
The docs companion runtime loads companion.json, resolves triggers, executes interaction actions, then updates the engine state machine and animation controller. The pipeline is:
companion.json
→ trigger resolver
→ interaction executor
→ runtime engine
→ rendererTriggers and actions are generic. Runtime code never branches on a companion id — behavior comes only from metadata.
Schema
id/name/type— species identity (for exampleflameorghost).description— short human-readable summary.personalityTraits— default personality tags for the species.capabilities— declared abilities such asfloating,followCursor, andreactToClick.interactions— metadata-driven behaviors with trigger, action, animation, cooldown, and duration.instances— optional named profiles that override name, traits, mood bias, and behavior preferences without changing assets.assets/animations— sprite paths and clips.
Animations
Each entry under animations is a clip id mapped to either a frame path array or a clip object with frames, optional fps, and optional loop. Common roles:
idle— ambient loop while waiting.happy— short reaction / celebration one-shot.sleep— dozing loop.fall— falling loop while gravity runs.
Paths are relative to the companion folder. The engine picks clips from runtime state and interaction metadata — not from companion-specific React branches.
Interactions
Each interaction supports:
id— stable interaction key.trigger—click | double_click | idle_timeout | drag_start | drag_end | drop | cursor_nearby | page_navigation | manual.action—play_animation | change_state | jump | sleep | move.animation— clip id to play.cooldown— milliseconds before the interaction can fire again.duration— how long the resulting behavior should last.state— optional target state forchange_state.
Legacy entries that only declare id and description are still accepted; the parser infers trigger and action from the id.
Personality
Species assets stay shared. Named instances override display name, traits, mood bias, and behavior preferences:
"instances": {
"spark": {
"name": "Spark",
"moodBias": "excited",
"behavior": { "energy": "high", "idleTimeoutMs": 2000 }
},
"ash": {
"name": "Ash",
"moodBias": "sleepy",
"behavior": { "energy": "calm", "idleTimeoutMs": 4800 }
}
}Deterministic moods are happy, neutral, sleepy, and excited. Mood is derived from runtime state plus personality bias and can nudge idle timeout behavior. There is no AI in this path.
Capabilities
floating— reserved for soft hover presentation.followCursor— reserved for future trail behavior.reactToClick— gates click and double-click interactions in the executor.
Example
Ember's built-in metadata (paths are relative to the companion folder):
{
"id": "ember",
"name": "Ember",
"type": "flame",
"description": "A tiny playful flame spirit.",
"lore": "Born from the lava of a restless volcano, Ember hopped onto cooler stone and never looked back. It warms desks, scorches boredom, and still smells faintly of ash and summer lightning.",
"personalityTraits": [
"playful",
"curious",
"energetic"
],
"capabilities": {
"floating": true,
"followCursor": false,
"reactToClick": true
},
"instances": {
"spark": {
"name": "Spark",
"personalityTraits": [
"energetic",
"bold",
"bright"
],
"moodBias": "excited",
"behavior": {
"energy": "high",
"idleTimeoutMs": 30000,
"cursorNearbyRadius": 140
}
},
"ash": {
"name": "Ash",
"personalityTraits": [
"calm",
"gentle",
"warm"
],
"moodBias": "sleepy",
"behavior": {
"energy": "calm",
"idleTimeoutMs": 30000,
"cursorNearbyRadius": 90
}
}
},
"interactions": [
{
"id": "react-click",
"description": "Reacts when clicked",
"trigger": "click",
"action": "play_animation",
"animation": "happy",
"cooldown": 900,
"duration": 1800
},
{
"id": "jump",
"description": "Bounces upward with heat",
"trigger": "double_click",
"action": "jump",
"animation": "happy",
"cooldown": 1200,
"duration": 900
},
{
"id": "sleep",
"description": "Dims and dozes off",
"trigger": "idle_timeout",
"action": "sleep",
"animation": "sleep",
"cooldown": 6000,
"duration": 4200
},
{
"id": "idle",
"description": "Softly flickers while waiting",
"trigger": "idle_timeout",
"action": "play_animation",
"animation": "idle",
"cooldown": 0,
"duration": 2800
},
{
"id": "fall",
"description": "Falls after being dropped",
"trigger": "drop",
"action": "change_state",
"state": "falling",
"animation": "fall",
"cooldown": 0
},
{
"id": "drag-start",
"description": "Settles when picked up",
"trigger": "drag_start",
"action": "play_animation",
"animation": "idle",
"cooldown": 0
},
{
"id": "nearby",
"description": "Notices a nearby cursor",
"trigger": "cursor_nearby",
"action": "play_animation",
"animation": "happy",
"cooldown": 2500,
"duration": 1200
},
{
"id": "page-hello",
"description": "Greets after navigation",
"trigger": "page_navigation",
"action": "play_animation",
"animation": "happy",
"cooldown": 4000,
"duration": 1400
},
{
"id": "scroll-glance",
"description": "Glances when the page scrolls",
"trigger": "scroll",
"action": "play_animation",
"animation": "idle",
"cooldown": 2200,
"duration": 900
},
{
"id": "celebrate",
"description": "Bursts brighter in joy",
"trigger": "click",
"action": "play_animation",
"animation": "celebrate",
"cooldown": 1200,
"duration": 1800
},
{
"id": "wake",
"description": "Flare-wakes from a nap",
"trigger": "click",
"action": "change_state",
"state": "idle",
"animation": "idle",
"cooldown": 400
},
{
"id": "blink",
"description": "Blinks with bright ember eyes",
"trigger": "idle_timeout",
"action": "play_animation",
"animation": "blink",
"cooldown": 5000,
"duration": 500
},
{
"id": "wiggle",
"description": "Wiggles with a happy squash",
"trigger": "surface_action",
"action": "play_animation",
"animation": "wiggle",
"cooldown": 1400,
"duration": 1400
},
{
"id": "dance",
"description": "Dances happily on command",
"trigger": "scroll",
"action": "play_animation",
"animation": "dance",
"duration": 1800,
"cooldown": 2800
},
{
"id": "spin",
"description": "Spins like a tiny spark",
"trigger": "page_navigation",
"action": "play_animation",
"animation": "spin",
"cooldown": 4500,
"duration": 1600
},
{
"id": "surprise",
"description": "Pops up in surprise",
"trigger": "double_click",
"action": "jump",
"animation": "surprise",
"cooldown": 1400,
"duration": 1400
},
{
"id": "glow",
"description": "Glows warmer for a moment",
"trigger": "cursor_nearby",
"action": "play_animation",
"animation": "glow",
"cooldown": 3000,
"duration": 1600
},
{
"id": "follow-cursor",
"description": "Can trail the cursor later",
"trigger": "cursor_nearby",
"action": "move",
"animation": "idle",
"cooldown": 3000,
"duration": 1000
}
],
"assets": {
"idle": "assets/idle.png"
},
"animations": {
"idle": {
"frames": [
"animations/idle/1.png",
"animations/idle/2.png",
"animations/idle/3.png",
"animations/idle/4.png",
"animations/idle/5.png"
],
"fps": 6,
"loop": true
},
"happy": {
"frames": [
"animations/happy/1.png",
"animations/happy/2.png",
"animations/happy/3.png",
"animations/happy/4.png"
],
"fps": 10,
"loop": false
},
"sleep": {
"frames": [
"animations/sleep/1.png",
"animations/sleep/2.png",
"animations/sleep/3.png"
],
"fps": 3,
"loop": true
},
"fall": {
"frames": [
"animations/fall/1.png",
"animations/fall/2.png",
"animations/fall/3.png"
],
"fps": 10,
"loop": true
},
"puff": {
"frames": [
"animations/puff/1.png",
"animations/puff/2.png",
"animations/puff/3.png",
"animations/puff/4.png"
],
"fps": 12,
"loop": false
},
"blink": {
"frames": [
"animations/blink/1.png",
"animations/blink/2.png",
"animations/blink/3.png"
],
"fps": 8,
"loop": false
},
"celebrate": {
"frames": [
"animations/celebrate/1.png",
"animations/celebrate/2.png",
"animations/celebrate/3.png",
"animations/celebrate/4.png",
"animations/celebrate/5.png"
],
"fps": 12,
"loop": false
},
"dance": {
"frames": [
"animations/dance/1.png",
"animations/dance/2.png",
"animations/dance/3.png",
"animations/dance/4.png",
"animations/dance/5.png",
"animations/dance/6.png"
],
"fps": 12,
"loop": true
},
"surprise": {
"frames": [
"animations/surprise/1.png",
"animations/surprise/2.png",
"animations/surprise/3.png"
],
"fps": 10,
"loop": false
},
"glow": {
"frames": [
"animations/glow/1.png",
"animations/glow/2.png",
"animations/glow/3.png",
"animations/glow/4.png",
"animations/glow/5.png"
],
"fps": 10,
"loop": true
},
"spin": {
"frames": [
"animations/spin/1.png",
"animations/spin/2.png",
"animations/spin/3.png",
"animations/spin/4.png",
"animations/spin/5.png",
"animations/spin/6.png",
"animations/spin/7.png",
"animations/spin/8.png"
],
"fps": 14,
"loop": true
},
"cry": {
"frames": [
"animations/cry/1.png",
"animations/cry/2.png",
"animations/cry/3.png",
"animations/cry/4.png"
],
"fps": 8,
"loop": true
},
"wiggle": {
"frames": [
"animations/wiggle/1.png",
"animations/wiggle/2.png",
"animations/wiggle/3.png",
"animations/wiggle/4.png",
"animations/wiggle/5.png",
"animations/wiggle/6.png"
],
"fps": 12,
"loop": true
}
}
}Custom companions
For a future authoring workflow, see Custom Companion.