Skip to content

Emotion ​

How NPCs feel — derived state, not stored. Always fresh, always contextual.

Core Idea ​

Emotion in AI World Sim is derived, not stored.

This means an NPC's emotion is always computed fresh from their current needs + recent events. There's no "anger level" that decays over time. There's only "given what just happened and what state they're in, how do they feel right now?"

This produces more natural behavior than storing emotions as state variables.


Emotion Model ​

Discrete Labels ​

NPCs feel one of these emotions at any moment:

ts
type Emotion =
  | 'happy'
  | 'sad'
  | 'angry'
  | 'anxious'
  | 'lonely'
  | 'content'
  | 'frustrated'
  | 'excited'
  | 'bored'
  | 'neutral';

Plus an Intensity ​

ts
type EmotionalState = {
  label: Emotion;
  intensity: number;  // 0-1
};

How Emotion is Derived ​

Step 1 — Base Score from Needs ​

ts
function emotionFromNeeds(needs: Needs): EmotionScore {
  let score = { happy: 0, sad: 0, anxious: 0, ... };

  if (needs.hunger > 80) score.frustrated += 0.6;
  if (needs.energy < 20) score.bored += 0.4;
  if (needs.social > 70) score.lonely += 0.7;
  if (needs.health < 30) score.sad += 0.5;
  if (needs.fun > 70) score.bored += 0.5;

  return score;
}

Step 2 — Event Modifier ​

Recent events shift the score:

ts
const eventModifiers = {
  'argument': { angry: +0.8, anxious: +0.3 },
  'good_news': { happy: +0.7, excited: +0.3 },
  'lost_money': { sad: +0.4, anxious: +0.6 },
  'met_friend': { happy: +0.6, content: +0.3 },
  'got_praised': { happy: +0.5 },
  'got_insulted': { angry: +0.6, sad: +0.3 },
};

Step 3 — Personality Modifier ​

ts
function personalityModifier(emotion: Emotion, personality: Personality): number {
  if (emotion === 'anxious' && personality.neuroticism > 0.7) return 1.5;
  if (emotion === 'happy' && personality.neuroticism > 0.7) return 0.5;
  if (emotion === 'lonely' && personality.extraversion < 0.3) return 1.4;
  return 1.0;
}

Step 4 — Memory Recall ​

Sometimes the system recalls a relevant memory that colors the emotion:

Margaret just heard Tom's name. She recalls the argument they had last week. Her current emotion is amplified toward frustrated + sad.

Step 5 — LLM Refinement (optional) ​

For ambiguous cases, the LLM is consulted to choose between close-scoring emotions.


What Emotion Affects ​

Emotion influences:

SystemHow
DecisionBias toward emotion-congruent actions
ConversationTone of voice, word choice
DiaryOverall mood of the entry
RelationshipsEasier to offend when angry, etc.

Emotion Decay ​

Emotion does not persist across ticks by default. It's re-derived each tick.

However, strong emotions (intensity > 0.8) leave a memory trace:

ts
if (state.intensity > 0.8) {
  await memory.write({
    type: 'emotion',
    content: `Felt very ${state.label} because ${event.summary}`,
    importance: 0.9,
  });
}

This memory can then re-trigger the emotion if similar contexts arise.


Mood vs Emotion ​

We make a careful distinction:

  • Emotion — current, derived, lasts one tick
  • Mood — longer-term baseline, persists across ticks, computed from rolling average of recent emotions
ts
function computeMood(npc: NPC): Emotion {
  // Average last 24 hours of emotions
  // Weight recent ones higher
  // Return dominant mood
}

Mood is what makes an NPC generally grumpy or generally cheerful over weeks.


Emotion in Diary ​

Diary generation takes the NPC's average emotion over the day as the dominant tone:

"It was a content day. Nothing special happened, but nothing bad either."

If the day's emotion was unstable, the diary may reflect that:

"Started anxious, ended happy. Eleanor came by in the afternoon and we talked for an hour."


Failure Modes ​

The emotion system has been tuned to avoid:

  • ❌ Constant drama — NPCs feeling extreme emotions every tick
  • ❌ Emotional flatness — NPCs feeling nothing ever
  • ❌ Incoherent swings — wild mood changes with no cause

The baseline assumption is that most NPCs feel mostly content or neutral on most days. Strong emotions are reserved for actual events.

Last updated:

Released under the MIT License.