Files
settled-reach/docs/workshops/wiki-review/round2-gestalt.md
T
jpmschweitzerandClaude Opus 4.6 0100b33635 docs(workshops): archive wiki review workshop (4 rounds + lead interview)
Long-term content strategy workshop: 300-world generator model,
cultural ingredients menu, three-system NPC architecture (9 patterns
x 6 motivations), Sacred/Profane/Middle Kingdom framework. 9 agents
across 4 rounds plus lead interview establishing the production path
from hand-authored Sova to generated 300 worlds.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-12 16:18:56 +01:00

635 lines
30 KiB
Markdown

# Round 2 — Gestalt (Systems Design)
*cracks knuckles*
Five tensions to reconcile. Let me work through each one.
---
## 1. Content Directory Convergence (Gestalt + Tyre)
I read Tyre's proposal carefully. We agree on ~80% of the structure. Let me map where we align, where we differ, and what the converged structure should be.
### Where We Agree
| Area | Consensus |
|------|-----------|
| Global/district split | Global content (factions, tech, contraband, knowledge) separate from per-district content |
| Districts as atomic pack | `districts/{district-slug}/` is the content unit |
| Per-district NPCs | `districts/{slug}/npcs/{npc}.yaml` |
| Per-district locations | `districts/{slug}/locations/{location}.yaml` |
| Schema files | Validation schemas at the top level |
| Mod overlay | Additive by default, explicit override declarations |
### Where We Differ (and Resolution)
| My Proposal | Tyre's Proposal | Resolution | Why |
|---|---|---|---|
| `_global/` | `global/` | **Tyre wins: `global/`** | Underscore prefix is for infrastructure (`_schema/`, `_meta/`), not game content. Factions are game content. |
| Separate `dialogue/` and `monologue/` dirs | `lines/{location}/` with files inside | **Tyre wins: `lines/`** | Cleaner. `lines/terminal/dialogue.yaml` + `lines/terminal/monologue-smuggler.yaml` keeps everything per-location without parallel directory trees. |
| `triangles/` as standalone dir | `templates/` (social site defs that include triangle specs) | **Tyre wins: `templates/`** | Triangles are a property of social sites, not standalone objects. A triangle definition makes no sense without its template context. |
| `facts/` per district for local FactId instances | All facts in `global/knowledge/facts.yaml` | **I push back here.** See below. |
| `regions/` for style guides | Not addressed | **Add `global/regions/`** | Miri's Krenn brief needs a home. Regional style guides are global (same culture applies wherever that culture exists). |
| No manifest | `_meta/manifest.yaml` | **Tyre wins: add `_meta/`** | Needed for pack metadata, load order, versioning. |
### The Local Facts Question
Tyre puts all 24 FactIds in `global/knowledge/facts.yaml`. I disagree for one specific category: **spatial/location facts**.
`contraband.ring_exists` is a legitimate global definition — the CONCEPT of a smuggling ring is vocabulary. But `location.corridor_b7_restricted` is meaningless outside Sova Transit District. If we add a second district, it will have its OWN restricted corridors with different names. These are per-district facts.
**Proposed split:**
```
global/knowledge/
facts.yaml # Abstract/universal FactIds: contraband.*, world.*, relationship.*
entity-attributes.yaml # 14 canonical keys
relationship-states.yaml
districts/sova-transit/
facts.yaml # District-specific FactIds: location.*, investigation.*, progress.*
```
The engine merges both at load time. Content authors know: "Am I defining a concept (global) or a location-specific observation (district)?"
This also solves Nigel's randomizer requirement — per-seed variation can swap which district facts exist without touching global vocabulary.
### Converged Structure
```
content/
_meta/
manifest.yaml
load-order.yaml
_schema/
npc.schema.yaml
location.schema.yaml
faction.schema.yaml
template.schema.yaml
fact.schema.yaml
dialogue.schema.yaml
monologue.schema.yaml
district.schema.yaml
global/
factions/
concord-assembly.yaml
lattice-commission.yaml
syndics.yaml
guardians-of-autonomy.yaml
veil-institute.yaml
the-unbound.yaml
technology/
neural-lattice.yaml
meridian.yaml
span-gates.yaml
founder-gates.yaml
clone-transfer.yaml
severance-tech.yaml
contraband/
lattice-components.yaml
medical-grade-replacements.yaml
severance-equipment.yaml
knowledge/
facts.yaml # Global/abstract FactIds only
entity-attributes.yaml
relationship-states.yaml
regions/
krenn-system.yaml # Miri's regional style guide
districts/
sova-transit/
district.yaml # Metadata, location list, faction presence, ambient config
facts.yaml # District-specific FactIds + starting confidences
npcs/
kael-davan.yaml
sera-venn.yaml
voss.yaml
...
locations/
the-terminal.yaml
the-last-shift.yaml
maintenance-corridors.yaml
templates/
logistics-hub.yaml # Social site: role slots, triangles, NPC assignments
bar.yaml
smuggling-ring.yaml
lines/
terminal/
dialogue.yaml
monologue-smuggler.yaml
monologue-detective.yaml
bar/
dialogue.yaml
monologue-smuggler.yaml
monologue-detective.yaml
corridor/
dialogue.yaml
monologue-smuggler.yaml
monologue-detective.yaml
```
### Mod Overlay Mechanics (Accepting Tyre's)
Tyre's three overlay operations (ADD / REPLACE / MERGE) are correct and better specified than mine:
- **Entity definitions REPLACE** (whole file — no partial NPC patches)
- **Line pools MERGE** (append, deduplicate by `id`, last-writer-wins on collision)
- **New files ADD** (no conflict)
**One addition to Tyre's spec:** I want to address his open question about mod pool flooding. If a mod adds 50 dialogue lines to a 20-line pool:
**Proposal: Weight tagging.** Each content pack declares a `weight` in its manifest (default: 1.0). The line selection system uses weight as a probability multiplier. Base content at weight 1.0, a mod at weight 0.3 means mod lines are 30% as likely to be selected as base lines. This lets modders add volume without dominating the pool.
```yaml
# In mod manifest
id: extra-bartender
weight: 0.5 # Mod lines selected at half the rate of base lines
```
### Tyre's Other Open Question: District vs Template as Atomic Pack
Tyre asked: "Is `district` the right atomic content pack unit? Or should it be `template` (social site)?"
**Answer: District.** Here's why:
Templates share NPCs (bridge NPCs span social sites). If a modder adds a new NPC to the bar, that NPC might reference Hub NPCs by name, or participate in a cross-template triangle. Packing at the template level would mean the mod has to patch multiple template packs to add one NPC — that's worse for modders.
District is the natural unit because triangles and bridge connections are district-internal. A mod that adds "a new bar NPC" is really adding "a new NPC to Sova Transit District who happens to frequent the bar." The mod touches `districts/sova-transit/` and everything resolves within that directory.
---
## 2. PC-as-NPC Converged Specification (Gestalt + Paula)
Paula and I are saying the same things at different granularity. She organized by deliverable type (required/optional/PC-only), I organized by data structure (transfer/transform/generate). Let me merge.
### The Full PC-Candidate NPC Spec
An NPC that can be upgraded to a PC must have ALL standard Tier 2+ fields PLUS a `## PC Upgrade` appendix containing structured data.
#### Fields from Standard NPC Brief (Required for ALL Tier 2+)
These 12 fields transfer directly into the PC brief (Paula's list, confirmed):
| # | Field | Source Axis | Transfer Type |
|---|---|---|---|
| 1 | Name | Core Identity | Direct |
| 2 | Age | Core Identity | Direct |
| 3 | Role | Core Identity | Direct → starting occupation + access |
| 4 | System Origin | Core Identity | Direct → cultural context |
| 5 | Lattice Tier | Core Identity | Direct → perception mode caps (D-017) |
| 6 | Employer | Core Identity | Direct → faction tag + cover |
| 7 | Want (Primary) | Axis 1 | Transform → orientation monologue seed |
| 8 | Want (Secondary) | Axis 1 | Transform → emotional anchor |
| 9 | Relationships (all) | Axis 3 | Transform → starting EntityKnowledge + RelationshipState |
| 10 | Daily Routine | Axis 5 | Transform → default schedule (deviations = player agency) |
| 11 | Information Inventory | Axis 6 | Transform → starting FactId confidences + knowledge graph |
| 12 | Faction | Inferred | Direct → access tier baseline |
#### Fields That Transform at Upgrade (Derivable or Design-Choice)
| # | Field | Status | Resolution |
|---|---|---|---|
| 13 | Secret/Vulnerability | **Partially required** | The PC knows their own secret. For smuggler: ring membership IS the secret (known to player, hidden from world). For detective: personal motive for requesting posting. The NPC brief provides the secret; the upgrade inverts it from "hidden from observer" to "player's burden to manage." |
| 14 | Contentment | Generated | From Want + Relationships + archetype. Smuggler starts moderate-low. Detective starts moderate. |
| 15 | Personality Traits | **Preset per archetype for v0.1** | Paula recommended this, I agree. Player-chosen personality is a v0.2+ feature. |
| 16 | Tell System | **Inverted** | This is the critical transform. The PC's tells become NPC-observable. Gore's insight applies: tells are how the world reads the PC. "You always check your lattice before lying" — the NPC knows. The PC doesn't control this mechanically; it fires when the PC takes certain actions. |
| 17 | Skill Set | Derived | From role + archetype. |
| 18 | Combat Tag | Derived | From archetype. |
#### PC Upgrade Appendix (New Fields, Required for PC-Candidate NPCs)
This is the section that must be added to any NPC who might become a PC. It's where Paula's 6 PC-only fields and my structured formats converge:
**Field 1: Starting EntityKnowledge Table**
```yaml
pc_upgrade:
starting_knowledge:
entities:
- target: kael_davan
state: Friendly
confidence: KnowsDetails
attributes:
name: "Kael Davan"
role: "dock worker"
faction: "ring member" # smuggler knows this
trust_level: "trusted"
relationship_type: "close colleague"
routine_pattern: "morning shift 06:00-14:00, bar after shift"
behavior_flags: "reliable,punctual"
- target: voss
state: Known
confidence: KnowsOf
attributes:
name: "Voss"
role: "shift supervisor"
faction: "civilian" # smuggler thinks this
trust_level: "reliable"
relationship_type: "authority figure"
# ... all known NPCs
```
**Field 2: Starting FactId Inventory**
```yaml
starting_facts:
- id: contraband.ring_exists
confidence: KnowsDetails
- id: contraband.lattice_components
confidence: KnowsDetails
- id: location.corridor_b7_restricted
confidence: KnowsDetails
- id: world.shift_schedule
confidence: KnowsDetails
- id: relationship.ring_membership
confidence: KnowsDetails
- id: investigation.manifest_discrepancy
confidence: null # smuggler doesn't know about this
- id: contraband.severance_tech
confidence: null # smuggler doesn't know about this
```
**Field 3: Reverse Knowledge (Bidirectional)**
This is my key contribution. Every NPC who knows the PC needs their EntityKnowledge entry defined:
```yaml
known_by:
- observer: kael_davan
state: Friendly
attributes:
name: "{PC Name}"
role: "dock worker"
faction: "ring member"
trust_level: "trusted"
relationship_type: "close colleague"
- observer: voss
state: Known
attributes:
name: "{PC Name}"
role: "dock worker"
faction: "civilian"
trust_level: "reliable"
- observer: hael
state: Known
attributes:
name: "{PC Name}"
role: "dock worker"
faction: "civilian"
trust_level: "uncertain" # knows PC, doesn't know ring
- observer: lera_sessik
state: Known
attributes:
name: "{PC first name}"
role: "bar regular"
trust_level: "reliable"
```
**Why this matters:** Without reverse knowledge, NPCs don't know the player exists at game start. Kael can't say "There you are. Good — I was starting to worry" unless Kael's knowledge graph already contains an entry for the PC with `state: Friendly`. Ozzie's "walking into Cheers" moment depends entirely on these reverse entries being populated.
**Field 4: Access Tier Map**
```yaml
access_tier_map:
kael_davan: insider
voss: peer
lera_sessik: peer
hael: peer
sera_venn: public # smuggler doesn't know Sera well
maret_korr: insider # hub colleague
drin: insider # hub colleague
torek_lintar: peer # bar regular
```
**Field 5: Orientation Monologue Hook**
```yaml
orientation:
monologue_pool: "smuggler" # which pool to activate
starting_want_text: >
Protect the operation and the people in it.
The money's good and Kael makes it manageable.
But Nils keeps pushing and Hael keeps worrying.
first_monologue_triggers:
- trigger: enter_location
location: terminal
text_ref: terminal_m_001 # "Morning shift. Recycled air and cargo lubricant."
```
**Field 6: Agency Boundaries**
```yaml
agency:
can_access: [terminal, bar, corridor_b7, residential]
restricted_without_justification: [commission_kiosk] # smuggler has no Commission business
default_cover: "dock worker on shift"
cover_breaks_when: "present in restricted areas during non-shift hours, or interacting with Commission systems"
```
### Validation Requirement
Paula's Step 5 is critical: **PC's starting knowledge must not contradict any NPC's information inventory.** If the PC "knows" Kael is in the ring, and Kael's Information Inventory says "nobody outside the ring knows" — that's consistent (PC IS ring). But if the PC "knows" Sera has unreported evidence, and Sera's profile says "nobody knows" — that's a knowledge graph violation.
The schema validator (Tyre's pipeline) should check this:
```
For each (target, attribute) in pc_upgrade.starting_knowledge.entities:
assert target NPC exists in district roster
assert attribute values are consistent with target NPC's Information Inventory
For each (observer, attribute) in pc_upgrade.known_by:
assert observer NPC exists in district roster
assert observer's Information Inventory is compatible with their knowing these attributes about PC
```
---
## 3. FRIEND Pool: Size and Cost
Nigel wants the FRIEND drawn from a pool. Gore agrees but insists THE FRIEND is never procedurally generated — the content is authored, the selection is randomized. I agree with both.
### Pool Size Analysis
| Pool Size (per character) | Total Tier 1 NPCs | Additional Authored Lines | Replayability Multiplier | v0.1 Feasible? |
|---|---|---|---|---|
| 1 (current: Kael, Sera) | 2 | 0 | 1x (no variation) | Yes (done) |
| 2 per character | 4 | ~140-200 | 4x (2 smuggler x 2 detective) | Stretch |
| 3 per character | 6 | ~280-400 | 9x | v0.2 target |
### The Graceful Degradation Insight
Here's what makes the pool work without exploding content costs: **FRIEND candidates that aren't selected as FRIEND still exist as excellent Tier 2 NPCs.**
If Kael is selected as the smuggler's FRIEND, Renn (the other candidate) is just a courier — a well-written one with a rich profile, but his contradiction arc isn't activated by the storyteller. His Phase 1 warmth exists. His tells exist. His secret exists. But the storyteller doesn't trigger the "observe contradiction in restricted area" event for Renn.
This means:
1. Every FRIEND candidate is always present in the world at full Tier 1 quality
2. Only the SELECTED FRIEND gets the contradiction arc ACTIVATED
3. The unselected candidates are the best Tier 2 NPCs in the district — richer than normal because they were written at Tier 1 depth
4. On replay, the player may encounter an NPC they KNOW has a secret (from a previous playthrough where that NPC was FRIEND) but whose arc isn't active. Gore's dramatic irony!
### Authoring Cost Per Additional FRIEND Candidate
Let me be precise about what "one additional FRIEND candidate" costs:
| Deliverable | Lines/Pages | Time Estimate |
|---|---|---|
| Full Tier 1 profile (10-axis, all sections) | ~2 pages | 3-4 hours |
| 70-100 authored dialogue/monologue lines | 70-100 lines | 8-12 hours |
| Contradiction arc (5 phases) | Part of profile | Included above |
| Tell progression (3-4 stages) | Part of profile + lines | Included above |
| Dual-lens notes for BOTH characters | ~0.5 pages | 1-2 hours |
| PC Upgrade appendix | ~0.5 pages YAML | 1-2 hours |
| Reverse knowledge entries from ALL NPCs | ~0.25 pages YAML | 1 hour |
| Cross-character authored relationship stubs (FRIEND↔PC) | 5-10 lines | 1-2 hours |
| **Total per candidate** | **~3 pages + 70-100 lines** | **~15-22 hours** |
### v0.1 Recommendation
**Ship with 1 FRIEND per character (Kael, Sera).** But architect the pool system from day one:
- The `templates/` YAML includes a `friend_pool` field listing eligible NPC IDs
- The seed selects from this pool at game start
- v0.1 pool has 1 entry per character — functionally identical to current hardcoded behavior
- v0.2 adds second candidates without any architectural change
```yaml
# In templates/logistics-hub.yaml (or a district-level config)
friend_pools:
smuggler:
- npc: kael_davan
contradiction_module: kael_exit_attempt
# v0.2: add renn or other candidate here
detective:
- npc: sera_venn
contradiction_module: sera_concealment
# v0.2: add second candidate here
```
### Gore's Constraint: Authored Relationship Depth
Gore correctly insists the PC-FRIEND relationship needs authored warmth, not just `state: Friendly`. Each FRIEND candidate needs specific relationship stubs for EVERY possible PC upgrade candidate:
- If the pool has 2 smuggler FRIENDs (Kael, Renn) and the PC could be any of 2-3 smuggler archetypes, we need: Kael↔PCa, Kael↔PCb, Renn↔PCa, Renn↔PCb relationship stubs. Each stub is 5-10 authored lines of shared history.
This is a combinatorial cost. At pool size 2x2: 4 stubs (20-40 lines). At 3x3: 9 stubs (45-90 lines). Manageable but worth tracking.
---
## 4. Triangle 1 Fix: The Nils-vs-Voss Escalation Decision
Paula and I both flagged Hub Power (T1) as the weakest triangle. The core problem: Nils is off-stage, so the smuggler never mediates a real-time confrontation. The triangle describes tension but doesn't produce a FORK.
### The Decision Moment: Volume Escalation
**Setup (storyteller-triggered, mid-game):**
Nils sends word through Renn that the next shipment is double volume. A connected system has a supply window — miss it and the opportunity is gone for months. Voss refuses to create the necessary schedule gap: "Two workers short on the same shift twice in a row? Maret will flag it. I won't do it."
Both sides appeal to the smuggler.
### The Fork
| Option | Action | Immediate Effect | Cascade to T2 | Cascade to T5 | Detective Observability |
|---|---|---|---|---|---|
| **A: Side with Nils** | Tell Voss to make it work, or work around Voss | Voss creates gap resentfully, does it clumsily | Maret notices the schedule anomaly more easily → T2 heats up | Pell sees higher volume, more risk → Pell's wavering accelerates | Schedule anomaly is louder signal. Detective monologue: "Third schedule adjustment this cycle. Pattern." |
| **B: Side with Voss** | Tell Renn to relay: "Not this cycle" | Nils is displeased, questions smuggler's loyalty | Voss is relieved, cooperates more → T2 stays cool | Ring internal tension rises from above → different pressure path on Pell | Schedule looks clean. Detective has less to work with. Smuggler's ring standing drops. |
| **C: Split the route** | Propose half-volume through normal route, half through untested alternative | Partial success. New route = new exposure surface | Maret sees half the anomaly (borderline noticeable) | Ring members stretched thinner → more behavioral tells across the board | Activity in unexpected area. Detective monologue: "Cargo movement in Section C? That's not on any manifest." |
### Mechanical Expression
```yaml
# In templates/logistics-hub.yaml or a storyteller module
decision_node:
id: volume_escalation
trigger: storyteller_mid_game # fires between minute 12-18
setup:
message_from: nils_davan # off-stage, delivered via renn
message_text: "Double volume. Next cycle. Make it happen."
voss_response: "I won't short the schedule again. Maret's watching."
options:
- id: side_with_nils
label: "Tell Voss to make it work"
effects:
- npc: voss
attribute_change: { trust_level: "resentful", exposure_risk: "escalating" }
- npc: maret_korr
behavior_flag_add: "noticed_schedule_gap"
- triangle: worried_knowledge
tension_increase: 2
- triangle: informant_question
tension_increase: 1
- investigation_signal: "schedule_anomaly_visible"
- id: side_with_voss
label: "Push back: not this cycle"
effects:
- npc: voss
attribute_change: { trust_level: "relieved", cooperation: "improved" }
- player:
attribute_change: { loyalty_assessment_by_nils: "questioned" }
- triangle: worried_knowledge
tension_increase: 0
- triangle: informant_question
tension_increase: 1 # pressure comes from above instead
- investigation_signal: null # clean schedule, less detective evidence
- id: split_route
label: "Split: half normal, half new route"
effects:
- npc: voss
attribute_change: { trust_level: "uncertain" }
- location: section_c
flag: "unexpected_cargo_activity"
- triangle: worried_knowledge
tension_increase: 1
- triangle: informant_question
tension_increase: 1
- investigation_signal: "new_route_observable"
```
### Why This Fixes T1
1. **The smuggler CHOOSES.** Not "caught between" — actively deciding which side to favor. Real consequence, can't have both.
2. **Cascading consequences.** Each choice ripples into T2 and T5. The detective's experience changes based on which option the smuggler picked (in NPC mode, the AI picks based on the NPC's personality/loyalty balance).
3. **Nils is present-by-proxy.** The message arrives through Renn. Nils doesn't need to be physically present — the DEMAND is present. The smuggler's response defines their relationship with an off-stage power.
4. **Detective observability differs per choice.** Option A is loudest (easiest for detective). Option B is quietest. Option C creates a new thread. This means the smuggler's choice shapes the detective's investigation difficulty on replay.
### Additional Fix: Nils Partial Presence
I still recommend one brief Nils comm interaction in v0.1 — even a text-only lattice message that the smuggler receives and must respond to. This costs ~5-8 authored lines and makes the off-stage vertex feel real. The response choices could mirror or foreshadow the volume escalation decision.
---
## 5. Routine Format + Cultural Patterns
My Round 1 proposed a structured `routine:` YAML block. Miri's Krenn brief introduces cultural patterns that the routine should accommodate: shift-end bar culture, kuum (transition drink), kolm (card game), 5+2 work cycles.
### The Problem
The engine needs parseable schedule data (time, location, activity). Cultural flavor is for writers, not parsers. If I embed `cultural_note` fields in the YAML, I'm mixing concerns — the schedule system ignores them, and writers have to read through engine data to find authoring guidance.
### Solution: Two Layers
**Layer 1: Engine-readable routine (YAML)**
```yaml
routine:
cycle: "5+2" # work days / rest days
schedule:
work_day:
- time: "06:00-06:30"
location: terminal
activity: shift_startup
- time: "06:30-10:30"
location: terminal
activity: work
- time: "10:30-11:00"
location: terminal_breakroom
activity: break
- time: "11:00-14:00"
location: terminal
activity: work
- time: "14:00-14:30"
location: terminal
activity: shift_end
- time: "14:30-16:00"
location: bar
activity: social
- time: "16:00-22:00"
location: residential
activity: home
rest_day:
- time: "09:00-12:00"
location: residential
activity: home
- time: "12:00-14:00"
location: bar
activity: social
- time: "14:00-22:00"
location: residential
activity: home
deviations:
- trigger: ring_operation
replaces: "22:00+"
location: corridor_b7
frequency: "1-2x per week"
- trigger: volume_escalation # the T1 decision node
replaces: "14:00-14:30"
location: corridor_b7
frequency: "once (event-driven)"
```
**Layer 2: Cultural context (prose, in the wiki NPC profile)**
Under the Daily Routine section, AFTER the YAML block:
> **Cultural context (Krenn System):**
> - Kael's shift startup involves grabbing leib from the canteen and eating standing — Krenn workers don't sit for breakfast
> - Break at 10:30 is social: lunch with the smuggler (if applicable), shared kalaa and supp
> - "Shift-end" at the bar means ordering kuum first (the spiced transition drink), then switching to grain spirit if staying. Kael usually shifts to the bar 2-3 days per work cycle
> - Hael expects Kael home by 17:00 on non-bar days. Late arrivals trigger Hael's worry axis
> - Ring operations happen during night shift transitions (22:00-23:00 window) — Kael uses "overtime" as cover to Hael
This gives writers everything they need for dialogue and monologue ("Kael orders kuum" → Mellanie knows what that is from Miri's brief) while the engine reads only the clean YAML schedule.
### How This Works With Miri's Specific Cultural Patterns
| Miri's Pattern | Where It Lives | Who Uses It |
|---|---|---|
| 5+2 work cycle | `routine.cycle: "5+2"` in YAML | Schedule system |
| Shift-end bar migration | `schedule.work_day` entry with `location: bar` after shift | Schedule system + monologue triggers |
| Kuum as transition drink | Cultural context prose + Mellanie's menu + dialogue tags | Writers (not engine) |
| Kolm card game timing | `schedule.rest_day` entry at bar, OR deviation with `trigger: kolm_night` | Schedule system + event system |
| Leib/kalaa food | Cultural context prose | Writers (environmental text, dialogue flavor) |
| Greeting conventions (no verbal greeting, chin-lift) | NOT in routine — in Miri's regional brief | Writers (dialogue `notes` field) |
### Style Guide Mandate
Every NPC routine section in the wiki should contain:
1. **YAML block** — engine-readable, schema-validated, per-tier minimum entries:
- Tier 3: 3+ time-location entries (minimum: work, social, home)
- Tier 2: 5+ entries with `work_day` and `rest_day` split
- Tier 1: 7+ entries with deviations specified
2. **Cultural context paragraph** — prose annotations for writers, referencing the regional brief
3. **Deviation list** — what pulls the NPC off their routine, with triggers and frequencies
The YAML block is the mechanical requirement. The cultural context is the authoring aid. Both are mandatory; neither replaces the other.
---
## Cross-Cutting: Paula's Smuggler Attribute Vocabulary
Paula proposed 5 smuggler-specific entity attribute keys. This is a strong proposal that I want to endorse and refine, because it directly addresses the "smuggler content pipeline is under-served" problem she identified.
### Endorsed Keys (with mechanical integration)
| Key | Values | What Drives It | What It Gates |
|---|---|---|---|
| `operational_reliability` | `reliable`, `slipping`, `compromised`, `unknown` | NPC behavior + ring event outcomes | Smuggler monologue tone, ring assignment willingness |
| `exposure_risk` | `low`, `escalating`, `critical` | Tells + detective proximity + NPC behavior | Smuggler tension monologue, ring operational decisions |
| `loyalty_assessment` | `solid`, `uncertain`, `wavering`, `turning` | Observation of NPC behavior over time | Smuggler dialogue options (confront vs. reassure), ring strategy |
| `leverage_held` | Freeform string (e.g., `"gambling debt"`, `"family secret"`) | Discovery through observation or conversation | Confrontation/pressure dialogue unlocks |
| `social_debt` | `they_owe_me`, `i_owe_them`, `mutual`, `none` | Relationship history (authored per NPC) | Favor-asking dialogue, cooperation likelihood |
These give the smuggler's knowledge model parity with the detective's investigation attributes. The detective has `tell_observed`, `contradiction_flagged`, `secret_held`, `secret_confidence`. The smuggler now has `operational_reliability`, `exposure_risk`, `loyalty_assessment`, `leverage_held`, `social_debt`.
Both characters build profiles of NPCs. The detective's profile is investigative. The smuggler's profile is operational/social. Different lenses on the same people — which is exactly the game's thesis.
**Implementation note for Tyre:** These 5 keys go into the entity-attributes schema alongside the existing 14. The `known_attributes` BTreeMap already supports arbitrary keys — no code change needed, just schema expansion and content authoring.
---
## Summary: Decisions for Recording
| # | Decision | Type | Status |
|---|---|---|---|
| 1 | Converged content directory structure (Gestalt+Tyre) | Architecture | **Propose** |
| 2 | District-specific facts split from global facts | Architecture | **Propose** |
| 3 | Mod overlay: ADD/REPLACE/MERGE + weight tagging | Architecture | **Propose** |
| 4 | District (not template) as atomic content pack | Architecture | **Propose** |
| 5 | PC-candidate NPC spec: 12 required + 6 optional + 6 PC-only fields | Content spec | **Propose** |
| 6 | Reverse knowledge (bidirectional) as mandatory PC upgrade field | Content spec | **Propose** |
| 7 | FRIEND pool architecture: v0.1 = 1 per character, system supports N | Content pipeline | **Propose** |
| 8 | Volume Escalation decision node for Triangle 1 | Content design | **Propose** |
| 9 | Two-layer routine format: YAML (engine) + prose (writers) | Style guide | **Propose** |
| 10 | 5 smuggler-specific entity attribute keys (Paula's proposal, endorsed) | Knowledge model | **Propose** |