diff --git a/content/_schema/dialogue-line.schema.json b/content/_schema/dialogue-line.schema.json index e895dac1f..4c509f627 100644 --- a/content/_schema/dialogue-line.schema.json +++ b/content/_schema/dialogue-line.schema.json @@ -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." } } } diff --git a/content/_schema/dialogue-pool.schema.json b/content/_schema/dialogue-pool.schema.json index d2bb25d26..6b4521ad0 100644 --- a/content/_schema/dialogue-pool.schema.json +++ b/content/_schema/dialogue-pool.schema.json @@ -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, v0.1 8 moods + focused added Sprint 8)" + "description": "Mood tags for selection weighting (D-035: list). 8 v0.1 values, renamed Sprint 14 to match voice guide vocabulary. Neutral = untagged." }, "tags": { "type": "array", diff --git a/content/_schema/monologue-pool.schema.json b/content/_schema/monologue-pool.schema.json index d633e344b..dc74b263c 100644 --- a/content/_schema/monologue-pool.schema.json +++ b/content/_schema/monologue-pool.schema.json @@ -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", diff --git a/decisions/content.md b/decisions/content.md index 3c06c56f9..3e9a046a5 100644 --- a/decisions/content.md +++ b/decisions/content.md @@ -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 diff --git a/docs/design/monologue-voice-guide.md b/docs/design/monologue-voice-guide.md index afd295d3e..80ecfaff6 100644 --- a/docs/design/monologue-voice-guide.md +++ b/docs/design/monologue-voice-guide.md @@ -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. diff --git a/server/Cargo.lock b/server/Cargo.lock index ea1dcf2fe..5b4567c66 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -1092,7 +1092,7 @@ dependencies = [ [[package]] name = "settled-reach-server" -version = "0.1.12" +version = "0.1.13" dependencies = [ "bevy_app", "bevy_ecs", diff --git a/server/src/bin/line_preview.rs b/server/src/bin/line_preview.rs index da3f66d4d..e027cd5d2 100644 --- a/server/src/bin/line_preview.rs +++ b/server/src/bin/line_preview.rs @@ -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", } diff --git a/server/src/content/line_pool.rs b/server/src/content/line_pool.rs index 41e1ac722..76e1172f4 100644 --- a/server/src/content/line_pool.rs +++ b/server/src/content/line_pool.rs @@ -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 { 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::().is_ok(), "Failed to parse mood: {}", v); diff --git a/server/src/simulation/dialogue.rs b/server/src/simulation/dialogue.rs index ff3603fe1..46ced91c3 100644 --- a/server/src/simulation/dialogue.rs +++ b/server/src/simulation/dialogue.rs @@ -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::().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::().register(npc); diff --git a/server/tests/determinism.rs b/server/tests/determinism.rs index 06d17090d..43b70a864 100644 --- a/server/tests/determinism.rs +++ b/server/tests/determinism.rs @@ -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![] },