# Dialogue Presence — Characters Who Exist

> **Purpose**: Make the player feel like they're talking to a person, not reading text. Covers portrait expressiveness, speaker identity through text behavior, dialogue box staging, contextual systems, and choice presentation. 2D RPG focus.

---

## 1. The Minimum Viable Portrait

Most indie devs either skip portraits or over-scope them. The sweet spot from shipped games:

### Expression Set (6 core + 2 per character)

| Expression | When | Example |
|---|---|---|
| **Neutral** | Default, exposition | Calm face, slight attention |
| **Happy** | Positive reaction, humor | Smile, eyes slightly closed |
| **Angry** | Conflict, frustration | Furrowed brows, tight mouth |
| **Sad** | Loss, vulnerability | Downcast eyes, soft mouth |
| **Surprised** | Plot twists, discoveries | Wide eyes, open mouth |
| **Thinking** | Puzzles, hesitation | Eyes averted, hand on chin or lip bite |
| **+2 personality** | Unique to character | Smirk (trickster), blush (shy), feral grin (berserker) |

**Why 6+2**: Persona 5 uses ~8-12 expressions per party member, but the core readability comes from 6 universal emotions. The +2 personality expressions are what make the character feel unique. An NPC can survive with 3-4 (neutral, happy, angry, surprised).

**Piege**: More expressions =/= more expressiveness. Celeste uses ~6-8 per character and hits harder than games with 20. The secret is _when_ the expression changes, not how many you have.

### Portrait Animation (3 layers, not spritesheet)

Persona 5's portraits are NOT full spritesheets. They're a base image + overlay patches:

```
Base portrait (static)
  + Mouth layer (3 frames: closed, half-open, open)
  + Eye layer (2 frames: open, closed)
  = Talking + blinking with minimal art assets
```

**Timing values (from Persona 5 / visual novel standards)**:

| Animation | Timing | Notes |
|---|---|---|
| **Mouth cycle** | 60-80ms per frame (3 frames) | Synced to text reveal, not real lip-sync |
| **Blink** | 100ms close + 60ms reopen | Every 3-5 seconds, randomized. Double-blink: rare, adds life |
| **Idle breathing** | 8-12 frame loop, ~1.5s cycle | Subtle 1-2px vertical shift. Sub-pixel animation for smoothness |
| **Expression transition** | Instant swap, no tween | Tweening between expressions looks uncanny in 2D. Hard cuts feel snappy |

**Piege**: Don't animate breathing on pixel art portraits smaller than 64x64. The 1px shift becomes a distracting jitter. At that size, blink alone is enough.

### The "Active Speaker" Signal

When 2+ characters are in a scene, the non-speaking portrait needs to feel present but receded:

- **Active**: Full brightness, slight scale up (1.0 -> 1.05), mouth animates
- **Inactive**: Dimmed to ~60% brightness (`modulate = Color(0.6, 0.6, 0.7, 1.0)` — slightly blue-shifted for depth), mouth stops, scale 1.0
- **Transition**: 150ms tween on modulate + scale. Instant on expression change

Persona 5 does this with dynamic cropping and angle shifts. For 2D indie scope: brightness + scale is the 80/20.

---

## 2. Speaker Identity Through Text (No Font Swap Needed)

Undertale's killer insight: every character _sounds_ different even when you can't hear them. Sans speaks in lowercase Comic Sans, Papyrus in uppercase. But you don't need different fonts to achieve this.

### The Character Voice Profile

Each character gets a voice profile resource that drives text behavior:

| Parameter | What it controls | Example values |
|---|---|---|
| **text_speed** | Characters per second | Calm elder: 15 cps. Nervous kid: 40 cps. Default: 25 cps |
| **blip_pitch** | Base pitch of the per-character sound blip | Deep voice: 0.6. Normal: 1.0. Squeaky: 1.5 |
| **blip_sound** | The actual AudioStream | Synth tone, click, bell, growl — one note per character |
| **blip_variance** | Random pitch range per blip | Monotone robot: 0.0. Natural speech: 0.05-0.1. Excited: 0.15 |
| **pause_comma** | Pause duration on `,` | Default: 150ms |
| **pause_period** | Pause duration on `.` | Default: 300ms |
| **pause_ellipsis** | Pause duration on `...` | Default: 500ms. Mysterious: 800ms. Impatient: 200ms |
| **text_color** | BBCode color override | Optional. Use sparingly — 2-3 characters max per game |
| **case_style** | Text transformation | `none`, `upper` (SHOUTING TYPE), `lower` (chill sans energy) |

**The blip is the voice**. Animal Crossing maps vowels to pitched samples. Undertale uses a single looped note. The minimum viable approach: one sine/square wave sample, pitched per character. Pitch + timing = personality.

**Timing values from shipped games**:

- Undertale default text speed: ~30 characters/second
- Celeste dialogue blips: pitched synth, every character revealed
- Animal Crossing: vowel-mapped samples with 0.05-0.1 pitch variance
- Standard comfortable reading speed for typewriter: 20-30 cps (50-33ms per character)

**Piege**: Playing a blip on EVERY character (including spaces) sounds like a machine gun. Skip blips on spaces, and play at most every 2nd character for speeds above 35 cps.

### Text Effects as Character Traits (BBCode)

Godot 4's `RichTextLabel` supports built-in BBCode effects AND custom `RichTextEffect` extensions:

| Effect | BBCode | Character archetype |
|---|---|---|
| **Shake** | `[shake rate=20 level=5]` | Angry, scared, unstable |
| **Wave** | `[wave amp=30 freq=5]` | Dreamy, magical, ethereal |
| **Fade** | `[fade start=4 length=14]` | Whispering, fading memory |
| **Rainbow** | `[rainbow freq=1 sat=0.8 val=0.8]` | Chaotic, divine, glitch |
| **Custom pulse** | `[pulse freq=2]` (custom RichTextEffect) | Heartbeat, power surge |

**Pattern**: Reserve text effects for _moments_, not permanent character traits. A calm mage whose text is always wavy becomes wallpaper. A calm mage whose text starts shaking when they lose control — that's presence.

**Piege**: `[shake]` applied to entire dialogues becomes unreadable after 3 seconds. Use it on 1-3 key words, never full sentences. `"I said [shake rate=25 level=8]GET OUT[/shake]."` hits. `"[shake]I said get out right now and never come back[/shake]."` is just noise.

---

## 3. Dialogue Box Staging

### Position Communicates Tone

| Position | Feel | When to use |
|---|---|---|
| **Bottom bar** (full width) | Classic JRPG, comfortable, familiar | Standard conversations, exposition |
| **Bottom bar** (2/3 width, offset) | Slightly more intimate | Character-focused moments |
| **Center screen** | Dramatic, demanding attention | Boss encounters, critical revelations, system messages |
| **Floating bubble** (near speaker) | Casual, spatial, comic-book | Overworld chatter, NPC bark, comedic beats |
| **Top screen** | Narration, omniscient voice | Narrator, inner thoughts, tutorial text |

**The shipping pattern**: Pick ONE primary position (usually bottom bar). Use the others as punctuation. If every important line jumps to center screen, none of them feel important.

### Box Animation (Fast In, Patient Out)

```
APPEAR:  0ms delay → 80-120ms ease-out scale (0.9 → 1.0) + fade (0 → 1)
         Text starts typing IMMEDIATELY — don't wait for box animation to finish
DISMISS: Player input → 60ms fade out (faster than appear)
         Next box appears with 0ms gap — no dead frames between lines
```

**Why asymmetric**: The player is _waiting_ for the box to appear (give it presence), but _initiating_ the dismiss (respect their input, be instant). A 200ms dismiss animation on every line makes dialogue feel sluggish.

**Piege**: Box slide-in animations (from bottom, from left) look cool in isolation but add 200-400ms of dead time PER LINE. Over a 50-line conversation, that's 10-20 seconds of watching boxes slide. Scale + fade at 80ms is imperceptible but still has texture.

### Portrait Integration

```
+------------------------------------------+
| [PORTRAIT]  Name                         |
|            "Dialogue text here that      |
|             wraps to fill the available  |
|             space in the box."     [▼]   |
+------------------------------------------+
```

**Layout values**:

| Element | Value | Notes |
|---|---|---|
| Portrait size | 96x96 to 128x128 px | Smaller = more text space. Larger = more presence |
| Portrait margin | 8-12px from box edge | Feels embedded, not floating |
| Name position | Above text, same line as portrait top | Not inside a separate nameplate unless stylized |
| Text area | Right of portrait, full remaining width | Left-aligned. NEVER center-aligned for dialogue |
| Continue indicator | Bottom-right corner | Bouncing triangle, 0.5s loop. Or thematic: spinning card, pulsing diamond |
| Box opacity | 85-92% black/dark | 100% blocks the game. Below 80% hurts readability |
| Box padding | 12-16px all sides | Tight padding feels cramped. Generous padding feels premium |

**Piege**: Portrait on the LEFT for protagonists, portrait on the RIGHT for other speakers. This left/right positioning creates a visual "conversation" layout that the brain reads as a dialogue naturally. Persona and Fire Emblem both do this. If all portraits are on the same side, conversations feel like monologues.

---

## 4. The Disco Elysium Pattern: Internal Voices

Disco Elysium's internal voices are the most innovative dialogue presence system in recent RPGs. 24 skills function as independent characters in your head, each with their own personality and agenda.

### How It Translates to a 2D RPG

Instead of one inner monologue, split the protagonist's internal voice into 2-4 aspects:

| Voice | Personality | Interjection style |
|---|---|---|
| **Instinct** | Gut feeling, danger sense | Short, urgent. `"[shake rate=15 level=3]Don't trust her.[/shake]"` |
| **Reason** | Analytical, cold | Measured, with ellipses. `"The timeline doesn't add up..."` |
| **Heart** | Empathetic, emotional | Warm, uses metaphor. `"She looks like she hasn't slept in days."` |
| **Memory** | Fragments, déjà vu | Italic, fragmented. `"[wave amp=15 freq=2]...the smell of burning paper...[/wave]"` |

**The design**: Each voice has its own text color (subtle tint, not screaming neon), its own blip sound, and its own text speed. They interject based on stat checks or context — not every line, just when something in the conversation triggers them.

**The feel**: The player isn't a blank slate reading someone else's story. They have an inner world that reacts. That's presence.

**Piege**: More than 4 internal voices = overwhelming. Disco Elysium gets away with 24 because it's a 60-hour CRPG built entirely around this system. For a standard 2D RPG, 2-3 is the ceiling.

---

## 5. The Hades Pattern: Contextual Freshness

Hades makes every conversation feel fresh across 50+ hours. The architecture:

### Priority Queue System

```
CHARACTER DIALOGUE POOL
├─ Priority: HIGH (story-critical, gated)
│   └─ "First time meeting after boss X defeated"
│   └─ "React to player's death to boss Y"
├─ Priority: MEDIUM (relationship, progression)
│   └─ "Third gift given — unlock backstory"
│   └─ "After player uses weapon Z for first time"
├─ Priority: LOW (flavor, world-building)
│   └─ "Random comment about the weather"
│   └─ "Callback to previous conversation"
└─ RECYCLED (fallback, already heard)
    └─ 3-5 generic lines per character, rotated
```

### Selection Logic

1. Check all HIGH priority lines where requirements are met → pick randomly from those
2. If none, check MEDIUM → pick randomly
3. If none, check LOW → pick randomly
4. If all exhausted, pick from RECYCLED (mark as heard, rotate)

**Requirements can be**:
- Story flags: `boss_defeated["hydra"] == true`
- Relationship level: `affinity["npc_name"] >= 3`
- Run count: `total_runs >= 10`
- Item/weapon equipped: `current_weapon == "spear"`
- Compound: `boss_defeated["hydra"] AND affinity["meg"] >= 2`

### Why It Works

The player NEVER hears the same line twice until the pool is exhausted. And because requirements gate content, conversations naturally evolve with progression. The player feels like the NPC "knows" what's happening.

**Piege**: The randomness within a priority tier is essential. If you sort by priority AND by order, every player hears lines in the same sequence. The randomness within tiers creates the illusion that "my playthrough's conversations are unique."

**Scale for indie**: Hades has 21,000+ voiced lines. You don't need that. Even 5-8 unique lines per NPC per act + 3 recycled fallbacks creates the "this NPC has things to say" feeling. The architecture matters more than the volume.

---

## 6. Dialogue Staging: Mise en Scene

### The Dim + Spotlight Pattern

When a conversation starts in a 2D game, the world doesn't need to disappear. But it needs to recede:

```
CONVERSATION START:
1. Darken the game world (CanvasModulate tween to 40-50% brightness, 300ms)
2. Spotlight on speaking characters (PointLight2D or keep character brightness at 100%)
3. Dialogue box fades in (100ms, from bottom or scale)

CONVERSATION END:
1. Dialogue box fades out (60ms)
2. World brightness restores (200ms)
```

**Why**: The dim creates a "stage" for the conversation. The player's focus narrows to the characters. When it lifts, the world feels bigger again by contrast. Fire Emblem GBA does this with a simple black overlay at 50% opacity.

### Character Positioning

For side-view dialogue (visual novel style in a 2D RPG):

| Layout | Feel | Example |
|---|---|---|
| **Protagonist left, NPC right** | Standard conversation | Most JRPGs, Fire Emblem |
| **Both characters same side** | Alliance, agreement | "We need to work together" |
| **NPC alone, center** | Monologue, power | Villain speech, oracle prophecy |
| **No portraits, text only** | Narration, disembodied voice | Inner thoughts, environmental text |
| **Portrait off-screen (cropped)** | Eavesdropping, mystery | Voice from behind a door |

**Slide-in timing**: Characters slide from offscreen to position in 200-300ms with ease-out. This mirrors comic panel transitions. Never instant-appear — the motion signals "a new speaker has entered."

### Background Shifts

For key conversations, swap or overlay the background:

- **Memory/flashback**: Desaturate + slight zoom (tween to `modulate = Color(0.7, 0.7, 0.8)` + camera zoom 1.05)
- **Tension**: Background pulse (subtle `CanvasModulate` oscillation between 40% and 35% brightness, 2s cycle)
- **Revelation**: Brief screen flash (white overlay 0.0 → 0.3 → 0.0, 200ms total), then brighter ambient

---

## 7. Choice Presentation

### Standard Choices (Untimed)

```
+------------------------------------------+
|  "Will you help me find the artifact?"   |
|                                          |
|  > Accept the quest                      |
|    Refuse politely                       |
|    Ask for more information              |
+------------------------------------------+
```

**Values**:
- Choices appear 200ms after the question text finishes typing (gives the player a beat to read)
- Selection indicator: gentle bounce (2px vertical, 0.4s cycle) or pulse
- Choice text does NOT use typewriter effect — appears instantly. The player needs to scan options, not wait
- Max 4 choices visible. More = decision paralysis. If you need 5+, restructure the conversation

### Timed Choices (Telltale Pattern)

A shrinking bar or fading element that forces a decision:

```
Timer bar: 5-8 seconds for standard choices
           3-4 seconds for pressure/combat dialogue
           10-12 seconds for life-changing decisions

On timeout: Select a DEFAULT option (usually silence or inaction)
            NEVER select a random option — the default must feel intentional
```

**The Telltale lesson**: The timer isn't about punishing slow readers. It's about making the player commit before they can overthink. 5 seconds feels urgent. 8 seconds feels pressured but fair.

**Piege**: NEVER use timed choices on the first dialogue encounter in your game. The player is still learning the UI. Reserve timed choices for moments of narrative tension where the pressure is the point.

### Consequence Signals

The "Clementine will remember that" is iconic but controversial. Players report it eliminates surprise — you immediately know which choices "matter."

**Better patterns**:

| Pattern | How | Feel |
|---|---|---|
| **Silent consequence** | No feedback at all | Player discovers impact later. Maximum surprise. Requires trust in writing |
| **Subtle reaction** | NPC expression changes, brief pause before next line | "Something shifted." Doesn't confirm WHAT changed |
| **Delayed echo** | 2-3 conversations later, NPC references the choice | "I'm glad you said that back then." Retrospective weight |
| **Immediate branch** | The next 2-3 lines change visibly | Player sees impact NOW. Most satisfying for short conversations |

**The shipping pattern**: Use immediate branch for gameplay-affecting choices (quest direction, ally recruitment). Use delayed echo for character-relationship choices. Use silent consequence for moral ambiguity where you WANT the player to wonder.

### Choice Reveal Animation

Don't dump all choices at once. Stagger them:

```
Choice 1: appears at 0ms
Choice 2: appears at 80ms
Choice 3: appears at 160ms
(Cursor auto-highlights Choice 1)
```

Each choice slides in from the right (12px travel, ease-out). The stagger creates a "cascade" feel that's more alive than a static list appearing in one frame. Total time: ~250ms for 3 choices. Fast enough to not feel slow, slow enough to register as animated.

---

## 8. Sound Design for Presence

### Per-Character Voice Blips

The cheapest, highest-impact dialogue presence technique:

1. Record or synthesize ONE short sample per character (50-100ms)
2. Play it on each revealed character, pitched slightly random (base_pitch +/- variance)
3. Skip on spaces. Skip on punctuation (punctuation = silence = natural pause)
4. At text speeds above 35 cps, play every 2nd character to avoid machine-gun effect

**Sample types by archetype**:
- **Stoic warrior**: Low sine wave or soft drum hit, pitch 0.7, variance 0.02
- **Cheerful child**: High bell or chime, pitch 1.4, variance 0.1
- **Mysterious figure**: Reversed piano note or breath sample, pitch 0.9, variance 0.05
- **Robot/construct**: Square wave or bit-crush, pitch 1.0, variance 0.0 (monotone = mechanical)
- **Ancient being**: Choir pad snippet or deep hum, pitch 0.5, variance 0.03

### Dialogue Box Sound Effects

| Event | Sound | Timing |
|---|---|---|
| Box appear | Soft pop or paper unfold | On box tween start |
| Box dismiss | Quick whoosh or click | On dismiss input |
| Choice appear | Subtle tick per choice | One per staggered choice |
| Choice select | Confirm chime | On input |
| Choice hover | Tiny click or tock | On cursor move |

Keep these at -12 to -18 dB relative to music. They should feel like texture, not events.

---

## Pieges (Design, Not API)

### The "wall of text" death
If a single dialogue box contains more than 3 lines of text, split it. Two shorter boxes feel conversational. One long box feels like reading a manual. Hades and Undertale rarely exceed 2 lines per box.

### Expression changes that happen off-screen
If a character's portrait changes expression while the player is reading text, they'll miss it. Change expressions at the START of a new dialogue box, not mid-sentence. The eye returns to the portrait at box transitions.

### Voice blips that don't match the portrait
If the portrait looks like a gruff warrior but the blip sounds like a bell, the disconnect breaks presence instantly. Prototype blip + portrait together, never separately.

### "Choose your response" syndrome
If every NPC conversation has a choice, choices become wallpaper. Reserve choices for moments where the player's opinion MATTERS. 70% of conversations should be linear with good portrait work and voice. 30% should have choices. That ratio keeps choices feeling significant.

### The narrator as a character
If your game has narration (describing environments, inner thoughts), give the narrator a distinct visual treatment — different box style, different text color, no portrait, different text speed. The narrator is a character too. When narration looks identical to dialogue, the player can't tell who's "talking."

---

## Stack Final

```
1. Portraits        → 6+2 expressions, overlay layers (mouth 3f, eyes 2f),
                       active/inactive dim, instant expression swap
2. Voice profiles   → text_speed, blip_pitch, blip_variance, pause timings,
                       optional text_color / case_style per character
3. Text effects     → BBCode sparingly: shake for anger, wave for magic,
                       fade for whispers. On key WORDS, not full lines
4. Box staging      → Bottom bar primary. 80-120ms appear, 60ms dismiss.
                       Dim world on conversation start (300ms)
5. Internal voices  → 2-3 aspects max, each with own color/blip/speed.
                       Stat-gated interjections, not every line
6. Priority queues  → HIGH/MEDIUM/LOW/RECYCLED pools per NPC.
                       Random within tier. 5-8 unique + 3 fallback = alive
7. Choices          → Staggered reveal (80ms per). Max 4 visible.
                       Timed only for tension. Consequence via delayed echo
8. Sound            → One blip sample per character. Skip spaces.
                       Box SFX at -15dB. Match blip to portrait vibe
```

See also: `state_machines.md` for dialogue state management, `.claude/skills/godot-composition.md` for box layout implementation, `.claude/skills/godot-audio.md` for blip AudioStreamPlayer setup.
