Writing Branching Dialogue
Given a request like "write a dialogue where the blacksmith warns the player about the bandits," don't jump straight to branches. Work through constraints → character → purpose → linear draft → branches, in that order. Output is always YAML conforming to schema.md, with portraits in character.outfit.expression namespace form (e.g. eva.default_outfit.surprised).
Minimal skeleton to start from:
YAMLconversation_id: blacksmith_bandit_warning participants: - id: torvin default_portrait: torvin.apron.neutral nodes: - id: start speaker: torvin portrait: torvin.apron.worried line: "You didn't come from the east road, did you?" responses: - text: "I did. Why?" goto: warn_bandits - text: "No, why?" goto: ask_curious
Progress checklist for each dialogue task:
- Constraints: Confirm max line length, schema file, target language, voice-over limits (if any)
- Characters: Establish personality, current mood, circumstance, relationship to player/other speakers
- Narrative purpose: What story beat does this serve? Target conversation length (short beat, medium scene, long branching quest hub)?
- Linear first pass: Write full linear dialogue with generic/placeholder player responses. Prioritize character voice and pacing over branch completeness
- Iterative branching: With the user, add branches at response points, one round at a time, expanding only where prompted
1. Establish Constraints
Ask or infer:
- Max characters/words per line (common: 60-120 chars for subtitles)
- Schema file to follow (default to
schema.mdif present in project) - Language and tone register (formal, colloquial, era-appropriate)
2. Establish Character(s)
For each speaker, note:
- Personality traits relevant to this scene
- Current mood (distinct from baseline personality)
- Circumstance (what just happened, what they want right now)
- Relationship dynamic with other participants
3. Learn Narrative Purpose
- What story beat is this serving (reveal, warning, bonding, betrayal, comic relief, transaction)?
- Intended length: quick barks (1-3 lines), short exchange (5-10 lines), full branching scene (10+ lines with multiple response layers)
- Where does this sit in the larger narrative (does it gate content, reveal lore, trigger a quest)?
4. Write Linear First Pass
- Full dialogue text, in character, hitting the story beat
- Player responses are placeholders — generic but plausible (e.g. "Tell me more," "I don't trust you," "Goodbye")
- Focus effort on NPC lines: voice, mood, pacing
- Confirm this draft with the user before branching
5. Iterative Branch Passes
- Add one layer of real branches at a time, at explicit response points
- Each branch should meaningfully diverge (different tone, information, consequence) — not just reword the same outcome
- Loop back to shared nodes where sensible to avoid combinatorial explosion, unless the user wants full divergence
- Re-confirm portrait/emotion changes per branch (a hostile response may need
angryportrait, notneutral)
Example 1:
Input: "Write a short dialogue where a guard stops the player at a gate, suspicious of stolen goods. Keep it to 2-3 exchanges, schema.md, portraits like guard.uniform.suspicious."
Output:
YAMLconversation_id: gate_guard_search participants: - id: guard default_portrait: guard.uniform.neutral nodes: - id: start speaker: guard portrait: guard.uniform.suspicious line: "Hold it. That crate wasn't on your manifest last week." responses: - text: "Must be a mistake." goto: guard_pushback - text: "It's none of your business." goto: guard_escalate - id: guard_pushback speaker: guard portrait: guard.uniform.skeptical line: "Funny kind of mistake. Open it." responses: - text: "[Placeholder] Comply" goto: end_search - text: "[Placeholder] Refuse" goto: end_standoff
Example 2: Input: "The blacksmith example — user asks for a branch where the player lies about not knowing the bandits."
Output: Add a new response at warn_bandits leading to a torvin.apron.suspicious portrait line calling out the lie, rather than reusing the neutral acceptance branch — because a lie should visibly cost the player something (trust, information, tone shift).
- Always output valid YAML matching the project's
schema.md— don't invent fields not in the schema - Namespace portraits consistently:
character.outfit.expression, lowercase, underscores for multi-word segments - Keep placeholder responses honest about being placeholders (e.g. prefix with
[Placeholder]) until branching pass replaces them - Let mood/circumstance override generic personality — a normally cheerful character who just lost something should not sound cheerful
- Converge branches back to shared nodes when the narrative outcome is equivalent, to keep the tree maintainable
- Surface pacing: don't let branches balloon past the agreed conversation length without flagging it to the user
- Don't write full branch trees before the user confirms the linear first pass — purpose and pacing must be locked first
- Don't let player response branches all resolve identically; if a branch doesn't change dialogue or state, it shouldn't exist
- Don't ignore character mood in favor of generic "on-brand" voice — mood is scene-specific, personality is not
- Don't exceed the agreed max line length; trim, don't wrap in the same node
- Don't invent portrait names outside the established namespace convention or introduce expressions the project doesn't use
- Don't treat this as a one-shot generation task — branching dialogue is inherently iterative with the user