Files
settled-reach/docs/workshops/wiki-review/round2-tyre.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

641 lines
29 KiB
Markdown

# Wiki Review Workshop — Round 2: Tyre (Technical Architect)
Cross-pollination synthesis. Responding to all Round 1 outputs with architectural reconciliation.
---
## 1. Unified Content Directory Structure
Gestalt proposed `_global/` + `districts/`. I proposed `_meta/` + `_schema/` + `global/` + `districts/`. The structures are 90% aligned. The 10% divergence matters because it signals moddability.
**The principle:** Underscore prefix = engine infrastructure (modders don't touch). No underscore = game content (modders override freely). Factions, technology, and contraband are game content. Schemas and manifests are infrastructure.
### Reconciled Structure
```
content/
_meta/
manifest.yaml # Pack metadata: id, version, dependencies
load-order.yaml # Explicit ordering for multi-pack scenarios
_schema/
npc.schema.yaml # Per-type validation (see Section 3)
location.schema.yaml
faction.schema.yaml
template.schema.yaml
fact.schema.yaml
dialogue.schema.yaml
monologue.schema.yaml
district.schema.yaml
pool.schema.yaml # NEW: Pool definition validation
global/
factions/
concord-assembly.yaml
lattice-commission.yaml
syndics.yaml
the-ring.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 # All FactId definitions (24 for v0.1)
entity-attributes.yaml # 14 canonical EntityKnowledge keys
relationship-states.yaml # RelationshipState enum reference
enums/
situations.yaml # 13 situation values (from Gestalt's _global/schema/)
topics.yaml # 9 topic values
moods.yaml # 8 mood values
triggers.yaml # 9 monologue trigger types
access-tiers.yaml # public/peer/insider/authority/hostile
activities.yaml # NEW: canonical routine activity enum
regions/
krenn-system.yaml # Regional style reference (from Miri's brief)
districts/
sova-transit/
district.yaml # District metadata, location list, faction presence
pools.yaml # NEW: Pool definitions for seed-time selection
npcs/
kael-davan.yaml
sera-venn.yaml
voss.yaml
lera-sessik.yaml
torek-lintar.yaml
devra.yaml
maret-korr.yaml
resha.yaml
hael.yaml
renn.yaml
pell.yaml
harek.yaml
drin.yaml
sess.yaml
olin.yaml
sabel.yaml
tav.yaml
locations/
the-terminal.yaml
the-last-shift.yaml
maintenance-corridors.yaml
templates/
logistics-hub.yaml # Social site: roles, slot counts, NPC assignments
bar.yaml
smuggling-ring.yaml
triangles/ # NEW: Separated from templates (Gestalt's proposal)
hub-power.yaml
worried-knowledge.yaml
bar-tensions.yaml
worried-partner.yaml
informant-question.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
```
### What changed from Round 1
| Change | Source | Rationale |
|---|---|---|
| `global/enums/` added | Gestalt's `_global/schema/` tag enums | Enum definitions are game content (modders can extend), not schema infrastructure |
| `triangles/` separated from `templates/` | Gestalt's Round 1 structure | Triangles are relationship structures, templates are spatial/social structures. Separation lets mods add triangles without touching templates. |
| `pools.yaml` added | Nigel's `pool_eligible` concept + Gore's FRIEND pool | Seed-time selection needs explicit pool definitions (see Section 2) |
| `global/enums/activities.yaml` added | Gestalt's routine YAML format | Routine `activity` values need a canonical enum (see Section 4) |
| `global/regions/` added | Miri's Krenn brief | Regional style data as engine-readable YAML, not just wiki docs |
| Underscore-prefix convention formalized | Tyre Round 1, refined | Clear signal: `_foo` = infrastructure, `foo` = content |
### Location shortcodes (Mellanie's dependency)
Mellanie correctly identified that location shortcodes are ambiguous (`hub` vs `terminal`, `bar` vs `last_shift`). The canonical shortcodes are the directory names under `lines/`:
| Location | Wiki slug | Content directory | Line ID prefix | Rationale |
|---|---|---|---|---|
| The Terminal (Logistics Hub) | `the-terminal` | `lines/terminal/` | `terminal_` | "Terminal" is what workers call it. Not "hub." |
| The Last Shift (Bar) | `the-last-shift` | `lines/bar/` | `bar_` | "Bar" is the functional category. "Last Shift" is the proper name. Line IDs use function. |
| Maintenance Corridors / B-7 | `maintenance-corridors` | `lines/corridor/` | `corridor_` | Workers call it "B-7" or "the back käik." Line IDs use generic. |
One name per location. Enforced in schema validation. The monologue guide should be updated to use these consistently — the current `hub_m_010` vs `terminal_m_001` inconsistency is a bug. `terminal_m_` is canonical.
---
## 2. FRIEND Pool + Mod Overlay: Seed-Time Selection
Gore's principle is correct: THE FRIEND is never procedurally *generated* — but FRIEND *selection* from a pool of authored candidates is the right architecture for replayability. Nigel's `pool_eligible` flag is the mechanism.
### How it works architecturally
**`districts/sova-transit/pools.yaml`:**
```yaml
# Pool definitions for seed-time selection
# Engine draws from these at game start using seeded RNG
pools:
# THE FRIEND selection — exactly 1 per character per playthrough
friend_smuggler:
description: "Smuggler's Tier 1 FRIEND NPC"
select: 1
candidates:
- npc: kael-davan # v0.1: only candidate
weight: 1.0
# v0.2+: additional candidates added here
# - npc: renn
# weight: 1.0
friend_detective:
description: "Detective's Tier 1 FRIEND NPC"
select: 1
candidates:
- npc: sera-venn # v0.1: only candidate
weight: 1.0
# Social site population — variable count per seed
bar_regulars:
description: "Regular NPCs present at The Last Shift"
select: 3-5
required: [torek-lintar] # Always present (triangle dependency)
candidates:
- npc: torek-lintar
weight: 1.0
- npc: olin
weight: 1.0
- npc: harek
weight: 0.8
# Mods add candidates here via MERGE
# Contraband type (Nigel's moral shuffle)
primary_contraband:
description: "Primary contraband type for this playthrough"
select: 1
candidates:
- type: lattice-components
weight: 1.0
# v0.2+: additional types
# - type: medical-grade-replacements
# weight: 0.8
# - type: severance-equipment
# weight: 0.5
# Role assignment (Nigel's network shuffle)
compromised_inspector:
description: "Which NPC is the compromised inspector"
select: 1
candidates:
- npc: drin
weight: 1.0
# v0.2+: additional candidates
```
### How mods extend pools
A mod adds candidates via the MERGE mechanic. The mod provides `districts/sova-transit/pools.yaml` with only the pools it extends:
```yaml
# Mod: jax-the-veteran
# File: districts/sova-transit/pools.yaml
pools:
bar_regulars:
candidates:
- npc: jax-korrenson
weight: 0.8
```
The engine merges this with the base pool definition. `jax-korrenson` is now a candidate for bar regular slots. The `select: 3-5` and `required: [torek-lintar]` from the base remain unchanged.
### Seed-time resolution
```
1. Engine loads all pool definitions (base + DLC + mods, merged)
2. Initialize seeded RNG from game seed
3. For each pool:
a. Include all `required` candidates
b. Draw remaining candidates up to `select` count, weighted by `weight`
c. Non-selected candidates are either:
- Absent from the district (pool_eligible NPCs with no other role)
- Present but demoted to background (if they have non-pool roles)
4. Instantiate selected NPCs with full content
5. Validate: all triangle dependencies satisfied (all triangle members present)
6. If validation fails, re-draw with constraint satisfaction
```
### FRIEND pool + content loading implications
When `friend_smuggler` selects `kael-davan`, the engine loads:
- `npcs/kael-davan.yaml` (full 10-axis Tier 1 data)
- All monologue lines with `subject: kael_davan` as a FRIEND prerequisite
- The specific contradiction arc content
When (in v0.2+) it selects `renn` instead, it loads Renn's Tier 1 data and Renn's contradiction arc content. **Both sets of content exist on disk simultaneously.** The pool selects which one activates.
This means content storage is larger than content runtime. A district with 3 FRIEND candidates stores 3x the FRIEND content, but only 1x runs per playthrough. That's cheap — YAML text is tiny. The expensive resource is *authoring time*, not disk space. Gore's point stands: each FRIEND candidate is ~70-100 hand-authored lines.
### v0.1 scope
For v0.1, every pool has exactly 1 candidate. No randomization — deterministic. But the pool INFRASTRUCTURE exists. v0.2 adds candidates without restructuring. This follows the D-009/D-010 principle: design for it now, build the simple version.
**Feasibility: Easy.** Pool definition parsing is a YAML file read. Seed-time selection is a weighted random draw. Constraint validation is a graph check. Total implementation: <0.5 sprint on top of the base content loader.
---
## 3. Schema Validation: Gestalt's Tables as Validators
Gestalt's hard/soft requirement tables from Round 1 are exactly what I need. Let me map them to the validation pipeline.
### Gestalt's hard requirements → JSON Schema `required` fields
| Gestalt requirement | Schema enforcement |
|---|---|
| Routine must specify hourly location presence | `npc.schema.yaml`: `routine.schedule` is `required`, items must have `time`, `location`, `activity` |
| Routine must reference canonical location slugs | Cross-reference validation: `location` value must exist in `districts/{district}/locations/` |
| Every NPC must have `faction` from canonical set | `npc.schema.yaml`: `faction` is `required`, `enum` from `global/enums/factions.yaml` |
| Every NPC must have `role` | `npc.schema.yaml`: `role` is `required`, `type: string` |
| Secret must map to FactId | Cross-reference validation: `secret.fact_id` must exist in `global/knowledge/facts.yaml` |
| Tell must map to `behavior_flags` value | `npc.schema.yaml`: each tell entry requires `flag` field |
| Contradiction must specify `contradiction_flagged` value | Tier 1 conditional: if `tier == 1`, `contradiction.flag` is `required` |
| Relationships must reference existing NPCs | Cross-reference validation: all `npc` references resolve within district |
### Gestalt's soft requirements → Schema warnings (non-blocking)
| Gestalt requirement | Validation behavior |
|---|---|
| Personality traits should use established vocabulary | Warning if trait not in `global/enums/personality-traits.yaml` |
| Voice samples should cover 2+ moods | Warning if `voice_sample` array length < tier-minimum |
| Information inventory must distinguish knows/doesn't-know | Warning if `information.knows` or `information.unknown` is empty |
| Contentment should use high/moderate/low vocabulary | Warning if value not in `{high, moderate-high, moderate, moderate-low, low}` |
### Per-tier validation (Gestalt's checklists)
Rather than separate schema files per tier, I'd use a single schema with conditional validation:
```yaml
# _schema/npc.schema.yaml (simplified)
type: object
required: [id, name, tier, role, faction, axes]
properties:
tier:
type: integer
enum: [1, 2, 3]
axes:
type: object
required: [want, routine, personality] # Tier 3 minimum
# Conditional: Tier 2+ requires all 10 axes
# Conditional: Tier 1 requires contradiction arc
allOf:
- if:
properties: { tier: { const: 2 } }
then:
properties:
axes:
required: [want, secret, relationships, tolerance, routine,
information, contentment, personality, tell, skills]
voice_sample:
minItems: 2
- if:
properties: { tier: { const: 1 } }
then:
properties:
axes:
required: [want, secret, relationships, tolerance, routine,
information, contentment, personality, tell, skills]
contradiction:
required: [phases, flag, tell_progression]
voice_sample:
minItems: 5
dual_lens:
required: [smuggler, detective]
```
This is one schema file, tier-aware. The validator reads the `tier` field and applies the right constraints.
### Implementation cost (revised from Round 1)
| Component | Effort | Sprint |
|---|---|---|
| Schema definitions (from Gestalt's tables) | 2-3 days | v0.1 |
| Structural validation (`make validate-content`) | 2-3 days | v0.1 |
| Cross-reference validation (FactId, NPC slug resolution) | 3-4 days | v0.2 |
| Per-tier conditional validation | 1-2 days | v0.1 |
| Warning-level soft validation | 1 day | v0.2 |
**v0.1 total: ~1 sprint.** Structural + tier-conditional validation. Cross-reference validation deferred to v0.2 — for v0.1, the human review catches reference errors (only 17 NPCs, 24 FactIds, manageable).
**Mellanie's prerequisite validation** (FactId typo detection) lands in the cross-reference sprint. Agreed it's critical — `contraband.ring_exist` vs `contraband.ring_exists` is the kind of bug that wastes hours. But for v0.1, a simple grep-based pre-commit check is cheaper than a full cross-reference validator. I'd do both: grep check in v0.1, schema cross-reference in v0.2.
---
## 4. Routine YAML Format + Cultural Schedule Patterns
Gestalt's structured routine block is the right format. Miri's cultural brief provides the content vocabulary. Here's how they connect.
### Gestalt's format, canonicalized
```yaml
# In npcs/kael-davan.yaml
routine:
schedule:
- time: "06:00-06:30"
location: terminal # Canonical shortcode
activity: shift_startup # Canonical activity enum
- time: "06:30-10:30"
location: terminal
activity: work_freight
- time: "10:30-11:00"
location: terminal
activity: social_break # Renamed from "social_lunch" for generality
- time: "11:00-14:00"
location: terminal
activity: work_freight
- time: "14:30-16:00"
location: bar
activity: social_offshift # The "shift-end" culture pattern (Miri)
- time: "16:00-22:00"
location: residential
activity: home
- time: "22:00-06:00"
location: residential
activity: sleep
deviations:
- trigger: ring_operation
replaces: "22:00-06:00" # Replaces sleep block
schedule:
- time: "22:00-23:30"
location: corridor
activity: ring_operation
- time: "23:30-06:00"
location: residential
activity: sleep
frequency: irregular # Storyteller-controlled
```
### Canonical activity enum (`global/enums/activities.yaml`)
Derived from Gestalt's examples + Miri's cultural patterns:
```yaml
# global/enums/activities.yaml
activities:
# Work activities
- shift_startup # Arriving, checking in, prep
- work_freight # Core logistics work
- work_maintenance # Maintenance/repair tasks
- work_admin # Office/scheduling work (Maret, Voss)
- work_inspection # Inspection rounds (Drin)
- work_commission # Commission field work (Sera)
# Social activities
- social_break # On-shift break (lunch, kuum)
- social_offshift # Post-shift bar visit (the "shift-end" pattern)
- social_evening # Evening socializing (card games, conversation)
- social_errand # Off-shift personal tasks
# Domestic
- home # At residential quarters
- sleep # Sleeping (low interaction priority)
# Ring activities (deviation-only, never in base schedule)
- ring_operation # Active smuggling work
- ring_meeting # Coordination with ring members
- ring_lookout # Watchpost duty (Tav)
# Special
- idle # No scheduled activity
- patrol # Security rounds (Harek on duty)
```
### How Miri's cultural patterns inform schedule authoring
Miri's brief describes the **5+2 work cycle** and **shift-end bar migration** as Krenn cultural patterns. These aren't engine data — they're authoring guidance:
| Cultural pattern | Schedule implication | Authoring rule |
|---|---|---|
| 5+2 work cycle | NPCs work 5 shifts, then 2 off | Routine must define both on-cycle and off-cycle days (v0.2+, when multi-day cycles matter) |
| Shift-end bar migration | After shift, workers go to The Last Shift | Most Tier 2-3 hub NPCs should have `social_offshift` at `bar` |
| Kuum at transitions | Hot drink at shift boundaries | `social_break` activity at shift start/end = where the casual dialogue fires |
| Card game evenings | Kolm at the bar, Harek's regular game | Harek + 2-3 regulars with `social_evening` at `bar` overlapping |
The cultural brief lives in `global/regions/krenn-system.yaml` as reference data. It doesn't drive the engine directly — it drives the AUTHORS who fill in the routine YAML.
### Engine consumption
The `ScheduleSystem` in the Rust server reads the routine YAML as:
```rust
struct ScheduleEntry {
start_tick: u64, // Converted from time string
end_tick: u64,
location: LocationId, // Resolved from shortcode
activity: Activity, // Enum from activities.yaml
}
struct NpcSchedule {
base: Vec<ScheduleEntry>,
deviations: Vec<ScheduleDeviation>,
}
```
Time strings ("06:00-14:00") are converted to tick ranges at load time using D-031's mapping (10 ticks = 1 game-minute). `location` shortcodes resolve against the district's location registry. `activity` values are validated against the canonical enum.
**Deviation triggers** (like `ring_operation`) are fired by the storyteller system, not the schedule system. The schedule system just needs to know "when trigger X fires, replace block Y with schedule Z." The storyteller decides *when* to fire the trigger.
Feasibility: **Straightforward.** This is a data-driven schedule with event-driven overrides. bevy_ecs handles this naturally — `NpcSchedule` is a component, the `ScheduleSystem` runs every tick, checks current time against entries, and moves NPCs. Deviations are applied when the storyteller emits the trigger event.
---
## 5. Wiki Taxonomy + Miri's `canonical_id`
Miri proposed two things I want to address: `wiki/cultural-groups/` as a new directory, and `canonical_id` in YAML frontmatter for collision-safe cross-referencing.
### `cultural-groups/` — fits perfectly
`wiki/cultural-groups/krenn-system.md` is a flat directory. No nesting. Exactly like `factions/`, `technology/`, `contraband/`. This is a new top-level wiki category, not a nesting violation. Add it.
The wiki index should add:
```markdown
### Cultural Groups (Regional Style Briefs)
- [Krenn System](cultural-groups/krenn-system.md) — Baltic-Nordic blend, logistics culture
```
### `canonical_id` — endorsed with format refinement
Miri proposed: `canonical_id: krenn.sova.transit.the-terminal`. I'd standardize the format:
```yaml
# Format: {system}.{station}.{district}.{entity-type}.{slug}
# Examples:
canonical_id: krenn.sova.transit.location.the-terminal
canonical_id: krenn.sova.transit.npc.kael-davan
canonical_id: global.faction.lattice-commission
canonical_id: global.technology.neural-lattice
```
Adding the entity type segment prevents collisions between, say, a location and an NPC with the same slug. It also makes the ID self-describing — you can parse the type from the ID without reading the file.
For the content directory, the canonical_id maps to file paths:
| `canonical_id` | Content path |
|---|---|
| `krenn.sova.transit.npc.kael-davan` | `content/districts/sova-transit/npcs/kael-davan.yaml` |
| `krenn.sova.transit.location.the-terminal` | `content/districts/sova-transit/locations/the-terminal.yaml` |
| `global.faction.lattice-commission` | `content/global/factions/lattice-commission.yaml` |
The mapping is derivable — content loader can resolve `canonical_id` to file path and vice versa. Cross-references in content YAML use `canonical_id`, not file paths. This is Miri's proposal, architecturally validated.
### Miri's deeper nesting — gentle pushback
Miri's Round 1 shows a wiki structure with 4 levels of spatial nesting:
```
wiki/star-systems/krenn/station-sova/sova-transit-district/the-terminal.md
```
The current wiki uses 2 levels: `locations/krenn-system/the-terminal.md`. I maintain that 2 levels is the right cap for navigation paths. The `canonical_id` carries the full spatial address — the filesystem doesn't need to.
**Compromise:** Keep wiki paths at max 2 levels for locations. Use `canonical_id` in frontmatter for full spatial addressing. The wiki index provides the navigational hierarchy through cross-links, not directory depth. This gives Miri's disambiguation without my tab-completion nightmare.
---
## 6. Template Role Slots (Nigel's Request)
Nigel asked for explicit role slot definitions in social site templates. This is a direct question to me and the answer is: **yes, and here's the spec.**
### Template role slot definition
```yaml
# content/districts/sova-transit/templates/bar.yaml
template:
id: bar
name: "The Last Shift"
location: the-last-shift
role_slots:
owner:
count: 1
required: true # Must be filled every seed
default: lera-sessik # Base game assignment
staff:
count: 1-2
required: true
default: [sess]
regular:
count: 3-5 # Variable per seed
required: false
pool: bar_regulars # References pools.yaml
outsider:
count: 0-2 # May or may not appear
required: false
pool: bar_outsiders
# Triangle integration: which triangles must have all members present
triangle_constraints:
- triangle: bar-tensions
required_members: [lera-sessik, torek-lintar]
# olin can be absent — triangle fires differently without the outsider node
```
This tells the engine: "The bar has 1 owner (always Lera), 1-2 staff (always Sess in v0.1), 3-5 regulars drawn from a pool, and 0-2 outsiders drawn from a pool." The randomizer fills slots. Mods extend the pools.
**Why this matters for mods:** A modder adding Jax doesn't need to know the template internals. They add Jax to the `bar_regulars` pool (via MERGE on `pools.yaml`), and the template's slot system handles placement. The modder never touches `templates/bar.yaml`.
**Why this matters for replayability:** Different seeds populate the bar differently. Playthrough 1 has Torek, Harek, and Olin as regulars. Playthrough 2 has Torek, Harek, and Jax (mod). Playthrough 3 has Torek and Olin only (smaller bar night). The bar feels alive and different each time.
---
## 7. Paula's Smuggler-Specific Attributes
Paula identified a real architectural gap: the `EntityKnowledge.known_attributes` vocabulary is detective-biased. Her proposed smuggler attributes are mechanically sound:
| Key | Values | Engine use |
|---|---|---|
| `operational_reliability` | `reliable`, `compromised`, `wavering` | Smuggler monologue tone, ring operation risk calculation |
| `exposure_risk` | `low`, `escalating`, `critical` | Smuggler stress system, storyteller escalation trigger |
| `loyalty_assessment` | `solid`, `uncertain`, `turning` | Smuggler dialogue access (who to trust with ring talk) |
| `leverage_held` | Free text (e.g., `gambling_debt`) | Smuggler-specific confrontation options |
| `social_debt` | `they_owe_me`, `i_owe_them`, `mutual`, `none` | Social manipulation mechanics |
**Architecture impact: None.** `known_attributes` is a `BTreeMap<String, String>` — any key-value pair works. Adding these keys requires zero engine changes. The content schema and monologue prerequisites just reference the new keys.
**Add these to `global/knowledge/entity-attributes.yaml` and to the wiki's entity-attributes.md.** Total canonical keys goes from 14 to 19. The Rust types don't change — it's all string keys in the BTreeMap.
Feasibility: **Trivial.** The hardest part is writing the monologue lines that use these as prerequisites — and that's Mellanie's job, not an engine task.
---
## 8. Cross-Cutting: What Needs to Happen
Summarizing everything into concrete outputs.
### Decisions to record (new D-entries)
| ID | Decision | Source |
|---|---|---|
| D-042 | Content directory structure: `_meta/` + `_schema/` + `global/` + `districts/` with underscore = infrastructure, no-underscore = moddable content | Tyre R1/R2 + Gestalt R1, reconciled |
| D-043 | `canonical_id` format: `{system}.{station}.{district}.{type}.{slug}` for collision-safe cross-referencing | Miri R1 + Tyre R2 refinement |
| D-044 | Mod overlay mechanics: ADD (new files), REPLACE (entity definitions), MERGE (pool-type content, deduplicate by ID) | Tyre R1 + Nigel R1 pool integration |
| D-045 | Pool-based seed-time selection: FRIEND, social site population, contraband type, role assignment. `pools.yaml` per district. v0.1 ships with 1 candidate per pool; architecture supports N candidates. | Nigel R1 + Gore R1 + Tyre R2 synthesis |
| D-046 | Schema-per-content-type validation with tier-conditional rules. Structural validation in v0.1, cross-reference validation in v0.2. | Gestalt R1 tables + Tyre R1/R2 implementation |
| D-047 | Template role slots: social site templates declare named slots with count ranges, required flags, and pool references. Modders extend pools, engine fills slots. | Nigel R1 request + Tyre R2 spec |
| D-048 | Canonical location shortcodes: `terminal`, `bar`, `corridor`. One name per location, enforced in schema, used in line ID prefixes. | Mellanie R1 dependency + Tyre R2 formalization |
| D-049 | Smuggler-specific entity attributes added to canonical vocabulary: `operational_reliability`, `exposure_risk`, `loyalty_assessment`, `leverage_held`, `social_debt`. Total canonical keys: 19. | Paula R1 proposal + Tyre R2 validation |
### Documents to create
| Document | Owner | Blocks |
|---|---|---|
| `wiki/cultural-groups/krenn-system.md` | Miri | All environmental text authoring |
| Cultural Groups section in `wiki/index.md` | Miri/Qatux | Wiki navigation |
| Location shortcode table | Mellanie/Tyre | All YAML content authoring |
| Freeform tag vocabulary (living list) | Mellanie | Content consistency |
| Environmental text type catalog | Mellanie | v0.1 environmental text authoring |
| Voice cards per playable character | Mellanie | Monologue authoring consistency |
| NPC-format briefs for smuggler + detective PCs | Paula | PC-as-NPC concept |
| Tier 1/2/3 template as standalone style guide | Paula | All future NPC authoring |
### Amendments to existing wiki pages
1. **`wiki/index.md`**: Add Cultural Groups section, add Thematic Core section (Gore's proposal)
2. **`wiki/knowledge/entity-attributes.md`**: Add 5 smuggler-specific attribute keys
3. **`wiki/knowledge/fact-catalog.md`**: Add smuggler-perspective progression text for all FactIds where smuggler starts at KnowsDetails
4. **`wiki/authoring/monologue-guide.md`**: Standardize location shortcodes (`terminal_m_` not `hub_m_`); add situation overlap rules, mood exclusivity rules
5. **NPC profiles (all)**: Add canonical full names for single-name NPCs; add `## Thematic Question` to Tier 1 template (Gore)
### New tickets for SI
1. Create `_schema/` directory with schema definitions from Gestalt's tables
2. Implement `make validate-content` CLI (structural + tier-conditional)
3. Create `content/` directory skeleton matching the unified structure above
4. Create `pools.yaml` template with v0.1 single-candidate pools
5. Create `global/enums/` YAML files from D-035 enum values
6. Add `global/knowledge/entity-attributes.yaml` with 19 canonical keys (14 existing + 5 smuggler)
7. Create `templates/` YAML files with role slot definitions for 3 social sites
8. Create `triangles/` YAML files for 5 v0.1 triangles
9. Stub NPC profiles for Nils Davan (off-stage, referenced in 5+ profiles)
10. Add pre-commit grep check for FactId typos in YAML content
---
*Tyre out. The architecture converges. The content team now has a concrete spec to write against, and the engine team has a concrete loading pipeline to build. Ready for Qatux to record and SI to ticket.*