System S-057 assigned to real star GJ 35 (Van Maanen's Star, DG white dwarf at 13.9 ly). Renamed across all content, server code, docs, decisions, wiki lore, and config files. 224 files updated. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
29 KiB
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/
van-maanens-star-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 Van Maanen's Star 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:
# 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:
# 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_davanas 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:
# _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
# 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:
# 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 Van Maanen's Star 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/van-maanens-star-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:
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/van-maanens-star-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:
### Cultural Groups (Regional Style Briefs)
- [Van Maanen's Star](cultural-groups/van-maanens-star-system.md) — Baltic-Nordic blend, logistics culture
canonical_id — endorsed with format refinement
Miri proposed: canonical_id: van-maanens-star.sova.transit.the-terminal. I'd standardize the format:
# Format: {system}.{station}.{district}.{entity-type}.{slug}
# Examples:
canonical_id: van-maanens-star.sova.transit.location.the-terminal
canonical_id: van-maanens-star.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 |
|---|---|
van-maanens-star.sova.transit.npc.kael-davan |
content/districts/sova-transit/npcs/kael-davan.yaml |
van-maanens-star.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/van-maanens-star/station-sova/sova-transit-district/the-terminal.md
The current wiki uses 2 levels: locations/van-maanens-star-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
# 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/van-maanens-star-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
wiki/index.md: Add Cultural Groups section, add Thematic Core section (Gore's proposal)wiki/knowledge/entity-attributes.md: Add 5 smuggler-specific attribute keyswiki/knowledge/fact-catalog.md: Add smuggler-perspective progression text for all FactIds where smuggler starts at KnowsDetailswiki/authoring/monologue-guide.md: Standardize location shortcodes (terminal_m_nothub_m_); add situation overlap rules, mood exclusivity rules- NPC profiles (all): Add canonical full names for single-name NPCs; add
## Thematic Questionto Tier 1 template (Gore)
New tickets for SI
- Create
_schema/directory with schema definitions from Gestalt's tables - Implement
make validate-contentCLI (structural + tier-conditional) - Create
content/directory skeleton matching the unified structure above - Create
pools.yamltemplate with v0.1 single-candidate pools - Create
global/enums/YAML files from D-035 enum values - Add
global/knowledge/entity-attributes.yamlwith 19 canonical keys (14 existing + 5 smuggler) - Create
templates/YAML files with role slot definitions for 3 social sites - Create
triangles/YAML files for 5 v0.1 triangles - Stub NPC profiles for Nils Davan (off-stage, referenced in 5+ profiles)
- 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.