molroomolroo
Guides

Gaming Integration

Use appraisal vectors to give NPCs real emotions in game events.

Gaming Integration

Games are full of events that are not text -- combat hits, item drops, level ups, NPC deaths, environmental changes. The appraisal endpoint (POST /v1/turn/appraisal) lets you translate these game events directly into emotional state changes without any LLM calls. This guide shows how to map game events to appraisal vectors and use the results for NPC dialogue and behavior.

Why Appraisal Vectors for Games?

The standard POST /v1/turn endpoint takes a text description and runs it through cognitive appraisal internally. This works for conversational apps, but games have structured events that you already understand. With the appraisal endpoint, you:

  • Skip the text interpretation step -- faster response times, lower cost
  • Control exactly how events are perceived -- you decide how relevant or surprising an event is to the NPC
  • Map any game mechanic -- combat, economy, social systems, environmental events
  • Keep it deterministic -- the same appraisal vector always produces the same emotional shift

Mapping Game Events to Appraisals

The 6-dimensional appraisal vector captures how a character perceives an event:

DimensionRangeGame Meaning
goal_relevance[0, 1]How much does this event matter to the NPC?
goal_congruence[-1, 1]Does this help (+) or harm (-) the NPC's goals?
expectedness[0, 1]Was this event anticipated?
controllability[0, 1]Can the NPC do anything about it?
agency[-1, 1]Who caused it? -1 = external, +1 = self
norm_compatibility[-1, 1]Does this align with the NPC's values?

Common Game Event Mappings

Game Eventgoal_relevancegoal_congruenceexpectednesscontrollabilityagencynorm_compatibility
Enemy critical hit0.8-0.90.30.2-0.40.0
Level up0.90.90.50.80.80.7
Ally death1.0-1.00.10.1-0.8-0.5
Treasure found0.60.80.20.50.30.5
Betrayal by ally1.0-0.90.10.1-0.9-1.0
Boss defeated1.01.00.40.70.90.8
Ambush0.9-0.70.00.2-0.8-0.3
Healing received0.50.70.60.3-0.50.6

Use these as starting points and adjust for your specific game mechanics and NPC personalities.

Code Example: Processing a Game Event

curl

# NPC takes critical damage
curl -X POST https://api.molroo.io/v1/turn/appraisal \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "sessionId": "npc_warrior_001",
    "context": "Enemy landed a critical hit for 50 damage",
    "appraisal": {
      "goal_relevance": 0.8,
      "goal_congruence": -0.9,
      "expectedness": 0.3,
      "controllability": 0.2,
      "agency": -0.4,
      "norm_compatibility": 0.0
    }
  }'

JavaScript

async function processGameEvent(npcSessionId, event) {
  const response = await fetch('https://api.molroo.io/v1/turn/appraisal', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer YOUR_API_KEY',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      sessionId: npcSessionId,
      context: event.description,
      appraisal: event.appraisal,
    }),
  })
 
  return response.json()
}
 
// NPC takes critical damage
const result = await processGameEvent('npc_warrior_001', {
  description: 'Enemy landed a critical hit for 50 damage',
  appraisal: {
    goal_relevance: 0.8,
    goal_congruence: -0.9,
    expectedness: 0.3,
    controllability: 0.2,
    agency: -0.4,
    norm_compatibility: 0.0,
  },
})
 
console.log(result.discrete_emotion)  // "fear" or "anger"
console.log(result.emotion_intensity) // 0.82
console.log(result.new_emotion)       // { V: -0.6, A: 0.8, D: -0.3 }
console.log(result.body_budget)       // { energy: 0.45, stress: 0.85 }

Building an Event Mapper

Rather than hardcoding appraisal values each time, create a mapping function:

const EVENT_MAPPINGS = {
  critical_hit: {
    goal_relevance: 0.8,
    goal_congruence: -0.9,
    expectedness: 0.3,
    controllability: 0.2,
    agency: -0.4,
    norm_compatibility: 0.0,
  },
  level_up: {
    goal_relevance: 0.9,
    goal_congruence: 0.9,
    expectedness: 0.5,
    controllability: 0.8,
    agency: 0.8,
    norm_compatibility: 0.7,
  },
  ally_death: {
    goal_relevance: 1.0,
    goal_congruence: -1.0,
    expectedness: 0.1,
    controllability: 0.1,
    agency: -0.8,
    norm_compatibility: -0.5,
  },
  treasure_found: {
    goal_relevance: 0.6,
    goal_congruence: 0.8,
    expectedness: 0.2,
    controllability: 0.5,
    agency: 0.3,
    norm_compatibility: 0.5,
  },
}
 
async function onGameEvent(npcSessionId, eventType, description) {
  const appraisal = EVENT_MAPPINGS[eventType]
  if (!appraisal) return null
 
  const response = await fetch('https://api.molroo.io/v1/turn/appraisal', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      sessionId: npcSessionId,
      context: description,
      appraisal,
    }),
  })
 
  return response.json()
}
 
// Usage
const result = await onGameEvent(
  'npc_warrior_001',
  'critical_hit',
  'Orc chieftain slammed their axe for 50 damage'
)

Dynamic Appraisals

For more nuance, scale appraisal values based on game context:

function getHitAppraisal(damage, maxHp, currentHp) {
  const severityRatio = damage / maxHp
  const remainingRatio = (currentHp - damage) / maxHp
 
  return {
    goal_relevance: Math.min(severityRatio * 2, 1.0),       // More relevant for bigger hits
    goal_congruence: -0.5 - (severityRatio * 0.5),          // Worse for bigger hits
    expectedness: 0.3,                                       // Adjust based on combat context
    controllability: Math.max(remainingRatio, 0.1),          // Less control when near death
    agency: -0.4,                                            // External cause
    norm_compatibility: 0.0,                                 // Neutral
  }
}
 
const appraisal = getHitAppraisal(50, 200, 120)

Using prompt_data for NPC Dialogue

The response includes prompt_data.formatted -- a pre-built text block you can feed directly into your dialogue system or LLM:

const result = await onGameEvent(
  'npc_warrior_001',
  'ally_death',
  'Companion Lyra fell in battle against the shadow knight'
)
 
// prompt_data.formatted contains the full emotional context:
// - Current emotion state and intensity
// - Body budget (energy, stress)
// - Personality influence on the response
// - Suggested response parameters (tone, engagement, verbosity)
 
// Feed it into your LLM for dialogue generation
const completion = await openai.chat.completions.create({
  model: 'gpt-4o',
  messages: [
    {
      role: 'system',
      content: result.prompt_data.formatted,
    },
    {
      role: 'user',
      content: 'The warrior looks at where Lyra fell.',
    },
  ],
})
 
// The LLM now generates dialogue informed by genuine grief
const npcDialogue = completion.choices[0].message.content

Using Emotion Data for Non-Dialogue Behavior

You do not need an LLM for every use case. Use the raw emotion data to drive game mechanics directly:

function updateNpcBehavior(npc, emotionResult) {
  const { discrete_emotion, emotion_intensity, body_budget } = emotionResult
 
  // Adjust combat behavior based on emotion
  if (discrete_emotion === 'fear' && emotion_intensity > 0.7) {
    npc.setBehavior('flee')
  } else if (discrete_emotion === 'anger' && emotion_intensity > 0.6) {
    npc.setAttackBonus(emotion_intensity * 0.3)
    npc.setBehavior('aggressive')
  }
 
  // Adjust animation/expression
  npc.setExpression(discrete_emotion)
  npc.setAnimationIntensity(emotion_intensity)
 
  // Use body budget for stamina
  npc.setStamina(body_budget.energy)
  if (body_budget.stress > 0.8) {
    npc.addStatusEffect('shaken')
  }
}

Persistent NPC State

Sessions persist across game sessions automatically. When a player saves and returns later, the NPC remembers its emotional history:

// Day 1: Player helps the NPC
await onGameEvent('npc_merchant_042', 'helped_by_player', 'Player rescued shop from bandits')
 
// Day 2: Player returns — the NPC still remembers
// Use the same session ID to continue the emotional state
const state = await fetch('https://api.molroo.io/v1/state/npc_merchant_042', {
  headers: { 'Authorization': 'Bearer YOUR_API_KEY' },
}).then(r => r.json())
 
console.log(state.interpersonal.rapport) // 0.72 — high rapport from previous help
console.log(state.interpersonal.trust)   // 0.65 — trust built from past interaction

Tip: Use deterministic session IDs based on your NPC identity, such as npc_{npcId} or {saveSlot}_npc_{npcId}, so the same NPC always maps to the same emotional session.

Batch Processing Multiple NPCs

When a game event affects multiple NPCs (like an explosion in a town square), process them in parallel:

async function processBatchEvent(npcSessions, eventType, description) {
  const appraisal = EVENT_MAPPINGS[eventType]
 
  const results = await Promise.all(
    npcSessions.map((sessionId) =>
      fetch('https://api.molroo.io/v1/turn/appraisal', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          sessionId,
          context: description,
          appraisal,
        }),
      }).then((r) => r.json())
    )
  )
 
  return results
}
 
// Explosion in town square — every nearby NPC reacts
const nearbyNpcs = ['npc_merchant_042', 'npc_guard_007', 'npc_child_003']
const reactions = await processBatchEvent(nearbyNpcs, 'ambush', 'Explosion in town square')
 
// Each NPC reacts differently based on their personality and current state
reactions.forEach((result, i) => {
  console.log(`${nearbyNpcs[i]}: ${result.discrete_emotion} (${result.emotion_intensity})`)
})

WebSocket for Real-time Games

For games that need frequent emotion updates (action RPGs, real-time combat), use a WebSocket connection instead of REST calls. See the WebSocket guide for setup details.

const ws = new WebSocket(
  `wss://api.molroo.io/v1/ws/npc_warrior_001?token=${API_KEY}`
)
 
ws.onmessage = (event) => {
  const msg = JSON.parse(event.data)
  if (msg.type === 'turn_result') {
    updateNpcBehavior(npc, msg.data)
  }
}
 
// Send events as they happen in real time
function onCombatHit(damage) {
  ws.send(JSON.stringify({
    type: 'appraisal',
    appraisal: getHitAppraisal(damage, npc.maxHp, npc.currentHp),
    context: `Took ${damage} damage in combat`,
  }))
}