refactor(content): rename mood vocabulary to match voice guide (D-035 Sprint 14 amendment)

Schema and Rust Mood enum renamed for author-friendly vocabulary:
fond→warm, comfortable→content, worried→anxious, concerned→frustrated.
Dropped: analytical (merged into focused), conflicted (modeled as
suspicious+warm collision). Added: hostile. Final 8 moods: anxious,
frustrated, content, suspicious, warm, hostile, relieved, focused.
Neutral = untagged. Resolves Gestalt's blocking issue on #121.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 19:00:07 +01:00
co-authored by Claude Opus 4.6
parent df4d7b4666
commit 91eb11da16
10 changed files with 66 additions and 134 deletions
+13 -15
View File
@@ -2,7 +2,7 @@
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "dialogue-line.schema.json",
"title": "Dialogue Line — canonical D-035 tag taxonomy",
"description": "Canonical single-line schema for dialogue and monologue pools. Implements the converged tag taxonomy from D-035 (6 structural + 3 selection + 2 authoring-only tags). Monologue-specific additions (character, trigger, prerequisite) are defined in $defs/monologue_extension.\n\nRust sync note: server/src/content/line_pool.rs is missing two Sprint 8 amendments — Situation::Greeting (14th situation) and Mood::Focused (9th mood). Content authored with these values will be silently skipped by the Rust parser until the server branch catches up. Filed as a server team sync issue.",
"description": "Canonical single-line schema for dialogue and monologue pools. Implements the converged tag taxonomy from D-035 (6 structural + 3 selection + 2 authoring-only tags). Monologue-specific additions (character, trigger, prerequisite) are defined in $defs/monologue_extension. Mood vocabulary renamed Sprint 14 to match voice guide (anxious/frustrated/content/suspicious/warm/hostile/relieved/focused).",
"type": "object",
"required": ["id", "text", "role", "access", "trust", "situation"],
"additionalProperties": false,
@@ -86,19 +86,18 @@
"items": {
"type": "string",
"enum": [
"fond",
"comfortable",
"worried",
"anxious",
"frustrated",
"content",
"suspicious",
"analytical",
"conflicted",
"concerned",
"warm",
"hostile",
"relieved",
"focused"
]
},
"uniqueItems": true,
"description": "D-028 Layer 4: mood tags for weighted selection. 9 v0.1 values (8 original + focused added Sprint 8 for Kael dialogue). Optional — defaults to empty if omitted. NOTE: 'focused' is not yet in server/src/content/line_pool.rs — lines using it will be skipped until Rust is updated."
"description": "D-028 Layer 4: mood tags for weighted selection. 8 v0.1 values. Neutral mood = omit tag (untagged lines are always eligible). Renamed Sprint 14 to match voice guide vocabulary."
},
"tags": {
"type": "array",
@@ -274,17 +273,16 @@
"mood_enum": {
"type": "string",
"enum": [
"fond",
"comfortable",
"worried",
"anxious",
"frustrated",
"content",
"suspicious",
"analytical",
"conflicted",
"concerned",
"warm",
"hostile",
"relieved",
"focused"
],
"description": "9 v0.1 mood values (D-035 + Sprint 8 amendment: focused added)."
"description": "8 v0.1 mood values. Renamed Sprint 14 to match voice guide vocabulary. Neutral = untagged."
}
}
}
+3 -4
View File
@@ -90,13 +90,12 @@
"items": {
"type": "string",
"enum": [
"fond", "comfortable", "worried", "suspicious",
"analytical", "conflicted", "concerned", "relieved",
"focused"
"anxious", "frustrated", "content", "suspicious",
"warm", "hostile", "relieved", "focused"
]
},
"uniqueItems": true,
"description": "Mood tags for selection weighting (D-035: list<enum>, v0.1 8 moods + focused added Sprint 8)"
"description": "Mood tags for selection weighting (D-035: list<enum>). 8 v0.1 values, renamed Sprint 14 to match voice guide vocabulary. Neutral = untagged."
},
"tags": {
"type": "array",
+6 -7
View File
@@ -153,19 +153,18 @@
"items": {
"type": "string",
"enum": [
"fond",
"comfortable",
"worried",
"anxious",
"frustrated",
"content",
"suspicious",
"analytical",
"conflicted",
"concerned",
"warm",
"hostile",
"relieved",
"focused"
]
},
"uniqueItems": true,
"description": "D-035 selection tag: mood tags for weighted selection. 9 v0.1 values (8 original + focused Sprint 8). Optional. NOTE: 'focused' not yet in server/src/content/line_pool.rs Mood enum."
"description": "D-035 selection tag: mood tags for weighted selection. 8 v0.1 values, renamed Sprint 14 to match voice guide vocabulary. Neutral = untagged."
},
"priority": {
"type": "integer",
+1
View File
@@ -83,6 +83,7 @@ How narrative, NPCs, and world content are created: content tiers, NPC generatio
- **Raised by:** Gestalt (schema design, Round 1/2), Mellanie (authoring validation, Round 1/2). Converged across both agents in Round 2.
- **Dissent:** None. Minor consolidations: Mellanie's 8 moods mapped to Gestalt's 8 (different names, same concepts). Mellanie's `crime` topic deliberately excluded (NPCs think of it as `cargo` or `money`).
- **Amendment (Sprint 8):** `focused` added as 9th mood (used in Kael dialogue at The Terminal and maintenance corridors). `greeting` added as 14th situation (used in PC dialogue pools for initial contact lines). Schema updated to match.
- **Amendment (Sprint 14):** Mood vocabulary renamed to match voice guide (monologue-voice-guide.md). Old → new: `fond``warm`, `comfortable``content`, `worried``anxious`, `concerned``frustrated`. Dropped: `analytical` (merged into `focused`), `conflicted` (modeled as `suspicious`+`warm` collision). Added: `hostile`. Final 8 moods: `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`. Neutral = untagged.
### D-036: Sova Transit District / Krenn System as v0.1 setting
- **Date:** 2026-02-11
+5 -65
View File
@@ -461,65 +461,9 @@ For Mellanie — a vocabulary guide per background to use when writing example l
## Gestalt — Mood → Delivery Validation (2026-02-20)
**Status: BLOCKING ISSUE — mood vocabulary mismatch between this document and the schema.**
**Status: RESOLVED — schema renamed Sprint 14 to match voice guide vocabulary.**
---
### The problem
Chapter 4 uses mood names that do **not match** the D-035 schema enum values or the Rust `Mood` type in `server/src/content/line_pool.rs`. Content authors following this guide will write `mood: warm` in their YAML files, but the schema only accepts `mood: fond`. The selection pipeline will silently skip lines with unrecognized mood tags.
**Voice guide names:** `neutral`, `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `focused`
**Schema/Rust enum names:** `fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved`, `focused`
---
### Mapping table (interim — for use until the vocabulary is reconciled)
Authors MUST use schema names in YAML files. Use this table to translate from Chapter 4 concepts:
| Chapter 4 concept | Schema tag to use | Fit quality | Notes |
|-------------------|------------------|-------------|-------|
| `neutral` | *(no tag — omit mood field)* | ✅ Good | Neutral = baseline = untagged. Selection system defaults to eligible untagged lines. |
| `anxious` | `worried` | ✅ Good | Schema description: "Anxious about something specific." Exact match. |
| `frustrated` | `concerned` | ⚠️ Imperfect | Schema description: "Worried about someone else's wellbeing." Close enough for v0.1 authoring, but semantically different. |
| `content` | `comfortable` | ✅ Good | Schema description: "At ease, relaxed." Good match. |
| `suspicious` | `suspicious` | ✅ Exact | Same word. |
| `warm` | `fond` | ✅ Good | Schema description: "Warm, affectionate." Exact match. |
| `hostile` | *(no clean match)* | ❌ GAP | `hostile` is not in the current schema enum. `conflicted` (torn between impulses) is the closest but doesn't capture adversarial register. See resolution below. |
| `focused` | `focused` | ✅ Exact | Sprint 8 addition. Same word. |
**Uncovered schema values** (not addressed in Chapter 4):
- `analytical` — described as "Thoughtful, assessing. Processing information." This is a mood the voice guide doesn't model explicitly. It surfaces in the detective's NPC interaction monologue during passive evidence-gathering. Mellanie: treat as a subdued version of `suspicious` without a specific object — the character is processing, not alarmed.
- `relieved` — described as "Tension has eased." The voice guide doesn't model this but it's mechanically useful post-confrontation or after a drop completes cleanly. Author a handful of `relieved` atmosphere lines per character per location; use the opposite of `anxious` as the authoring direction.
- `conflicted` — described as "Torn between competing impulses." The voice guide doesn't model this explicitly but it IS the dominant emotional state during the FRIEND arc's middle phase. FRIEND arc lines where the character is caught between loyalty and evidence should carry `conflicted` (not `suspicious` alone).
---
### Resolution proposal
**Recommended resolution (Gestalt proposes — Lead to decide):**
The voice guide's mood vocabulary is more intuitive for authors. Recommend updating the schema enum and Rust `Mood` type to match Chapter 4 names:
| Current schema name | Proposed new name | Rationale |
|--------------------|-------------------|-----------|
| `fond` | `warm` | Chapter 4 name is clearer for authors |
| `comfortable` | `content` | Chapter 4 name is clearer |
| `worried` | `anxious` | Chapter 4 name is more precise |
| `analytical` | keep or → `focused` | Overlaps with `focused`; consider merging |
| `conflicted` | keep as `conflicted` | Has specific meaning, Chapter 4 doesn't cover it |
| `concerned` | `frustrated` | Schema `concerned` = "worried about others" ≠ author intent; `frustrated` is more useful |
| `relieved` | keep as `relieved` | Useful, add coverage in voice guide |
| `suspicious` | `suspicious` | No change |
| `focused` | `focused` | No change |
| *(missing)* | add `hostile` | Required for adversarial register modeled in Chapter 4 |
This would require: schema update, Rust enum update (5 renames + 1 addition), global enums YAML update, and any existing content using the old names updated. Content volume is low enough that this is feasible now before large-scale authoring begins.
**Alternative (minimal change):** Update only the voice guide to use the current schema names. Authors use less-intuitive terms but no code changes needed. Lower cost but worse authoring experience.
---
Schema and Rust `Mood` enum updated to use Chapter 4 names directly: `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`. Neutral mood = omit tag (untagged lines are always eligible). `analytical` and `conflicted` dropped (merged into `focused` and modeled as `suspicious`+`warm` collision respectively). `hostile` added. Authors can now use voice guide mood names directly in YAML files.
### Mood as selection weight — mechanical coherence
@@ -529,12 +473,8 @@ The selection model in Section 4.3 is mechanically coherent with how the pipelin
- Untagged lines remain eligible — correct
- Mood-tagged lines are preferred when mood matches — correct
No issues with the model. The only mechanical problem is the vocabulary mismatch documented above.
**Trait modifier system (Chapters 2-3):** Mechanically coherent. Traits are authoring modifiers, not runtime filters. No engine-side trait tracking needed for monologue. The authoring guide is sufficient.
**Trait modifier system (Chapters 2-3):** Mechanically coherent. Traits are authoring modifiers, not runtime filters. No engine-side trait tracking needed for monologue (unlike NPC dialogue, where trait modifiers shape the transformation guide). The authoring guide is sufficient. ✅
**FRIEND arc special case (Section 4.3):** The note about prioritizing mood-tagging for crisis beats is correct. The collision of `suspicious` + `warm` as the trust-contamination zone is the right call.
**FRIEND arc special case (Section 4.3):** The note about prioritizing mood-tagging for crisis beats is correct. The collision of `suspicious` (or `conflicted` in schema) + `warm` as the trust-contamination zone is the right call — that tension is what makes those lines land. ✅
---
**Mood → delivery validation: CONDITIONAL PASS.** The delivery model is mechanically coherent. Blocking issue: vocabulary mismatch with schema must be resolved before Mellanie authors any mood-tagged lines. Lead decision required on resolution path (schema rename vs. voice guide rename). All other mood system design is approved.
**Mood → delivery validation: PASS.** The delivery model is mechanically coherent. Vocabulary mismatch resolved. All mood system design approved.
+1 -1
View File
@@ -1092,7 +1092,7 @@ dependencies = [
[[package]]
name = "settled-reach-server"
version = "0.1.12"
version = "0.1.13"
dependencies = [
"bevy_app",
"bevy_ecs",
+5 -6
View File
@@ -630,13 +630,12 @@ fn topic_str(t: &Topic) -> &'static str {
fn mood_str(m: &Mood) -> &'static str {
match m {
Mood::Fond => "fond",
Mood::Comfortable => "comfortable",
Mood::Worried => "worried",
Mood::Anxious => "anxious",
Mood::Frustrated => "frustrated",
Mood::Content => "content",
Mood::Suspicious => "suspicious",
Mood::Analytical => "analytical",
Mood::Conflicted => "conflicted",
Mood::Concerned => "concerned",
Mood::Warm => "warm",
Mood::Hostile => "hostile",
Mood::Relieved => "relieved",
Mood::Focused => "focused",
}
+18 -22
View File
@@ -164,19 +164,17 @@ impl FromStr for Topic {
/// D-028 Layer 4: Mood tag — influences weighted selection.
///
/// 9 v0.1 values: 8 original + Focused added Sprint 8 (D-035 amendment)
/// for Kael dialogue at The Terminal and maintenance corridors.
/// 8 v0.1 values aligned to voice guide vocabulary (Sprint 14 rename).
/// Neutral mood is represented by omitting the mood tag (untagged = baseline).
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum Mood {
Fond,
Comfortable,
Worried,
Anxious,
Frustrated,
Content,
Suspicious,
Analytical,
Conflicted,
Concerned,
Warm,
Hostile,
Relieved,
/// Added Sprint 8 (D-035 amendment): Kael dialogue at Terminal/corridors.
Focused,
}
@@ -184,13 +182,12 @@ impl FromStr for Mood {
type Err = ParseEnumError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s {
"fond" => Ok(Self::Fond),
"comfortable" => Ok(Self::Comfortable),
"worried" => Ok(Self::Worried),
"anxious" => Ok(Self::Anxious),
"frustrated" => Ok(Self::Frustrated),
"content" => Ok(Self::Content),
"suspicious" => Ok(Self::Suspicious),
"analytical" => Ok(Self::Analytical),
"conflicted" => Ok(Self::Conflicted),
"concerned" => Ok(Self::Concerned),
"warm" => Ok(Self::Warm),
"hostile" => Ok(Self::Hostile),
"relieved" => Ok(Self::Relieved),
"focused" => Ok(Self::Focused),
_ => Err(ParseEnumError {
@@ -709,15 +706,14 @@ mod tests {
#[test]
fn mood_parse_all_values() {
let values = [
"fond",
"comfortable",
"worried",
"anxious",
"frustrated",
"content",
"suspicious",
"analytical",
"conflicted",
"concerned",
"warm",
"hostile",
"relieved",
"focused", // Sprint 8 amendment (D-035)
"focused",
];
for v in values {
assert!(v.parse::<Mood>().is_ok(), "Failed to parse mood: {}", v);
+12 -12
View File
@@ -68,7 +68,7 @@ pub struct CurrentMood(pub Mood);
impl Default for CurrentMood {
fn default() -> Self {
Self(Mood::Comfortable)
Self(Mood::Content)
}
}
@@ -891,14 +891,14 @@ mod tests {
#[test]
fn score_mood_match_adds_three() {
let line = make_line("moody", &[], &[Mood::Worried]);
assert_eq!(score_line(&line, Some(Mood::Worried), &[]), 4); // 1 base + 3 mood
let line = make_line("moody", &[], &[Mood::Anxious]);
assert_eq!(score_line(&line, Some(Mood::Anxious), &[]), 4); // 1 base + 3 mood
}
#[test]
fn score_mood_mismatch_stays_base() {
let line = make_line("moody", &[], &[Mood::Worried]);
assert_eq!(score_line(&line, Some(Mood::Fond), &[]), 1);
let line = make_line("moody", &[], &[Mood::Anxious]);
assert_eq!(score_line(&line, Some(Mood::Warm), &[]), 1);
}
#[test]
@@ -987,7 +987,7 @@ mod tests {
fn select_deterministic_with_same_seed() {
let line_a = make_line("a", &[], &[]);
let line_b = make_line("b", &[Topic::Cargo], &[]);
let line_c = make_line("c", &[], &[Mood::Worried]);
let line_c = make_line("c", &[], &[Mood::Anxious]);
let candidates = vec![&line_a, &line_b, &line_c];
let cooldown = DialogueCooldownTracker::default();
@@ -1003,7 +1003,7 @@ mod tests {
fn select_favors_higher_scored_lines() {
// Line with matching mood gets +3, so should be selected more often
let neutral = make_line("neutral", &[], &[]);
let matched = make_line("matched", &[], &[Mood::Worried]);
let matched = make_line("matched", &[], &[Mood::Anxious]);
let candidates = vec![&neutral, &matched];
let cooldown = DialogueCooldownTracker::default();
@@ -1012,7 +1012,7 @@ mod tests {
let mut rng = rand_chacha::ChaCha20Rng::seed_from_u64(seed);
if let Some(line) = select_dialogue_line(
&candidates,
Some(Mood::Worried),
Some(Mood::Anxious),
&[],
&cooldown,
0,
@@ -1077,7 +1077,7 @@ mod tests {
trust: TrustTier::Surface,
situation: vec![Situation::NightShift],
topic: vec![Topic::Routine],
mood: vec![Mood::Comfortable],
mood: vec![Mood::Content],
tags: vec![],
knowledge_grant: None,
},
@@ -1089,7 +1089,7 @@ mod tests {
trust: TrustTier::Real,
situation: vec![Situation::Investigation],
topic: vec![Topic::Cargo, Topic::Investigation],
mood: vec![Mood::Conflicted],
mood: vec![Mood::Suspicious],
tags: vec![],
knowledge_grant: None,
},
@@ -1122,7 +1122,7 @@ mod tests {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
CurrentMood(Mood::Comfortable),
CurrentMood(Mood::Content),
))
.id();
world.resource_mut::<EntityRegistry>().register(npc);
@@ -1414,7 +1414,7 @@ mod tests {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
CurrentMood(Mood::Comfortable),
CurrentMood(Mood::Content),
))
.id();
world.resource_mut::<EntityRegistry>().register(npc);
+2 -2
View File
@@ -392,7 +392,7 @@ fn different_seed_produces_different_replay() {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
CurrentMood(Mood::Comfortable),
CurrentMood(Mood::Content),
));
}
@@ -408,7 +408,7 @@ fn different_seed_produces_different_replay() {
situation: vec![Situation::Routine],
topic: vec![],
mood: if i % 2 == 0 {
vec![Mood::Comfortable]
vec![Mood::Content]
} else {
vec![]
},