From 5fdb7f9c5370d661c5aa8006c9a8c48312a5bf51 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Thu, 12 Feb 2026 17:45:16 +0100 Subject: [PATCH] docs(design): add content directory structure, update entity attributes, stub Nils Davan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wave 1 critical blockers for Sprint 3: - #384: Content directory structure design doc (10 sections + 2 appendices, defines canonical IDs, 8 JSON schemas, 3-tier validation pipeline, migration path) - #374: Update entity-attributes.md (14→16 keys, secret→leverage rename, add 4 role-perspective keys, archetype interpretation note) - #376: Nils Davan off-stage NPC stub (GHOST+HANDLER pattern, lattice messages, Triangle 1 role, relationship to Kael) Co-Authored-By: Claude Opus 4.6 --- docs/design/content-directory-structure.md | 605 +++++++++++++++++++++ docs/wiki/knowledge/entity-attributes.md | 163 +++++- docs/wiki/npcs/nils-davan.md | 110 ++++ 3 files changed, 848 insertions(+), 30 deletions(-) create mode 100644 docs/design/content-directory-structure.md create mode 100644 docs/wiki/npcs/nils-davan.md diff --git a/docs/design/content-directory-structure.md b/docs/design/content-directory-structure.md new file mode 100644 index 000000000..f43ee7217 --- /dev/null +++ b/docs/design/content-directory-structure.md @@ -0,0 +1,605 @@ +# Content Directory Structure — The Settled Reach v0.1 + +**Ticket:** #384 (blocks #385: directory skeleton, #386: schema definitions) +**Status:** Design specification +**Authority:** This document is the source of truth for the `content/` directory layout. The server team implements the directory skeleton (#385) and schema files (#386) against this spec. + +**Decisions referenced:** D-024 (10-axis NPC model), D-027 (vertical slice), D-028 (dialogue architecture), D-032 (monologue partition), D-034 (THE FRIEND), D-035 (tag taxonomy), D-036 (Sova Transit setting), D-049 (YAML format), D-057 (content directory structure) + +--- + +## 1. Overview + +The `content/` directory holds all game content in a structured, validated, mod-compatible layout. It is the canonical runtime format consumed by the Rust/bevy_ecs simulation server. All content files are YAML, validated against JSON Schema definitions at build time and deserialized via serde at load time. + +This document defines: + +- The complete directory tree +- Canonical ID format and naming conventions +- Schema file inventory and purpose +- Validation pipeline (3-tier) +- Migration path from the current wiki (`docs/wiki/`) + +The directory structure is designed so that a district is the atomic content pack unit. Districts can be added, removed, or replaced independently. The structure supports future mod overlay without v0.1 implementation. + +--- + +## 2. Top-Level Layout + +``` +content/ + content.yaml # Manifest: district list, content version, load order + _meta/ # Infrastructure metadata (underscore prefix = not game content) + README.md # Explains _meta and _schema conventions + _schema/ # JSON Schema validation files + npc-profile.schema.json + dialogue-pool.schema.json + monologue-pool.schema.json + district.schema.json + triangle.schema.json + routine.schema.json + location.schema.json + fact-catalog.schema.json + global/ # Cross-district content (not district-scoped) + factions/ # Faction profiles + technology/ # Technology definitions + contraband/ # Contraband item profiles + knowledge/ # Shared FactId definitions, entity attribute enums + enums/ # Shared enum values (situations, moods, topics, access tiers) + regions/ # Star system / station metadata + districts/ # Per-district content packs + sova-transit/ # v0.1 district (D-036) +``` + +### Conventions + +- **Underscore prefix** (`_meta/`, `_schema/`): Infrastructure directories. Not game content. The server content loader skips directories starting with `_` when scanning for content files. +- **`content.yaml`**: The manifest file. Lists enabled districts, content version, and load order. The server reads this first. +- **`global/`**: Content that applies across all districts. Faction definitions, shared enums, fact catalogs, and region metadata live here. +- **`districts/`**: One subdirectory per district. Each district is a self-contained content pack. + +### Manifest Format + +```yaml +# content/content.yaml +version: "0.1.0" +districts: + - id: "sova-transit" + path: "districts/sova-transit" + enabled: true +``` + +The `version` field tracks content schema version. The `districts` list defines load order. Disabled districts are skipped entirely at load time. + +--- + +## 3. District Structure + +Each district directory is the atomic pack unit. It contains everything the server needs to instantiate that district: NPC profiles, locations, dialogue, monologue, routines, and triangle definitions. + +``` +districts/sova-transit/ + district.yaml # District metadata + npcs/ # NPC profile files (one YAML file per NPC) + kael-davan.yaml + sera-venn.yaml + voss.yaml + lera-sessik.yaml + torek-lintar.yaml + devra.yaml + maret-korr.yaml + resha.yaml + naia-tamm.yaml + renn.yaml + pell.yaml + harek.yaml + drin.yaml + sess.yaml + olin.yaml + sabel.yaml + tav.yaml + locations/ # Location definition files (one per location) + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + templates/ # Social site template definitions + triangles/ # Triangle relationship definitions + hub-power.yaml + worried-knowledge.yaml + bar-tensions.yaml + worried-partner.yaml + informant-question.yaml + dialogue/ # Tagged dialogue line pools + the-terminal/ # Subdirectory per location + dock-worker.yaml # One file per template role + shift-supervisor.yaml + scheduler.yaml + new-hire.yaml + courier.yaml + the-last-shift/ + bar-owner.yaml + bartender.yaml + bar-regular.yaml + maintenance-corridors/ + ring-operative.yaml + monologue/ # Tagged monologue line pools (per character) + smuggler/ # Hard partition per D-032 + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml # Location-independent lines + detective/ + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml + routines/ # NPC daily routine schedules + schedules.yaml # All NPC schedules for this district +``` + +### District Metadata + +```yaml +# districts/sova-transit/district.yaml +canonical_id: "krenn.sova.transit" +display_name: "Sova Transit District" +system: "krenn" +station: "sova" +district: "transit" +description: > + A 40-year-old prefab-modular-retrofitted freight logistics hub on Station Sova. + Three social sites: The Terminal (logistics hub), The Last Shift (bar), + and maintenance corridors. +locations: + - "the-terminal" + - "the-last-shift" + - "maintenance-corridors" +npc_count: 17 +``` + +### Subdirectory Rationale + +| Directory | Scoped by | Rationale | +|-----------|-----------|-----------| +| `npcs/` | One file per NPC | NPC profiles are the most frequently edited content. One file per NPC enables parallel authoring and clean diffs. | +| `locations/` | One file per location | Location metadata (name, tiles, sightlines, ambient) is independent of NPC content. | +| `templates/` | One file per social site template | Template definitions (roles, triangle slots, NPC capacity) are structural and rarely change after initial authoring. | +| `triangles/` | One file per triangle | Triangle definitions reference NPCs by canonical ID. Separate files enable independent authoring and review. | +| `dialogue/` | Location > role | Dialogue lines are authored per template role at a specific location. The location subdirectory groups all roles present at that location. This matches the authoring workflow: write all dialogue for The Terminal, then all dialogue for The Last Shift. | +| `monologue/` | Character > location | Hard partition by playable character (D-032). Within each character, one file per location plus a `general.yaml` for location-independent lines. | +| `routines/` | Single file per district | All NPC schedules in one file enables cross-NPC scheduling validation (no two NPCs assigned to the same tile at the same time). | + +--- + +## 4. Canonical ID Format + +``` +{system}.{station}.{district}.{type}.{slug} +``` + +### Examples + +| Canonical ID | Resolves to | +|---|---| +| `krenn.sova.transit.npc.kael-davan` | `districts/sova-transit/npcs/kael-davan.yaml` | +| `krenn.sova.transit.npc.sera-venn` | `districts/sova-transit/npcs/sera-venn.yaml` | +| `krenn.sova.transit.location.the-terminal` | `districts/sova-transit/locations/the-terminal.yaml` | +| `krenn.sova.transit.location.the-last-shift` | `districts/sova-transit/locations/the-last-shift.yaml` | +| `krenn.sova.transit.triangle.hub-power` | `districts/sova-transit/triangles/hub-power.yaml` | +| `krenn.sova.transit.triangle.worried-knowledge` | `districts/sova-transit/triangles/worried-knowledge.yaml` | + +### Type Segment Values + +| Type | Description | +|------|-------------| +| `npc` | NPC profile | +| `location` | Location definition | +| `template` | Social site template | +| `triangle` | Triangle relationship definition | + +### Rules + +1. **Canonical IDs are stable references.** Renaming a file does not change its canonical ID. The `canonical_id` field inside each YAML file is the source of truth. File names are a convenience for human navigation; the loader resolves by `canonical_id`, not by file path. +2. **Canonical IDs are globally unique.** No two content files across any district may share a canonical ID. The build-time validator enforces this. +3. **Slugs use kebab-case.** Lowercase, hyphen-separated: `kael-davan`, `the-terminal`, `hub-power`. +4. **Short-form IDs.** Within content files, NPC references use the short form `npc:{slug}` (e.g., `npc:kael-davan`). Location references use `loc:{district}:{slug}` (e.g., `loc:sova-transit:the-terminal`). These short forms are unambiguous within a single district. The full canonical ID is used for cross-district references (future feature). + +--- + +## 5. Schema Specifications + +All schema files live in `content/_schema/`. They use JSON Schema draft 2020-12. Each schema file validates one content type. + +### Schema Inventory + +| Schema File | Validates | Key Constraints | Decision Reference | +|---|---|---|---| +| `npc-profile.schema.json` | `districts/*/npcs/*.yaml` | 10-axis model (want, secret, relationships, tolerance, routine, information, contentment + personality, tells, skills). Tier-conditional fields: `friend_arc` only on FRIEND pattern T1 NPCs. Pattern enum (9 values). Motivation enum (6 values). Access tiers. Trust levels. Triangle membership. | D-024, D-034 | +| `dialogue-pool.schema.json` | `districts/*/dialogue/**/*.yaml` | Role + location scoped. 6 structural tags (id, text, role, access, trust, situation) + 3 selection tags (topic, mood, tags). Knowledge grants per line. Access is a list (multi-tier eligibility). Situation enum: 13 values. Topic enum: 9 values. Mood enum: 8 values. | D-028, D-035 | +| `monologue-pool.schema.json` | `districts/*/monologue/**/*.yaml` | Hard partition by `character` (smuggler/detective, D-032). Trigger enum: 9 types. Prerequisite object (AND-only logic: facts, entity attributes, relationship state). Priority (0-10). Cooldown (ticks). 160 char max per line (display constraint, D-059). | D-032, D-035 | +| `district.schema.json` | `districts/*/district.yaml` | District metadata: canonical_id, display_name, system, station, district slug, description, location list, NPC count. | D-036 | +| `triangle.schema.json` | `districts/*/triangles/*.yaml` | Triangle definition: 3 NPC members (by canonical_id short form), roles within the triangle, fork conditions, resolution states. Self-contained forks (no cross-triangle cascade in v0.1). | D-024, D-047 | +| `routine.schema.json` | `districts/*/routines/*.yaml` | Schedule entries per NPC: phase (Morning/Afternoon/Evening/Night), location, tile coordinates, activity. Deviation entries: trigger condition, override location/tile/activity. Deviations are load-bearing for FRIEND arc spatial staging. | D-034 | +| `location.schema.json` | `districts/*/locations/*.yaml` | Location metadata: canonical_id, display_name, tile bounds, sightline properties, ambient sound reference, social site membership. | D-025, D-036 | +| `fact-catalog.schema.json` | `global/knowledge/*.yaml` | Fact definitions: fact_id, description, discoverable_by (character list), progression (confidence levels with description text), abstract flag (whether fact can reach Direct confidence). | D-035 | + +### Schema Design Principles + +1. **Required fields are minimal.** Only fields the server needs to instantiate an entity are required. Authoring-only fields (`dual_lens`, `notes`) are optional and ignored at load time. +2. **Enum values are defined in schema, mirrored in `global/enums/`.** The JSON Schema files contain the authoritative enum definitions. The `global/enums/*.yaml` files are the human-readable reference and the source for IDE autocomplete. Both must agree; the build-time validator checks this. +3. **Pattern validation on IDs.** Canonical IDs, line IDs, and reference IDs use regex patterns in the schema to catch malformed references at validation time, before the server ever sees them. +4. **Conditional fields.** The `friend_arc` object in `npc-profile.schema.json` is only present on FRIEND-pattern NPCs. The schema uses JSON Schema `if`/`then` for tier-conditional validation where feasible; otherwise, cross-reference validation at build time catches mismatches. + +### Line ID Formats + +| Content Type | ID Pattern | Example | +|---|---|---| +| Dialogue | `{location_slug}_{d}_{###}` | `the-terminal_d_001` | +| Monologue (smuggler) | `{location_slug}_m_s_{###}` | `the-terminal_m_s_001` | +| Monologue (detective) | `{location_slug}_m_d_{###}` | `the-terminal_m_d_001` | +| Examine | `{location_slug}_{e}_{###}` | `the-terminal_e_001` | + +Line IDs are stable. They are never reused, even if a line is deleted. Numbering gaps are expected and acceptable. + +--- + +## 6. Global Content Structure + +``` +global/ + factions/ # Faction profiles (cross-district) + lattice-commission.yaml + syndics.yaml + the-ring.yaml + concord-assembly.yaml + guardians-of-autonomy.yaml + veil-institute.yaml + the-unbound.yaml + technology/ # Technology definitions + contraband/ # Contraband item profiles + knowledge/ # Shared FactId definitions + contraband.yaml # Contraband-related facts + location.yaml # Location-related facts + investigation.yaml # Investigation-related facts + world.yaml # World/setting facts + relationship.yaml # Relationship-related facts + progress.yaml # Progression-related facts + enums/ # Shared enum values + situations.yaml # 13 situation values (D-035) + topics.yaml # 9 topic values + moods.yaml # 8 mood values + access-tiers.yaml # public, insider, authority, peer, hostile + trust-tiers.yaml # surface, real, secret + triggers.yaml # 9 monologue trigger types + patterns.yaml # 9 thematic patterns (System A) + motivations.yaml # 6 functional motivations (System B) + regions/ # Star system / station metadata + krenn.yaml # Krenn System profile (D-036) +``` + +### Global Content Rationale + +| Directory | Contents | Why Global | +|---|---|---| +| `factions/` | One file per faction. Faction name, description, political stance, NPC membership references. | Factions span districts. An NPC in Sova Transit may belong to a faction headquartered elsewhere. | +| `technology/` | Technology definitions relevant to gameplay (lattice types, span gate specs). | Technology is universal across the setting. | +| `contraband/` | Contraband item profiles (unlicensed lattice components, medical-grade replacements, counter-surveillance tech per D-037). | Contraband types are not district-specific; the same items may appear in multiple districts. | +| `knowledge/` | FactId catalog. Each fact has an ID, description, discoverability, and confidence progression text. | Facts are referenced by NPC profiles, dialogue lines, and monologue prerequisites across all districts. The fact catalog is the single source of truth for what can be known. | +| `enums/` | Enum value definitions. One file per enum type. | Enums are shared vocabulary. Dialogue in any district uses the same 13 situations, 9 topics, and 8 moods. | +| `regions/` | Star system and station metadata. | Region data provides setting context for districts. Multiple districts may exist on a single station. | + +### Entity Schema (Attribute Keys) + +The 16 EntityKnowledge keys (D-055) are defined in: + +``` +global/ + knowledge/ + entity-attributes.yaml # 16 canonical EntityKnowledge keys with value enums +``` + +This file defines the attribute key names, their categories (Identity, Spatial, Behavioral, Relational, Role-perspective), allowed value types, and the 4 new role-perspective keys (`risk_assessment`, `loyalty_assessment`, `position_integrity`, `moral_weight`). + +--- + +## 7. File Naming Conventions + +### General Rules + +| Rule | Convention | Example | +|---|---|---| +| Extension | Always `.yaml` (never `.yml`) | `kael-davan.yaml` | +| Case | kebab-case for all filenames | `the-terminal.yaml`, `hub-power.yaml` | +| NPC files | Named by NPC slug | `kael-davan.yaml`, `sera-venn.yaml` | +| Location files | Named by location slug | `the-terminal.yaml`, `the-last-shift.yaml` | +| Triangle files | Named by triangle slug | `hub-power.yaml`, `worried-knowledge.yaml` | +| Dialogue files | Named by template role | `dock-worker.yaml`, `bar-owner.yaml` | +| Monologue files | Named by location (inside character subdirectory) | `smuggler/the-terminal.yaml` | +| General monologue | `general.yaml` for location-independent lines | `smuggler/general.yaml` | +| Enum files | Named by enum type (plural) | `situations.yaml`, `moods.yaml` | +| Faction files | Named by faction slug | `lattice-commission.yaml`, `the-ring.yaml` | +| Fact files | Named by fact category | `contraband.yaml`, `investigation.yaml` | + +### Directory Naming + +- District directories use the district slug: `sova-transit/` +- Dialogue subdirectories use the location slug: `the-terminal/` +- Monologue subdirectories use the character name: `smuggler/`, `detective/` + +### What NOT to Do + +- Do not use CamelCase or PascalCase in filenames. +- Do not use underscores in filenames (underscores are reserved for line ID segments). +- Do not abbreviate names: `maintenance-corridors.yaml`, not `maint-corr.yaml`. +- Do not nest deeper than 3 levels within a district directory. + +--- + +## 8. Mod-Compatible Conventions + +The directory structure is designed to support future mod overlay, where a mod mirrors the same tree and the loader merges mod content with base content. v0.1 does not implement overlay loading, but the structure is ready for it. + +### Design Principles + +1. **District as atomic pack unit.** A mod can add an entirely new district by placing a new directory under `districts/`. No existing files need modification. +2. **Mirrored tree.** A mod that modifies existing content mirrors the exact same directory structure. For example, a mod adding an NPC to Sova Transit would place a file at `mod-name/districts/sova-transit/npcs/new-npc.yaml`. +3. **Canonical IDs prevent collisions.** Because canonical IDs include the system/station/district prefix, mods in different districts cannot accidentally collide. Mods in the same district use a mod-specific prefix convention (future spec). +4. **Global content extension.** A mod can add new factions, facts, or enum values by placing files in `mod-name/global/`. The overlay loader (future) merges these with base global content. + +### v0.1 Scope + +- The directory structure is mod-compatible by design. +- No overlay loader is implemented in v0.1. +- No mod tooling, mod manifest format, or mod loading order is defined in v0.1. +- These are future tickets. The only v0.1 requirement is that the base content structure does not preclude mod overlay. + +--- + +## 9. Validation Pipeline + +Content validation operates at three tiers. Each tier catches different classes of errors. All three must pass for content to be considered valid. + +### Tier 1: Authoring-Time (IDE) + +**Tool:** YAML Language Server + JSON Schema association +**What it catches:** Syntax errors, missing required fields, wrong field types, invalid enum values. + +Configuration: each YAML content file includes a `$schema` comment or the IDE is configured to associate `_schema/*.schema.json` files with the corresponding content directories. + +```yaml +# Example: NPC profile with schema association +# yaml-language-server: $schema=../../_schema/npc-profile.schema.json +canonical_id: "npc:kael-davan" +display_name: "Kael Davan" +# ... +``` + +This tier is optional (not all authors use IDE schema validation) but strongly recommended. It provides instant feedback during authoring. + +### Tier 2: Build-Time (`make validate-content`) + +**Tool:** `tooling/content-tools validate content/` (Rust CLI) +**What it catches:** Everything Tier 1 catches, plus cross-reference errors. + +Build-time validation runs two passes: + +**Pass 1 — JSON Schema validation:** +- Every YAML file in `content/` is validated against its corresponding `_schema/*.schema.json` file. +- File-to-schema mapping is determined by directory location (all files in `districts/*/npcs/` validate against `npc-profile.schema.json`). +- All errors are collected and reported at once (no fail-on-first). + +**Pass 2 — Cross-reference validation:** +- All `canonical_id` references resolve to existing files. +- All FactId references in prerequisites resolve to defined facts in `global/knowledge/`. +- All NPC relationship targets (`npc:{slug}`) resolve to existing NPC profiles. +- All location references (`loc:{district}:{slug}`) resolve to existing location files. +- All triangle member references resolve to existing NPC profiles. +- No duplicate `canonical_id` values across all files. +- Enum values in content files match definitions in `global/enums/`. +- Monologue character partitions are correct (no smuggler lines in detective files, no detective lines in smuggler files). +- Routine schedule locations resolve to existing location files. + +**Exit code:** 0 if all checks pass, non-zero if any errors. CI gate: content changes must pass `make validate-content`. + +### Tier 3: Load-Time (Server Startup) + +**Tool:** Rust `serde_yaml` deserialization + semantic validation in `server/src/content/` +**What it catches:** Type mismatches between YAML and Rust structs (schema drift), semantic errors that require runtime context. + +Load-time validation runs in sequence: + +1. **Deserialization:** `serde_yaml::from_str()` deserializes each YAML file into the corresponding Rust struct. Any type mismatch, missing required field, or unrecognized enum value causes an immediate load failure. This catches schema drift between the JSON Schema definitions and the Rust struct definitions. +2. **StableId assignment:** Deterministic integer IDs are assigned from sorted canonical IDs. This produces repeatable entity IDs across loads. +3. **Relationship wiring:** Canonical ID references (`npc:{slug}`) are resolved to StableIds. Unresolvable references cause load failure. +4. **FriendArc bonding:** FRIEND-pattern NPC profiles are linked to their bonded playable character via StableId. +5. **Schedule validation:** Routine entries are checked for location/tile validity. + +**Failure mode:** The server fails fast on any load-time error. No partial loads. All content must be valid or the server does not start. This is intentional: partial content loads produce subtle, hard-to-debug runtime errors. + +--- + +## 10. Migration Path + +### Current State + +Game content currently lives in `docs/wiki/`, organized as human-readable Markdown files: + +``` +docs/wiki/ + npcs/ # NPC profile pages (Markdown) + factions/ # Faction descriptions + knowledge/ # FactId catalog, entity attributes + locations/ # Location descriptions + contraband/ # Contraband profiles + technology/ # Technology descriptions + world/ # World-building (Krenn System, Sova Station) + authoring/ # Authoring guides and style references + index.md # Wiki index +``` + +### Migration Strategy + +The wiki (`docs/wiki/`) remains the authoring source during v0.1. Authors write and edit in the wiki. The `content/` directory is the runtime format — what the server loads. + +The conversion from wiki Markdown to runtime YAML is a **manual process** during v0.1, tracked as ticket #398 (future). The process: + +1. Author writes/edits NPC profile in `docs/wiki/npcs/kael-davan.md`. +2. Author (or tooling) converts the profile to `content/districts/sova-transit/npcs/kael-davan.yaml`, conforming to `_schema/npc-profile.schema.json`. +3. `make validate-content` confirms the YAML is valid. +4. Server loads from `content/`. + +### Mapping Table + +| Wiki Source | Content Target | Notes | +|---|---|---| +| `docs/wiki/npcs/*.md` | `content/districts/sova-transit/npcs/*.yaml` | One-to-one mapping. Wiki profile is the human-readable source; YAML is the machine-readable runtime format. | +| `docs/wiki/factions/*.md` | `content/global/factions/*.yaml` | Faction profiles converted to structured YAML. | +| `docs/wiki/knowledge/fact-catalog.md` | `content/global/knowledge/*.yaml` | Single Markdown catalog splits into per-category YAML files. | +| `docs/wiki/knowledge/entity-attributes.md` | `content/global/knowledge/entity-attributes.yaml` | EntityKnowledge key definitions. | +| `docs/wiki/contraband/*.md` | `content/global/contraband/*.yaml` | Contraband item profiles. | +| `docs/wiki/locations/*.md` | `content/districts/sova-transit/locations/*.yaml` | Location metadata extracted from descriptions. | +| `docs/wiki/world/*.md` | `content/global/regions/*.yaml` | System/station metadata. | +| (new content) | `content/districts/sova-transit/dialogue/**/*.yaml` | Dialogue pools are new content authored directly in YAML. No wiki source. | +| (new content) | `content/districts/sova-transit/monologue/**/*.yaml` | Monologue pools are new content authored directly in YAML. No wiki source. | +| (new content) | `content/districts/sova-transit/routines/*.yaml` | Routine schedules are new content authored directly in YAML. No wiki source. | +| (new content) | `content/districts/sova-transit/triangles/*.yaml` | Triangle definitions are new content authored directly in YAML. No wiki source. | + +### What the Wiki Is NOT + +The wiki is not deprecated. It remains the primary authoring and review surface for narrative content. Authors should not be expected to write raw YAML for narrative text. The conversion to YAML is a production step, not an authoring step. + +Dialogue, monologue, routines, and triangles are exceptions: these content types are authored directly in YAML because their structure is inherently machine-readable (tagged line pools, schedule entries, relationship definitions). The schemas provide IDE autocomplete for these files. + +--- + +## Appendix A: Complete Directory Tree (v0.1) + +``` +content/ + content.yaml + _meta/ + README.md + _schema/ + npc-profile.schema.json + dialogue-pool.schema.json + monologue-pool.schema.json + district.schema.json + triangle.schema.json + routine.schema.json + location.schema.json + fact-catalog.schema.json + global/ + factions/ + lattice-commission.yaml + syndics.yaml + the-ring.yaml + concord-assembly.yaml + guardians-of-autonomy.yaml + veil-institute.yaml + the-unbound.yaml + technology/ + contraband/ + knowledge/ + contraband.yaml + location.yaml + investigation.yaml + world.yaml + relationship.yaml + progress.yaml + entity-attributes.yaml + enums/ + situations.yaml + topics.yaml + moods.yaml + access-tiers.yaml + trust-tiers.yaml + triggers.yaml + patterns.yaml + motivations.yaml + regions/ + krenn.yaml + districts/ + sova-transit/ + district.yaml + npcs/ + kael-davan.yaml + sera-venn.yaml + voss.yaml + lera-sessik.yaml + torek-lintar.yaml + devra.yaml + maret-korr.yaml + resha.yaml + naia-tamm.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/ + triangles/ + hub-power.yaml + worried-knowledge.yaml + bar-tensions.yaml + worried-partner.yaml + informant-question.yaml + dialogue/ + the-terminal/ + dock-worker.yaml + shift-supervisor.yaml + scheduler.yaml + new-hire.yaml + courier.yaml + the-last-shift/ + bar-owner.yaml + bartender.yaml + bar-regular.yaml + maintenance-corridors/ + ring-operative.yaml + monologue/ + smuggler/ + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml + detective/ + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml + routines/ + schedules.yaml +``` + +## Appendix B: Enum Value Reference (v0.1) + +For quick reference during authoring. Authoritative source: `content/global/enums/*.yaml`. + +**Situations (13):** `arrival`, `shift_start`, `shift_end`, `shift_transition`, `bar_evening`, `night_shift`, `investigation`, `confrontation`, `social`, `alone`, `emergency`, `routine`, `observation` + +**Topics (9):** `colleague`, `routine`, `cargo`, `money`, `trust`, `danger`, `institution`, `personal`, `investigation` + +**Moods (8):** `fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved` + +**Access Tiers (5):** `public`, `insider`, `authority`, `peer`, `hostile` + +**Trust Tiers (3):** `surface`, `real`, `secret` + +**Monologue Triggers (9):** `enter_location`, `observe_npc`, `hear_sound`, `observe_anomaly`, `post_conversation`, `discover_evidence`, `witness_interaction`, `time_idle`, `return_visit` + +**NPC Patterns (9):** `FRIEND`, `MIRROR`, `ANCHOR`, `GHOST`, `CATALYST`, `THRESHOLD`, `REMNANT`, `SYSTEM`, `NOBODY` + +**NPC Motivations (6):** `HANDLER`, `WITNESS`, `TURNCOAT`, `CIVILIAN`, `OPERATOR`, `SKEPTIC` + +**NPC Tiers (3):** `1` (production-level), `2` (full template), `3` (background) + +--- + +*Ticket #384 — Content directory structure design. Blocks #385 (directory skeleton) and #386 (schema definitions).* diff --git a/docs/wiki/knowledge/entity-attributes.md b/docs/wiki/knowledge/entity-attributes.md index a41dcc1b8..58bb19d3a 100644 --- a/docs/wiki/knowledge/entity-attributes.md +++ b/docs/wiki/knowledge/entity-attributes.md @@ -12,7 +12,8 @@ The knowledge graph uses these attributes to determine: - What the observer's monologue says about the target - Which dialogue lines are available - How the target renders in the observer snapshot (name vs. description) -- Whether secrets or contradictions are discovered +- Whether leverage or contradictions are discovered +- How role-perspective assessments shape the observer's internal narrative --- @@ -83,7 +84,7 @@ The knowledge graph uses these attributes to determine: - `"friend"` — Sera to detective - `"authority figure"` — Voss to subordinates - `"suspect"` — Kael to detective (post-discovery) -- `"partner"` — Hael to Kael +- `"partner"` — Naia to Kael - `"stranger"` — new NPCs **Usage**: Drives `RelationshipState` calculation (Unknown/Known/Friendly/PersonOfInterest/Hostile). Determines emotional tone of monologue. @@ -92,16 +93,18 @@ The knowledge graph uses these attributes to determine: ### `trust_level` -**Value format**: Trust assessment (subjective, observer's judgment). +**Value format**: Trust assessment (subjective, observer's judgment). This key encompasses both the read action (what the observer can observe about trust dynamics) and the state (what level of trust currently exists between observer and target). There is no separate `trust_read` key — the observation and the conclusion are stored together as a single assessment. + +**Values**: `trusted`, `reliable`, `uncertain`, `suspicious`, `compromised`. **Examples**: - `"trusted"` — high trust, shares sensitive information -- `"reliable"` — professional trust -- `"uncertain"` — cautiously neutral -- `"suspicious"` — flagged for observation +- `"reliable"` — professional trust, predictable behavior +- `"uncertain"` — cautiously neutral, still reading the relationship +- `"suspicious"` — flagged for observation, inconsistencies detected - `"compromised"` — known to be under pressure or bought -**Usage**: Gates trust-tier dialogue (surface/real/secret). Detective tracking trust levels determines which NPCs can be turned. +**Usage**: Gates trust-tier dialogue (surface/real/secret). Detective tracking trust levels determines which NPCs can be turned. Smuggler uses trust levels to gauge operational reliability. The trust read (what the observer notices) and the trust state (what the observer concludes) are one assessment — the observer watches, interprets, and assigns a level in the same cognitive step. --- @@ -134,40 +137,42 @@ The knowledge graph uses these attributes to determine: --- -## Secret Attributes +## Leverage Attributes -### `secret_held` +### `leverage_held` -**Value format**: What the observer believes this entity is hiding. +**Value format**: What the observer believes gives them (or could give them) power over this entity. This covers secrets, debts, promises, obligations, and compromising positions — not just secrets. Any information or relationship that creates asymmetric power over the target qualifies as leverage. **Examples**: -- `"ring membership"` — detective's suspicion about Kael -- `"unreported evidence"` — smuggler's read on Sera (if observant) -- `"gambling debt"` — if Drin's vulnerability is discovered -- `"exit attempt"` — if Kael's unauthorized contact is discovered +- `"ring membership"` — detective's suspicion about Kael (secret) +- `"unreported evidence"` — smuggler's read on Sera (concealed information) +- `"gambling debt"` — if Drin's vulnerability is discovered (financial obligation) +- `"exit attempt"` — if Kael's unauthorized contact is discovered (betrayal) +- `"owes favor to Voss"` — social obligation +- `"witnessed the handoff"` — compromising position -**Usage**: Drives PersonOfInterest state. Gates confrontation dialogue. Smuggler and detective track different secrets about the same NPCs. +**Usage**: Drives PersonOfInterest state. Gates confrontation dialogue. Smuggler and detective track different leverage about the same NPCs. The broader scope (beyond secrets) allows content authors to capture debts, promises, and obligations that create power dynamics without requiring additional keys. --- -### `secret_confidence` +### `leverage_confidence` -**Value format**: How certain the observer is about the secret. +**Value format**: How certain the observer is about the leverage. **Examples**: - `"suspected"` — behavioral flags suggest something, no proof - `"likely"` — multiple indicators align - `"confirmed"` — direct evidence or confession -**Usage**: Dialogue tone. "Suspected" secrets allow fishing questions. "Confirmed" secrets enable direct confrontation. +**Usage**: Dialogue tone. "Suspected" leverage allows fishing questions. "Confirmed" leverage enables direct confrontation or exploitation. --- -## Investigation Attributes (Detective-Specific) +## Investigation Attributes ### `tell_observed` -**Value format**: Specific tell noted by the detective. +**Value format**: Specific tell noted by the observer. **Examples**: - `"looks left when lying"` — Kael's tell @@ -177,6 +182,8 @@ The knowledge graph uses these attributes to determine: **Usage**: Behavioral profiling. Accumulated tells build case strength. Monologue references specific tells when they recur. +> **Implementation note**: The server may track tells via `behavior_flags` internally, since tells are a subset of behavioral observations. `tell_observed` is retained as a documented content-authoring key because it serves a distinct narrative purpose: `behavior_flags` accumulates raw observations ("nervous,lattice_checking"), while `tell_observed` captures the observer's interpretive conclusion ("looks left when lying"). Content authors reference `tell_observed` for monologue prerequisites that require the observer to have formed a specific read on the target. The server implementation may store both, or derive `tell_observed` from `behavior_flags` pattern matching. + --- ### `contradiction_flagged` @@ -190,6 +197,87 @@ The knowledge graph uses these attributes to determine: **Usage**: THE FRIEND arc trigger. When this attribute is set, monologue tone shifts. Dialogue options change. RelationshipState moves toward PersonOfInterest. +> **Implementation note**: Like `tell_observed`, the server may track contradictions via `behavior_flags` (e.g., a flag with a `contradiction:` prefix). `contradiction_flagged` is retained as a documented key because it serves as a distinct narrative trigger — content authors check for its presence to gate arc transitions and monologue shifts. The separation keeps the content authoring interface clean even if the server implementation unifies storage. + +--- + +## Role-Perspective Attributes + +These four keys capture the observer's subjective assessment of the target through the lens of their own role, goals, and ethical framework. The same key with the same value reads differently depending on the observer's archetype — interpretation lives in the content layer (monologue pools, voice registers, archetype briefs), not in the schema. See [Archetype Interpretation Note](#archetype-interpretation-note) below. + +### `risk_assessment` + +**Value format**: Observer's assessment of how much danger this entity poses or attracts. + +**Values**: `none`, `low`, `moderate`, `high`, `critical`. + +**Examples**: +- Smuggler's assessment of Pell (wavering member) = `"high"` (unpredictable, could expose the ring) +- Detective's assessment of Voss = `"moderate"` (obstructive but not directly dangerous) +- Smuggler's assessment of Naia = `"low"` (uninvolved, unlikely to cause exposure) + +**Usage**: Gates operational monologue. High-risk targets trigger cautionary internal dialogue. Critical-risk targets may trigger evasion or preemptive action. The nature of "risk" depends on the archetype (see interpretation note). + +--- + +### `loyalty_assessment` + +**Value format**: Observer's assessment of this entity's reliability and loyalty. + +**Values**: `solid`, `dependable`, `uncertain`, `wavering`, `hostile`. + +**Examples**: +- Smuggler's assessment of Renn (courier) = `"dependable"` (reliable, follows instructions) +- Smuggler's assessment of Kael (mid-game) = `"wavering"` (showing signs of doubt) +- Detective's assessment of Sera = `"solid"` (committed to the investigation) + +**Usage**: Determines how much the observer relies on or confides in the target. Wavering loyalty triggers monitoring behavior. Hostile loyalty triggers defensive or adversarial posture. + +--- + +### `position_integrity` + +**Value format**: Observer's assessment of this entity's cover, position stability, or facade status. + +**Values**: `solid`, `thin`, `cracking`, `blown`, `N/A`. + +**Examples**: +- Ring member's assessment of Kael = `"thin"` (behavior changes are visible to anyone watching) +- Smuggler's self-assessment near the detective = `"cracking"` (too many close calls) +- Detective's assessment of a civilian witness = `"N/A"` (no cover to assess) + +**Usage**: Tracks active deception status. When position_integrity degrades, the observer's monologue reflects increasing anxiety or strategic recalculation. `N/A` is used when the concept of cover/facade does not apply to the target. + +--- + +### `moral_weight` + +**Value format**: Observer's assessment of this entity's moral involvement in the situation. + +**Values**: `innocent`, `peripheral`, `complicit`, `compromised`, `willing`. + +**Examples**: +- Detective's assessment of Naia = `"innocent"` (uninvolved partner caught in the fallout) +- Smuggler's assessment of Drin = `"compromised"` (dragged in by debt, not by choice) +- Smuggler's assessment of Torek = `"willing"` (fully aware, actively participating) + +**Usage**: Shapes the observer's moral arc and internal conflict. Monologue tone shifts when the observer assigns moral weight — guilt, justification, and rationalization are driven by this key. The Comfort-Doubt-Reckoning-Compromise arc references `moral_weight` to determine which phase the observer occupies relative to specific NPCs. + +--- + +### Archetype Interpretation Note + +Role-perspective keys use the same value enums across all archetypes, but their meaning is archetype-dependent. The schema stores data; the content layer provides interpretation. + +| Key | Smuggler reads as... | Detective reads as... | +|-----|---------------------|----------------------| +| `risk_assessment` | Operational risk to the ring (exposure, interdiction, betrayal) | Threat to public safety or obstruction of the investigation | +| `loyalty_assessment` | Loyalty to the smuggling operation | Cooperation with the investigation or institutional loyalty | +| `position_integrity` | Cover identity stability (can I still pass as legitimate?) | Authority and credibility (does my badge still open doors here?) | +| `moral_weight` | Complicity in the ring's operations (how dirty are their hands?) | Culpability in the crime (how responsible are they?) | + +This pattern scales to all 8 planned archetypes without schema modification. An administrator reads `risk_assessment` as political liability; a newcomer reads it as personal vulnerability; a merchant reads it as commercial exposure. Same keys, same enums, different meaning — the archetype brief and voice register provide the lens. + --- ## Examples: Full EntityKnowledge Entries @@ -214,6 +302,10 @@ EntityKnowledge { ("trust_level".to_string(), "trusted".to_string()), ("routine_pattern".to_string(), "morning shift 06:00-14:00, bar after shift".to_string()), ("behavior_flags".to_string(), "reliable,punctual".to_string()), + ("risk_assessment".to_string(), "low".to_string()), // Kael is reliable, no exposure concern + ("loyalty_assessment".to_string(), "solid".to_string()), // loyal to the ring + ("position_integrity".to_string(), "solid".to_string()), // cover is intact + ("moral_weight".to_string(), "willing".to_string()), // fully aware, active participant ]), } ``` @@ -244,9 +336,13 @@ EntityKnowledge { ("routine_pattern".to_string(), "morning shift, frequent late departures".to_string()), ("behavior_flags".to_string(), "nervous,lattice_checking,evasive".to_string()), ("tell_observed".to_string(), "looks left when lying".to_string()), - ("secret_held".to_string(), "unauthorized meetings".to_string()), - ("secret_confidence".to_string(), "confirmed".to_string()), + ("leverage_held".to_string(), "unauthorized meetings".to_string()), + ("leverage_confidence".to_string(), "confirmed".to_string()), ("contradiction_flagged".to_string(), "meeting_unknown_contact".to_string()), + ("risk_assessment".to_string(), "high".to_string()), // obstructing investigation, potential flight risk + ("loyalty_assessment".to_string(), "hostile".to_string()), // uncooperative, evasive + ("position_integrity".to_string(), "cracking".to_string()), // his story is falling apart + ("moral_weight".to_string(), "complicit".to_string()), // involved, degree of responsibility unclear ]), } ``` @@ -271,8 +367,12 @@ EntityKnowledge { ("trust_level".to_string(), "uncertain".to_string()), ("routine_pattern".to_string(), "bar regular, off-duty evenings".to_string()), ("behavior_flags".to_string(), "observant,avoidance_pattern".to_string()), - ("secret_held".to_string(), "knows something about Torek or Hael".to_string()), - ("secret_confidence".to_string(), "suspected".to_string()), + ("leverage_held".to_string(), "knows something about Torek or Naia".to_string()), + ("leverage_confidence".to_string(), "suspected".to_string()), + ("risk_assessment".to_string(), "moderate".to_string()), // Commission presence is inherently risky + ("loyalty_assessment".to_string(), "uncertain".to_string()), // unknown allegiance + ("position_integrity".to_string(), "N/A".to_string()), // Sera isn't maintaining a cover + ("moral_weight".to_string(), "peripheral".to_string()), // not involved in ring, but near it ]), } ``` @@ -282,20 +382,23 @@ EntityKnowledge { ## Attribute Lifecycle 1. **Initial state** (Unknown NPC): No attributes. EntityKnowledge doesn't exist yet. -2. **First observation** (Direct confidence): `name` (if visible via lattice query or introduction), `role` (inferred from context), `species`, `routine_pattern` starts accumulating. -3. **Repeated interaction** (KnowsOf): `relationship_type` evolves, `trust_level` set, `behavior_flags` accumulate. -4. **Investigation depth** (KnowsDetails): `secret_held`, `tell_observed`, `contradiction_flagged` added by detective. Smuggler gains `faction` clarity. -5. **Knowledge decay**: Attributes persist even as confidence decays. A KnowsOf entry retains all attributes; only positional certainty degrades. +2. **First observation** (Direct confidence): `name` (if visible via lattice query or introduction), `role` (inferred from context), `species`, `routine_pattern` starts accumulating. Initial `risk_assessment` and `moral_weight` may be set to defaults (`none` and `innocent` respectively) or left absent until the observer forms an opinion. +3. **Repeated interaction** (KnowsOf): `relationship_type` evolves, `trust_level` set, `behavior_flags` accumulate. `loyalty_assessment` and `position_integrity` become meaningful as the observer builds a model of the target's reliability and stability. +4. **Investigation depth** (KnowsDetails): `leverage_held`, `tell_observed`, `contradiction_flagged` added by detective. Smuggler gains `faction` clarity. Role-perspective keys (`risk_assessment`, `loyalty_assessment`, `position_integrity`, `moral_weight`) shift as the observer's understanding deepens — a target initially assessed as `risk_assessment: "low"` may escalate to `"high"` after contradictions surface. +5. **Knowledge decay**: Attributes persist even as confidence decays. A KnowsOf entry retains all attributes; only positional certainty degrades. Role-perspective assessments are particularly sticky — once the observer has formed a judgment, it takes strong counter-evidence to revise it. --- ## Content Authoring Guidance -- **Monologue prerequisites** can reference specific attributes: `known_attributes["secret_held"] == "ring membership"`. +- **Monologue prerequisites** can reference specific attributes: `known_attributes["leverage_held"] == "ring membership"`. - **Dialogue access tiers** can check `faction` or `relationship_type`. - **Tell system observations** append to `behavior_flags` as comma-separated values. - **THE FRIEND contradiction** is triggered by setting `contradiction_flagged`. +- **Role-perspective prerequisites** can gate monologue lines by assessment: `known_attributes["risk_assessment"] == "high"` triggers different monologue pools depending on the observer's archetype (smuggler hears operational caution; detective hears investigative urgency). +- **Leverage-based confrontation** uses `leverage_held` + `leverage_confidence` together: a "suspected" leverage allows indirect probing dialogue, while "confirmed" leverage enables direct confrontation or exploitation moves. +- **Moral arc tracking** uses `moral_weight` to determine which phase of the observer's arc (Comfort/Doubt/Reckoning/Compromise) is active relative to each NPC. --- -**Total canonical keys**: 14 (4 identity, 2 social, 2 behavioral, 2 secret, 2 investigation, 2 detective-specific). Additional keys can be added as needed, but these 14 cover v0.1 requirements. +**Total canonical keys**: 16 (4 identity, 2 social, 2 behavioral, 2 leverage, 2 investigation, 4 role-perspective). The 12 shared keys cover entity facts and relational dynamics; the 4 role-perspective keys capture the observer's subjective assessment. This schema scales to all 8 planned archetypes without modification — interpretation lives in content (monologue pools, voice registers, archetype briefs), not in the key definitions. diff --git a/docs/wiki/npcs/nils-davan.md b/docs/wiki/npcs/nils-davan.md new file mode 100644 index 000000000..acad928da --- /dev/null +++ b/docs/wiki/npcs/nils-davan.md @@ -0,0 +1,110 @@ +# Nils Davan + +**Off-Stage NPC** | Smuggling Ring | Entangled + +--- + +## Core Identity + +**Name**: Nils Davan +**Age**: ~38 +**Role**: Ring Coordinator (upstream authority) — strategic direction, personnel decisions, volume targets +**System Origin**: Krenn System (same as Kael — siblings) +**Current Location**: Off-station (exact location unknown to most ring members) +**Lattice Tier**: Unknown (operates through lattice messages; communication protocols managed by Devra) +**Employer**: The Arrangement (ring leadership, operational) + +--- + +## Essential Function + +Nils is the **off-stage authority figure** in the smuggling ring. He does not appear physically in any v0.1 scene. His presence is felt entirely through: + +- **Lattice messages to Kael** (~5-8 authored lines): operational instructions, volume pressure, occasional personal/familial notes +- **References in NPC dialogue**: ring members mention Nils by name or by implication ("the coordinator", "your brother", "someone above Devra") +- **Reputation and operational pressure**: the escalation that drives Triangle 1 originates with Nils + +Nils is the **escalation pressure source**. He pushes for higher volume, tighter schedules, more throughput. This pressure cascades down through Devra (who executes), Kael (who handles dock-side operations), and Voss (who adjusts shift schedules to create coverage windows). Every character in the ring feels Nils's influence without seeing him. + +--- + +## Relationships + +### Kael Davan (younger sibling, ring member) + +- **Type**: Blood loyalty, complicated by power dynamic +- **History**: Nils recruited Kael into the ring. "It's just moving containers. You're good at logistics. We take care of our own." +- **Current dynamic**: Genuine familial bond, but Nils puts the operation first. Pushes Kael for higher volume. Doesn't know Kael is trying to exit the ring. +- **Mechanical relevance**: If Kael's exit attempt is discovered, Nils is the primary threat. Nils's reaction — family loyalty vs. operational security — is undefined in v0.1 but implied to be dangerous. + +### Devra Talsen (on-station coordinator) + +- **Type**: Professional respect, operational partnership +- **Dynamic**: Nils gives strategic direction; Devra handles execution. They respect each other's competence but disagree on risk tolerance — Devra favors steady volume, Nils pushes for escalation. +- **Communication**: Lattice messages, operational protocols. Devra is Nils's primary on-station voice. + +### Arvo Voss (shift supervisor) + +- **Type**: Resource exploitation, no personal relationship +- **Dynamic**: Nils exploits Voss's schedule authority to create coverage windows for ring operations. Voss resents the invisible hand above him — has positional authority on the floor, but the ring's needs override his scheduling decisions. Voss complies because the money is good and because refusing Nils has consequences he doesn't want to test. +- **Nils's view**: Voss is a resource, not a person. Functional, replaceable. + +### The Smuggler (PC, if ring-member path) + +- **Type**: Unseen boss +- **Dynamic**: If the smuggler is a ring member, Nils is the authority known by reputation and felt through operational pressure. The smuggler has never met Nils but follows orders relayed through Devra and Kael. + +--- + +## Known Attributes by Other NPCs + +| NPC | What They Know About Nils | +|-----|---------------------------| +| **Kael** | Sibling, ring leader, pushing for higher volume, cares about family but puts operation first. Recruited Kael personally. | +| **Devra** | Full operational picture. Communication protocols. Nils's identity, strategic priorities, risk tolerance. Primary on-station contact. | +| **Voss** | "Someone above Devra" giving orders. Never met Nils. Resents the pressure. Knows consequences of refusal exist. | +| **Renn** | Ring coordinator exists. Follows orders relayed through Devra. Minimal direct knowledge. | +| **Pell** | Knows the ring has leadership above local operations. No personal details. | +| **Smuggler (if ring)** | Nils exists, gives orders, is Kael's older sibling. Known by reputation. | +| **Nobody in Sova** | Nils's exact location, the full supply chain above Nils, Nils's broader network. | + +--- + +## Pattern / Motivation + +**GHOST + HANDLER** + +- **GHOST**: Physically absent from Sova Transit District. Power felt exclusively through intermediaries — lattice messages, relayed orders, operational pressure. No one outside the ring's inner circle (Kael, Devra) has met Nils in person. +- **HANDLER**: Manages ring operations from above. Sets volume targets, applies escalation pressure, makes personnel decisions. The ring's strategic direction comes from Nils; execution is delegated. + +**Core motivation**: Growth. Nils wants the operation to scale. More volume, more profit, more reach. This puts him in tension with every on-station operator who prefers stability (Devra, Voss, Kael). Nils either doesn't see or doesn't care that escalation increases risk for people on the ground. + +--- + +## v0.1 Presence + +Nils is **never physically present** in any v0.1 game scene. His entire existence in the vertical slice is mediated: + +### Lattice Messages (~5-8 authored lines) + +Operational instructions to Kael. Pressure to increase volume. Occasional personal/familial note that humanizes the authority figure. These messages are a plot device — the player (as smuggler) may see or hear about them through Kael. + +### Dialogue References + +NPCs mention Nils by name or by implication: +- Kael: references "my brother" or "Nils" in ring contexts +- Voss: "another adjustment" from above, resentment toward unseen authority +- Devra: operational coordination, professional references +- Generic ring dialogue: "Nils says to push through more this cycle" + +### Triangle 1 Volume Escalation Fork (Ticket #377) + +Nils's transmitted message serves as a **plot device** — the escalation order that forces Triangle 1 (Voss - Kael - Nils) into active tension. The fork is triggered by Nils's demand for higher volume, which Kael must relay to Voss, who must adjust schedules, creating observable disruption. + +--- + +## Cross-References + +- [Kael Davan](kael-davan.md) — younger sibling, ring member, dock-side operator +- [Devra Talsen](devra.md) — on-station coordinator, executes Nils's strategic direction +- [Arvo Voss](voss.md) — shift supervisor, resents Nils's influence over his scheduling authority