feat(schema): implement D-035 converged tag taxonomy as canonical JSON Schema (#168)
- Add content/_schema/dialogue-line.schema.json — canonical standalone line schema with all 6 structural + 3 selection + 2 authoring-only tags and $defs for monologue extension (character, trigger, prerequisite) - Update dialogue-pool.schema.json: add dual_lens and notes authoring-only tags, add additionalProperties: false to knowledge_grant - Update monologue-pool.schema.json: add all 6 structural tags as required fields for schema compliance (role=player_character, access=[public], trust=surface are constrained constants); add topic, mood, dual_lens, notes - Enumerate all 14 v0.1 situations and 9 v0.1 topics as allowed enum values per D-035 - Fix Rust sync gap: add Situation::Greeting and Mood::Focused to line_pool.rs (D-035 Sprint 8 amendments — were in schema but missing from Rust enums, causing content with these values to be silently dropped at runtime) - Update line_preview.rs situation_str, mood_str match arms and help text for new variants; all 25 line_pool tests pass Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,290 @@
|
||||
{
|
||||
"$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.",
|
||||
"type": "object",
|
||||
"required": ["id", "text", "role", "access", "trust", "situation"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9-]*_(d|m)_[0-9]{3}$",
|
||||
"description": "Stable machine-parseable line ID: {template}_{d|m}_{###}. d = dialogue, m = monologue."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "The authored line text."
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9-]*$",
|
||||
"description": "Template-defined role slug (e.g. dock-worker, bar-owner, player_character). Not NPC name — NPC assignment is runtime."
|
||||
},
|
||||
"access": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": ["public", "insider", "authority", "peer", "hostile"]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "D-028 Layer 1: access tiers this line is eligible for. List — a line can be eligible for multiple tiers. Hard filter."
|
||||
},
|
||||
"trust": {
|
||||
"type": "string",
|
||||
"enum": ["surface", "real", "secret"],
|
||||
"description": "D-028 Layer 3: minimum trust tier required. Hard filter. Ordering: surface < real < secret."
|
||||
},
|
||||
"situation": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"arrival",
|
||||
"shift_start",
|
||||
"shift_end",
|
||||
"shift_transition",
|
||||
"bar_evening",
|
||||
"night_shift",
|
||||
"investigation",
|
||||
"confrontation",
|
||||
"social",
|
||||
"alone",
|
||||
"emergency",
|
||||
"routine",
|
||||
"observation",
|
||||
"greeting"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "D-028 Layer 2: situations in which this line can fire. 14 v0.1 values (13 original + greeting added Sprint 8 for PC dialogue initial contact lines). NOTE: 'greeting' is not yet in server/src/content/line_pool.rs — lines using it will be skipped until Rust is updated."
|
||||
},
|
||||
"topic": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"colleague",
|
||||
"routine",
|
||||
"cargo",
|
||||
"money",
|
||||
"trust",
|
||||
"danger",
|
||||
"institution",
|
||||
"personal",
|
||||
"investigation"
|
||||
]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "D-028 Layer 4: topic tags for weighted selection. 9 v0.1 values. Optional — defaults to empty if omitted. Note: 'crime' deliberately excluded; NPCs think of it as 'cargo' or 'money'."
|
||||
},
|
||||
"mood": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fond",
|
||||
"comfortable",
|
||||
"worried",
|
||||
"suspicious",
|
||||
"analytical",
|
||||
"conflicted",
|
||||
"concerned",
|
||||
"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."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"description": "Freeform escape hatch for author intent not covered by the structured taxonomy. Not consumed by the engine selection pipeline."
|
||||
},
|
||||
"knowledge_grant": {
|
||||
"type": "object",
|
||||
"description": "Knowledge the player gains from hearing this line. Feeds into the knowledge graph (D-041).",
|
||||
"required": ["fact_id", "confidence"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fact_id": {
|
||||
"type": "string",
|
||||
"description": "FactId from the knowledge vocabulary (#368)."
|
||||
},
|
||||
"confidence": {
|
||||
"type": "string",
|
||||
"enum": ["suspects", "knows_of", "knows_details", "direct"],
|
||||
"description": "D-041 confidence tier granted."
|
||||
}
|
||||
}
|
||||
},
|
||||
"dual_lens": {
|
||||
"type": "object",
|
||||
"description": "Authoring-only: per-character notes for content with different resonance for smuggler vs detective. NOT consumed by the engine.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"smuggler": { "type": "string" },
|
||||
"detective": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "Authoring-only: freeform author notes, context, or intent documentation. NOT consumed by the engine."
|
||||
}
|
||||
},
|
||||
"$defs": {
|
||||
"monologue_extension": {
|
||||
"title": "Monologue-specific additions (D-035)",
|
||||
"description": "Additional required fields for monologue lines. Applied ON TOP OF the base dialogue line schema. Pool-level character partitioning (D-032) is enforced at the pool root, not per-line.",
|
||||
"type": "object",
|
||||
"required": ["trigger"],
|
||||
"properties": {
|
||||
"character": {
|
||||
"type": "string",
|
||||
"enum": ["smuggler", "detective"],
|
||||
"description": "D-032: hard partition tag. Which playable character this line belongs to. Must match the parent pool's character field."
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enter_location",
|
||||
"observe_npc",
|
||||
"hear_sound",
|
||||
"observe_anomaly",
|
||||
"post_conversation",
|
||||
"discover_evidence",
|
||||
"witness_interaction",
|
||||
"time_idle",
|
||||
"return_visit"
|
||||
],
|
||||
"description": "What causes this monologue line to fire. 9 v0.1 trigger types."
|
||||
},
|
||||
"prerequisite": {
|
||||
"description": "Knowledge state gate. null = unconditional (fires whenever triggered). Conditions are AND-evaluated. Uses FactIds from the knowledge vocabulary (#368, D-041).",
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"facts": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/fact_prerequisite" }
|
||||
},
|
||||
"entity_attributes": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/attribute_prerequisite" }
|
||||
},
|
||||
"relationship": {
|
||||
"type": "object",
|
||||
"required": ["target", "state"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"target": { "type": "string" },
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "known", "friendly", "person_of_interest", "hostile"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 10,
|
||||
"default": 5,
|
||||
"description": "Selection priority. Higher = more likely to fire when multiple lines are eligible. Default: 5."
|
||||
},
|
||||
"cooldown": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"default": 0,
|
||||
"description": "Minimum simulation ticks before this line can fire again. Default: 0 (no cooldown)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"fact_prerequisite": {
|
||||
"type": "object",
|
||||
"required": ["fact_id", "min_confidence"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fact_id": {
|
||||
"type": "string",
|
||||
"description": "FactId from the knowledge vocabulary (#368, D-041)."
|
||||
},
|
||||
"min_confidence": {
|
||||
"type": "string",
|
||||
"enum": ["suspects", "knows_of", "knows_details", "direct"],
|
||||
"description": "Minimum D-041 confidence level required for this fact."
|
||||
}
|
||||
}
|
||||
},
|
||||
"attribute_prerequisite": {
|
||||
"type": "object",
|
||||
"required": ["entity", "key", "value"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"entity": { "type": "string" },
|
||||
"key": { "type": "string" },
|
||||
"value": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"situation_enum": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"arrival",
|
||||
"shift_start",
|
||||
"shift_end",
|
||||
"shift_transition",
|
||||
"bar_evening",
|
||||
"night_shift",
|
||||
"investigation",
|
||||
"confrontation",
|
||||
"social",
|
||||
"alone",
|
||||
"emergency",
|
||||
"routine",
|
||||
"observation",
|
||||
"greeting"
|
||||
],
|
||||
"description": "14 v0.1 situation values (D-035 + Sprint 8 amendment)."
|
||||
},
|
||||
"topic_enum": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"colleague",
|
||||
"routine",
|
||||
"cargo",
|
||||
"money",
|
||||
"trust",
|
||||
"danger",
|
||||
"institution",
|
||||
"personal",
|
||||
"investigation"
|
||||
],
|
||||
"description": "9 v0.1 topic values (D-035)."
|
||||
},
|
||||
"mood_enum": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fond",
|
||||
"comfortable",
|
||||
"worried",
|
||||
"suspicious",
|
||||
"analytical",
|
||||
"conflicted",
|
||||
"concerned",
|
||||
"relieved",
|
||||
"focused"
|
||||
],
|
||||
"description": "9 v0.1 mood values (D-035 + Sprint 8 amendment: focused added)."
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,14 +106,28 @@
|
||||
"knowledge_grant": {
|
||||
"type": "object",
|
||||
"description": "Knowledge the player gains from hearing this line",
|
||||
"required": ["fact_id", "confidence"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fact_id": { "type": "string" },
|
||||
"confidence": {
|
||||
"type": "string",
|
||||
"enum": ["suspects", "knows_of", "knows_details", "direct"]
|
||||
}
|
||||
},
|
||||
"required": ["fact_id", "confidence"]
|
||||
}
|
||||
},
|
||||
"dual_lens": {
|
||||
"type": "object",
|
||||
"description": "Authoring-only: per-character resonance notes (NOT consumed by engine)",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"smuggler": { "type": "string" },
|
||||
"detective": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "Authoring-only: freeform author notes (NOT consumed by engine)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "monologue-pool.schema.json",
|
||||
"title": "Monologue Line Pool",
|
||||
"description": "Tagged monologue lines, hard-partitioned by character (D-032, D-035).",
|
||||
"description": "Tagged monologue lines, hard-partitioned by character (D-032, D-035). Monologue lines carry all 6 D-035 structural tags for schema compliance, but role/access/trust are fixed constants for player-character internal voice (role=player_character, access=[public], trust=surface). The engine does not gate monologue on access or trust — these tags exist for taxonomy uniformity only.",
|
||||
"type": "object",
|
||||
"required": ["character", "location", "lines"],
|
||||
"additionalProperties": false,
|
||||
@@ -10,12 +10,12 @@
|
||||
"character": {
|
||||
"type": "string",
|
||||
"enum": ["smuggler", "detective"],
|
||||
"description": "Playable character this pool belongs to (hard partition per D-032)"
|
||||
"description": "Playable character this pool belongs to (hard partition per D-032). All lines in this pool belong to this character."
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9-]*$",
|
||||
"description": "Location slug, or 'general' for location-independent lines"
|
||||
"description": "Location slug, or 'general' for location-independent lines."
|
||||
},
|
||||
"lines": {
|
||||
"type": "array",
|
||||
@@ -26,70 +26,177 @@
|
||||
"$defs": {
|
||||
"monologue_line": {
|
||||
"type": "object",
|
||||
"required": ["id", "text", "trigger"],
|
||||
"required": ["id", "text", "role", "access", "trust", "situation", "trigger"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"pattern": "^[a-z][a-z0-9-]*_m_[sd]_[0-9]{3}$",
|
||||
"description": "Stable line ID: {location_slug}_m_{s|d}_{###}"
|
||||
"description": "Stable line ID: {location_slug}_m_{s|d}_{###}. s = smuggler, d = detective."
|
||||
},
|
||||
"text": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 160,
|
||||
"description": "Line text — 160 char max to fit monologue display without scrolling"
|
||||
"description": "Line text — 160 char max to fit monologue display without scrolling."
|
||||
},
|
||||
"role": {
|
||||
"type": "string",
|
||||
"const": "player_character",
|
||||
"description": "D-035 structural tag — always player_character for monologue. Monologue is the player character's internal voice."
|
||||
},
|
||||
"access": {
|
||||
"type": "array",
|
||||
"items": { "const": "public" },
|
||||
"minItems": 1,
|
||||
"maxItems": 1,
|
||||
"description": "D-035 structural tag — always [public] for monologue. No access gating applies to internal voice."
|
||||
},
|
||||
"trust": {
|
||||
"type": "string",
|
||||
"const": "surface",
|
||||
"description": "D-035 structural tag — always surface for monologue. No trust gating applies to internal voice."
|
||||
},
|
||||
"situation": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"arrival",
|
||||
"shift_start",
|
||||
"shift_end",
|
||||
"shift_transition",
|
||||
"bar_evening",
|
||||
"night_shift",
|
||||
"investigation",
|
||||
"confrontation",
|
||||
"social",
|
||||
"alone",
|
||||
"emergency",
|
||||
"routine",
|
||||
"observation",
|
||||
"greeting"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"uniqueItems": true,
|
||||
"description": "D-035 structural tag: situations in which this monologue line is contextually appropriate. 14 v0.1 values. The engine selects using trigger; situation provides additional authoring context for filtering by the caller. NOTE: 'greeting' is not yet in server/src/content/line_pool.rs Situation enum."
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"enter_location", "observe_npc", "hear_sound",
|
||||
"observe_anomaly", "post_conversation", "discover_evidence",
|
||||
"witness_interaction", "time_idle", "return_visit"
|
||||
"enter_location",
|
||||
"observe_npc",
|
||||
"hear_sound",
|
||||
"observe_anomaly",
|
||||
"post_conversation",
|
||||
"discover_evidence",
|
||||
"witness_interaction",
|
||||
"time_idle",
|
||||
"return_visit"
|
||||
],
|
||||
"description": "What causes this line to fire"
|
||||
"description": "What causes this line to fire. 9 v0.1 trigger types (D-035 monologue-specific tag)."
|
||||
},
|
||||
"prerequisites": {
|
||||
"type": "object",
|
||||
"description": "AND-only prerequisite conditions",
|
||||
"properties": {
|
||||
"facts": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/fact_prerequisite" }
|
||||
},
|
||||
"entity_attributes": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/attribute_prerequisite" }
|
||||
},
|
||||
"relationship": {
|
||||
"description": "Knowledge state gate (D-035 monologue-specific tag). null or omitted = unconditional. Conditions are AND-evaluated. Uses FactIds from the knowledge vocabulary (#368, D-041).",
|
||||
"oneOf": [
|
||||
{ "type": "null" },
|
||||
{
|
||||
"type": "object",
|
||||
"required": ["target", "state"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"target": { "type": "string" },
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "known", "friendly", "person_of_interest", "hostile"]
|
||||
"facts": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/fact_prerequisite" }
|
||||
},
|
||||
"entity_attributes": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/$defs/attribute_prerequisite" }
|
||||
},
|
||||
"relationship": {
|
||||
"type": "object",
|
||||
"required": ["target", "state"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"target": { "type": "string" },
|
||||
"state": {
|
||||
"type": "string",
|
||||
"enum": ["unknown", "known", "friendly", "person_of_interest", "hostile"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"topic": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"colleague",
|
||||
"routine",
|
||||
"cargo",
|
||||
"money",
|
||||
"trust",
|
||||
"danger",
|
||||
"institution",
|
||||
"personal",
|
||||
"investigation"
|
||||
]
|
||||
},
|
||||
"uniqueItems": true,
|
||||
"description": "D-035 selection tag: topic tags for weighted selection. 9 v0.1 values. Optional."
|
||||
},
|
||||
"mood": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"fond",
|
||||
"comfortable",
|
||||
"worried",
|
||||
"suspicious",
|
||||
"analytical",
|
||||
"conflicted",
|
||||
"concerned",
|
||||
"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."
|
||||
},
|
||||
"priority": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 10,
|
||||
"default": 5,
|
||||
"description": "Selection priority (higher = more likely to fire)"
|
||||
"description": "Selection priority (higher = more likely to fire when multiple lines are eligible). Default: 5."
|
||||
},
|
||||
"cooldown": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Minimum ticks before this line can fire again"
|
||||
"default": 0,
|
||||
"description": "Minimum simulation ticks before this line can fire again. Default: 0."
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" }
|
||||
"items": { "type": "string" },
|
||||
"description": "D-035 selection tag: freeform tags. Not consumed by the selection pipeline."
|
||||
},
|
||||
"dual_lens": {
|
||||
"type": "object",
|
||||
"description": "Authoring-only: notes on how this line reads differently for smuggler vs detective. NOT consumed by engine.",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"smuggler": { "type": "string" },
|
||||
"detective": { "type": "string" }
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "string",
|
||||
"description": "Authoring-only: freeform author notes. NOT consumed by engine."
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -98,10 +205,14 @@
|
||||
"required": ["fact_id", "min_confidence"],
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"fact_id": { "type": "string" },
|
||||
"fact_id": {
|
||||
"type": "string",
|
||||
"description": "FactId from the knowledge vocabulary (#368, D-041)."
|
||||
},
|
||||
"min_confidence": {
|
||||
"type": "string",
|
||||
"enum": ["suspects", "knows_of", "knows_details", "direct"]
|
||||
"enum": ["suspects", "knows_of", "knows_details", "direct"],
|
||||
"description": "Minimum D-041 confidence level required."
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user