docs: add NPC inner monologue design (task 1.4)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
root
2026-03-08 20:53:51 +00:00
parent 69c36e776b
commit b151d8a785
@@ -0,0 +1,136 @@
# Task 1.4: NPC Inner Monologue / Thought Bubbles
## Overview
Periodically generate inner thoughts for NPCs based on their needs, relationships, and recent events. Display as italic first-person text in the NPC info panel and mood-contextual emoji bubbles floating above sprites.
## Architecture
### Data Flow
1. `ThoughtSystem` runs each tick, tracks per-NPC cooldowns and a global generation timer
2. Every ~90s, collects followed NPCs + event-triggered NPCs into a batch
3. Sends one batched LLM request for up to 8 NPCs
4. Parses numbered responses, stores as `thought` component on each entity
5. Broadcasts thoughts to clients via `npc-thought` socket event
6. Client: info panel shows italic first-person text; sprite shows floating emoji
### New ECS Component
```typescript
interface Thought {
text: string; // First-person thought, e.g. "I could really use something to eat..."
emoji: string; // Mood emoji, e.g. "🍖"
tick: number; // When generated
}
```
### New Server Systems/Services
- **`ThoughtSystem`** — per-tick system managing timing, cooldowns, batch collection
- **`ThoughtGenerator`** — batched LLM prompt builder + response parser (in `server/src/llm/`)
### Triggers
Each trigger respects a per-NPC cooldown of ~90s minimum.
| Trigger | Condition |
|---------|-----------|
| Periodic | Every ~90s for followed NPCs (fallback if no event-driven thought recently) |
| Need critical | Hunger or energy below threshold |
| Post-interaction | Social outcome just fired |
| Relationship tier change | Classification changed |
| Idle | NPC wandering with nothing notable happening |
### Batching Strategy
- Collect up to 8 pending thought requests per cycle
- Single LLM call with numbered NPC contexts (name, personality, state, recent events)
- Parse numbered responses back to individual NPCs
- If parsing fails for a line, skip that NPC (no crash, no retry)
- Batch size, timer interval, and cooldown are configurable constants
### Rate Budget
- Target: ~20 thought requests/hour (leaving ~20/hour for backstories + narrations)
- Batch size 8 means 20 requests can serve ~160 individual thoughts/hour
- Per-NPC cooldown prevents any single NPC from dominating
- Daily budget: ~480 thought requests/day well within 1000/day limit shared across all features
- All timing constants configurable for tuning
### Emoji Mapping
Derived from thought context before LLM call:
| Emoji | Condition |
|-------|-----------|
| 🍖 | Hungry (low hunger need) |
| 😴 | Tired (low energy need) |
| 😊 | Positive interaction / good mood |
| 😤 | Negative interaction / frustrated |
| 🤔 | Idle / pondering / curious personality |
| 💭 | Generic fallback |
### Socket Events
**New server → client event:**
- `npc-thought``{ entityId: EntityId, text: string, emoji: string }`
Sent to all clients when a thought is generated. Clients decide whether to display based on local follow state and visibility.
### Client Display
**Info Panel (NpcInfoPanel.ts):**
- New "Thought" section above Recent Events
- Italic first-person text, e.g. *"I could really use something to eat..."*
- Shows only the most recent thought, replaced when a new one arrives
- Hidden when no thought exists for this NPC
**Map Sprite (GameScene.ts):**
- Small emoji rendered above NPC sprite head
- Fade-in over ~0.5s, hold ~4s, fade-out over ~0.5s
- Only one emoji at a time per NPC (new replaces old)
### Prompt Template
Batched template asking for multiple NPC thoughts in one request:
```
System: You write brief NPC inner thoughts in first person. One sentence each, grounded and specific. No purple prose. Respond with numbered lines matching the input.
User:
Generate a brief inner thought for each NPC:
1. Bjorn — Personality: SOC:6, EMP:7, TMP:13, CUR:11. State: very hungry, just argued with Helga (rival).
2. Helga — Personality: SOC:14, EMP:15, TMP:8, CUR:9. State: content, recently befriended Sven.
3. Sven — Personality: SOC:10, EMP:12, TMP:10, CUR:16. State: idle, wandering.
```
Expected response:
```
1. My stomach is killing me... and that smug look on Helga's face isn't helping.
2. It's nice having someone like Sven around — someone who actually listens.
3. I wonder what's past those hills to the north...
```
### Priority Rules
1. Followed NPCs always eligible for thoughts
2. Event-triggered thoughts (need critical, tier change) eligible regardless of follow status — but only if the NPC is followed (to conserve budget)
3. Non-followed NPCs do not generate thoughts (can revisit later)
### Configuration
All tuning values in a config object (similar to `relationshipConfig.ts`):
```typescript
export const thoughtConfig = {
periodicIntervalTicks: number; // ~90s worth of ticks
perNpcCooldownTicks: number; // ~90s worth of ticks
maxBatchSize: number; // 8
needCriticalThreshold: number; // e.g. 20 (out of 100)
emojiFadeDurationMs: number; // 500
emojiHoldDurationMs: number; // 4000
};
```