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:
type Emotion =
| 'happy'
| 'sad'
| 'angry'
| 'anxious'
| 'lonely'
| 'content'
| 'frustrated'
| 'excited'
| 'bored'
| 'neutral';Plus an Intensity
type EmotionalState = {
label: Emotion;
intensity: number; // 0-1
};How Emotion is Derived
Step 1 — Base Score from Needs
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:
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
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:
| System | How |
|---|---|
| Decision | Bias toward emotion-congruent actions |
| Conversation | Tone of voice, word choice |
| Diary | Overall mood of the entry |
| Relationships | Easier 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:
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
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.