docs(copy): add authoring guides for base text, cultures, and content structure

Three scalability-focused authoring guides for content production
across 300+ systems:
- base-text-authoring-guide.md: elevation checklist, quality test
- culture-authoring-guide.md: 8-step workflow, modifier contract,
  cross-cultural contrast test, one-day-per-culture target
- content-structure-canonical.md: directory template, surviving
  structure audit, culture RON field reference

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 11:46:20 +01:00
co-authored by Claude Opus 4.6
parent 65e033443f
commit 9fd16fd818
3 changed files with 771 additions and 0 deletions
+150
View File
@@ -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 1525 on-shift behaviors plus 510 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.*
+236
View File
@@ -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.01.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. |
+385
View File
@@ -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.01.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.*