docs: add NPC inner monologue design (task 1.4)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
|
||||
};
|
||||
```
|
||||
Reference in New Issue
Block a user