From cc8fc7def7117e984b6fa2a04898b88f3fa58f44 Mon Sep 17 00:00:00 2001 From: root Date: Sat, 7 Mar 2026 12:52:31 +0000 Subject: [PATCH] docs: add NPC social interactions design Covers awareness zone, interaction state machine, cooldowns, brain/movement guards, broadcast format, and client emoji overlay. Co-Authored-By: Claude Opus 4.6 --- ...26-03-07-npc-social-interactions-design.md | 100 ++++++++++++++++++ 1 file changed, 100 insertions(+) create mode 100644 docs/plans/2026-03-07-npc-social-interactions-design.md diff --git a/docs/plans/2026-03-07-npc-social-interactions-design.md b/docs/plans/2026-03-07-npc-social-interactions-design.md new file mode 100644 index 0000000..3d9ad04 --- /dev/null +++ b/docs/plans/2026-03-07-npc-social-interactions-design.md @@ -0,0 +1,100 @@ +# NPC Social Interactions Design + +## Overview + +Add NPC-to-NPC social interactions triggered by proximity. NPCs within a 5-tile awareness zone can initiate interactions that play out as a face-each-other → pause → emoji sequence. Outcomes are currently random but the system is designed to support future personality, relationships, and seek-out behavior. + +## New ECS Component: SocialState + +Every NPC receives a `SocialState` component at spawn time. + +```typescript +type InteractionPhase = 'none' | 'facing' | 'pausing' | 'emoting'; + +interface SocialState { + phase: InteractionPhase; + partnerId: string | null; + phaseTimer: number; // ticks remaining in current phase + outcome: 'positive' | 'negative' | null; + globalCooldown: number; // ticks until this NPC can interact with anyone + pairCooldowns: Map; // entityId -> ticks remaining +} +``` + +- `globalCooldown`: 50 ticks (5s) +- `pairCooldowns` entries: 300 ticks (30s) +- Cooldowns decrement each tick regardless of interaction state + +## Social System + +New `socialSystem` runs after `npcBrainSystem`, before `movementSystem`. + +System order: needsDecay → npcBrain → **social** → movement. + +### Phase 1: Update active interactions + +For NPCs with `phase !== 'none'`, decrement `phaseTimer`. When timer hits 0, advance: + +| Transition | Duration | Action | +|---|---|---| +| `facing` → `pausing` | 10 ticks (1s) | NPCs face each other, movement set to idle | +| `pausing` → `emoting` | 15 ticks (1.5s) | Roll outcome independently per NPC (50/50 positive/negative) | +| `emoting` → `none` | 20 ticks (2s) | Emoji visible; on exit set global + pair cooldowns | + +### Phase 2: Detect new interactions + +For NPCs with `phase === 'none'` and `globalCooldown === 0`: + +1. Find all other NPCs within 5-tile Manhattan distance +2. Filter out those with active pair cooldowns or already interacting +3. Pick closest eligible NPC as partner +4. **Target rejection**: If target is not wandering AND their relevant need is below threshold (hunger < 30 or energy < 20), skip — they're too busy +5. If valid partner: set both NPCs to `facing` phase, update directions to face each other + +Process in entity ID order to resolve conflicts — first initiator to claim an NPC wins. + +### Cooldown decay + +Decrement `globalCooldown` and all `pairCooldowns` each tick. Remove pair entries that hit 0. + +## Integration: Brain & Movement Guards + +- **npcBrainSystem**: If entity has `socialState.phase !== 'none'`, skip entirely +- **movementSystem**: Same check — skip entities in active social phase +- Path preservation: movement component's `target` and `path` are left untouched during interaction. When phase returns to `none`, movement resumes naturally (brain skips idle check if path exists, movement continues walking). + +## Edge Cases + +- **NPC despawn during interaction**: Reset remaining partner's phase to `none` +- **Simultaneous targeting**: Process in entity ID order; first claim wins +- **Player entities**: Filter out entities with `PlayerControlled` component + +## Broadcast & Client Rendering + +### Server: add to EntityState + +```typescript +socialState?: { + phase: InteractionPhase; + partnerId: string | null; + outcome: 'positive' | 'negative' | null; +} +``` + +Only phase, partnerId, and outcome are serialized — no cooldowns or timers. + +### Client: floating emoji overlay + +- When `socialState.phase === 'emoting'` with non-null outcome, render HTML element above sprite +- Positive: happy emoji, Negative: angry emoji +- Float-up animation: translate Y upward ~20px over 2s, fade out in last 0.5s +- Clean up element when emoting phase ends or entity leaves view +- Track active elements to avoid duplicates +- Styling is placeholder (plain emoji text) — will be reworked to match game aesthetic later + +## Future Extensions (not in scope now) + +- NPC personality/likes/dislikes influencing outcome +- Persistent relationship tracking between NPC pairs +- NPCs seeking out liked/disliked NPCs for interactions +- Interaction outcomes affecting needs or mood