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>
641 lines
29 KiB
Markdown
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.*
|