diff --git a/docs/design/base-text-authoring-guide.md b/docs/design/base-text-authoring-guide.md new file mode 100644 index 000000000..8d29ffea5 --- /dev/null +++ b/docs/design/base-text-authoring-guide.md @@ -0,0 +1,150 @@ +# Base Text Authoring Guide — The Settled Reach +**Ticket:** #645 +**Author:** Mellanie +**Status:** canonical +**Last updated:** 2026-03-13 + +--- + +## Purpose + +Base text is the culture-neutral semantic layer that every NPC line must have. It serves two purposes simultaneously: + +1. **LLM seed** — the culture injector uses it as input. The better the base text, the better the voiced output. +2. **Fallback** — when "AI-Enhanced Dialogue" is off or pre-voicing hasn't caught up, the player sees the base text directly. It must be functional and readable on its own. + +This guide establishes the quality bar for all base text and documents failure modes to avoid. + +--- + +## The quality bar (three tests) + +Before committing a base text line, ask three questions: + +1. **Does this show a moment, not a category?** + A category tells you *what kind of thing* the NPC does. A moment shows you *a specific instance* of doing it. + - Category: `"tends crops in the field"` — you know the role, not the person + - Moment: `"works back along a crop row with a hoe, stopping twice to pull weeds by the root before moving on"` — you see a specific action sequence + +2. **Could you picture a specific person doing this?** + If the line could appear in a stock photo caption, it needs specificity. Add the HOW or the small physical detail that anchors it. + +3. **Would you be okay if this were the only text the player ever sees?** + The voice pipeline may not be available. The base text is not a draft — it is the default experience for players below spec or with AI-Enhanced Dialogue disabled. Author it to that bar. + +--- + +## Failure modes and fixes + +### 1. Category verb (`"tends"`, `"checks"`, `"monitors"`, `"manages"`) + +These describe a role, not an action. Replace with a physical verb sequence. + +| Below bar | At bar | +|---|---| +| `"tends rows of low-growing crops"` | `"works back along a crop row with a hoe, stopping twice to pull weeds by the root"` | +| `"checks seedling trays"` | `"crouches over seedling trays and pinches back a leggy stem that grew sideways"` | +| `"monitors the loading floor"` | `"stands at the mezzanine rail watching a forklift angle into a tight bay"` | + +Fix: replace the category verb with the specific physical action that *constitutes* the checking or tending. + +### 2. Quality modifier instead of specific action + +Phrases like `"with practiced ease"`, `"with no wasted motion"`, `"efficiently"` are editorial commentary. The reader is told a quality instead of shown it. + +| Below bar | At bar | +|---|---| +| `"lifts a crate of produce onto a flatbed with practiced ease"` | `"lifts a crate of produce onto the flatbed in one motion and reaches for the next before it settles"` | +| `"swaps a panel module with practiced speed and no wasted motion"` | `"swaps a panel module in under a minute, tools back in the pouch before the cover is latched"` | + +Fix: remove the modifier. Show the efficiency through the action sequence itself — specificity creates the impression. + +### 3. Universal gesture (no character information) + +Some gestures apply to anyone anywhere — wiping sweat, yawning, stretching. These are placeholder movements. Replace with something that shows role, circumstance, or cultural habit. + +| Below bar | At bar | +|---|---| +| `"wipes sweat from her forehead with the back of a gloved hand"` | `"pulls a glove off with her teeth to wipe her face, pulls it back on without looking"` | +| `"stretches"` | `"straightens up and rolls her neck once before kneeling back down"` | + +Fix: find the physical quirk that's specific to this role, this body, this situation. + +### 4. "Corporate-speak" / institutional passive + +Lines that sound like they belong in a procedure manual. The NPC is a person, not a process. + +| Below bar | At bar | +|---|---| +| `"initiates task completion protocol"` | `"gets back to sorting the manifests"` | +| `"performs routine inspection of the facility perimeter"` | `"walks the fence line at a measured, unhurried pace"` | + +Fix: use plain present tense and a physical verb. If you can hear a robot saying it, rewrite it. + +--- + +## Lines you must NOT change + +**`Factual`-type lines** are protected. These are lines bearing: +- Specific numbers or quantities: `"fourteen crates in bay seven"` +- Causal chains: `"the pressure dropped when the coupling failed"` +- Denial statements: `"I don't know anything about that"` + +Factual lines bypass the LLM entirely (D-138: `ContentType::Factual`). The pipeline serves them as exact base text. Do not elevate them — precision is their quality. + +If you're unsure whether a line is Factual: if it contains a number, a named quantity, a specific event chain, or a denial with named content, treat it as protected. + +--- + +## Cultural neutrality requirement + +Base text must not contain culture-specific vocabulary. The culture injectors add that layer at voicing time. Base text is the semantic skeleton. + +**What to avoid:** +- Krenn oath vocabulary: `"void take it"`, `"blood and void"` — these go in voice_examples, not base text +- Culture-specific greetings or farewells +- Slang that implies a particular register + +**Test:** Could this base text be voiced convincingly in *any* culture? If the answer is yes, it's culturally neutral. If not, strip the culture-specific markers. + +--- + +## Volume guidance + +Each role should have 15–25 on-shift behaviors plus 5–10 off-shift / social site behaviors. This gives the generator enough variety across a session without repetition. Roles with narrower activity ranges (security, militia) can have fewer. + +--- + +## Elevation checklist (per line) + +Before committing, run through: + +- [ ] Physical verb sequence, not category verb +- [ ] No quality modifiers — show efficiency through action, not through adjective +- [ ] Character information visible (not a universal gesture) +- [ ] No institutional/passive language +- [ ] Culturally neutral (no culture-specific vocabulary) +- [ ] Not a Factual-type line (no numbers, denials, or causal chains to preserve) +- [ ] Reads as a complete, legible observation on its own + +--- + +## Examples from the Krenn zones (reference) + +These lines passed elevation review (Sprint 26): + +**Strong — moment-specific, role-grounded:** +- `"kneels at the base of a struggling plant and parts the soil with two fingers"` +- `"threads a wire through a conduit clip and bites the insulation back with her teeth"` +- `"keeps a junior worker between herself and the inspection team as the inspectors pass through"` +- `"trades a quick look with a colleague when the foreman walks by"` +- `"leans back on a stool and tracks a browser who has checked the same display twice without committing"` + +**Strong — off-shift texture:** +- `"sits with boots off under the table, socked feet flat on the floor"` +- `"refills a neighbor's cup from her own jug without being asked"` +- `"buys a round for the table and returns to her seat before anyone can thank her"` + +--- + +*Last review: Sprint 26, base text elevation pass (#645). Apply this standard to all new zone RON files.* diff --git a/docs/design/content-structure-canonical.md b/docs/design/content-structure-canonical.md new file mode 100644 index 000000000..8fc814556 --- /dev/null +++ b/docs/design/content-structure-canonical.md @@ -0,0 +1,236 @@ +# Content Structure — Canonical Template (v0.2+) + +**Created:** 2026-03-13 (Sprint 26, #656 cleanup) +**Supersedes:** `content-directory-structure.md` v0.1 design spec (that doc describes the *removed* structure) + +This document defines the canonical directory layout for campaign content after removal of v0.1 hand-authored dead weight. It is the template to replicate across 300 systems. + +--- + +## Actual Directory Hierarchy + +The real hierarchy (note: diverges from old design doc which used `content/districts/`): + +``` +content/ + global/ # Cross-system content + culture-{name}.ron # Voice pipeline persona (LIVE GENERATOR INPUT) + culture-{name}.example.ron # Example output for reference + zone-identity-spec.example.ron # Zone identity template example + factions/ # Faction profiles (one per faction) + knowledge/ # Shared FactId catalog (one per category) + enums/ # Shared enum definitions + contraband/ # Contraband item profiles + technology/ # Technology definitions + + campaigns/ + main/ + systems/ + {system-slug}/ # e.g. krenn/ + system.yaml # System metadata + stations/ + {station-slug}/ # e.g. sova/ + station.yaml # Station metadata + districts/ + {district-slug}/ # e.g. transit/ + district.yaml # Zone metadata (CANONICAL METADATA) + locations/ # Physical location definitions + templates/ # Social site role-slot definitions + triangles/ # Drama triangles (3-NPC clusters) + environmental/ # Environmental text (signs, terminals, docs) + routines/ # NPC schedule definitions + ticker/ # News ticker content + + modules/ + tier1/ # Tier 1 drama modules (authored) + {module-slug}.yaml + + gauntlet/ # Test world (immutable — do not modify) + rooms/ + + _schema/ # JSON Schema validation files + _meta/ # Infrastructure metadata + schemas/ # (additional schemas) +``` + +--- + +## What Each Piece Is + +| Path | Type | Who produces it | Notes | +|------|------|-----------------|-------| +| `global/culture-{name}.ron` | **Generator input** (authored) | Copy team | Voice pipeline persona. One per culture. DO NOT DELETE. | +| `global/factions/*.yaml` | **Authored** | Copy team | Faction profiles. One per faction. | +| `global/knowledge/*.yaml` | **Authored** | Copy team | FactId catalog by category. | +| `global/enums/*.yaml` | **Authored** | Copy team | Shared enum definitions (situations, moods, etc.) | +| `system.yaml` | **Metadata** | Copy team | System display name, factions present, setting notes. | +| `station.yaml` | **Metadata** | Copy team | Station description, district list. | +| `district.yaml` | **Metadata** | Copy team | Zone display name, location list, target NPC count. | +| `locations/*.yaml` | **Metadata** | Copy team | One file per location: display name, description, type. | +| `templates/*.yaml` | **Authored** | Copy team | Role slot definitions (what roles exist, capacity range). Survives to v0.2+. The `v01_assignments` field is v0.1-only — clear for generated content. | +| `triangles/*.yaml` | **Authored** | Copy team | Drama triangles with fork conditions and resolution states. Template material for generated equivalents. | +| `environmental/*.yaml` | **Authored** | Copy team | Environmental text objects with optional dual-lens properties (v0.1 remains). | +| `routines/schedules.yaml` | **Authored** | Copy team | NPC daily routines. Will be generated per-NPC in v0.2+. | +| `ticker/*.yaml` | **Authored** | Copy team | News ticker content for this district. | +| `modules/tier1/*.yaml` | **Authored** | Copy team | Drama modules (Paula's schema). | + +--- + +## What Was Removed (v0.1 Dead Weight) + +These directories and files existed in v0.1 and have been deleted (Sprint 26, #656/#657): + +| Removed | Reason | +|---------|--------| +| `npcs/*.yaml` | Hand-authored named NPC profiles. Replaced by generator output. All 23 files deleted. | +| `dialogue/` | Hand-authored NPC dialogue pools per role/location. Replaced by voice pipeline. | +| `monologue/detective/` | Detective player monologue. Detective archetype removed per D-117. | +| `monologue/smuggler/` | Smuggler player monologue. Smuggler archetype removed per D-117. | +| `insert/detective.yaml` | Detective insert HUD content. | +| `insert/smuggler.yaml` | Smuggler insert HUD content. | +| `items/smuggler-inventory.yaml` | Smuggler item definitions. | +| `pools.yaml` | v0.1 NPC candidate pool definitions. All candidates referenced deleted NPCs. | +| `content/global/knowledge/investigation.yaml` | Investigation facts. Detective system removed. (Deleted in #657.) | +| `content/global/factions/lattice-commission.yaml` | Detective faction. (Deleted in #657.) | +| Several `docs/design/` detective/smuggler docs | Archetype design docs. (Deleted in #657.) | + +--- + +## Canonical Template: New System Checklist + +When adding a new system for v0.2+ (target: 300 systems), replicate this pattern: + +``` +content/campaigns/main/systems/{system-slug}/ + system.yaml # 1. Author system metadata + stations/ + {station-slug}/ + station.yaml # 2. Author station metadata + districts/ + {district-slug}/ + district.yaml # 3. Author district metadata + locations/ # 4. Define locations (5-10 per district) + {location-slug}.yaml + templates/ # 5. Define social site templates (role slots) + {template-slug}.yaml + routines/ # 6. Author schedule structure + schedules.yaml # (v0.2: generated per NPC) + ticker/ # 7. Author news ticker content + {location-slug}.yaml +``` + +**Note:** `environmental/` and `triangles/` are authored for hand-crafted districts. For generated districts, these may be generated or omitted in v0.2+. + +**No NPCs.** No dialogue. No monologue. Those are generator outputs, not authored inputs. + +--- + +## Culture RON File Reference + +Culture files are the primary input for the voice pipeline. One file per culture, living at `content/global/culture-{name}.ron`. + +**Use `content/global/culture-krenn.ron` as the authoring template — not `culture-krenn.example.ron`.** The example file is an older stub without voice pipeline fields. + +### Required fields (must be present) + +``` +id: "slug" # kebab-case, matches filename +name: "Display Name Culture" +description: "Single sentence: settlement age, location type, economic base, social character" + +naming: ( + style: # prose: phoneme style, casing norms + given_names: [...] # 30-50 names minimum for adequate generator variety + family_names: [...] # 10-20 names + family_name_used_socially: bool +) + +speech: ( + register: # prose: formality, rhythm, directness + filler_words: [...] # 4-8 words + greetings: [...] # 3-6 phrases + farewells: [...] # 3-6 phrases + exclamations: [...] # 4-8 oath/exclamation phrases +) + +values: ( + description: # prose: core value system + favored_traits: [...] # subset of valid traits (see below) + disfavored_traits: [...] # subset of valid traits +) +``` + +### Optional fields (omit or include as needed) + +``` +voice_persona: Some("PERSONA: ...") # Full LLM prompt persona block +voice_examples: [ # 3-5 input/output pairs + (input: "...", output: "..."), +] +occasional_injections: [ # Rolled per-prompt at given frequency + ( + kind: "oath" # human-readable category label + clause: "When ... use ...", # LLM instruction text + example: Some((input: "...", output: "...")), # optional + frequency: 0.25, # 0.0–1.0 + suppress_on_tells: [Guarded], # optional: omit injection when these tells active + ), +] +behavior_modifiers: [ # Culture-specific action modifier clauses (#633) + (category: "work_style", clause: "with mechanical efficiency"), +] +``` + +### Valid enum values + +**PersonalityTrait** (for `favored_traits` / `disfavored_traits`): +`Cautious, Bold, Honest, Deceptive, Compassionate, Ruthless, Curious, Incurious, Social, Reclusive` + +**TellCategory** (for `suppress_on_tells`): +`Nervous, Angry, Friendly, Guarded, RoutineDeviation` + +### voice_persona: NOT-list requirement (D-138, Spike 2) + +Spike 2 found that **explicit NOT-lists in the persona block are critical** for voice quality. Without them, the LLM defaults to register-neutral generic output. Add an explicit "NEVER:" or "NOT:" section at the end of the persona block: + +``` +voice_persona: Some( + "PERSONA: ...\n + [positive instructions]\n\n + NEVER: [list 3-5 explicit negatives that define what this culture does NOT sound like]" +), +``` + +Example negatives for Krenn (currently implicit, should be made explicit in a future pass): +- Never effusive, never corporate-polished, never deferential to rank +- Never flowery or verbose + +`culture-krenn.ron` does not yet have an explicit NOT-list — this is a known gap to address when doing a persona quality pass. + +### behavior_modifiers: status + +`culture-krenn.ron` does not populate `behavior_modifiers` — it defaults to `[]`. This field is for the decomposed behavior system (#633/#634), which is not yet shipped. Leave empty for all cultures until #634 format is confirmed. + +--- + +## Scale Readiness Audit + +### Passes at 300 systems + +| Structure | Ready | Notes | +|-----------|-------|-------| +| `content/campaigns/main/systems/` | Yes | One directory per system. No monolithic files. | +| `content/global/factions/` | Yes | One file per faction — will grow to ~20-50 factions across 300 systems | +| `content/global/knowledge/` | Mostly | Knowledge catalog is organized by category, not by system. Flat list works to ~500 facts. | +| `content/global/culture-{name}.ron` | Yes | One file per culture (expected ~10-20 cultures total). | +| `templates/*.yaml` | Yes | Role slot definitions are reusable across systems of the same type. | +| `modules/tier1/` | Yes | Flat directory. Drama modules are self-contained. | + +### Needs attention at scale + +| Structure | Issue | Recommendation | +|-----------|-------|----------------| +| `content/global/knowledge/investigation.yaml` | **Deleted** — was detective-system-only. Other knowledge files need review for detective/smuggler references. | Audit surviving knowledge files post-#657. | +| `environmental/*.yaml` | Has v0.1 `dual_lens` (smuggler/detective) properties in the terminal/bar/corridors files. These are dead properties once archtypes are removed. | Flag to client team — strip `dual_lens` field from environmental schema for v0.2. | +| `templates/` v01_assignments | Fields reference deleted named NPCs. Not validated but stale. | Remove `v01_assignments` and `reference_links` from templates when generating v0.2+ content. | +| `triangles/` members | Reference deleted named NPCs (e.g., `npc:voss`, `npc:kael-davan`). These are unresolvable after NPC deletion. | These triangles are v0.1 examples. Either delete or annotate as templates for generated equivalents. | diff --git a/docs/design/culture-authoring-guide.md b/docs/design/culture-authoring-guide.md new file mode 100644 index 000000000..9b94c6f3c --- /dev/null +++ b/docs/design/culture-authoring-guide.md @@ -0,0 +1,385 @@ +# Culture Authoring Guide — The Settled Reach +**Ticket:** #653 +**Author:** Mellanie +**Status:** canonical +**Last updated:** 2026-03-13 + +--- + +## Purpose + +Every NPC in The Settled Reach is voiced through their culture. The voice pipeline (D-138) uses a culture profile as its primary LLM prompt — the culture injector is the single biggest determinant of how an NPC sounds. Culture files are authored once and used thousands of times across every generated NPC in that system. + +This guide explains how to author a new culture RON file from scratch. The target: one experienced author can produce a complete, Spike-validated culture profile in approximately one working day. + +Three reference cultures exist in `content/global/`: +- `culture-krenn.ron` — Mid-Reach, freight, working-class pragmatic (the Spike 1/2 reference) +- `culture-vael.ron` — Core-adjacent, administrative/research, institutional-formal +- `culture-osse.ron` — Deep Frontier, extraction industry, terse-utilitarian + +--- + +## Step 1: Establish the cultural parameters (30 minutes) + +Before writing a single word of the RON file, answer these five questions from `docs/design/cultural-generation-guide.md`: + +1. **Reach Position:** Core / Mid-Reach / Frontier / Deep Frontier + → Determines authority attitude, institutional density, Commission presence + +2. **Economic Base:** Freight / Extraction / Manufacturing / Service / Research / Mixed + → Determines daily vocabulary, rhythms, what NPCs care about + +3. **Settlement Age:** New (< 20 yr) / Established (20-80 yr) / Mature (80-200 yr) / Old (200+ yr) + → Determines rootedness, architectural character, whether "here" is home + +4. **Heritage Root:** Frost / Tide / Iron / Dust / Stone / Vine / Salt + → Determines spatial grammar and social privacy model (D-104/D-105) + +5. **Institutional Ecosystem:** Which institutions are visibly present? Which are resented? + +Write these down before you open a RON file. They are the source of truth for everything that follows. + +--- + +## Step 2: Define the voice register (10 minutes) + +Derive the voice register from your five parameters. Ask: + +- How does authority feel here? (Collegial / Distant / Resented / Absent) +- What is the cost of saying too much? (Social / Physical / Legal / None) +- What earns trust here? (Credential / Shared risk / Repeated interaction / Demonstrated skill) +- What is the cultural mode of emotional expression? (Understatement / Direct / Suppressed / Public) + +Write a one-sentence register description. This becomes `speech.register` in the RON. + +**Examples:** +| Culture | Register | +|---|---| +| Krenn | direct, minimal pleasantries, gets to the point | +| Vael | complete sentences, institutional framing, measured, precise | +| Osse | terse, functional, equipment vocabulary, economy of language | + +--- + +## Step 3: Author the naming conventions (15 minutes) + +Naming conventions follow from Reach position + settlement age + economic base: + +| Parameter | Naming tendency | +|---|---| +| Core, old settlement | Polysyllabic, family-name-primary, formal phonemes | +| Mid-Reach, mature | Mixed — 1-2 syllables, either name primary depending on sub-culture | +| Frontier, new | Short, work-derived, first-name-only or work-name | +| Extraction industry | Often functional; legal names exist but aren't used | +| Administrative | Family names primary; given names reserved | + +**IP check rule:** All names must be checked against: +- Hamilton (Contemporary English given names — avoid) +- Banks (Whimsical/elaborate — avoid) +- Reynolds Firefly (Earth-ethnic echoes — avoid) +- Simmons (Greek/Latin — avoid overt register matches) + +Generate 25-35 given names and 15-20 family names. The generator needs variety across playthroughs. + +--- + +## Step 4: Author the voice persona (60 minutes — the core work) + +The `voice_persona` field is the LLM instruction that shapes all re-voiced output for NPCs from this culture. This is where one working day of authoring matters most. + +### Structure (6-block format, ~200-250 tokens total) + +``` +PERSONA: You are a [Culture] [role type]. + +[BLOCK 1 — REGISTER, ~25 tokens] +State the register style, why it exists, one distinguishing marker. + +[BLOCK 2 — CULTURAL CONTEXT, ~25 tokens] +One sentence: what shaped this culture's voice. The social or environmental fact. + +[BLOCK 3 — VOCABULARY, ~40 tokens] +Exclamations: [closed list — ONLY these expressions. No invented variants.] +Greetings: [closed list] +Address conventions: [family-name-primary, first-name-only, etc.] + +[BLOCK 4 — VALUES, ~20 tokens] +Two core values as behavioral instructions. "You do X." not "This culture values X." + +[BLOCK 5 — NOT-LIST, ~20 tokens] +2-3 exclusions SPECIFIC to this culture. (Universal NIs cover global constraints.) +Start with: "Do not use..." + +[BLOCK 6 — EXAMPLE PAIRS (~70-80 tokens): go in voice_examples field, not here] +``` + +### The NEVER: block — most important element + +**Format:** The NEVER: block is a separate, explicitly labeled section at the END of the persona string. It must not be embedded as a numbered instruction. + +``` +[positive instructions 1-8]\n\nNEVER: [3-5 explicit negatives defining what this culture does NOT sound like] +``` + +**Why it matters:** Spike 2 finding — explicit NEVER: blocks are critical for voice quality. Without them, the LLM defaults to register-neutral generic output. The numbered instructions alone are insufficient. The NEVER: section must be visually and structurally distinct — not "8. Do not use..." but a labeled block after the numbered list. + +The not-list prevents register leakage. Write it by asking: what does this culture NEVER sound like? + +| Culture | NEVER block content | +|---|---| +| Krenn | Corporate/institutional language, effusive or polished register, deference to unearned rank, family names in casual use, verbosity | +| Vael | Void-adjacent exclamations (frontier register), casual contractions, sentence fragments, direct emotional expression, physical labor as value ground | +| Osse | Institutional framing or courtesy protocols, formal complete sentences, Commission-world vocabulary, Core-system exclamations, elaboration where a number suffices | + +The NEVER: content must be culture-specific. Global constraints (no religious language, no Earth references) go in the universal RULES const — do not repeat them per-culture. + +### Writing the persona instructions + +Use second-person imperative: "Be direct." not "This culture is direct." + +Each numbered instruction should target a DIFFERENT speech dimension so trait modifiers can stack cleanly: +- Instruction 1 → delivery style (brevity, formality) +- Instruction 2 → cultural context (why they speak this way) +- Instruction 3 → address conventions (name usage) +- Instruction 4 → emotional register (how feelings appear in speech) +- Instruction 5 → institutional framing (how authority is referenced) +- Instruction 6 → exclamation vocabulary (closed, explicit list) +- Instruction 7 → trust signal (how the culture verifies credibility) +- Instruction 8 → not-list (what this voice never does) + +**Test before committing:** Read the persona out loud. Can you imagine an NPC responding to you from this profile? Would a speaker from this culture be clearly distinct from Krenn if you heard both without labels? + +--- + +## Step 5: Author voice examples (20 minutes) + +Write 3-5 `(input, output)` example pairs for the `voice_examples` array. + +**Input:** A culture-neutral semantic description of an action (same as base text format) +**Output:** That action rendered in the culture's voice + +Example pairs are the model's pattern anchors — small LLMs are pattern matchers before instruction-followers. The examples matter as much as the instructions. + +**Rules for example pairs:** +1. Cover the range: neutral, positive relationship, refusal/deflection, high-affect +2. Output should be short (1-2 sentences). Long outputs lose the pattern. +3. Each output must be recognizably THIS culture — read them side by side with Krenn examples. They should sound like different people. +4. Do not use real NPC names in examples — use generic role references + +### Cross-cultural contrast test + +Before finalizing examples, run this test. Take the same three base-text inputs and write outputs in Krenn, Vael, and Osse voices. If the outputs are hard to distinguish, the culture persona needs sharpening. + +**Example inputs for the test:** +- "declines to answer a question about an overnight run" +- "acknowledges a colleague's greeting while continuing to work" +- "thanks a colleague for covering a shift" + +| Input | Krenn | Vael | Osse | +|---|---|---|---| +| Declines question | "Look, that's not mine to say." | "I'm not in a position to speak to that. You'd want to refer to the relevant operations log." | "Not my section. Ask Grek." | +| Acknowledges greeting | "Hey. Yeah. Catch you at shift end." | "Harthen. Good. I'll be with you in a moment." | "Doss. Yeah." | +| Thanks colleague | "Appreciated. See you at handoff." | "That was well handled. I've noted it." | "Solid. Owe you one." | + +If all three rows sound similar, the culture personas need work. + +--- + +## Step 6: Author occasional injections (15 minutes) + +`occasional_injections` are cultural vocabulary or speech patterns that should appear occasionally but not always — oath vocabulary, institutional references, equipment ground truth. The composition engine handles frequency; the author specifies what and when. + +**Fields:** +- `kind` — a string identifier (e.g., `"oath"`, `"protocol_cite"`, `"equipment_ground"`) +- `clause` — the LLM instruction when this injection fires +- `example` — `Some((input, output))` showing correct usage, or `None` +- `frequency` — float 0.0–1.0 (how often the composition engine fires this injection) +- `suppress_on_tells` — tells that should suppress this injection (e.g., oath suppressed when Guarded) + +**Frequency guidelines:** +- 0.25 — Krenn oath vocabulary (appears in ~1 in 4 high-affect contexts) +- 0.20 — Vael protocol citation (appears occasionally in procedural exchange) +- 0.15 — Osse equipment ground (appears in work-adjacent contexts) + +Start with one injection per culture. More can be added after Spike validation confirms the base persona is clean. + +--- + +## Step 7: Author the tell-tone table (15 minutes) + +Each culture profile should include a tell-tone table mapping each `TellCategory` to how that tell expresses in this culture's register. This table is not in the RON file — it's authoring documentation used when tell-context injectors are written. + +Include this table in a comment block in the RON file header, or maintain it separately in the culture's authoring notes. + +**Format:** + +| TellCategory | How it reads in this culture | +|---|---| +| Nervous | [culture-specific description] | +| Angry | [culture-specific description] | +| Friendly | [culture-specific description] | +| Guarded | [culture-specific description] | +| RoutineDeviation | [culture-specific description] | + +**Design principle (from D-138):** Humans are humans first. Universal baseline (phenomenon-class behavior) is always readable across cultures. Cultural flavor is conditional and additive — the LLM adds cultural texture only if it doesn't significantly change the information conveyed. Per-culture tell-tone tables are optional enrichment. + +--- + +## Step 8: Validate and commit + +```bash +tooling/validate-ron content/global/culture-{id}.ron culture +``` + +A passing validation confirms: +- All required fields are present and correctly typed +- Trait names are valid enum values +- RON syntax is correct + +After validation, the culture profile is ready for Spike 1 prompt testing. Human review of voiced output is required before the culture goes into baked content. + +--- + +## Tell-tone reference tables + +### Krenn tell-tone table + +| TellCategory | Krenn-inflected tonal register | +|---|---| +| Nervous | Answers run shorter than usual. Eyes stay on task. Nothing's wrong — just things to do. | +| Guarded | Direct past the point of directness. Closes conversation paths fast without being unfriendly. | +| Friendly | One beat more than the exchange needed. A word of warmth lands casually, not performed. | +| Angry | Steady. Even. The kind of steady that takes effort. Not hostile — just flat in a way that doesn't feel natural for Krenn. | +| RoutineDeviation | Unhurried. Unremarkably normal. Like nothing's worth noticing. | + +### Vael tell-tone table + +| TellCategory | Vael-inflected tonal register | +|---|---| +| Nervous | Sentences become over-complete — more formal than the situation warrants. Qualifications multiply. "As I understand it" appears where it wasn't needed. | +| Guarded | Responses are brief and technically correct. Every question answered with only what was asked. Institutional framing appears even in informal exchange. | +| Friendly | Slightly less formal than baseline — a given name used where a family name would normally appear. The exchange closes with a word of genuine regard. | +| Angry | Register becomes precisely correct — almost pedantically so. The effort to maintain composure shows as over-precision. | +| RoutineDeviation | Procedures are cited where they wouldn't normally be. A person who deviates from routine anchors to institutional language as cover. | + +### Osse tell-tone table + +| TellCategory | Osse-inflected tonal register | +|---|---| +| Nervous | Words get shorter. Already-minimal speech contracts to one-word answers and physical acknowledgment. The silence between words lengthens. | +| Guarded | Assessment framing appears: "Why do you need to know?" — stated or implied. Eye contact shifts from reading-the-room to reading-you specifically. | +| Friendly | Slightly more than the exchange needed — an extra beat, an unrequested detail, a "good shift" that lands with more weight. Not warm, just more. | +| Angry | Everything slows down. Deliberate. Responses accurate, closed, no opening for follow-up. | +| RoutineDeviation | The opposite of nervous: unusually conversational. When someone is off-routine on purpose, they over-normalize by saying more than usual. | + +--- + +## Checklist: culture profile complete? + +- [ ] Five parameters documented (Reach Position, Economic, Age, Heritage Root, Institutions) +- [ ] Register statement authored and distinctive +- [ ] 25+ given names, 15+ family names, phoneme pattern consistent +- [ ] `family_name_used_socially` accurately reflects the culture +- [ ] Voice persona: 8 numbered instructions, ~200-250 tokens +- [ ] Explicit `NEVER:` block at end of persona string (separate from numbered instructions) +- [ ] NEVER block content is culture-specific (not repeating universal constraints) +- [ ] Cross-cultural contrast test passed (Krenn/Vael/Osse comparison) +- [ ] 3-5 voice examples authored, range covered +- [ ] At least 1 occasional injection with suppression logic +- [ ] Tell-tone table authored (5 TellCategories) +- [ ] `tooling/validate-ron content/global/culture-{id}.ron culture` passes + +--- + +--- + +## Addendum: Behavior Modifiers (#634) + +*Added Sprint 26. Applies to all culture files after the composable behavior engine (#633) ships.* + +### What behavior_modifiers does + +The composable behavior engine assembles observable behavior strings from two parts: + +``` +BehaviorPrimitive.action + BehaviorModifier.clause → assembled behavior string +``` + +Example: +- Action: `"moves freight containers"` +- Modifier: `"without wasted motion"` (Krenn, `work_pace`) +- Result: `"moves freight containers without wasted motion"` + +A culture provides a pool of modifier clauses. When the engine assembles a behavior, it looks for a modifier whose `category` matches the primitive's `modifier_hint` string. If no match exists, it falls back to any modifier in the pool. If no modifiers exist at all, the action text is used as-is. + +**Modifier clauses must grammatically trail any action text.** They are appended with a space. Test by mentally substituting different action verbs: does the clause still read naturally? + +### BehaviorContext: when to use each tag + +Every `BehaviorPrimitive` carries a `context` field that gates when it is eligible for selection. The composition engine filters by context before modifier selection. + +| Context | Use for | Example action | +|---------|---------|----------------| +| `OnShift` | Physical task execution, work-site behaviors, role-specific routines | "checks a manifest against a handheld scanner", "guides a freight container into position" | +| `OffDuty` | Break-room behaviors, personal downtime, non-work physical states | "slumps into a break room chair and stares at nothing", "unwraps a meal packet standing at the counter" | +| `Social` | Social-site behaviors (bars, cantinas, communal spaces) — may overlap with OffDuty but specifically at a social venue | "nurses a drink at the end of the bar", "trades short words with a neighbor" | +| `Any` | Behaviors valid in any context — ambient, universal physical states | "rubs the back of their neck", "pauses and looks at nothing for a moment" | + +**Decision rule:** +- If the behavior only makes sense on a work site → `OnShift` +- If the behavior only makes sense in a break room or personal space → `OffDuty` +- If the behavior specifically happens at a social venue → `Social` +- If the behavior could appear anywhere → `Any` +- When unsure, prefer `Any` over a specific context — you can always narrow later + +### Modifier category vocabulary (the contract) + +These seven categories define the interface between primitives (Mellanie, role-action templates) and modifiers (culture authors). A primitive's `modifier_hint` must use one of these strings for a culture modifier to match it. + +| Category | What it inflects | Example modifier (Krenn) | +|---|---|---| +| `work_pace` | Speed and intensity of physical task execution | "without wasted motion" | +| `physical_manner` | Body language and movement quality during tasks | "without looking up from the task" | +| `social_signal` | Brief acknowledgments made during or between tasks | "with a short nod" | +| `task_completion` | How tasks are finished, wrapped up, or handed off | "and moves on" | +| `environmental_scan` | Awareness or vigilance while in a space | "with one eye still on the floor" | +| `offduty_posture` | Body language and social stance when off-shift | "without making a show of it" | +| `authority_response` | Behavior when supervisors or authority figures are present | "at the same pace as before" | + +**The fallback is safe but less targeted.** A primitive with no `modifier_hint` gets a random modifier from the full culture pool. This still produces culturally inflected output — just without category precision. Prefer leaving `modifier_hint` unset over using an incorrect category string. + +### Authoring behavior_modifiers for a new culture + +**Minimum:** 2 modifiers for each of the 7 categories = 14 modifiers total. Fewer categories will still work (fallback to any), but full coverage ensures every action type gets the right cultural texture. + +**Process:** +1. For each category, ask: *How does this culture DISTINCTIVELY express this dimension?* + - Not "how does any person do this" but "what is the cultural tell for this culture specifically?" +2. Write the clause as a short trailing phrase (4-8 words). Test: does it read naturally after "moves freight containers ___"? +3. Avoid clauses that are too action-specific — they need to trail any verb, not just one type +4. Run `tooling/validate-ron content/global/culture-{id}.ron culture` to confirm syntax + +**Three-culture contrast for modifiers** (same as voice persona contrast test): + +| Category | Krenn | Vael | Osse | +|---|---|---|---| +| work_pace | "without wasted motion" | "with practiced deliberation" | "at extraction pace" | +| physical_manner | "without ceremony" | "with institutional correctness" | "tool already in hand" | +| social_signal | "with a short nod" | "with a measured acknowledgment" | "with a word or a look" | +| task_completion | "and moves on" | "with the relevant entry already updated" | "gear stowed before moving on" | +| environmental_scan | "with one eye still on the floor" | "aware of who is observing" | "pressure and exits both in range" | +| offduty_posture | "without making a show of it" | "maintaining composed reserve" | "with one ear still on the work floor" | +| authority_response | "at the same pace as before" | "with appropriate professional deference" | "without a change in pace" | + +If your new culture's modifiers would fit in any of the existing columns above without sounding different — the culture needs more work before modifiers are authored. + +### Updated checklist + +Add these items to the culture profile checklist: + +- [ ] `behavior_modifiers` array populated with at least 2 clauses per category (14 minimum) +- [ ] Each clause tested: grammatically trails "moves freight containers ___" and "checks a manifest ___" +- [ ] Three-culture contrast check: modifiers are clearly distinct from Krenn, Vael, and Osse +- [ ] `tooling/validate-ron` still passes after adding modifiers + +*Addendum: Sprint 26, #634 (composable behavior content). Category vocabulary established jointly by Gestalt (culture modifiers) and Mellanie (behavior primitives). Add new categories only by agreement between both sides — unmatched categories silently fall back to any modifier.*