# Conflicts: # CHANGELOG.md # content/_meta/README.md # content/_meta/npc-authoring-style-guide.md # wiki/_templates/cultural-group.md # wiki/_templates/institution.md # wiki/_templates/star-system.md # wiki/characters/devra.md # wiki/characters/drin.md # wiki/characters/harek.md # wiki/characters/lera-sessik.md # wiki/characters/maret-korr.md # wiki/characters/naia-tamm.md # wiki/characters/nils-davan.md # wiki/characters/pell.md # wiki/characters/renn.md # wiki/characters/resha.md # wiki/characters/sabel.md # wiki/characters/sera-venn.md # wiki/characters/torek-lintar.md # wiki/characters/voss.md # wiki/star-systems/krenn/index.md
386 lines
21 KiB
Markdown
386 lines
21 KiB
Markdown
# 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-van-maanens-star.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 |
|
||
|---|---|
|
||
| Van Maanen's Star | 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 |
|
||
|---|---|
|
||
| Van Maanen's Star | 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 Van Maanen's Star 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 Van Maanen's Star 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 Van Maanen's Star, 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 | Van Maanen's Star | 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 — Van Maanen's Star 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
|
||
|
||
### Van Maanen's Star tell-tone table
|
||
|
||
| TellCategory | Van Maanen's Star-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 Van Maanen's Star. |
|
||
| 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 (Van Maanen's Star/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"` (Van Maanen's Star, `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 (Van Maanen's Star) |
|
||
|---|---|---|
|
||
| `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 | Van Maanen's Star | 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 Van Maanen's Star, 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.*
|