diff --git a/docs/workshops/v01-content-scoping/SUMMARY.md b/docs/workshops/v01-content-scoping/SUMMARY.md new file mode 100644 index 000000000..dc30d8f8c --- /dev/null +++ b/docs/workshops/v01-content-scoping/SUMMARY.md @@ -0,0 +1,325 @@ +# v0.1 Content Scoping Workshop — Summary + +**Date:** 2026-02-12 +**Called by:** Jeroen +**Participants:** Gestalt (Systems Design), Paula (Narrative), Tyre (Architecture), Mellanie (Copywriter), Stig (UI), Dudley (Server), SI (Project Manager), Qatux (Documenter) +**Rounds:** 2 + closing round (lead resolutions) +**Purpose:** Scope and prioritize content work for v0.1 vertical slice. Apply Wiki Review long-term strategy (300-world generator model) to the hand-authored Sova Transit District proof. +**Key constraint:** v0.1 proves the interaction model. All structures must be generator-compatible for the 300-world future. + +**Source files:** +- `docs/workshops/v01-content-scoping/round1-tracking.md` +- `docs/workshops/v01-content-scoping/round2-tracking.md` +- `docs/workshops/v01-content-scoping/si-ticket-changes.md` +- `docs/workshops/v01-content-scoping/v01-content-scoping-workshop-brief.md` +- Round 1 responses: `round1-gestalt.md` through `round1-si.md` +- Round 2 responses: `round2-gestalt.md` through `round2-si.md` + +--- + +## 1. Workshop Overview + +**Round 1** — 7 responses across 6 topics (wiki fixes, NPC architecture, content directory, interaction model, scope boundary, tickets). Produced 17 consensus items, 5 disagreements, 7 wiki gaps, 7 decisions ready to formalize, 26 surviving tickets + 7 new proposals. + +**Round 2** — Lead issued 4 decisions resolving the major disagreements (NPC mapping, content format, interaction model, pause behavior). 7 responses addressed remaining convergence work. Gestalt changed 6 of 10 NPC mapping positions to Paula's reads. Tyre and Dudley independently produced structurally identical ObserverSnapshot v3 definitions. Mellanie delivered canonical PC voice registers and the Kael Davan content pack template. + +**Closing round** — Lead resolved 3 remaining questions: Q-012 (16 EntityKnowledge keys), Q-013 (server-authoritative pause, NEW-12 killed), Q-016 (`verbs[]` rename confirmed). Established process rule: no ticket merges across domain teams. + +--- + +## 2. Decisions Made (D-042 through D-061) + +### From Round 1 Consensus (7) + +| ID | Decision | +|----|----------| +| D-042 | Drin promoted from Tier 3 to Tier 2. | +| D-043 | THE NOBODY mechanic deferred to v0.2; hidden pattern+motivation data ships in v0.1 content files. | +| D-044 | v0.1 interaction model: 7 interactions (Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear). | +| D-045 | v0.1 scope IN additions: News ticker, PC-as-NPC, Time progression, Relationship state transitions. | +| D-046 | v0.1 scope OUT additions: Inventory, Stealth, Combat, Player-facing save/load, Lattice modification. | +| D-047 | v0.1 triangles: 3 active forks (T1, T2, T4), 2 passive tensions (T3, T5). | +| D-048 | Client receives all text from server via state updates; client does not load or evaluate content files. | + +### From Round 2 + Closing (13) + +| ID | Decision | +|----|----------| +| D-049 | YAML is the content file format for v0.1. RON converter available as optional build-time tool. Server loads YAML directly; RON cache is a future production optimization. | +| D-050 | Gestalt's NPC pattern/motivation mapping is canonical for v0.1. Paula's emotional/narrative layer becomes v0.2 annotations. Version-toggle approach. | +| D-051 | v0.1 ships single context-sensitive action; multi-verb architecture modeled underneath. Server computes N available verbs per entity. v0.1 client shows priority-0 only. v0.2 client reads full list — no server changes. | +| D-052 | 3-state pause: Normal (100%), Overlay (50%), Paused (0%). Server-authoritative — client is a dumb renderer. Client sends pause IPC command; reads `sim_speed` from ObserverSnapshot. | +| D-053 | Self-contained triangle forks for v0.1. No cross-triangle cascade. v0.2 adds storyteller-managed cascade. | +| D-054 | ObserverSnapshot v3 protocol. Extends v2 with: `sim_speed`, `nearby_interactions`, `active_dialogue`, `monologue`, `overheard`, `knowledge_updates`, `examine_result`, `ticker_headlines`. Backward compatible via serde defaults. | +| D-055 | 16 EntityKnowledge keys. 4 new generic role-perspective keys. `trust_read` merged into `trust_level`. `secret_held` renamed to `leverage_held` (expanded value space). `secret_confidence` → `leverage_confidence`. `social_debt` and `loyalty_conflict` deferred to v0.2. | +| D-056 | PC voice registers defined. Smuggler: feeling-first, fragments, concrete/physical. Detective: analysis-first, complete sentences, institutional vocabulary. | +| D-057 | Content directory: `content/` with `schema/`, `global/`, `districts/` top-level split. JSON Schema validation at build time. Dialogue by location+role. Monologue partitioned by character. | +| D-058 | THE FRIEND content pack template: Kael Davan. 91 lines across 5 arc phases. Becomes template for all subsequent packs. | +| D-059 | Monologue display: 160 char max, 2-line max, 4-6s display, 2s cooldown, queue depth 1, 9-level priority ordering. | +| D-060 | `actions[]` renamed to `verbs[]` across all surfaces (server, client, YAML, wiki, decisions). | +| D-061 | No ticket merges across domain teams. Shared epics fine; each team gets own ticket with own definition of done. | + +--- + +## 3. EntityKnowledge — 16 Keys + +**Architecture:** All keys are observer-to-target. Interpretation lives in content (monologue pools, voice registers, archetype briefs), not schema. Same key, same enum, different meaning per archetype. + +### 12 Existing Keys + +| # | Key | Category | +|---|-----|----------| +| 1 | `name` | Identity | +| 2 | `role` | Identity | +| 3 | `faction` | Identity | +| 4 | `species` | Identity | +| 5 | `location` | Spatial | +| 6 | `routine_pattern` | Behavioral | +| 7 | `trust_level` | Relational (absorbs `trust_read`) | +| 8 | `leverage_held` | Relational (renamed from `secret_held`; expanded: secrets, debts, promises, obligations, compromising positions) | +| 9 | `leverage_confidence` | Relational (renamed from `secret_confidence`) | +| 10 | `relationship_state` | Relational | +| 11 | `behavior_flags` | Behavioral (tracks tells + contradictions) | +| 12 | `known_associates` | Social | + +### 4 New Role-Perspective Keys + +| # | Key | Values | What It Stores | +|---|-----|--------|---------------| +| 13 | `risk_assessment` | none / low / moderate / high / critical | How this NPC threatens observer's position | +| 14 | `loyalty_assessment` | solid / dependable / uncertain / wavering / hostile | How predictably this NPC serves observer's interests | +| 15 | `position_integrity` | solid / thin / cracking / blown / N/A | Status of active deception involving this NPC | +| 16 | `moral_weight` | innocent / peripheral / complicit / compromised / willing | Observer's subjective moral assessment | + +**Deferred to v0.2:** `social_debt` (no favor economy in 30-min session), `loyalty_conflict` (inferable from `behavior_flags` + `loyalty_assessment`). + +**Not added:** `tell_observed`, `contradiction_flagged` — tracked in existing `behavior_flags`. + +--- + +## 4. NPC Mapping + +### v0.1 Canonical (Gestalt's Mechanical Mapping) + +| # | Name | Tier | Pattern | Motivation | +|---|------|------|---------|-----------| +| 1 | Kael Davan | T1 | FRIEND | OPERATOR | +| 2 | Sera Venn | T1 | FRIEND | WITNESS | +| 3 | Naia Tamm | T1* | MIRROR | CIVILIAN | +| 4 | Voss | T2 | SYSTEM | OPERATOR | +| 5 | Lera Sessik | T2 | ANCHOR | OPERATOR | +| 6 | Torek Lintar | T2 | CATALYST | CIVILIAN | +| 7 | Devra | T2 | THRESHOLD | HANDLER | +| 8 | Maret Korr | T2 | WITNESS | SKEPTIC | +| 9 | Resha | T2 | CATALYST | CIVILIAN | +| 10 | Drin | T2 | SYSTEM | TURNCOAT | +| 11 | Renn | T2 | THRESHOLD | OPERATOR | +| 12 | Pell | T2 | CATALYST | TURNCOAT | +| 13 | Harek | T2 | ANCHOR | HANDLER | +| 14 | Sess | T3 | ANCHOR | CIVILIAN | +| 15 | Olin | T3 | CATALYST | CIVILIAN | +| 16 | Sabel | T3 | GHOST | OPERATOR | +| 17 | Tav | T3 | SYSTEM | CIVILIAN | + +**Off-stage:** Nils Davan — GHOST + HANDLER + +**Distribution:** 8 of 9 patterns present (REMNANT correctly absent; NOBODY deferred). All 6 motivations present. CIVILIAN at 6/17 (35%) matches D-029 noise floor (~30%). + +### v0.2 Emotional Layer (Paula's Annotations) + +Paula annotated all 10 disagreement NPCs with emotional/narrative depth descriptions specifying what content each v0.2 motivation/pattern unlocks (~30-50 lines per NPC). The version-toggle approach: same NPCs, same schema, deeper analytical lens. + +### Resolution Process + +Gestalt changed 6 of 10 Round 1 disagreement positions to Paula's reads (Maret pattern+motivation, Resha motivation, Harek pattern, Drin pattern, Sabel pattern, Olin motivation). Gestalt held on 3 (Voss motivation, Devra motivation, Sess pattern). Tav was a design-choice change (NOBODY deferred = SYSTEM+CIVILIAN instead). + +--- + +## 5. Content Architecture + +### Format +YAML for all content files (D-049). RON converter in `tooling/` as optional build-time optimization. Server loads YAML directly via `serde_yaml`. JSON Schema validation at authoring and build time. + +### Directory Structure + +``` +content/ + content.yaml # manifest + _schema/ # JSON Schema files + _meta/ # infrastructure + global/ + facts.yaml + factions.yaml + enums/ # situations, topics, moods, triggers, access-tiers, activities + knowledge/ + entity-attributes.yaml + regions/ + districts/ + sova-transit/ + district.yaml + npcs/ # per-NPC YAML (tier-conditional fields) + locations/ + triangles/ # 5 triangle definitions + lines/ + dialogue/ # by location + role (not NPC) + monologue/ # partitioned by character + routines/ + templates/ +``` + +**Canonical ID format:** `{system}.{station}.{district}.{type}.{slug}` + +### Validation Pipeline (3-tier) +1. **Authoring time:** IDE with JSON Schema autocomplete +2. **Build time:** `make validate-content` — JSON Schema + cross-reference validation +3. **Load time:** serde_yaml deserialization + semantic validation (StableId assignment, relationship wiring) + +### Schemas Defined +- `npc-profile.schema.json` — tier-conditional fields, pattern/motivation enums, axes, tells, access tiers, trust levels, friend_arc, triangle membership +- `dialogue-pool.schema.json` — tagged per D-028/D-035, role+location based, access/trust/situation gating, knowledge_grants +- `monologue-pool.schema.json` — character-partitioned (D-032), trigger+prerequisite (AND-only)+priority+cooldown, 160 char max + +--- + +## 6. Interaction Model + +### 7 Verbs (D-044) +Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear. + +### Architecture +- Server computes N available verbs per entity every tick via `verbs[]` field (D-060 rename from `actions[]`) +- Priority-sorted: context determines which verb surfaces as `[E]` prompt +- Post-contradiction: Examine can rise above Talk (detective investigating flagged NPC) +- `verbs[]` embedded in ObserverSnapshot via `nearby_interactions`, not separate message + +### v0.1 Client +Single context-sensitive action. `[E] Talk` / `[E] Examine`. One keybind. `InteractPrimary` PlayerAction variant sends priority-0 verb. + +### v0.2 Extension +`[E] Talk [F] Observe` — horizontal label row. `InteractWith` PlayerAction variant. Client reads full `verbs[]` list. **No server changes needed.** + +### Convergence +Tyre (`AvailableActions`/`ActionOption`), Dudley (`EntityInteractions`/`AvailableAction`), and Stig (`InteractionOptions`) independently produced structurally identical designs. Naming unification deferred to implementation (Q-015). + +--- + +## 7. Pause System + +### 3 States (D-052) + +| State | Speed | Trigger | +|-------|-------|---------| +| Normal | 100% (1.0) | Default | +| Overlay | 50% (0.5) | Knowledge panel, dialogue | +| Paused | 0% (0.0) | Spacebar (always) | + +### Authority +**Server-authoritative.** Tick rate is a server simulation concern. Client is a dumb renderer — renders ObserverSnapshots at whatever rate the server sends them. Client sends pause IPC command to server. Client reads `sim_speed` from ObserverSnapshot for visual feedback. + +Spacebar from any state goes to Paused. Spacebar from Paused resumes to pre-pause state. Multiple overlays don't stack below 50% in v0.1. + +**NEW-12 (client pause state machine) killed** — client doesn't manage time. Stig's `PauseManager` reduced to sending IPC commands and reading `sim_speed`. + +--- + +## 8. Scope Boundary + +### IN (v0.1) +- WASD movement + collision +- Context-sensitive interaction (7 verbs, single-action prompt) +- Monologue system (tagged triggers, FactId prerequisites, AND-only) +- Knowledge graph (player learns facts, relationships change) +- 2 playable characters (smuggler, detective) +- THE FRIEND arc (Kael for smuggler, Sera for detective) +- 5 triangles: 3 active forks (T1, T2, T4), 2 passive tensions (T3, T5) +- 6 wow moments (D-039) +- 3 social sites (Terminal, Last Shift, Maintenance Corridors) +- NPC routines (schedule-based movement) +- News ticker (world-space at bar) +- PC-as-NPC (other character exists as NPC) +- Time progression (DayPhase) +- Relationship state transitions (Friendly → PersonOfInterest) +- Self-contained triangle forks (no cross-triangle cascade) +- ~695 authored content items (~200 monologue + ~350 dialogue + ~50 environmental + ~20 ticker + ~50 UI + ~25 PC-as-NPC) + +### OUT (v0.1) +- THE NOBODY / dynamic tier promotion (data ships, mechanic deferred) +- Archetype transitions +- Multiple FRIEND candidates per character +- Secondary contraband variation +- Gate network / multi-world +- Content generation / randomization +- Modding support (mod-compatible conventions ship; overlay implementation doesn't) +- Inventory system (evidence is knowledge, not physical items) +- Stealth mechanics (social stealth, not mechanical) +- Combat system +- Player-facing save/load (session-based 30-minute slice) +- Lattice modification/upgrade +- Cross-triangle cascade (storyteller-managed, v0.2) +- `social_debt` / `loyalty_conflict` EntityKnowledge keys + +--- + +## 9. Ticket Summary + +### Counts + +| Category | Count | Teams | +|----------|-------|-------| +| Wiki fixes (A-series) | 8 | copy | +| Style guides & specs (B-series) | 5 | copy | +| Content directory & schema (C-series) | 10 | copy (1), server (8), ci (1) | +| Design specs (D-series) | 2 | server (1), copy (1) | +| Workshop tickets (NEW 1-7) | 7 | copy | +| Lead decision tickets (NEW 8-14, excl. killed NEW-12) | 6 | server (4), client (2) | +| **Total new tickets** | **38** | **copy (21), server (13), client (2), ci (1)** | +| Existing ticket updates | 10 | — | + +### Sprint 3 Allocation + +**Copy team (15 tickets):** +- P0: #261 (Dual Lens Guide — **critical path root**, blocks 9 tickets), C1 (content dir design), A1 (canonical names), A2 (Naia Tamm rename), A7 (smuggler attributes) +- P1: B2 (MIRROR spec), B4 (smuggler voice + moral arc), NEW-1 (NPC mapping), NEW-5 (wow moment checklist), A10 (Nils stub), A11 (Triangle 1 fix) +- P2: B3 (PC-as-NPC spec), A8 (shortcode fix), NEW-2 (Drin expansion), NEW-7 (interaction verb spec) + +**Server team (6 additions to existing 10):** +- C2 (directory skeleton), C3 (schemas), C9 (validate-content CLI), NEW-9 (proximity + InteractionOptions), #326-P1 (loader skeleton), NEW-11 (tick rate scaling) + +**Client team (1 addition):** +- NEW-10 (extensible interaction prompt) + +### Critical Paths + +**Chain 1 — Content Authoring:** +`#261` → B4 → A6 (Sprint 4) → NEW-4 (Sprint 4) → content packs (Sprint 5) + +**Chain 2 — Content Architecture:** +C1 → C2+C3 (Sprint 3) → C4-C8+NEW-3 (Sprint 4) → #326-P2 (Sprint 4) + +**Chain 3 — Interaction Pipeline:** +NEW-7 (soft) → NEW-9 (server) → NEW-10 (client) + +**#261 (Dual Lens Authoring Guide) is the single biggest blocker.** Blocks 9 downstream tickets across the entire content pipeline. Recommended: Paula as owner, 5-day time-box. + +### Key Existing Ticket Updates +- #368: Close (wiki delivered) +- #369: Repurpose as parent epic +- #326: Split into Phase 1 (Sprint 3, skeleton) + Phase 2 (Sprint 4, real content) +- #86: Update ECS component list to workshop output +- #88: Add #237 (A* pathfinding) dependency + +--- + +## 10. Open Items Carried Forward + +| ID | Item | Owner | Priority | +|----|------|-------|----------| +| Q-014 | 3 NPC surnames awaiting Miri validation: Drin Vassar, Sess Torin, Tav Lenn | Miri | Low | +| Q-015 | Interaction struct naming: `AvailableActions` (Tyre) vs `EntityInteractions` (Dudley). Trivial — resolved at implementation time. | Tyre/Dudley | Low | +| Q-017 | 695 authored items validated as scope input but not independently verified by other agents. | SI/Mellanie | Low (informational) | + +None of these block Sprint 3 work. + +--- + +*Compiled by Qatux. Workshop produced 20 decisions (D-042 through D-061), 38 new tickets + 10 updates, canonical NPC mapping for 17 NPCs, 16-key EntityKnowledge spec, full content architecture, and Sprint 3-5 roadmap. 3 minor open items carried forward. #261 is the critical path root.* diff --git a/docs/workshops/v01-content-scoping/round1-dudley.md b/docs/workshops/v01-content-scoping/round1-dudley.md new file mode 100644 index 000000000..31673dcf9 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-dudley.md @@ -0,0 +1,826 @@ +# Round 1 — Dudley (Server Developer) + +## Topic 2: NPC Architecture — Server-Side Entity Data & ECS Design + +### Current State + +The server already has a basic NPC component set from D-024 (`server/src/npc/mod.rs`): `Npc` marker, `Want`, `Secret`, `Relationships`, `ToleranceThreshold`, `DailyRoutine`, `InformationInventory`, `Contentment`, `PersonalityTraits`, `TellSystem`, `SkillSet`, `CombatCapability`. These implement the 10-axis model but are string-heavy placeholders. They don't yet encode the three-system NPC model (pattern + motivation + composition). + +The knowledge graph (`server/src/knowledge/`) is production-ready for Sprint 2 scope: `KnowledgeGraph` component with BTreeMap storage, `EntityKnowledge`/`FactKnowledge` entries, confidence hierarchy, decay system, event-driven updates via `KnowledgeEventQueue`. + +### What Each Pattern Requires (System A) + +Patterns define what the character *means to the story*. Most pattern behavior emerges from existing D-024 axes plus a pattern tag and pattern-specific state. The simulation doesn't need a unique system per pattern — it needs the pattern tag to gate content selection and a small amount of pattern-specific state. + +```rust +/// System A: Thematic pattern tag. +/// Drives content selection (which monologue pool, which dialogue options). +/// The pattern itself is behavioral coloring on top of D-024 axes, +/// not a separate simulation system. +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ThematicPattern { + Friend, + Mirror, + Anchor, + Ghost, + Catalyst, + Threshold, + Remnant, + System, + Nobody, +} +``` + +Pattern-specific server-side state needed for v0.1: + +| Pattern | Extra State Beyond D-024 | v0.1? | +|---------|-------------------------|-------| +| FRIEND | `FriendArc` component — phase tracking, contradiction fact IDs, tell progression stage, discovery state | YES (Kael, Sera) | +| MIRROR | None — MIRROR power is *absence* of contradiction. Normal D-024 axes, no secret, no hidden state. | YES (Naia Tamm) | +| ANCHOR | None — contentment axis (already exists) IS the anchor mechanic. | Maybe | +| GHOST | `GhostInfluence` — list of entities this ghost controls, communication channel. Not physically present. | NO (v0.2+) | +| CATALYST | `CatalystTrigger` — arrival condition, disruption event chain. | NO (v0.2+) | +| THRESHOLD | `GateCondition` — what the player needs to pass. Maps to knowledge prerequisites. | Maybe | +| REMNANT | None — InformationInventory with deep historical facts suffices. | Maybe | +| SYSTEM | `InstitutionalRole` — faction affiliation, procedure set, authority level. | YES (Torek) | +| NOBODY | `NobodyState` — promotion stage (Nobody/Noticed/Recognized/Known/Invested), interaction counter, dormant pattern+motivation. | DEFERRED per workshop brief | + +THE FRIEND is the only pattern that requires a dedicated component in v0.1: + +```rust +/// THE FRIEND arc state (D-034). +/// Tracks the emotional progression that exercises every content pipeline. +/// One per FRIEND NPC (Kael for smuggler, Sera for detective). +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct FriendArc { + /// Which playable character this FRIEND belongs to. + /// The simulation doesn't know "the player" — it knows character IDs. + pub bonded_character: StableId, + /// Current arc phase. Advances based on knowledge events + interaction count. + pub phase: FriendPhase, + /// FactIds that constitute the contradiction (e.g., "kael.secret_meetings"). + /// When the bonded character learns these, phase shifts to Doubt. + pub contradiction_facts: Vec, + /// Tell progression: index into the tell sequence. + /// Tells become more visible as phase advances. + pub tell_stage: u8, + /// Has the bonded character directly observed the contradiction? + pub contradiction_discovered: bool, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum FriendPhase { + /// Default — warmth, trust-building. "We're in this together." + Warmth, + /// Player has hints — something is off. Tells become visible. + Trust, + /// Contradiction evidence accumulating. Monologue turns questioning. + Doubt, + /// Direct confrontation or discovery. Relationship fractures. + Conflict, +} +``` + +### What Each Motivation Requires (System B) + +Motivations define what the character *does in gameplay*. Like patterns, most behavior comes from D-024 axes. The motivation tag gates which interaction options the server offers and how the NPC responds in dialogue selection. + +```rust +/// System B: Functional motivation tag. +/// Drives NPC behavior in interactions — what they want from the player, +/// what information they'll share, how they respond to pressure. +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum FunctionalMotivation { + Handler, + Witness, + Turncoat, + Civilian, + Operator, + Skeptic, +} +``` + +Motivation-specific state: + +| Motivation | Extra State | v0.1 Notes | +|-----------|-------------|------------| +| HANDLER | `HandlerState` — which entities they manage, pressure level, directive queue | YES — Voss manages the ring | +| WITNESS | `WitnessKnowledge` — specific observed FactIds, willingness to share, fear level | YES — key investigation mechanic | +| TURNCOAT | `LoyaltyState` — current allegiance, flip conditions, which side they're reporting to | Maybe (depends on NPC mapping) | +| CIVILIAN | None — the *absence* of special state IS the civilian mechanic. Normal D-024 axes. | YES — most Tier 3 NPCs | +| OPERATOR | `OperatorGoal` — current objective, resources managed, competence level | YES — Kael, Lera | +| SKEPTIC | `SkepticTarget` — what institution/system they doubt, what questions they ask | Maybe | + +Most motivations don't need dedicated components. The motivation tag + existing D-024 axes + content tagging (D-035) handle the behavioral differentiation. The server's job is to expose the tag for content selection, not to simulate each motivation as a separate state machine. + +### Proposed ECS Component Design (v0.1 Complete) + +Here's the full component bundle for spawning an NPC entity. Components are split into identity (always present), behavioral (from D-024), pattern/motivation (System A+B), and optional: + +```rust +/// Identity components — every NPC has these +pub struct NpcIdentity { + pub stable_id: StableId, // server-side, never changes + pub canonical_id: String, // content-addressing key ("sova.kael_davan") + pub display_name: String, // "Kael Davan" + pub short_name: String, // "Kael" — used in social contexts (D-036) +} + +/// Content tier — determines simulation depth (D-026 + D-029) +#[derive(Component, Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ContentTier { + Tier1, // Production-level (FRIEND, MIRROR) — full sim, all axes active + Tier2, // Templated with variation — full sim, most axes active + Tier3, // Procedural filler — background sim, minimal axes +} + +/// Access tier map — D-028 Layer 1. +/// Determines which dialogue access tiers this NPC responds to +/// for each interacting character. +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct AccessTierMap { + /// Default access tier for unknown characters + pub default_access: AccessTier, + /// Per-entity overrides (e.g., Kael treats smuggler as "insider") + pub overrides: BTreeMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum AccessTier { + Public, + Insider, + Authority, + Peer, + Hostile, +} + +/// Trust level for gossip gating — D-028 Layer 3. +/// Per-relationship trust determines disclosure tier. +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct TrustLevels { + /// Per-entity trust level. Absent = surface only. + pub levels: BTreeMap, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +pub enum TrustTier { + Surface = 0, + Real = 1, + Secret = 2, +} + +/// Triangle membership — which social triangles this NPC participates in. +/// Used by the server to determine which events ripple through which NPCs. +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct TriangleMembership { + pub triangles: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TriangleRef { + pub triangle_id: String, // "hub_power", "dock_loyalty", etc. + pub role: String, // NPC's role within this triangle +} + +/// NPC routine — schedule-based movement (D-031 day phases) +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct NpcRoutine { + pub schedule: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct RoutineEntry { + pub phase: DayPhase, + pub location: String, // canonical location ID ("sova.terminal", "sova.last_shift") + pub tile: TilePosition, // exact position within location + pub activity: String, // "working", "drinking", "patrolling" — drives animation + availability +} + +/// Mood state — drives D-035 mood tag selection for dialogue lines +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct MoodState { + pub current_mood: Mood, + pub stress: f32, // 0.0-1.0, accumulated from events +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum Mood { + Neutral, + Friendly, + Guarded, + Anxious, + Hostile, + Distracted, + Resigned, + Amused, +} +``` + +**Full NPC entity bundle for spawning:** + +```rust +/// Everything needed to spawn a v0.1 NPC entity. +/// Not all components required — ContentTier determines which are populated. +struct NpcBundle { + // Always present: + npc: Npc, + identity: NpcIdentity, + content_tier: ContentTier, + pattern: ThematicPattern, + motivation: FunctionalMotivation, + position: TilePosition, + knowledge: KnowledgeGraph, + routine: NpcRoutine, + + // Tier 1-2: + want: Want, + secret: Secret, + relationships: Relationships, + tolerance: ToleranceThreshold, + information: InformationInventory, + contentment: Contentment, + personality: PersonalityTraits, + tells: TellSystem, + skills: SkillSet, + access_tiers: AccessTierMap, + trust_levels: TrustLevels, + mood: MoodState, + triangles: TriangleMembership, + + // Pattern-specific (optional): + friend_arc: Option, + + // Combat (optional, D-024): + combat: Option, +} +``` + +### Key Design Principle + +The simulation guarantees: **pattern and motivation are tags for content selection, not unique simulation subsystems.** The server doesn't run 9 different pattern AIs. It runs one NPC tick loop that checks the pattern tag when selecting dialogue, scheduling tell visibility, and evaluating arc progression. THE FRIEND is the sole exception in v0.1 because its arc progression is load-bearing for the vertical slice. + +--- + +## Topic 3: Content Loader — Load Sequence & NPC Spawning + +### What the Server Content Loader Needs + +The server needs to transform content files into ECS entities with components. State consistency requires a defined load sequence with validation at each stage. + +**Server content requirements:** + +1. **District definition** — map dimensions, chunk layout, walkability grid, z-levels, location boundaries +2. **NPC profiles** — identity, axes, pattern, motivation, routine, relationships, knowledge, access tiers +3. **FactId catalog** — all valid fact identifiers with metadata (what category, what it means) +4. **Triangle definitions** — which NPCs, what roles, escalation conditions +5. **Dialogue line pools** — tagged lines per D-035 (server does selection, client does display) +6. **Monologue pools** — tagged lines per D-035 (separate per character, D-032) +7. **Contraband definitions** — what items exist, manifest templates for ring operations + +**What the server does NOT need from content files:** +- Visual assets (sprites, tilesets) — client only +- Audio assets — client only +- UI text that isn't dialogue/monologue — client only +- Wiki prose — authoring format only + +### Proposed Load Sequence + +``` +PHASE 1: VALIDATE + Read content manifest (content/manifest.ron) + Validate all referenced files exist + Validate schema compliance (FactId catalog, NPC profiles, etc.) + FAIL FAST if any validation error — do not partially load + +PHASE 2: GLOBAL RESOURCES + Load FactId catalog → FactCatalog resource + Load contraband definitions → ContrabandDefs resource + Load triangle definitions → TriangleDefs resource + Load dialogue pools → DialoguePool resource + Load monologue pools → MonologuePool resource (per-character, D-032) + +PHASE 3: MAP + Load district walkability grid → WalkabilityMap resource (already exists) + Load location boundaries → LocationMap resource (new) + Load interactable object positions → spawn Object entities + +PHASE 4: ENTITIES + For each NPC profile: + Allocate StableId via EntityRegistry + Spawn entity with NpcBundle components (derived from profile) + Initialize KnowledgeGraph with background facts + Set initial position from routine (current day phase) + For each playable character: + Spawn with PlayerCharacter marker + full component set + Initialize KnowledgeGraph with character-specific background + +PHASE 5: RELATIONSHIPS + Wire up Relationships components (now that all StableIds exist) + Wire up AccessTierMap overrides (per-entity access) + Wire up TrustLevels (per-entity trust) + Wire up FriendArc.bonded_character + Wire up TriangleMembership references + +PHASE 6: VERIFY + Assert all StableId references resolve + Assert all routine locations exist in LocationMap + Assert all FactIds in NPC knowledge exist in FactCatalog + Assert FriendArc NPCs exist and are bonded to valid characters + Log load summary: N entities, M facts, K dialogue lines +``` + +Phase ordering matters. Phase 5 requires all entities from Phase 4 to exist (StableId resolution). Phase 3 must complete before Phase 4 (NPCs need valid positions). Phase 2 must complete before Phase 4 (NPCs reference FactIds and dialogue pools). + +### How an NPC Profile Becomes a Spawned Entity + +Assuming RON format (Tyre's call on format — I need structured, typed data, not free-form YAML): + +``` +// content/districts/sova/npcs/kael_davan.ron +NpcProfile( + canonical_id: "sova.kael_davan", + display_name: "Kael Davan", + short_name: "Kael", + content_tier: Tier1, + pattern: Friend, + motivation: Operator, + + // D-024 axes + want: "Exit the ring, protect partner Naia. Wants a clean life.", + secret: "Meeting unknown contact in restricted corridor. Trying to leave the ring.", + tolerance_threshold: 0.65, + contentment: 0.4, + personality: ["loyal", "cautious", "practical"], + skills: ["cargo_handling", "logistics", "forging_manifests"], + combat_trained: false, + + // Routine (D-031 day phases) + routine: [ + (phase: Morning, location: "sova.terminal", tile: (42, 18, 0), activity: "working"), + (phase: Afternoon, location: "sova.terminal", tile: (45, 20, 0), activity: "working"), + (phase: Evening, location: "sova.last_shift", tile: (80, 55, 0), activity: "drinking"), + (phase: Night, location: "sova.residential", tile: (30, 70, 0), activity: "sleeping"), + ], + + // Knowledge — what Kael knows at game start + background_facts: [ + ("contraband.ring_exists", KnowsDetails), + ("contraband.lattice_components", KnowsDetails), + ("npc.voss.is_handler", KnowsDetails), + ("npc.naia.partner", KnowsDetails), + ("location.restricted_corridor", KnowsOf), + ], + + // Relationships (resolved to StableIds at load time via canonical_id lookup) + relationships: [ + (target: "sova.naia_tamm", kind: "partner", trust: Real), + (target: "sova.voss", kind: "handler", trust: Real), + (target: "sova.drin", kind: "colleague", trust: Surface), + ], + + // Access tiers — who can talk to Kael at what level + access: ( + default: Public, + overrides: [ + ("sova.smuggler", Insider), // smuggler PC gets insider access + ("sova.detective", Public), // detective is just another face + ("sova.voss", Peer), + ], + ), + + // FRIEND-specific (only if pattern == Friend) + friend_arc: Some(( + bonded_character: "sova.smuggler", + initial_phase: Warmth, + contradiction_facts: [ + "kael.secret_meetings", + "kael.restricted_corridor_visits", + ], + )), + + // Tell system (D-034) + tells: [ + "Checks comm device when alone — frequency increases as stress rises", + "Avoids eye contact with Voss after receiving new instructions", + "Takes longer routes to avoid restricted corridor during busy hours", + ], +) +``` + +**Load transformation:** + +1. Parser reads `NpcProfile` from RON file +2. Loader allocates `StableId` via `EntityRegistry::register()` +3. Canonical ID → StableId mapping stored in `ContentAddressMap` resource +4. Components constructed from profile fields — string references to other NPCs deferred to Phase 5 +5. `KnowledgeGraph::with_background()` called with the background_facts list +6. Entity spawned with full component bundle +7. Phase 5: `relationships`, `access.overrides`, `trust_levels`, and `friend_arc.bonded_character` resolved using `ContentAddressMap` canonical_id → StableId lookup + +### Resources the Loader Creates + +```rust +/// Maps canonical content IDs to runtime StableIds. +/// Created during Phase 4, used in Phase 5 for reference resolution. +#[derive(Resource)] +pub struct ContentAddressMap { + pub by_canonical: BTreeMap, +} + +/// All valid FactIds in this district + global facts. +/// Used for validation and monologue prerequisite checking. +#[derive(Resource)] +pub struct FactCatalog { + pub facts: BTreeMap, +} + +pub struct FactMetadata { + pub category: String, + pub description: String, + pub discoverable_by: Vec, // which characters can learn this +} + +/// Location boundaries within the district. +/// Maps location IDs to tile regions for proximity triggers. +#[derive(Resource)] +pub struct LocationMap { + pub locations: BTreeMap, +} + +pub struct LocationDef { + pub canonical_id: String, + pub bounds: Vec, // bounding polygon or rect + pub z_level: i32, +} + +/// Tagged dialogue line pool (D-028, D-035). +/// Server selects lines; client displays them. +#[derive(Resource)] +pub struct DialoguePool { + pub lines: Vec, + // Indices by role, access, trust for fast filtering + pub by_role: BTreeMap>, +} + +/// Tagged monologue line pool (D-032, D-035). +/// Separate per playable character — hard partition. +#[derive(Resource)] +pub struct MonologuePool { + pub smuggler: Vec, + pub detective: Vec, +} +``` + +--- + +## Topic 4: Interaction Model — Server Events & Availability + +### Minimum Server Events for v0.1 + +The simulation needs to support four interaction types: approach + dialogue, monologue triggers, object examination, and overhearing. Each requires specific server-side events. + +```rust +/// Interaction events flowing through the server. +/// Produced by interaction systems, consumed by dialogue/monologue/knowledge systems. +#[derive(Debug, Clone)] +pub enum InteractionEvent { + /// Player entered proximity range of an NPC (triggers interaction availability). + /// Range: configurable, default 3 tiles (Manhattan distance). + ProximityEnter { + character: Entity, + target: Entity, + distance: u32, + }, + + /// Player left proximity range of an NPC. + ProximityExit { + character: Entity, + target: Entity, + }, + + /// Player requested interaction with target entity. + /// Server validates: is target in range? Is target interactable? What options? + InteractionRequest { + character: Entity, + target: Entity, + tick: u64, + }, + + /// Server response: available interaction options for this target. + /// Sent to client via ObserverSnapshot extension. + InteractionOptions { + character: Entity, + target: Entity, + options: Vec, + }, + + /// Player selected a dialogue topic/line. + DialogueSelect { + character: Entity, + npc: Entity, + topic: String, + tick: u64, + }, + + /// Server selected a dialogue response (D-035 pipeline output). + DialogueResponse { + npc: Entity, + line_id: String, + text: String, + // Knowledge updates that result from this line being spoken + knowledge_grants: Vec, + }, + + /// Conversation ended (player walked away, topic exhausted, NPC terminated). + DialogueEnd { + character: Entity, + npc: Entity, + reason: DialogueEndReason, + }, + + /// Monologue triggered by game state (D-035 trigger types). + MonologueTrigger { + character: Entity, + trigger_type: MonologueTriggerType, + context: MonologueContext, + }, + + /// Player examined an interactable object. + ExamineObject { + character: Entity, + object: Entity, + tick: u64, + }, + + /// Overheard conversation fragment (proximity-based, D-018 sound model). + OverheardFragment { + listener: Entity, + speakers: Vec, + content_hint: String, // not full text — what the listener picked up + knowledge_grants: Vec, + }, +} + +#[derive(Debug, Clone)] +pub struct InteractionOption { + pub kind: InteractionKind, + pub label: String, // displayed to player + pub enabled: bool, // false = visible but greyed out (shows the player what's possible) + pub disabled_reason: Option, // why it's greyed out +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum InteractionKind { + Talk, // initiate dialogue + Examine, // look more closely + Overhear, // listen to nearby conversation (passive — may not need explicit action) +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DialogueEndReason { + PlayerLeft, // walked out of range + TopicExhausted, // no more lines available + NpcTerminated, // NPC ended conversation (mood, tolerance) + Interrupted, // another event broke the conversation +} + +/// What knowledge a dialogue line grants when spoken. +#[derive(Debug, Clone)] +pub struct KnowledgeGrant { + pub grant_type: KnowledgeGrantType, +} + +#[derive(Debug, Clone)] +pub enum KnowledgeGrantType { + LearnFact { fact_id: FactId, confidence: KnowledgeConfidence }, + LearnAttribute { target: StableId, key: String, value: String }, + UpdateRelationship { target: StableId, new_state: RelationshipState }, +} + +/// Monologue trigger types (D-035 `trigger` enum). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum MonologueTriggerType { + EnterLocation, + ObserveNpc, + HearSound, + ObserveAnomaly, + PostConversation, + DiscoverEvidence, + WitnessInteraction, + TimeIdle, + ReturnVisit, +} + +/// Context passed with monologue triggers for line selection. +#[derive(Debug, Clone)] +pub struct MonologueContext { + pub location: Option, + pub observed_npc: Option, + pub relevant_facts: Vec, +} +``` + +### Proximity Trigger System + +```rust +/// System: check proximity between player character and NPCs each tick. +/// Emits ProximityEnter/ProximityExit events when distance crosses threshold. +/// v0.1 uses Manhattan distance on same z-level. Simple, deterministic. +const INTERACTION_RANGE: u32 = 3; + +fn check_proximity( + player_query: Query<(Entity, &TilePosition), With>, + npc_query: Query<(Entity, &TilePosition), With>, + mut proximity_state: ResMut, + mut events: ResMut, + time: Res, +) { + let Ok((player_entity, player_pos)) = player_query.single() else { return }; + + for (npc_entity, npc_pos) in npc_query.iter() { + let distance = player_pos.manhattan_distance(npc_pos); + let in_range = distance.is_some_and(|d| d <= INTERACTION_RANGE); + let was_in_range = proximity_state.is_near(player_entity, npc_entity); + + if in_range && !was_in_range { + proximity_state.set_near(player_entity, npc_entity); + events.push(InteractionEvent::ProximityEnter { + character: player_entity, + target: npc_entity, + distance: distance.unwrap(), + }); + } else if !in_range && was_in_range { + proximity_state.set_far(player_entity, npc_entity); + events.push(InteractionEvent::ProximityExit { + character: player_entity, + target: npc_entity, + }); + } + } +} + +/// Tracks which entities are currently in proximity. +/// BTreeSet for deterministic iteration. +#[derive(Resource, Default)] +pub struct ProximityState { + near_pairs: BTreeSet<(Entity, Entity)>, +} +``` + +### How the Server Determines Available Interaction Options + +When the player sends `PlayerAction::Interact`, the server runs a pipeline: + +``` +1. RANGE CHECK + Is there an NPC or interactable object within INTERACTION_RANGE? + If multiple targets, pick closest (Manhattan distance tiebreaker: lowest StableId). + +2. AVAILABILITY CHECK (per target) + Is the NPC in an interactable activity? (not sleeping, not in combat) + What's the NPC's current mood? (Hostile NPCs may refuse) + Is the NPC already in conversation with someone else? + +3. ACCESS TIER RESOLUTION + Look up AccessTierMap for the interacting character. + Determine: Public / Insider / Authority / Peer / Hostile access level. + +4. OPTION GENERATION + Talk: always available if NPC is interactable + not hostile + Examine: always available for objects, available for NPCs if in LOS + (Overhear is passive — triggered by proximity to NPC-NPC conversations, not by player action) + +5. RESPONSE + Pack InteractionOptions into ObserverSnapshot extension. + Client renders the context-sensitive prompt. +``` + +The critical insight: **the server never sends dialogue text unprompted.** The flow is: +- Server tells client "you can Talk to Kael" (via snapshot) +- Client shows interaction prompt +- Player selects "Talk" +- Server runs dialogue selection pipeline (D-035: access filter → situation filter → trust filter → mood/topic weighted selection) +- Server sends selected line + any knowledge grants +- Client displays line, processes knowledge grants + +This keeps the server authoritative over what you can learn and when. + +### What Goes into the ObserverSnapshot (v3) + +The ObserverSnapshot needs new fields for interaction support: + +```rust +pub struct ObserverSnapshot { + // ... existing v2 fields ... + + /// NPCs currently in interaction range with available options. + pub nearby_interactions: Vec, + + /// Active dialogue state (if in conversation). + pub active_dialogue: Option, + + /// Monologue lines triggered this tick. + pub monologue_lines: Vec, + + /// Overheard fragments (proximity-based). + pub overheard: Vec, +} + +pub struct NearbyInteraction { + pub entity_id: u64, // StableId + pub display_name: String, + pub options: Vec, +} + +pub struct ActiveDialogue { + pub npc_entity_id: u64, + pub npc_name: String, + pub current_line: Option, + pub available_topics: Vec, +} + +pub struct DialogueLineDisplay { + pub line_id: String, + pub text: String, + pub mood: String, +} + +pub struct MonologueLine { + pub line_id: String, + pub text: String, + pub trigger: MonologueTriggerType, +} + +pub struct OverheardLine { + pub speaker_id: u64, + pub fragment: String, +} +``` + +--- + +## Topic 5: Server Features Blocked by Missing Content + +### What I Can Build Right Now (No Content Dependency) + +These server systems are purely structural — they need the *schema* but not the *data*: + +| System | Status | Blocked? | +|--------|--------|----------| +| Content loader framework (Phase 1-6 skeleton) | Can build | NOT blocked — test with fixture data | +| Proximity trigger system | Can build | NOT blocked | +| Interaction event pipeline | Can build | NOT blocked | +| Dialogue selection pipeline (D-035 tag filtering) | Can build | NOT blocked — test with synthetic tagged lines | +| Monologue trigger system | Can build | NOT blocked — test with synthetic lines | +| Monologue prerequisite checker | Already built (KnowledgeGraph.fact_at_least) | Done | +| NPC routine scheduler (day phase → position) | Can build | NOT blocked | +| Knowledge decay | Already built | Done | +| FriendArc phase transition logic | Can build | NOT blocked — trigger conditions are structural | + +### What I Cannot Build Without Content + +| Blocked Feature | What Content Is Missing | Who Produces It | +|----------------|------------------------|-----------------| +| **Actual NPC spawning** | NPC profiles in RON/YAML format. Need all 17 Sova NPCs with axes, routine, relationships, background knowledge, access tiers. | Paula (profiles) + Tyre (format) | +| **Dialogue selection testing with real lines** | Tagged dialogue line pools per D-035. Need at minimum THE FRIEND lines (Kael: ~70-100, Sera: ~70-100) and a baseline pool for Tier 2-3 NPCs. | Mellanie (authored lines) | +| **Monologue testing with real lines** | Tagged monologue pools per D-032/D-035. Need smuggler pool + detective pool, minimum viable before repetition. | Mellanie (authored lines) | +| **NPC routine pathfinding** | District map with walkability data and location definitions. Need the Sova Transit District spatial layout. | Tyre (map format) + Stig (Godot map editor?) | +| **Overheard conversations** | NPC-to-NPC conversation scripts or generation rules. Which NPCs talk to each other, about what, when. | Paula (scripts) + Gestalt (rules) | +| **FactId catalog** | Complete list of discoverable facts for Sova, categorized. Currently wiki has some fact vocabulary but not a machine-readable catalog. | Gestalt (catalog design) + Paula (fact content) | +| **Triangle escalation logic** | Formal escalation conditions for each triangle. "When X learns Y, Z happens." Currently prose descriptions only. | Gestalt (conditions) + Paula (narrative) | +| **Contraband manifest data** | Manifest templates, discrepancy patterns, cargo types. Detective investigation vectors need concrete data. | Miri (setting) + Gestalt (mechanics) | + +### Priority Order for Unblocking + +1. **FactId catalog** — blocks everything knowledge-related. I need the schema NOW, content can come incrementally. +2. **NPC profiles in structured format** — blocks spawning. Even partial profiles unblock integration testing. +3. **District spatial layout** — blocks routine scheduler, proximity system integration testing. +4. **Dialogue line pools** — blocks dialogue pipeline integration. Synthetic data works for unit tests but not for end-to-end. +5. **Monologue line pools** — blocks monologue pipeline integration. Same story. + +### What I Propose to Do in Parallel + +While content is being produced, I'll build with fixture/synthetic data: + +- **Fixture NPC profiles**: hand-write 3-4 test profiles (Kael, Sera, one Tier 2, one Tier 3) in whatever format Tyre proposes. These become the "reference implementation" that real profiles must match. +- **Synthetic dialogue pool**: ~20-30 tagged lines exercising all D-035 tags. Enough to test the selection pipeline, not enough to feel like a game. +- **Synthetic monologue pool**: ~10-15 lines per character with varied trigger types and prerequisites. +- **Test district map**: 50x50 walkable area with 3 blocked-out locations (terminal, bar, corridors). + +This lets me build and test every system end-to-end. When real content arrives, it drops into the same loader — the schema is the contract. + +### One Dependency I Need Resolved + +**The Interact action is currently a no-op** (`server/src/simulation/input.rs:86`). To build the interaction pipeline, I need to extend `PlayerAction` with: + +```rust +pub enum PlayerAction { + // ... existing variants ... + Interact, // becomes: interact with nearest target + InteractWith(u64), // interact with specific entity by StableId + SelectDialogueTopic(String), // choose a topic in active dialogue + EndDialogue, // walk away from conversation +} +``` + +This is a protocol change (ObserverSnapshot v2 → v3). Stig needs to know about the new snapshot fields. I need Tyre's sign-off on the protocol evolution strategy. diff --git a/docs/workshops/v01-content-scoping/round1-gestalt.md b/docs/workshops/v01-content-scoping/round1-gestalt.md new file mode 100644 index 000000000..8db72dd42 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-gestalt.md @@ -0,0 +1,468 @@ +# Round 1 — Gestalt (Systems Design) + +*cracks knuckles* + +Let me break down what all of this actually means mechanically. + +--- + +## Topic 1: Wiki Fixes + +### 1a. Do any triangles need restructuring beyond Triangle 1? + +**Triangle 1 (Hub Power)**: Yes, needs a fork. Addressed below. + +**Triangle 2 (Worried Knowledge)**: Structurally sound but smuggler lens is weak. The smuggler "manages" Maret and Drin — that's passive. The smuggler needs a DECISION in this triangle too. Proposed fix: when Maret gets close to reporting, the smuggler can either (a) feed Maret a cover story through Voss, (b) pressure Drin to reassure Maret, or (c) let it ride and hope Maret stays scared. This isn't a restructure — it's adding a smuggler decision node to an existing triangle. + +**Triangle 3 (Bar Tensions)**: Fine as-is. This is a MUNDANE triangle (D-029: 50% mundane). Its job is to be noise floor. Torek's ambiguous spending, Olin's ambiguous intent, Lera's ambiguous loyalty — the player reads signals that might mean nothing. That IS the mechanic. Don't add a fork — the ambiguity IS the fork. The player decides whether to investigate or ignore, and both decisions have consequences (wasted time or missed signal). + +**Triangle 4 (Worried Partner)**: Excellent. No changes. Both FRIEND NPCs are nodes. The triangle IS the contradiction structure. This is the best-designed triangle in the set. + +**Triangle 5 (Informant Question)**: Structurally sound but resolution timing matters. Pell's wavering, Renn's suspicion, Drin's vulnerability — all three paths toward unraveling operate on different timescales. In a 30-minute session, the player observes the TENSION but probably doesn't see the RESOLUTION. That's fine for v0.1 — the tension is the content, the resolution is the hook for continued play. No restructure needed, but we need to be explicit: Triangle 5 is a **passive triangle** in v0.1 (observable tension, unresolved fork). + +**Summary: Active vs. Passive Triangles for v0.1** + +| Triangle | Status | Smuggler Decision? | Detective Decision? | Resolution in 30 min? | +|----------|--------|---------------------|---------------------|----------------------| +| 1: Hub Power | **Active** | YES (escalation fork) | No (observes consequences) | Partial | +| 2: Worried Knowledge | **Active** | YES (manage Maret) | YES (flip Drin or work Maret) | Partial | +| 3: Bar Tensions | **Passive** | No (assess risk) | No (read signals) | No (ambiguity is permanent) | +| 4: Worried Partner | **Active** | YES (protect Kael/lie to Hael) | YES (ask Sera about Hael) | Yes (FRIEND contradiction) | +| 5: Informant Question | **Passive** | No (observes Pell's nervousness) | No (sees downstream signals) | No (hook for continuation) | + +**3 active forks, 2 passive tensions. That's correct for 30 minutes.** + +--- + +### 1b. Triangle 1 Fork Design + +The existing triangle: Nils (off-stage) pushes for higher volume. Voss (shift supervisor) wants stability. Kael is caught between sibling loyalty and operational caution. The smuggler's role is currently passive — they just watch. + +**The fork: The Escalation Decision** + +**Setup (minute 5-10):** Devra approaches the smuggler during a routine shift. Message from Nils: next shipment is double volume. Nils needs more hands and wider timing windows. Voss has to adjust the shift schedule to create a bigger gap. The smuggler is asked to help convince Voss — or handle the extra volume personally. + +**The smuggler decides:** + +| Choice | Immediate Effect | Triangle Consequence | Detective Signal | Mechanical Outcome | +|--------|-----------------|---------------------|-----------------|-------------------| +| **Side with Nils (escalate)** | Extra shipment gets handled. More money. Kael is relieved (less family pressure). | Voss gets more anxious, becomes sloppy. Voss's tells accelerate. Operation becomes more visible. | Manifest discrepancies spike. Voss's behavior flags: `"nervous,schedule_changes,evasive"`. Detective investigation ACCELERATES. | `exposure_risk` increases globally. `cover_integrity` of ring drops to `"thin"`. Wow moment #2 (Character's Eye) triggers EARLIER. | +| **Side with Voss (stabilize)** | Shipment gets rejected or delayed. Nils applies more pressure on Kael directly. | Kael's distraction increases (more contact with exit coordinator). Operation stays quiet but INTERNAL tension builds. | Fewer observable signals. Detective investigation SLOWS. But Kael's tells (lattice checking, distraction) become more visible. | `exposure_risk` stays low. But Kael's `behavior_flags` escalate faster. FRIEND contradiction (wow moment #3) hits HARDER because it comes from internal pressure, not external. | +| **Mediate (delay)** | Smuggler proposes a compromise: half volume increase, phased over two shipments. Neither side satisfied. | Nils loses confidence in the smuggler. Voss is temporarily calmed. Kael is confused ("I thought you were handling this"). | Mixed signals. Operation is inconsistent — some shifts are busier, others normal. Detective reads organizational confusion accurately. | Smuggler's `operational_trust` with Nils drops. Smuggler's relationship with Devra becomes strained. Creates a SECOND decision point later when Nils sends a more forceful message. | + +**What the detective observes (per smuggler choice):** + +The detective doesn't see the decision. They see its CONSEQUENCES: + +- **Post-escalation:** "Voss changed the night shift schedule. Third time this week. The pattern is getting louder." Monologue trigger: `observe_anomaly` + `prerequisite: { "investigation.shift_mismatch": "Suspects" }`. The detective's investigation accelerates because Voss's anxiety produces more observable signals. + +- **Post-stabilize:** "The hub's been quiet this week. Schedules normal. But that dock worker — Kael — he's checked his lattice eleven times this shift." The detective's investigation pivots from institutional signals (schedules, manifests) to personal signals (Kael's behavior). + +- **Post-mediate:** "Something's off. Last night's shift had extra cargo. Tonight it's normal. There's no pattern I can find, and that's the pattern." The detective is confused, which is realistic and interesting — real investigations hit dead ends. + +**Key design note:** The fork doesn't change WHAT the detective discovers — it changes WHEN and HOW. All three paths lead to the ring. The smuggler's choice shapes the detective's journey, not the detective's destination. + +--- + +### 1c. Should Drin be promoted to Tier 2? + +**Yes. Unambiguously.** + +Drin is doing Tier 2 work in a Tier 3 body. Mechanically, Drin is: +- Member of Triangle 2 (Worried Knowledge) — one of our 3 active triangles +- Member of Triangle 5 (Informant Question) — one of our 2 passive triangles +- Compromised by Harek (gambling debt) — a coercion arc +- The easiest NPC for the detective to flip +- The most vulnerable ring asset for the smuggler to manage +- A bridge between 2 triangles across 2 social sites + +A Tier 3 NPC gets ~10-15 authored lines and 3 axes (Want, Routine, Personality). That's not enough for an NPC who appears in 2 triangles and is a potential informant. + +**Tier 2 requires:** + +| Axis | Drin's Content | +|------|---------------| +| **Want** | Keep his job, clear the debt, stop looking over his shoulder | +| **Secret** | Gambling debt to Harek. Harek holds IOUs. Drin looks the other way during inspections in exchange for debt forgiveness. | +| **Relationship 1** | Harek (debt holder, coercer) — fear-based, asymmetric | +| **Relationship 2** | Voss (depends on Drin's compliance) — professional, strained | +| **Relationship 3** | Maret (colleague, doesn't know about Drin's compromise) — guilt-adjacent | +| **Tolerance** | LOW — already past breaking. The compromise IS his post-threshold state. | +| **Routine** | Maintenance shifts, inspection rounds (these ARE the windows he's leaving open) | +| **Information** | Knows inspection schedule gaps, knows he's being used, doesn't know ring structure beyond "someone benefits from my compliance" | +| **Contentment** | Very low. Trapped. Remembers when the job was just a job. | +| **Personality** | Quiet, avoidant, anxious. Doesn't make eye contact during compromised inspections. | +| **Tell** | Changes route when Harek is present. Shorter inspection times on specific shifts. Increased break frequency (stress smoking equivalent). | +| **Skills** | Maintenance tech, inspection procedures. No combat. | + +**Content requirement: ~40-60 authored lines** (Tier 2 standard): +- 15-20 dialogue (routine greetings, deflection, cracking under pressure) +- 8-12 trust-gated (what Drin reveals when scared vs. when trusting) +- 5-8 unprompted (nervous comments, redirect attempts) +- 5-8 monologue per character (detective: "He's hiding something, his inspection times are irregular" / smuggler: "Drin's been twitchy. If he cracks, we're all exposed") + +**Tier adjustment: Tier 3 roster drops from 5 to 4** (Sess, Olin, Sabel, Tav). Tier 2 grows from 10 to 11. Total content budget increase: +30-45 authored lines. This is well within scope for the value Drin provides. + +--- + +## Topic 2: v0.1 NPC Architecture + +### 2a. Full NPC → Pattern + Motivation Mapping + +Here's every NPC mapped. I'm assigning based on what each character DOES in v0.1, not what they might become at 300-world scale. + +| # | Name | Tier | Thematic Pattern | Functional Motivation | Composition Rationale | +|---|------|------|-----------------|----------------------|----------------------| +| 1 | Kael Davan | **T1** | **FRIEND** | **OPERATOR** | Trust anchor hiding a contradiction + running ring cargo operations. FRIEND+OPERATOR = identity betrayal ("I didn't know who you really were") | +| 2 | Sera Venn | **T1** | **FRIEND** | **WITNESS** | Trust anchor hiding a contradiction + carrying unreported evidence. FRIEND+WITNESS = loyalty vs. duty ("She saw something and chose silence") | +| 3 | Voss | T2 | **SYSTEM** | **OPERATOR** | Institutional authority (shift supervisor) + running the schedule that enables smuggling. SYSTEM+OPERATOR = corrupted authority ("The system works for the wrong people") | +| 4 | Lera Sessik | T2 | **ANCHOR** | **OPERATOR** | "Is this enough?" embodied + running a business on willful ignorance. ANCHOR+OPERATOR = contentment with a price ("She makes it work by not looking") | +| 5 | Torek Lintar | T2 | **CATALYST** | **CIVILIAN** | His spending disrupts equilibrium + he's just living his life (spending money, going to the bar). CATALYST+CIVILIAN = accidental disruption ("He doesn't know he's a liability") | +| 6 | Devra | T2 | **THRESHOLD** | **HANDLER** | Gatekeeper between player and ring's deeper layers + managing operations from above. THRESHOLD+HANDLER = obstacle with authority ("You go through her or you don't go") | +| 7 | Maret Korr | T2 | **SYSTEM** | **WITNESS** | Institutional scheduler (the manifests, the numbers) + saw discrepancies she hasn't reported. SYSTEM+WITNESS = honest system under threat ("The numbers don't lie, but she might have to") | +| 8 | Resha | T2 | **CATALYST** | **SKEPTIC** | New hire whose questions force defensiveness + doubts the "mind your business" culture. CATALYST+SKEPTIC = innocent disruption ("They just asked a question. Everyone flinched.") | +| 9 | Naia Tamm | T2 | **MIRROR** | **CIVILIAN** | Honest transparency reveals others' dishonesty + living her life (teaching, worrying about Kael). MIRROR+CIVILIAN = innocence endangered ("The one person who isn't lying, surrounded by liars") | +| 10 | Drin | **T2** (promoted) | **REMNANT** | **TURNCOAT** | Remembers when the job was just a job + loyalty coerced from institution to ring by debt. REMNANT+TURNCOAT = corruption's collateral ("He was an honest inspector once") | +| 11 | Sess | T3 | **ANCHOR** | **CIVILIAN** | Quiet contentment behind the bar + uninvolved, discreet. ANCHOR+CIVILIAN = social wallpaper with depth ("The bartender sees everything and says nothing — because nothing's wrong with his life") | +| 12 | Olin | T3 | **CATALYST** | **SKEPTIC** | New arrival whose presence forces everyone to assess threat + asks questions with ambiguous intent. CATALYST+SKEPTIC = the unknown variable ("Friend or threat? The question itself is the disruption.") | +| 13 | Renn | T2 | **THRESHOLD** | **OPERATOR** | Physical gatekeeper of cargo movement (courier) + competent, goal-oriented, runs the route. THRESHOLD+OPERATOR = the machinery of the operation ("Renn moves the product. Get past Renn, you're inside.") | +| 14 | Pell | T2 | **CATALYST** | **TURNCOAT** | Wavering creates potential disruption to entire operation + loyalty actively shifting. CATALYST+TURNCOAT = ticking bomb ("When Pell breaks, everything moves.") | +| 15 | Harek | T2 | **GHOST** | **HANDLER** | Shapes events through debt leverage on Drin, not through direct ring membership + applies pressure from above (off-duty security with information and leverage). GHOST+HANDLER = invisible power ("Harek doesn't run the ring. He just owns the inspector.") | +| 16 | Sabel | T3 | **THRESHOLD** | **OPERATOR** | Off-station gatekeeper of supply chain + runs the external logistics. THRESHOLD+OPERATOR = distant machinery (1-2 brief appearances, mostly off-stage) | +| 17 | Tav | T3 | **SYSTEM** | **CIVILIAN** | Ring lookout following procedure + uninvolved beyond their assigned role. SYSTEM+CIVILIAN = the lowest rung ("Tav watches the door. That's it. That's the whole job.") | + +**Pattern distribution:** + +| Pattern | Count | NPCs | v0.1 Coverage | +|---------|-------|------|--------------| +| FRIEND | 2 | Kael, Sera | Full (production-level, D-034) | +| MIRROR | 1 | Naia Tamm | Full (T2, emotional depth) | +| ANCHOR | 2 | Lera, Sess | Full + sketch | +| SYSTEM | 3 | Voss, Maret, Tav | Full + sketch | +| THRESHOLD | 3 | Devra, Renn, Sabel | Full + sketch | +| CATALYST | 4 | Torek, Resha, Olin, Pell | Full + sketch | +| GHOST | 1 | Harek | Light (indirect power) | +| REMNANT | 1 | Drin | Light (promoted T2) | +| NOBODY | 0 | — | Deferred to v0.2 | + +**Motivation distribution:** + +| Motivation | Count | NPCs | +|-----------|-------|------| +| OPERATOR | 5 | Kael, Voss, Lera, Renn, Sabel | +| WITNESS | 2 | Sera, Maret | +| HANDLER | 2 | Devra, Harek | +| TURNCOAT | 2 | Drin, Pell | +| CIVILIAN | 5 | Torek, Naia Tamm, Sess, Olin, Tav | +| SKEPTIC | 2 | Resha, Olin | + +Note: Olin gets dual motivation (CIVILIAN/SKEPTIC) because his intent is ambiguous. In the data, he's tagged SKEPTIC (asking questions), but the player doesn't know if that's genuine curiosity or something else. + +**Composition check — does this create interesting interactions?** + +Sova has 4 CATALYSTs. That's a lot. But it's thematically correct: this is a district under pressure. Multiple disruption sources is what makes the ring fragile and the investigation possible. Every CATALYST threatens the status quo differently — Torek through visibility, Resha through innocence, Olin through ambiguity, Pell through betrayal. + +The 5 OPERATORs show a district where everyone is RUNNING something — cargo (Kael), schedules (Voss), a bar (Lera), a route (Renn), a supply line (Sabel). The district hums with operational competence. That's the "quotidian-with-undertow" atmosphere of D-036. + +--- + +### 2b. Required vs. Deferrable Patterns and Motivations + +**Required for v0.1** (must be present AND mechanically exercised): + +| System | Required | Why | +|--------|----------|-----| +| FRIEND | Must ship | Emotional core, D-034, wow moments #3 and #4 | +| MIRROR | Must ship | Proves non-FRIEND Tier 1 depth works. Naia Tamm's honesty is the contrast that makes the liars visible. | +| ANCHOR | Must ship | "Is this enough?" is the thematic question. Lera must embody contentment-with-a-price. | +| SYSTEM | Must ship | Institutional authority is what the detective works within and the smuggler works around. Voss is essential. | +| THRESHOLD | Must ship | The investigation REQUIRES gatekeepers. Devra is the wall between the detective and the ring's interior. | +| CATALYST | Must ship | Without disruption, nothing happens in 30 minutes. Torek or Pell must force someone's hand. | +| OPERATOR | Must ship | People who run things ARE the district. | +| WITNESS | Must ship | Investigation requires witnesses. Sera and Maret. | +| HANDLER | Must ship | Pressure from above drives tension. Devra. | +| TURNCOAT | Must ship | Loyalty shifting is the detective's lever and the smuggler's fear. Drin or Pell. | +| CIVILIAN | Must ship | Noise floor (D-029). Without civilians, every NPC is suspicious. | +| SKEPTIC | Should ship | Questions create friction. Resha's honest questions. Low effort — just give Resha dialogue that asks inconvenient things. | + +**Deferrable to v0.2+:** + +| System | Status | Why Deferrable | +|--------|--------|----------------| +| GHOST (full) | Present, light | Harek's indirect power works at T2 without GHOST-specific mechanics. The "absent power" system (Nils operating off-stage, Ghost entities sending messages through intermediaries) is v0.2 content. | +| REMNANT (full) | Present, light | Drin's "remembers simpler times" is narrative color, not a mechanical system. v0.2 can add REMNANT-specific content (history triggers, memory-based dialogue). | +| NOBODY | **Deferred** | Dynamic tier promotion is a separate system. See 2c. | + +--- + +### 2c. THE NOBODY — Ship in v0.1 or Defer? + +**Recommendation: Ship DATA, defer MECHANIC.** + +| What | v0.1 | v0.2 | +|------|------|------| +| Hidden pattern+motivation in NPC data | YES | YES | +| Tier 3 NPCs have name, routine, greeting | YES | YES | +| Interaction counter (track player attention) | NO | YES | +| Stage transitions (NOBODY → NOTICED → RECOGNIZED → KNOWN → INVESTED) | NO | YES | +| Dormant content activation (hidden Tier 2 content unlocks) | NO | YES | +| Content pool for promotion paths | NO | YES | + +**Why defer the mechanic:** + +1. **System scope**: Dynamic tier promotion requires an interaction counter, trigger evaluation, content activation pipeline, and enough dormant content to justify promotion. Each of those is a system to build, test, and prove. That's v0.2 scope. + +2. **The wow moments don't need it**: D-039 lists 6 wow moments. None require NOBODY promotion. The 30-minute session proves observation, contradiction, divergence, and emotional attachment. NOBODY promotion is a 60+ minute payoff. + +3. **Architectural compatibility**: The content schema includes hidden fields for pattern and motivation from day one. Tier 3 NPCs have `hidden_pattern` and `hidden_motivation` in their content files. The promotion engine reads these when it exists. Zero rework needed. + +4. **Risk reduction**: If NOBODY promotion doesn't work (feels gamey, triggers too easily, content feels thin at activation), we haven't spent v0.1 effort on it. + +**What this means for Tier 3 NPCs in v0.1:** + +Sess, Olin, Sabel, Tav are functional sketches. They have names, routines, greetings, and 10-15 authored lines each. They feel like PEOPLE — they nod at you, they move through the district on schedule, they have brief exchanges with each other. They're just not promotion targets yet. In v0.1, the player's attention is on the 13 NPCs who are already activated (2 Tier 1, 11 Tier 2). That's plenty for 30 minutes. + +--- + +### 2d. Smuggler Attributes → EntityKnowledge Keys + +The wiki review identified 8 smuggler-specific attributes. Here's how they map to the knowledge graph. + +**Existing keys that already serve double duty:** + +| Wiki Attribute | Maps To | How It Works | +|----------------|---------|-------------| +| `trust_read` | **`trust_level`** (existing) | Same key, different interpretation per character. Detective: "will they cooperate?" Smuggler: "will they hold under pressure?" Values already cover both: `"trusted"`, `"reliable"`, `"uncertain"`, `"suspicious"`, `"compromised"`. | +| `leverage_held` | **`secret_held`** (existing) | Leverage IS secret knowledge. Smuggler knowing Drin's gambling debt is stored as `secret_held: "gambling_debt"`. The fact that this is leverage is contextual — the smuggler's operational framework makes it leverage, the detective's framework makes it evidence. Same data, different interpretation. | + +**New smuggler-specific keys (4 for v0.1):** + +| New Key | Value Format | Example | Mechanical Purpose | +|---------|-------------|---------|-------------------| +| `exposure_risk` | enum: `"minimal"`, `"moderate"`, `"high"`, `"critical"` | Torek: `"high"` (spending is visible). Maret: `"moderate"` (smart, might report). | Smuggler risk assessment. Gates monologue: "Torek's spending is going to get us all caught." Drives smuggler decisions about which NPCs to manage. | +| `loyalty_assessment` | enum: `"solid"`, `"reliable"`, `"uncertain"`, `"wavering"`, `"turned"` | Pell: `"wavering"`. Renn: `"solid"`. Kael: `"reliable"` (smuggler doesn't know about exit yet). | Operational trust map. Different from `trust_level` — that's interpersonal, this is operational. You can trust someone personally but doubt their operational loyalty. | +| `cover_integrity` | enum: `"solid"`, `"thin"`, `"cracking"`, `"blown"` | Kael: `"solid"` (baseline) → `"cracking"` (post-contradiction). Voss: `"thin"` (after escalation fork). | Cover assessment. When `cover_integrity` drops, smuggler monologue shifts: "Kael's cover is slipping. If I can see it, so can the detective." | +| `moral_weight` | enum: `"innocent"`, `"peripheral"`, `"complicit"`, `"compromised"`, `"willing"` | Naia Tamm: `"innocent"`. Drin: `"compromised"`. Lera: `"complicit"`. Kael: `"willing"`. | Moral arc driver. The smuggler's Comfort→Doubt→Reckoning→Compromise phases are tracked through who they've assessed as `"innocent"` vs. `"complicit"`. When innocents are threatened, the smuggler doubts. | + +**Deferred to v0.2 (2 keys):** + +| Deferred Key | Why Defer | +|-------------|-----------| +| `social_debt` | Favor economy doesn't have time to develop in 30 minutes. The smuggler doesn't owe or collect favors in the vertical slice — they make decisions. | +| `loyalty_conflict` | Can be inferred from `behavior_flags` + `loyalty_assessment`. Kael's conflict (ring vs. partner) shows up as `behavior_flags: "distracted,evasive"` + implicit knowledge from the smuggler's pre-existing information about Kael's home life. A dedicated key is cleaner but not essential. | + +**Updated EntityKnowledge key count: 18** (14 existing + 4 new smuggler-specific) + +| Category | Keys | Character | +|----------|------|-----------| +| Identity | `name`, `role`, `faction`, `species` | Both | +| Social | `relationship_type`, `trust_level` | Both | +| Behavioral | `routine_pattern`, `behavior_flags` | Both | +| Secret | `secret_held`, `secret_confidence` | Both | +| Detective-specific | `tell_observed`, `contradiction_flagged` | Detective | +| Smuggler-specific | `exposure_risk`, `loyalty_assessment`, `cover_integrity`, `moral_weight` | Smuggler | + +**Symmetry note:** The detective has 2 specific keys (`tell_observed`, `contradiction_flagged`). The smuggler has 4. This asymmetry is intentional and correct. The detective's knowledge graph is EVIDENCE-focused (fewer keys, deeper per-NPC). The smuggler's is RISK-focused (more keys, broader assessment across all NPCs). Different information architectures for different gameplay loops. + +**Full example — Smuggler's knowledge of Drin (post-Drin-promotion, mid-game):** + +```rust +EntityKnowledge { + last_known_position: Some(TilePosition { x: 52, y: 18 }), + last_observed_tick: 1600, + last_updated_tick: 1600, + confidence: KnowledgeConfidence::KnowsDetails, + source: KnowledgeSource::Inferred { + basis: vec![FactId("relationship.trust_network".to_string())], + }, + state: KnowledgeState::Active, + relationship: RelationshipState::Known, + known_attributes: BTreeMap::from([ + ("name".to_string(), "Drin".to_string()), + ("role".to_string(), "maintenance tech".to_string()), + ("faction".to_string(), "civilian".to_string()), + ("relationship_type".to_string(), "asset".to_string()), + ("trust_level".to_string(), "compromised".to_string()), + ("routine_pattern".to_string(), "maintenance shifts, inspection rounds".to_string()), + ("behavior_flags".to_string(), "nervous,avoids_harek_eye_contact".to_string()), + ("secret_held".to_string(), "gambling_debt".to_string()), + ("secret_confidence".to_string(), "confirmed".to_string()), + // Smuggler-specific: + ("exposure_risk".to_string(), "high".to_string()), + ("loyalty_assessment".to_string(), "uncertain".to_string()), + ("cover_integrity".to_string(), "cracking".to_string()), + ("moral_weight".to_string(), "compromised".to_string()), + ]), +} +``` + +**How this drives gameplay:** The smuggler sees Drin and their monologue fires: "Drin's twitchy again. His cover's cracking. If the detective gets to him first, he'll fold in ten seconds." The `exposure_risk: "high"` + `cover_integrity: "cracking"` combination gates a smuggler decision: spend time reassuring Drin (protecting the operation) or write him off and plan for what happens when he breaks. + +--- + +## Topic 4: Minimum Interaction Set + +### What proves the core loop? + +The core loop (D-027 criterion #4): **OBSERVE → NOTICE → FOLLOW → DISCOVER** + +Let me map this to concrete interactions: + +**7 interactions. No more.** + +| # | Interaction | Input | System | Output | Proves | +|---|-------------|-------|--------|--------|--------| +| 1 | **Move** | WASD keys | Movement + collision | Position change, LOS update | Physical embodiment, exploration | +| 2 | **Look** | Passive (automatic) | Perception + LOS | See NPCs, objects, environment | Observation, spatial awareness | +| 3 | **Monologue** | Triggered (automatic) | Monologue system | Internal text + audio chime | Character voice, anomaly detection | +| 4 | **Examine Object** | Context-key on environment | Interaction system | Description text, knowledge update | Environmental investigation | +| 5 | **Examine NPC** | Context-key on NPC (non-dialogue) | Observation system | Behavioral observations, tell detection | Passive intelligence gathering | +| 6 | **Talk** | Context-key on NPC (dialogue) | Dialogue system | Tagged line selection, response | Active information gathering, confrontation | +| 7 | **Overhear** | Passive (proximity) | Sound/proximity system | Overheard dialogue fragments | Environmental intelligence, atmosphere | + +**Why these 7 and not 6 or 8:** + +The critical split is **Examine NPC vs. Talk**. These MUST be separate interactions: + +- **Examine NPC** = study without initiating contact. You watch Kael from across the hub. Monologue fires: "He's checked his lattice three times since I sat down." Updates `behavior_flags` and `tell_observed`. No dialogue, no social cost. + +- **Talk** = initiate dialogue. You walk up to Kael and say something. Tagged line selection fires based on access tier, trust level, situation. Social cost: Kael knows you engaged. NPCs react to being approached. + +The detective's loop is: **Examine first** (accumulate tells across multiple observations) → **Talk later** (use evidence in confrontation). If you collapse Examine and Talk into one "Interact" action, the detective can't study behavior without initiating contact. That breaks the investigation loop. + +The smuggler's loop is inverted: **Talk first** (social, operational, casual) → **Examine when suspicious** ("something's off with Kael, let me watch him"). If you collapse them, the smuggler can't shift from social mode to surveillance mode. + +**Context-sensitive prompt logic:** + +| Target | Range | Available Actions | Prompt Text | +|--------|-------|-------------------|-------------| +| NPC | Out of LOS | None | (not visible) | +| NPC | In LOS, far | Look (auto) | (no prompt — monologue may fire) | +| NPC | In LOS, mid | Look + Examine | `[E] Observe` | +| NPC | In LOS, close | Look + Examine + Talk | `[E] Observe` / `[F] Talk` | +| Object | In LOS, close | Examine | `[E] Examine` | +| NPC conversation | In earshot | Overhear (auto) | (no prompt — text appears as overheard) | + +**What's explicitly NOT in the minimum set:** + +| Excluded | Why | When | +|----------|-----|------| +| Inventory (pick up items) | v0.1 is about observation, not collection. Evidence is knowledge-graph entries, not physical items. | v0.2 | +| Stealth (crouch, hide) | No stealth system. The player's presence is always visible. Managing attention is social, not mechanical. | v0.3+ | +| Combat | Not in D-027 success criteria. No combat encounters in 30-minute session. | v0.2+ | +| Lattice query (search databases) | The detective's analytical lattice works through monologue, not a separate interaction. "Checking the manifest" = Examine Object, not a lattice query UI. | v0.2 (as expansion of Examine) | +| Trade/buy/sell | No economic interaction in v0.1. | v0.3+ | +| Use item | No item use. | v0.2+ | + +**Does this minimum prove the 6 wow moments?** + +| Wow Moment | Required Interactions | Covered? | +|------------|----------------------|----------| +| 1. Arrival | Move + Look + Monologue | YES | +| 2. Character's Eye | Look + Monologue (auto-triggered) | YES | +| 3. FRIEND Contradiction | Look + Examine NPC + Talk | YES | +| 4. Divergence Reveal | Move + Look + Monologue (replay) | YES | +| 5. News Ticker Gut-Punch | Look (read ticker) + Monologue | YES (requires ticker as environmental object, Examine not needed — it's passive text in the environment) | +| 6. Quiet Moment | Move (idle) + Monologue (time_idle trigger) | YES | + +All 6 wow moments are achievable with 7 interactions. No additional interactions required. + +--- + +## Topic 5: v0.1 Scope Boundary + +### Is the IN/OUT split correct? + +Let me go item by item. + +**IN list review:** + +| # | Item | Verdict | Notes | +|---|------|---------|-------| +| 1 | WASD movement + collision | **CORRECT IN** | Non-negotiable. | +| 2 | Context-sensitive interaction | **CORRECT IN** | The 7-interaction model above. | +| 3 | Monologue system | **CORRECT IN** | Drives wow moments #2, #5, #6. | +| 4 | Knowledge graph | **CORRECT IN** | Core mechanic. Without it, nothing gates. | +| 5 | 2 playable characters | **CORRECT IN** | D-027. Non-negotiable. | +| 6 | THE FRIEND arc | **CORRECT IN** | D-034. Non-negotiable. | +| 7 | 5 triangles, 1 fork each | **ADJUST** | 5 triangles YES. But only **3 active forks** (T1, T2, T4). Triangles 3 and 5 are passive (tension visible, fork not resolved). See Topic 1a. | +| 8 | 6 wow moments | **CORRECT IN** | D-039. Non-negotiable (lead directive). | +| 9 | 3 social sites | **CORRECT IN** | Required for triangles and bridge NPCs. | +| 10 | NPC routines | **CORRECT IN** | Routines create the patterns the player reads. Essential for Examine NPC and tell detection. | + +**MISSING from IN — should be added:** + +| Missing Item | Why It Must Be IN | Dependency | +|-------------|-------------------|------------| +| **News ticker** | Wow moment #5 requires a ticker with dual monologue reactions. Must exist as environmental text. | Client (Stig): ticker display. Copy (Mellanie): ticker text + monologue lines. | +| **PC-as-NPC** | When playing smuggler, the detective exists as an NPC (and vice versa). Required for Divergence Reveal (wow moment #4) — second playthrough reveals the other character was there all along. | Content: ~20-30 authored items per PC-as-NPC (SUMMARY confirms). Server: spawn the other PC as an NPC entity. | +| **Time progression** | NPC routines require time to pass. The 30-minute session maps to in-game time (D-031). Without time, routines don't advance, shift transitions don't happen, the smuggling window never opens. | Server (Dudley): game clock. Already decided in D-031. | +| **Relationship state transitions** | The knowledge graph stores `RelationshipState` (Unknown/Known/Friendly/PersonOfInterest/Hostile). FRIEND arc requires Friendly→PersonOfInterest transition. Without state transitions, the color shift (green→amber) never happens. | Server: state machine. Knowledge graph: transition triggers. | + +**OUT list review:** + +| # | Item | Verdict | Notes | +|---|------|---------|-------| +| 1 | THE NOBODY / dynamic tier promotion | **CORRECT OUT** | Data ships, mechanic deferred. See Topic 2c. | +| 2 | Archetype transitions | **CORRECT OUT** | No archetype system in v0.1. | +| 3 | Multiple FRIEND candidates | **CORRECT OUT** | 1 per character. Pool architecture from day one, selection deferred. | +| 4 | Secondary contraband variation | **CORRECT OUT** | Primary contraband fixed (lattice components). Secondary (medical/severance) referenced in dialogue but not mechanically varied per seed. | +| 5 | Gate network / multi-world | **CORRECT OUT** | 1 district. No gates. | +| 6 | Content generation / randomization | **CORRECT OUT** | v0.1 is hand-authored. | +| 7 | Modding support | **CORRECT OUT** | Obviously. | + +**MISSING from OUT — should be explicitly excluded:** + +| Should Be Explicitly OUT | Why | Risk If Not Excluded | +|--------------------------|-----|---------------------| +| **Inventory system** | No item pickup or management. Evidence is knowledge, not physical. | Feature creep. Someone will want "pick up the manifest printout." No. The detective READS the manifest (Examine Object). Knowledge graph updates. No physical item. | +| **Stealth mechanics** | No crouch, hide, LOS manipulation. | Immersive sim instincts will demand it. Defer. The smuggler's "stealth" is social (being in the right place at the right time, having the right cover identity). | +| **Combat system** | No fighting. Zero combat encounters. | D-027 success criteria don't mention combat. If combat appears in v0.1, it ate someone else's scope. | +| **Save/load** | Session-based. Play 30 minutes, experience the slice. No mid-session save. | Testing needs save/load (Dudley's concern). But SHIPPED v0.1 doesn't. Dev-mode save is fine, player-facing save is v0.2. | +| **Lattice modification/upgrade** | Player cannot modify their neural lattice. | Contraband IS lattice components. Someone will ask "can the player use smuggled mods?" No. Not in v0.1. | + +**Revised scope boundary summary:** + +``` +IN (v0.1 — SHIPS): + Core Systems: + [x] WASD movement + collision + [x] 7-interaction model (Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear) + [x] Monologue system (tagged triggers, FactId prerequisites, audio chime) + [x] Knowledge graph (EntityKnowledge, FactIds, RelationshipState transitions) + [x] Time progression (game clock, day phases) + [x] NPC routines (schedule-based movement, routine deviations as tells) + + Content: + [x] 2 playable characters (smuggler, detective) + [x] PC-as-NPC (~20-30 authored items per character) + [x] THE FRIEND arc (Kael/smuggler, Sera/detective) + [x] THE MIRROR (Naia Tamm) + [x] 5 triangles (3 active forks, 2 passive tensions) + [x] 3 social sites (Terminal, Last Shift, Maintenance Corridors) + [x] News ticker (environmental text + dual monologue reactions) + [x] 6 wow moments (D-039) + [x] 17 NPCs (2 T1, 11 T2, 4 T3) — Drin promoted + [x] 8 patterns exercised (NOBODY data-only) + [x] 6 motivations exercised + [x] 18 EntityKnowledge keys (14 shared + 2 detective + 4 smuggler, symmetry note: 2 detective-specific keys are deliberately not mirrored as they are not needed) + +OUT (v0.2+): + [ ] THE NOBODY mechanic (dynamic tier promotion) + [ ] Archetype transitions + [ ] Multiple FRIEND candidates per character + [ ] Secondary contraband variation per seed + [ ] Gate network / multi-world + [ ] Content generation / randomization + [ ] Modding support + [ ] Inventory system + [ ] Stealth mechanics + [ ] Combat system + [ ] Player-facing save/load + [ ] Lattice modification/upgrade + [ ] social_debt / loyalty_conflict EntityKnowledge keys +``` + +**Final scope assessment:** The IN list is tight. Every item serves at least one wow moment or D-027 criterion. The 4 additions (news ticker, PC-as-NPC, time progression, relationship state transitions) are all REQUIRED by existing scope items — they're dependencies, not additions. The 5 explicit exclusions (inventory, stealth, combat, save/load, lattice mods) prevent the most likely scope creep vectors. + +Is this fun? Does this create interesting decisions? YES — because the smuggler and detective have different interaction priorities (Talk-first vs. Examine-first), different knowledge keys (operational vs. evidential), and different relationships to the same NPCs. The same 17 NPCs, 5 triangles, and 3 social sites produce two fundamentally different 30-minute experiences. That's the proof. diff --git a/docs/workshops/v01-content-scoping/round1-mellanie.md b/docs/workshops/v01-content-scoping/round1-mellanie.md new file mode 100644 index 000000000..af9190a1f --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-mellanie.md @@ -0,0 +1,261 @@ +# Round 1 — Mellanie (Copywriter / LIBRARIAN) + +## v0.1 Content Scoping Workshop + +--- + +## Topic 1: Wiki Fixes — Voice Review + +### 1a. Naia Tamm (THE MIRROR) — Voice Samples Needed: YES + +The current Hael profile has three voice samples. All three are anxious: worried-to-Sera, warm-with-Kael-reference, brittle-near-breaking. That's the Worried Partner voice. It's a good voice. It's not THE MIRROR voice. + +THE MIRROR is Tier 1 emotional depth with NO contradiction arc, no secret. Her power is transparency. The wiki summary says: "When events drag the MIRROR into the conspiracy, the damage is devastating — the player broke the one genuinely innocent person." That needs voice samples that establish what innocence sounds like so the player recognizes what they're losing. + +**Naia Tamm needs 5 voice samples minimum:** + +1. **Baseline warm** — How she talks when nothing's wrong. Open, direct, no subtext. This is the calibration line. The player needs to hear what "no hidden agenda" sounds like in a world where everyone has one. + > *"The kids did something brilliant today — Ren figured out the cycle conversion problem by herself. Eight years old. I almost cried in front of the class."* + +2. **Concerned for Kael** — Not the anxious spiral from the current profile. Straightforward worry from someone who doesn't play games. + > *"Kael's been quiet. I don't need an explanation, I need to know they're okay. That's it. That's the whole thing."* + +3. **Perceptive without suspicion** — THE MIRROR reads emotional states accurately but doesn't weaponize the reading. Notices things, says them plainly. + > *"You seem different today. Not bad-different. Just... carrying something. You know you can put it down here, right?"* + +4. **Under pressure** — When events start closing in. THE MIRROR doesn't become evasive or strategic. She becomes more honest, not less. That's what makes it devastating. + > *"Something's happening that nobody's telling me about. I can feel it. I don't want to guess. I want someone to just say it."* + +5. **Damaged** — After events have touched her. THE MIRROR doesn't transform into a different archetype. She stays herself — honest, transparent — but now the honesty includes pain. + > *"I didn't do anything wrong. I know that. But everything's different now, and I can't make it go back."* + +**Voice discipline for THE MIRROR:** Complete sentences. No operational subtext. No evasion vocabulary. Naia doesn't deflect — she states. Where Kael clips ("Don't worry about it") and Sera redirects ("He's just loud"), Naia just... says the thing. That directness is her signature and her vulnerability. + +**Dual-lens note:** Both characters should monologue differently about Naia. The smuggler sees someone to protect ("Naia doesn't know. She can't know."). The detective sees someone too trusting for this environment ("Tamm. Open book. Either she's genuine or she's the best liar in the district. ...No. She's genuine. And that's worse."). + +**Action:** Paula does the rename cascade. I'll write the voice samples above into the updated profile. These five samples are enough for me to author monologue about Naia and dialogue for her. + +--- + +### 1b. Kael and Sera Voice Distinctness — SUFFICIENT, with one gap + +**Kael's voice:** Direct, clipped, practical. Short sentences. Warm through understatement. Evasive through brevity. +- Casual: *"Shift's looking smooth today."* (7 words, done.) +- Evasive: *"It's nothing. Just a thing."* (No elaboration. The silence IS the tell.) + +**Sera's voice:** Competent, precise, conversational. Complete sentences with context. Professional warmth. Evasive through redirection. +- Casual: *"There you are. Grab a seat — Hael was here earlier, you just missed them."* (Provides context, frames the social landscape.) +- Evasive: *"Torek? He's just loud. Gets a couple drinks in him and decides everyone needs to hear his opinions."* (Plausible alternative explanation, delivered smoothly.) + +These are authoring-ready. I can write Kael lines and Sera lines and they won't sound like each other. The distinction is structural: Kael speaks in fragments, Sera speaks in sentences. Kael's warmth is physical presence ("There you are — good"), Sera's warmth is social attention ("Grab a seat, let me tell you about..."). + +**The gap:** I need voice parameters for the smuggler and detective as internal narrators, not just for the NPCs they observe. The monologue guide has example lines but no formal voice sample set for the PC voices. I know what Kael sounds like when he talks. I know what the smuggler sounds like when she thinks ABOUT Kael. But I need a sharper definition of: + +- **Smuggler internal voice:** How terse? How emotional? How operational? The authoring guide examples lean toward short + feeling-forward ("Kael's here. Good — I was starting to worry."). Is that the canonical register? +- **Detective internal voice:** How clinical? How personal? How much institutional vocabulary? The examples lean analytical-with-subtext ("Dock worker. Davan, K. Unremarkable on paper."). Confirm or adjust. + +**Ask:** This might be the voice kit work that's blocked on Paula's Dual Lens Guide (#261). If the workshop can confirm the internal voice registers from the monologue guide examples as canonical, I can start writing without waiting. The examples in the authoring guide are good. Just tell me they're the baseline and I'll scale from there. + +--- + +## Topic 4: Interaction Model — Monologue Pool Sizing + +### How Much Monologue Does v0.1 Need? + +Short answer: **80-110 monologue lines per character, 160-220 total.** + +Here's the math. + +### Trigger Frequency in a 30-Minute Session + +I walked through a mental playthrough. Player enters 3 locations, observes NPCs, has conversations, discovers things, idles, returns to places. Estimated trigger fires: + +| Trigger Type | Fires Per Session | Notes | +|---|---|---| +| `enter_location` | 8-12 | 3 sites, multiple visits | +| `observe_npc` | 15-20 | Known NPCs entering sightlines | +| `hear_sound` | 3-5 | Rare, high-impact | +| `observe_anomaly` | 4-6 | Tells, cargo anomalies | +| `post_conversation` | 6-10 | After dialogue exchanges | +| `discover_evidence` | 3-5 | Physical evidence moments | +| `witness_interaction` | 4-6 | NPC-NPC observations | +| `time_idle` | 3-5 | Player pauses, watches | +| `return_visit` | 4-6 | Returning to familiar locations | +| **Total triggers** | **50-75** | | + +Not every trigger should fire a line. Monologue competes with gameplay for attention. If the inner voice never shuts up, the player stops listening. I'd say the system should fire on **40-50% of eligible triggers** = the player hears **~25-35 monologue lines per playthrough**. + +### Why 80-110 Lines Per Character, Not 25-35? + +Three reasons: + +**1. Prerequisite gating creates narrow windows.** A line gated on `relationship_state: PersonOfInterest` only fires after the contradiction (~minute 15-20). A line with `prerequisite: null` fires from the start. The pool isn't flat — it's a funnel. Many lines are only eligible for part of the session. You need more authored lines than the player will hear because most lines are locked most of the time. + +**2. Repetition threshold.** If the player enters the Terminal 3 times and hears the same arrival line twice, the world feels like a game. Minimum 3 lines per location per trigger type that shares a prerequisite window. Some trigger types need 4-5 variants (especially `observe_npc` for THE FRIEND, who the player sees repeatedly). + +**3. Knowledge progression creates parallel tracks.** The smuggler who knows `contraband.ring_exists` at `KnowsDetails` hears different lines than the smuggler at `Suspects`. Same trigger, same location, different prerequisite — different line. Each confidence level on important FactIds needs its own line. + +### Per-Trigger Breakdown (One Character, All Locations) + +| Trigger Type | Lines Needed | Per Location | Rationale | +|---|---|---|---| +| `enter_location` | 12-15 | 4-5 | Baseline + knowledge-gated variants per site | +| `observe_npc` | 20-25 | varies | 2-3 per observable NPC across relationship states | +| `hear_sound` | 6-8 | 2-3 | Rare trigger, each must count | +| `observe_anomaly` | 8-12 | 3-4 | Tell observations, cargo anomalies, schedule breaks | +| `post_conversation` | 10-15 | varies | 1-2 per NPC conversation, gated on content/outcome | +| `discover_evidence` | 6-8 | 2-3 | Specific evidence moments, heavily gated | +| `witness_interaction` | 6-8 | 2-3 | NPC-NPC observation, relationship-dependent | +| `time_idle` | 6-8 | 2-3 | Character-revealing, atmospheric | +| `return_visit` | 6-8 | 2-3 | Location revisits with accumulated knowledge | +| **Total per character** | **80-107** | | | + +**Two characters x ~95 average = ~190 monologue lines total.** + +Round up for safety margin: **~200 monologue lines** is the target. That gives the player ~25-35 heard lines per session with minimal repetition and knowledge-state variation. + +### THE FRIEND Gets Disproportionate Coverage + +Kael (smuggler's perspective) and Sera (detective's perspective) should each have **15-20 monologue lines** from their respective character's pool — more than any other single NPC. THE FRIEND contradiction is the emotional core. The player needs to feel the warmth, notice the cracks, experience the shift, and live in the contaminated aftermath. That's 4-5 phases x 3-4 lines per phase. + +Other NPCs get 2-4 monologue lines each (first observation, notable behavior, knowledge-gated reactions). Background NPCs might get 1 generic observation line or none. + +--- + +## Topic 5: v0.1 Scope — Content That Must Ship + +### The Hard Numbers + +Everything below is the **floor** — minimum viable for a 30-minute session that doesn't feel empty, repetitive, or placeholder. + +#### 1. Monologue Pools — ~200 lines + +| Category | Smuggler | Detective | Total | +|---|---|---|---| +| THE FRIEND observations | 15-20 | 15-20 | 30-40 | +| Other NPC observations | 20-30 | 20-30 | 40-60 | +| Location arrival/return | 15-20 | 15-20 | 30-40 | +| Evidence/anomaly reactions | 12-15 | 12-15 | 24-30 | +| Atmosphere/idle/sound | 10-12 | 10-12 | 20-24 | +| Post-conversation reactions | 8-12 | 8-12 | 16-24 | +| **Subtotal** | **80-109** | **80-109** | **160-218** | + +**Target: ~200 monologue lines, hard-partitioned by character.** + +#### 2. NPC Dialogue — ~300-400 lines + +| NPC Tier | NPCs | Lines Per NPC | Total | +|---|---|---|---| +| Tier 1 — THE FRIEND (Kael, Sera) | 2 | 70-100 | 140-200 | +| Tier 1 — THE MIRROR (Naia Tamm) | 1 | 50-70 | 50-70 | +| Tier 2 — Essential (Voss, Lera) | 2 | 25-35 | 50-70 | +| Tier 2 — Triangle support (Torek, Maret, others) | 3-4 | 15-25 | 45-100 | +| Tier 3 — Background | 5-8 | 5-10 | 25-80 | +| **Subtotal** | **13-17** | | **310-520** | + +**Target: ~350 dialogue lines across all NPCs.** The FRIEND NPCs account for 40-50% of the dialogue budget. That's correct — they exercise every pipeline. + +#### 3. Environmental Text — ~50 items + +| Category | Count | Examples | +|---|---|---| +| Terminal screens | 8-10 | Shift schedules, manifest summaries, status readouts | +| Safety/regulatory notices | 6-8 | "Authorized personnel beyond this point," hazard markings | +| Bar signage | 4-6 | Menu, house rules, "The Last Shift" sign, back room door | +| Corridor markers | 4-6 | Section labels, access warnings, maintenance tags | +| Lattice/kiosk displays | 4-6 | Commission notices, calibration status, public announcements | +| Interactable documents | 5-8 | Manifest pages, crew rosters, posted schedules (evidence) | +| **Subtotal** | | **~50** | + +#### 4. News Ticker / Meridian Feed — ~20 items + +Headlines that create atmosphere and occasionally deliver world-state information. Mix of: +- Gate network status (3-4 lines) +- Krenn System local news (4-5 lines) +- Commission regulatory notices (3-4 lines) +- Economic/trade reports (3-4 lines) +- Human interest / cultural (3-4 lines) + +**Target: 20 ticker items.** Some are static background; 3-5 should be reactive to game state (new headlines after key events). + +#### 5. UI Microcopy — ~50 strings + +| Category | Count | +|---|---| +| Interaction prompts ("Talk," "Examine," "Listen") | 10-12 | +| Knowledge panel labels and descriptors | 12-15 | +| Relationship state labels (Friendly, PersonOfInterest, etc.) | 5-8 | +| Status messages ("New information," "Contradiction discovered") | 8-10 | +| Tutorial/onboarding monologue (diegetic, D-016) | 10-15 | +| **Subtotal** | **~50** | + +The tutorial text is monologue, not HUD — per D-016, the character teaches the player through internal voice. "How do I interact with this person?" becomes the character thinking *"Sera's over there. Should go say hello."* That's 10-15 lines of contextual teaching buried in the monologue pool. + +#### 6. PC-as-NPC Content — ~20-30 lines + +The other playable character exists as an NPC (D-029 Concept A). The detective exists in the smuggler's world; the smuggler exists in the detective's world. + +- NPC dialogue for the other PC: ~10-15 lines (surface-level, public access, functional) +- Monologue about the other PC: ~5-8 lines per character (identification, assessment, interaction reactions) +- Environmental evidence of the other PC: ~3-5 items (schedule entries, incident reports, lattice traces) + +**Target: ~25 items across both perspectives.** + +--- + +### Grand Total: v0.1 Content That Must Ship + +| Category | Items | Priority | +|---|---|---| +| Monologue pools (2 characters) | ~200 lines | **Critical** — core interaction proof | +| NPC dialogue | ~350 lines | **Critical** — interaction model requires it | +| Environmental text | ~50 items | **High** — world must feel inhabited | +| News ticker | ~20 items | **Medium** — atmosphere, can be thin at launch | +| UI microcopy | ~50 strings | **High** — game is unplayable without it | +| PC-as-NPC content | ~25 items | **Medium** — D-027 requires both characters exist | +| **TOTAL** | **~695** | | + +**~695 authored content items.** Not lines of code. Not generated output. Hand-written, tagged, reviewed content. + +For context: the authoring guide already contains ~40 example monologue lines across 3 sample pools. Those are usable as-is — they're tagged, prerequisite-gated, and voice-correct. That's ~6% of the monologue target. A start, not a finish. + +--- + +### Authoring Order (What I Write First) + +1. **THE FRIEND: Kael content pack** — ~100 lines (dialogue + monologue from both characters). This exercises every tag, every trigger type, every prerequisite format, every access tier. If this works, everything else is variation. + +2. **THE FRIEND: Sera content pack** — ~100 lines. Different contradiction mechanics (cumulative vs. sudden), different voice, same pipeline depth. + +3. **THE MIRROR: Naia Tamm** — ~60-70 lines. Tests the honest-NPC pattern. Simpler prerequisites, no contradiction arc, but full emotional depth. + +4. **Tier 2 NPCs** — ~100-150 lines across Voss, Lera, Torek, Maret. Each gets a focused pack covering their triangle roles. + +5. **Environmental text + ticker** — ~70 items. Can be written in parallel with NPC packs. Uses Miri's setting details. + +6. **Remaining monologue fill** — Top up the pools to ~200 total. Fill gaps in trigger coverage, add knowledge-state variants, idle lines, atmosphere. + +7. **UI copy + tutorial monologue** — ~50 strings. Blocked on Stig's UI spec (need display constraints and interaction prompt format). + +8. **Tier 3 background + PC-as-NPC** — ~50 items. Last priority. These can be thin and still work. + +--- + +### Dependencies / Asks + +| I Need | From | Blocking | +|---|---|---| +| Confirm monologue guide examples as canonical PC voice registers | Gestalt or Paula | Monologue authoring (all) | +| Naia Tamm rename cascade complete | Paula | MIRROR content pack | +| Smuggler-lens sections in NPC profiles | Paula | Smuggler monologue about Tier 2 NPCs | +| Display constraints (char limits, stacking rules, ticker format) | Stig + Araminta | UI microcopy, ticker, environmental text | +| Knowledge state vocabulary (which FactIds gate which content) | Gestalt | Prerequisite authoring for monologue | +| Content file schema (YAML structure confirmed) | Tyre | All content files | + +**Biggest risk:** THE FRIEND content packs are the critical path. If those work — tags validate, prerequisites fire correctly, voice is distinct, dual-lens reads land — everything else scales from the pattern. If those don't work, we find out with 200 lines already written. + +Write the benchmark first. Validate. Then scale. + +--- + +*Mellanie, Round 1. Three words, not thirty — except when you need the numbers.* diff --git a/docs/workshops/v01-content-scoping/round1-paula.md b/docs/workshops/v01-content-scoping/round1-paula.md new file mode 100644 index 000000000..297e46697 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-paula.md @@ -0,0 +1,519 @@ +# Round 1 — Paula (Librarian / Narrative & Political Depth) + +## Topic 1: Wiki Fixes + +### 1a. Hael → Naia Tamm Rename — Full Cascade Plan + +**16 files require changes.** The rename touches every file that references Hael by name, including cross-references in profiles, location descriptions, knowledge system examples, and the monologue authoring guide. + +#### File-by-file rename plan + +| # | File | Change Type | Details | +|---|------|-------------|---------| +| 1 | `docs/wiki/npcs/hael.md` | **Rename + rewrite** | File becomes `docs/wiki/npcs/naia-tamm.md`. All internal references change. Header becomes "Naia Tamm — THE MIRROR". Pattern assignment changes from implicit to explicit MIRROR. | +| 2 | `docs/wiki/npcs/index.md` | Content update | Roster table row 9: "Hael" → "Naia Tamm". Triangle 4 diagram: all 3 "Hael" labels. Bridge NPCs table: not listed (Naia isn't a bridge NPC). Social site listing for The Last Shift: "Hael (Teacher)" → "Naia Tamm (Teacher)". | +| 3 | `docs/wiki/index.md` | Content update | NPC listing under Tier 2: `[Hael](npcs/hael.md) — Kael's partner` → `[Naia Tamm](npcs/naia-tamm.md) — Kael's partner`. | +| 4 | `docs/wiki/npcs/kael-davan.md` | Content update | **Heaviest single file.** 20+ occurrences. Axis 1 secondary Want, Axis 2 (deep secret), Relationship 2 (full section), Axis 4 tolerance, Axis 5 routine, Axis 6 information inventory, Axis 7 contentment, Voice sample (Mood: Protective), Dual-lens smuggler notes, Triangle 4 role notes, Secret section, Contradiction arc Phase 1 through 5, Authoring notes. Every "Hael" → "Naia" in dialogue/casual references (Krenn first-name-primary convention). Every "Hael" → "Naia Tamm" in formal/profile references. | +| 5 | `docs/wiki/npcs/sera-venn.md` | Content update | ~15 occurrences. Relationship 2 (full section), Axis 4 tolerance, Axis 5 routine, Axis 6 information inventory, Axis 7 contentment, Tell system, Voice sample, Dual-lens notes, Triangle 4 role notes, Secret section, Contradiction arc, Authoring notes. Same casual/formal distinction as Kael's file. | +| 6 | `docs/wiki/npcs/lera-sessik.md` | Content update | Axis 6 information inventory: "Hael's worry about Kael" → "Naia Tamm's worry about Kael". | +| 7 | `docs/wiki/npcs/maret-korr.md` | Content update | Axis 5 routine: "goes for Hael's company sometimes" → "goes for Naia's company sometimes". | +| 8 | `docs/wiki/authoring/monologue-guide.md` | Content update | Any monologue examples referencing Hael. | +| 9 | `docs/wiki/locations/krenn-system/the-last-shift.md` | Content update | NPC presence list, social dynamics references. | +| 10 | `docs/wiki/world/sova-transit-district.md` | Content update | Any district NPC references. | +| 11 | `docs/wiki/factions/the-ring.md` | Content update | Likely references Hael as collateral/vulnerability for Kael. | +| 12 | `docs/wiki/knowledge/relationship-states.md` | Content update | Any example entries using Hael as a relationship target. | +| 13 | `docs/wiki/knowledge/entity-attributes.md` | Content update | Scan for Hael in example EntityKnowledge entries. | +| 14 | `docs/wiki/knowledge/fact-catalog.md` | Content update | FactId entries referencing Hael (e.g., `npc.hael.worry`, `relationship.kael.hael`). IDs change to `npc.naia_tamm.*` and `relationship.kael.naia_tamm`. | +| 15 | `docs/wiki/world/krenn-system.md` | Content update | NPC naming examples list. | +| 16 | `decisions/content.md` | Content update | D-034 references Hael twice (Kael's partner, Triangle 4). D-036 references Hael in the naming examples list. | + +#### Naming convention for the rename + +Per Krenn System conventions (D-036, Miri Round 1): +- **Formal/profile references:** "Naia Tamm" (full canonical name) +- **Casual/dialogue/in-character references:** "Naia" (first-name-primary, Krenn convention) +- **NPC profile page title:** "Naia Tamm — THE MIRROR" +- **Canonical ID:** `krenn.naia-tamm` +- **Filename:** `naia-tamm.md` + +#### Profile header changes for naia-tamm.md + +The renamed file also needs structural updates to reflect the MIRROR pattern: + +``` +# Naia Tamm — THE MIRROR + +**Tier 1 (MIRROR) NPC** | The Last Shift (Bar) | Mundane +``` + +Note: The brief identifies Naia Tamm as the first MIRROR instance with "Tier 1 emotional depth." The SUMMARY confirms: "50-70 authored lines." This is between Tier 1 (70-100) and Tier 2 (40-60). The profile needs a content requirement update reflecting MIRROR-specific depth: +- ~50-70 authored lines (no generation expansion — same rule as FRIEND) +- Full 10-axis profile (already present in current Tier 2 format) +- NO contradiction arc, NO secret — this is the MIRROR's defining constraint +- Emotional weight comes from what HAPPENS TO Naia, not what Naia hides + +--- + +### 1b. Smuggler Attribute Vocabulary + +The existing EntityKnowledge attributes (entity-attributes.md) are detective-shaped: `tell_observed`, `contradiction_flagged`, `secret_held`, `secret_confidence` all describe an investigative lens. The smuggler needs parallel attributes that describe an operational lens. + +#### Proposed Smuggler-Specific EntityKnowledge Keys + +| Key | Value Format | What It Captures | Examples | +|-----|-------------|------------------|----------| +| `trust_read` | enum: `solid`, `reliable`, `shaky`, `unknown`, `compromised` | Smuggler's assessment of whether this person can be trusted with operational knowledge | Kael: `"solid"` (pre-contradiction), `"compromised"` (post-observation). Voss: `"reliable"`. Drin: `"compromised"` (known vulnerability). | +| `exposure_risk` | enum: `none`, `low`, `moderate`, `high`, `critical` | How much this NPC's behavior threatens operational security | Torek: `"high"` (spending beyond means). Maret: `"moderate"` (observant, could notice). Resha: `"moderate"` (asks questions). Pell: `"critical"` (might turn). | +| `loyalty_assessment` | enum: `committed`, `dependable`, `neutral`, `wavering`, `hostile` | Smuggler's read on where this person's loyalty lies | Renn: `"committed"`. Kael: `"dependable"` (pre-contradiction). Pell: `"wavering"`. Voss: `"dependable"` (financially motivated). | +| `leverage_held` | string or null | What the smuggler knows they could use against this person | Drin: `"gambling debt to Harek"`. Voss: `"schedule manipulation evidence"`. Pell: `"ring membership knowledge"`. | +| `social_debt` | string or null | Favors owed, obligations, reciprocal relationships | Kael: `"years of mutual cover, personal friendship"`. Lera: `"bar provides meeting space, ring provides revenue"`. | +| `moral_weight` | enum: `none`, `light`, `heavy`, `crushing` | How much guilt or moral cost the smuggler feels about this relationship | Naia Tamm: `"heavy"` (lying to Kael's innocent partner). Renn: `"none"` (operational colleague, no personal bond). Maret: `"light"` (knows she's worried, pragmatically ignores it). | +| `cover_integrity` | enum: `solid`, `stressed`, `cracking`, `blown` | Whether this person's cover/role is holding up | Kael: `"stressed"` → `"cracking"` (tells accumulating). Torek: `"cracking"` (spending visible). Devra: `"solid"` (brokering cover holds). | +| `loyalty_conflict` | string or null | Which competing loyalties this person faces | Kael: `"ring vs. Naia Tamm, ring vs. exit attempt"`. Sera: `"Commission vs. Naia Tamm's friendship"`. Voss: `"shift authority vs. Nils's demands"`. | + +#### How smuggler attributes map to EntityKnowledge + +These 8 keys would be added to the `known_attributes` BTreeMap alongside the existing 14. They use the same string:string format. The smuggler's knowledge graph populates them; the detective's does not (or populates them differently — the detective's `trust_read` of an NPC reflects institutional trust, not operational trust). + +#### What a smuggler-lens section looks like in an NPC profile + +Every NPC profile currently has "Dual-Lens Notes" with a "Smuggler" and "Detective" subsection. The smuggler-lens expansion adds structured operational data below the monologue note. Here's what the expanded format looks like, using Voss as an example: + +```markdown +### How the Smuggler Sees Voss + +**Operational Profile:** + +| Attribute | Value | Rationale | +|-----------|-------|-----------| +| trust_read | reliable | Financially motivated, predictable | +| exposure_risk | moderate | Institutional face — first person investigated | +| loyalty_assessment | dependable | Compliant because the money is good | +| leverage_held | schedule manipulation evidence | Smuggler knows Voss adjusts manifests | +| social_debt | null | No personal relationship | +| moral_weight | none | Voss chose this; smuggler doesn't feel guilt | +| cover_integrity | stressed | Manifest reworks are increasing | +| loyalty_conflict | shift authority vs. Nils's demands | Resents being managed, might cooperate if pressured | + +**Monologue tone (baseline):** Operational assessment. *"Voss is tense today. +Nils must have changed the window again."* + +**Monologue tone (post-escalation):** Risk evaluation. *"Voss reworked the +manifest three times today. He's rattled. If he cracks, we're all exposed."* + +**Moral arc phase:** Comfort (baseline) — Voss is a tool, not a person the +smuggler thinks about. If Voss breaks, the phase shifts to Doubt ("Am I +responsible for what happens to him?"). +``` + +Each NPC profile gets this treatment. The smuggler-lens sections should match the detective-lens sections in depth and specificity. The detective reads people through behavioral analysis; the smuggler reads people through operational risk assessment. + +#### Smuggler Moral Arc Phases + +The brief identifies 4 phases: **Comfort → Doubt → Reckoning → Compromise.** These map to the smuggler's relationship with the consequences of the operation: + +| Phase | Trigger | Internal State | Monologue Tone | +|-------|---------|---------------|----------------| +| **Comfort** | Default. The operation is manageable. Money helps. People are fine. | Pragmatic rationalization. "This is a service. Nobody's getting hurt." | Matter-of-fact, confident | +| **Doubt** | Sees the operation's human cost. Naia Tamm's worry. Maret's anxiety. Pell's terror. | Questions the rationalization. "Nobody's getting hurt — except everyone adjacent to this." | Reflective, unsettled | +| **Reckoning** | A specific harm: Pell disappears. Drin is broken. Naia Tamm discovers Kael's secret. The smuggler's actions contributed. | Can't rationalize. Must choose: continue, reform, or exit. | Conflicted, sharp | +| **Compromise** | Player choice determines resolution. Some damage is done. The question becomes: can you live with it? | Whatever the player decided, the cost is visible. | Quiet, weighted | + +Every NPC's smuggler-lens section should include a note about which moral arc phase the relationship triggers or inhabits. + +--- + +### 1c. Nils Davan Stub Page + Triangle 1 Escalation Sequence + +#### Nils Davan Stub Page + +```markdown +# Nils Davan + +**Off-Stage NPC** | Smuggling Ring (Coordinator) | Entangled + +--- + +## Core Identity + +**Name**: Nils Davan +**Age**: 39 +**Role**: Ring coordinator (strategic — personnel, security, growth decisions) +**System Origin**: Krenn System, Station Sova (born and raised, same as Kael) +**Lattice Tier**: Standard professional +**Employer**: Self-employed (freight consulting cover); The Arrangement + (ring leadership) +**Relationship to Kael**: Older sibling + +--- + +## Function + +Nils is the ring's strategic leader — the person who decides volume, +personnel, risk tolerance. Nils brought Kael into the ring ("It's just +moving containers. You're good at logistics. We take care of our own."). +Nils pushes for escalation: higher volume, tighter margins, more profit. +This push is the primary source of tension in Triangle 1 and the catalyst +for Kael's exit attempt. + +**Off-stage in v0.1.** Nils is referenced constantly but never directly +encountered by the player. Nils exists through: +- Kael's dialogue ("Nils wants the next shipment doubled") +- Voss's resentment ("Another 'adjustment' from above") +- Devra's coordination ("Nils says push the timeline forward") +- Renn's reporting ("I told Nils about Pell") + +This is the GHOST pattern: absent power shaping events through influence. + +--- + +## What the Player Knows + +**Smuggler:** Knows Nils exists, knows Nils is Kael's sibling, knows Nils +gives strategic orders. May receive direct instructions from Nils via +lattice communication (voice only — Nils doesn't appear in person). The +smuggler experiences Nils as a disembodied authority figure whose decisions +create consequences the smuggler must manage. + +**Detective:** Doesn't know Nils exists initially. Nils's name surfaces +through investigation: Kael's family records (lattice query), ring members' +references in overheard conversations, Pell's potential testimony. The +detective builds Nils as a conceptual entity before ever having a name — +"someone is giving orders" → "the coordinator" → "Nils Davan, Kael's +sibling." + +--- + +## Escalation Pressure + +Nils is pushing for increased volume because: +1. External demand (Sabel reports higher demand for lattice components + from other systems) +2. Profit motive (higher volume = higher margins, Nils takes the + largest cut) +3. Ambition (Nils wants to expand beyond Sova to other districts on the + station) + +This escalation creates the pressure that: +- Makes Voss anxious (more schedule manipulation, more risk) +- Makes Kael desperate (higher stakes, more danger for Naia) +- Makes Pell crack (risk-to-reward ratio inverted) +- Makes the whole operation more detectable (the detective arrives + during this escalation, not by coincidence) + +--- + +## Pattern + Motivation + +**Pattern:** GHOST — absent power shaping events +**Motivation:** HANDLER — managing operations, applying pressure from above + +--- + +**Cross-References:** [Kael Davan](kael-davan.md), [Voss](voss.md), +[Devra](devra.md), [Renn](renn.md), [Pell](pell.md) +``` + +#### Triangle 1 Escalation Sequence + +Triangle 1 (Hub Power: Voss - Kael - Nils) is currently the weakest triangle because the smuggler role is passive — the smuggler observes tension but doesn't make decisions. Here's the escalation fork: + +**The Nils-vs-Voss Escalation Decision** + +Nils (via lattice message or Devra intermediary) orders a doubled shipment during a window when Voss has already flagged the schedule as too tight. Voss refuses to create the schedule gap. Kael is caught in the middle. + +**Escalation sequence (5 beats across the 30-minute session):** + +| Beat | Timing | Event | Smuggler Sees | Detective Sees | +|------|--------|-------|---------------|----------------| +| 1 | ~5 min | Nils sends order for doubled volume next cycle via Devra | Devra relays: "Nils wants double. Next cycle." Smuggler knows this means tighter windows, more risk. | Nothing visible. Normal hub operations. | +| 2 | ~10 min | Voss pushes back — refuses to rework schedule for the doubled window | Voss is visibly stressed. Clipped with Kael. "I already posted the rotation." | Voss reworks the manifest for the third time (tell). Detective notes nervous schedule adjustments. | +| 3 | ~15 min | Kael asks the smuggler for advice — **THE FORK** | **Decision point.** Kael says: "Nils wants double. Voss says no. I need to talk to one of them. Which one?" | Kael and smuggler have a private conversation the detective can't hear. Observable: Kael and an unknown colleague talking intensely near the break room. | +| 4 | ~20 min | **Path A**: Smuggler backs Voss → smuggler tells Kael to push back on Nils. Kael contacts Nils (via lattice), argues for maintaining current volume. Nils is unhappy. **Path B**: Smuggler backs Nils → smuggler tells Kael to pressure Voss. Kael pressures Voss to comply. Voss capitulates, resentfully. **Path C**: Smuggler stays neutral → smuggler tells Kael to handle it himself. Kael freezes, does nothing. Nils interprets silence as refusal. | **Path A**: Voss relaxes slightly. Manifests stabilize. But Nils has marked the smuggler as unreliable. **Path B**: Voss's schedule suddenly changes again. Fourth manifest revision. Detective monologue: "Something just changed. The schedule revised again — after hours." **Path C**: Nothing changes immediately. But the unresolved tension escalates into Beat 5. | **All paths**: Detective observes varying levels of hub stress. Path B is most detectable (visible schedule change). Path A is least detectable (tension decreases). Path C delays visibility to Beat 5. | +| 5 | ~25 min | **Consequences arrive.** **Path A**: Nils sends a cold message to Kael: "We'll discuss this." Kael is frightened. The smuggler realizes pushing back on Nils has consequences for Kael — and the smuggler is the one who recommended it. **Path B**: The doubled shipment goes through during Voss's reluctant window. Renn runs the courier route. The operation succeeds but Voss's fourth manifest revision creates a data anomaly Maret will find. **Path C**: Nils acts unilaterally — sends the shipment anyway, bypassing normal coordination. Devra scrambles. The operation succeeds sloppily. More detectable. | **Path A**: Kael is visibly shaken. Smuggler monologue: "I told Kael to push back. Now Nils is angry. Did I just put a target on Kael's back?" **Path B**: Smuggler monologue: "It worked. But Voss looks like he's going to break. And Maret was watching the manifests." **Path C**: Smuggler monologue: "I didn't decide. Nils decided for everyone. That's what happens when you don't choose." | **All paths produce detective-visible signals** — the question is timing and magnitude. Path B is loudest. Path C is messiest. Path A is quietest but pushes consequences to the FRIEND contradiction arc. | + +**Why this works:** The smuggler is no longer passive. The decision has immediate consequences (Beat 4) and delayed consequences (Beat 5) that feed into other triangles. Path B feeds Triangle 2 (Maret sees the anomaly). All paths affect Kael's emotional state, which feeds Triangle 4 (Worried Partner). The detective sees the EFFECTS without understanding the cause — which is exactly how occlusion-based investigation works. + +--- + +### 1d. Drin — Tier 2 Promotion Content + +If Gestalt confirms promotion, here's what the expanded Tier 2 profile contains. Drin currently has 3 axes (Want, Routine, Personality) + voice hint + function note. Tier 2 requires all 10 axes + voice sample + dual-lens notes + triangle notes + ~40-60 authored lines. + +#### Axes to add (7 new axes for Drin): + +**2. Secret / Vulnerability:** +- Surface secret: Gambling debt to Harek. Known to bar regulars, not formally reported. +- Deep secret: Drin knows his inspection negligence is being used by the ring. He's not looking away by accident anymore — after the first few "coincidental" clean inspections during flagged container processing, Drin understood the pattern. He's complicit, not just negligent. The debt is the lever; the knowledge is the chain. +- Vulnerability: Double bind. Reporting his compromise exposes him to institutional consequences (negligent inspections). Not reporting makes him deeper complicit each cycle. + +**3. Relationships:** +- Harek: Debt holder. The relationship started as friendly (card games, bar nights). The debt transformed it into a power dynamic Drin can't escape. Harek doesn't even know he's a tool — Harek thinks it's just a card game debt. +- Voss: Shift supervisor. Voss needs Drin's inspections to stay clean. Drin reads Voss's professional attention as surveillance. Every "good work, Drin" from Voss feels loaded. +- Maret Korr: Fellow hub worker. Maret processes the manifests Drin's inspections clear. They share break room time occasionally. Drin is aware that Maret is smart enough to connect his inspection reports to the manifest anomalies. He avoids prolonged interaction with her — not out of guilt, but out of fear. + +**4. Tolerance Threshold:** +- Low and declining. The gambling debt was manageable. The inspection negligence was uncomfortable. The realization that he's actively enabling a smuggling operation has pushed Drin past functional tolerance. He's operating on routine, not commitment. One external push — the detective asking about inspection protocols, Pell approaching him for help, Harek calling in the debt — could trigger a break. + +**5. Daily Routine** (expanded from current): +- 06:00-14:00: Day shift (maintenance rounds + dock inspections) +- Inspection windows: 3-4 per shift, scheduled by Voss. The "compromised" inspections happen during specific bay rotations that align with ring activity. Drin knows which ones. His body language changes during those inspections — faster, less thorough, eyes forward not searching. +- 10:30: Break room. Avoids Harek if present. Avoids Maret for different reasons. May eat alone. +- 14:00-16:00: Post-shift. Home or bar for card games (2x/week — the games continue because stopping would signal awareness). +- Night: Home, insomnia, reviewing what he ignored during the day. + +**6. Information Inventory:** +- Knows: Which containers receive cursory inspections (operational awareness). Harek's debt amount and terms. Voss's schedule patterns. The maintenance corridors' physical layout (pipe access, junction boxes, environmental systems — maintenance tech knowledge). Which corridors have Meridian dead spots. Maret's increasing scrutiny of manifests (observed her checking records after his shifts). +- Doesn't know: Ring structure (Drin doesn't know names, hierarchy, or the operation's scope — only that specific containers are expected to pass). Kael's involvement (Drin works different parts of the bay). Pell, Renn, Devra (has no ring contact — the compromise flows through the debt, not through recruitment). + +**7. Contentment:** +- Very low. Was moderate before the debt became leverage. Drin found satisfaction in maintenance work — fixing things, keeping systems running. The inspection compromise pollutes that satisfaction. Every clean report filed is a small self-betrayal. Drin's contentment trajectory: steady → declining → collapsed. He still finds momentary peace in pure maintenance tasks (pipe repair, wiring, physical work with no moral dimension). + +**8. Personality Traits** (expanded from "quiet, methodical, avoidant"): +- Primary: Avoidant — dodges confrontation, minimizes interaction, retreats into procedural work. +- Secondary: Competent — genuinely skilled at maintenance. The competence is his last source of self-respect. +- Tertiary: Guilty — the guilt is corrosive but suppressed. Manifests as insomnia, shortened social interactions, inability to maintain eye contact during compromised inspections. + +**9. Tell System** (new): + +| Tell | Trigger | Observable To | +|------|---------|---------------| +| Faster inspections on flagged bays | Ring-relevant containers scheduled for his inspection window | Smuggler (knows the pattern), Maret (if comparing inspection times to bay traffic), Detective (if tracking inspection duration data) | +| Avoids Harek at the bar | Debt anxiety — doesn't want casual interaction that reminds him of the leverage | Bar regulars, Detective (if tracking avoidance patterns) | +| Insomnia tells — tired eyes, slower response | Accumulated guilt disrupting sleep | Maret (works near him), Voss (notices the performance dip), anyone paying attention at the hub | +| Refuses to discuss inspection procedures | Topic triggers defensive response — "I run them by the book" | Detective (classic deflection — compare with Sera's topic avoidance), Resha (asks procedural questions, gets stonewalled) | + +**10. Skill Set** (expanded): +- Occupational: Environmental systems maintenance, pipe/wiring repair, dock inspection protocols, equipment calibration, facility safety checks. +- Ring-relevant (involuntary): Knows which inspection windows matter, can identify containers by routing codes, understands bay access schedules. +- Combat tag: No. +- Lattice capability: Baseline. + +#### Voice Sample (Tier 2: 2-3 lines): + +**Mood: Professional (baseline)** +*"Junction 4-C has a pressure variance. I'll need 20 minutes with the coupling. Standard repair."* + +**Mood: Evasive (pressed about inspections)** +*"I run them by the book. Every container gets the standard check. If you've got questions about the process, talk to Voss."* [Flat delivery, eye contact avoided] + +**Mood: Breaking (if directly confronted)** +*"What do you want me to say? I do the inspections. I file the reports. If there's a problem, it's not — I didn't — I just do the job."* [Voice cracks. Eyes down.] + +#### Content requirement: ~40-60 authored lines + +--- + +### 1e. Canonical Names — Which NPCs Need Them + +**5 NPCs already have full canonical names:** +- Kael Davan +- Sera Venn +- Lera Sessik +- Torek Lintar +- Maret Korr + +**1 NPC gains a full name through the rename:** +- Hael → Naia Tamm + +**1 off-stage NPC has a full canonical name:** +- Nils Davan (Kael's sibling — surname established) + +**10 NPCs currently have first names only and need canonical surnames:** + +| NPC | Current Name | Proposed Full Name | Surname Source | Rationale | +|-----|-------------|-------------------|---------------|-----------| +| Voss | Voss | Voss Harsk | Miri's surname list | Short-form first name + consonant-heavy surname. Harsk has the authoritative weight appropriate for a shift supervisor. | +| Devra | Devra | Devra Rosta | Miri's surname list | Mid-form + fluid surname. Rosta has the off-station feel (Devra was born on Orbital Manufacturing Platform 3). | +| Resha | Resha | Resha Neven | Miri's surname list | Mid-form + softer surname. Neven feels younger, matches Resha's 23-year-old new-hire energy. | +| Renn | Renn | Renn Kosse | Miri's surname list | Short-form + double-s surname. Kosse is one of Miri's example names — works as surname for a young courier. | +| Pell | Pell | Pell Talvik | Miri's surname list | Short-form + heritage-weight surname. Talvik is an "old founding family" name per Miri — ironic for someone cracking under pressure. | +| Harek | Harek | Harek Pellam | Miri's surname list | Mid-form + institutional-feeling surname. Pellam has the station security weight. | +| Drin | Drin | Drin Vassar | Extrapolated from Krenn phonetics | Short-form + mid-weight surname. Vassar follows the consonant-vowel-consonant pattern. Not on Miri's list — propose for Miri to validate. | +| Sess | Sess | Sess Torin | Extrapolated from Krenn phonetics | Short-form + compact surname. Torin echoes the utilitarian Krenn naming style. Propose for Miri validation. | +| Olin | Olin | Olin (no surname given) | Deliberate omission | Olin is the ambiguity NPC. A drifter who doesn't volunteer a surname is itself a data point. The detective notes the missing surname; the smuggler reads it as evasion. The absence is load-bearing. | +| Sabel | Sabel | Sabel (surname unknown) | Off-station character | Sabel operates from elsewhere in the Krenn System. No one in Sova uses Sabel's surname — the transactional relationship doesn't require it. If needed later, Miri can generate one. | +| Tav | Tav | Tav Lenn | Extrapolated from Krenn phonetics | Short-form + monosyllabic surname. Tav is young and peripheral — a short, working-class name pair. Propose for Miri validation. | + +**Notes for Miri:** +- 3 surnames are extrapolated (Vassar, Torin, Lenn) and need validation against Krenn phonetic rules. +- 2 NPCs (Olin, Sabel) deliberately omit surnames for narrative reasons. +- Olin's missing surname should be noted in the profile as a deliberate character detail, not a gap. + +**Implementation note:** Per D-036, Krenn convention is first-name-primary in social contexts. Adding surnames does NOT change how NPCs are addressed in dialogue. Surnames appear in: profile headers, formal identification (detective lattice queries), and the player's knowledge graph when they learn the full name. Most players will never use or need the surnames — they exist for world consistency and investigative depth. + +--- + +### 1f. Other Wiki Gaps Not in the Brief + +Having reviewed all 17 NPC profiles, the 3 location files, knowledge system files, and the authoring guide, here are gaps I identified beyond what the brief covers: + +#### Gap 1: PC-as-NPC Profiles Missing + +D-029 references "PC-as-NPC Concept A for v0.1" — the other playable character exists as an NPC (~20-30 authored items). **No wiki content exists for this.** We need: +- A smuggler-as-NPC profile (how the detective sees and interacts with the smuggler character when not played) +- A detective-as-NPC profile (how the smuggler sees and interacts with the detective character when not played) + +These are load-bearing for the dual-lens experience. The smuggler player should encounter "the Commission investigator" as a threatening NPC. The detective player should encounter "the dock worker" as an unremarkable colleague of Kael's. + +**Blocks:** Server entity spawning, dialogue systems, monologue content about the other PC. + +#### Gap 2: Monologue Content Pool — Zero Authored Lines + +The wiki profiles contain example monologue lines (embedded in contradiction arc descriptions), but there is no monologue content file. The authoring guide (`docs/wiki/authoring/monologue-guide.md`) defines the format, but no actual pool exists. Mellanie needs a minimum viable pool before the monologue system can be tested. + +**Blocks:** Monologue system integration testing, client UI monologue display. + +#### Gap 3: NPC Routine YAML Layer Missing + +D-028 and the SUMMARY specify a 2-layer routine format: YAML (engine-readable) + prose (human-readable). The wiki has the prose layer for all NPCs (Section 5 in each profile). The YAML layer does not exist. + +**Blocks:** Server NPC scheduling, routine deviation detection. + +#### Gap 4: Triangle Fork Decision Points Not Mechanically Specified + +Triangle 1's fork is designed above (1c). But Triangles 2-5 also need player decision points specified for both characters. Currently the triangles describe tension but not player agency. Specifically: +- **Triangle 2 (Worried Knowledge):** What decision does the detective make about Maret? (Approach as witness? Apply pressure? Wait?) What does the smuggler decide about Drin? (Reinforce the compromise? Try to relieve pressure?) +- **Triangle 3 (Bar Tensions):** What decision does either character make about Olin? (Investigate? Befriend? Ignore?) What about Torek's spending? (Smuggler: warn him? Detective: follow the money?) +- **Triangle 4 (Worried Partner):** Decision points exist implicitly (in the FRIEND contradiction arcs) but aren't formalized as explicit player choices. +- **Triangle 5 (Informant Question):** What does the smuggler decide about Pell? (Support, report, or manage?) How does the detective find Pell? + +**Blocks:** Storyteller event triggers, dialogue branching, consequence propagation. + +#### Gap 5: FactId Catalog — Smuggler Path Entries Sparse + +The fact catalog (`docs/wiki/knowledge/fact-catalog.md`) likely has detective-oriented entries (manifest discrepancies, behavioral observations). Smuggler-path FactIds need equivalent coverage: operational facts (who handles what), risk assessments, loyalty intelligence. + +**Blocks:** Knowledge graph population for smuggler character, monologue prerequisite checking. + +#### Gap 6: Location Files Lack Smuggler-Specific Detail + +The three location files (Terminal, Last Shift, Maintenance Corridors) describe physical spaces but don't map smuggling-relevant features: dead spots, timing windows, surveillance gaps, handoff points. The smuggler experiences these spaces as an operational landscape; the locations need that layer. + +**Blocks:** Environmental monologue triggers (smuggler entering a corridor and thinking about the operation), spatial gameplay design. + +#### Gap 7: Harek Is Tier 2 But Missing Triangle Membership + +Harek has a full Tier 2 profile (10 axes, voice sample, dual-lens notes) but is explicitly "not a triangle member." He's described as "external pressure on Drin" in Triangle 2. This is structurally odd — Harek does Tier 2 work (the leverage dynamic is essential to Triangle 2 and Triangle 5) without being IN a triangle. If Drin is promoted, Harek's role as the unseen fourth wall of Triangle 2 should be formalized — either as a triangle member or as a named structural role ("external pressure node"). + +--- + +## Topic 2: v0.1 NPC Architecture — Pattern/Motivation Mapping + +The workshop brief asks whether the existing 17 NPCs implicitly match the three-system NPC architecture. **Yes — the profiles were designed before the formal system was named, but the patterns are already present.** Let me make the mapping explicit. + +### Full NPC → Pattern + Motivation Assignment + +| # | NPC | Tier | Pattern | Motivation | Implicit Evidence in Current Profile | +|---|-----|------|---------|-----------|--------------------------------------| +| 1 | Kael Davan | 1 | **FRIEND** | **OPERATOR** | Definitionally THE FRIEND (D-034). Runs dock operations + ring cargo handling. Competent, goal-oriented. | +| 2 | Sera Venn | 1 | **FRIEND** | **WITNESS** | Definitionally THE FRIEND (D-034). Accidentally witnessed manifest discrepancies. Carries information she can't act on. | +| 3 | Naia Tamm | 1* | **MIRROR** | **CIVILIAN** | Established as THE MIRROR (SUMMARY). No contradiction, no secret. Uninvolved in conspiracy. Living their life. | +| 4 | Voss | 2 | **SYSTEM** | **HANDLER** | Institutional embodiment — the shift supervisor IS the legitimate system. Manages operations, applies scheduling pressure. Procedure-citing under stress. | +| 5 | Lera Sessik | 2 | **ANCHOR** | **OPERATOR** | Embodies contentment — "Is this enough? Yes, if the bar stays open." Running the bar is her life. Genuine answer to the thematic question. | +| 6 | Torek Lintar | 2 | **CATALYST** | **CIVILIAN** | His spending forces others to react. Disrupts the bar's equilibrium. Not managing anything — just living visibly. The disruption is unintentional. | +| 7 | Devra | 2 | **THRESHOLD** | **OPERATOR** | Gatekeeper between the local ring and the external supply chain. Running the external coordination. The detective must get past Devra to reach the upstream network. | +| 8 | Maret Korr | 2 | **WITNESS** | **SKEPTIC** | Accidentally witnessed manifest discrepancies (parallel to Sera but from the data side). Doubts the system — the numbers don't add up, and she can't ignore them. | +| 9 | Resha | 2 | **CATALYST** | **CIVILIAN** | Her questions force hub workers to react defensively. New energy disrupting an equilibrium. Not suspicious — just curious in the wrong direction. | +| 10 | Renn | 2 | **THRESHOLD** | **OPERATOR** | Physical gatekeeper — the courier who controls corridor access. Running the transport operation. The detective must trace Renn's routes to map the physical smuggling chain. | +| 11 | Pell | 2 | **CATALYST** | **TURNCOAT** | His potential defection forces a crisis. Loyalty actively shifting. The most dangerous NPC in the ring because his decision cascades everywhere. | +| 12 | Harek | 2 | **ANCHOR** | **HANDLER** | Embodies complacent contentment — "Is this enough? Yes, absolutely." But unconsciously manages Drin through debt leverage. The comfortable man whose comfort creates a tool for others. | +| 13 | Drin | 3→2 | **SYSTEM** | **TURNCOAT** | Institutional embodiment — the dock inspector IS the inspection system. His compromise means the system itself is corrupted. Loyalty shifting under pressure (could crack and cooperate). | +| 14 | Sess | 3 | **NOBODY** | **CIVILIAN** | Social wallpaper. Sees everything, says nothing. Background that could become foreground through player investment. Living their life. | +| 15 | Olin | 3 | **CATALYST** | **CIVILIAN** | New variable forcing evaluation. Ambiguous intent creates productive uncertainty. Uninvolved (as far as anyone knows). | +| 16 | Sabel | 3 | **GHOST** | **OPERATOR** | Absent power — off-station, never fully present. Shapes events through shipments. Running the upstream supply operation. | +| 17 | Tav | 3 | **NOBODY** | **OPERATOR** | Peripheral, barely visible. Running lookout operations at the lowest level. Background ring member who the detective might never notice. | + +*Naia Tamm at "Tier 1 (MIRROR)" — not THE FRIEND tier but production emotional depth. + +#### Off-stage NPC: + +| NPC | Pattern | Motivation | Notes | +|-----|---------|-----------|-------| +| Nils Davan | **GHOST** | **HANDLER** | Absent authority applying pressure through intermediaries. Classic GHOST+HANDLER composition. | + +### Pattern Coverage Analysis + +| Pattern | v0.1 Count | NPCs | Notes | +|---------|-----------|------|-------| +| FRIEND | 2 | Kael, Sera | Core v0.1 requirement (D-034) | +| MIRROR | 1 | Naia Tamm | First MIRROR instance | +| ANCHOR | 2 | Lera, Harek | Both embody "is this enough?" differently | +| GHOST | 2 | Sabel, Nils | Both off-stage — absent power | +| CATALYST | 3 | Torek, Resha, Olin (+ Pell) | Pell is CATALYST by effect, TURNCOAT by motivation | +| THRESHOLD | 2 | Devra, Renn | Both gatekeep access to the operation | +| SYSTEM | 2 | Voss, Drin | Both ARE institutional processes (scheduling, inspection) | +| NOBODY | 2 | Sess, Tav | Minimum viable NOBODY presence | +| REMNANT | 0 | — | **Not represented in v0.1.** No character embodies "remembers when things were different." Acceptable — REMNANT is a long-game pattern that requires historical depth v0.1 doesn't have. | + +**Missing: REMNANT.** This is fine for v0.1. Sova Transit District is 40 years old — there's no deep history for a REMNANT to remember. REMNANT patterns belong on older worlds (mature/ancient drift stages) where someone lived through the founding generation. + +### Motivation Coverage Analysis + +| Motivation | v0.1 Count | NPCs | +|-----------|-----------|------| +| HANDLER | 3 | Voss, Harek, Nils | +| WITNESS | 2 | Sera, Maret | +| TURNCOAT | 2 | Pell, Drin | +| CIVILIAN | 5 | Naia Tamm, Torek, Resha, Olin, Sess | +| OPERATOR | 5 | Kael, Lera, Devra, Renn, Tav | +| SKEPTIC | 1 | Maret | + +**SKEPTIC is underrepresented.** Only Maret doubts the system. But this is mechanically correct — Sova's culture is pragmatic, mind-your-business. Questioning is rare. The detective IS the external skeptic. One internal SKEPTIC (Maret) is enough for v0.1. + +### Composition Highlights + +The most dramatically rich compositions in v0.1: + +| Composition | NPC | Why It Works | +|------------|-----|-------------| +| FRIEND+OPERATOR | Kael | "I was your friend AND I was running the operation you're investigating." Identity betrayal at maximum. | +| FRIEND+WITNESS | Sera | "I was your friend AND I was sitting on evidence the whole time." Concealment as betrayal. | +| MIRROR+CIVILIAN | Naia Tamm | "I was the one innocent person, and the conspiracy touched me anyway." Innocence endangered. | +| SYSTEM+TURNCOAT | Drin | "The system itself is corrupt, and the person embodying it might break." Institutional rot. | +| CATALYST+TURNCOAT | Pell | "The person forcing the crisis is the person whose loyalty is shifting." Maximum instability. | +| GHOST+HANDLER | Nils | "The invisible authority whose pressure creates every visible consequence." Classic absent power. | +| ANCHOR+HANDLER | Harek | "The comfortable man whose comfort creates tools for others." Unconscious complicity. | + +--- + +## Topic 5: Scope Boundary — What Content Is Missing That Blocks Implementation? + +### Content that MUST be authored before coding starts + +These are items where the server or client team cannot proceed without authored content: + +| # | Missing Content | Blocks | Priority | Team | +|---|----------------|--------|----------|------| +| 1 | **Smuggler-lens sections for all 17 NPC profiles** | Smuggler character knowledge graph population, dual-lens monologue selection, smuggler EntityKnowledge examples | Critical | Copy (Paula + Mellanie) | +| 2 | **PC-as-NPC profiles** (smuggler seen by detective, detective seen by smuggler) | Server entity spawning for second character, dialogue with other PC, monologue about other PC | Critical | Copy (Paula) | +| 3 | **Minimum monologue pool** (~50-80 lines per character) | Monologue system integration testing, client UI display, tag taxonomy validation | Critical | Copy (Mellanie) | +| 4 | **NPC routine YAML layer** (engine-readable schedules for all 17 NPCs) | Server NPC scheduling, routine-based movement, deviation detection | Critical | Copy (Paula) + Server (Dudley) format spec | +| 5 | **Triangle fork decision points** (mechanically specified for all 5 triangles) | Storyteller event triggers, dialogue branching, consequence system | High | Copy (Paula + Gestalt) | +| 6 | **Smuggler EntityKnowledge attribute definitions** (8 new keys) | Smuggler knowledge graph implementation, smuggler monologue prerequisites | High | Copy (Paula) + Server (Dudley) | +| 7 | **FactId catalog — smuggler path entries** | Knowledge graph population, monologue prerequisite validation | High | Copy (Paula) | +| 8 | **Content file schema** (wiki → engine-readable format) | Content loader development, hot-reload testing | High | Tyre + Dudley | +| 9 | **Location files — smuggler operational layer** (dead spots, timing windows, handoff points) | Environmental monologue triggers, spatial gameplay, smuggler navigation | Medium | Copy (Paula + Miri) | +| 10 | **Naia Tamm MIRROR-depth content** (~50-70 authored lines) | MIRROR pattern validation, emotional baseline testing | Medium | Copy (Mellanie) | +| 11 | **Nils Davan stub** (as designed in 1c above) | Triangle 1 fork implementation, GHOST pattern reference | Medium | Copy (Paula) — draft provided above | +| 12 | **Drin Tier 2 expansion** (if promoted) | Triangle 2 + Triangle 5 implementation, TURNCOAT pattern testing | Medium | Copy (Paula) — draft provided above | + +### Content that is nice-to-have but does NOT block implementation + +- Full canonical names (surnames) — cosmetic, knowledge graph uses canonical_id not surnames +- NOBODY tier promotion content — explicitly OUT for v0.1 +- Archetype transition mechanics — v0.2+ +- Secondary contraband variation — deferred +- Gate topology — not relevant to single-district v0.1 + +### The honest truth about the gap + +The wiki is detective-complete and smuggler-incomplete. Every NPC profile has a "How the Detective Sees [NPC]" section that provides enough mechanical detail for implementation. The smuggler sections are 1-2 sentence monologue notes. This means the server team could build and test the detective's knowledge graph, monologue system, and interaction model from existing content. They CANNOT do the same for the smuggler. + +The smuggler-lens content is the single largest content gap blocking v0.1 implementation. It's not a small fix — it requires adding structured operational data, moral arc phases, and smuggler-specific EntityKnowledge attributes to every NPC profile. Estimated scope: 17 NPC profiles x ~30 minutes of writing each = ~8-10 hours of focused copy work. + +But what SUSTAINS that across hours of play is the smuggler moral arc. The detective has a clear progression (investigate → discover → confront → resolve). The smuggler needs an equally clear progression (participate → doubt → reckon → compromise). Every NPC interaction should advance or complicate one of those phases. The smuggler-lens sections need to map every NPC to a moral arc phase, not just an operational assessment. + +Let me complicate this further: the smuggler and detective paths aren't just parallel — they're entangled through the NPCs. When the smuggler makes a decision about Pell (Triangle 5), the consequences affect what the detective sees when investigating Pell's disappearance. When the detective pressures Maret (Triangle 2), the smuggler sees Maret's increased anxiety. The cross-character consequence propagation is the v0.1 proof of concept. If we ship smuggler content that doesn't map to detective consequences (and vice versa), we've built two separate games in the same district, not one occlusion-based experience. + +**Recommendation:** Prioritize items 1-4 (smuggler-lens, PC-as-NPC, monologue pool, routine YAML) as Sprint 3 copy team deliverables. Items 5-7 can proceed in parallel with server architecture work. Items 8-12 are dependencies that should be ticketed and scheduled. diff --git a/docs/workshops/v01-content-scoping/round1-si.md b/docs/workshops/v01-content-scoping/round1-si.md new file mode 100644 index 000000000..4c9cff24d --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-si.md @@ -0,0 +1,233 @@ +# Round 1 — SI (Project Manager) +## Topic 6: Ticket Landscape for v0.1 Content Scoping + +--- + +## 1. Round 2 Ticket List Review — What Survives the 300-World Reframe? + +The Round 2 list (`si-ticket-changes.md`) proposed 29 new tickets across 4 groups. The reframe changes their priority and framing but kills fewer than expected — because v0.1 is hand-authored, most content tickets are still needed. The key shift: every structure created now must be a generator template later. + +### 1a. Tickets That Survive Unchanged + +These are v0.1 essentials unaffected by the 300-world reframe: + +| R2 ID | Title | Team | Rationale | +|-------|-------|------|-----------| +| A1 | Apply canonical full names to all NPC wiki pages | copy | Required for v0.1 content. Names are content, not metadata. | +| A2 | Rename Hael → Naia Tamm across all wiki files | copy | Unanimous decision. Blocks all Naia content. | +| A7 | Add 5 smuggler-specific entity attributes | copy | Dual-lens parity is a v0.1 requirement (D-027). | +| A8 | Standardize location shortcodes in monologue-guide | copy | Bug fix. Blocks monologue authoring. | +| A10 | Stub NPC profile for Nils Davan | copy | Required for Triangle 1 fix. | +| A11 | Fix Triangle 1 — add Volume Escalation decision | copy | v0.1 requires 5 triangles with forks (D-027, D-039). | +| B2 | THE MIRROR pattern spec | copy | Required for Naia Tamm. Also becomes a generator template. | +| B3 | PC-as-NPC unified spec | copy | Required for two-character proof (D-027). | +| B4 | Smuggler voice card + moral arc spec | copy | Dual-lens parity. Also becomes a generator template. | +| B5 | NPC-format briefs for both PCs | copy | Required for wow moment #4 (Divergence Reveal). | +| C1 | Content directory structure design doc | copy | Prerequisite for all content conversion. | + +### 1b. Tickets That Survive — Reframed + +These still need doing but their framing shifts to "generator template first": + +| R2 ID | Title | Team | Reframe | +|-------|-------|------|---------| +| A3 | Create wiki/cultural-groups/krenn-system.md | copy | Becomes the reference implementation for seed.yaml + brief.md dual artifact. Overlaps with existing #319. **Merge** A3 into #319 with expanded scope. | +| A4 | Create regional style guide template | copy | Becomes the seed.yaml template definition, not just a wiki template. Every field must map to a generator input. | +| A6 | Add smuggler-perspective FactId progression text | copy | Survives. Blocked by B4 (smuggler voice). | +| A9 | Add Thematic Question field to Tier 1 profiles | copy | Low priority. Nice-to-have for authoring guidance. | +| A12 | Create environmental text examples | copy | Needed for wow moments (#1 Arrival, #5 News Ticker). | +| B1 | NPC Authoring Style Guide | copy | Reframed: this becomes the NPC generator specification. The style guide IS the generator input format. Most complex ticket — many blockers. | +| C2 | Create content/ directory skeleton | server | Schema must be 300-world compatible from day one. | +| C3 | Content schema definitions | server | Schemas become validation rules for generated content too. | +| C4-C8 | Enum, attribute, pool, template, triangle YAMLs | server | All survive. These ARE the v0.1 content in engine-readable format. | +| C9 | Implement make validate-content CLI | server | Validates hand-authored v0.1 AND future generated output. | +| C10 | Pre-commit FactId typo check | ci | Cheap safety net. Survives. | +| D2 | Seed configuration schema | server | Needed for v0.1 (even with 1-candidate pools). | + +### 1c. Tickets That Are Obsolete or Deferred + +| R2 ID | Title | Disposition | Rationale | +|-------|-------|-------------|-----------| +| A5 | Add Cultural Groups section to wiki/index.md | **Defer** (v0.2+) | Wiki index restructuring is cosmetic. Content itself matters more. | +| D1 | cultural_gate modifier design doc | **Defer** (v0.2+) | Not in v0.1 IN list. Cultural gating is a generator-era feature. | +| D3 | Secondary contraband variant FactId text | **Kill** | Explicitly OUT in v0.1 scope ("Secondary contraband variation" listed as v0.2+). | + +**Score: 26 survive, 1 deferred, 1 deferred, 1 killed = 26 actionable for v0.1.** + +--- + +## 2. Existing Ticket Updates + +These existing tickets need status changes based on workshop context: + +| ID | Title | Current Status | Action | +|----|-------|---------------|--------| +| #368 | Knowledge vocabulary for v0.1 content | in_progress (Sprint 2) | **Close when wiki review is accepted.** The wiki at `docs/wiki/` is the output. Workshop is the review. | +| #369 | Wiki Review Workshop Outputs (epic) | backlog | **Repurpose** as parent epic for v0.1 content scoping tickets. Update description to reflect this workshop's scope. | +| #301 | Wiki taxonomy and structure | backlog | **Assign to copy team.** Downscope for v0.1: focus on Sova district structure only. Full 500+ world taxonomy is a generator-era concern. | +| #319 | Krenn System wiki entry | backlog | **Merge with A3.** Expanded scope: wiki page + seed.yaml reference implementation. Assign to copy. | +| #261 | Dual Lens Authoring Guide | backlog | **Still critical.** Workshop confirms it blocks all content authoring (#190, #191, #192, #297, #298, #299, #300). Assign to copy. Sprint 3 target. | +| #310 | Character voice speech patterns | backlog | **Overlaps with B4** (smuggler voice card). Merge or make B4 block #310. | +| #306 | News ticker / Meridian feed content | backlog | **Overlaps with D4.** D4 expands scope from 20-30 to 40-50 headlines. Update #306 description. | +| #326 | YAML content loader | backlog | **Assign to server.** Blocked by C1 + C2. Sprint 4 target (after content exists). | + +--- + +## 3. New Tickets Needed (Not in Round 2 List) + +The IN list and workshop topics reveal gaps not covered by the Round 2 tickets: + +| Proposed ID | Title | Type | Team | Priority | Description | Dependencies | +|-------------|-------|------|------|----------|-------------|--------------| +| NEW-1 | NPC pattern/motivation mapping for v0.1 | task | copy | high | Assign System A pattern + System B motivation to all 17 Sova NPCs. Output: mapping table in wiki. Informs all NPC content authoring and server component design. | None | +| NEW-2 | Drin tier promotion evaluation | task | copy | medium | Evaluate whether Drin should be promoted from Tier 3 to Tier 2. Drin participates in Triangle 2 and Triangle 5 and is compromised by Harek — Tier 2 work in a Tier 3 profile. If promoted, expand profile. | NEW-1 | +| NEW-3 | Wiki → YAML content conversion (Sova NPCs) | story | copy | high | Convert 17 NPC wiki profiles to engine-readable YAML in content/districts/sova-transit/npcs/. Must match schema from C3. First concrete content files. | C1, C2, C3 | +| NEW-4 | Monologue content pool — minimum viable set | story | copy | high | Author minimum monologue line pool to avoid repetition in a 30-minute session. Size TBD by Mellanie (Topic 4 answer), but estimate 80-120 lines covering: arrival observations, NPC routine commentary, environmental flavor, contradiction hints, idle reflections. Dual-lens (smuggler + detective variants). | #261, B4 | +| NEW-5 | v0.1 wow moment content checklist | task | copy | high | Map D-039's 6 wow moments to specific content deliverables. Track which tickets deliver which moments. Acceptance criteria: every wow moment has identified content backing it. | None | +| NEW-6 | PC-as-NPC content authoring (authored lines) | story | copy | high | Author the actual content for PC-as-NPC: 5-7 routine entries per character, 3-5 observation monologue lines per character when seeing the other PC, reverse dual-lens notes. ~20-30 authored items total. Distinct from B5 (spec) — this is the writing. | B3, B5 | +| NEW-7 | Interaction model minimum spec | story | copy | medium | Document the minimum interaction verbs for v0.1: walk, talk, observe, overhear. What triggers each? What UI does each need? What server events? Bridges Topic 4 into implementation tickets for server/client. | None (informed by workshop Topic 4 answers) | + +--- + +## 4. Sprint Assignment + +### Sprint 3 (Know) — Copy Team + +Sprint 3's goal: "Player can observe NPCs, track knowledge, interact." The server team needs content to load. Copy's Sprint 3 is the **content foundation sprint**. + +**Sprint 3 copy tickets (proposed):** + +| Priority | Ticket | Rationale | Blocked By | +|----------|--------|-----------|------------| +| P0 | A2: Hael → Naia Tamm rename | Unblocks all Naia content. Quick win. | — | +| P0 | A1: Apply canonical full names | Unblocks content conversion. Quick win. | — | +| P0 | A7: Smuggler entity attributes | Unblocks smuggler-lens content. | — | +| P0 | C1: Content directory structure design | Unblocks all content conversion. | — | +| P0 | #261: Dual Lens Authoring Guide | Blocks 9 downstream tickets. Critical path. | — | +| P1 | A10: Nils Davan stub | Unblocks A11. | — | +| P1 | A11: Triangle 1 fix | Needed for 5-triangle requirement. | A10 | +| P1 | B2: MIRROR pattern spec | Unblocks Naia content. Generator template. | — | +| P1 | B4: Smuggler voice + moral arc | Unblocks dual-lens authoring. Generator template. | A7 | +| P1 | NEW-1: NPC pattern/motivation mapping | Informs all NPC content + server components. | — | +| P1 | NEW-5: Wow moment content checklist | Tracking ticket. Ensures nothing falls through. | — | +| P2 | A8: Shortcode fix | Cleanup. Blocks monologue authoring quality. | — | +| P2 | B3: PC-as-NPC spec | Unblocks B5 and NEW-6. | A7 | +| P2 | NEW-7: Interaction model minimum spec | Bridges workshop into server/client tickets. | — | + +**Sprint 3 server tickets (content-related, proposed additions):** + +| Priority | Ticket | Rationale | Blocked By | +|----------|--------|-----------|------------| +| P1 | C2: Create content/ directory skeleton | Unblocks all YAML content files. | C1 | +| P1 | C3: Content schema definitions | Unblocks validation + content conversion. | C1 | + +### Sprint 4+ (later sprints) + +| Ticket | Team | Rationale for Deferral | +|--------|------|----------------------| +| A3/merged #319: Krenn brief + seed.yaml | copy | Needs A4 template first. Not blocking Sprint 3 content. | +| A4: Regional style guide / seed template | copy | Generator-era format. v0.1 can use wiki directly. | +| A6: Smuggler FactId progression text | copy | Blocked by B4. Sprint 4 if B4 completes Sprint 3. | +| A9: Thematic Question field | copy | Low priority authoring guidance. | +| A12: Environmental text examples | copy | Sprint 4. Needed for wow moment #1 but not blocking server. | +| B1: NPC Authoring Style Guide | copy | Most complex ticket with 5+ blockers. Sprint 4-5 assembly. | +| B5: PC NPC-format briefs | copy | Blocked by B3. Sprint 4. | +| NEW-3: Wiki → YAML conversion | copy | Blocked by C1, C2, C3. Sprint 4. | +| NEW-4: Monologue content pool | copy | Blocked by #261, B4. Sprint 4-5. | +| NEW-6: PC-as-NPC content authoring | copy | Blocked by B3, B5. Sprint 5. | +| C4-C8: Content YAML files | server | Blocked by C2. Sprint 4. | +| C9: validate-content CLI | server | Blocked by C3. Sprint 4. | +| C10: FactId pre-commit | ci | No blockers but low urgency. Sprint 4. | +| D2: Seed configuration schema | server | Sprint 4-5. Needed before game-start flow. | +| #326: YAML content loader | server | Blocked by C2. Sprint 4-5. | + +--- + +## 5. Dependency Chain — Critical Path + +``` +Sprint 3 Critical Path (Copy): + +#261 (Dual Lens Guide) ─────────────────────────────┐ + ├── blocks #297 (FRIEND: Kael) │ + ├── blocks #298 (FRIEND: Sera) │ + ├── blocks #299 (Opening hook: smuggler) │ + ├── blocks #300 (Opening hook: detective) │ + ├── blocks #310 (Voice speech patterns) │ + ├── blocks NEW-4 (Monologue pool) │ + └── blocks #190, #191, #192 (Content packs) │ + │ +A7 (Smuggler attributes) ──┬── blocks B4 (Voice+arc) │ + ├── blocks B3 (PC-as-NPC) │ + │ │ +B4 (Smuggler voice+arc) ──┼── blocks A6 (FactId text)│ + │ blocks NEW-4 │ + │ │ +B3 (PC-as-NPC spec) ──────┼── blocks B5 (PC briefs) │ + │ blocks NEW-6 (Content) │ + │ │ +C1 (Content dir design) ──┬── blocks C2 (skeleton) │ + ├── blocks C3 (schemas) │ + │ │ +C2 (skeleton) ────────────┼── blocks C4-C8 (YAMLs) │ + │ blocks NEW-3 (convert) │ + │ blocks #326 (loader) │ + │ │ +C3 (schemas) ─────────────┼── blocks C9 (validator) │ + │ blocks NEW-3 (convert) │ + │ +A10 (Nils stub) ──────────┼── blocks A11 (Triangle 1) │ + │ +B2 (MIRROR spec) ─────────┼── blocks A9 (Thematic Q) │ + │ blocks Naia authoring │ +``` + +**The two critical chains for Sprint 3 are:** + +1. **Content authoring chain:** #261 → B4 → A6 → content packs → monologue pool +2. **Content architecture chain:** C1 → C2/C3 → C4-C8/NEW-3 → #326 (loader) + +Both chains must begin in Sprint 3 even though they won't complete until Sprint 4-5. Sprint 3's job is to lay the foundation: get #261, C1, A7, and B2 done. + +--- + +## 6. IN List → Ticket Coverage Map + +| v0.1 IN List Item | Existing Tickets | New Tickets Needed | Status | +|-------------------|------------------|--------------------|--------| +| WASD movement + collision | Sprint 1-2 (done) | — | Covered | +| Context-sensitive interaction | #240 (Sprint 3) | NEW-7 (spec) | Partially covered — needs spec | +| Monologue system | #45, #119, #120, #122, #253 | NEW-4 (content pool) | Architecture covered, content gap | +| Knowledge graph | #351, #368, #309 | — | Covered | +| 2 playable characters | #57, #180, #182 | — | Covered (architecture) | +| THE FRIEND arc (Kael, Sera) | #297, #298 (blocked by #261) | — | Covered but blocked | +| 5 triangles with forks | #42, #106, #188, #250 | A11 (Triangle 1 fix), C8 (YAML) | Partially covered — weak Triangle 1 | +| 6 wow moments (D-039) | No dedicated tickets | NEW-5 (checklist) | Gap — needs tracking ticket | +| 3 social sites | #54, #190, #191, #192 | C7 (YAML templates) | Covered but blocked by #261 | +| NPC routines | #88, #101 (Sprint 3) | — | Covered (server) | +| Dual-lens parity | #261 | A7, B4, A6 | Major gap — smuggler lens incomplete | +| PC-as-NPC | — | B3, B5, NEW-6 | Gap — no tickets exist yet | +| Content directory | #326 (loader only) | C1, C2, C3 | Gap — structure undefined | +| Content validation | — | C9, C10 | Gap — no validation exists | + +--- + +## 7. Summary + +**Round 2 list scorecard:** 26 of 29 tickets survive (1 killed: D3 secondary contraband; 2 deferred: A5 wiki index, D1 cultural_gate). + +**New tickets proposed:** 7 (NEW-1 through NEW-7). + +**Sprint 3 copy team:** 14 tickets proposed. Critical path starts with #261 (Dual Lens Guide) and C1 (Content directory design). + +**Biggest risk:** #261 is the single biggest blocker in the entire content pipeline. It blocks 9 tickets directly and cascades into everything. If it slips, Sprint 4-5 content authoring is delayed. Recommend it gets assigned immediately with Paula as owner. + +**Action items for Round 2:** +1. Other agents answer Topics 1-5 — their answers refine NEW-1 (pattern mapping), NEW-4 (pool size), NEW-7 (interaction minimum) +2. Lead approves ticket list +3. SI creates tickets +4. Qatux documents scope decisions + +--- + +*Compiled by SI. Source: Round 2 ticket list, current ticket database (369 tickets), workshop brief, decisions/scope.md (D-027, D-039).* diff --git a/docs/workshops/v01-content-scoping/round1-stig.md b/docs/workshops/v01-content-scoping/round1-stig.md new file mode 100644 index 000000000..111af7485 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-stig.md @@ -0,0 +1,373 @@ +# Round 1 Response: Stig (UI Developer) +## v0.1 Content Scoping Workshop + +--- + +## Topic 3: What Does the Client Need from Content Files? + +The client is a renderer. It doesn't decide what happens — the server does. So the client's content needs are narrow: display strings, visual parameters, and layout hints. Everything else stays server-side. + +### What the client loads directly + +| Data | Format | Why | +|------|--------|-----| +| **NPC display names** | `display_name: "Kael Davan"` | Name label above entity rectangle. Must be the name the *player character* knows — "Dock Worker" before identification, "Kael Davan" after. Server sends the current display name per entity via state update, not the content file. | +| **Location display names** | `display_name: "The Terminal"` | HUD location indicator, minimap labels. Short names only — "The Terminal", not "Sova Transit Logistics Hub, Bay 7-C". | +| **Monologue text** | `text: "Morning shift. Recycled air..."` | Rendered by the monologue display system. The server selects the line; the client receives the `id` + `text`. Client doesn't evaluate prerequisites. | +| **Dialogue text** | `text: "Container 4471 sat in temp..."` | Rendered in the dialogue panel. Server selects, client displays. | +| **Entity color palette** | `relationship_colors` map in a client config | D-033 palette. Client derives color from `RelationshipState` sent by server. Not per-NPC content. | +| **Time of day** | Phase enum: `morning`, `afternoon`, `evening`, `night` | HUD clock display. Server sends current phase. Client renders as text and ambient lighting tint. | + +### What the client does NOT need + +- NPC profiles, axes, secrets, triangle definitions — server-only +- FactId catalog, prerequisite maps — server-only evaluation +- Access tiers, trust levels — server filters before sending to client +- NPC routines/schedules — server simulates, client receives position updates +- Knowledge graph state — server tracks, sends display-relevant slices + +### Format recommendation + +Client content should be a thin layer. I don't need to parse the full wiki YAML. What I need is: + +```yaml +# content/client/strings.yaml +locations: + terminal: + display_name: "The Terminal" + short_name: "Terminal" + minimap_label: "T" + bar: + display_name: "The Last Shift" + short_name: "Last Shift" + minimap_label: "LS" + corridor: + display_name: "Corridor B-7" + short_name: "B-7" + minimap_label: "B7" + +hud: + time_phases: + morning: "Morning Shift" + afternoon: "Afternoon" + evening: "Evening" + night: "Night Cycle" +``` + +Monologue and dialogue text can arrive as part of the server state update — `{type: "monologue", id: "terminal_m_001", text: "Morning shift..."}`. The client doesn't need the full YAML pools on disk. Server selects, client renders. This keeps the client thin and prevents any information leakage (the client literally cannot show what it doesn't have). + +**Bottom line:** The client needs display strings + the server's state stream. It should never have access to content it shouldn't be rendering. Every pixel of information passes through the server's information boundary first. + +--- + +## Topic 4: UI Elements for the Minimum Interaction Model + +I'll walk through each element: what it looks like, where it sits, how the player reaches it. + +### 4.1 The Screen Layout (Overview) + +``` ++------------------------------------------------------------------+ +| | +| | +| | +| GAME WORLD | +| (full viewport) | +| | +| | +| | +| [MONOLOGUE] | +| bottom-left, 1-2 lines | +| | +| [INTERACTION PROMPT] | +| near-player, contextual | +| | ++------------------------------------------------------------------+ +| LOCATION | TIME | | [MINIMAP] | +| The Terminal| Morning | | (insert) | +| | | | | ++------------------------------------------------------------------+ +``` + +The game world fills the viewport. UI elements float over it, positioned to stay out of the player's sightline toward the center of the screen where the action is. + +**Core principle: the center is sacred.** Nothing permanent lives in the center third of the screen. That's where the character is, where NPCs are, where the player looks. UI lives at the edges and appears only when relevant. + +### 4.2 Interaction Prompt + +**What it is:** A single-action context label that appears when the player is close enough to interact with something. + +**Position:** Offset from the target entity, above and slightly right. Tracks the entity position in world space (not screen-fixed). Small — 12-14px equivalent at reference resolution. + +**Appearance:** +``` + [Talk] <- NPC within interaction range + Kael Davan <- entity name (if known) + [====] <- entity rectangle +``` + +Or for objects: +``` + [Examine] + Manifest Terminal + [====] +``` + +**Behavior:** +- **Appears** when player enters interaction radius (~1.5 tiles) AND has line of sight +- **Disappears** when player moves away or breaks LOS +- **Changes based on context:** The same NPC shows "Talk" normally, "Ask about..." after contradiction discovery, or nothing if no interaction is available +- **Single action.** No menus. No radial wheels. The server decides what the primary interaction is based on entity state + player knowledge. The prompt shows ONE thing. Press [E] or [Space] to do it. +- If multiple interactions are available, the prompt shows the most relevant one. The player gets a *decision* about whether to interact, not a *menu* of options. + +**Why single-action:** Too many clicks. A radial menu means the player stops, reads, selects, then engages. A single prompt means: approach, press, you're in. The game should feel like walking into a conversation, not operating a database. + +**Edge case — multiple valid actions:** If the player needs to choose between "Talk" and "Examine" on the same entity, the prompt changes based on proximity angle or a simple toggle (tap [E] = primary, hold [E] = secondary). But for v0.1, I'd argue: default to "Talk" for NPCs, "Examine" for objects, and handle the edge cases when they arise. + +### 4.3 Dialogue Display + +**What it is:** How conversation text appears once the player initiates dialogue. + +**Position:** Bottom-center of the screen, overlaying the game world. The world stays visible behind it — the player can still see NPCs moving, can still read the room. + +**Layout:** +``` ++------------------------------------------------------------------+ +| | +| GAME WORLD | +| (still visible, slightly dimmed) | +| | +| | ++------------------------------------------------------------------+ +| | +| KAEL DAVAN | +| "Container 4471 sat in temp storage overnight. | +| Scheduling backed up again." | +| | +| > [Continue] [End conversation] | +| | ++------------------------------------------------------------------+ +``` + +**Specifications:** +- **Width:** ~60% of screen width, centered horizontally +- **Height:** Variable, 3-5 lines maximum. If the line is longer, it scrolls on input +- **Background:** Semi-transparent dark panel (80% opacity). Not opaque — the world behind bleeds through. The conversation is happening IN the world, not in a separate screen +- **Speaker name:** Top-left of the panel, in the entity's relationship color (D-033). Teal if unknown, green if friendly, amber if person of interest +- **Text:** Off-white, clean sans-serif, left-aligned. One NPC line at a time +- **Advance:** [Space] or [E] advances to next line. No auto-advance — the player controls the pace +- **Exit:** [Escape] or [Q] ends conversation at any point. The NPC reacts (server determines reaction based on conversation progress) + +**What this is NOT:** +- No branching dialogue trees visible on screen. The server determines what the NPC says next based on access tier, trust, and state. The player experiences a conversation, not a menu +- No portraits. Top-down game — we see the entities. A portrait would be a second representation that contradicts the top-down perspective +- No response selection in v0.1. The conversation flows. The player's choices are: continue, leave, or (post-discovery) confront. Confrontation is a separate interaction prompt, not a dialogue option + +### 4.4 Monologue Display + +**What it is:** The player character's internal voice. Text that represents what the character thinks, notices, feels. + +**Position:** Bottom-left of the viewport, above the HUD bar. Offset from the dialogue panel position so they never overlap. + +**Layout:** +``` ++------------------------------------------------------------------+ +| | +| GAME WORLD | +| | +| | +| | +| | +| +-----------------------------------------+ | +| | Kael's here. Good -- I was starting | | +| | to worry. | | +| +-----------------------------------------+ | +| | ++------------------------------------------------------------------+ +``` + +**Specifications:** +- **Width:** ~40% of screen width, anchored left +- **Height:** 1-2 lines. Monologue is brief (authoring guide says "one breath") +- **Background:** Minimal. A subtle text shadow or very faint panel (20-30% opacity). The monologue should feel like it's floating in the character's head, not displayed in a UI box +- **Text color:** Character-specific tint. Detective: cool blue-white `#c8d8f0`. Smuggler: warm amber-white `#f0e0c8`. Subtle — not a spotlight, just a whisper of character identity +- **Animation:** Fades in over 0.3s, holds for 4-6 seconds (adjusted by text length), fades out over 0.5s. Does not stack — a new monologue replaces the current one. If the old one hasn't finished, it fades faster (0.15s) before the new one appears +- **Audio cue:** Soft chime on appearance (D-038: `sfx_monologue_chime.ogg`). Sharper variant for anomaly observations (`sfx_monologue_chime_urgent.ogg`) +- **During dialogue:** Monologue can appear DURING a conversation (post_conversation trigger fires after exchange, but observe_anomaly can fire mid-conversation if the player spots something while talking). When dialogue panel is active, monologue shifts up slightly to sit above the dialogue panel, maintaining the same left-anchored position + +**Critical rule: monologue must not compete with dialogue.** They occupy different screen regions (left vs. center), use different visual treatments (floating vs. paneled), and serve different functions (character's inner voice vs. NPC's spoken words). If both are on screen simultaneously, the player reads dialogue first (center, larger, solid panel) and catches monologue peripherally (left, smaller, ghostly). This mirrors how internal reactions work — you hear the other person speaking while your own thought forms underneath. + +### 4.5 Knowledge Panel (Neural Lattice Interface) + +**What it is:** The player's diegetic notebook. A neural lattice overlay that shows what the character knows — people, facts, relationships. + +**Diegetic framing:** This isn't a game journal. It's the character checking their implant's records. The detective's Analytical lattice organizes information automatically. The smuggler's Baseline lattice shows simpler records — people they know, places they've been. + +**Access:** Press [Tab] to toggle. Pauses the game (or slows to near-pause — design decision for Gestalt). The lattice overlay slides in from the right side of the screen. + +**Layout:** +``` ++------------------------------------------------------------------+ +| | +| GAME WORLD | LATTICE OVERLAY | +| (visible, | | +| dimmed) | PEOPLE [tab] | +| | Kael Davan [green] | +| | Sera Venn [green] | +| | Voss [teal] | +| | "Dock Worker" [teal] | +| | | +| | PLACES | +| | The Terminal | +| | The Last Shift | +| | | +| | OBSERVATIONS | +| | "Manifest discrepancy | +| | — Container 4471" | +| | | ++------------------------------------------------------------------+ +``` + +**Specifications:** +- **Width:** ~30% of screen width, anchored right +- **Sections:** People / Places / Observations. Simple tabs or vertical scroll +- **People entries:** Display name + relationship color dot. Click/select for detail: known attributes, last seen location, relationship state +- **Places:** Location names discovered so far. Click for minimap highlight +- **Observations:** Key facts in plain language. These are the FactIds rendered as human-readable sentences. "Manifest discrepancy observed at Terminal" not "`investigation.manifest_discrepancy: Suspects`" +- **Visual treatment:** Dark panel with a subtle scan-line effect or faint grid overlay — this is a lattice interface, not a paper notebook. The diegetic feel comes from the typography and visual treatment, not from elaborate 3D renders +- **Character-specific:** The detective's lattice shows more analytical structure (observations auto-categorized, flagged items highlighted in amber). The smuggler's lattice is simpler — people and places, fewer analytical overlays +- **Empty state:** At game start, the lattice is sparse. Detective has a few case-file entries. Smuggler knows people but has no "observations" section initially. The lattice fills as the player discovers things + +### 4.6 HUD Elements + +**What's always visible:** + +``` ++------------------------------------------------------------------+ +| | +| GAME WORLD | +| | ++------------------------------------------------------------------+ +| The Terminal | Morning Shift | +----------+ | +| | | | MINIMAP | | +| | | | (D-013)| | ++------------------------------------------------------------------+ +``` + +**Location name:** Bottom-left corner of the HUD bar. Shows current area. Updates on zone transition with a brief fade. + +**Time of day:** Next to location. Shows the current phase in plain language ("Morning Shift", "Evening", "Night Cycle"). Updates on phase transition. No clock numbers — the character doesn't check the time, they feel the shift rhythm. + +**Minimap (insert):** Bottom-right corner. Small — 120x120px at reference resolution. Shows: +- Player position (character-color dot, D-033) +- Known NPC positions within close range (relationship-colored dots) +- Location boundaries (thin lines, muted) +- POI markers for discovered locations (small icons) +- Fog — areas not yet discovered are dark + +The minimap IS the neural lattice navigation overlay (D-013). It should look like a technology interface, not a game map. Subtle grid lines, faint glow at the edges, no terrain texturing. Information appears on it only as the character learns it. + +**What's NOT always visible:** +- Character name — the player knows who they are. Don't waste pixels reminding them +- Health/status bars — no health system in v0.1 +- Inventory indicators — no inventory in v0.1 (knowledge-based, per Gestalt's direction) +- Quest markers — this is not that kind of game +- Mini-objectives — the player decides what to do + +### 4.7 News Ticker + +The Meridian news feed in The Last Shift. This is a diegetic element — it exists in the world, on the bar's display screen. + +**Implementation:** A world-space text object attached to the bar's display entity. Scrolling text, left-to-right, station news headlines. The player reads it by standing near the display, not through a HUD overlay. + +**Why world-space:** D-039 Wow Moment #5 requires the same ticker to produce different monologue reactions per character. If the ticker were a HUD element, it would feel like a notification system. In world space, it's a thing in the bar that you walk over to and read. The monologue fires because your character is looking at it, not because the UI decided to inform you. + +--- + +## Topic 5: Minimum UI for a 30-Minute Vertical Slice + +### Must be polished (player-facing, first impression) + +| Element | Why polished | Effort level | +|---------|-------------|-------------| +| **Monologue display** | This IS the game's voice. If monologue feels janky, the character feels dead. Smooth fade, correct timing, readable text, audio chime sync. | Medium — text rendering + animation + audio trigger | +| **Interaction prompt** | First thing the player tries. Walk to NPC, see "Talk", press key, it works. If this is clunky, the game feels broken. | Low — context label + input handler | +| **Dialogue panel** | Second thing the player tries. Text must be readable, pacing must feel natural, exit must work cleanly. | Medium — text display + input flow + state management | +| **Entity relationship colors** | D-033. The color palette tells the player who matters before any text appears. Green = safe, amber = something changed. THE FRIEND's color shift (green to amber) is Wow Moment #3. | Low — shader/tint parameter driven by server state | +| **Fog of perception** | D-011. If the player can see everything, there's no game. LOS shadowcasting must work. | High — but this is Dudley/Tyre territory, not UI | + +### Can be placeholder (functional, not pretty) + +| Element | Why placeholder is OK | Placeholder spec | +|---------|----------------------|-----------------| +| **Minimap** | Players will use it but don't expect polish in a vertical slice. A dot on a dark rectangle is sufficient. | Player dot + area boundary lines. No labels, no icons. | +| **HUD bar** | Location name + time phase in plain text, no styling. | Left-aligned text, default font, dark background strip. | +| **Knowledge panel** | Players may not even find it in 30 minutes. But it must exist for the detective's analytical loop. | Plain text list on a dark overlay. No scan-line effects, no diegetic styling. Functional data display. | +| **News ticker** | D-039 Wow Moment #5 needs it, but the ticker itself can be simple scrolling text. The wow is in the monologue reaction, not the ticker's visual polish. | Scrolling text on a world-space panel. Monospace font. Functional. | + +### Can be deferred entirely (v0.2+) + +| Element | Why defer | +|---------|----------| +| **Perception mode overlays** | D-017: only natural vision in v0.1. No thermal, no camera feeds, no tracking. | +| **Inventory UI** | No inventory system in v0.1. Knowledge-based interaction. | +| **Character selection screen** | v0.1 can start with a character toggle or separate launch configs. No menu needed. | +| **Settings/options menu** | Dev builds. Hardcode resolution, keybindings. | +| **Tutorial/onboarding** | Monologue IS the tutorial (D-016: "That terminal might have access logs"). No separate tutorial UI. | +| **Map zoom/pan** | Camera locked (D-015). No map interaction beyond the minimap. | + +### The 30-minute visual journey + +Here's what the player actually sees, minute by minute: + +**Minute 0-1 (Arrival):** Black screen fades in. Station ambient plays. The game world appears, character in the center, fog surrounding them. First monologue fades in at bottom-left: "Morning shift. Recycled air and cargo lubricant." Chime sounds. HUD bar shows "The Terminal | Morning Shift". Minimap shows a small illuminated area around the player. + +**Minute 1-5 (Exploration):** Player moves with WASD. Fog recedes as they explore. NPC rectangles appear in the viewport — colored dots. Walking near an NPC shows their display name and [Talk] prompt. First dialogue: the panel slides up from the bottom, semi-transparent, NPC name in relationship color. Text advances on input. Conversation ends, panel slides down. Monologue fires: "Kael's here. Good." + +**Minute 5-15 (The Character's Eye):** Monologue fires unprompted with the urgent chime — the character noticed something the player didn't. The text is different: sharper, the urgent variant. "Dock worker Davan -- lattice activity spiked." The player hasn't done anything — the game is teaching them that their character has a mind. + +**Minute 15-20 (Building routine):** Player moves between Terminal and Bar. Location name transitions on the HUD. The bar is warmer (amber lighting tint). Minimap updates with new area. Monologue at bar entry: different tone, different character voice. Dialogue with Sera Venn — the panel text feels different from Kael's. Same UI, different voice. + +**Minute 20-25 (THE FRIEND's contradiction):** Kael's entity color shifts — green to amber. 0.5s fade per D-033. This is silent, visual. No fanfare. The urgent monologue chime fires. "Kael? By the service hatch? And that's -- I don't know them." If the player presses [Tab], the knowledge panel now shows Kael as "Person of Interest" with an observation entry. + +**Minute 25-30 (The quiet moment):** Player standing still in a corridor. After idle time, monologue fades in without chime. Reflective. Quiet. "No Meridian signal down here. No eyes, no ears." The game breathes. The UI is invisible. The world is present. + +### Summary: v0.1 UI bill of materials + +| System | Files/Scenes | Priority | +|--------|-------------|----------| +| Monologue renderer | 1 scene + 1 script | P0 (must ship) | +| Interaction prompt | 1 scene + 1 script | P0 | +| Dialogue panel | 1 scene + 1 script | P0 | +| Entity color system | Shader + config | P0 | +| HUD bar (location + time) | 1 scene + 1 script | P1 (placeholder OK) | +| Minimap | 1 scene + 1 script | P1 | +| Knowledge panel | 1 scene + 1 script | P1 | +| News ticker (world-space) | 1 scene + 1 script | P1 | +| Fog rendering | Shader + integration | P0 (shared with server team) | + +Total: ~9 scenes, ~9 scripts, 2 shaders. This is a tight UI. Every element earns its pixel. + +--- + +## Open Questions for Other Participants + +**For Dudley (server):** +- What does the state update message look like? I need: entity positions, display names, relationship states, monologue triggers (id + text), dialogue lines (id + text + speaker), time phase, location zone ID. What's the IPC message format? +- Does the server send the full text of monologue/dialogue lines, or just IDs that the client looks up locally? I prefer full text in the message (keeps client thin, prevents info leakage), but there's a bandwidth trade. + +**For Gestalt (systems):** +- Does opening the knowledge panel pause the game? Semi-pause? If NPCs keep moving while you read, you miss things. If the game pauses, it breaks immersion. I lean toward semi-pause (simulation runs at 10% speed). +- Single-action interaction prompt: does this work for the systems design? Or does the player need to choose between "Talk" and "Observe" on the same NPC? + +**For Mellanie (copy):** +- Maximum monologue line length? I'm designing for 1-2 lines of display text (~80 characters per line, ~160 max). Is that enough for the voice? +- How many monologue lines can fire in sequence before it feels like a wall of text? I'd say 1 at a time with 2-3 second gaps minimum. + +**For Araminta (visual):** +- Diegetic styling for the knowledge panel — how far do we push the "lattice interface" look in v0.1? My instinct: minimal. Faint grid, slightly blue-shifted palette, clean type. Save the fancy scan-line effects for when we have art budget. +- Monologue text color per character — I proposed detective blue-white and smuggler amber-white. Does this conflict with the entity color palette (D-033)? + +--- + +*The player's eye goes to the center. Everything else stays out of the way until it's needed. Then it appears, does its job, and vanishes.* diff --git a/docs/workshops/v01-content-scoping/round1-tracking.md b/docs/workshops/v01-content-scoping/round1-tracking.md new file mode 100644 index 000000000..5bf05cedc --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-tracking.md @@ -0,0 +1,491 @@ +# Round 1 Tracking — Qatux (Documenter) +## v0.1 Content Scoping Workshop + +**Status:** All 7 responses received (Gestalt, Paula, Tyre, Mellanie, Stig, Dudley, SI). Round 1 complete. +**Date:** 2026-02-12 + +--- + +## 1. Agreements (Consensus Items) + +These items have clear consensus across respondents and could be decided immediately: + +| # | Item | Supporters | Status | +|---|------|-----------|--------| +| A-01 | **Drin promoted to Tier 2** | Gestalt (unambiguous yes), Paula (full Tier 2 profile drafted), Mellanie (implicitly — budgets Drin as Tier 2) | Ready to decide | +| A-02 | **THE NOBODY mechanic deferred to v0.2; data ships in v0.1** | Gestalt (explicit: ship data, defer mechanic), Dudley (NobodyState deferred per brief) | Ready to decide | +| A-03 | **YAML as content format** (with dissent — see D-01) | Tyre (detailed rationale), Stig (accepts server-sent text), Mellanie (needs author-readable format) | Needs resolution with Dudley | +| A-04 | **7-interaction minimum** (Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear) | Gestalt (designed it), Stig (UI for each), Dudley (server events for each) | Ready to decide | +| A-05 | **~200 monologue lines total** (~100 per character) | Mellanie (80-110 per character, detailed math), Gestalt (implicitly validates via wow moment mapping) | Ready to decide | +| A-06 | **~695 total authored content items for v0.1** | Mellanie (200 monologue + 350 dialogue + 50 environmental + 20 ticker + 50 UI + 25 PC-as-NPC) | Needs validation from others | +| A-07 | **5 triangles: 3 active forks, 2 passive tensions** | Gestalt (explicit table: T1/T2/T4 active, T3/T5 passive), Paula (aligns with triangle fork detail) | Ready to decide | +| A-08 | **Naia Tamm needs MIRROR-specific voice samples** | Mellanie (5 samples written), Paula (naming cascade planned) | Ready to decide | +| A-09 | **Content directory: `global/` + `districts/` top-level split** | Tyre (detailed tree), Dudley (load sequence matches), Gestalt (aligns with Wiki Review consensus) | Ready to decide | +| A-10 | **Dialogue files by location + role, not by NPC** | Tyre (explicit design choice, per D-035) | Needs validation from Dudley, Gestalt | +| A-11 | **THE FRIEND arc is the critical path for v0.1** | Tyre (Tier 4 risk, "everything converges on wow moment #3"), Dudley (FriendArc is the sole pattern-specific component), Gestalt (maps all wow moments to FRIEND), Mellanie (writes FRIEND content first) | Consensus | +| A-12 | **Client receives text from server, doesn't load content files directly** | Stig (explicit: "server selects, client renders"), Dudley (server authoritative over dialogue/monologue selection), Tyre (aligns with D-010 information boundary) | Ready to decide | +| A-13 | **Single-action interaction prompt (no radial menus)** | Stig (explicit: "single prompt, press E"), Gestalt (context-sensitive prompt table) | Ready to decide | +| A-14 | **PC-as-NPC required for v0.1** | Gestalt (missing from IN list, adds it), Paula (Gap 1: profiles missing, must be authored), Mellanie (budgets ~25 items) | Ready to decide | +| A-15 | **News ticker required for v0.1** | Gestalt (missing from IN list, wow moment #5), Stig (world-space implementation), Mellanie (budgets ~20 items) | Ready to decide | +| A-16 | **Time progression required for v0.1** | Gestalt (missing from IN list, dependency for routines), Dudley (DayPhase already exists), Stig (HUD display designed) | Ready to decide | +| A-17 | **Self-contained triangle forks for v0.1 (no cross-triangle cascade)** | Tyre (explicit scope control recommendation, "strongly recommend self-contained") | Needs validation from Gestalt | + +--- + +## 2. Disagreements (Require Resolution) + +### D-01: Content File Format — YAML vs. RON + +| Position | Advocate | Argument | +|----------|----------|----------| +| **YAML** | Tyre | Author readability, native multi-line text, JSON Schema validation, any language can emit it, 1.5ms parse difference irrelevant | +| **RON** | Dudley | Structured + typed data, all examples in RON format, mentions "Tyre's call on format" suggesting deference but preference for RON | + +**Impact:** Affects every content file, loader implementation, validation pipeline, generator output format. +**Note:** Dudley writes "Tyre's call on format" in his response, which suggests he'll defer. But his manifest example uses `content/manifest.ron` and all NPC profile examples are in RON. The two proposals need reconciliation before implementation. + +### D-02: NPC Pattern/Motivation Mapping — 10 of 17 NPCs Disagree + +This is the largest disagreement in Round 1. Gestalt and Paula independently mapped all 17 NPCs. They agree on 7 and disagree on 10. + +#### Full Comparison Table + +| # | NPC | Tier | Gestalt Pattern | Paula Pattern | Match? | Gestalt Motivation | Paula Motivation | Match? | +|---|-----|------|----------------|--------------|--------|-------------------|-----------------|--------| +| 1 | Kael Davan | T1 | FRIEND | FRIEND | YES | OPERATOR | OPERATOR | YES | +| 2 | Sera Venn | T1 | FRIEND | FRIEND | YES | WITNESS | WITNESS | YES | +| 3 | Naia Tamm | T1* | MIRROR | MIRROR | YES | CIVILIAN | CIVILIAN | YES | +| 4 | Voss | T2 | SYSTEM | SYSTEM | YES | **OPERATOR** | **HANDLER** | **NO** | +| 5 | Lera Sessik | T2 | ANCHOR | ANCHOR | YES | OPERATOR | OPERATOR | YES | +| 6 | Torek Lintar | T2 | CATALYST | CATALYST | YES | CIVILIAN | CIVILIAN | YES | +| 7 | Devra | T2 | THRESHOLD | THRESHOLD | YES | **HANDLER** | **OPERATOR** | **NO** | +| 8 | Maret Korr | T2 | **SYSTEM** | **WITNESS** | **NO** | **WITNESS** | **SKEPTIC** | **NO** | +| 9 | Resha | T2 | CATALYST | CATALYST | YES | **SKEPTIC** | **CIVILIAN** | **NO** | +| 10 | Renn | T2 | THRESHOLD | THRESHOLD | YES | OPERATOR | OPERATOR | YES | +| 11 | Pell | T2 | CATALYST | CATALYST | YES | TURNCOAT | TURNCOAT | YES | +| 12 | Harek | T2 | **GHOST** | **ANCHOR** | **NO** | HANDLER | HANDLER | YES | +| 13 | Drin | T2 (promoted) | **REMNANT** | **SYSTEM** | **NO** | TURNCOAT | TURNCOAT | YES | +| 14 | Sess | T3 | **ANCHOR** | **NOBODY** | **NO** | CIVILIAN | CIVILIAN | YES | +| 15 | Olin | T3 | CATALYST | CATALYST | YES | **SKEPTIC** (dual) | **CIVILIAN** | **NO** | +| 16 | Sabel | T3 | **THRESHOLD** | **GHOST** | **NO** | OPERATOR | OPERATOR | YES | +| 17 | Tav | T3 | **SYSTEM** | **NOBODY** | **NO** | **CIVILIAN** | **OPERATOR** | **NO** | + +**Summary:** 7 full agreements, 4 pattern-only disagreements, 2 motivation-only disagreements, 4 both-disagree cases. + +#### Analysis of Each Disagreement + +| NPC | Disagreement | Gestalt's Reasoning | Paula's Reasoning | Assessment | +|-----|-------------|--------------------|--------------------|------------| +| **Voss** (motivation) | OPERATOR vs HANDLER | "Running the schedule that enables smuggling" | "Manages operations, applies scheduling pressure" | Both defensible. Gestalt sees Voss as OPERATING the system; Paula sees Voss as HANDLING people through scheduling authority. Voss arguably does both. | +| **Devra** (motivation) | HANDLER vs OPERATOR | "Managing operations from above" = HANDLER | "Running the external coordination" = OPERATOR | Swapped vs Voss. Gestalt sees Devra as a handler (pressure from above), Paula sees Devra as running things (operational). | +| **Maret** (both) | SYSTEM+WITNESS vs WITNESS+SKEPTIC | "Institutional scheduler... saw discrepancies" | "Accidentally witnessed discrepancies... doubts the system" | Core question: is Maret primarily a SYSTEM embodiment (institutional role) or a WITNESS (defined by what she saw)? Motivation: does she WITNESS (passive) or SKEPTIC (active questioning)? | +| **Resha** (motivation) | SKEPTIC vs CIVILIAN | "Doubts the 'mind your business' culture" | "Not suspicious — just curious" | Key difference: Gestalt interprets Resha's questions as genuine skepticism; Paula interprets them as innocent curiosity (CIVILIAN). | +| **Harek** (pattern) | GHOST vs ANCHOR | "Shapes events through debt leverage, not direct membership" | "Embodies complacent contentment — but unconsciously creates tools" | Gestalt sees Harek's indirect influence as GHOST-like; Paula sees Harek's comfortable contentment as ANCHOR-like. Both are valid reads of different aspects. | +| **Drin** (pattern) | REMNANT vs SYSTEM | "Remembers when the job was just a job" | "The dock inspector IS the inspection system" | Gestalt foregrounds Drin's nostalgia; Paula foregrounds Drin's institutional role. SYSTEM seems mechanically stronger (his compromise corrupts the system). | +| **Sess** (pattern) | ANCHOR vs NOBODY | "Quiet contentment behind the bar" | "Social wallpaper... background that could become foreground" | Gestalt sees Sess as genuinely content (ANCHOR at T3); Paula sees Sess as currently invisible (NOBODY). Depends on whether ANCHOR can be T3. | +| **Olin** (motivation) | SKEPTIC (dual) vs CIVILIAN | "Asks questions with ambiguous intent" = SKEPTIC | "Uninvolved (as far as anyone knows)" = CIVILIAN | Gestalt gives Olin dual motivation (ambiguity IS the character). Paula keeps it simpler. | +| **Sabel** (pattern) | THRESHOLD vs GHOST | "Off-station gatekeeper of supply chain" | "Absent power — off-station, never fully present" | Gestalt sees Sabel as a gatekeeper (THRESHOLD); Paula sees Sabel as absent power (GHOST). Both are right about different functions. | +| **Tav** (both) | SYSTEM+CIVILIAN vs NOBODY+OPERATOR | "Ring lookout following procedure... lowest rung" | "Peripheral, barely visible... running lookout operations" | Gestalt sees a system functionary; Paula sees a background figure doing a job. | + +**Resolution path:** These disagreements reflect genuinely different analytical lenses. Gestalt maps from *mechanical function* (what does this NPC do in the system?). Paula maps from *narrative weight* (what does this NPC mean to the story?). Round 2 should produce a single canonical mapping with explicit rationale per NPC. + +### D-03: Smuggler EntityKnowledge Keys — 4 New vs 8 New + +| Position | Advocate | Keys | +|----------|----------|------| +| **4 new keys** (+ 2 existing repurposed, + 2 deferred) | Gestalt | New: `exposure_risk`, `loyalty_assessment`, `cover_integrity`, `moral_weight`. Repurposed: `trust_level` → `trust_read`, `secret_held` → `leverage_held`. Deferred: `social_debt`, `loyalty_conflict`. | +| **8 new keys** (all as new additions) | Paula | All 8 as separate new keys: `trust_read`, `exposure_risk`, `loyalty_assessment`, `leverage_held`, `social_debt`, `moral_weight`, `cover_integrity`, `loyalty_conflict` | + +**Key difference:** Gestalt argues `trust_read` and `leverage_held` already exist as `trust_level` and `secret_held` with per-character interpretation. Paula treats them as distinct keys. Gestalt also defers `social_debt` (no time for favor economy in 30min) and `loyalty_conflict` (inferable from existing keys). + +**Impact:** Affects knowledge graph schema, smuggler monologue prerequisite vocabulary, content authoring complexity. + +### D-04: Examine NPC vs Talk — Separate or Combined? + +| Position | Advocate | Argument | +|----------|----------|----------| +| **Separate interactions** (E to Examine, F to Talk) | Gestalt | Detective loop: examine first → talk later. Smuggler loop: talk first → examine when suspicious. Collapsing them breaks both loops. | +| **Single primary action** (context determines) | Stig | "Default to Talk for NPCs, Examine for objects, handle edge cases when they arise." Asks Gestalt to confirm. | + +**Impact:** Directly affects UI design, keybinding, server interaction pipeline. +**Note:** Not a strong disagreement — Stig asks the question, Gestalt has a firm answer. Needs explicit decision. + +### D-05: Knowledge Panel Pause Behavior + +Stig asks: "Does opening the knowledge panel pause the game? Semi-pause?" +- Stig leans toward semi-pause (10% simulation speed) +- No response from Gestalt yet in this round + +--- + +## 3. Open Questions (From Respondents) + +### From Gestalt +- Q: Should triangle forks cascade across triangles in v0.1? (Tyre says no — self-contained) + +### From Paula +- Q: Other wiki gaps? (She identified 7 additional gaps beyond the brief's list — see section 7) +- Q: Harek's structural role — formalize as triangle member or "external pressure node"? + +### From Tyre +- Q: Protocol evolution v2 → v3 — sign-off needed for new ObserverSnapshot fields +- Q: Line previewer CLI — when to build? (Early, per his recommendation) + +### From Mellanie +- Q: Confirm monologue guide examples as canonical PC voice registers? (Blocks all monologue authoring) +- Q: Maximum monologue line length? (She needs char limits from Stig — Stig proposes ~160 chars/2 lines) +- Q: Smuggler internal voice register — how terse? How operational? +- Q: Detective internal voice register — how clinical? How institutional? + +### From Stig +- Q: IPC message format for state updates? (For Dudley) +- Q: Server sends full text or line IDs? (Stig prefers full text — keeps client thin) +- Q: Lattice panel diegetic styling — how far in v0.1? (For Araminta) +- Q: Monologue text colors per character — conflict with D-033 entity palette? (For Araminta) + +### From Dudley +- Q: `PlayerAction::Interact` is currently a no-op — needs extension (protocol change) +- Q: Need Tyre sign-off on ObserverSnapshot v2 → v3 protocol evolution + +--- + +## 4. Scope Boundary — Revised IN/OUT + +### Gestalt's Additions to IN (4 items) + +| Added Item | Rationale | Cross-Support | +|-----------|-----------|---------------| +| **News ticker** | Required for wow moment #5 | Stig (designed world-space implementation), Mellanie (budgeted 20 items) | +| **PC-as-NPC** | Required for divergence reveal (wow moment #4) | Paula (identified as Gap 1), Mellanie (budgeted 25 items) | +| **Time progression** | Required for NPC routines, shift transitions | Dudley (DayPhase exists), Stig (HUD designed) | +| **Relationship state transitions** | Required for FRIEND arc (Friendly → PersonOfInterest) | Dudley (RelationshipState in knowledge graph) | + +### Gestalt's Additions to OUT (5 items) + +| Excluded Item | Rationale | Risk if not excluded | +|-------------|-----------|---------------------| +| **Inventory system** | Evidence is knowledge, not physical items | Feature creep ("pick up the manifest") | +| **Stealth mechanics** | Social stealth, not mechanical stealth | Immersive sim instincts will demand it | +| **Combat system** | Not in D-027 criteria | Ate scope from other features | +| **Player-facing save/load** | Session-based 30-minute slice | Dev-mode save fine, player-facing deferred | +| **Lattice modification/upgrade** | Contraband IS lattice components but player can't use them | "Can the player use smuggled mods?" No. | + +--- + +## 5. Content Volume Estimates (Mellanie) + +| Category | Items | Priority | +|----------|-------|----------| +| Monologue pools (2 characters) | ~200 lines | Critical | +| NPC dialogue | ~350 lines | Critical | +| Environmental text | ~50 items | High | +| News ticker | ~20 items | Medium | +| UI microcopy | ~50 strings | High | +| PC-as-NPC content | ~25 items | Medium | +| **TOTAL** | **~695** | | + +**Authoring order (Mellanie's proposed sequence):** +1. THE FRIEND: Kael content pack (~100 lines) — exercises every pipeline +2. THE FRIEND: Sera content pack (~100 lines) +3. THE MIRROR: Naia Tamm (~60-70 lines) +4. Tier 2 NPCs (~100-150 lines: Voss, Lera, Torek, Maret) +5. Environmental text + ticker (~70 items) +6. Remaining monologue fill (top up to ~200) +7. UI copy + tutorial monologue (~50 strings) +8. Tier 3 background + PC-as-NPC (~50 items) + +--- + +## 6. Technical Risk Assessment (Tyre) + +| Feature | Risk | Primary Factor | Mitigation | +|---------|------|----------------|------------| +| WASD + collision | Low | None significant | Already built | +| 3 social sites | Low | Art (not v0.1 concern) | Functional boxes | +| NPC routines | Low-Medium | Content authoring time | Start schedules early | +| Knowledge graph | Medium | Downstream dependency chain | Sprint 2 narrow scope | +| 2 playable characters | Medium | Content volume doubling | Content validation tooling | +| Context-sensitive interaction | Medium | Dialogue selection tuning | Line previewer CLI | +| **Monologue system** | **High** | Trigger detection + prerequisite eval | Simple AND-only prerequisites | +| **THE FRIEND arc** | **High** | Integration of all systems | Dedicated integration test | +| 5 triangle forks | Medium-High | Cascade complexity | Self-contained forks | +| 6 wow moments | Varies | #3 is the critical path | Prioritize everything unblocking #3 | + +**Critical path:** Knowledge graph → Contradiction detection → FRIEND phase transitions → Spatial staging → Monologue trigger → Monologue display → FRIEND content authored and loaded. + +**What will take longer than expected (Tyre's warnings):** +1. Monologue prerequisite tuning (gap between "works" and "feels right") +2. Dialogue selection weighting (same tuning problem) +3. Spatial staging for THE FRIEND (Kael in Corridor B-7 when player is nearby, without feeling scripted) +4. Content conversion (17 NPC wiki profiles → YAML, ~2-3 full days) + +--- + +## 7. Wiki Gaps Identified (Paula) + +Beyond the 5 issues in the brief, Paula identified 7 additional gaps: + +| # | Gap | Blocks | Priority | +|---|-----|--------|----------| +| Gap 1 | **PC-as-NPC profiles missing** | Server entity spawning, dialogue, monologue about other PC | Critical | +| Gap 2 | **Monologue content pool — zero authored lines** | Monologue system integration testing | Critical | +| Gap 3 | **NPC routine YAML layer missing** | Server NPC scheduling, deviation detection | Critical | +| Gap 4 | **Triangle fork decision points not mechanically specified** | Storyteller triggers, dialogue branching | High | +| Gap 5 | **FactId catalog — smuggler path entries sparse** | Knowledge graph population, monologue prerequisites | High | +| Gap 6 | **Location files lack smuggler operational detail** | Environmental monologue triggers, spatial gameplay | Medium | +| Gap 7 | **Harek has no triangle membership despite T2 role** | Triangle 2/5 formalization | Medium | + +--- + +## 8. ECS / Server Architecture (Dudley) + +### Components Proposed + +| Component | Purpose | v0.1? | +|-----------|---------|-------| +| `ThematicPattern` | System A tag (9 patterns) | Yes | +| `FunctionalMotivation` | System B tag (6 motivations) | Yes | +| `FriendArc` | THE FRIEND phase tracking, contradiction detection | Yes (Kael, Sera only) | +| `ContentTier` | Tier1/Tier2/Tier3 simulation depth | Yes | +| `AccessTierMap` | D-028 Layer 1 dialogue access filtering | Yes | +| `TrustLevels` | D-028 Layer 3 gossip gating | Yes | +| `TriangleMembership` | Triangle role references per NPC | Yes | +| `NpcRoutine` | Schedule-based movement (DayPhase → position) | Yes | +| `MoodState` | D-035 mood tag for dialogue selection | Yes | +| `NobodyState` | Promotion stages + interaction counter | Deferred | +| `GhostInfluence` | Off-stage influence | Deferred | +| `CatalystTrigger` | Arrival/disruption event chain | Deferred | + +### Key Design Principle (Dudley) +"Pattern and motivation are tags for content selection, not unique simulation subsystems. THE FRIEND is the sole exception in v0.1 because its arc progression is load-bearing." + +### What Dudley Can Build Now (No Content Dependency) +- Content loader framework (Phase 1-6 skeleton) +- Proximity trigger system +- Interaction event pipeline +- Dialogue selection pipeline (D-035 tag filtering) +- Monologue trigger system +- NPC routine scheduler +- FriendArc phase transition logic +- All with fixture/synthetic data + +### What Dudley Cannot Build Without Content +- Actual NPC spawning (needs profiles in structured format) +- Real dialogue/monologue testing (needs authored lines) +- NPC routine pathfinding (needs district spatial layout) +- Overheard conversations (needs NPC-NPC scripts) +- FactId catalog (needs machine-readable version) +- Triangle escalation logic (needs formal conditions) + +--- + +## 9. UI Architecture (Stig) + +### v0.1 UI Bill of Materials + +| Element | Priority | Effort | +|---------|----------|--------| +| Monologue renderer | P0 (must ship) | Medium | +| Interaction prompt | P0 | Low | +| Dialogue panel | P0 | Medium | +| Entity color system | P0 | Low | +| HUD bar (location + time) | P1 (placeholder OK) | Low | +| Minimap | P1 | Medium | +| Knowledge panel | P1 | Medium | +| News ticker (world-space) | P1 | Low | +| Fog rendering | P0 (shared with server) | High | + +**Total:** ~9 scenes, ~9 scripts, 2 shaders. + +### Key Design Decisions (Stig) +- Center of screen is sacred (no permanent UI) +- Monologue: bottom-left, 40% width, fades in/out, character-tinted text +- Dialogue: bottom-center, 60% width, semi-transparent dark panel, world visible behind +- Interaction prompt: world-space, tracks entity, single action +- Knowledge panel: right-side overlay on [Tab], diegetic lattice interface +- News ticker: world-space text on bar's display entity (not HUD) +- Monologue display time: 4-6 seconds adjusted by text length +- Character text colors: Detective blue-white `#c8d8f0`, Smuggler amber-white `#f0e0c8` + +--- + +## 10. Cross-Reference: Ticket Categories (Tyre) + +Tyre proposes ticket splits across 8 categories: + +1. **Content Infrastructure** — directory structure, content loader (4 phases), validation schemas, line previewer CLI +2. **Wiki → Content Conversion** — NPC profiles (17), locations (3), fact catalog, triangles (5), routines +3. **Monologue System** — trigger detection, prerequisite evaluation, line pool selection, pacing/cooldown, content authoring, display UI, chime audio +4. **Dialogue System** — pool loader, selection algorithm, content authoring, UI +5. **Interaction Model** — proximity detection, state machine, prompt UI +6. **THE FRIEND Arc** — phase transitions, contradiction detection, tell accumulation, content (Kael + Sera), relationship colors, integration test +7. **NPC Rendering + Routines** — schedule execution, deviation events, sprite/label rendering, movement animation +8. **Cross-Cutting** — PC-as-NPC, news ticker, smuggler lens parity, Naia Tamm rename + +**Dependency chain critical path (Tyre):** +``` +Content directory structure + -> Content loader (Phase 1-2) + -> NPC profile conversion -> NPC schedule execution + -> Content loader (Phase 3-4) + -> Dialogue pool loader -> Dialogue selection -> Dialogue UI + -> Monologue trigger detection + -> Prerequisite evaluation + -> Line pool selection + -> Monologue pacing -> Monologue display UI + +Knowledge graph (Sprint 2) + -> Contradiction detection -> FRIEND phase transitions + -> Monologue prerequisite evaluation + -> Tell system accumulation +``` + +--- + +## 11. Items Requiring Round 2 Resolution + +| # | Item | Required Participants | Priority | +|---|------|---------------------|----------| +| R2-01 | **Canonical NPC pattern/motivation mapping** (resolve 10 disagreements) | Gestalt, Paula | Critical | +| R2-02 | **Content format: YAML vs RON** | Tyre, Dudley | Critical | +| R2-03 | **Smuggler EntityKnowledge keys: 4 new vs 8 new** | Gestalt, Paula, Dudley | High | +| R2-04 | **Examine NPC vs Talk: separate or combined interaction?** | Gestalt, Stig, Dudley | High | +| R2-05 | **Knowledge panel pause behavior** | Gestalt, Stig | Medium | +| R2-06 | **PC voice register confirmation** (blocks Mellanie's authoring) | Gestalt or Paula | High | +| R2-07 | **Triangle fork cascade: self-contained or cross-triangle?** | Gestalt, Tyre | High | +| R2-08 | **Harek's structural role** (triangle member vs external pressure node) | Gestalt, Paula | Medium | +| R2-09 | **ObserverSnapshot v2 → v3 protocol change** | Tyre, Dudley, Stig | High | +| R2-10 | **SI ticket review** (pending SI response) | SI | High | + +--- + +## 12. Decisions Ready to Formalize + +If the lead approves, these can become decisions immediately (no further discussion needed): + +| Proposed ID | Decision | Source | +|-------------|----------|--------| +| D-042 | Drin promoted from Tier 3 to Tier 2 | Gestalt (unanimous), Paula (profile drafted) | +| D-043 | THE NOBODY mechanic deferred to v0.2; hidden pattern+motivation data ships in NPC content files for v0.1 | Gestalt, Dudley | +| D-044 | v0.1 interaction model: 7 interactions (Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear) | Gestalt (designed), Stig (UI for each), Dudley (server events) | +| D-045 | v0.1 scope: add News ticker, PC-as-NPC, Time progression, Relationship state transitions to IN list | Gestalt (rationale per item) | +| D-046 | v0.1 scope: add Inventory, Stealth, Combat, Player-facing save/load, Lattice modification to explicit OUT list | Gestalt (rationale per item) | +| D-047 | v0.1 triangles: 3 active forks (T1, T2, T4), 2 passive tensions (T3, T5) | Gestalt | +| D-048 | Client receives all text from server via state updates; client does not load or evaluate content files | Stig, Dudley, Tyre (information boundary principle) | + +--- + +## 13. Ticket Analysis (SI) + +### Round 2 List Scorecard + +**26 of 29 tickets survive the 300-world reframe.** + +| Disposition | Count | Tickets | +|-------------|-------|---------| +| Survive unchanged | 11 | A1, A2, A7, A8, A10, A11, B2, B3, B4, B5, C1 | +| Survive reframed | 15 | A3 (merge into #319), A4, A6, A9, A12, B1, C2, C3, C4-C8, C9, C10, D2 | +| Deferred (v0.2+) | 2 | A5 (wiki index restructure), D1 (cultural_gate modifier) | +| Killed | 1 | D3 (secondary contraband variant FactIds — explicitly OUT) | + +### 7 New Tickets Proposed + +| ID | Title | Team | Priority | Key Dependencies | +|----|-------|------|----------|-----------------| +| NEW-1 | NPC pattern/motivation mapping for v0.1 | copy | high | None | +| NEW-2 | Drin tier promotion evaluation | copy | medium | NEW-1 | +| NEW-3 | Wiki → YAML content conversion (Sova NPCs) | copy | high | C1, C2, C3 | +| NEW-4 | Monologue content pool — minimum viable set | copy | high | #261, B4 | +| NEW-5 | v0.1 wow moment content checklist | copy | high | None | +| NEW-6 | PC-as-NPC content authoring (authored lines) | copy | high | B3, B5 | +| NEW-7 | Interaction model minimum spec | copy | medium | None (informed by workshop Topic 4) | + +### Existing Ticket Updates + +| ID | Title | Action | +|----|-------|--------| +| #368 | Knowledge vocabulary for v0.1 content | Close when wiki review accepted | +| #369 | Wiki Review Workshop Outputs (epic) | Repurpose as parent epic for v0.1 content scoping | +| #301 | Wiki taxonomy and structure | Assign to copy, downscope for v0.1 (Sova only) | +| #319 | Krenn System wiki entry | Merge with A3 (expanded: wiki page + seed.yaml reference) | +| **#261** | **Dual Lens Authoring Guide** | **CRITICAL BOTTLENECK — blocks 9 downstream tickets** | +| #310 | Character voice speech patterns | Merge with or block on B4 | +| #306 | News ticker / Meridian feed content | Update scope per D4 (40-50 headlines) | +| #326 | YAML content loader | Assign to server, blocked by C1+C2, Sprint 4 | + +### Sprint 3 Copy Team (14 tickets proposed) + +**P0 — Must start immediately:** + +| Ticket | Title | Blocked By | +|--------|-------|------------| +| A2 | Hael → Naia Tamm rename | — | +| A1 | Apply canonical full names | — | +| A7 | Smuggler entity attributes | — | +| C1 | Content directory structure design | — | +| **#261** | **Dual Lens Authoring Guide** | **— (Critical path root)** | + +**P1 — Start after P0s:** + +| Ticket | Title | Blocked By | +|--------|-------|------------| +| A10 | Nils Davan stub | — | +| A11 | Triangle 1 fix | A10 | +| B2 | MIRROR pattern spec | — | +| B4 | Smuggler voice + moral arc | A7 | +| NEW-1 | NPC pattern/motivation mapping | — | +| NEW-5 | Wow moment content checklist | — | + +**P2 — Fit in around P0/P1:** + +| Ticket | Title | Blocked By | +|--------|-------|------------| +| A8 | Shortcode fix | — | +| B3 | PC-as-NPC spec | A7 | +| NEW-7 | Interaction model minimum spec | — | + +### Sprint 3 Server Tickets (content-related) + +| Ticket | Title | Blocked By | +|--------|-------|------------| +| C2 | Create content/ directory skeleton | C1 | +| C3 | Content schema definitions | C1 | + +### Critical Path (SI's Analysis) + +Two parallel chains must begin in Sprint 3: + +**Chain 1 — Content authoring:** +``` +#261 (Dual Lens Guide) → B4 (Smuggler voice) → A6 (FactId text) → content packs → monologue pool +``` + +**Chain 2 — Content architecture:** +``` +C1 (Directory design) → C2/C3 (skeleton/schemas) → C4-C8/NEW-3 (YAML files) → #326 (loader) +``` + +**#261 is the single biggest blocker in the entire content pipeline.** It blocks 9 tickets directly and cascades into everything. SI recommends immediate assignment with Paula as owner. + +### v0.1 IN List → Ticket Coverage + +| IN List Item | Status | Gaps | +|-------------|--------|------| +| WASD movement + collision | Covered (Sprint 1-2 done) | — | +| Context-sensitive interaction | Partially covered | Needs NEW-7 spec | +| Monologue system | Architecture covered | Content gap (NEW-4) | +| Knowledge graph | Covered | — | +| 2 playable characters | Covered (architecture) | — | +| THE FRIEND arc | Covered but **blocked by #261** | — | +| 5 triangles with forks | Partially covered | Weak Triangle 1 (A11) | +| 6 wow moments | **Gap** | Needs NEW-5 tracking ticket | +| 3 social sites | Covered but blocked by #261 | — | +| NPC routines | Covered (server) | — | +| Dual-lens parity | **Major gap** | Smuggler lens incomplete (A7, B4, A6) | +| PC-as-NPC | **Gap** | No tickets exist (B3, B5, NEW-6) | +| Content directory | **Gap** | Structure undefined (C1, C2, C3) | +| Content validation | **Gap** | No validation exists (C9, C10) | + +--- + +*Qatux, Round 1 tracking. For the record: all 7 responses cataloged, 17 consensus items, 5 disagreements requiring resolution, 7 additional wiki gaps identified, 10 open questions, 7 decisions ready to formalize. SI confirms 26 surviving tickets + 7 new proposals. #261 (Dual Lens Authoring Guide) is the critical bottleneck — blocks 9 downstream tickets across the entire content pipeline.* diff --git a/docs/workshops/v01-content-scoping/round1-tyre.md b/docs/workshops/v01-content-scoping/round1-tyre.md new file mode 100644 index 000000000..ec001dba5 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round1-tyre.md @@ -0,0 +1,672 @@ +# Round 1 — Tyre (Technical Architect) +## v0.1 Content Scoping Workshop + +*cracks knuckles* + +Three topics assigned to me. Let me be honest about what each means technically. + +--- + +## Topic 3: Content Directory Structure + +### Format Decision: YAML + +Not RON, not JSON, not TOML. YAML. Here's why: + +| Criterion | YAML | RON | JSON | +|---|---|---|---| +| Content author readability | Excellent | Poor (Rust syntax) | Decent | +| Multi-line text (dialogue) | Native (`\|`, `>`) | Awkward | Escaped strings | +| serde support in Rust | `serde_yaml` (mature) | `ron` crate (mature) | `serde_json` (mature) | +| Schema validation tooling | JSON Schema (rich) | None standard | JSON Schema (native) | +| Generator output compatibility | Any language can emit YAML | Rust-only ergonomic | Any language | +| Hot-reload parse speed | Fast enough (~2ms for 100KB) | Faster (~0.5ms) | Fast (~1ms) | + +RON would be natural if only Rust engineers touched content. But Mellanie and Paula author content, and asking them to write `NpcProfile(axes: Axes(want: "Protect the operation"))` instead of `want: "Protect the operation"` is a tax with no return. The 1.5ms parse speed difference is irrelevant — content loads once at startup and on hot-reload. + +**Validation pipeline:** YAML files validate against JSON Schema definitions (one schema per content type). The server's `content_loader` also validates by attempting `serde_yaml::from_reader::()` — if it deserializes into the Rust struct, it's valid. Both checks run: schema catches structural issues early (missing required fields, wrong types), serde catches semantic issues (enum variant doesn't exist, value out of range). + +### Proposed Directory Structure + +``` +content/ + content.yaml # Manifest: lists all districts, content version + schema/ # JSON Schema definitions for validation + npc.schema.json + location.schema.json + fact.schema.json + district.schema.json + dialogue.schema.json + monologue.schema.json + routine.schema.json + triangle.schema.json + global/ # Shared across ALL districts (generator-safe) + facts/ # FactId definitions — the vocabulary + contraband.yaml # contraband.ring_exists, contraband.lattice_components, ... + location.yaml # location.corridor_b7_restricted, ... + investigation.yaml # investigation.manifest_discrepancy, ... + world.yaml # world.commission_regulations, ... + relationship.yaml # relationship.ring_membership, ... + progress.yaml # progress.initial_suspicion, ... + factions/ + lattice-commission.yaml + syndics.yaml + the-ring.yaml # Ring is district-local but faction def is global + concord-assembly.yaml + guardians-of-autonomy.yaml + entity-schema/ # EntityKnowledge attribute definitions + attributes.yaml # The 14 canonical keys from entity-attributes.md + enums/ # Tag enum definitions (D-035) + situations.yaml # 13 situation enums + topics.yaml # 9 topic enums + moods.yaml # 8 mood enums + access-tiers.yaml # public, insider, authority, peer, hostile + trust-tiers.yaml # surface, real, secret + triggers.yaml # 9 monologue trigger types + districts/ + sova-transit/ # v0.1: hand-authored district + district.yaml # District metadata, social site refs, NPC roster + npcs/ # One file per NPC + kael-davan.yaml # Tier 1 FRIEND — full 10-axis + tells + contradiction + sera-venn.yaml # Tier 1 FRIEND — full 10-axis + tells + contradiction + voss.yaml # Tier 2 + lera-sessik.yaml # Tier 2 + torek-lintar.yaml # Tier 2 + devra.yaml # Tier 2 + maret-korr.yaml # Tier 2 + resha.yaml # Tier 2 + harek.yaml # Tier 2 + naia-tamm.yaml # Tier 1 MIRROR (renamed from Hael) + renn.yaml # Tier 2 + pell.yaml # Tier 2 + drin.yaml # Tier 2 or 3 (pending promotion decision) + sess.yaml # Tier 3 + olin.yaml # Tier 3 + sabel.yaml # Tier 3 + tav.yaml # Tier 3 + locations/ + the-terminal.yaml # Social site: logistics hub + the-last-shift.yaml # Social site: bar / "Lera's" + maintenance-corridors.yaml # Social site: smuggling spaces + triangles/ + hub-power.yaml # Triangle 1: Voss/Nils power struggle + ring-trust.yaml # Triangle 2: internal ring loyalty + bar-tension.yaml # Triangle 3: bar social dynamics + investigation-pressure.yaml # Triangle 4: detective pressure + leverage-web.yaml # Triangle 5: Harek/Drin/compromise + dialogue/ # Grouped by location, then by role + the-terminal/ + dock-worker.yaml # Role-based, not NPC-named + shift-supervisor.yaml + scheduler.yaml + new-hire.yaml + the-last-shift/ + bartender.yaml + bar-regular.yaml + bar-owner.yaml + maintenance-corridors/ + courier.yaml + ring-operative.yaml + monologue/ # Hard partition per character (D-032) + smuggler/ + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml # time_idle, non-location triggers + detective/ + the-terminal.yaml + the-last-shift.yaml + maintenance-corridors.yaml + general.yaml + routines/ + schedules.yaml # All NPC schedules: time → location → activity +``` + +### Key Design Choices + +**1. `global/` vs `districts/` split.** +Everything in `global/` is district-independent. FactId vocabulary, faction definitions, attribute schemas, tag enums — these are the *language* the content speaks. Districts are *instances* of content written in that language. When generators produce District #247, they use the same `global/` vocabulary. + +**2. Dialogue by location + role, NOT by NPC.** +Per D-035: `role` is a template-defined role, NPC assignment is runtime. The file `dialogue/the-terminal/dock-worker.yaml` contains all dialogue lines for the dock-worker role at the Terminal. At runtime, Kael (or any other dock worker) draws from this pool based on access/trust/situation filtering. This is generator-compatible — a generated district creates its own role pools. + +**3. Monologue files are per-character per-location.** +D-032 mandates hard partition. The directory structure enforces it physically. No risk of accidentally sharing lines between characters. + +**4. Facts in `global/`, not per-district.** +The FactId vocabulary (`contraband.ring_exists`) is global — it's the *language of knowledge*. The specific confidence progression text in the fact catalog is also global (it describes what each confidence level means for that fact). What varies per district is which facts are *relevant* and which NPCs *know* them — that's in the NPC profiles and the knowledge graph at runtime, not in the fact definitions. + +Exception: if future districts introduce district-specific facts (e.g., `mining.union_dispute` on a mining world), those can live in `districts/{district}/facts/` as extensions. The loader merges global + local. + +**5. One NPC per file.** +Each NPC profile is self-contained. This means: +- Content authors can work on different NPCs in parallel without merge conflicts +- Generators output one file per generated NPC +- Validation runs per-file (fast feedback) +- Git blame is clean (who wrote what, when) + +### Content Addressing + +**`canonical_id`** is a required field inside every content YAML file. It uniquely identifies the entity across the entire game. Format: `{type}:{slug}`. + +```yaml +# content/districts/sova-transit/npcs/kael-davan.yaml +canonical_id: "npc:kael-davan" +``` + +```yaml +# content/global/facts/contraband.yaml +facts: + - fact_id: "contraband.ring_exists" # FactId string IS the canonical_id +``` + +```yaml +# content/districts/sova-transit/locations/the-terminal.yaml +canonical_id: "loc:sova-transit:the-terminal" +``` + +```yaml +# content/districts/sova-transit/triangles/hub-power.yaml +canonical_id: "tri:sova-transit:hub-power" +``` + +**Why `canonical_id` is in the file, not derived from the path:** +- Files can be reorganized without breaking references +- Generators can place files anywhere and assign IDs programmatically +- Cross-file references use the `canonical_id` string, not file paths +- Duplicate ID detection is a validation rule (build fails if two files claim the same canonical_id) + +**Runtime mapping:** At content load, the server builds a `ContentRegistry`: +```rust +struct ContentRegistry { + // canonical_id string → StableId (assigned deterministically via hash or sequential) + id_map: BTreeMap, + // Reverse lookup + reverse_map: BTreeMap, +} +``` + +The `StableId(u64)` assigned at load time is deterministic — same content files always produce same IDs (sorted canonical_ids, sequential assignment). This satisfies D-010 principle 4 (deterministic simulation). + +### Wiki → Content File Mapping + +The wiki is the **authoring reference** (narrative intent, character depth, prose descriptions). Content files are the **engine data** (structured values, tagged lines, schedule timings). The transformation is: + +| Wiki Source | Content Target | Transformation | +|---|---|---| +| `wiki/npcs/kael-davan.md` | `districts/sova-transit/npcs/kael-davan.yaml` | 10-axis prose → structured YAML fields. Routine prose → time/location pairs. Tells prose → tell tag list. | +| `wiki/locations/krenn-system/the-terminal.md` | `districts/sova-transit/locations/the-terminal.yaml` | Prose description → spatial properties (bounds, sightlines, Meridian coverage, access requirements). | +| `wiki/knowledge/fact-catalog.md` | `global/facts/*.yaml` | Per-FactId entries, split by category file. Confidence progression text included as authoring reference. | +| `wiki/knowledge/entity-attributes.md` | `global/entity-schema/attributes.yaml` | Attribute key definitions + valid values + usage notes. | +| `wiki/factions/*.md` | `global/factions/*.yaml` | Faction identity → mechanical properties (access tier modifiers, trust defaults). | +| `wiki/authoring/monologue-guide.md` | `global/enums/*.yaml` + schema definitions | Tag taxonomy → enum value lists + schema. | +| `wiki/npcs/index.md` (triangles) | `districts/sova-transit/triangles/*.yaml` | Triangle descriptions → structured NPC references + fork definitions. | + +**The wiki doesn't go away.** Wiki remains the human reference for "why is Kael this way?" The content file says `want: "Protect the operation and the people in it"` — the wiki explains what that means narratively, what the secondary want is, how it evolved. Writers read wiki, engine reads content files. + +### NPC Profile File Example + +```yaml +# content/districts/sova-transit/npcs/kael-davan.yaml +canonical_id: "npc:kael-davan" +display_name: "Kael Davan" +tier: 1 +pattern: "FRIEND" # System A (thematic) +motivation: "OPERATOR" # System B (functional) — pending Gestalt's mapping +district: "sova-transit" + +# D-024: 10-axis model +axes: + want: "Protect the operation and the people in it" + secret: + surface: "Ring member, handles physical cargo" + deep: "Trying to exit the ring via unauthorized contact" + relationships: + - target: "npc:nils-davan" + kind: "sibling" + trust: 0.85 + - target: "npc:naia-tamm" # Renamed from Hael + kind: "partner" + trust: 0.95 + - target: "npc:smuggler-pc" # PC-as-NPC reference + kind: "close_colleague" + trust: 0.90 + tolerance: + current_stress: 0.55 + threshold: 0.75 + routine: + description: "Morning shift 06:00-14:00, bar after shift, home evenings" + information: + known_facts: + - "contraband.ring_exists:KnowsDetails" + - "contraband.lattice_components:KnowsDetails" + - "contraband.supply_chain:KnowsOf" + - "location.corridor_b7_restricted:KnowsDetails" + - "location.smuggling_route:KnowsDetails" + - "world.shift_schedule:KnowsDetails" + contentment: 0.45 + +# D-024: 3 supporting axes +personality: + traits: ["loyal", "careful", "conflict-avoidant"] +tells: + - trigger: "lying" + behavior: "looks left" + - trigger: "stressed" + behavior: "lattice checking" + - trigger: "discussing_exit" + behavior: "forced casualness" +skills: + set: ["logistics", "cargo_handling", "dock_operations"] + combat_trained: false + +# Entity attribute defaults (what a NEW observer would learn) +initial_attributes: + role: "dock worker" + faction: "civilian" # True faction hidden until discovered + species: "human" + routine_pattern: "morning shift at logistics hub, bar after shift" + +# THE FRIEND arc data (Tier 1 only) +friend_arc: + character: "smuggler" # This NPC is FRIEND to the smuggler + phases: + - name: "warmth" + triggers: [] # Default state + - name: "trust" + triggers: + - fact: "relationship.trust_network" + min_confidence: "KnowsOf" + - name: "doubt" + triggers: + - observation: "kael_in_corridor_b7" # Spatial staging trigger + - name: "conflict" + triggers: + - attribute_set: "contradiction_flagged" + contradiction: + type: "spatial" # Seen in wrong place + location: "loc:sova-transit:maintenance-corridors" + expected_location: "loc:sova-transit:the-terminal" + time_window: "shift_transition" +``` + +### Dialogue File Example + +```yaml +# content/districts/sova-transit/dialogue/the-terminal/dock-worker.yaml +role: "dock_worker" +location: "loc:sova-transit:the-terminal" + +lines: + - id: "terminal_d_001" + text: "Manifest says 240 kilos but that container's sitting heavy. Could be packing material. Could be anything." + access: [insider, peer] + trust: surface + situation: [routine, shift_start] + topic: [cargo] + mood: [comfortable] + + - id: "terminal_d_002" + text: "Voss moved the schedule again. Third time this rotation. Says it's efficiency." + access: [public] + trust: surface + situation: [routine, shift_end] + topic: [routine, colleague] + mood: [comfortable] + + - id: "terminal_d_003" + text: "You want to know about the night shift? Nobody wants the night shift. That's your answer." + access: [authority] + trust: surface + situation: [investigation] + topic: [routine, investigation] + mood: [suspicious] + + - id: "terminal_d_015" + text: "Nils has been... look, I can't talk about family here. Not with the scanners running." + access: [insider, peer] + trust: real + situation: [social, alone] + topic: [trust, personal] + mood: [worried, conflicted] + tags: ["nils_reference", "ring_internal"] +``` + +### Monologue File Example + +```yaml +# content/districts/sova-transit/monologue/smuggler/the-terminal.yaml +character: smuggler +location: "loc:sova-transit:the-terminal" + +lines: + - id: "terminal_m_s_001" + text: "Same dock, same hum, same shift. Kael's already at his station. Reliable as the gate cycle." + trigger: enter_location + prerequisite: null + topic: [routine, colleague] + mood: [comfortable] + tags: ["opening", "kael_reference"] + + - id: "terminal_m_s_007" + text: "That container's been in temp storage fourteen hours. Standard turnaround is six. Someone's buying time." + trigger: observe_anomaly + prerequisite: + facts: + contraband.supply_chain: KnowsOf + topic: [cargo] + mood: [analytical] + tags: ["investigation_seed"] + + - id: "terminal_m_s_012" + text: "Kael? In Corridor B-7? During shift transition? He has no reason to be there. None that I know of." + trigger: observe_npc + prerequisite: + entity: + target: "npc:kael-davan" + attribute: "routine_pattern" + condition: "deviation" + facts: + location.corridor_b7_restricted: KnowsOf + topic: [colleague, danger] + mood: [suspicious, conflicted] + tags: ["friend_contradiction", "wow_moment_3"] + dual_lens: + detective: "The dock worker — Davan — in a restricted corridor during transition. That's not routine maintenance." + notes: "THE FRIEND contradiction moment. Urgent chime. This is wow moment #3." +``` + +### Content Load Sequence (Server-Side) + +``` +PHASE 1: VOCABULARY LOAD (global/) + ├── Load global/enums/*.yaml → build tag enum registries + ├── Load global/facts/*.yaml → build FactId registry + ├── Load global/factions/*.yaml → build faction definitions + ├── Load global/entity-schema/attributes.yaml → build attribute key registry + └── Validate: all enum values, fact IDs, attribute keys are unique + +PHASE 2: DISTRICT LOAD (districts/{id}/) + ├── Load district.yaml → district metadata, NPC roster, location list + ├── Load npcs/*.yaml → NPC profiles + │ ├── Validate canonical_ids unique + │ ├── Validate relationship targets exist + │ ├── Validate known_facts reference valid FactIds + │ └── Build canonical_id → StableId mapping (ContentRegistry) + ├── Load locations/*.yaml → location spatial definitions + ├── Load triangles/*.yaml → triangle NPC references + fork definitions + │ └── Validate all NPC refs exist in ContentRegistry + ├── Load dialogue/**/*.yaml → line pool registry + │ └── Validate all tag values against enum registries + ├── Load monologue/**/*.yaml → per-character line pool registry + │ └── Validate prerequisites reference valid FactIds + entity attributes + └── Load routines/schedules.yaml → NPC schedule data + +PHASE 3: ENTITY SPAWN + ├── For each NPC in roster: + │ ├── Spawn bevy_ecs Entity + │ ├── Insert Npc marker component + │ ├── Insert axis components (Want, Secret, Relationships, etc.) + │ ├── Insert KnowledgeGraph component (pre-populated from information.known_facts) + │ ├── Insert DailyRoutine component (from routine data) + │ ├── Register StableId ↔ Entity in EntityRegistry + │ └── If Tier 1 FRIEND: insert FriendArc component with phase data + ├── For each location: + │ ├── Spawn location entity with spatial properties + │ └── Register in ContentRegistry + └── Build dialogue/monologue indices for fast runtime lookup + +PHASE 4: READY + └── Content loaded, entities spawned, registries built → simulation can start +``` + +**Hot-reload path:** File watcher detects YAML change → re-parse affected file → validate → if NPC: update axis components in-place (no respawn needed for value changes). If dialogue/monologue: rebuild affected line pool index. If structural change (new NPC, removed NPC): log warning, require restart. Hot-reload is a development convenience, not a production feature. + +--- + +## Topic 5: Technical Risk in the IN List + +I'll tier these. Tier 1 = low risk. Tier 2 = medium, manageable. Tier 3 = hard, needs careful scoping. Tier 4 = this is where the schedule dies if we're not careful. + +### Tier 1 — Low Risk (will work, standard engineering) + +**WASD movement + collision.** Already have `movement.rs` with `TilePosition`. Collision is tile-based occupancy checking. 8-directional movement is in `PlayerAction` already. Z-level transitions are the only wrinkle — handle as tile properties (stairs, elevators). Estimate: done or nearly done. + +**3 social sites (Terminal, Last Shift, Maintenance Corridors).** These are map content, not systems work. Server needs location entities with spatial bounds and properties (Meridian coverage, access restrictions). Client needs tile rendering. The hard part is art — which is explicitly "functional boxes with labels" for v0.1 (D-014). Estimate: location data loading is part of content loader; rendering is standard tile mapping. + +**NPC routines (schedule-based movement).** Time system exists (D-031, `time.rs`). Day phases exist. Schedule format is defined. NPCs move to specified locations at phase boundaries. This is a state machine: `current_phase → lookup schedule → pathfind to target location → move`. Estimate: 2-3 dev-days for the schedule system. Content authoring for 17 NPC schedules is the longer pole. + +### Tier 2 — Medium Risk (technically straightforward but integration-heavy) + +**Knowledge graph.** D-041 is well-defined. Types exist in `types.rs`. Sprint 2 delivers core data structures + direct observation flow + basic decay. The risk isn't the knowledge graph itself — it's that **everything depends on it**. Monologue prerequisites query it. Dialogue filtering queries it. The FRIEND arc's contradiction detection queries it. Entity rendering uses `RelationshipState` from it. If knowledge graph delivery slips, everything downstream slips. + +*Mitigation:* Knowledge graph Sprint 2 scope is intentionally narrow (direct observation + decay only). Sprint 3 adds the complex stuff. But Sprint 3 is also when monologue, dialogue, and FRIEND arc need to work. The dependency chain is tight. + +**2 playable characters seeing same world differently.** Architecturally, this is already designed for (D-010 principle 3). The `ObserverSnapshot` is per-entity — two players get different snapshots. Knowledge graphs are per-entity — two PCs have different knowledge. The risk is **content volume and consistency**. Every piece of monologue content is written twice (one per character). Every NPC needs a smuggler-lens AND detective-lens. Every triangle fork needs to make sense from both perspectives. + +*Mitigation:* The dual-lens requirement is a content team problem more than a server/client problem. But it doubles the content validation surface area — a monologue line that references a fact the detective can't learn is a content bug, not a code bug. Content validation tooling (Topic 3's schema validation) is the mitigation. + +**Context-sensitive interaction (approach NPC → prompt → dialogue).** Server: proximity trigger → interaction state → dialogue pool query. Client: prompt rendering → dialogue display. The plumbing is standard. The risk is the **dialogue selection algorithm**. D-035 defines 6 structural tags + 3 selection tags. Filtering by access + trust + situation is a hard filter (combinatorial but finite). Selecting by topic + mood is a weighted selection (needs tuning). The algorithm itself is maybe 200 lines of Rust. The tuning to make it *feel good* is the unknown. + +*Mitigation:* Build the line previewer CLI (already on my priority list) early. Let Mellanie and Paula test dialogue selection before it's in-game. Iterate on weights outside the game loop. + +### Tier 3 — High Risk (technically complex, integration-dependent) + +**Monologue system (tagged triggers, FactId prerequisites).** This is the feature that makes or breaks the vertical slice. Nine trigger types, each requiring different detection logic: + +| Trigger | Detection Complexity | Server System Required | +|---|---|---| +| `enter_location` | Low | Location transition event | +| `observe_npc` | Medium | LOS + entity identification | +| `hear_sound` | Medium | Sound range model (D-018) | +| `observe_anomaly` | **High** | Routine deviation detection | +| `post_conversation` | Low | Dialogue end event | +| `discover_evidence` | Medium | Knowledge graph state change | +| `witness_interaction` | **High** | Two-entity observation in LOS | +| `time_idle` | Low | Timer on no-input | +| `return_visit` | Medium | Location visit history | + +`observe_anomaly` and `witness_interaction` are the dangerous ones. `observe_anomaly` means "NPC is doing something outside their routine" — the server needs to know what "normal" looks like to detect "abnormal." This requires the routine system to track expected vs. actual behavior, which is a step beyond simple schedule execution. + +`witness_interaction` means "I see NPC A talking to NPC B" — requires tracking NPC-to-NPC interactions AND checking whether those interactions are in the observer's LOS. This is a second-order perception query. + +**Prerequisite evaluation** is the other risk. A prerequisite like: +```yaml +prerequisite: + entity: + target: "npc:kael-davan" + attribute: "routine_pattern" + condition: "deviation" + facts: + location.corridor_b7_restricted: KnowsOf +``` +...is a small query language. The server needs to evaluate arbitrary combinations of entity attribute checks + fact confidence checks + potentially boolean logic (AND/OR). Keep this dead simple for v0.1 — AND-only, no nesting, explicit conditions. The temptation to build a general-purpose query engine is scope death. + +*Estimate:* 8-12 dev-days for the full monologue pipeline (trigger detection + prerequisite evaluation + pool selection + pacing). The "pacing" part (how often monologue fires, cooldown between lines, priority when multiple lines qualify) is pure playtesting — budget 2-3 days of tuning time. + +**THE FRIEND arc (Kael for smuggler, Sera for detective).** This is the integration test for EVERYTHING: +- Knowledge graph contradiction detection (Sprint 3) +- Monologue trigger system (all 9 types exercise) +- Tell system (behavioral observation accumulation over time) +- Multi-phase dialogue shifts (D-028 all 4 layers) +- Spatial staging (NPC in specific place at specific time) +- Relationship state transitions (Friendly → PersonOfInterest) + +The risk isn't any individual system — it's the intersection. THE FRIEND arc requires all systems to work together correctly. A bug in knowledge decay timing means the contradiction window passes unnoticed. A bug in spatial staging means Kael is never in Corridor B-7 when the player is nearby. A bug in tell accumulation means the player has no behavioral evidence before the contradiction. + +*Mitigation:* THE FRIEND arc needs its own integration test — a scripted scenario that walks through the full 5-phase arc and validates each system fires correctly. This is D-030's CauseChain paying off: every knowledge event, every monologue trigger, every relationship state change gets provenance tracking, and the integration test can verify the causal chain matches expectations. + +*Estimate:* Server systems: 5-8 dev-days (contradiction detection + spatial staging + tell accumulation + FRIEND phase transitions). Content: probably the largest single content deliverable in v0.1 (70-100 lines per FRIEND, times 2 FRIENDs, all hand-authored). Client: relationship color shifts (D-033), dialogue phase UI cues. + +### Tier 4 — Schedule Killers (needs aggressive scoping or it takes over) + +**5 triangles with at least 1 fork each.** Each fork is a branching decision point. Each branch has consequences that ripple through NPCs, knowledge, and potentially other triangles. + +The math: 5 triangles × 1 fork × 2 branches × N consequences per branch. If consequences cascade across triangles, N grows fast. If Triangle 1's fork outcome changes NPC attitudes in Triangle 3, then Triangle 3's fork depends on Triangle 1's resolution, and you have dependency chains. + +*Scope control:* For v0.1, forks should be **terminal decisions** — they affect the triangle's own NPCs and change the player's knowledge, but they don't cascade into other triangles. Cross-triangle contamination is a v0.2 feature. This keeps each fork to 2-3 consequence entities, not 10-15. + +*Estimate:* If forks are self-contained: 2-3 dev-days for the fork/consequence system + 3-5 dev-days for content authoring 5 fork scenarios. If forks cascade: 8-12 dev-days for cascade resolution + testing. **Strongly recommend self-contained forks for v0.1.** + +**6 wow moments.** These aren't independent features — they're integration milestones. Each one is a "everything up to here works" gate: + +| Wow Moment | Systems Required | Risk | +|---|---|---| +| #1 Arrival | Ambiance + routines + opening monologue | Low | +| #2 Character's Eye | Monologue trigger + routine deviation detection | **Medium-High** | +| #3 FRIEND Contradiction | Full FRIEND arc + spatial staging + contradiction detection | **High** | +| #4 Divergence Reveal | Dual-lens content + relationship colors + monologue partitioning | Medium | +| #5 News Ticker | Ticker system + dual monologue reactions | **Medium** (ticker is new UI element) | +| #6 Quiet Moment | time_idle trigger + reflective monologue | Low | + +The dependency chain: #1 is achievable early. #6 is achievable early. #2 requires monologue system. #5 requires ticker + monologue. #4 requires knowledge graph + rendering. #3 requires everything. + +**#3 is the critical path.** If THE FRIEND contradiction doesn't work, the demo's emotional peak is missing. Everything should be prioritized to unblock #3. + +### Summary Risk Matrix + +| Feature | Risk Level | Primary Risk Factor | Mitigation | +|---|---|---|---| +| WASD + collision | Low | None significant | Already built | +| 3 social sites | Low | Art (not scope concern at v0.1) | Functional boxes | +| NPC routines | Low-Medium | Content authoring time | Start schedules early | +| Knowledge graph | Medium | Downstream dependency chain | Sprint 2 narrow scope holds | +| 2 playable characters | Medium | Content volume doubling | Content validation tooling | +| Context-sensitive interaction | Medium | Dialogue selection tuning | Line previewer CLI | +| Monologue system | **High** | Trigger detection + prerequisite evaluation | Simple AND-only prerequisites, defer complex triggers | +| THE FRIEND arc | **High** | Integration of all systems | Dedicated integration test, prioritize for critical path | +| 5 triangle forks | **Medium-High** | Cascade complexity | Self-contained forks, no cross-triangle effects in v0.1 | +| 6 wow moments | Varies | #3 is the critical path | Prioritize everything that unblocks #3 | + +**What will take longer than expected:** +1. **Monologue prerequisite tuning.** The gap between "prerequisites work" and "prerequisites feel right" is large. Content authors will write prerequisites that are too strict (nothing fires) or too loose (everything fires). Budget iteration time. +2. **Dialogue selection weighting.** Same tuning problem. Getting NPCs to say contextually appropriate things from a tagged pool requires weight iteration. +3. **Spatial staging for THE FRIEND.** Getting Kael into Corridor B-7 *when the player is nearby* without feeling scripted is a storyteller-level challenge compressed into one specific moment. +4. **Content conversion.** Transforming 17 NPC wiki profiles + 24 fact entries + 3 location descriptions + 5 triangles from prose markdown into structured YAML is manual labor. Each NPC file needs ~30 minutes of careful conversion. Budget 2-3 full days for the initial wiki → content conversion pass. + +--- + +## Topic 6: Cross-Team Ticket Splits + +The content pipeline creates work that spans all three teams (server, client, copy). Here's my proposed split for the major ticket categories. Each "ticket" below is a logical unit that should be one or two actual tickets. + +### Content Infrastructure (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| Content directory structure | Create `content/` tree, `content.yaml` manifest | — | Populate initial files | +| Content loader (Phase 1-2) | `ContentRegistry`, YAML deserialization, entity spawn from content | — | — | +| Content loader (Phase 3-4) | Line pool indexing, hot-reload watcher | — | — | +| Content validation schemas | serde validation on load | — | JSON Schema files for YAML | +| Line previewer CLI | Build CLI: YAML → filtered line output | — | Test with real content | + +### Wiki → Content Conversion (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| NPC profile conversion (17 NPCs) | Define `NpcProfile` serde struct | — | Convert wiki MD → YAML (17 files) | +| Location conversion (3 sites) | Define `LocationDef` serde struct | — | Convert wiki MD → YAML (3 files) | +| Fact catalog conversion | Already defined (`FactId`, `FactKnowledge`) | — | Split fact-catalog.md → 6 YAML files | +| Triangle definitions | Define `TriangleDef` serde struct | — | Write 5 triangle YAML files | +| Routine/schedule conversion | Define `ScheduleEntry` serde struct | — | Write schedules.yaml for 17 NPCs | + +### Monologue System (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| Monologue trigger detection | 9 trigger type implementations | — | — | +| Monologue prerequisite evaluation | Knowledge graph query engine (simple AND-only) | — | — | +| Monologue line pool selection | Tag filtering + weighted selection | — | — | +| Monologue pacing/cooldown | Rate limiter, priority queue, cooldown timers | — | — | +| Monologue content authoring | — | — | Write monologue lines (2 chars × 3 locs + general) | +| Monologue display UI | — | Text panel, chime trigger, fade/scroll | — | +| Monologue chime audio | — | Audio event handler for monologue chime SFX | — | + +### Dialogue System (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| Dialogue pool loader | YAML → indexed line pool, tag filtering | — | — | +| Dialogue selection algorithm | Access/trust hard filter + topic/mood weighted select | — | — | +| Dialogue content authoring | — | — | Write role-based dialogue (10+ roles × location) | +| Dialogue UI | — | Dialogue box, NPC name display, line rendering | — | + +### Interaction Model (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| Proximity detection | Distance threshold check per tick | — | — | +| Interaction state machine | Idle → Prompt → InDialogue → Exit | — | — | +| Interaction prompt UI | — | "Press E to talk" / context-sensitive label | — | + +### THE FRIEND Arc (Sprint 3-4, cross-team) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| FRIEND phase transition system | Phase state machine, trigger evaluation | — | — | +| Contradiction detection | `Contradicted` KnowledgeState, spatial observation vs. expected location | — | — | +| Tell system accumulation | Behavior flag aggregation, tell detection events | — | — | +| FRIEND content (Kael) | — | — | 70-100 lines: dialogue + monologue + tells + contradiction | +| FRIEND content (Sera) | — | — | 70-100 lines: dialogue + monologue + tells + contradiction | +| Relationship color rendering | — | Entity color shifts per D-033 RelationshipState | — | +| FRIEND integration test | Test scenario: full 5-phase arc validation | — | Test content fixtures | + +### NPC Rendering + Routines (Sprint 3) + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| NPC schedule execution | DayPhase → location lookup → pathfind → move | — | — | +| NPC routine deviation events | Expected vs. actual location tracking | — | — | +| NPC sprite/label rendering | — | Entity kind → sprite, name display from snapshot | — | +| NPC movement animation | — | Interpolated movement between tiles | — | + +### Cross-Cutting + +| Ticket | Server | Client | Copy | +|---|---|---|---| +| PC-as-NPC (other character exists) | NPC entity with PC profile data | NPC rendering (same as any NPC) | ~20-30 authored items | +| News ticker system | Ticker event generation | Ticker UI widget | Ticker text + dual monologue reactions | +| Smuggler lens parity | — | — | Smuggler attribute vocabulary + lens sections for all 17 NPCs | +| Hael → Naia Tamm rename | Update any hardcoded refs | — | Execute rename across all wiki + content files | + +### Dependency Chain + +``` +Content directory structure + └── Content loader (Phase 1-2) + ├── NPC profile conversion ──→ NPC schedule execution + ├── Location conversion + ├── Fact catalog conversion + └── Content loader (Phase 3-4) + ├── Dialogue pool loader ──→ Dialogue selection ──→ Dialogue UI + ├── Monologue trigger detection + │ └── Monologue prerequisite evaluation + │ └── Monologue line pool selection + │ └── Monologue pacing ──→ Monologue display UI + └── Triangle definitions ──→ Triangle fork system + +Proximity detection ──→ Interaction state machine ──→ Interaction prompt UI ──→ Dialogue UI + +Knowledge graph (Sprint 2) ──→ Contradiction detection ──→ FRIEND phase transitions + ──→ Monologue prerequisite evaluation + ──→ Tell system accumulation + +FRIEND content (copy) ──→ FRIEND integration test +Monologue content (copy) ──→ Monologue pacing tuning +Dialogue content (copy) ──→ Dialogue selection tuning +``` + +**Critical path to wow moment #3 (THE FRIEND contradiction):** +Knowledge graph → Contradiction detection → FRIEND phase transitions → Spatial staging (routine deviation) → Monologue trigger (observe_npc with deviation condition) → Monologue display → FRIEND content authored and loaded. + +Every block on this chain is either server or copy team work. Client only needs monologue display and relationship color rendering to support #3. **Server and copy are the bottleneck for the emotional peak of the demo.** + +--- + +## Summary + +1. **Format: YAML.** Content authors write it, serde reads it, generators produce it later. +2. **Structure: `global/` + `districts/`.** Vocabulary is global, instances are per-district. Generator-compatible from day one. +3. **Addressing: `canonical_id` in every file.** Maps to `StableId` at runtime via `ContentRegistry`. Deterministic assignment. +4. **Critical path: THE FRIEND arc.** Every technical risk converges on wow moment #3. Server-side systems (monologue triggers, prerequisite evaluation, contradiction detection, spatial staging) and copy-side content (FRIEND profiles, dialogue, monologue) are the two parallel tracks that must converge. +5. **Biggest schedule risk: content conversion + monologue tuning.** The system can be built, but making it *feel right* requires authored content and iteration time. Start the wiki → YAML conversion early so server engineers have real data to test against. diff --git a/docs/workshops/v01-content-scoping/round2-dudley.md b/docs/workshops/v01-content-scoping/round2-dudley.md new file mode 100644 index 000000000..974f037bc --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-dudley.md @@ -0,0 +1,787 @@ +# Round 2 — Dudley (Server Developer) + +## 1. YAML Acceptance — Loader Adaptation + +Accepted. YAML is the content format for v0.1. The lead has spoken and Tyre's rationale is sound — content authors (Mellanie, Paula) need to write and read these files, and `serde_yaml` is mature enough for our needs. + +### What Changes in the Loader + +Almost nothing structurally. The `serde` derive macros work identically across formats — the Rust structs stay the same, only the deserializer call changes: + +```rust +// Round 1 (RON): +// let profile: NpcProfile = ron::from_str(&content)?; + +// Round 2 (YAML): +let profile: NpcProfile = serde_yaml::from_str(&content)?; +``` + +The 6-phase load sequence from Round 1 is format-agnostic. All phase ordering, validation rules, and resource creation remain identical. Specific adaptations: + +| Item | Round 1 (RON) | Round 2 (YAML) | Impact | +|------|---------------|----------------|--------| +| Manifest | `content/manifest.ron` | `content/content.yaml` | Filename only | +| NPC profiles | `.ron` files | `.yaml` files | Extension only | +| Dialogue/monologue | `.ron` files | `.yaml` files | Extension only | +| Multi-line text | Rust string escaping | YAML native `\|` / `>` blocks | Better for authors — no escaping | +| Validation | Serde-only | Serde + JSON Schema (Tyre's dual validation) | Strictly more validation | +| Dependency | `ron` crate | `serde_yaml` crate | Swap one dependency | + +The one genuine improvement: YAML's native multi-line text support (`|` for literal blocks) means dialogue and monologue text will be cleaner in the content files. No escaped newlines, no awkward string concatenation. Content authors benefit directly. + +### Cargo.toml Change + +```toml +# Add: +serde_yaml = "0.9" +# The ron crate stays for potential future use (RON converter per lead's decision) +``` + +### RON Converter + +Per the lead's decision, we build a RON converter. My recommendation: + +- **Location:** `tooling/content-converter/` — a small Rust binary, not part of the server +- **Direction:** YAML → RON only (content files are authored in YAML; if we ever want RON for hot-path loading, the converter produces it) +- **When it runs:** Build-time, optional. Not load-time — the server loads YAML directly. The converter is insurance for a hypothetical future where YAML parse speed matters (it won't for v0.1) +- **Priority:** Low. Build it when someone asks for it, not proactively + +--- + +## 2. Multi-Action Interaction Model + +### The Architecture: N Actions, 1 Client Selection + +The server computes ALL available actions for each nearby entity. The `InteractionOptions` struct from Round 1 already supports this. The v0.1 client selects the top-ranked action to display. The v0.2 client shows the full list. + +```rust +/// Available interactions for a nearby entity. +/// Server computes all; client displays based on version. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct EntityInteractions { + /// StableId of the target entity + pub entity_id: u64, + /// Display name (knowledge-filtered — "Dock Worker" or "Kael Davan") + pub display_name: String, + /// All available actions, ordered by priority (highest first). + /// v0.1 client shows actions[0] only. + /// v0.2+ client shows all (radial menu, list, etc.) + pub actions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AvailableAction { + /// Action identifier used in PlayerAction::InteractWith + pub action_id: String, + /// What the action is + pub kind: ActionKind, + /// Display label for the client ("Talk", "Examine", "Ask about the schedule") + pub label: String, + /// Priority for v0.1 single-action selection (higher = shown first) + pub priority: u8, + /// Can this action be performed right now? + pub enabled: bool, + /// Why not? (shown as tooltip in v0.2+, ignored in v0.1) + pub disabled_reason: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ActionKind { + /// Study the entity without initiating contact. + /// Updates knowledge graph with behavioral observations. + Examine, + /// Initiate dialogue. Opens conversation state. + Talk, + /// Look at an environmental object. Returns description + knowledge grants. + ExamineObject, + /// Listen to a nearby NPC-NPC conversation. Passive — no social cost. + Overhear, +} +``` + +### Priority Ranking System + +The server determines action priority per context. This is where the "single context-sensitive action" behavior lives: + +```rust +/// Determine default action priority for a given entity and context. +/// Returns actions sorted by priority (highest first). +fn rank_actions( + target_kind: EntityKind, + observer_knowledge: &KnowledgeGraph, + target_stable_id: &StableId, + target_in_conversation: bool, + target_activity: &str, +) -> Vec { + let mut actions = Vec::new(); + + match target_kind { + EntityKind::Npc => { + // Examine is always available for NPCs in LOS + let examine_priority = if observer_knowledge + .entity_knowledge(target_stable_id) + .map(|ek| ek.known_attributes.contains_key("contradiction_flagged")) + .unwrap_or(false) + { + // Post-contradiction: Examine becomes high priority + // "Watch them more carefully" + 90 + } else { + // Default: Examine is secondary to Talk + 40 + }; + + actions.push(AvailableAction { + action_id: format!("examine_{}", target_stable_id.0), + kind: ActionKind::Examine, + label: "Observe".to_string(), + priority: examine_priority, + enabled: true, + disabled_reason: None, + }); + + // Talk: available if NPC is not sleeping, not in combat, not already + // in conversation with someone else + let talk_enabled = !target_in_conversation + && target_activity != "sleeping"; + + actions.push(AvailableAction { + action_id: format!("talk_{}", target_stable_id.0), + kind: ActionKind::Talk, + label: "Talk".to_string(), + priority: if talk_enabled { 50 } else { 10 }, + enabled: talk_enabled, + disabled_reason: if !talk_enabled { + Some("Busy".to_string()) + } else { + None + }, + }); + + // Overhear: available if NPC is in conversation with another NPC + if target_in_conversation { + actions.push(AvailableAction { + action_id: format!("overhear_{}", target_stable_id.0), + kind: ActionKind::Overhear, + label: "Listen".to_string(), + priority: 60, + enabled: true, + disabled_reason: None, + }); + } + } + EntityKind::Object => { + actions.push(AvailableAction { + action_id: format!("examine_obj_{}", target_stable_id.0), + kind: ActionKind::ExamineObject, + label: "Examine".to_string(), + priority: 50, + enabled: true, + disabled_reason: None, + }); + } + _ => {} + } + + // Sort by priority descending + actions.sort_by(|a, b| b.priority.cmp(&a.priority)); + actions +} +``` + +### How v0.1 and v0.2 Differ — Client Side Only + +| Behavior | v0.1 | v0.2+ | +|----------|------|-------| +| Actions computed | ALL (server computes full list) | ALL (same) | +| Actions sent to client | ALL (via `EntityInteractions.actions`) | ALL (same) | +| Actions displayed | `actions[0]` only — single prompt | All enabled actions — menu/radial | +| Player input | `Interact` → server applies `actions[0]` | `InteractWith(action_id)` → server applies specific action | +| Architecture change needed for v0.2 | **None on server.** Client reads more of the existing data. | Client UI change only | + +The key guarantee: **zero server-side changes for v0.2 multi-verb.** The server already sends everything. The client just reads more of what's already there. + +### PlayerAction Extension + +```rust +pub enum PlayerAction { + // ... existing movement variants ... + + /// v0.1: interact with nearest entity, server picks top action. + Interact, + + /// v0.2+: interact with specific entity using specific action. + /// Falls back to top action if action_id is None. + InteractWith { + target_id: u64, // StableId of target + action_id: Option, // specific action, or None for default + }, + + /// Select a dialogue topic during active conversation. + DialogueSelect { topic: String }, + + /// End the active conversation. + DialogueEnd, +} +``` + +For v0.1, the client sends `Interact`. The server resolves to the nearest entity and applies `actions[0]`. When v0.2 introduces multi-verb UI, the client sends `InteractWith { target_id, action_id }`. The server handles both — `Interact` is syntactic sugar for "find nearest, pick top action." + +### Stig's Question: IPC Message Format + +Stig asked what the state update looks like. The answer: **full text in the message, not IDs for client-side lookup.** This keeps the client thin and prevents information leakage. The `ObserverSnapshot` already carries everything the client needs. Interaction data rides the same channel: + +```rust +/// ObserverSnapshot v3 — adds interaction and dialogue data. +pub struct ObserverSnapshot { + pub version: u8, // 3 + pub tick: u64, + pub game_time: GameTime, + pub player_facing: FacingDirection, + pub entities: Vec, + pub visible_tiles: Vec, + + // v3 additions: + + /// Entities within interaction range with available actions. + /// Empty if no interactable entities are nearby. + pub nearby_interactions: Vec, + + /// Active dialogue state. None if not in conversation. + pub active_dialogue: Option, + + /// Monologue lines triggered this tick. Usually 0 or 1. + pub monologue: Vec, + + /// Overheard conversation fragments. Passive, proximity-based. + pub overheard: Vec, + + /// Current simulation speed (1.0 = normal, 0.5 = overlay, 0.0 = paused). + /// Client uses this to adjust animation/interpolation rates. + pub sim_speed: f32, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActiveDialogue { + pub npc_entity_id: u64, + pub npc_name: String, + pub npc_relationship_color: RelationshipState, + /// Current NPC line. None before first line / between lines. + pub current_line: Option, + /// Available topics the player can raise. Empty if conversation is NPC-led. + pub available_topics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DialogueLineDisplay { + pub line_id: String, + pub text: String, + pub mood: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TopicOption { + pub topic_id: String, + pub label: String, + pub enabled: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MonologueDisplay { + pub line_id: String, + pub text: String, + pub trigger: String, + /// true = use urgent chime (observe_anomaly, discover_evidence) + pub urgent: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OverheardDisplay { + pub speaker_id: u64, + pub speaker_name: String, + pub fragment: String, +} +``` + +Everything the client needs to render is in the snapshot. The client never queries content files. The server is the single source of truth. + +--- + +## 3. Pause System — Tick Rate Modulation + +### The Model: SimulationSpeed Resource + +Currently `SimulationTime` has a boolean `paused` field. That's insufficient for 50% speed. I'll replace it with a speed multiplier model: + +```rust +/// Simulation speed states. +/// Determines how many ticks advance per real-time frame. +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum SimSpeed { + /// Normal speed: 1 tick per frame at target TPS (10 tps per D-031). + Normal, + /// Reduced speed: tick every other frame (50% speed). + /// Used when UI overlays are active (knowledge panel, dialogue). + Overlay, + /// Full pause: no ticks advance. + /// Spacebar toggle. Always available. + Paused, +} + +impl SimSpeed { + /// Returns the tick advance rate. + /// Normal = advance every frame. + /// Overlay = advance every 2nd frame. + /// Paused = never advance. + pub fn should_tick(&self, frame_counter: u64) -> bool { + match self { + SimSpeed::Normal => true, + SimSpeed::Overlay => frame_counter % 2 == 0, + SimSpeed::Paused => false, + } + } + + /// Serialized speed for client animation adjustment. + pub fn as_f32(&self) -> f32 { + match self { + SimSpeed::Normal => 1.0, + SimSpeed::Overlay => 0.5, + SimSpeed::Paused => 0.0, + } + } +} + +/// Resource tracking simulation speed and frame count. +#[derive(Resource, Debug, Clone)] +pub struct SimulationSpeed { + /// Current speed state. + pub speed: SimSpeed, + /// Frame counter for Overlay tick skipping. + pub frame_counter: u64, + /// Stack of speed modifiers. Highest priority wins. + /// When all modifiers are removed, returns to Normal. + speed_stack: Vec, +} + +#[derive(Debug, Clone)] +struct SpeedModifier { + source: SpeedSource, + speed: SimSpeed, + priority: u8, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +pub enum SpeedSource { + /// Spacebar toggle — highest priority, always wins. + PlayerPause, + /// Knowledge panel open. + KnowledgePanel, + /// Active dialogue with NPC. + Dialogue, +} +``` + +### State Machine + +``` + ┌──────────┐ + ┌──────→│ Normal │◄──────┐ + │ │ (1.0x) │ │ + │ └────┬─────┘ │ + │ │ │ + close overlay open overlay spacebar + │ │ │ + │ ┌────▼─────┐ │ + ├──────→│ Overlay │──────→│ + │ │ (0.5x) │ │ + │ └────┬─────┘ │ + │ │ │ + │ spacebar │ + │ │ │ + │ ┌────▼─────┐ │ + └───────│ Paused │───────┘ + │ (0.0x) │ + └──────────┘ +``` + +Rules: +1. **Spacebar always toggles full pause.** From Normal → Paused. From Overlay → Paused. From Paused → returns to whatever state was active before pause (Normal or Overlay). +2. **Opening knowledge panel or dialogue → Overlay (50%).** Multiple overlays don't stack — it's 50% whether you have one or three open. +3. **Closing all overlays → Normal.** But only if not explicitly paused by spacebar. +4. **Spacebar overrides everything.** It's the master switch. + +### Implementation: Modified advance_tick + +```rust +/// System: advance tick based on current simulation speed. +/// Replaces the current boolean-paused advance_tick. +pub fn advance_tick( + mut time: ResMut, + mut speed: ResMut, +) { + speed.frame_counter += 1; + + if speed.speed.should_tick(speed.frame_counter) { + time.tick += 1; + } +} + +/// System: process pause/speed PlayerActions. +pub fn process_speed_input( + mut speed: ResMut, + input_events: Res, + // ... other input handling +) { + // Spacebar pause toggle + // When paused by spacebar, restore previous speed on unpause + // Overlay open/close events modify the speed stack +} +``` + +### What the Client Needs to Know + +The `sim_speed: f32` field in ObserverSnapshot v3 tells the client: +- `1.0` → normal animation/interpolation +- `0.5` → halve animation rates, entities move at half speed visually +- `0.0` → freeze all animation, UI stays responsive + +The client doesn't manage pause state. It reads the speed from the snapshot and adjusts rendering accordingly. The server is authoritative. + +### Dialogue-Specific Pause Behavior + +When dialogue is active (player is talking to an NPC), simulation runs at Overlay speed (50%). This means: +- NPCs continue their routines at half speed — the world doesn't freeze when you talk +- Other NPCs can walk past during your conversation — you might miss something +- But it's slow enough that the player doesn't feel punished for engaging in dialogue + +If the player hits spacebar during dialogue, full pause. They can read at their own pace. Spacebar again resumes to Overlay (because dialogue is still active). + +--- + +## 4. FriendArc Phase Transitions — Triggers and Content Dependencies + +### Phase Transition State Machine + +``` + WARMTH ──────→ TRUST ──────→ DOUBT ──────→ CONFLICT + │ │ │ │ + │ auto (tick) │ knowledge │ observation │ knowledge + + │ │ threshold │ + monologue │ dialogue + │ │ │ │ + ▼ ▼ ▼ ▼ + "We're good" "I trust you" "Something's "I know what + wrong" you did" +``` + +### Phase-by-Phase: Triggers, Content, and What the Server Needs + +#### Phase 1: WARMTH → TRUST + +**Trigger:** Interaction count threshold + time elapsed. + +```rust +/// Transition from Warmth to Trust. +/// This is the "getting to know you" phase — automatic progression +/// as the player interacts with the FRIEND over time. +fn check_warmth_to_trust( + friend_arc: &FriendArc, + interaction_log: &InteractionLog, + time: &SimulationTime, +) -> bool { + let interactions_with_friend = interaction_log + .count_interactions(friend_arc.bonded_character, friend_arc.friend_entity); + let time_since_first = time.tick.saturating_sub( + interaction_log.first_interaction_tick( + friend_arc.bonded_character, friend_arc.friend_entity + ).unwrap_or(time.tick) + ); + + // Require both: enough interactions AND enough time. + // Prevents rushing through the arc by spam-talking. + interactions_with_friend >= 3 && time_since_first >= 600 // 1 game-hour +} +``` + +**Content required:** +- 5-8 dialogue lines for Warmth phase (casual, friendly, establishing rapport) +- 3-5 monologue lines for Warmth (positive: "Kael's reliable. Good to have someone you can count on.") +- Trust transition is silent — no dramatic moment. The player just notices the FRIEND is warmer + +**Server state change:** `FriendArc.phase = Trust`. No knowledge graph change. No visual change. The NPC's dialogue pool shifts to Trust-phase lines (gated by the `situation: [friend_trust]` tag). + +#### Phase 2: TRUST → DOUBT + +**Trigger:** Knowledge threshold — the player character learns specific facts that create tension. + +```rust +/// Transition from Trust to Doubt. +/// Requires the bonded character to accumulate suspicious knowledge. +/// NOT a single event — it's a gradual shift. +fn check_trust_to_doubt( + friend_arc: &FriendArc, + observer_kg: &KnowledgeGraph, +) -> bool { + // The character must know at least ONE of the doubt-triggering facts + // at KnowsOf or higher confidence. + let doubt_facts = &friend_arc.doubt_trigger_facts; + + doubt_facts.iter().any(|fact_id| { + observer_kg.fact_at_least(fact_id, KnowledgeConfidence::KnowsOf) + }) +} +``` + +**Content required for Kael (smuggler's FRIEND):** +- Doubt-triggering facts (defined in NPC profile): + - `"kael.schedule_inconsistency"` — Kael's routine doesn't fully match the posted schedule + - `"ring.internal_pressure"` — the smuggler learns the ring is under strain +- 5-8 dialogue lines for Trust phase (deeper, more personal, sharing more) +- 3-5 monologue lines for Doubt phase (questioning: "Kael left early again. Third time this rotation. Where does he go?") +- Tell acceleration: Kael's tells become more frequent in Doubt phase. The `tell_stage` counter advances, making tells more visible + +**Content required for Sera (detective's FRIEND):** +- Doubt-triggering facts: + - `"sera.avoidance_pattern"` — Sera consistently avoids Torek Lintar + - `"investigation.evidence_gap"` — evidence exists that should have been reported but wasn't +- Same line counts as Kael, detective-perspective + +**Server state change:** `FriendArc.phase = Doubt`. Tell stage advances (`tell_stage += 1`). Monologue pool shifts. No visual change yet — the entity color stays green (Friendly). The player should feel uneasy but not alarmed. + +#### Phase 3: DOUBT → CONFLICT + +**Trigger:** Direct observation of the contradiction. This is the critical moment — wow moment #3. + +```rust +/// Transition from Doubt to Conflict. +/// Requires DIRECT OBSERVATION of the contradiction. +/// This cannot be told to the player — they must SEE it. +fn check_doubt_to_conflict( + friend_arc: &FriendArc, + observer_kg: &KnowledgeGraph, +) -> bool { + // ALL contradiction facts must be known at KnowsDetails or higher. + // At least one must have source = DirectObservation. + let all_known = friend_arc.contradiction_facts.iter().all(|fact_id| { + observer_kg.fact_at_least(fact_id, KnowledgeConfidence::KnowsDetails) + }); + + // The discovery flag is set by the spatial staging system + // when the player observes the FRIEND in the wrong place. + all_known && friend_arc.contradiction_discovered +} +``` + +**How `contradiction_discovered` gets set — spatial staging:** + +This is the hardest part. The server must arrange for the FRIEND to be in a specific place at a specific time, and the player must be able to observe it. The simulation can't script this — it must create the opportunity and let the player find it. + +```rust +/// System: check if the bonded character observes the FRIEND +/// in a contradiction location during a contradiction window. +fn check_contradiction_observation( + friend_query: Query<(&FriendArc, &NpcRoutine, &TilePosition), With>, + observer_query: Query<(&KnowledgeGraph, &TilePosition), With>, + location_map: Res, + time: Res, + mut event_queue: ResMut, +) { + for (arc, routine, npc_pos) in friend_query.iter() { + if arc.phase != FriendPhase::Doubt { + continue; + } + + // Is the FRIEND currently at their contradiction location? + let at_contradiction = arc.contradiction_location.as_ref() + .map(|loc| location_map.contains_position(loc, npc_pos)) + .unwrap_or(false); + + // Is it the right time window? + let in_window = arc.contradiction_time_window.as_ref() + .map(|w| w.active_at(&time)) + .unwrap_or(true); // no time window = always active + + if !at_contradiction || !in_window { + continue; + } + + // Is the bonded character observing? + // (They must have LOS to the FRIEND's current position) + // The perception system handles LOS. We check if the bonded + // character has Direct confidence on the FRIEND. + for (obs_kg, _obs_pos) in observer_query.iter() { + if let Some(entry) = obs_kg.entity_knowledge(&arc.friend_stable_id) { + if entry.confidence == KnowledgeConfidence::Direct { + // The player character is looking at their FRIEND + // in the wrong place at the wrong time. + // This is wow moment #3. + + // Emit knowledge event for contradiction facts + // Emit monologue trigger (observe_npc with deviation) + // Set contradiction_discovered = true + } + } + } + } +} +``` + +**Content required for the contradiction moment:** +- **Kael:** Observed in Corridor B-7 (maintenance corridors) during shift transition, meeting unknown contact. The smuggler knows Kael has no reason to be there. + - Monologue line (urgent chime): "Kael? In Corridor B-7? During shift transition? He has no reason to be there. None that I know of." (`terminal_m_s_012` from Tyre's example) + - Knowledge grant: `"kael.secret_meetings": KnowsDetails` + - Knowledge grant: `"kael.restricted_corridor_visits": KnowsDetails` +- **Sera:** Observed avoiding Torek Lintar (turning away when he enters the bar). The detective recognizes the pattern. + - Monologue line (urgent chime): detective-equivalent — recognizing deliberate avoidance + - Knowledge grant: `"sera.avoidance_pattern": KnowsDetails` + +**Server state change:** `FriendArc.phase = Conflict`. `FriendArc.contradiction_discovered = true`. **RelationshipState shifts: Friendly → PersonOfInterest.** Entity color shifts green → amber (D-033, 0.5s fade). Tell stage maxes out. Dialogue pool shifts to Conflict-phase lines. + +This is the single most important moment in the vertical slice. State consistency requires: +1. Knowledge graph updated with contradiction facts +2. FriendArc phase advanced +3. RelationshipState changed +4. Monologue triggered (urgent) +5. Entity color change visible in next ObserverSnapshot + +All five must happen atomically within the same tick. I need to verify the tick order handles this correctly — knowledge event processing must complete before snapshot generation. + +#### Phase 4: CONFLICT (Terminal) + +**Trigger:** None — this is the final phase. The player is in Conflict with their FRIEND. + +**Content required:** +- 8-12 Conflict-phase dialogue lines (confrontation options, defensive NPC responses, the NPC's sympathetic motivation revealed) +- 5-8 Conflict-phase monologue lines (the character processing betrayal, re-evaluating past interactions) +- No clean resolution — D-034 mandates this. The conversation can happen, understanding can emerge, but the trust is broken + +**Server state:** `FriendArc.phase = Conflict`. RelationshipState stays PersonOfInterest (or shifts to Hostile if the confrontation goes badly). The arc is complete. + +### Revised FriendArc Component + +```rust +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct FriendArc { + /// Which character this FRIEND is bonded to (StableId). + pub bonded_character: StableId, + /// StableId of this FRIEND NPC (for knowledge graph lookups). + pub friend_stable_id: StableId, + /// Current phase. + pub phase: FriendPhase, + + // --- Transition triggers --- + + /// Facts that trigger Doubt phase (any one sufficient). + pub doubt_trigger_facts: Vec, + /// Facts that constitute the full contradiction (all required for Conflict). + pub contradiction_facts: Vec, + /// Location where the contradiction occurs. + pub contradiction_location: Option, // canonical location ID + /// Time window for the contradiction (day phase + optional minute range). + pub contradiction_time_window: Option, + + // --- State tracking --- + + /// Tell visibility stage (0 = minimal, advances as phase progresses). + pub tell_stage: u8, + /// Has the bonded character directly observed the contradiction? + pub contradiction_discovered: bool, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TimeWindow { + pub phase: DayPhase, + pub start_minute: Option, // within the phase + pub end_minute: Option, +} + +impl TimeWindow { + pub fn active_at(&self, time: &SimulationTime) -> bool { + if time.day_phase() != self.phase { + return false; + } + let tod = time.time_of_day_minutes(); + let phase_start = match self.phase { + DayPhase::Morning => 0, + DayPhase::Afternoon => 360, + DayPhase::Evening => 720, + DayPhase::Night => 1080, + }; + let start = phase_start + self.start_minute.unwrap_or(0); + let end = phase_start + self.end_minute.unwrap_or(360); + tod >= start && tod < end + } +} +``` + +### Content Summary — What I Need from Mellanie and Paula + +| FRIEND NPC | Phase | Lines Needed | Type | Blocker? | +|-----------|-------|-------------|------|----------| +| **Kael** | Warmth | 5-8 | dialogue (casual, friendly) | No — generic enough to synthesize | +| **Kael** | Warmth | 3-5 | monologue (positive, routine) | No | +| **Kael** | Trust | 5-8 | dialogue (personal, deeper) | Yes — needs character voice | +| **Kael** | Trust | 3-5 | monologue (warming, comfortable) | Yes | +| **Kael** | Doubt | 5-8 | dialogue (evasive, stressed) | Yes | +| **Kael** | Doubt | 3-5 | monologue (questioning, uneasy) | Yes | +| **Kael** | Conflict | 8-12 | dialogue (confrontation, revelation) | Yes — most critical | +| **Kael** | Conflict | 5-8 | monologue (processing, betrayal) | Yes | +| **Kael** | Contradiction | 1 | monologue (urgent, wow moment #3) | **CRITICAL** | +| **Sera** | (same counts) | ~35-46 | (same breakdown) | Same blockers | + +**Total per FRIEND NPC: ~35-46 authored lines minimum.** +**Total for both: ~70-92 lines.** + +This aligns with D-034's estimate of 70-100 per FRIEND. The breakdown by phase is what I need to test each transition independently. + +### Interaction Log Component + +I need a lightweight interaction tracking component to support the Warmth → Trust transition: + +```rust +/// Tracks interaction history between entities. +/// Used for FriendArc Warmth→Trust transition and future NOBODY promotion. +#[derive(Component, Debug, Clone, Serialize, Deserialize)] +pub struct InteractionLog { + /// (target_stable_id, tick) pairs. + /// BTreeMap for deterministic iteration. + pub interactions: BTreeMap>, +} + +impl InteractionLog { + pub fn new() -> Self { + Self { interactions: BTreeMap::new() } + } + + pub fn record(&mut self, target: StableId, tick: u64) { + self.interactions.entry(target).or_default().push(tick); + } + + pub fn count(&self, target: &StableId) -> usize { + self.interactions.get(target).map(|v| v.len()).unwrap_or(0) + } + + pub fn first_tick(&self, target: &StableId) -> Option { + self.interactions.get(target).and_then(|v| v.first().copied()) + } +} +``` + +This also becomes the foundation for NOBODY tier promotion in v0.2 — the same interaction counter that drives FriendArc Warmth→Trust will drive NOBODY→NOTICED. Building it now means zero rework later. + +--- + +## Summary of Round 2 Deliverables + +| Decision | Resolution | +|----------|-----------| +| **YAML format** | Accepted. `serde_yaml` replaces `ron` for content loading. RON converter in `tooling/` as low-priority insurance. | +| **Multi-action model** | Server computes ALL available actions per entity, ranked by priority. v0.1 client shows `actions[0]`. v0.2 client shows all. Zero server changes needed for v0.2. | +| **Pause system** | `SimSpeed` enum: Normal (1.0x), Overlay (0.5x), Paused (0.0x). Spacebar always toggles full pause. UI overlays trigger Overlay speed. Speed stack with priority resolution. | +| **FriendArc transitions** | 4-phase state machine with explicit triggers: Warmth (interaction count + time) → Trust (knowledge threshold) → Doubt (specific facts) → Conflict (direct observation of contradiction). Contradiction detection requires spatial staging system. | + +### Dependencies I'm Tracking + +1. **ObserverSnapshot v3** — Tyre needs to sign off on the protocol extension. I've proposed the full struct. Stig needs the `sim_speed` field and `nearby_interactions` / `active_dialogue` / `monologue` / `overheard` fields. +2. **FRIEND content packs** — Mellanie needs to produce phase-tagged dialogue and monologue lines per the breakdown above. These unblock integration testing of the FriendArc state machine. +3. **Contradiction spatial data** — Paula needs to specify the exact contradiction setup for Kael and Sera in the NPC profiles: which location, which time window, what the contradiction looks like when observed. +4. **FactId catalog** — Still the #1 blocker. I need the machine-readable fact vocabulary before I can build prerequisite evaluation. Gestalt and Paula produce this. diff --git a/docs/workshops/v01-content-scoping/round2-gestalt.md b/docs/workshops/v01-content-scoping/round2-gestalt.md new file mode 100644 index 000000000..05d4f2488 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-gestalt.md @@ -0,0 +1,422 @@ +# Round 2 — Gestalt (Systems Design) + +*cracks knuckles* + +Four resolution tasks. Let me be honest about each. + +--- + +## Task 1: Canonical NPC Mapping (Definitive) + +The lead confirmed my mechanical mapping wins for v0.1. Paula's emotional/narrative reads become v0.2 annotations. But "winning" doesn't mean refusing to learn from the disagreements. I reviewed all 10 and changed my mind on 6 of them. + +### Changes from Round 1 (Where Paula Was Right) + +Let me be transparent about what I got wrong and why: + +| NPC | My R1 | Paula's R1 | Final R2 | Who Was Right | Why I Changed | +|-----|-------|-----------|----------|---------------|---------------| +| **Maret** (pattern) | SYSTEM | WITNESS | **WITNESS** | Paula | Maret's v0.1 function is defined by what she SAW, not her institutional role. The player (both characters) approaches Maret because she carries information. SYSTEM describes her job; WITNESS describes her story function. | +| **Maret** (motivation) | WITNESS | SKEPTIC | **SKEPTIC** | Paula | If pattern is WITNESS and motivation is WITNESS, there's no compositional tension. WITNESS+SKEPTIC = "she saw something AND she doubts" — the doubt makes her dangerous because she might ACT. Data-driven doubt IS skepticism. | +| **Resha** (motivation) | SKEPTIC | CIVILIAN | **CIVILIAN** | Paula | CATALYST+SKEPTIC implies deliberate questioning. CATALYST+CIVILIAN = "innocent disruption" — Resha doesn't INTEND to cause problems. The accidental quality makes the disruption more realistic and more dangerous (you can't negotiate with curiosity). | +| **Harek** (pattern) | GHOST | ANCHOR | **ANCHOR** | Paula | Harek is PHYSICALLY PRESENT. GHOST means absent power; Harek is at the bar playing cards. His leverage on Drin is indirect, but HE isn't absent. Paula's read: Harek embodies complacent contentment whose comfort accidentally creates a tool for others. The "unconscious lever" is more interesting than "invisible puppeteer." | +| **Drin** (pattern) | REMNANT | SYSTEM | **SYSTEM** | Paula | Paula nailed this: "The dock inspector IS the inspection system. His compromise means the system itself is corrupted." REMNANT requires historical depth a 40-year-old station doesn't have. Drin doesn't remember centuries of tradition — he remembers 5 years ago. That's not REMNANT-scale. SYSTEM+TURNCOAT = institutional rot. | +| **Sabel** (pattern) | THRESHOLD | GHOST | **GHOST** | Paula | Sabel is off-station with 1-2 brief visits. The player doesn't interact with Sabel enough for THRESHOLD mechanics (getting past a gatekeeper). The player learns about Sabel through others' references. That's GHOST — absent power shaping events through supply chain influence. | +| **Olin** (motivation) | SKEPTIC (dual) | CIVILIAN | **CIVILIAN** | Paula | Dropped dual designation. At Tier 3, Olin has 10-15 lines. Not enough content for SKEPTIC function. CIVILIAN is the safe tag — if Olin gets promoted to T2 in v0.2, the hidden motivation can be revealed. 1 pattern + 1 motivation per NPC, no exceptions. | + +### What I Kept (Where My Mechanical Read Holds) + +| NPC | Disagreement | My R1 | Final R2 | Why I'm Keeping It | +|-----|-------------|-------|----------|-------------------| +| **Voss** (motivation) | OPERATOR vs HANDLER | OPERATOR | **OPERATOR** | Voss RUNS the schedule. That's operating, not handling. HANDLER implies top-down pressure — Devra and Harek do that. Voss is middle management: competent, procedural, operating the machinery. The schedule IS his operation. | +| **Devra** (motivation) | HANDLER vs OPERATOR | HANDLER | **HANDLER** | Devra conveys Nils's strategy, manages external contacts, delivers escalation demands. That's pressure from above. She doesn't run day-to-day operations (Kael, Renn, Voss do). She manages the people who run things. | +| **Sess** (pattern) | ANCHOR vs NOBODY | ANCHOR | **ANCHOR** | NOBODY is deferred to v0.2. Tagging Sess as NOBODY means the tag does nothing in v0.1. ANCHOR at T3 = contentment as atmospheric texture. The bartender who sees everything and says nothing because his life is fine. v0.2 note: Sess could be retagged NOBODY if dynamic tier promotion activates. | + +### The Definitive v0.1 NPC Mapping + +| # | Name | Tier | Pattern | Motivation | Composition Read | +|---|------|------|---------|-----------|-----------------| +| 1 | **Kael Davan** | **T1** | **FRIEND** | **OPERATOR** | Trust anchor running ring cargo. Identity betrayal: "I was your friend AND your co-conspirator." | +| 2 | **Sera Venn** | **T1** | **FRIEND** | **WITNESS** | Trust anchor carrying unreported evidence. Concealment betrayal: "I was your friend AND I was hiding evidence." | +| 3 | **Naia Tamm** | **T1*** | **MIRROR** | **CIVILIAN** | Honest transparency, no secrets, uninvolved. "The one person not lying — surrounded by liars." | +| 4 | **Voss** | T2 | **SYSTEM** | **OPERATOR** | Institutional authority operating the schedule that enables smuggling. Corrupted machinery: "The system works — for the wrong people." | +| 5 | **Lera Sessik** | T2 | **ANCHOR** | **OPERATOR** | Contentment embodied, running a bar on willful ignorance. Profitable blindness: "Is this enough? Yes — if I don't look." | +| 6 | **Torek Lintar** | T2 | **CATALYST** | **CIVILIAN** | Spending disrupts equilibrium. Uninvolved, just living visibly. Accidental signal: "He doesn't know he's a liability." | +| 7 | **Devra** | T2 | **THRESHOLD** | **HANDLER** | Ring gatekeeper, manages operations from above. Obstacle with authority: "You go through her or you don't go." | +| 8 | **Maret Korr** | T2 | **WITNESS** | **SKEPTIC** | Accidentally observed discrepancies, doubts the numbers. Dangerous knowledge: "She saw something and she can't stop questioning it." | +| 9 | **Resha** | T2 | **CATALYST** | **CIVILIAN** | New hire forcing defensiveness with innocent questions. Accidental disruption: "She just asked. Everyone flinched." | +| 10 | **Drin** | **T2** | **SYSTEM** | **TURNCOAT** | Inspector whose compromise corrupts the institution. Loyalty shifting under coercion. Institutional rot: "The inspection system is broken because the inspector is broken." | +| 11 | **Renn** | T2 | **THRESHOLD** | **OPERATOR** | Courier controlling cargo flow. Physical gatekeeper: "Renn moves the product. Trace Renn, find the route." | +| 12 | **Pell** | T2 | **CATALYST** | **TURNCOAT** | Wavering member forcing potential crisis. Maximum instability: "When Pell breaks, everything moves." | +| 13 | **Harek** | T2 | **ANCHOR** | **HANDLER** | Complacent contentment whose comfort creates a tool. Unconscious lever: "Harek doesn't know his card games built a weapon." | +| 14 | **Sess** | T3 | **ANCHOR** | **CIVILIAN** | Quiet contentment behind the bar. Social wallpaper: "Sees everything, says nothing — because nothing's wrong with HIS life." | +| 15 | **Olin** | T3 | **CATALYST** | **CIVILIAN** | New arrival forcing evaluation. Ambiguous intent: "Friend or threat? The question IS the disruption." | +| 16 | **Sabel** | T3 | **GHOST** | **OPERATOR** | Off-station absent power running the supply chain. Distant machinery: "Sabel shapes events from elsewhere." | +| 17 | **Tav** | T3 | **SYSTEM** | **CIVILIAN** | Ring lookout following procedure. Lowest rung: "Tav watches the door. That's the whole job." | + +*Off-stage:* + +| NPC | Pattern | Motivation | Notes | +|-----|---------|-----------|-------| +| **Nils Davan** | **GHOST** | **HANDLER** | Absent authority applying pressure through intermediaries. | + +### Pattern Distribution (Final) + +| Pattern | Count | NPCs | Coverage | +|---------|-------|------|----------| +| FRIEND | 2 | Kael, Sera | Full (D-034 production level) | +| MIRROR | 1 | Naia Tamm | Full (T1 emotional depth) | +| ANCHOR | 3 | Lera, Harek, Sess | Full + sketch. Three versions of "is this enough?" | +| SYSTEM | 3 | Voss, Drin, Tav | Full + sketch. Institutional machinery (legit + criminal) | +| CATALYST | 3 | Torek, Resha, Pell | Full. Three disruption modes (accidental, innocent, deliberate) | +| THRESHOLD | 2 | Devra, Renn | Full. Access gatekeepers (organizational + physical) | +| WITNESS | 1 | Maret | Full. Data-driven information carrier | +| GHOST | 1+1 | Sabel (+Nils off-stage) | Light. Absent power through supply/authority | +| REMNANT | 0 | — | Correctly absent. 40-year station lacks historical depth. | +| NOBODY | 0 | — | Deferred to v0.2. Data ships (hidden fields in content). | + +8 of 9 patterns present. All 6 motivations present. + +### Motivation Distribution (Final) + +| Motivation | Count | NPCs | +|-----------|-------|------| +| OPERATOR | 5 | Kael, Voss, Lera, Renn, Sabel | +| CIVILIAN | 6 | Naia Tamm, Torek, Resha, Olin, Sess, Tav | +| HANDLER | 2+1 | Devra, Harek (+Nils off-stage) | +| WITNESS | 1 | Sera | +| TURNCOAT | 2 | Drin, Pell | +| SKEPTIC | 1 | Maret | + +CIVILIAN at 6/17 (35%) — matches D-029's noise floor requirement (~30% flat). OPERATOR at 5 reflects a logistics hub where everyone is RUNNING something. All 6 motivations represented. + +### Why This Mapping Creates Interesting Decisions + +**ANCHOR saturation (3):** Three different answers to "is this enough?" Lera: "Yes, if I don't look." Harek: "Yes, absolutely — pass me the cards." Sess: "Yes, and I'd rather you didn't disturb it." This isn't redundancy — it's the thematic floor the investigation disrupts. The player walks through contentment before contamination touches it. + +**SYSTEM × TURNCOAT (Drin):** The most mechanically productive composition in the set. The institution IS the character AND the character is betraying the institution. For the detective: crack the inspector, crack the system. For the smuggler: keep the inspector quiet, keep the system working for you. Same NPC, inverted interactions. That's the dual-lens proof. + +**CATALYST diversity (3 types):** Torek disrupts through SPENDING (accidental, financial signal). Resha disrupts through ASKING (innocent, social signal). Pell disrupts through WAVERING (deliberate, operational signal). Three types of disruption, three types of player assessment. The smuggler reads them as threats with different urgency levels. The detective reads them as leads with different reliability levels. + +--- + +## Task 2: Generic Key Architecture (The Bloat Question) + +The lead asks: *"If the original EntityKnowledge set was incomplete for the smuggler, are we doing this again for the administrator or the corporate tycoon?"* + +The answer is **no — if we design the keys correctly now.** + +### The Problem with Per-Archetype Keys + +My Round 1 proposal: 4 smuggler-specific keys (`exposure_risk`, `loyalty_assessment`, `cover_integrity`, `moral_weight`). If we repeat this for 8 archetypes: + +``` +8 archetypes × 4 unique keys = 32 archetype-specific keys ++ 14 shared keys += 46 total keys (unsustainable) +``` + +Administrator needs `bureaucratic_leverage`, `regulatory_compliance`... +Merchant needs `market_position`, `asset_vulnerability`... +Every archetype spawns its own vocabulary. That's schema bloat AND content bloat (every NPC profile must have 46 fields). + +### The Solution: 4 Generic Perspective Keys + +Look at what my 4 smuggler keys actually represent at the ABSTRACT level: + +| Smuggler Key | Abstract Question | +|-------------|-------------------| +| `exposure_risk` | "How does this NPC threaten MY operational position?" | +| `loyalty_assessment` | "How reliably will this NPC act in MY interest?" | +| `cover_integrity` | "How well is the relevant deception holding?" | +| `moral_weight` | "How morally culpable is this NPC in MY judgment?" | + +These questions are UNIVERSAL. Every archetype asks them — just with different words: + +| Abstract Question | Smuggler | Detective | Administrator | Merchant | +|------------------|----------|-----------|--------------|----------| +| "How does this NPC threaten my position?" | "Will they expose the ring?" | "Will they obstruct my investigation?" | "Will they expose my corruption?" | "Will they undercut my deal?" | +| "How reliably will this NPC serve my interests?" | "Can I count on them in operations?" | "Will they testify/cooperate?" | "Will they vote with me?" | "Will they honor the contract?" | +| "How well is the deception holding?" | "Is their cover solid?" | "Is my informant still believable?" | "Does the public still buy it?" | "Does the competitor still trust me?" | +| "How culpable is this NPC?" | "How deep in the ring?" | "How guilty?" | "How corrupt?" | "How honest?" | + +**Same 4 questions. Same 4 keys. 8 archetypes.** + +### Proposed Generic Keys + +| Key Name | Value Enum | What It Stores | +|----------|-----------|---------------| +| `threat_to_position` | `"none"`, `"low"`, `"moderate"`, `"high"`, `"critical"` | Observer's assessment of how this NPC threatens the observer's operational security, institutional standing, or strategic position. | +| `reliability` | `"solid"`, `"dependable"`, `"uncertain"`, `"wavering"`, `"hostile"` | Observer's assessment of how predictably this NPC will serve the observer's interests. | +| `facade_status` | `"solid"`, `"thin"`, `"cracking"`, `"blown"`, `"N/A"` | Status of any active deception involving this NPC (their cover, the observer's cover, a shared front). N/A if no deception in play. | +| `moral_standing` | `"innocent"`, `"peripheral"`, `"complicit"`, `"compromised"`, `"willing"` | Observer's subjective moral assessment of this NPC within the observer's ethical framework. | + +### How Interpretation Works + +The key stores GENERIC data. The monologue system provides CHARACTER-SPECIFIC interpretation through D-032's hard partition (separate pools per character). + +**Same NPC, same keys, different character pools:** + +```yaml +# Smuggler monologue about Maret +- id: hub_m_031 + text: "Maret's been checking manifests again. She's going to notice something she can't un-notice." + character: smuggler + trigger: observe_npc + prerequisite: + target.known_attributes.threat_to_position: "high" + target.known_attributes.name: "Maret Korr" + +# Detective monologue about Maret +- id: hub_m_032 + text: "Korr looks worried. She processes manifests — if the numbers are off, she'd know." + character: detective + trigger: observe_npc + prerequisite: + target.known_attributes.threat_to_position: "high" + target.known_attributes.name: "Maret Korr" +``` + +Same key (`threat_to_position: "high"`), same NPC — but the smuggler reads it as "she's a risk to the operation" while the detective reads it as "she's a potential witness at risk." The interpretation lives in the monologue text, not in the key schema. + +### Why This Scales to 8 Archetypes Without Bloat + +| Archetype | threat_to_position means... | reliability means... | facade_status means... | moral_standing means... | +|-----------|---------------------------|---------------------|----------------------|------------------------| +| Smuggler | Exposure risk to the ring | Operational loyalty | Cover integrity | Innocent vs. complicit | +| Detective | Obstruction to investigation | Cooperation likelihood | Informant/cover status | Guilt level | +| Administrator | Political liability | Voting alignment | Public image coherence | Corruption level | +| Merchant | Competitive threat | Contract reliability | Deal integrity | Business ethics | +| Newcomer | Social danger | Friendliness | Acceptance facade | Community trustworthiness | +| Operator | Operations disruption | Team dependability | Mission cover | Professional ethics | +| Exile | Personal danger | Alliance reliability | Identity concealment | Threat vs. ally | +| Fixer | Client risk | Job reliability | Operation facade | How dirty | + +**Total EntityKnowledge keys: 18.** 14 shared + 4 generic perspective. Works for all 8 archetypes. No additional keys needed at v1.0. + +### Value Enum Design + +The enums are deliberately archetype-neutral: + +- `"solid"` / `"dependable"` / `"uncertain"` / `"wavering"` / `"hostile"` — these words work in ANY context. A smuggler reads "wavering" as "might turn on us." A detective reads "wavering" as "might cooperate if pressured." Same word, different operational meaning. + +- `"innocent"` / `"peripheral"` / `"complicit"` / `"compromised"` / `"willing"` — these work for any moral framework. A smuggler uses them to track guilt distance from the ring. An administrator uses them to track corruption distance from a scandal. The progression is the same. + +### How Content Authors Use This + +NPC profiles get a SINGLE set of character-perspective attributes, not per-archetype sections: + +```yaml +# In kael-davan.yaml, perspective section +perspectives: + smuggler: + threat_to_position: "low" # Kael is reliable (pre-contradiction) + reliability: "solid" # Trusted colleague + facade_status: "solid" # Cover holds (pre-contradiction) + moral_standing: "willing" # Fully committed ring member + detective: + threat_to_position: "moderate" # Kael handles cargo on suspicious shifts + reliability: "uncertain" # Unknown cooperativeness + facade_status: "solid" # Appears to be a normal dock worker + moral_standing: "complicit" # Connected to discrepancies (if known) +``` + +Content authors populate perspective values per character in the NPC YAML. The server loads these as initial `known_attributes` values for each character's knowledge graph. Values update through gameplay (Kael's `facade_status` shifts from `"solid"` to `"cracking"` when tells accumulate). + +### Addressing Paula's 8-Key Proposal + +Paula proposed all 8 smuggler attributes as new keys. My generic architecture maps her proposal: + +| Paula's Key | Maps To | Notes | +|-------------|---------|-------| +| `trust_read` | `trust_level` (existing) | Same key, context-dependent interpretation. Already works. | +| `exposure_risk` | `threat_to_position` (generic) | Renamed for archetype-neutrality. | +| `loyalty_assessment` | `reliability` (generic) | Renamed. | +| `leverage_held` | `secret_held` (existing) | Leverage IS secret knowledge. Same storage. | +| `social_debt` | Deferred to v0.2 | 30-minute session doesn't develop favor economy. | +| `moral_weight` | `moral_standing` (generic) | Renamed. | +| `cover_integrity` | `facade_status` (generic) | Renamed. | +| `loyalty_conflict` | Deferred to v0.2 | Inferable from `behavior_flags` + `reliability`. | + +**Result:** 2 existing keys reused + 4 generic keys created + 2 deferred. Total schema: 18 keys. Works for 8 archetypes. + +--- + +## Task 3: Self-Contained Triangle Forks (Confirmed) + +**I agree with Tyre's recommendation: self-contained forks for v0.1, no cross-triangle cascade.** + +But "self-contained" needs a precise definition, because there's a trap: + +### The Shared-NPC Problem + +Voss appears in BOTH Triangle 1 (Hub Power) and Triangle 2 (Worried Knowledge). If the smuggler escalates in Triangle 1, Voss gets more anxious. That anxiety is part of Voss's ENTITY STATE — it's stored in `behavior_flags`, `routine_pattern`, etc. If Triangle 2's fork reads Voss's entity state, then Triangle 1's decision HAS cascaded into Triangle 2. + +Is that a cross-triangle cascade? Technically yes. Should we prevent it? **No — that would require NPCs to have separate states per triangle, which is absurd.** + +### What "Self-Contained" Actually Means + +**Self-contained forks = independent decision state machines, shared NPC state.** + +| Allowed (v0.1) | Not Allowed (v0.2+) | +|----------------|---------------------| +| Each triangle has its own fork with its own trigger, decision, and outcome | Triangle 1's OUTCOME directly gates Triangle 2's FORK (if T1 escalates, T2 fork changes) | +| NPCs carry behavioral state across triangles (Voss's anxiety is visible everywhere) | Triangle forks modify other triangles' state machines (T1 pushes T2 from state A to state B) | +| Players OBSERVE cross-triangle effects through NPC behavior | Fork resolution in one triangle REQUIRES resolution of another | +| Monologue can reference NPC state from any source | The storyteller sequences forks based on other forks' outcomes | + +**In implementation:** + +``` +Triangle 1 Fork: + trigger: Devra delivers Nils's escalation demand + decision: smuggler chooses escalate/stabilize/mediate + outcome: modifies Voss.behavior_flags, Kael.behavior_flags + DOES NOT: modify Triangle 2's fork state + +Triangle 2 Fork: + trigger: detective notices Maret's manifest re-checking pattern + decision: detective approaches Maret or pressures Drin + outcome: modifies Maret.trust_level, Drin.cover_integrity + DOES NOT: depend on Triangle 1's resolution + +Shared state visible to both: + Voss.behavior_flags may contain "nervous" from T1 AND "overworked" from T2 + The player sees ALL of Voss's behavioral state regardless of which triangle produced it + Cross-triangle OBSERVATION is emergent — it's systems interacting, not scripted cascade +``` + +**Why this is right for v0.1:** + +1. **Testing scope**: Self-contained forks can be tested independently. Each triangle fork is a unit test. Cross-triangle cascade requires integration tests across all 5 triangles. + +2. **Content scope**: Each fork needs its own decision content (dialogue, monologue, consequences). 3 active forks × 3 choices each = 9 content branches. Adding cascade dependencies multiplies this combinatorially. + +3. **The emergent observation IS the payoff**: The player noticing that Voss is anxious in BOTH the hub (Triangle 1) and during manifest processing (Triangle 2) is an emergent connection. They're doing investigation — connecting dots across contexts. That's MORE interesting than scripted cascade, and it comes for free from shared NPC state. + +4. **v0.2 cascade path**: If we want Triangle 1's escalation to gate Triangle 2's timing in v0.2, the architecture supports it. The fork state machines are independent now; adding dependencies later is an additive change, not a rewrite. + +### Triangle Fork Summary (Final) + +| Triangle | Fork Type | Decision | Self-Contained? | +|----------|-----------|----------|-----------------| +| T1: Hub Power | **Active** | Smuggler: escalate/stabilize/mediate | YES — modifies Voss+Kael state, no T2 gate | +| T2: Worried Knowledge | **Active** | Detective: approach Maret / pressure Drin / wait | YES — modifies Maret+Drin state, no T1 gate | +| T3: Bar Tensions | **Passive** | No fork — ambiguity is permanent | N/A | +| T4: Worried Partner | **Active** | Both PCs: FRIEND contradiction paths A/B/C | YES — modifies Kael/Sera state, no other triangle gate | +| T5: Informant Question | **Passive** | No fork — tension observable, resolution post-session | N/A | + +--- + +## Task 4: Knowledge Panel Pause — Sim Speed Architecture + +**Lead decision: 50% sim speed when panel is open. Spacebar always pauses from anywhere.** + +### Why 50% Is Correct (Not 10%, Not Full Pause) + +Stig proposed 10%. Lead overruled to 50%. Here's the mechanical reasoning: + +| Speed | Effect | Problem | +|-------|--------|---------| +| 100% (no slow) | World runs at full speed while you read the knowledge panel | NPCs walk away while you're cross-referencing. Punishes information gathering. | +| 50% (lead's choice) | World runs at half speed | You can SEE NPCs moving slowly while you think. Creates tension: "Do I keep reading or close and follow?" That's an INTERESTING DECISION. | +| 10% (Stig's proposal) | World barely moves | No tension. Functionally equivalent to pause. If you're going to pause, just pause. 10% is a nothing-state. | +| 0% (full pause) | World stops | No tension. The knowledge panel becomes a reference tool, not a strategic moment. Removes the feeling of time pressure that makes investigation feel real. | + +**50% creates a micro-decision every time you open the panel:** "Is the information worth the time?" That's asymmetric information AS MECHANIC — even the player's attention is a scarce resource. + +### Sim Speed State Machine + +Two independent toggles, multiplicative: + +``` +effective_speed = if pause_override { 0.0 } else { base_speed × ui_modifier } +``` + +| Toggle | Controls | Values | +|--------|----------|--------| +| `ui_modifier` | Whether a UI overlay is open | `1.0` (no overlay) or `0.5` (knowledge panel open) | +| `pause_override` | Spacebar | `false` (not paused) or `true` (paused) | + +**State combinations:** + +| State | ui_modifier | pause_override | Effective Speed | Player Experience | +|-------|------------|---------------|----------------|-------------------| +| Normal play | 1.0 | false | **100%** | Full speed. NPCs move, routines advance. | +| Panel open | 0.5 | false | **50%** | Slow motion. NPCs drift. Time passes. Tension. | +| Paused (from normal) | 1.0 | true | **0%** | Full stop. World frozen. Read, think, plan. | +| Paused (from panel) | 0.5 | true | **0%** | Full stop. Panel still visible. No time pressure. | + +**Key behaviors:** +- **Spacebar always pauses.** From any state. No exceptions. This is accessibility AND good design. +- **Spacebar resumes to previous state.** If you paused from panel-open (50%), unpausing returns to 50%, not 100%. If you paused from normal play, unpausing returns to 100%. +- **Tab toggles panel.** Opening panel sets `ui_modifier = 0.5`. Closing panel sets `ui_modifier = 1.0`. Independent of pause state. +- **Dialogue: also 50%.** When dialogue is active (Talk interaction), the same `ui_modifier = 0.5` applies. NPCs who aren't in the conversation continue moving at half speed. This means other events can happen WHILE you're talking — the world doesn't freeze for your conversation. That's immersive sim design. + +### v0.2 Extension + +The multiplicative model is composable. Future UI states can define their own modifiers: + +| UI State | Modifier | Combined | +|----------|----------|----------| +| Knowledge panel | 0.5 | 0.5 | +| Dialogue | 0.5 | 0.5 | +| Panel + Dialogue? | 0.5 × 0.5 = 0.25 | 25% (both open simultaneously) | +| Cutscene | 0.0 | Full freeze (storyteller controls) | + +For v0.1, only panel and dialogue use 0.5. The system supports future states without redesign. + +### Implementation Note for Dudley + +The server tick rate doesn't change. The CLIENT requests a sim speed modifier. The server processes ticks at variable intervals: + +- Normal: 1 tick per 50ms (20 ticks/sec) +- 50%: 1 tick per 100ms (10 ticks/sec) +- Paused: 0 ticks (server queues but doesn't process) + +The server's tick processing is deterministic regardless of interval. Changing the interval changes PACING, not LOGIC. All systems (NPC routines, monologue triggers, tell accumulation) operate on tick count, not wall-clock time. A 50% slowdown means the same events happen — they just take twice as long in wall-clock time. + +--- + +## Cross-Reference: Addressing Tracking Doc Items + +A few items from Qatux's tracking that directly involve my assignments: + +### A-17: Self-contained triangle forks +**Confirmed.** See Task 3. Independent decision state machines, shared NPC state. Tyre was right. + +### D-04: Examine vs Talk +**Lead decided: conceptually separate, v0.1 ships single action.** I accept this. My 7-interaction model stands as the DESIGN TARGET. v0.1 implements it as context-sensitive single action (server knows about multiple actions per entity, client surfaces the best one). v0.2 adds multi-verb UI. The architecture must support both — that's Tyre and Dudley's task. + +### D-05: Knowledge Panel Pause +**Resolved.** See Task 4. 50% sim speed, spacebar override to full pause. Multiplicative model. + +### A-10: Dialogue files by location + role +**I validate this.** It aligns with D-035 (role-based, not NPC-based line assignment) and the generator model (templates define roles, NPCs fill roles). The file organization should be: +``` +districts/sova-transit/dialogue/ + hub-dock-worker.yaml # Role: dock worker, Location: hub + hub-supervisor.yaml # Role: supervisor, Location: hub + bar-owner.yaml # Role: bar owner, Location: bar + bar-regular.yaml # Role: bar regular, Location: bar + ... +``` +NPCs are assigned to roles at content load time. A line tagged `role: dock_worker` + `access: insider` fires for Kael (if smuggler has insider access to Kael) regardless of which NPC fills the dock_worker role. This is generator-compatible. + +--- + +## Summary of Changes from Round 1 + +| Item | Round 1 | Round 2 | Change Reason | +|------|---------|---------|---------------| +| Maret pattern | SYSTEM | **WITNESS** | Story function > job description | +| Maret motivation | WITNESS | **SKEPTIC** | Compositional tension (WITNESS+SKEPTIC > WITNESS+WITNESS) | +| Resha motivation | SKEPTIC | **CIVILIAN** | Innocent disruption > deliberate questioning | +| Harek pattern | GHOST | **ANCHOR** | Present contentment > absent power | +| Drin pattern | REMNANT | **SYSTEM** | Institutional role > nostalgia (station too young for REMNANT) | +| Sabel pattern | THRESHOLD | **GHOST** | Absent influence > present gatekeeper | +| Olin motivation | SKEPTIC (dual) | **CIVILIAN** | Dropped dual designation. 1 per NPC. | +| Smuggler keys | 4 smuggler-specific | **4 generic perspective** | `threat_to_position`, `reliability`, `facade_status`, `moral_standing` — works for all 8 archetypes | +| EntityKnowledge total | 18 (14+4) | **18 (14+4)** | Same count, generic names | +| Triangle forks | Self-contained (tentative) | **Self-contained (confirmed)** | Independent state machines, shared NPC state | +| Panel pause | Not addressed | **50% sim speed, spacebar override** | Multiplicative toggle model | diff --git a/docs/workshops/v01-content-scoping/round2-mellanie.md b/docs/workshops/v01-content-scoping/round2-mellanie.md new file mode 100644 index 000000000..f1a16836f --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-mellanie.md @@ -0,0 +1,461 @@ +# Round 2 — Mellanie (Copywriter) + +## v0.1 Content Scoping Workshop + +--- + +## Task 1: Canonical PC Voice Registers + +These are authoring specs, not narrative documents. Every monologue line I write gets checked against these parameters. If a line doesn't match the register, it gets cut. + +--- + +### Smuggler — Internal Voice Register + +**One-line summary:** Watches people, not systems. Thinks in fragments. Worries about friends the way a dock worker worries — practically, physically, with the knot in the stomach, not the word for it. + +#### Voice Parameters + +| Parameter | Value | Example | +|---|---|---| +| **Sentence length** | Short. Fragments welcome. Rarely more than 2 sentences per monologue fire. | "Kael's here. Good." | +| **Vocabulary** | Concrete, physical, sensory. Uses trade jargon (containers, manifests, shifts, routes) without explaining it. No institutional language. | "Recycled air and cargo lubricant." Not: "Standard atmospheric recycling." | +| **Emotional register** | Feeling-first, analysis second. Names the gut before the brain catches up. | "Starting to worry" before "something's off." | +| **Relationship mode** | First-name basis with everyone. Thinks relationally — people are defined by connection, not role. | "Kael" not "Davan." "Voss is tense" not "the shift supervisor seems agitated." | +| **Observation priority** | People > environment > objects. Notices body language before noticing cargo. | "Kael keeps checking his lattice" before "Container 4471 is still in temp." | +| **Operational thinking** | Present when thinking about the job. Time-aware, route-aware, schedule-aware. But always filtered through "is this safe for my people?" | "Transition window opens in fifty minutes. After that, twenty minutes of reduced oversight." | +| **Self-reference** | Rare. The smuggler thinks about others, not herself. When self-reference occurs, it's through physical sensation. | "My stomach dropped" not "I feel betrayed." | +| **Humor** | Dry, situational, understated. Dark when stressed. Never witty — that's a different register. | "Home sweet home" about a cargo bay. | +| **What she doesn't do** | Doesn't analyze systems. Doesn't use institutional vocabulary. Doesn't explain things to herself that she already knows. Doesn't monologue in paragraphs. | + +#### Voice Anchors (Reference Lines) + +These 5 lines define the boundaries. Every smuggler monologue line should feel like it could sit next to these without register collision. + +1. **Baseline warm:** *"Kael's already at the dock. Good. The day's better when he's on shift."* + — Short. Personal. Physical presence matters. + +2. **Operational:** *"Shift transition in thirty minutes. If Voss kept the schedule thin, the route's clear."* + — Time-specific. Practical. Other people's actions determine safety. + +3. **Concerned:** *"Kael keeps checking his lattice. Waiting for a message? Not like him to be jumpy."* + — Observation leads to interpretation. Interpretation is relational ("not like him"), not clinical. + +4. **Hurt:** *"He looked left. He always looks left when he's making something up. Two years I've known that tell. Kael is lying to me."* + — Personal history weaponized. The "two years" is the knife. Ends with a flat declaration, not a question. + +5. **Atmospheric:** *"Morning shift. Recycled air and cargo lubricant. Home sweet home."* + — Three fragments. Sensory detail. Irony through understatement. + +#### Anti-Patterns (What the Smuggler Does NOT Sound Like) + +- "I should investigate this anomaly further." (Too clinical. She'd think: "That's not right.") +- "The scheduling irregularity suggests systemic manipulation." (Institutional vocabulary. She'd think: "Voss changed the rotation again.") +- "I need to consider the implications of Kael's behavior for our operational security." (Analytical distance. She'd think: "What the hell is Kael doing?") +- "According to the manifest, container 4471 should weigh 240 kilograms." (Explaining what she already knows. She'd think: "4471's heavy. Too heavy.") + +--- + +### Detective — Internal Voice Register + +**One-line summary:** Categorizes first, feels second. Thinks in complete sentences. The institutional training runs in the background like an operating system — it never turns off, but sometimes the human overrides it. + +#### Voice Parameters + +| Parameter | Value | Example | +|---|---|---| +| **Sentence length** | Complete sentences, but not long ones. More structured than the smuggler. Occasional fragments for emphasis or snap judgments. | "Standard prefab, heavy foot traffic. Let's see what the shift change tells me." | +| **Vocabulary** | Institutional layer over plain speech. Professional terms leak in: "case file," "filing it," "noted," "flagged." Not technical jargon — investigative habit. | "Dock worker. Davan, K. Unremarkable on paper." The surname-comma-initial is institutional reflex. | +| **Emotional register** | Analysis-first, personal second. When personal breaks through, it's notable — a crack in the professional surface that signals emotional weight. | "She's not lying. But she's not telling me everything. Sera, what do you know?" — The first-name appeal at the end IS the emotion. | +| **Relationship mode** | Surname-first for targets and unknowns. First-name for allies and people he trusts. The shift from surname to first-name (or back) IS a relationship marker. | "Davan, K." early → "Kael" never (smuggler's FRIEND, not detective's). "Sera" always (detective's FRIEND). | +| **Observation priority** | Patterns > people > environment. Notices deviations from expected behavior before noticing the person. | "That's twice now" before "Sera leaves." The count matters more than the actor. | +| **Analytical thinking** | Automatic. Counts things, tracks frequencies, flags inconsistencies. Self-directs: "filing it," "noted," "coincidence?" These are internal bookkeeping prompts. | "Three times. Three excuses. That's data." | +| **Self-reference** | More common than the smuggler, but always through the professional lens. The detective monitors his own state as evidence. | "I'm reading too much into this" — self-aware about analytical bias. | +| **Humor** | Wry, observational. Appreciates irony at a distance. Can laugh at himself. Never cruel. | "Only place in this district that doesn't smell like freight lubricant." | +| **What he doesn't do** | Doesn't think in fragments (that's the smuggler). Doesn't use trade jargon he hasn't learned. Doesn't name-drop relationships he doesn't have. Doesn't express vulnerability directly — it leaks, he doesn't pour. | + +#### Voice Anchors (Reference Lines) + +1. **Clinical baseline:** *"Logistics hub. Standard prefab, heavy foot traffic. Let's see what the shift change tells me."* + — Categorize. Evaluate. Self-direct. Three operations in three sentences. + +2. **Pattern recognition:** *"That's the third time. Sera leaves when Torek arrives. Three different excuses, same result. That's not coincidence."* + — Counting is automatic. Conclusion is flat and declarative. No hedging. + +3. **Personal leak:** *"Good to see Sera. She always was the reliable one. At least someone in this district speaks my language."* + — First-name. Past tense ("always was") implies shared history. "My language" is institutional belonging. + +4. **Conflicted:** *"Same booth. Same warm smile. Same offer to buy me a drink. ...Everything except whatever she's not saying."* + — Repetition builds the pattern. The ellipsis is the human catching up to the analyst. The final clause is the knife. + +5. **Atmospheric:** *"Grain spirit and low conversation. Only place in this district that doesn't smell like freight lubricant."* + — Sensory but evaluative. The detective doesn't just notice smells — he catalogs them as data points. + +#### Anti-Patterns (What the Detective Does NOT Sound Like) + +- "Kael's here. Good." (Too terse. That's the smuggler. The detective would think: "Davan's on shift. Standard.") +- "I feel uneasy about Sera's behavior." (Direct emotional vocabulary. He'd think: "Something's off with Sera. Can't place it yet.") +- "This container has been tampered with, indicating smuggling activity." (Explaining the obvious. He'd think: "Weight's wrong. Noted.") +- "My heart sank when I saw the evidence." (Physical emotion — that's the smuggler's register. The detective: "Didn't want to see that. But there it is.") + +--- + +### Voice Register Comparison — Same Observation, Two Characters + +**Trigger: Seeing a container in temp storage overnight.** + +- **Smuggler:** *"That container's been in temp since yesterday. Someone forgot to process it, or someone's stalling."* + — Concrete. Two explanations, both practical. No judgment yet. + +- **Detective:** *"Container in overnight temp. Unusual for a hub this size — turnaround is normally sub-eight hours. Flag it."* + — Categorical. Quantified deviation. Self-directed action. + +**Trigger: Entering the bar.** + +- **Smuggler:** *"Lera's. Grain spirit and noise. Could use both right now."* + — Informal name. Sensory. Personal need. + +- **Detective:** *"The Last Shift. Same crowd as yesterday. Let's see who's NOT here tonight."* + — Official name. Pattern comparison. Investigative frame. + +**Trigger: Noticing Naia Tamm is upset.** + +- **Smuggler:** *"Naia looks rough today. Should check on her later."* + — First-name. Immediate concern. Plans to act. + +- **Detective:** *"Tamm seems distressed. Not her baseline. Something at home, or something she's heard?"* + — Surname. Clinical assessment. Hypothesizes cause. + +**Trigger: Hearing raised voices behind a door.** + +- **Smuggler:** *"Raised voices from the back room. Devra's in there. That's not a brokering call."* + — Identifies the person. Interprets through operational knowledge. + +- **Detective:** *"Arguing behind that door. The bartender didn't flinch. Either she's used to it or she knows what it is."* + — Notes the non-reaction. Third-party behavior is data. + +--- + +### Display Constraints (per Stig's R1 proposal) + +Adopting Stig's parameters as authoring constraints: + +| Constraint | Value | Implication for Writing | +|---|---|---| +| Max length | ~160 characters | Hard ceiling. Most lines should be 80-120. | +| Max visual lines | 2 | No three-line monologue. If it wraps to 3, cut it. | +| Display time | 4-6 seconds (length-adjusted) | Player reads at ~250 WPM = ~20-25 words in 5 seconds. Target: 15-25 words per line. | +| Fade behavior | Fade in/out | No abrupt text pops. Lines must read cleanly mid-fade. | +| Character tint | Smuggler: amber-white `#f0e0c8`, Detective: blue-white `#c8d8f0` | Voice must be distinguishable WITHOUT color — color confirms, doesn't create. | + +**The 25-word rule:** If a monologue line exceeds 25 words, it better be the most important line in the pool. Most lines should be 10-18 words. The authoring guide examples average 14 words. That's the target. + +--- + +## Task 2: THE FRIEND Content Pack — Kael Davan (Structure) + +### What's In the Pack + +The Kael Davan content pack is a single deliverable containing everything the engine needs to run THE FRIEND arc for the smuggler's path. It's also the benchmark — if this pack validates against the tag taxonomy, loads correctly, triggers appropriately, and sounds right, every subsequent content pack follows the same structure. + +### Pack Contents + +``` +content/ + districts/ + sova-transit/ + terminal/ + dialogue.yaml # Kael's dialogue lines at The Terminal (shared pool) + monologue-smuggler.yaml # Smuggler monologue at Terminal (includes Kael observations) + monologue-detective.yaml # Detective monologue at Terminal (includes Kael observations) + bar/ + dialogue.yaml # Kael's dialogue lines at The Last Shift + monologue-smuggler.yaml + monologue-detective.yaml + corridor/ + dialogue.yaml # Kael's dialogue lines in Corridor B-7 + monologue-smuggler.yaml + monologue-detective.yaml +``` + +**Note:** Kael's content lives inside location files, not a per-NPC file. Per D-035 and Tyre's directory design, dialogue is organized by location. Kael's lines appear in `terminal/dialogue.yaml` alongside other NPCs who speak at the Terminal. The `role: dock_worker` tag identifies which lines belong to Kael's template role. + +The pack is authored as a single document, then split into location files during content conversion. For authoring purposes, I'll write it as one file with location headers and deliver the split files. + +### Line Budget (Kael Davan) + +| Content Type | Lines | Location Split | +|---|---|---| +| **Kael dialogue** | | | +| Greetings (5 access tiers) | 5 | Terminal: 3, Bar: 2 | +| Shift/work talk (insider, peer) | 8 | Terminal: 6, Bar: 2 | +| Bar casual (peer) | 4 | Bar: 4 | +| Ring coordination (insider) | 6 | Terminal: 3, Corridor: 3 | +| Deflection/evasion (post-contradiction) | 6 | Terminal: 3, Bar: 2, Corridor: 1 | +| Confrontation responses | 4 | Terminal: 1, Bar: 1, Corridor: 2 | +| Hael references (casual, unprompted) | 3 | Terminal: 1, Bar: 2 | +| **Dialogue subtotal** | **36** | | +| | | | +| **Smuggler monologue about Kael** | | | +| Phase 1 — Comfort (warmth, baseline) | 4 | Terminal: 2, Bar: 2 | +| Phase 2 — First Crack (concern, tells) | 4 | Terminal: 3, Bar: 1 | +| Phase 3 — Contradiction (alarm, shock) | 3 | Corridor: 2, Terminal: 1 | +| Phase 4 — Confrontation aftermath (hurt, conflict) | 3 | Terminal: 1, Bar: 1, Corridor: 1 | +| Phase 5 — Contaminated trust (doubt over warmth) | 4 | Terminal: 2, Bar: 1, Corridor: 1 | +| **Smuggler monologue subtotal** | **18** | | +| | | | +| **Detective monologue about Kael** | | | +| Identification / first observation | 2 | Terminal: 2 | +| Behavioral observation (tells noted) | 3 | Terminal: 2, Bar: 1 | +| Suspicion / analytical escalation | 3 | Terminal: 2, Corridor: 1 | +| Informant potential assessment | 2 | Terminal: 1, Bar: 1 | +| **Detective monologue subtotal** | **10** | | +| | | | +| **Tell observation lines** | | | +| Lattice checking (first + repeat) | 2 | Terminal: 2 | +| Shortened interactions / evasive | 2 | Terminal: 1, Bar: 1 | +| Looks-left tell (casual + confirmation) | 2 | Bar: 1, Corridor: 1 | +| Spatial deviation (Corridor B-7) | 2 | Corridor: 2 | +| **Tell subtotal** | **8** | | +| | | | +| **Trust-gated lines** (Kael's deeper dialogue) | | | +| Insider: ring operational talk | 6 | Terminal: 4, Corridor: 2 | +| Insider: close colleague warmth | 4 | Terminal: 2, Bar: 2 | +| Secret: hints about wanting out (pre-contradiction, rare) | 3 | Bar: 2, Corridor: 1 | +| **Trust-gated subtotal** | **13** | | +| | | | +| **Unprompted lines** (Kael volunteers) | | | +| Early: offers information, warmth | 3 | Terminal: 2, Bar: 1 | +| Late: warnings, deflections | 3 | Terminal: 1, Bar: 1, Corridor: 1 | +| **Unprompted subtotal** | **6** | | +| | | | +| **PACK TOTAL** | **91** | | + +91 lines. Within the 70-100 range from D-034. The slight overcount gives me room to cut — better to write 91 and kill 10 weak ones than write 70 and wish I had more coverage. + +### Arc Phase Mapping to Triggers + Prerequisites + +This is the structural skeleton. Every line in the pack maps to one of these slots. + +#### Phase 1: Comfort (0-10 min, RelationshipState: Friendly) + +| Trigger | Prerequisite | Content Type | Count | +|---|---|---|---| +| `observe_npc` | `relationship_state: Friendly, subject: kael_davan` | Smuggler monologue: warmth | 2 | +| `observe_npc` | `relationship_state: Friendly, subject: kael_davan` | Smuggler monologue: routine fondness | 2 | +| `post_conversation` | `relationship_state: Friendly, subject: kael_davan` | Smuggler monologue: social comfort | 1 | +| NPC dialogue | `access: [insider, peer], trust: surface` | Kael greetings, shift talk | 8 | +| NPC dialogue | `access: [insider], trust: real` | Kael ring talk, colleague warmth | 6 | +| NPC dialogue (unprompted) | `access: [insider]` | Kael volunteers info | 2 | + +**Phase 1 total: ~21 lines.** Heavy on dialogue (establish Kael's voice), lighter on monologue (smuggler's comfortable, not reflective). + +#### Phase 2: First Crack (10-15 min, still Friendly, flags accumulating) + +| Trigger | Prerequisite | Content Type | Count | +|---|---|---|---| +| `observe_anomaly` | `behavior_flags: lattice_checking` | Smuggler monologue: mild concern | 2 | +| `post_conversation` | `behavior_flags: evasive` | Smuggler monologue: worry | 2 | +| `observe_npc` (detective) | `relationship_state: Known, subject: kael_davan` | Detective monologue: behavioral note | 2 | +| NPC dialogue | `access: [insider, peer], trust: surface` | Kael: shorter, deflective | 4 | +| Tell observation | `behavior_flags` gates | Tells: lattice, shortened interactions | 3 | + +**Phase 2 total: ~13 lines.** Tell observations ramp up. Dialogue gets clipped. Smuggler monologue shifts from fond to concerned. + +#### Phase 3: The Contradiction (15-20 min, Friendly -> PersonOfInterest) + +| Trigger | Prerequisite | Content Type | Count | +|---|---|---|---| +| `witness_interaction` | `relationship_state: Friendly, subject: kael_davan` | Smuggler monologue: shock/alarm (THE moment) | 2 | +| `observe_anomaly` | `contradiction_flagged: meeting_unknown_contact` | Smuggler monologue: processing | 1 | +| `observe_npc` (detective) | behavioral flags accumulating | Detective monologue: suspicion spike | 2 | +| Tell observation | `contradiction_flagged` | Spatial deviation tell | 2 | + +**Phase 3 total: ~7 lines.** Fewest lines, highest stakes. Every word carries maximum weight. The contradiction monologue is the most important line in the pack. + +#### Phase 4: Confrontation (20-25 min, PersonOfInterest) + +| Trigger | Prerequisite | Content Type | Count | +|---|---|---|---| +| `post_conversation` | `tell_observed: looks_left_when_lying` | Smuggler monologue: the tell confirmation | 2 | +| `post_conversation` | `relationship_state: PersonOfInterest` | Smuggler monologue: hurt/conflict | 1 | +| NPC dialogue | `access: [insider], trust: surface` | Kael: deflection, evasion | 6 | +| NPC dialogue | `access: [insider]` | Kael: confrontation responses | 4 | +| NPC dialogue (unprompted) | `access: [insider]` | Kael: warnings | 2 | +| Tell observation | `tell_observed` gate | Looks-left confirmation | 1 | + +**Phase 4 total: ~16 lines.** Dialogue-heavy — the confrontation is a conversation. Kael's evasion lines are the hardest to write because they must be plausible without being convincing. + +#### Phase 5: Contaminated Trust (25-30 min, PersonOfInterest, new equilibrium) + +| Trigger | Prerequisite | Content Type | Count | +|---|---|---|---| +| `observe_npc` | `relationship_state: PersonOfInterest, subject: kael_davan` + `contradiction_flagged` | Smuggler monologue: doubt over warmth | 3 | +| `return_visit` | `relationship_state: PersonOfInterest` | Smuggler monologue: location contamination | 1 | +| `time_idle` | `relationship_state: PersonOfInterest, subject: kael_davan` | Smuggler monologue: rumination | 1 | +| `observe_npc` (detective) | `secret_confidence: confirmed/suspected` | Detective monologue: informant potential | 2 | +| NPC dialogue | `access: [public], trust: surface` | Kael: surface normal (warmth locked out) | 4 | +| NPC dialogue | `access: [insider], trust: real` (if smuggler chose understanding path) | Kael: confession hints | 3 | +| Trust-gated: secret | Path A only | Kael: deeper truth | 2 | + +**Phase 5 total: ~16 lines.** The contamination lines are the ones where voice discipline matters most — same warmth, new subtext. "Same old Kael" lines where the smuggler hears what used to be comfort and now hears a question. + +### Cross-Phase Lines (Not Phase-Specific) + +| Content Type | Count | Notes | +|---|---|---| +| Detective identification/first observation | 2 | Fires before detective has context on Kael | +| Kael Hael references (casual, any phase) | 3 | Establish relationship before it's load-bearing | +| Kael bar casual (not phase-gated) | 4 | Social texture | +| Detective pattern-forming observations | 3 | Cumulative, phase-independent | +| Kael insider ring coordination | 4 | Operational dialogue, pre-contradiction | + +**Cross-phase total: ~16 lines.** + +### Phase Total Reconciliation + +| Phase | Lines | % of Pack | +|---|---|---| +| Phase 1: Comfort | 21 | 23% | +| Phase 2: First Crack | 13 | 14% | +| Phase 3: Contradiction | 7 | 8% | +| Phase 4: Confrontation | 16 | 18% | +| Phase 5: Contaminated Trust | 16 | 18% | +| Cross-phase | 16 | 18% | +| **Total** | **89** | ~98% (rounding) | + +Close to the 91 estimate. The 2-line gap is margin for lines that emerge during writing — there's always one observation or reaction that only becomes obvious while you're in the character's head. + +### Format (Sample YAML) + +Each line in the pack looks like this. Showing one per phase to demonstrate prerequisite progression: + +```yaml +# Phase 1: Comfort — smuggler sees Kael, no concerns +- id: terminal_m_010 + character: smuggler + trigger: observe_npc + prerequisite: + relationship_state: Friendly + subject: kael_davan + text: "Kael's already at the dock. Good. The day's better when he's on shift." + mood: [fond] + topic: [colleague] + situation: [shift_start] + access: [insider, peer] + trust: surface + tags: [kael, baseline, pre_contradiction] + notes: "Phase 1 warmth. Establishes the bond." + +# Phase 2: First Crack — smuggler notices tell +- id: terminal_m_011 + character: smuggler + trigger: observe_anomaly + prerequisite: + all: + - relationship_state: Friendly + subject: kael_davan + - known_attributes: + subject: kael_davan + key: behavior_flags + value_contains: lattice_checking + text: "Kael keeps checking his lattice. Waiting for a message? Not like him to be jumpy." + mood: [concerned] + topic: [colleague] + situation: [routine] + access: [insider] + trust: real + tags: [kael, tell, first_crack] + +# Phase 3: Contradiction — the moment +- id: corridor_m_010 + character: smuggler + trigger: witness_interaction + prerequisite: + relationship_state: Friendly + subject: kael_davan + text: "Kael? Down here? And that's — I don't know them. Not crew, not ring. Who the hell is that?" + mood: [concerned] + topic: [colleague] + situation: [observation] + access: [insider] + trust: real + tags: [the_friend, contradiction, kael, critical_moment] + notes: "THE CONTRADICTION. Sets contradiction_flagged. Green -> amber." + +# Phase 4: Tell confirmation — the knife +- id: corridor_m_011 + character: smuggler + trigger: post_conversation + prerequisite: + all: + - relationship_state: PersonOfInterest + subject: kael_davan + - known_attributes: + subject: kael_davan + key: tell_observed + value: looks_left_when_lying + text: "He looked left. Always looks left when he's making something up. Two years, Kael. I know your tells." + mood: [conflicted] + topic: [trust] + situation: [confrontation] + access: [insider] + trust: real + tags: [the_friend, kael, tell_confirmation] + +# Phase 5: Contaminated trust — the new normal +- id: terminal_m_015 + character: smuggler + trigger: observe_npc + prerequisite: + all: + - relationship_state: PersonOfInterest + subject: kael_davan + - known_attributes: + subject: kael_davan + key: contradiction_flagged + value: meeting_unknown_contact + text: "Same old Kael. Same jokes, same routine. ...What are you not telling me?" + mood: [conflicted] + topic: [colleague] + situation: [routine] + access: [insider] + trust: real + tags: [kael, the_friend, contaminated_trust] +``` + +### Dependencies Before I Can Write the Full Pack + +| Dependency | Owner | Status | Impact if Missing | +|---|---|---|---| +| Content directory structure confirmed | Tyre | Round 2 (expected) | I write the pack as a single doc and split later — not blocking | +| FactId catalog (smuggler path entries) | Gestalt / Paula | Gap 5 from tracking | I use authoring-guide FactIds as placeholders, replace when catalog lands | +| EntityKnowledge key resolution (4 vs 8 keys) | Gestalt / Paula | D-03 disagreement | I write prerequisites using the Round 1 key names, adapt to winner | +| Monologue display constraints | Stig | R1 proposal: ~160 char, 2-line, 4-6 sec | Adopted. Writing to these constraints now. | +| NPC pattern/motivation mapping finalized | Gestalt | Round 2 (lead awarded to Gestalt) | Doesn't change Kael's content — he's FRIEND+OPERATOR regardless | + +**None of these are hard blockers.** The biggest risk is FactId placeholder drift — if the final catalog changes `contraband.ring_exists` to `smuggling.ring_confirmed`, I have to search-replace across the pack. Manageable, but annoying at scale. I'd rather the catalog stabilizes before I author all 695 items. + +### Authoring Timeline + +**Kael pack (91 lines):** I can draft the full pack once this workshop closes. Dialogue lines require the most iteration (Kael's voice under pressure is tricky — evasive without sounding scripted). Monologue lines are faster (I've been living in both character registers for two workshops now). Tell lines are mechanical — they map directly to the wiki's tell system table. + +Estimate: full draft in one session. Review pass in a second. Revision after Gestalt validates tags and Dudley confirms prerequisites fire correctly. + +**Sera pack follows immediately.** Different contradiction mechanics (cumulative pattern vs. single observation), different voice (Sera's precision vs. Kael's brevity), same structure. Lessons from Kael's pack inform Sera's. + +--- + +### Note on the 695 Number + +Round 1 tracking (A-06) flags my 695 total as "needs validation from others." Let me be clear about what it is and isn't: + +- **695 is the authored floor.** Below this, the 30-minute session has gaps — locations without environmental text, NPCs without enough dialogue variation, monologue pools that repeat. +- **695 is not the generation-expanded total.** D-028 specifies a 4x generation pass for dialogue ("write 10, generate 40"). If we apply that to the ~350 dialogue lines, the engine sees ~1,400 dialogue lines. But the 350 are what I hand-write. +- **695 doesn't include content the engine generates.** Routine greetings, time-of-day variations, personality-trait transformations — those are generation-pass output, not authored input. + +The number to track is **695 authored items.** That's the scope. That's what I deliver. + +--- + +*Mellanie, Round 2. Voice registers defined, pack structure mapped, dependencies identified. Ready to write the benchmark.* diff --git a/docs/workshops/v01-content-scoping/round2-paula.md b/docs/workshops/v01-content-scoping/round2-paula.md new file mode 100644 index 000000000..b596afb2b --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-paula.md @@ -0,0 +1,367 @@ +# Round 2 — Paula (Narrative & Political Depth) + +## Task 1: NPC Pattern/Motivation Mapping — v0.2 Emotional Layer + +Gestalt's mechanical mapping is canonical for v0.1. Accepted. + +But the versioned approach creates something valuable: **two analytical lenses on the same NPCs.** Gestalt maps from function ("What does this NPC do in the system?"). I map from meaning ("What does this NPC mean to the story?"). For v0.1, function drives implementation. For v0.2, meaning drives emotional depth expansion. + +Here's every disagreement annotated with what the v0.2 emotional layer adds: + +### Disagreement Resolution + v0.2 Notes + +| NPC | v0.1 (Gestalt) | v0.2 Layer (Paula) | What the v0.2 Layer Adds | +|-----|----------------|-------------------|--------------------------| +| **Voss** | SYSTEM + OPERATOR | SYSTEM + HANDLER | v0.1 Voss runs schedules (OPERATOR). v0.2 Voss manages people through scheduling authority — the resentment, the fear, the performative competence hiding real vulnerability. HANDLER unlocks Voss-specific monologue about being trapped between institutional role and criminal obligation. "I manage people. They just don't know who I'm managing them for." | +| **Devra** | THRESHOLD + HANDLER | THRESHOLD + OPERATOR | v0.1 Devra manages from above (HANDLER). v0.2 Devra runs the operation's machinery — she's the most competent person in the ring and knows it. OPERATOR unlocks content about Devra's ambition, her quiet belief she could run this better than Nils, her frustration at being the brain behind someone else's decisions. | +| **Maret** | SYSTEM + WITNESS | WITNESS + SKEPTIC | v0.1 Maret is the institutional scheduler who saw something (SYSTEM+WITNESS). v0.2 Maret doubts — not just "the numbers are off" but "the entire system I trusted is corrupt, and I'm complicit by filing clean reports." SKEPTIC unlocks Maret's crisis of institutional faith, the thing that makes her more than a data point for the detective. | +| **Resha** | CATALYST + SKEPTIC | CATALYST + CIVILIAN | v0.1 treats Resha's questions as genuine skepticism (SKEPTIC). v0.2 adds the tragedy: Resha is a CIVILIAN who accidentally became a CATALYST. She's not questioning the system — she's trying to learn it. The system is so broken that LEARNING it is disruptive. CIVILIAN unlocks content about Resha's bewilderment when people react to her innocent questions with fear. | +| **Harek** | GHOST + HANDLER | ANCHOR + HANDLER | v0.1 Harek shapes events through debt leverage (GHOST+HANDLER). v0.2 adds: Harek embodies contentment. He's the man who has enough, wants nothing more, and whose comfortable complacency created a tool for others without him meaning to. ANCHOR unlocks content about unconscious complicity — the "Is this enough?" question answered by someone who never thought to ask it, whose answer is "yes" while his comfort enables harm. | +| **Drin** | REMNANT + TURNCOAT | SYSTEM + TURNCOAT | v0.1 Drin remembers simpler times (REMNANT). v0.2 adds: Drin IS the inspection system. His compromise doesn't just corrupt a person — it corrupts the institutional process itself. SYSTEM unlocks content about institutional rot as metaphor: when the inspector is compromised, what does "inspection" even mean? | +| **Sess** | ANCHOR + CIVILIAN | NOBODY + CIVILIAN | v0.1 Sess embodies quiet contentment (ANCHOR). v0.2 adds: Sess is invisible — social wallpaper that the player walks past twenty times before realizing the bartender has seen everything. NOBODY unlocks dynamic tier promotion content for v0.2: the moment the player invests in Sess, the quiet contentment reveals itself as an observation post. | +| **Olin** | CATALYST + SKEPTIC | CATALYST + CIVILIAN | v0.1 gives Olin dual motivation (ambiguity). v0.2 resolves the ambiguity per playthrough: some seeds, Olin IS a skeptic (asking questions because they're probing). Other seeds, Olin is a civilian (asking questions because they genuinely need work). The randomized motivation resolution is a v0.2 generator feature. | +| **Sabel** | THRESHOLD + OPERATOR | GHOST + OPERATOR | v0.1 Sabel is the off-station gatekeeper (THRESHOLD). v0.2 adds: Sabel is absent power — the upstream force shaping local events. GHOST unlocks content about Sabel's indifference: Sabel doesn't care about Sova's people, only about the supply chain. If the operation burns, Sabel moves on. That coldness is narrative content for v0.2 (the player discovers the ring is disposable to its supplier). | +| **Tav** | SYSTEM + CIVILIAN | NOBODY + OPERATOR | v0.1 Tav follows ring lookout procedure (SYSTEM+CIVILIAN). v0.2 adds: Tav is peripheral, barely visible, the lowest rung. NOBODY unlocks tier promotion content — the player who notices Tav discovers a young person trapped in a role they barely understand, doing dangerous work because someone told them it mattered. | + +### What the v0.2 layer means for content planning + +The v0.2 layer adds **~30-50 authored lines per NPC** of emotional-depth content: expanded monologue pools, deeper dialogue gating, and pattern-specific interaction triggers (e.g., ANCHOR contentment dialogues, NOBODY promotion paths, GHOST influence chains). This is the "write 10, generate 40" Tier 2 expansion target. + +For now, Gestalt's v0.1 mapping governs implementation. My mapping governs the content roadmap. Both are correct — they describe different layers of the same characters. + +--- + +## Task 2: The Key Bloat Question — Generic Assessment Architecture + +The lead asks: "If the original EntityKnowledge set was incomplete for the smuggler, are we doing this again for the administrator or the corporate tycoon?" + +**The honest truth: No, but only if we name the keys correctly.** + +### The Problem With "Smuggler-Specific" Keys + +Gestalt proposed 4 new keys: `exposure_risk`, `loyalty_assessment`, `cover_integrity`, `moral_weight`. These are described as "smuggler-specific." But look at what they actually capture: + +| Key | What It Really Measures | Is This Smuggler-Specific? | +|-----|------------------------|---------------------------| +| `exposure_risk` | "How much does this NPC's behavior threaten my current interests?" | No. An administrator assesses exposure risk too — but it's political exposure, not operational. | +| `loyalty_assessment` | "Where does this NPC's loyalty lie relative to me?" | No. Every archetype navigates loyalty networks. | +| `cover_integrity` | "Is my position/identity intact around this person?" | No. An administrator's "cover" is their public reputation. A newcomer's "cover" is their attempt to blend in. | +| `moral_weight` | "How much moral cost do I carry in this relationship?" | No. Already generic. Every human feels guilt, complicity, or indifference. | + +These aren't smuggler keys. They're **role-perspective assessment keys** that every archetype uses with different semantics. + +### The Generic Architecture + +**Proposal: 6 role-perspective keys that work for all 8 archetypes.** Same keys, archetype-specific interpretation, zero per-archetype schema bloat. + +| Generic Key | Smuggler Interpretation | Detective Interpretation | Administrator | Newcomer | Merchant | +|------------|------------------------|------------------------|---------------|----------|----------| +| `risk_assessment` | Exposure of ring operations | Exposure of investigation | Exposure of political position | Vulnerability as an outsider | Exposure of trade relationships | +| `loyalty_assessment` | Loyal to the ring? | Cooperative with investigation? | Aligned with my faction? | Willing to help a newcomer? | Reliable trade partner? | +| `position_integrity` | Is my cover identity holding? | Is my authority respected? | Is my reputation intact? | Am I blending in? | Is my business relationship secure? | +| `moral_weight` | Guilt about complicity | Guilt about using people | Guilt about political compromises | Guilt about disrupting others | Guilt about exploiting information | +| `tell_observed` | Behavioral tell suggesting ring exposure | Behavioral tell suggesting concealment | Political tell (nervous in meetings) | Social tell (who's welcoming, who's not) | Commercial tell (price sensitivity, evasion) | +| `contradiction_flagged` | Behavior contradicts ring loyalty | Behavior contradicts claimed innocence | Behavior contradicts public stance | Behavior contradicts stated welcome | Behavior contradicts stated terms | + +**The values use the same enums.** `risk_assessment: "high"` means different things to different archetypes, but the schema is identical. The content authoring guide (the archetype brief, the voice register, the monologue prerequisite rules) provides the interpretation. The knowledge graph stores data; the content layer provides meaning. + +### Why This Works + +Let me complicate this by testing it against the hardest case. The **Insider** archetype (the community member, the person embedded in the social fabric). Does `risk_assessment` work? + +- Insider's `risk_assessment` of Kael: "How much does Kael's behavior threaten the community I'm part of?" Value: `"moderate"` — Kael's nervousness is unsettling but hasn't caused concrete harm yet. +- Insider's `loyalty_assessment` of Voss: "Where does Voss's loyalty lie — with the shift crew or with whoever's pressuring him?" Value: `"uncertain"` — Voss seems stressed but still functional. +- Insider's `position_integrity` assessment of themselves near the detective: "Does the detective's presence threaten my standing in the community?" Value: `"stressed"` — people are nervous, the community is pulling inward, and the insider's position as a trusted neighbor is complicated by anyone who cooperates with authorities. + +It works. Same keys. Different lens. Zero schema change. + +### What IS Archetype-Specific + +The archetype-specific content lives in three places, none of which are EntityKnowledge keys: + +1. **Monologue prerequisite interpretation.** The prerequisite `risk_assessment == "high"` triggers different monologue lines per archetype. The smuggler thinks: "Torek's spending is going to get us caught." The administrator thinks: "Torek's spending is drawing Commission attention to the district." Same trigger, different pool, same key. + +2. **Dual-lens section in NPC profiles.** Every NPC profile already has per-character lens sections. For 8 archetypes, this becomes an archetype-lens section per NPC — but that's content authoring, not schema work. And at 300-world scale, the generator produces these from archetype brief + NPC profile + composition rules. + +3. **Voice register.** The smuggler's internal voice is operational and terse. The detective's is analytical and institutional. An administrator's would be political and strategic. A newcomer's would be uncertain and observational. These registers shape how the same knowledge is expressed, not what knowledge exists. + +### The Practical Answer + +**For v0.1:** Rename Gestalt's 4 keys to the generic versions: + +| Current (Gestalt's v0.1) | Renamed (Generic) | Change Required | +|--------------------------|-------------------|-----------------| +| `exposure_risk` | `risk_assessment` | Rename in schema + content | +| `loyalty_assessment` | `loyalty_assessment` | No change — already generic | +| `cover_integrity` | `position_integrity` | Rename in schema + content | +| `moral_weight` | `moral_weight` | No change — already generic | + +2 renames. That's it. The v0.1 smuggler and detective content is authored against the generic keys. When v0.2 adds the Newcomer archetype, the Newcomer's knowledge graph uses the same 6 role-perspective keys with Newcomer-specific content. Zero schema changes. Zero key bloat. + +**For v0.2+:** `social_debt` and `loyalty_conflict` (Gestalt's deferred keys) should also be evaluated against this lens. `social_debt` generalizes cleanly (every archetype navigates obligations). `loyalty_conflict` may fold into `loyalty_assessment` + `behavior_flags` as Gestalt argued. That's a v0.2 decision. + +**Total key count per archetype: 20** (14 shared + 6 role-perspective). Same for smuggler, detective, administrator, merchant, newcomer, insider, fixer, exile. No per-archetype keys. No bloat. The schema scales to 8 archetypes without modification. + +### What SUSTAINS This Across 8 Archetypes + +The keys are the skeleton. The archetype brief is the muscle. Each archetype needs: + +1. A **voice register** (how their monologue sounds) +2. An **assessment framework** (what "risk" means to them, what "loyalty" means) +3. A **moral arc** (what their version of Comfort → Doubt → Reckoning → Compromise looks like) + +These are content documents, not schema changes. They live in the authoring guide, not the codebase. Mellanie writes them; Paula designs them; Gestalt validates that the mechanical triggers work. + +The smuggler moral arc (Comfort → Doubt → Reckoning → Compromise) is the prototype. Every archetype gets a 4-phase arc with the same structure and different content. The administrator's arc might be: Authority → Doubt → Reckoning → Adaptation. The newcomer's: Wonder → Doubt → Reckoning → Belonging-or-Departure. + +But the keys don't change. The keys are the infrastructure. The content is the building. + +--- + +## Task 3: Wiki Fix Execution Plan + +### Prioritized Execution Order + +The changes are ordered by dependency — later items reference earlier items' outputs. + +| Priority | Change | Files Affected | Content Status | Estimated Effort | +|----------|--------|---------------|----------------|-----------------| +| **P0-1** | Naia Tamm rename | 16 files | Cascade plan ready (Round 1). Content changes specified per-file. | ~2 hours (mechanical, but must be thorough) | +| **P0-2** | Nils Davan stub page | 1 new file + 3 cross-ref updates | Full draft ready (Round 1). | ~30 min | +| **P0-3** | Drin Tier 2 expansion | 1 file rewrite + index update | Full draft ready (Round 1). 7 new axes, voice sample, tell system. | ~1 hour | +| **P1-1** | Canonical name application | 10 NPC profile headers + index | Name table ready (Round 1). 3 names need Miri validation. | ~1 hour | +| **P1-2** | Triangle 1 escalation fork | index.md triangle section + Kael/Voss notes | 5-beat sequence designed (Round 1). Self-contained per D-INTERACT. | ~1 hour | +| **P2-1** | Smuggler-lens sections (all 17 NPCs) | 17 NPC profile files | Template designed (Round 1). Per-NPC content requires authoring. | ~8-10 hours | +| **P2-2** | PC-as-NPC profiles | 2 new files | Gap identified (Round 1). Content not yet drafted. | ~3-4 hours | +| **P2-3** | FactId catalog — smuggler entries | 1 file | Gap identified (Round 1). Entries not yet drafted. | ~2 hours | + +### P0-1: Naia Tamm Rename — Execution-Ready Content + +Below is the exact content change needed for each file. I'm specifying what to find and what to replace so this can be executed mechanically. + +#### File 1: `docs/wiki/npcs/hael.md` → rename to `docs/wiki/npcs/naia-tamm.md` + +Full rewrite of the file header and all internal references. Key structural changes: + +**Old header:** +```markdown +# Hael + +**Tier 2 NPC** | The Last Shift (Bar) | Mundane +``` + +**New header:** +```markdown +# Naia Tamm — THE MIRROR + +**Tier 1 (MIRROR) NPC** | The Last Shift (Bar) | Mundane +``` + +**Core Identity section changes:** +- `**Name**: Hael` → `**Name**: Naia Tamm` +- Add: `**Canonical ID**: krenn.naia-tamm` +- Add: `**Pattern**: MIRROR (no contradiction arc, no secret)` +- Add: `**Motivation**: CIVILIAN (v0.1 mechanical mapping)` + +**All internal "Hael" references** → "Naia Tamm" (formal) or "Naia" (casual/dialogue). The Krenn convention (D-036) is first-name-primary in social contexts, so dialogue and monologue use "Naia." Profile text and cross-references use "Naia Tamm." + +**Secret/Vulnerability section** — must be reframed for MIRROR pattern: +```markdown +### 2. Secret / Vulnerability + +**Surface secret**: None. Naia has no secrets. This is THE MIRROR's +defining trait. + +**Deep secret**: None. Naia Tamm is the one person in the district who +is exactly what she appears to be — a teacher worried about her partner. +The MIRROR's power is simplicity. When events drag Naia into the +conspiracy (through Kael's ring involvement, through Sera's concealment), +the damage is devastating precisely because Naia was genuine. + +**Vulnerability**: Emotional exposure. Naia's worry about Kael is visible, +exploitable, and honest. She has no defenses because she has no agenda. +The detective can cultivate Naia as a source without guilt — until the +consequences of that cultivation become apparent. +``` + +**Content requirements section** — update for MIRROR-specific depth: +```markdown +## Content Requirements (MIRROR — Tier 1 Emotional Depth) + +**~50-70 authored lines total** (no generation expansion): + +- **20-25 dialogue lines**: Bar greetings (3), Kael-related conversation + (8-10), Sera interaction (4-5), school/community (3-4), response to + bad news (3-5) +- **8-12 trust-gated lines**: Honest disclosures about Kael's behavior + changes, emotional confessions about the relationship, what Naia has + told Sera +- **5-8 unprompted lines**: Naia volunteers worry about Kael, asks + about Kael if the player has seen him, mentions school events +- **8-12 monologue lines per character**: + - Smuggler monologue about Naia: guilt (4-5 — "She doesn't know. + She can never know."), protectiveness (3-4), moral weight (2-3) + - Detective monologue about Naia: sympathy (3-4 — "The partner. + Worried. Doesn't know what I know."), source assessment (2-3), + moral cost of using her (3-4) +- **3-5 tell observation lines**: Visible stress, checking for Kael, + increased bar presence, confiding in Sera +- **NO contradiction discovery lines** — THE MIRROR has no contradiction. + The emotional payload comes from what happens TO Naia, not what Naia + hides. +``` + +**Cross-reference links at bottom:** +```markdown +**Cross-References:** [Kael Davan](kael-davan.md), [Sera Venn](sera-venn.md), +[Lera Sessik](lera-sessik.md) +``` + +#### File 2: `docs/wiki/npcs/index.md` + +**Roster table row 9:** +- Old: `| 9 | Hael | Teacher (Kael's Partner) | Bar | Tier 2 | Mundane | Worried Partner |` +- New: `| 9 | **Naia Tamm** | Teacher (Kael's Partner) | Bar | **Tier 1 (MIRROR)** | Mundane | Worried Partner |` + +**Triangle 4 diagram (3 label changes):** +``` + Naia Tamm (Teacher, Kael's Partner) + / \ + worried about confides in friend + Kael's late nights, | + evasive answers | + | | + Kael (Dock Worker) <--friendship--> Sera (Commission Field Tech) +``` + +**Triangle 4 description text** — every "Hael" → "Naia Tamm" (formal) or "Naia" (in-character). Approximately 8 replacements. + +**Social site listing — The Last Shift:** +- Old: `- Hael (Teacher) — Kael's partner, worried` +- New: `- Naia Tamm (Teacher) — Kael's partner, worried` + +**Content Deliverables section:** +- Move Naia Tamm from "Tier 2" list to a new "Tier 1 (MIRROR)" subsection, or note the tier change explicitly. + +#### File 3: `docs/wiki/index.md` + +**NPC listing:** +- Old: `- [Hael](npcs/hael.md) — Kael's partner (referenced)` +- New: `- [Naia Tamm](npcs/naia-tamm.md) — Kael's partner, THE MIRROR` + +Also move from "Tier 2" to a new bullet under Tier 1: +```markdown +- **Tier 1 (MIRROR):** + - [Naia Tamm](npcs/naia-tamm.md) — Kael's partner, teacher +``` + +#### File 4: `docs/wiki/npcs/kael-davan.md` (~20+ replacements) + +This is the heaviest file. Every "Hael" becomes "Naia" in dialogue/monologue contexts and "Naia Tamm" in descriptive/profile contexts. Key sections: + +- **Axis 1 (Want), Secondary**: "Protect Hael (partner)" → "Protect Naia (partner)" +- **Axis 2 (Secret)**: "Hael doesn't know about the smuggling" → "Naia doesn't know about the smuggling" +- **Relationship 2 header**: "Hael (partner, teacher, civilian)" → "Naia Tamm (partner, teacher, civilian)" +- **Relationship 2 body**: All "Hael" → "Naia" (casual reference in relationship description) +- **Axis 4 (Tolerance)**: "Hael's increasing worry" → "Naia's increasing worry" +- **Axis 5 (Routine)**: "Home with Hael" → "Home with Naia" +- **Axis 6 (Information)**: "Hael's worry" → "Naia's worry", "Hael talked to Sera" → "Naia talked to Sera" +- **Axis 7 (Contentment)**: "guilt about lying to Hael" → "guilt about lying to Naia" +- **Voice sample**: "I'm fine, Hael" → "I'm fine, Naia" (Mood: Protective line) +- **Dual-lens notes**: All Hael references +- **Triangle 4 role notes**: All Hael references +- **Secret section**: All Hael references +- **Contradiction arc**: Multiple references throughout all 5 phases +- **Authoring notes**: "Hael references" → "Naia references" + +File link in cross-references: +- Old: `[Hael](hael.md)` (if present) +- New: `[Naia Tamm](naia-tamm.md)` + +#### File 5: `docs/wiki/npcs/sera-venn.md` (~15 replacements) + +Same pattern as Kael. Key sections: +- **Axis 1 (Want), Secondary**: "Protect Hael" → "Protect Naia Tamm" +- **Relationship 2**: Full section rewrite — "Hael" → "Naia" / "Naia Tamm" +- **Axis 4, 5, 6, 7**: All Hael references +- **Tell system**: References to Hael observing Sera's avoidance +- **Voice sample**: "Hael was here earlier" → "Naia was here earlier" +- **Triangle 4 role notes**: All Hael references +- **Secret section**: All Hael references +- **Contradiction arc**: Hael references throughout +- **Authoring notes**: "Hael references" → "Naia references" + +#### Files 6-16: Lighter touch changes + +| File | Changes | +|------|---------| +| `docs/wiki/npcs/lera-sessik.md` | Axis 6 info inventory: "Hael's worry about Kael" → "Naia Tamm's worry about Kael" | +| `docs/wiki/npcs/maret-korr.md` | Axis 5 routine: "goes for Hael's company" → "goes for Naia's company" | +| `docs/wiki/authoring/monologue-guide.md` | Any monologue examples referencing Hael | +| `docs/wiki/locations/krenn-system/the-last-shift.md` | NPC presence list, social dynamics | +| `docs/wiki/world/sova-transit-district.md` | NPC references | +| `docs/wiki/factions/the-ring.md` | Kael vulnerability references | +| `docs/wiki/knowledge/relationship-states.md` | Example entries | +| `docs/wiki/knowledge/entity-attributes.md` | Example entries | +| `docs/wiki/knowledge/fact-catalog.md` | FactId entries: `npc.hael.*` → `npc.naia_tamm.*`, `relationship.kael.hael` → `relationship.kael.naia_tamm` | +| `docs/wiki/world/krenn-system.md` | Naming examples list: "Hael" → keep (it's a valid Krenn first name), but add "Naia" to the list if not present | +| `decisions/content.md` | D-034: "partner Hael" → "partner Naia Tamm". D-036: naming examples list update. | + +### P0-2: Nils Davan Stub — Execution-Ready Content + +The full stub page draft was provided in my Round 1 response. It's ready to write to `docs/wiki/npcs/nils-davan.md`. + +**Cross-reference updates needed after creation:** + +| File | Change | +|------|--------| +| `docs/wiki/npcs/index.md` | Add Nils to the Smuggling Ring social site listing. Add to roster table as off-stage NPC. | +| `docs/wiki/index.md` | Add `[Nils Davan](npcs/nils-davan.md) — Ring coordinator (off-stage)` to NPC listing. | +| `docs/wiki/npcs/kael-davan.md` | Add cross-reference link at bottom: `[Nils Davan](nils-davan.md)` | +| `docs/wiki/npcs/voss.md` | Add cross-reference link | +| `docs/wiki/npcs/devra.md` | Add cross-reference link | +| `docs/wiki/npcs/renn.md` | Add cross-reference link | + +### P0-3: Drin Tier 2 Expansion + +Full 7-axis draft provided in Round 1. Gestalt confirms promotion (A-01 consensus). Execution: + +1. Rewrite `docs/wiki/npcs/drin.md` from Tier 3 format to Tier 2 format using my Round 1 draft +2. Update `docs/wiki/npcs/index.md` roster table: Tier 3 → Tier 2, update entanglement +3. Update `docs/wiki/index.md`: Move Drin from Tier 3 listing to Tier 2 +4. Add Gestalt's v0.1 mapping to header: `**Pattern**: REMNANT | **Motivation**: TURNCOAT` + +### P1-1: Canonical Names + +Apply the name table from Round 1. **Three names need Miri validation** before applying: Drin Vassar, Sess Torin, Tav Lenn. Olin deliberately omits surname (narrative choice). Sabel deliberately omits surname (off-station character). + +### P1-2: Triangle 1 Escalation Fork + +**Important: Self-contained per Tyre's recommendation (confirmed by lead as D-INTERACT).** My Round 1 design included cross-triangle cascade (Path B feeding Triangle 2 via Maret). For v0.1, the fork must be self-contained. + +Revised Triangle 1 fork — self-contained version: + +The 5-beat structure stays the same, but Beat 5 consequences are resolved WITHIN Triangle 1: + +| Path | Self-Contained Consequence | What Does NOT Cascade | +|------|---------------------------|----------------------| +| **A (Back Voss)** | Nils is angry at Kael. Kael is frightened. Smuggler feels responsible. | Nils's anger does not trigger Pell's crisis (Triangle 5). | +| **B (Back Nils)** | Voss capitulates but is visibly rattled. Fourth manifest revision happens. | The manifest revision does NOT trigger Maret's discovery (Triangle 2). Maret discovers independently. | +| **C (Stay neutral)** | Nils acts unilaterally. Operation is sloppier. Smuggler's standing drops. | The sloppy operation does NOT make the detective's investigation easier. Detective's timeline is independent. | + +This keeps Triangle 1 testable in isolation while preserving the emotional weight of each choice. Cross-triangle cascade becomes a v0.2 feature when the storyteller can manage multi-triangle state. + +### Execution Dependencies + +``` +P0-1 (Naia Tamm rename) ─── no dependencies, start immediately +P0-2 (Nils stub) ─── no dependencies, start immediately +P0-3 (Drin expansion) ─── blocked by A-01 formalization (consensus confirmed) +P1-1 (Canonical names) ─── blocked by Miri validation (3 names) +P1-2 (Triangle 1 fork) ─── depends on P0-2 (Nils stub must exist first) +P2-1 (Smuggler-lens) ─── depends on key bloat resolution (Task 2 above) +P2-2 (PC-as-NPC) ─── depends on P2-1 (smuggler-lens template establishes format) +P2-3 (Smuggler FactIds) ─── depends on P2-1 +``` + +**P0-1 and P0-2 can start immediately and run in parallel.** I recommend these as the first Sprint 3 copy team commits. diff --git a/docs/workshops/v01-content-scoping/round2-si.md b/docs/workshops/v01-content-scoping/round2-si.md new file mode 100644 index 000000000..7c164bea7 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-si.md @@ -0,0 +1,290 @@ +# Round 2 — SI (Project Manager) +## Finalized Ticket List for v0.1 Content Scoping + +Incorporates: all 7 Round 1 responses, Qatux tracking doc, lead decisions (D-NPC, D-FORMAT, D-INTERACT, D-PAUSE). + +--- + +## 1. Lead Decisions — Impact on Tickets + +| Decision | Ticket Impact | +|----------|---------------| +| **D-NPC:** Gestalt's mechanical mapping wins for v0.1 | NEW-1 simplified: "apply Gestalt's mapping" (not resolve disagreements). Paula's emotional layer becomes v0.2 annotation. | +| **D-FORMAT:** YAML for content, RON converter available | Confirms C-series as YAML. Adds NEW-8: RON converter tool. | +| **D-INTERACT:** Single action for v0.1, multi-verb architecture underneath | NEW-7 reframed. Adds NEW-9 (server: InteractionOptions) + NEW-10 (client: extensible prompt). | +| **D-PAUSE:** Spacebar always pauses, overlays = 50% sim speed | Adds NEW-11 (server: tick rate scaling) + NEW-12 (client: pause state machine). | + +--- + +## 2. Round 1 Consensus — Impact on Tickets + +| Consensus Item | Ticket Impact | +|----------------|---------------| +| **A-01:** Drin promoted to Tier 2 | NEW-2 changes from "evaluation" to "execute promotion" — expand profile. | +| **A-02:** NOBODY deferred to v0.2 | No v0.1 ticket. Hidden data ships in NPC YAML files (handled by C-series). | +| **A-05:** ~200 monologue lines | Sizes NEW-4 at ~200 lines (~100 per character). | +| **A-06:** ~695 total authored items | Overall content scope validation. No new ticket, but informs sprint capacity. | +| **A-07:** 3 active + 2 passive triangles | Confirms A11 scope (Triangle 1 only needs fixing). T3/T5 need less work. | +| **A-11:** THE FRIEND is critical path | Confirms #261 → #297/#298 chain is the highest-priority dependency. | +| **A-12:** Client receives text from server | No client-side content loading tickets needed. Simplifies client scope. | +| **A-14:** PC-as-NPC confirmed for v0.1 | Validates B3, B5, NEW-6. | +| **A-15:** News ticker confirmed for v0.1 | #306 confirmed. Scope: ~20 headlines. | +| **A-17:** Self-contained triangle forks | Reduces C8 complexity (no cross-triangle cascade logic). | +| **Tyre: Line previewer CLI** | Adds NEW-13. | +| **Paula: 7 additional wiki gaps** | Gaps 1-5 already covered by existing tickets. Gap 6 (location smuggler detail) → folded into content conversion. Gap 7 (Harek triangle membership) → folded into NEW-1 mapping. | + +--- + +## 3. Existing Ticket Updates (Revised) + +| ID | Title | Action | Sprint Target | +|----|-------|--------|---------------| +| #368 | Knowledge vocabulary for v0.1 content | **Close** — wiki at `docs/wiki/` is the delivered output | Sprint 2 (done) | +| #369 | Wiki Review Workshop Outputs (epic) | **Repurpose** as parent epic for all v0.1 content scoping tickets. Update description. | — | +| #301 | Wiki taxonomy and structure | **Assign to copy.** Downscope: Sova district structure only for v0.1. Full 500+ world taxonomy is generator-era. | Sprint 3 | +| #319 | Krenn System wiki entry | **Merge with A3.** Expanded: wiki page + seed.yaml reference implementation. Assign to copy. | Sprint 4 | +| #261 | Dual Lens Authoring Guide | **CRITICAL PATH ROOT.** Assign to copy. Owner: Paula. Blocks 9 downstream tickets. | Sprint 3 (P0) | +| #310 | Character voice speech patterns | **Subsume into B4** (smuggler voice card + moral arc). B4 is the larger ticket. Close #310 when B4 lands. | Sprint 3 (via B4) | +| #306 | News ticker / Meridian feed content | **Confirm scope:** ~20 dual-lens headlines. Assign to copy. | Sprint 4 | +| #326 | YAML content loader | **Assign to server.** Blocked by C1 + C2. | Sprint 4 | +| #86 | Core NPC components | **Already Sprint 3 server.** Dudley's ECS component list from Round 1 informs this. | Sprint 3 | +| #88 | Daily routine system | **Already Sprint 3 server.** Needs NPC routine YAML from content conversion. | Sprint 3 | + +--- + +## 4. Finalized New Ticket Proposals + +### Group A: Wiki Content Fixes (copy team) + +| ID | Title | Type | Priority | Sprint | Blocked By | Description | +|----|-------|------|----------|--------|------------|-------------| +| A1 | Apply canonical full names to all NPC wiki pages | task | medium | 3 | — | Apply Miri's 12 full names to NPC wiki pages. Update Core Identity blocks, environmental text references. | +| A2 | Rename Hael → Naia Tamm across all wiki files | task | medium | 3 | — | Find-and-replace across ~8 wiki files. Triangle 4 becomes Naia-Kael-Sera. | +| A6 | Add smuggler-perspective FactId progression text | task | high | 4 | B4 | Add smuggler-path text for all FactIds where smuggler starts at KnowsDetails or has different knowledge path. | +| A7 | Add smuggler-specific entity attributes to entity-attributes.md | task | high | 3 | — | Scope depends on Round 2 resolution of D-03 (Gestalt's 4 new + 2 repurposed vs Paula's 8 new). Add to wiki/knowledge/entity-attributes.md. | +| A8 | Standardize location shortcodes in monologue-guide.md | task | medium | 3 | — | Fix hub_m_ → terminal_m_ inconsistency. Add shortcode reference table. | +| A10 | Stub NPC profile for Nils Davan | task | medium | 3 | — | Off-stage profile: Core Identity, relationship to Kael, role in ring, known attributes. | +| A11 | Fix Triangle 1 (Hub Power) — Volume Escalation decision | story | high | 3 | A10 | Add smuggler decision fork: side with Nils (louder signal), side with Voss (tension), or split route (exposure). Self-contained — no cross-triangle cascade per A-17. | +| A12 | Create environmental text examples file | task | medium | 4 | A3/#319 | Bar menu, safety notice, news ticker cycle, corridor graffiti. Krenn-voiced. Needed for wow moment #1 (Arrival). | + +### Group B: Style Guides & Specs (copy team) + +| ID | Title | Type | Priority | Sprint | Blocked By | Description | +|----|-------|------|----------|--------|------------|-------------| +| B1 | NPC Authoring Style Guide | story | critical | 5 | A3, A4, A7, B2, B4 | Combined document: tier templates, authoring rules, regional culture, content pipeline. Reframed as generator specification — this IS the NPC generator input format. Most complex ticket; many blockers. | +| B2 | THE MIRROR pattern spec | story | high | 3 | — | Define honest-character NPC pattern. No contradiction arc, no secret. Tier 1 emotional depth. Naia Tamm is first instance. Output becomes generator template. | +| B3 | PC-as-NPC unified spec | story | high | 3 | A7 | Unified spec: starting knowledge graph, starting relationship map, reverse knowledge (bidirectional), orientation monologue, agency boundaries, authored relationship stubs, tell inversion. 9-step conversion checklist. | +| B4 | Smuggler voice card + moral arc spec | story | high | 3 | A7 | Two deliverables: (1) Smuggler voice card — paired examples with detective showing dual-lens register differences. (2) Smuggler moral arc — 4 phases (Comfort→Doubt→Reckoning→Compromise), smuggler-specific FactIds, monologue triggers. Subsumes #310 scope. | +| B5 | NPC-format briefs for both PCs — Concept A v0.1 | story | high | 4 | B3 | NPC-mode behavior scripts (5-7 routine entries each), reverse dual-lens notes, observation monologue (3-5 lines per character). ~20-30 authored items total. | + +### Group C: Content Directory & Schema (split teams) + +| ID | Title | Type | Priority | Sprint | Team | Blocked By | Description | +|----|-------|------|----------|--------|------|------------|-------------| +| C1 | Content directory structure design doc | story | high | 3 | copy | — | Document converged structure: `_meta/` + `_schema/` (infrastructure), `global/` + `districts/` (content). Canonical_id format. Mod overlay mechanics. District as atomic pack unit. Informs all content implementation. | +| C2 | Create content/ directory skeleton | task | high | 3 | server | C1 | Create directory tree with empty YAML stubs. `_meta/`, `_schema/`, `global/` (factions, technology, contraband, knowledge, enums, regions), `districts/sova-transit/` (district.yaml, npcs/, locations/, templates/, triangles/, lines/). | +| C3 | Content schema definitions (YAML Schema files) | story | high | 3 | server | C1 | Create `_schema/` with validation schemas: npc.schema.yaml (tier-conditional), location, faction, template, fact, dialogue, monologue, district, pool. | +| C4 | Create global/enums/ YAML files from D-035 | task | medium | 4 | server | C2 | Populate enums: situations (13), topics (9), moods (8), triggers (9), access-tiers (5), activities (new). | +| C5 | Create global/knowledge/entity-attributes.yaml | task | medium | 4 | server | C2 | Machine-readable entity attribute definitions. 14 detective + smuggler keys (count depends on D-03 resolution). | +| C6 | Create pools.yaml for v0.1 seed-time selection | task | medium | 4 | server | C2 | Sova single-candidate pools: friend_smuggler, friend_detective, bar_regulars, compromised_inspector, primary_contraband. Architecture supports N candidates. | +| C7 | Create template YAML files with role slot definitions | task | medium | 4 | server | C2 | 3 social sites: logistics-hub.yaml, bar.yaml, smuggling-ring.yaml. Role slots with count ranges, required flags, pool references, triangle constraints. | +| C8 | Create triangle YAML files for 5 v0.1 triangles | task | medium | 4 | server | C2 | 5 triangles. Self-contained forks (no cross-triangle cascade per A-17). Includes updated T1 with Volume Escalation. | +| C9 | Implement make validate-content CLI | story | high | 4 | server | C3 | Structural + tier-conditional validation. Reads _schema/, validates all YAML. v0.2: cross-reference validation. | +| C10 | Pre-commit FactId typo check | task | medium | 4 | ci | — | Grep-based pre-commit check validating FactId references against canonical facts.yaml. | + +### Group D: Design Specs + +| ID | Title | Type | Priority | Sprint | Team | Blocked By | Description | +|----|-------|------|----------|--------|------|------------|-------------| +| D2 | Seed configuration schema design | story | medium | 4 | server | — | Schema for what the randomizer produces at game-start: FRIEND selections, social site population, compromised NPC, contraband type. v0.1 has single-candidate pools but architecture must support N. | +| D4 | News ticker headline pool | task | medium | 4 | copy | — | ~20 dual-lens headlines for Meridian feed at Lera's. Freight data, politics, local infrastructure, sports, Commission advisories. Each works as dual-lens monologue trigger. | + +### Group NEW: Tickets from Round 1 + Round 2 + +| ID | Title | Type | Priority | Sprint | Team | Blocked By | Description | +|----|-------|------|----------|--------|------|------------|-------------| +| NEW-1 | Apply Gestalt's NPC pattern/motivation mapping | task | high | 3 | copy | — | Apply the canonical v0.1 mapping (Gestalt's Round 2 finalized table) to wiki and content files. 17 NPCs × (pattern + motivation). Includes Harek's structural role per Paula Gap 7. | +| NEW-2 | Drin Tier 2 profile expansion | task | medium | 3 | copy | NEW-1 | Drin confirmed Tier 2 (A-01). Expand wiki profile to full Tier 2 depth: all 10 profile axes, smuggler lens section, triangle membership formalized. | +| NEW-3 | Wiki → YAML content conversion (Sova NPCs) | story | high | 4 | copy | C1, C2, C3 | Convert 17 NPC wiki profiles to engine-readable YAML. Includes routine YAML layer (Paula Gap 3), triangle fork decision points (Gap 4), location smuggler detail (Gap 6). Tyre estimates 2-3 days. | +| NEW-4 | Monologue content pool — minimum viable (~200 lines) | story | high | 4 | copy | #261, B4 | ~100 lines per character. Categories: arrival observations, NPC routine commentary, environmental flavor, contradiction hints, idle reflections, tutorial/orientation. Follows Mellanie's authoring order (FRIEND packs first, then fill). | +| NEW-5 | v0.1 wow moment content checklist | task | high | 3 | copy | — | Map D-039's 6 wow moments to specific content deliverables + implementation tickets. Track completion. Acceptance: every moment has identified content + code backing it. | +| NEW-6 | PC-as-NPC content authoring | story | high | 5 | copy | B3, B5 | Author the actual lines: 5-7 routine entries per character, 3-5 observation monologue lines, reverse dual-lens notes. ~25 authored items (Mellanie's budget). | +| NEW-7 | v0.1 interaction verb spec | task | medium | 3 | copy | — | Document the 7 interaction types (A-04: Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear). Define triggers, UI needs, server events per type. v0.1 surfaces single context-sensitive action; architecture supports N verbs. | + +### Group NEW-R2: Tickets from Lead's Round 2 Decisions + +| ID | Title | Type | Priority | Sprint | Team | Blocked By | Description | +|----|-------|------|----------|--------|------|------------|-------------| +| NEW-8 | YAML → RON converter tool | task | medium | 4 | server | C1 | Build-time converter: reads content YAML, emits RON. Lives in `tooling/`. Runs as make target (`make content-ron`). Not on critical path — engine consumes YAML in v0.1; RON is future-proofing. | +| NEW-9 | Multi-verb interaction architecture — server InteractionOptions | story | high | 3 | server | — | Server models N available actions per entity via `InteractionOptions` component. v0.1 client picks first/best; v0.2 client shows menu. Must not be throwaway. Includes ObserverSnapshot v3 extension for interaction data. | +| NEW-10 | Multi-verb interaction architecture — client extensible prompt | story | high | 3 | client | NEW-9 | Client-side interaction prompt designed for extensibility. v0.1: single context-sensitive E prompt. Architecture supports v0.2 multi-verb menu. Prompt system reads InteractionOptions from server state. | +| NEW-11 | Pause system — server tick rate scaling | story | medium | 3 | server | — | Tick rate supports 3 states: full speed (normal play), 50% speed (UI overlay open), 0% (spacebar pause). Server receives pause/speed commands from client. | +| NEW-12 | Pause system — client state machine | story | medium | 3 | client | NEW-11 | Client pause state machine: Normal → Overlay (50% speed, triggered by knowledge panel / dialogue) → Paused (spacebar from any state). Spacebar always overrides to full pause. | +| NEW-13 | Line previewer CLI | task | medium | 4 | server | C3, C9 | CLI tool to preview monologue/dialogue line selection for a given game state. Tyre's recommendation for content tuning. Lives in `tooling/`. Helps content authors test prerequisite logic without running the full game. | + +--- + +## 5. Sprint Plan + +### Sprint 3: Know (Copy Team — Content Foundation) + +**Goal:** Deliver the content specs and wiki fixes that unblock server implementation and content authoring. + +| Priority | Ticket | Title | Owner Hint | Effort | +|----------|--------|-------|------------|--------| +| **P0** | **#261** | **Dual Lens Authoring Guide** | **Paula** | **Large** | +| **P0** | **C1** | **Content directory structure design** | **Tyre/Gestalt** | **Medium** | +| P0 | A1 | Apply canonical full names | Paula | Small | +| P0 | A2 | Hael → Naia Tamm rename | Paula | Small | +| P0 | A7 | Smuggler entity attributes | Paula | Small | +| P1 | B2 | MIRROR pattern spec | Gestalt/Gore | Medium | +| P1 | B4 | Smuggler voice + moral arc (subsumes #310) | Gore/Mellanie | Medium | +| P1 | NEW-1 | Apply Gestalt's NPC pattern mapping | Gestalt | Small | +| P1 | NEW-5 | Wow moment content checklist | SI | Small | +| P1 | A10 | Nils Davan stub | Paula | Small | +| P1 | A11 | Triangle 1 fix | Gestalt/Paula | Medium | +| P2 | B3 | PC-as-NPC spec | Gestalt/Paula | Medium | +| P2 | A8 | Shortcode fix | Mellanie | Small | +| P2 | NEW-2 | Drin Tier 2 profile expansion | Paula | Small | +| P2 | NEW-7 | Interaction verb spec | Gestalt | Small | + +**Sprint 3 copy total: 15 tickets (2 P0, 6 P1, 5 P2, 2 small enough to parallelize)** + +### Sprint 3: Know (Server Team — Content-Adjacent, Proposed Additions) + +| Priority | Ticket | Title | Effort | +|----------|--------|-------|--------| +| P1 | C2 | Create content/ directory skeleton | Small | +| P1 | C3 | Content schema definitions | Medium | +| P1 | NEW-9 | Multi-verb interaction architecture — InteractionOptions | Medium | +| P2 | NEW-11 | Pause system — tick rate scaling | Small | + +### Sprint 3: Know (Client Team — Proposed Additions) + +| Priority | Ticket | Title | Effort | +|----------|--------|-------|--------| +| P1 | NEW-10 | Multi-verb interaction — extensible prompt | Medium | +| P2 | NEW-12 | Pause system — client state machine | Small | + +### Sprint 4 (Content Conversion + Authoring Begins) + +| Ticket | Title | Team | Blocked By | +|--------|-------|------|------------| +| NEW-3 | Wiki → YAML conversion (17 NPCs) | copy | C1, C2, C3 | +| C4 | Enum YAMLs from D-035 | server | C2 | +| C5 | Entity-attributes YAML | server | C2 | +| C6 | Pools YAML | server | C2 | +| C7 | Social site template YAMLs | server | C2 | +| C8 | Triangle YAMLs | server | C2 | +| C9 | validate-content CLI | server | C3 | +| C10 | Pre-commit FactId check | ci | — | +| A6 | Smuggler FactId progression text | copy | B4 | +| A12 | Environmental text examples | copy | A3/#319 | +| B5 | PC NPC-format briefs | copy | B3 | +| D4 | News ticker headlines (~20) | copy | — | +| #306 | News ticker content (align with D4) | copy | — | +| D2 | Seed configuration schema | server | — | +| NEW-8 | YAML → RON converter | server | C1 | +| NEW-13 | Line previewer CLI | server | C3, C9 | +| #326 | YAML content loader | server | C1, C2 | +| A3/#319 | Krenn brief + seed.yaml reference | copy | — | + +### Sprint 5+ (Content at Scale) + +| Ticket | Title | Team | Blocked By | +|--------|-------|------|------------| +| NEW-4 | Monologue content pool (~200 lines) | copy | #261, B4 | +| NEW-6 | PC-as-NPC content authoring (~25 items) | copy | B3, B5 | +| B1 | NPC Authoring Style Guide (generator spec) | copy | A3, A4, A7, B2, B4 | +| A4 | Regional style guide / seed.yaml template | copy | — | +| A9 | Thematic Question field (Tier 1 profiles) | copy | B2 | +| #297 | THE FRIEND: Kael content pack | copy | #261 | +| #298 | THE FRIEND: Sera content pack | copy | #261 | +| #190 | Workplace content pack | copy | #261 | +| #191 | Bar content pack | copy | #261 | +| #192 | Smuggling ring content pack | copy | #261 | + +--- + +## 6. Revised Dependency Chain + +``` +SPRINT 3 — Two Parallel Critical Chains +======================================== + +Chain 1: Content Authoring Pipeline +------------------------------------ +#261 (Dual Lens Guide) [P0, CRITICAL PATH ROOT] + ├── blocks B4 (Smuggler voice + moral arc) [Sprint 3] + │ ├── blocks A6 (Smuggler FactId text) [Sprint 4] + │ └── blocks NEW-4 (Monologue pool) [Sprint 5] + ├── blocks #297 (FRIEND: Kael) [Sprint 5] + ├── blocks #298 (FRIEND: Sera) [Sprint 5] + ├── blocks #299 (Opening hook: smuggler) [Sprint 5] + ├── blocks #300 (Opening hook: detective) [Sprint 5] + └── blocks #190, #191, #192 (Content packs) [Sprint 5] + +A7 (Smuggler attributes) [P0, Sprint 3] + ├── blocks B4 (Smuggler voice) [Sprint 3] + └── blocks B3 (PC-as-NPC spec) [Sprint 3] + └── blocks B5 (PC briefs) [Sprint 4] + └── blocks NEW-6 (PC-as-NPC authoring) [Sprint 5] + +Chain 2: Content Architecture Pipeline +--------------------------------------- +C1 (Content dir design) [P0, Sprint 3] + ├── blocks C2 (skeleton) [Sprint 3, server] + │ ├── blocks C4 (enums) [Sprint 4] + │ ├── blocks C5 (entity attrs) [Sprint 4] + │ ├── blocks C6 (pools) [Sprint 4] + │ ├── blocks C7 (templates) [Sprint 4] + │ ├── blocks C8 (triangles) [Sprint 4] + │ ├── blocks NEW-3 (wiki→YAML) [Sprint 4] + │ └── blocks #326 (YAML loader) [Sprint 4] + ├── blocks C3 (schemas) [Sprint 3, server] + │ ├── blocks C9 (validator) [Sprint 4] + │ ├── blocks NEW-3 (wiki→YAML) [Sprint 4] + │ └── blocks NEW-13 (line previewer) [Sprint 4] + └── blocks NEW-8 (RON converter) [Sprint 4] + +Independent Chains (Sprint 3) +------------------------------ +B2 (MIRROR spec) → A9 (Thematic Question) [Sprint 4] +A10 (Nils stub) → A11 (Triangle 1 fix) [Sprint 3] +NEW-9 (InteractionOptions, server) → NEW-10 (Extensible prompt, client) [Sprint 3] +NEW-11 (Tick rate scaling, server) → NEW-12 (Pause state machine, client) [Sprint 3] +``` + +--- + +## 7. Ticket Count Summary + +| Category | Count | Teams | +|----------|-------|-------| +| Wiki fixes (A-series) | 8 | copy | +| Style guides & specs (B-series) | 5 | copy | +| Content directory & schema (C-series) | 10 | copy (1), server (8), ci (1) | +| Design specs (D-series) | 2 | server (1), copy (1) | +| Round 1 new tickets (NEW 1-7) | 7 | copy | +| Round 2 new tickets (NEW 8-13) | 6 | server (4), client (2) | +| **Total new tickets** | **38** | copy (21), server (13), client (2), ci (1), + 1 epic update | +| Existing ticket updates | 10 | — | + +**Delta from Round 1:** +6 tickets (NEW-8 through NEW-13 from lead decisions), +2 server Sprint 3 additions (NEW-9, NEW-11), +2 client Sprint 3 additions (NEW-10, NEW-12). Removed: D3 (killed), A5 (deferred), D1 (deferred). Merged: A3 into #319, #310 into B4. + +--- + +## 8. Risks & Recommendations + +**Risk 1: #261 is still the critical path root.** Every content authoring ticket flows through it. If it slips even one sprint, the entire content pipeline delays. Recommend: assign Paula immediately, time-box to 5 working days, accept "good enough" over "perfect." + +**Risk 2: Sprint 3 copy team has 15 tickets.** That's a lot. But 7 are small tasks (A1, A2, A7, A8, A10, NEW-1, NEW-2) that can be parallelized. The real load is #261, C1, B2, B4, and A11. Recommend: Paula owns #261 + wiki fixes (A1, A2, A7, A10, NEW-2). Gestalt owns C1, NEW-1, B2, A11, NEW-7. Mellanie owns A8, B4. SI owns NEW-5. + +**Risk 3: Server team Sprint 3 is already heavy** (10 existing tickets). Adding C2, C3, NEW-9, NEW-11 is +4. Recommend: C2 and C3 are blocked by C1 anyway — they can start mid-sprint when C1 lands. NEW-9 and NEW-11 are independent and can begin immediately. + +**Risk 4: Content conversion (NEW-3) is a Sprint 4 bottleneck.** Tyre estimates 2-3 full days for 17 NPC conversions. This blocks the server from testing with real data. Recommend: start with the 3 Tier 1 NPCs (Kael, Sera, Naia) as soon as C2 lands, don't wait for all 17. + +--- + +*Proposals only — no tickets created. Awaiting lead approval.* +*Compiled by SI. Source: Round 1 tracking doc (Qatux), all 7 Round 1 responses, lead Round 2 decisions.* diff --git a/docs/workshops/v01-content-scoping/round2-stig.md b/docs/workshops/v01-content-scoping/round2-stig.md new file mode 100644 index 000000000..a3a8b1a53 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-stig.md @@ -0,0 +1,375 @@ +# Round 2 Response: Stig (UI Developer) +## v0.1 Content Scoping Workshop + +--- + +## Task 1: Single-Action Prompt Designed for Multi-Verb Extension + +The lead confirmed: v0.1 ships single-action. Multi-verb is v0.2. I need to design the prompt system so the upgrade path is clean — no throwaway architecture. + +### How it works in v0.1 + +The server sends an `InteractionOptions` payload per entity when the player enters interaction range. v0.1 format: + +``` +InteractionOptions { + entity_id: "kael_davan", + display_name: "Kael Davan", + primary: { verb: "Talk", keybind: "E" }, + secondary: null +} +``` + +The client renders: + +``` + [E] Talk + Kael Davan + [====] +``` + +One prompt. One key. The player approaches, reads, presses. Done. + +**For objects:** +``` +InteractionOptions { + entity_id: "manifest_terminal_03", + display_name: "Manifest Terminal", + primary: { verb: "Examine", keybind: "E" }, + secondary: null +} +``` + +Renders as: +``` + [E] Examine + Manifest Terminal + [====] +``` + +Same pattern. The verb changes. The keybind stays the same. The player learns one input: "E means interact." + +### How it extends to v0.2 (multi-verb) + +The server payload grows: + +``` +InteractionOptions { + entity_id: "kael_davan", + display_name: "Kael Davan", + primary: { verb: "Talk", keybind: "E" }, + secondary: { verb: "Observe", keybind: "F" }, + actions: [ + { verb: "Talk", keybind: "E" }, + { verb: "Observe", keybind: "F" } + ] +} +``` + +The client renders both: + +``` + [E] Talk [F] Observe + Kael Davan + [====] +``` + +**What changes in the client:** +- The prompt renderer reads `actions[]` instead of just `primary` +- Layout goes from single-label to horizontal label row +- Keybind display pluralizes + +**What does NOT change:** +- The prompt's position (world-space, entity-tracked) +- The fade behavior (proximity-triggered) +- The input routing (keybind → server action request) +- The overall visual language (bracket-key + verb) + +**The v0.1 client code:** + +```gdscript +# InteractionPrompt.gd + +func update_prompt(options: InteractionOptions) -> void: + if options == null: + hide() + return + + # v0.1: show primary action only + # v0.2: iterate options.actions[] for multi-label + _label.text = "[%s] %s" % [options.primary.keybind, options.primary.verb] + _name_label.text = options.display_name + + _target_entity = options.entity_id + show() +``` + +The `options.actions[]` field exists in the protocol from day one. The v0.1 client ignores it. The v0.2 client reads it. No protocol change needed. + +### Architectural notes for Dudley/Tyre + +The `InteractionOptions` should be part of the `ObserverSnapshot` update — not a separate message. When the player is near an entity, the snapshot includes the interaction payload. When they move away, it's `null`. The client never requests interaction options — they arrive passively as part of perception. + +This means the server's `InteractionResolver` system evaluates available actions every tick for entities in the player's interaction radius. In v0.1 that's a simple lookup (NPC → Talk, Object → Examine). In v0.2 it evaluates relationship state, knowledge, and context to determine the full action set. + +**For Gestalt's Examine-vs-Talk question:** The server models both verbs internally from v0.1 onward. It just surfaces only the primary one to the client. If the server determines that "Observe" is more relevant than "Talk" at a given moment (e.g., player has already talked to this NPC this shift, or NPC is currently in an anomalous state), the server can swap the primary. Single-action doesn't mean single-verb-forever — it means the server picks the best one. + +--- + +## Task 2: Pause System State Machine + +Three states. Clean transitions. No ambiguity. + +### States + +``` +RUNNING ← normal gameplay, full sim speed +OVERLAY_ACTIVE ← UI overlay open (knowledge panel, dialogue), 50% sim speed +PAUSED ← spacebar pause, 0% sim speed +``` + +### Transitions + +``` + [Tab] [Space] + RUNNING ──────────────> OVERLAY_ACTIVE ──────────────> PAUSED + ^ | | + | [Tab] | [Space] | + +────────────────────────+ | | + | | | + +─────────────────────────────────────+ | + | [Space] | + +─────────────────────────────────────────────────────+ +``` + +### Rules + +| Input | From RUNNING | From OVERLAY_ACTIVE | From PAUSED | +|-------|-------------|--------------------|-| +| **[Space]** | → PAUSED | → PAUSED | → previous state (RUNNING or OVERLAY_ACTIVE) | +| **[Tab]** (knowledge panel) | → OVERLAY_ACTIVE | → RUNNING (closes panel) | → PAUSED + OVERLAY_ACTIVE (panel opens, stays paused) | +| **[E]** (interaction/dialogue) | → OVERLAY_ACTIVE (if dialogue starts) | stays OVERLAY_ACTIVE | no effect | +| **[Esc]** | no effect | → RUNNING (closes overlay) | → previous state | + +**Key behaviors:** + +1. **Spacebar is king.** It always toggles to/from PAUSED. Period. No other input overrides spacebar. If the player is in dialogue and hits space, the game pauses with the dialogue still visible. + +2. **Overlay = 50% speed, not 0%.** When the knowledge panel is open, the world still breathes. NPCs still move (slowly). Time still passes (slowly). This prevents the knowledge panel from becoming a freeze-frame intelligence tool. The player can check their notes, but the world doesn't wait. + +3. **Spacebar from OVERLAY_ACTIVE goes to PAUSED, not RUNNING.** If I'm reading my knowledge panel and I want the world to stop completely, I hit space. I don't have to close the panel first. Conversely, hitting space from PAUSED with an overlay open returns to OVERLAY_ACTIVE (50% speed with panel still visible), not RUNNING. + +4. **Dialogue is an overlay.** When in conversation, sim runs at 50%. NPCs outside the conversation keep moving. This means: while you're talking to Kael, Sera might leave the bar. You could miss it. That's the game. If you want to freeze everything, pause. + +5. **Multiple overlays don't stack speed reductions.** If dialogue is active (50%) and the player opens the knowledge panel (also 50%), sim stays at 50%. Not 25%. + +### Implementation (client-side) + +```gdscript +# PauseManager.gd +enum SimState { RUNNING, OVERLAY_ACTIVE, PAUSED } + +var _state: SimState = SimState.RUNNING +var _pre_pause_state: SimState = SimState.RUNNING +var _active_overlays: int = 0 # count of open overlays + +func _input(event: InputEvent) -> void: + if event.is_action_pressed("pause"): # spacebar + _toggle_pause() + elif event.is_action_pressed("knowledge_panel"): # tab + _toggle_knowledge_panel() + +func _toggle_pause() -> void: + if _state == SimState.PAUSED: + _set_state(_pre_pause_state) + else: + _pre_pause_state = _state + _set_state(SimState.PAUSED) + +func _toggle_knowledge_panel() -> void: + if _knowledge_panel_open: + _close_knowledge_panel() + else: + _open_knowledge_panel() + +func _open_knowledge_panel() -> void: + _knowledge_panel_open = true + _active_overlays += 1 + if _state == SimState.RUNNING: + _set_state(SimState.OVERLAY_ACTIVE) + # if PAUSED, stay PAUSED — panel opens visually but sim doesn't resume + +func _close_knowledge_panel() -> void: + _knowledge_panel_open = false + _active_overlays -= 1 + if _active_overlays == 0 and _state == SimState.OVERLAY_ACTIVE: + _set_state(SimState.RUNNING) + +func _set_state(new_state: SimState) -> void: + _state = new_state + match new_state: + SimState.RUNNING: + Engine.time_scale = 1.0 + SimState.OVERLAY_ACTIVE: + Engine.time_scale = 0.5 + SimState.PAUSED: + Engine.time_scale = 0.0 +``` + +### Question for Dudley + +Does the server need to know about pause state? If the client sets `Engine.time_scale = 0.0`, does the server keep ticking and the client just stops rendering? Or does the client need to signal the server to pause its tick loop? + +For single-player (v0.1), I think the client controls this entirely. The server runs as a subprocess — if the client freezes its consumption of server ticks, the server's output buffer fills and it naturally stalls. But this depends on the IPC design. Dudley should confirm. + +For the 50% overlay speed: does the client request half-speed ticks from the server, or does it receive full-speed ticks and render every other one? I lean toward the client setting the tick request rate. Cleaner. + +--- + +## Task 3: Monologue Display Constraints (Confirmed) + +Mellanie needs hard numbers. Here they are. + +### Display constraints + +| Parameter | Value | Rationale | +|-----------|-------|-----------| +| **Max characters** | 160 | Two lines at ~80 chars/line at reference font size. Fits 40% viewport width. | +| **Max lines** | 2 | One breath. Monologue competes with gameplay — the player is moving, looking, deciding. Two lines is a glance. Three is a paragraph. | +| **Display duration (base)** | 4.0 seconds | Long enough to read 160 characters at comfortable pace (~40 chars/sec reading speed for on-screen text). | +| **Display duration (short, <80 chars)** | 3.0 seconds | Short lines don't need 4 seconds. Scale linearly. | +| **Display duration (urgent chime)** | 5.0 seconds | Anomaly observations deserve an extra beat. The player needs time to register that something changed. | +| **Fade in** | 0.3 seconds | Fast enough to not feel laggy. Slow enough to not pop. | +| **Fade out** | 0.5 seconds | Slightly slower than fade-in. The thought lingers. | +| **Interruption fade** | 0.15 seconds | When a new monologue replaces the current one, the old one exits fast. No collision. | +| **Cooldown between lines** | 2.0 seconds minimum | Two monologues back-to-back feel like a data dump. The gap is where the player absorbs. | +| **Max queue depth** | 1 | If two triggers fire simultaneously, the higher-priority one displays and the other is dropped. No stacking, no scroll-back. The character's mind moves on. | + +### Priority ordering (when two triggers fire at once) + +``` +1. observe_anomaly ← the game is telling you something important +2. witness_interaction ← the game is telling you something happened +3. discover_evidence ← you found something +4. post_conversation ← reaction to what just happened +5. hear_sound ← environmental awareness +6. observe_npc ← identification / emotional reaction +7. enter_location ← atmospheric +8. return_visit ← atmospheric +9. time_idle ← lowest — reflective, only when nothing else is happening +``` + +`observe_anomaly` always wins. If THE FRIEND's contradiction fires at the same time as an `enter_location` line, the contradiction shows. The atmospheric line is gone. The player will never know it existed. That's fine — the important thing happened. + +### What Mellanie should write to + +**Target:** 80-120 characters for most lines. 160 is the hard maximum — use it rarely. The best monologue lines are 40-80 characters. One punchy thought. + +Examples from the authoring guide, with character counts: + +- `"Morning shift. Recycled air and cargo lubricant. Home sweet home."` — 66 chars. Perfect. One line, one breath. +- `"Kael's here. Good — I was starting to worry."` — 47 chars. Even better. Fast, emotional. +- `"Dock worker Davan — lattice activity spiked. Three pings in two minutes. Expecting a message? Or checking for surveillance?"` — 124 chars. This pushes into line 2 but earns it — the detective's analytical voice needs the full thought. +- `"He looked left. He always looks left when he's making something up. Two years I've known that tell. Kael is lying to me."` — 121 chars. This is a climax moment. Two lines justified. + +**Rule of thumb:** If it fits on one line (~80 chars), it should be one line. Two lines are for moments that matter. + +### Font specification (for reference) + +Monologue text at reference resolution (1920x1080): +- **Font:** Clean sans-serif (Godot default or a custom face — Araminta's call) +- **Size:** 18-20px equivalent +- **Line height:** 1.4x +- **Max width:** 40% of viewport = ~768px at 1920 wide +- **At 18px, ~80 characters fit in 768px** with standard proportional font metrics + +This gives comfortable readability at typical viewing distance. Not tiny, not shouting. The text whispers. + +### Monologue during dialogue + +When a dialogue panel is active, monologue can still fire (e.g., `observe_anomaly` while in conversation). The monologue display shifts up to sit above the dialogue panel: + +``` +Normal state: ++------------------------------------------------------------------+ +| | +| GAME WORLD | +| | +| +--monologue----------------------------+ | +| | Kael's here. Good. | | +| +----------------------------------------+ | +| | ++------------------------------------------------------------------+ + +During dialogue: ++------------------------------------------------------------------+ +| | +| GAME WORLD | +| | +| +--monologue----------------------------+ | +| | That's not anyone from our rotation. | | +| +----------------------------------------+ | ++------------------------------------------------------------------+ +| KAEL DAVAN | +| "Just a friend. Don't worry about it." | +| [Continue] [End] | ++------------------------------------------------------------------+ +``` + +The monologue sits in the world layer. The dialogue sits in the HUD layer. They don't overlap. The player reads dialogue first (larger, centered, paneled), catches monologue peripherally (smaller, left, ghostly). Two voices — one external, one internal — simultaneously. + +This is critical for Wow Moment #3. The contradiction fires monologue WHILE the player might be in conversation. The internal "What the hell?" happens at the same moment as the external smooth deflection. The UI must support both without making the player choose which to read. + +--- + +## Responses to Round 1 Open Questions (Answered by Lead Decisions) + +### D-04 (Examine vs Talk) — Resolved + +Lead decision: conceptually separate, v0.1 ships single-action. Covered in Task 1 above. The server models both verbs; the client surfaces one. Extensible to multi-verb in v0.2 without protocol changes. + +### D-05 (Knowledge Panel Pause) — Resolved + +Lead decision: 50% sim speed for overlays, spacebar always pauses. Covered in Task 2 above. + +### Client text delivery — Resolved (A-12, proposed D-048) + +Server sends full text, client renders. Confirmed by lead and tracking doc. No local content loading on the client. + +--- + +## Updated UI Bill of Materials (incorporating Round 2 decisions) + +| System | Scenes | Scripts | Shaders | Priority | Notes | +|--------|--------|---------|---------|----------|-------| +| **Monologue renderer** | 1 | 1 | 0 | P0 | Fade, timing, priority queue, character tint | +| **Interaction prompt** | 1 | 1 | 0 | P0 | World-space tracking, single-action (extensible to multi) | +| **Dialogue panel** | 1 | 1 | 0 | P0 | Semi-transparent, text advance, exit handling | +| **Entity color system** | 0 | 1 | 1 | P0 | D-033 palette, 0.5s fade transitions | +| **Pause manager** | 0 | 1 | 0 | P0 | State machine: RUNNING / OVERLAY / PAUSED | +| **HUD bar** | 1 | 1 | 0 | P1 | Location + time phase. Placeholder text in v0.1 | +| **Minimap** | 1 | 1 | 1 | P1 | Insert-style, player dot + area bounds | +| **Knowledge panel** | 1 | 1 | 0 | P1 | Right-side overlay, People/Places/Observations | +| **News ticker** | 1 | 1 | 0 | P1 | World-space scrolling text on bar display entity | +| **Fog rendering** | 0 | 0 | 1 | P0 | Shared with server team (shadowcast → visual) | +| **TOTAL** | **7** | **9** | **3** | | | + +Reduced from 9 scenes to 7 (pause manager and entity colors don't need dedicated scenes). Added 1 shader (fog rendering confirmed as client-side). + +--- + +## Dependencies I'm Waiting On + +| From | What I Need | Blocks | +|------|-------------|--------| +| **Dudley** | IPC message format — specifically `ObserverSnapshot` structure for interaction options, monologue payloads, dialogue payloads. | Interaction prompt, dialogue panel, monologue renderer | +| **Dudley** | Pause architecture — does client control sim speed, or does it signal the server? | Pause manager | +| **Tyre** | `ObserverSnapshot` v3 protocol spec (Round 2 task for Tyre). | Everything that reads server state | +| **Araminta** | Font selection, diegetic styling direction for knowledge panel. | Knowledge panel visual treatment | +| **Mellanie** | First monologue content (even 5-10 lines) for integration testing. | Monologue renderer testing | + +I can build all UI scaffolding with mock data. But integration testing needs real payloads from the server and real text from Mellanie. The sooner those arrive, the sooner we know if the display constraints actually feel right at runtime. + +--- + +*Spacebar always pauses. Everything else fades in when needed and disappears when it's done.* diff --git a/docs/workshops/v01-content-scoping/round2-tracking.md b/docs/workshops/v01-content-scoping/round2-tracking.md new file mode 100644 index 000000000..5371daf17 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-tracking.md @@ -0,0 +1,594 @@ +# Round 2 Tracking — Qatux (Documenter) +## v0.1 Content Scoping Workshop — CLOSING ROUND + +**Status:** All 7 responses received (Gestalt, Paula, Tyre, Dudley, Stig, Mellanie, SI). Workshop complete. +**Date:** 2026-02-12 + +--- + +## 1. Lead Decisions (Round 2 Brief) + +Four lead decisions were issued at the start of Round 2, resolving the major disagreements from Round 1: + +| ID | Decision | Round 1 Disagreement Resolved | +|----|----------|-------------------------------| +| **D-NPC** | Gestalt's mechanical mapping is canonical for v0.1. Paula's emotional/narrative layer becomes v0.2 annotations. | D-02 (10 of 17 NPC mapping disagreements) | +| **D-FORMAT** | YAML for content files. RON converter available as optional build-time tool. | D-01 (YAML vs RON) | +| **D-INTERACT** | Single context-sensitive action for v0.1. Multi-verb architecture modeled underneath from day one. | D-04 (Examine vs Talk: separate or combined) | +| **D-PAUSE** | Spacebar always pauses (0% speed). UI overlays (knowledge panel, dialogue) trigger 50% sim speed. 3-state system. | D-05 (Knowledge panel pause behavior) | + +**Additionally resolved by lead direction:** +- **D-03 (EntityKnowledge keys):** 16 total keys. 4 new generic role-perspective keys. `trust_read` merged into existing `trust_level`. `leverage_held` absorbs `secret_held`. `secret_confidence` renamed to `leverage_confidence`. (See Section 5.) +- **R2-06 (PC voice registers):** Assigned to Mellanie as Round 2 task. +- **R2-07 (Triangle fork cascade):** Self-contained forks confirmed. +- **R2-09 (ObserverSnapshot v3):** Assigned to Tyre as Round 2 task. + +**Closing round additions (post-Round 2 responses):** +- **Q-012 resolved:** 16 EntityKnowledge keys finalized (see Section 5). +- **Q-013 resolved:** Server-authoritative pause. Client is a dumb renderer. NEW-12 (client pause state machine) killed. +- **Q-016 resolved:** `actions[]` → `verbs[]` rename confirmed across server code, client code, YAML content, wiki docs, decision files. Folded into NEW-7. +- **Process rule:** No ticket merges across domain teams. Shared epics are fine; each team gets its own ticket with its own definition of done. + +--- + +## 2. Round 1 Disagreement Resolutions + +### D-02 Resolved: NPC Pattern/Motivation Mapping + +Gestalt reviewed all 10 disagreements from Round 1 and **changed his position on 6 of 10**, adopting Paula's reads. Both agents converged on a single canonical mapping. + +| NPC | Round 1 Gestalt | Round 1 Paula | Round 2 Final | Who Prevailed | Gestalt's Rationale for Change | +|-----|----------------|--------------|---------------|---------------|-------------------------------| +| **Maret** (pattern) | SYSTEM | WITNESS | **WITNESS** | Paula | Story function > job description | +| **Maret** (motivation) | WITNESS | SKEPTIC | **SKEPTIC** | Paula | WITNESS+SKEPTIC creates compositional tension | +| **Resha** (motivation) | SKEPTIC | CIVILIAN | **CIVILIAN** | Paula | Innocent disruption > deliberate questioning | +| **Harek** (pattern) | GHOST | ANCHOR | **ANCHOR** | Paula | Harek is physically present; contentment, not absent power | +| **Drin** (pattern) | REMNANT | SYSTEM | **SYSTEM** | Paula | Station too young for REMNANT; SYSTEM+TURNCOAT = institutional rot | +| **Sabel** (pattern) | THRESHOLD | GHOST | **GHOST** | Paula | Off-station = absent power, not gatekeeper | +| **Olin** (motivation) | SKEPTIC (dual) | CIVILIAN | **CIVILIAN** | Paula | Dropped dual designation; 1 pattern + 1 motivation per NPC | +| **Voss** (motivation) | OPERATOR | HANDLER | **OPERATOR** | Gestalt | Voss RUNS the schedule; middle management operating machinery | +| **Devra** (motivation) | HANDLER | OPERATOR | **HANDLER** | Gestalt | Devra conveys Nils's strategy; pressure from above | +| **Sess** (pattern) | ANCHOR | NOBODY | **ANCHOR** | Gestalt | NOBODY deferred to v0.2; ANCHOR at T3 = contentment as texture | + +**Paula's response:** Accepted the versioned approach. Annotated all 10 disagreement NPCs with v0.2 emotional layer descriptions. Each annotation specifies what content the v0.2 motivation/pattern unlocks (~30-50 lines per NPC). + +### D-01 Resolved: YAML Format + +**Dudley accepts YAML.** One-line change: `serde_yaml::from_str()` replaces `ron::from_str()`. Multi-line YAML blocks (`|`) improve author experience. RON crate retained for potential future use. + +### D-04 Resolved: Examine vs Talk + +**Converged on: conceptually separate, v0.1 ships single action.** The server models both verbs internally; priority resolution determines which surfaces as the `[E]` prompt. Post-contradiction, Examine can become primary (detective investigating a flagged NPC). The v0.2 client reads the full action list — no server changes needed. + +### D-05 Resolved: Pause System + +**3-state system adopted by all respondents.** Gestalt, Stig, and Dudley each independently designed compatible state machines. + +--- + +## 3. Definitive v0.1 NPC Mapping + +The canonical mapping, produced by Gestalt with 6 changes from Paula's input: + +| # | Name | Tier | Pattern | Motivation | Composition Read | +|---|------|------|---------|-----------|-----------------| +| 1 | **Kael Davan** | **T1** | **FRIEND** | **OPERATOR** | Trust anchor running ring cargo | +| 2 | **Sera Venn** | **T1** | **FRIEND** | **WITNESS** | Trust anchor carrying unreported evidence | +| 3 | **Naia Tamm** | **T1*** | **MIRROR** | **CIVILIAN** | Honest transparency, no secrets | +| 4 | **Voss** | T2 | **SYSTEM** | **OPERATOR** | Institutional authority operating the schedule | +| 5 | **Lera Sessik** | T2 | **ANCHOR** | **OPERATOR** | Contentment embodied, running a bar on willful ignorance | +| 6 | **Torek Lintar** | T2 | **CATALYST** | **CIVILIAN** | Spending disrupts equilibrium | +| 7 | **Devra** | T2 | **THRESHOLD** | **HANDLER** | Ring gatekeeper, manages operations from above | +| 8 | **Maret Korr** | T2 | **WITNESS** | **SKEPTIC** | Accidentally observed discrepancies, doubts the numbers | +| 9 | **Resha** | T2 | **CATALYST** | **CIVILIAN** | New hire forcing defensiveness with innocent questions | +| 10 | **Drin** | **T2** | **SYSTEM** | **TURNCOAT** | Inspector whose compromise corrupts the institution | +| 11 | **Renn** | T2 | **THRESHOLD** | **OPERATOR** | Courier controlling cargo flow | +| 12 | **Pell** | T2 | **CATALYST** | **TURNCOAT** | Wavering member forcing potential crisis | +| 13 | **Harek** | T2 | **ANCHOR** | **HANDLER** | Complacent contentment whose comfort creates a tool | +| 14 | **Sess** | T3 | **ANCHOR** | **CIVILIAN** | Quiet contentment behind the bar | +| 15 | **Olin** | T3 | **CATALYST** | **CIVILIAN** | New arrival forcing evaluation | +| 16 | **Sabel** | T3 | **GHOST** | **OPERATOR** | Off-station absent power running the supply chain | +| 17 | **Tav** | T3 | **SYSTEM** | **CIVILIAN** | Ring lookout following procedure | + +**Off-stage:** Nils Davan — GHOST + HANDLER + +**Distribution:** 8 of 9 patterns present (REMNANT correctly absent; NOBODY deferred to v0.2). All 6 motivations present. CIVILIAN at 6/17 (35%) matches D-029 noise floor (~30%). + +--- + +## 4. Key Architecture Convergences + +### 4.1 Multi-Verb Interaction Architecture + +Tyre, Dudley, and Stig independently produced compatible designs: + +| Agent | Contribution | Compatible? | +|-------|-------------|-------------| +| **Tyre** | `AvailableActions` per entity with priority-sorted `ActionOption` list; `ActionKind` enum; `InteractPrimary` vs `InteractWith` PlayerAction variants | Canonical design | +| **Dudley** | `EntityInteractions` with `AvailableAction` list; server-side `rank_actions()` priority system; `PlayerAction::InteractWith` for v0.2 | Compatible — naming differs, structure identical | +| **Stig** | `InteractionOptions` with `primary`/`secondary` fields; v0.2 extension reads `actions[]` array; world-space prompt renderer | Client-side compatible | + +**Minor naming divergence:** Tyre uses `AvailableActions`/`ActionOption`, Dudley uses `EntityInteractions`/`AvailableAction`. These are the same concept. + +**Lead confirmation (closing round):** `actions[]` → `verbs[]` rename confirmed. Scope: server code, client code, YAML content files, wiki docs, decision files. Folded into NEW-7 ticket description. + +**Key design principles (unanimous):** +- Server computes N available verbs per entity every tick +- v0.1 client shows only priority-0 verb (single `[E]` prompt) +- v0.2 client renders full list — no server changes needed +- Context-sensitive priority (post-contradiction: Examine rises above Talk) +- `InteractionOptions`/`AvailableActions` embedded in ObserverSnapshot, not separate message +- Server is authoritative; client never queries content files + +### 4.2 Pause System (3-State) + +| State | Speed | Trigger | All 3 Agree? | +|-------|-------|---------|-------------| +| **Normal** | 100% (1.0) | Default | Yes | +| **Overlay** | 50% (0.5) | Knowledge panel, dialogue | Yes | +| **Paused** | 0% (0.0) | Spacebar (always) | Yes | + +**Gestalt:** Multiplicative toggle model (`effective_speed = if pause_override { 0.0 } else { base_speed * ui_modifier }`). Multiple overlays don't stack (50% stays 50%). v0.2 extension: panel + dialogue could = 25%. + +**Stig:** GDScript `PauseManager` with `SimState` enum, `_pre_pause_state` tracking for resume, overlay count tracking. Spacebar from OVERLAY_ACTIVE goes to PAUSED (not RUNNING). Questions for Dudley: does server need pause signals, or does client control? + +**Dudley:** `SimSpeed` enum resource with `speed_stack` (priority-based). `SimSpeed::Overlay` ticks every 2nd frame. Server is authoritative over tick advancement. `sim_speed: f32` field in ObserverSnapshot tells client the current rate. + +**Resolved (closing round):** Server-authoritative pause. Tick rate is a server simulation concern. Client is a dumb renderer — it renders ObserverSnapshots at whatever rate the server sends them. Client sends a pause IPC command to the server; that's it. Stig's `PauseManager` GDScript is reduced to sending IPC commands and reading `sim_speed` from the snapshot for visual feedback. **NEW-12 (client pause state machine) killed** — client doesn't manage time. + +### 4.3 ObserverSnapshot v3 + +Tyre and Dudley independently produced v3 definitions. They are structurally identical: + +| Field | Tyre's Proposal | Dudley's Proposal | Status | +|-------|----------------|-------------------|--------| +| `sim_speed: f32` | Yes (default 1.0) | Yes | Agreed | +| `nearby_interactions` | `Vec` | `Vec` | Agreed (naming differs) | +| `active_dialogue` | `Option` | `Option` | Agreed | +| `monologue` | `Vec` | `Vec` | Agreed | +| `overheard` | `Vec` | `Vec` | Agreed (naming differs) | +| `knowledge_updates` | `Vec` | Not in Dudley's R2 snapshot | Tyre adds delta updates | +| `examine_result` | `Option` | Not in Dudley's R2 snapshot | Tyre adds | +| `ticker_headlines` | `Vec` | Not in Dudley's R2 snapshot | Tyre adds | + +**Wire size estimate (Tyre):** v3 adds ~50-1500 bytes per tick on top of v2's 2-8 KB. Total: ~2-10 KB/tick at 10 tps = 20-100 KB/s. Well within IPC budget. + +**Backward compatibility:** MessagePack serializes by field name. New fields use `#[serde(default)]`. v2 client reading v3 snapshot ignores unknown fields. v3 client reading v2 snapshot gets empty defaults. + +### 4.4 Content Directory + Schema + +Tyre produced the definitive directory tree and JSON Schema definitions: + +**Directory structure:** `content/content.yaml` (manifest) + `content/schema/` (JSON Schema files) + `content/global/` (facts, factions, enums, entity-schema) + `content/districts/sova-transit/` (npcs/, locations/, triangles/, dialogue/, monologue/, routines/). + +**Schemas defined:** +- `npc-profile.schema.json` — Full NPC profile with tier-conditional fields, pattern/motivation enums, axes, tells, access tiers, trust levels, friend_arc, triangle membership +- `dialogue-pool.schema.json` — Tagged dialogue lines per D-028/D-035. Role + location based, with access/trust/situation gating and knowledge_grants +- `monologue-pool.schema.json` — Character-partitioned (D-032). Trigger + prerequisite (AND-only) + priority + cooldown. 160 char max per Stig + +**Validation pipeline (3-tier):** +1. Authoring time: IDE with JSON Schema autocomplete +2. Build time (`make content-validate`): JSON Schema + cross-reference validation +3. Load time: serde_yaml deserialization + semantic validation (StableId assignment, relationship wiring) + +### 4.5 YAML + RON Converter + +| Aspect | Tyre | Dudley | Status | +|--------|------|--------|--------| +| Content format | YAML | YAML (accepted) | Agreed | +| RON converter location | `tooling/content-tools/` | `tooling/content-converter/` | Minor naming difference | +| When converter runs | Build-time optional | Build-time optional, low priority | Agreed | +| Development loading | YAML direct | YAML direct | Agreed | +| Production loading | RON cache preferred, YAML fallback | YAML (RON is insurance) | Agreed — YAML in v0.1, RON as future optimization | + +Tyre provides dual-format loader (~20 lines) with `load_with_cache()` fallback pattern. `content/.cache/` is gitignored. + +--- + +## 5. EntityKnowledge Keys — RESOLVED: 16 Keys + +Both Gestalt and Paula independently arrived at the same conclusion: **generic perspective keys, not per-archetype keys.** This resolves D-03 from Round 1. + +### Convergence (Round 2 Responses) + +| Point | Gestalt | Paula | Converged? | +|-------|---------|-------|-----------| +| Per-archetype keys are unsustainable | Yes (32 archetype-specific + 14 shared = 46 total) | Yes (same conclusion) | **Yes** | +| Generic keys work for all 8 archetypes | Yes | Yes | **Yes** | +| Interpretation lives in content, not schema | Yes (monologue pools provide character-specific meaning) | Yes (archetype brief + voice register + moral arc) | **Yes** | +| `social_debt` deferred to v0.2 | Yes | Accepts | **Yes** | +| `loyalty_conflict` deferred to v0.2 | Yes (inferable from `behavior_flags` + `reliability`) | Accepts | **Yes** | + +### Final Resolution (Closing Round Lead Decision) + +**16 total EntityKnowledge keys.** 12 existing shared keys + 4 new generic role-perspective keys. Key merges and renames applied: + +#### 4 New Role-Perspective Keys (observer→target, all archetypes) + +| Key | Value Enum | What It Stores | +|-----|-----------|---------------| +| `risk_assessment` | `none`, `low`, `moderate`, `high`, `critical` | How this NPC threatens the observer's position (exposure, obstruction, liability, competition — archetype-dependent) | +| `loyalty_assessment` | `solid`, `dependable`, `uncertain`, `wavering`, `hostile` | How predictably this NPC serves the observer's interests | +| `position_integrity` | `solid`, `thin`, `cracking`, `blown`, `N/A` | Status of active deception involving this NPC (cover, reputation, facade) | +| `moral_weight` | `innocent`, `peripheral`, `complicit`, `compromised`, `willing` | Observer's subjective moral assessment within their ethical framework | + +**Key names adopt Paula's naming** (`risk_assessment` over `threat_to_position`, `position_integrity` over `facade_status`). **Value enums adopt Gestalt's archetype-neutral vocabulary.** + +#### Key Merges and Renames + +| Action | Old | New | Rationale | +|--------|-----|-----|-----------| +| **Merge** | `trust_read` | `trust_level` (existing) | "Read is the action, level is the end state." Same key, per-character interpretation. | +| **Absorb** | `secret_held` | `leverage_held` (existing, renamed) | "Secret is a subtype of leverage." Value space expanded: secrets, debts, promises, obligations, compromising positions. | +| **Rename** | `secret_confidence` | `leverage_confidence` | Follows `secret_held` → `leverage_held` rename. | +| **Defer** | `social_debt` | v0.2 | 30-minute session doesn't develop favor economy. | +| **Defer** | `loyalty_conflict` | v0.2 | Inferable from `behavior_flags` + `loyalty_assessment`. | +| **Not added** | `tell_observed`, `contradiction_flagged` | — | Tracked in `behavior_flags` (existing). No 7th key needed. | + +#### Final Key Inventory (16 total) + +| # | Key | Category | New/Existing | +|---|-----|----------|-------------| +| 1 | `name` | Identity | Existing | +| 2 | `role` | Identity | Existing | +| 3 | `faction` | Identity | Existing | +| 4 | `species` | Identity | Existing | +| 5 | `location` | Spatial | Existing | +| 6 | `routine_pattern` | Behavioral | Existing | +| 7 | `trust_level` | Relational | Existing (absorbs `trust_read`) | +| 8 | `leverage_held` | Relational | Existing (renamed from `secret_held`, expanded value space) | +| 9 | `leverage_confidence` | Relational | Existing (renamed from `secret_confidence`) | +| 10 | `relationship_state` | Relational | Existing | +| 11 | `behavior_flags` | Behavioral | Existing (tracks tells + contradictions) | +| 12 | `known_associates` | Social | Existing | +| 13 | `risk_assessment` | **Role-perspective** | **NEW** | +| 14 | `loyalty_assessment` | **Role-perspective** | **NEW** | +| 15 | `position_integrity` | **Role-perspective** | **NEW** | +| 16 | `moral_weight` | **Role-perspective** | **NEW** | + +**This scales to all 8 archetypes without modification.** Interpretation lives in content (monologue pools, voice registers, archetype briefs), not schema. The smuggler reads `risk_assessment: "high"` as exposure risk; the detective reads it as obstruction risk; the administrator reads it as political liability. Same key, same enum, different meaning. + +--- + +## 6. FriendArc Phase Transitions (Dudley) + +Dudley produced the definitive 4-phase state machine with explicit trigger code: + +| Phase Transition | Trigger | Content Dependency | +|-----------------|---------|-------------------| +| **Warmth → Trust** | Interaction count (>=3) + time elapsed (>=600 ticks / 1 game-hour) | 5-8 casual dialogue + 3-5 positive monologue | +| **Trust → Doubt** | Knowledge threshold — observer learns a doubt-triggering fact at KnowsOf+ | 5-8 deeper dialogue + 3-5 questioning monologue | +| **Doubt → Conflict** | Direct observation of contradiction (`contradiction_discovered` flag) | **1 critical monologue line** (wow moment #3) + 8-12 confrontation dialogue | + +**Contradiction spatial staging:** Server schedules NPC routine deviation (Kael in Corridor B-7 during shift transition). The `check_contradiction_observation` system checks: (1) FRIEND at contradiction location, (2) within time window, (3) bonded character has Direct confidence (LOS). All 5 state changes must happen atomically within one tick: knowledge graph updated, FriendArc phase advanced, RelationshipState changed (Friendly → PersonOfInterest), monologue triggered (urgent), entity color shifted (green → amber). + +**Content required per FRIEND NPC:** ~35-46 authored lines minimum. Both FRIEND NPCs: ~70-92 lines. Aligns with D-034 estimate. + +**New component:** `InteractionLog` — BTreeMap tracking (target_stable_id, tick) pairs. Supports Warmth→Trust transition and future NOBODY tier promotion. + +--- + +## 7. PC Voice Registers (Mellanie) + +Mellanie produced canonical voice register specifications for both playable characters: + +### Smuggler — Internal Voice Register + +**One-line summary:** Watches people, not systems. Thinks in fragments. Worries about friends practically, physically, with the knot in the stomach. + +| Parameter | Value | +|-----------|-------| +| Sentence length | Short. Fragments welcome. Rarely >2 sentences. | +| Vocabulary | Concrete, physical, sensory. Trade jargon without explanation. | +| Emotional register | Feeling-first, analysis second. | +| Relationship mode | First-name basis. People defined by connection, not role. | +| Observation priority | People > environment > objects. | +| Self-reference | Rare. Through physical sensation, not emotional vocabulary. | +| Humor | Dry, situational, understated. Dark when stressed. | + +**5 voice anchors provided:** Baseline warm, Operational, Concerned, Hurt, Atmospheric. +**4 anti-patterns defined:** Too clinical, institutional vocabulary, analytical distance, explaining known facts. + +### Detective — Internal Voice Register + +**One-line summary:** Categorizes first, feels second. Thinks in complete sentences. Institutional training runs in the background. + +| Parameter | Value | +|-----------|-------| +| Sentence length | Complete but not long. More structured than smuggler. | +| Vocabulary | Institutional layer over plain speech. "Noted," "flagged," "filed it." | +| Emotional register | Analysis-first. Personal breaks through as notable cracks. | +| Relationship mode | Surname-first for targets. First-name for allies. The shift IS a marker. | +| Observation priority | Patterns > people > environment. Counts things, tracks frequencies. | +| Self-reference | More common, always through professional lens. | +| Humor | Wry, observational. Appreciates irony at a distance. | + +**5 voice anchors provided.** 4 comparison examples (same trigger, two characters) demonstrating voice distinctness. + +### Display Constraints (Mellanie confirms Stig's R1 proposal) + +| Constraint | Value | +|-----------|-------| +| Max length | ~160 characters (hard ceiling) | +| Target range | 80-120 characters (most lines), 40-80 (ideal) | +| Max visual lines | 2 | +| Word target | 10-18 words (14 average) | +| Display time | 4-6 seconds (length-adjusted) | +| Character tint | Smuggler: amber-white `#f0e0c8`, Detective: blue-white `#c8d8f0` | + +--- + +## 8. THE FRIEND Content Pack — Kael Davan (Mellanie) + +Mellanie mapped the complete Kael Davan content pack structure: + +| Content Type | Lines | Phase Distribution | +|-------------|-------|-------------------| +| Kael dialogue | 36 | Terminal: 17, Bar: 13, Corridor: 6 | +| Smuggler monologue about Kael | 18 | 5 phases (Comfort 4, First Crack 4, Contradiction 3, Confrontation 3, Contaminated Trust 4) | +| Detective monologue about Kael | 10 | Identification 2, Behavioral 3, Suspicion 3, Informant 2 | +| Tell observation lines | 8 | Lattice 2, Shortened 2, Looks-left 2, Spatial 2 | +| Trust-gated lines | 13 | Insider 6, Close colleague 4, Secret hints 3 | +| Unprompted lines | 6 | Early warmth 3, Late warnings 3 | +| **PACK TOTAL** | **91** | Within D-034 range (70-100) | + +**Phase distribution analysis:** +- Phase 1 (Comfort): 21 lines (23%) — heavy on dialogue, light on monologue +- Phase 2 (First Crack): 13 lines (14%) — tells ramp up +- Phase 3 (Contradiction): 7 lines (8%) — fewest lines, highest stakes +- Phase 4 (Confrontation): 16 lines (18%) — dialogue-heavy +- Phase 5 (Contaminated Trust): 16 lines (18%) — voice discipline critical +- Cross-phase: 16 lines (18%) + +**Sample YAML lines provided** for each phase demonstrating prerequisite progression. Format aligns with Tyre's `monologue-pool.schema.json`. + +**695 total validation (Mellanie clarifies):** 695 is the authored floor. Does not include generation-expanded output (D-028: "write 10, generate 40" for dialogue). Does not include engine-generated content (routine greetings, time-of-day variations). + +--- + +## 9. UI Specifications Confirmed (Stig) + +### Updated Bill of Materials + +| System | Scenes | Scripts | Shaders | Priority | +|--------|--------|---------|---------|----------| +| Monologue renderer | 1 | 1 | 0 | P0 | +| Interaction prompt | 1 | 1 | 0 | P0 | +| Dialogue panel | 1 | 1 | 0 | P0 | +| Entity color system | 0 | 1 | 1 | P0 | +| Pause manager | 0 | 1 | 0 | P0 | +| HUD bar | 1 | 1 | 0 | P1 | +| Minimap | 1 | 1 | 1 | P1 | +| Knowledge panel | 1 | 1 | 0 | P1 | +| News ticker | 1 | 1 | 0 | P1 | +| Fog rendering | 0 | 0 | 1 | P0 | +| **TOTAL** | **7** | **9** | **3** | | + +**Changes from Round 1:** Reduced from 9 scenes to 7. Added 1 shader (fog rendering). Pause manager added (P0, new in R2). + +### Monologue Display Parameters (Stig, confirmed by Mellanie) + +| Parameter | Value | +|-----------|-------| +| Max characters | 160 | +| Max lines | 2 | +| Base duration | 4.0s | +| Short duration (<80 chars) | 3.0s | +| Urgent chime duration | 5.0s | +| Fade in | 0.3s | +| Fade out | 0.5s | +| Interruption fade | 0.15s | +| Cooldown between lines | 2.0s minimum | +| Max queue depth | 1 (drop lower-priority) | + +**Priority ordering:** observe_anomaly (1) > witness_interaction (2) > discover_evidence (3) > post_conversation (4) > hear_sound (5) > observe_npc (6) > enter_location (7) > return_visit (8) > time_idle (9). + +**Critical UI interaction:** Monologue fires during dialogue (for wow moment #3). Monologue sits in world layer; dialogue sits in HUD layer. Two voices simultaneously — one external, one internal. + +### Interaction Prompt — Single-Action Extensible + +v0.1: `[E] Talk` / `[E] Examine` — single keybind, context-sensitive verb. +v0.2: `[E] Talk [F] Observe` — horizontal label row, same prompt position. + +The `verbs[]` field (renamed from `actions[]` per D-060) exists in the protocol from day one. The v0.1 client ignores it. The v0.2 client reads it. No protocol change needed. + +--- + +## 10. Ticket Analysis — Finalized (SI) + +### Lead Decision Impact on Tickets + +| Decision | Ticket Impact | +|----------|---------------| +| D-NPC | NEW-1 simplified to "apply Gestalt's mapping." Paula's layer → v0.2 annotation. | +| D-FORMAT | Confirms C-series as YAML. Adds NEW-8 (RON converter). | +| D-INTERACT | NEW-7 reframed. Adds NEW-9 (server InteractionOptions) + NEW-10 (client extensible prompt). | +| D-PAUSE | Adds NEW-11 (server tick rate scaling). ~~NEW-12 (client pause state machine)~~ **killed in closing round** — client is a dumb renderer. | + +### Final Ticket Count + +| Category | Count | Teams | +|----------|-------|-------| +| Wiki fixes (A-series) | 8 | copy | +| Style guides & specs (B-series) | 5 | copy | +| Content directory & schema (C-series) | 10 | copy (1), server (8), ci (1) | +| Design specs (D-series) | 2 | server (1), copy (1) | +| Round 1 new tickets (NEW 1-7) | 7 | copy | +| Round 2 new tickets (NEW 8-13) | 5 | server (4), client (1) | +| **Total new tickets** | **37** | **copy (21), server (13), client (1), ci (1)** | +| Existing ticket updates | 10 | — | + +**Delta from Round 1:** +5 tickets (NEW-8 through NEW-13 from lead decisions, minus NEW-12 killed in closing round). Removed: D3 (killed), A5 (deferred), D1 (deferred), NEW-12 (killed — client doesn't manage time). Merged: A3 into #319, #310 into B4. + +### Sprint 3 Allocation + +**Copy team (15 tickets):** +- P0: #261 (Dual Lens Guide, **critical path root**), C1 (Content directory design), A1 (canonical names), A2 (Naia Tamm rename), A7 (smuggler attributes) +- P1: B2 (MIRROR spec), B4 (smuggler voice + moral arc), NEW-1 (NPC mapping), NEW-5 (wow moment checklist), A10 (Nils stub), A11 (Triangle 1 fix) +- P2: B3 (PC-as-NPC), A8 (shortcode fix), NEW-2 (Drin expansion), NEW-7 (interaction verb spec) + +**Server team (4 additions):** C2 (directory skeleton), C3 (schemas), NEW-9 (InteractionOptions), NEW-11 (tick rate scaling) + +**Client team (1 addition):** NEW-10 (extensible prompt). ~~NEW-12 killed~~ — client sends pause IPC command; server manages tick rate. + +### Critical Path (Revised) + +Two parallel chains: + +**Chain 1 — Content Authoring:** +``` +#261 (Dual Lens Guide) [P0, ROOT] + -> B4 (Smuggler voice + moral arc) + -> A6 (Smuggler FactId text) [Sprint 4] + -> NEW-4 (Monologue pool ~200 lines) [Sprint 5] + -> #297 (FRIEND: Kael) [Sprint 5] + -> #298 (FRIEND: Sera) [Sprint 5] + -> #190, #191, #192 (Content packs) [Sprint 5] +``` + +**Chain 2 — Content Architecture:** +``` +C1 (Content dir design) [P0] + -> C2 (skeleton) + C3 (schemas) [Sprint 3] + -> C4-C8 (YAML population) [Sprint 4] + -> NEW-3 (wiki->YAML conversion) [Sprint 4] + -> #326 (YAML loader) [Sprint 4] + -> C9 (validator) [Sprint 4] +``` + +**Independent chains (Sprint 3):** +- A10 (Nils stub) → A11 (Triangle 1 fix) +- NEW-9 (InteractionOptions, server) → NEW-10 (extensible prompt, client) +- NEW-11 (tick rate scaling, server) — no client counterpart (client reads `sim_speed` from snapshot) + +### Risks (SI) + +1. **#261 remains critical path root.** Every content authoring ticket flows through it. Recommended: assign Paula, time-box 5 days. +2. **Sprint 3 copy team has 15 tickets.** 7 are small/parallelizable. Real load: #261, C1, B2, B4, A11. +3. **Server Sprint 3 already heavy** (10 existing + 4 new). C2/C3 blocked by C1 anyway. +4. **Content conversion (NEW-3) is Sprint 4 bottleneck.** Start with 3 Tier 1 NPCs as soon as C2 lands. + +--- + +## 11. Wiki Fix Execution Plan (Paula) + +Paula produced execution-ready content changes for each priority level: + +| Priority | Change | Files | Status | Effort | +|----------|--------|-------|--------|--------| +| P0-1 | Naia Tamm rename | 16 files | Exact per-file changes specified | ~2 hours | +| P0-2 | Nils Davan stub | 1 new + 3 cross-ref | Full draft ready | ~30 min | +| P0-3 | Drin Tier 2 expansion | 1 rewrite + index | Full draft ready | ~1 hour | +| P1-1 | Canonical names | 10 headers + index | Name table ready; 3 need Miri validation | ~1 hour | +| P1-2 | Triangle 1 escalation fork | index + 2 NPC notes | Self-contained version (v0.1) | ~1 hour | +| P2-1 | Smuggler-lens sections (17 NPCs) | 17 profiles | Template ready; per-NPC content needed | ~8-10 hours | +| P2-2 | PC-as-NPC profiles | 2 new files | Gap identified; not yet drafted | ~3-4 hours | +| P2-3 | FactId catalog smuggler entries | 1 file | Gap identified; not yet drafted | ~2 hours | + +**P0-1 and P0-2 can start immediately.** P0-3 blocked by A-01 formalization (consensus confirmed). P1-1 blocked by Miri validation (3 names). P1-2 depends on P0-2. + +**P1-2 (Triangle 1) — self-contained revision:** Cross-triangle cascade removed per D-INTERACT. Path B no longer triggers Maret's discovery. Path C no longer makes detective investigation easier. Emotional weight preserved within Triangle 1. + +--- + +## 12. Dependencies Matrix + +| Agent | Waiting On | From | Blocks | Status | +|-------|-----------|------|--------|--------| +| **Stig** | IPC message format (ObserverSnapshot v3 struct) | Dudley/Tyre | All client UI | Open | +| ~~**Stig**~~ | ~~Pause architecture decision~~ | ~~Dudley~~ | ~~Pause manager~~ | **Resolved** — server-authoritative | +| **Stig** | Font selection, diegetic styling | Araminta | Knowledge panel visual | Open | +| **Stig** | First monologue content (5-10 lines) | Mellanie | Monologue renderer testing | Open | +| **Dudley** | ObserverSnapshot v3 sign-off | Tyre | Protocol implementation | Open | +| **Dudley** | FactId catalog (machine-readable) | Gestalt/Paula | Prerequisite evaluation | Open | +| **Dudley** | FRIEND content packs (phase-tagged) | Mellanie | FriendArc integration testing | Open | +| **Dudley** | Contradiction spatial data (location, time window) | Paula | Spatial staging system | Open | +| **Mellanie** | Content directory confirmed | Tyre | File structure for packs | Open | +| **Mellanie** | FactId catalog stabilized | Gestalt/Paula | Prerequisite placeholders | Open | +| ~~**Mellanie**~~ | ~~EntityKnowledge key resolution~~ | ~~Lead~~ | ~~Prerequisite vocabulary~~ | **Resolved** — 16 keys, 4 role-perspective | +| **Paula** | Miri validation (3 NPC surnames) | Miri | Canonical name application | Open | + +--- + +## 13. Decisions Ready to Formalize + +### From Lead's Round 2 Decisions + +| Proposed ID | Decision | Source | +|-------------|----------|--------| +| D-049 | **YAML is the content file format for v0.1.** RON converter available as optional build-time tool in `tooling/`. Server loads YAML directly in development; RON cache is a production optimization. | D-FORMAT lead decision; Tyre design; Dudley acceptance | +| D-050 | **Gestalt's NPC pattern/motivation mapping is canonical for v0.1.** Paula's emotional/narrative layer becomes v0.2 annotations. Version-toggle approach: same NPCs, different analytical depth per version. | D-NPC lead decision; Gestalt definitive table; Paula versioned layer | +| D-051 | **v0.1 ships single context-sensitive action; multi-verb architecture modeled underneath.** Server computes N available actions per entity. v0.1 client shows priority-0 only. v0.2 client reads full list. No server changes for v0.2 multi-verb. | D-INTERACT lead decision; Tyre architecture; Dudley implementation; Stig client design | +| D-052 | **3-state pause system: Normal (100%), Overlay (50%), Paused (0%). Server-authoritative.** Spacebar always pauses from any state. UI overlays (knowledge panel, dialogue) trigger 50% speed. Multiple overlays don't stack below 50%. Spacebar resumes to pre-pause state. **Server controls tick rate; client is a dumb renderer.** Client sends pause IPC command to server. Client reads `sim_speed` from ObserverSnapshot for visual feedback. NEW-12 (client pause state machine) killed. | D-PAUSE lead decision + closing round resolution; Gestalt mechanical design; Dudley server SimSpeed authoritative | +| D-053 | **Self-contained triangle forks for v0.1.** Independent decision state machines, shared NPC state. No cross-triangle cascade. Players observe cross-triangle effects emergently through NPC behavior. v0.2 adds storyteller-managed cascade. | Gestalt confirmation; Tyre recommendation (R1); Paula revised Triangle 1 | +| D-054 | **ObserverSnapshot v3 protocol.** Extends v2 with: sim_speed, nearby_interactions, active_dialogue, monologue, overheard, knowledge_updates, examine_result, ticker_headlines. Backward compatible via serde defaults. Wire overhead ~50-1500 bytes/tick. | Tyre full definition; Dudley compatible definition | +| D-055 | **16 EntityKnowledge keys total. 4 new generic role-perspective keys: `risk_assessment`, `loyalty_assessment`, `position_integrity`, `moral_weight`.** All observer→target. `trust_read` merged into existing `trust_level`. `secret_held` renamed to `leverage_held` with expanded value space (secrets, debts, promises, obligations, compromising positions). `secret_confidence` renamed to `leverage_confidence`. `social_debt` and `loyalty_conflict` deferred to v0.2. `tell_observed` and `contradiction_flagged` tracked in existing `behavior_flags` — no dedicated keys. Scales to all 8 archetypes without schema changes. | Gestalt generic architecture; Paula generic architecture; closing round lead resolution | +| D-056 | **PC voice registers defined.** Smuggler: feeling-first, fragments, concrete/physical, first-name basis. Detective: analysis-first, complete sentences, institutional vocabulary, surname-first for targets. Both characters have 5 voice anchors, 4 anti-patterns, and 4 comparison examples. | Mellanie canonical specifications | +| D-057 | **Content directory structure: `content/` with `schema/`, `global/`, `districts/` top-level split.** JSON Schema validation at build time. Dialogue by location + role (not NPC). Monologue partitioned by character. `canonical_id` format: `npc:{slug}`, `loc:{district}:{location}`. | Tyre finalized tree + schema definitions | +| D-058 | **THE FRIEND content pack template: Kael Davan.** 91 lines across 5 arc phases + cross-phase. Pack validates tag taxonomy, load correctness, trigger appropriateness. Structure becomes template for all subsequent content packs. | Mellanie pack structure + sample YAML | +| D-059 | **Monologue display constraints.** 160 char max, 2-line max, 4-6s display time, 2s cooldown, queue depth 1 (drop lower priority), 9-level priority ordering with observe_anomaly highest. | Stig constraints; Mellanie adoption | +| D-060 | **`actions[]` renamed to `verbs[]` across all surfaces.** Scope: server code, client code, YAML content files, wiki docs, decision files. Folded into NEW-7 ticket description. | Closing round lead decision | +| D-061 | **No ticket merges across domain teams.** Shared epics are fine, but each team gets its own ticket with its own definition of done. | Closing round lead decision (process rule) | + +### From Round 1 (Carried Forward — Unchanged) + +| Proposed ID | Decision | Source | +|-------------|----------|--------| +| D-042 | Drin promoted from Tier 3 to Tier 2 | Round 1 consensus (A-01) | +| D-043 | THE NOBODY mechanic deferred to v0.2; hidden data ships in v0.1 | Round 1 consensus (A-02) | +| D-044 | v0.1 interaction model: 7 interactions (Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear) | Round 1 consensus (A-04) | +| D-045 | v0.1 scope: add News ticker, PC-as-NPC, Time progression, Relationship state transitions to IN list | Round 1 consensus (A-14, A-15, A-16) | +| D-046 | v0.1 scope: add Inventory, Stealth, Combat, Player-facing save/load, Lattice modification to explicit OUT list | Gestalt (Round 1) | +| D-047 | v0.1 triangles: 3 active forks (T1, T2, T4), 2 passive tensions (T3, T5) | Round 1 consensus (A-07) | +| D-048 | Client receives all text from server via state updates; client does not load or evaluate content files | Round 1 consensus (A-12) | + +--- + +## 14. Open Items + +### Resolved in Closing Round + +| # | Item | Resolution | Decision | +|---|------|-----------|----------| +| Q-012 | EntityKnowledge generic key count: 4 vs 6 | **16 total keys.** 4 new role-perspective (`risk_assessment`, `loyalty_assessment`, `position_integrity`, `moral_weight`). `trust_read` → `trust_level` (merge). `secret_held` → `leverage_held` (absorb + expand). `secret_confidence` → `leverage_confidence` (rename). `tell_observed`/`contradiction_flagged` tracked in `behavior_flags`. No 7th key. | D-055 | +| Q-013 | Pause authority: client vs server | **Server-authoritative.** Client is a dumb renderer. Sends pause IPC command. Reads `sim_speed` from snapshot. NEW-12 killed. | D-052 (updated) | +| Q-016 | `actions[]` → `verbs[]` scope | **Confirmed across all surfaces:** server code, client code, YAML content, wiki docs, decision files. Folded into NEW-7. | D-060 | + +### Still Open + +| # | Item | Owner | Status | Priority | +|---|------|-------|--------|----------| +| Q-014 | 3 NPC surnames awaiting Miri validation: Drin Vassar, Sess Torin, Tav Lenn. | Miri | Blocked on world-building | Low | +| Q-015 | Interaction struct naming: `AvailableActions`/`ActionOption` (Tyre) vs `EntityInteractions`/`AvailableAction` (Dudley). Trivial — needs one canonical choice. | Tyre/Dudley | Sprint 3 implementation | Low | +| Q-017 | Mellanie's 695 authored items — validated by SI as scope input but not independently verified by other agents. | SI/Mellanie | Informational | Low | + +--- + +## 15. Workshop Summary + +### What This Workshop Produced + +1. **Canonical NPC mapping** — 17 NPCs with pattern + motivation, through collaborative resolution (Gestalt changed 6 of 10 positions based on Paula's arguments) +2. **Content architecture** — Full directory tree, 3 JSON Schema definitions, 3-tier validation pipeline, dual-format loader +3. **Interaction architecture** — Multi-verb server model with single-action client filter, extensible without server changes +4. **Pause system** — 3-state model with unanimous design convergence across 3 agents +5. **ObserverSnapshot v3** — Full protocol definition with backward compatibility, produced independently by 2 agents with structural agreement +6. **PC voice registers** — Canonical specifications for both characters with voice anchors, anti-patterns, and comparison examples +7. **FRIEND content pack template** — 91-line Kael Davan pack with phase-by-phase structure, prerequisite progression, and sample YAML +8. **FriendArc state machine** — 4-phase transitions with explicit trigger code and content dependencies +9. **Wiki fix execution plan** — 8 prioritized changes with per-file specifications for top-priority items +10. **37 new tickets + 10 updates** — Full Sprint 3-5 roadmap across copy, server, client, and CI teams (NEW-12 killed in closing round) + +### What Remains Unresolved + +- 3 NPC surnames awaiting Miri validation (Q-014) +- Interaction struct naming — trivial, resolved at implementation time (Q-015) +- 695 authored items scope validation (Q-017, informational) + +### Critical Path Unchanged + +**#261 (Dual Lens Authoring Guide) remains the single biggest blocker.** Blocks 9 downstream tickets. Recommended: assign Paula, Sprint 3 P0, time-box 5 days. + +--- + +*Qatux, Round 2 tracking (updated with closing round resolutions). For the record: all 7 Round 2 responses cataloged. 4 lead decisions implemented + 3 closing round resolutions (Q-012: 16 keys, Q-013: server-authoritative pause, Q-016: verbs[] rename confirmed). 10 Round 1 disagreements resolved (6 changed to Paula, 3 kept Gestalt, 1 deferred by design). 20 decisions ready to formalize (D-042 through D-061: 13 new from Round 2 + closing, 7 carried from Round 1). 37 new tickets proposed + 10 updates (NEW-12 killed). 3 minor items remain open. Workshop complete.* diff --git a/docs/workshops/v01-content-scoping/round2-tyre.md b/docs/workshops/v01-content-scoping/round2-tyre.md new file mode 100644 index 000000000..2a8165356 --- /dev/null +++ b/docs/workshops/v01-content-scoping/round2-tyre.md @@ -0,0 +1,1154 @@ +# Round 2 — Tyre (Technical Architect) +## v0.1 Content Scoping Workshop + +Four tasks from the lead. Let me work through them. + +--- + +## Task 1: Finalized Content Directory YAML Structure + Schema Snippets + +YAML confirmed. Here's the finalized structure with actual JSON Schema definitions for the core content types. These schemas are the contract between copy (authoring) and server (loading). + +### Final Directory Tree + +``` +content/ + content.yaml # Manifest: district list, content version, load order + schema/ # JSON Schema definitions for YAML validation + npc-profile.schema.json + location.schema.json + fact-catalog.schema.json + district.schema.json + dialogue-pool.schema.json + monologue-pool.schema.json + routine.schema.json + triangle.schema.json + global/ # District-independent vocabulary + facts/ + contraband.yaml + location.yaml + investigation.yaml + world.yaml + relationship.yaml + progress.yaml + factions/ + lattice-commission.yaml + syndics.yaml + the-ring.yaml + concord-assembly.yaml + guardians-of-autonomy.yaml + veil-institute.yaml + the-unbound.yaml + enums/ + 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) + entity-schema/ + attributes.yaml # 14 canonical EntityKnowledge keys + 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 + 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 +``` + +### Schema: Content Manifest + +```yaml +# content/content.yaml +version: "0.1.0" +districts: + - id: "sova-transit" + path: "districts/sova-transit" + enabled: true +``` + +### Schema: NPC Profile (`npc-profile.schema.json`) + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "NpcProfile", + "description": "NPC content profile for The Settled Reach. Maps to server NpcBundle.", + "type": "object", + "required": ["canonical_id", "display_name", "short_name", "tier", "pattern", "motivation", "district", "axes", "initial_attributes"], + "properties": { + "canonical_id": { + "type": "string", + "pattern": "^npc:[a-z0-9-]+$", + "description": "Stable content address. Format: npc:{slug}" + }, + "display_name": { "type": "string", "minLength": 1 }, + "short_name": { "type": "string", "minLength": 1 }, + "tier": { "enum": [1, 2, 3] }, + "pattern": { + "enum": ["FRIEND", "MIRROR", "ANCHOR", "GHOST", "CATALYST", "THRESHOLD", "REMNANT", "SYSTEM", "NOBODY"] + }, + "motivation": { + "enum": ["HANDLER", "WITNESS", "TURNCOAT", "CIVILIAN", "OPERATOR", "SKEPTIC"] + }, + "district": { "type": "string" }, + "axes": { + "type": "object", + "required": ["want", "secret", "relationships", "tolerance", "routine", "information", "contentment"], + "properties": { + "want": { "type": "string" }, + "secret": { + "type": "object", + "required": ["surface"], + "properties": { + "surface": { "type": "string" }, + "deep": { "type": "string" } + } + }, + "relationships": { + "type": "array", + "items": { + "type": "object", + "required": ["target", "kind", "trust"], + "properties": { + "target": { + "type": "string", + "pattern": "^npc:[a-z0-9-]+$", + "description": "canonical_id of relationship target" + }, + "kind": { "type": "string" }, + "trust": { "type": "number", "minimum": 0.0, "maximum": 1.0 } + } + } + }, + "tolerance": { + "type": "object", + "required": ["current_stress", "threshold"], + "properties": { + "current_stress": { "type": "number", "minimum": 0.0, "maximum": 1.0 }, + "threshold": { "type": "number", "minimum": 0.0, "maximum": 1.0 } + } + }, + "routine": { + "type": "object", + "required": ["description"], + "properties": { + "description": { "type": "string" } + } + }, + "information": { + "type": "object", + "required": ["known_facts"], + "properties": { + "known_facts": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z_]+\\.[a-z_]+:(Suspects|KnowsOf|KnowsDetails)$", + "description": "Format: fact_id:ConfidenceLevel" + } + } + } + }, + "contentment": { "type": "number", "minimum": 0.0, "maximum": 1.0 } + } + }, + "personality": { + "type": "object", + "properties": { + "traits": { "type": "array", "items": { "type": "string" } } + } + }, + "tells": { + "type": "array", + "items": { + "type": "object", + "required": ["trigger", "behavior"], + "properties": { + "trigger": { "type": "string" }, + "behavior": { "type": "string" } + } + } + }, + "skills": { + "type": "object", + "properties": { + "set": { "type": "array", "items": { "type": "string" } }, + "combat_trained": { "type": "boolean", "default": false } + } + }, + "initial_attributes": { + "type": "object", + "required": ["role", "faction", "species"], + "properties": { + "role": { "type": "string" }, + "faction": { "type": "string" }, + "species": { "type": "string", "default": "human" }, + "routine_pattern": { "type": "string" } + }, + "description": "What a brand-new observer would learn about this NPC on first sight. Maps to EntityKnowledge.known_attributes." + }, + "access_tiers": { + "type": "object", + "properties": { + "default": { "enum": ["public", "insider", "authority", "peer", "hostile"] }, + "overrides": { + "type": "object", + "additionalProperties": { + "enum": ["public", "insider", "authority", "peer", "hostile"] + }, + "description": "canonical_id → access tier overrides" + } + } + }, + "trust_levels": { + "type": "object", + "additionalProperties": { + "enum": ["surface", "real", "secret"] + }, + "description": "canonical_id → trust tier" + }, + "friend_arc": { + "type": "object", + "properties": { + "character": { "enum": ["smuggler", "detective"] }, + "phases": { + "type": "array", + "items": { + "type": "object", + "required": ["name"], + "properties": { + "name": { "enum": ["warmth", "trust", "doubt", "conflict"] }, + "triggers": { "type": "array" } + } + } + }, + "contradiction": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "location": { "type": "string" }, + "expected_location": { "type": "string" }, + "time_window": { "type": "string" } + } + } + }, + "description": "Only present for FRIEND pattern NPCs (Tier 1)" + }, + "triangle_membership": { + "type": "array", + "items": { + "type": "object", + "required": ["triangle_id", "role"], + "properties": { + "triangle_id": { "type": "string" }, + "role": { "type": "string" } + } + } + } + } +} +``` + +### Schema: Dialogue Pool (`dialogue-pool.schema.json`) + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "DialoguePool", + "description": "Tagged dialogue line pool per D-028, D-035.", + "type": "object", + "required": ["role", "location", "lines"], + "properties": { + "role": { "type": "string", "description": "Template-defined role (not NPC name)" }, + "location": { + "type": "string", + "pattern": "^loc:[a-z0-9-]+:[a-z0-9-]+$" + }, + "lines": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "text", "access", "trust", "situation"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z_]+_d_[0-9]{3}$", + "description": "{location}_{d}_{###} per D-035" + }, + "text": { "type": "string", "maxLength": 300 }, + "access": { + "type": "array", + "items": { "enum": ["public", "insider", "authority", "peer", "hostile"] }, + "minItems": 1 + }, + "trust": { "enum": ["surface", "real", "secret"] }, + "situation": { + "type": "array", + "items": { + "enum": [ + "arrival", "shift_start", "shift_end", "shift_transition", + "bar_evening", "night_shift", "investigation", "confrontation", + "social", "alone", "emergency", "routine", "observation" + ] + }, + "minItems": 1 + }, + "topic": { + "type": "array", + "items": { + "enum": [ + "colleague", "routine", "cargo", "money", "trust", + "danger", "institution", "personal", "investigation" + ] + } + }, + "mood": { + "type": "array", + "items": { + "enum": [ + "fond", "comfortable", "worried", "suspicious", + "analytical", "conflicted", "concerned", "relieved" + ] + } + }, + "tags": { + "type": "array", + "items": { "type": "string" } + }, + "knowledge_grants": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { "enum": ["learn_fact", "learn_attribute", "update_relationship"] }, + "fact_id": { "type": "string" }, + "confidence": { "enum": ["Suspects", "KnowsOf", "KnowsDetails"] }, + "target": { "type": "string" }, + "key": { "type": "string" }, + "value": { "type": "string" } + } + }, + "description": "Knowledge updates that occur when this line is spoken to the player" + } + } + } + } + } +} +``` + +### Schema: Monologue Pool (`monologue-pool.schema.json`) + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "MonologuePool", + "description": "Tagged monologue line pool per D-032, D-035. Hard-partitioned by character.", + "type": "object", + "required": ["character", "location", "lines"], + "properties": { + "character": { "enum": ["smuggler", "detective"] }, + "location": { "type": "string" }, + "lines": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "text", "trigger"], + "properties": { + "id": { + "type": "string", + "pattern": "^[a-z_]+_m_[sd]_[0-9]{3}$", + "description": "{location}_m_{s|d}_{###}" + }, + "text": { + "type": "string", + "maxLength": 160, + "description": "~160 chars max, 2-line display per Stig's constraint" + }, + "trigger": { + "enum": [ + "enter_location", "observe_npc", "hear_sound", + "observe_anomaly", "post_conversation", "discover_evidence", + "witness_interaction", "time_idle", "return_visit" + ] + }, + "prerequisite": { + "type": ["object", "null"], + "properties": { + "facts": { + "type": "object", + "additionalProperties": { + "enum": ["Suspects", "KnowsOf", "KnowsDetails"] + }, + "description": "FactId → minimum confidence. ALL must be met (AND logic)." + }, + "entity": { + "type": "object", + "properties": { + "target": { "type": "string" }, + "attribute": { "type": "string" }, + "condition": { "type": "string" } + }, + "description": "Entity attribute check. AND with facts if both present." + }, + "relationship": { + "type": "object", + "properties": { + "target": { "type": "string" }, + "min_state": { + "enum": ["Unknown", "Known", "Friendly", "PersonOfInterest", "Hostile"] + } + } + } + }, + "description": "AND-only prerequisite. All specified conditions must be true. Null = always eligible." + }, + "topic": { + "type": "array", + "items": { "type": "string" } + }, + "mood": { + "type": "array", + "items": { "type": "string" } + }, + "tags": { + "type": "array", + "items": { "type": "string" } + }, + "priority": { + "type": "integer", + "minimum": 0, + "maximum": 10, + "default": 5, + "description": "Higher priority lines are preferred when multiple qualify. 0=lowest, 10=critical (wow moments)." + }, + "cooldown": { + "type": "integer", + "minimum": 0, + "description": "Minimum ticks before this line can fire again. 0 = no repeat." + }, + "dual_lens": { + "type": "object", + "description": "Authoring-only. Per-character notes for the content team." + }, + "notes": { + "type": "string", + "description": "Authoring-only. Author intent and context." + } + } + } + } + } +} +``` + +### Schema: Fact Catalog Entry + +```yaml +# content/global/facts/contraband.yaml +category: "contraband" +facts: + - fact_id: "contraband.ring_exists" + description: "A smuggling operation exists in the district" + discoverable_by: ["smuggler", "detective"] + progression: + Suspects: "Something's going on with the cargo schedules." + KnowsOf: "There's a smuggling operation running through the logistics hub." + KnowsDetails: "The ring moves unlicensed lattice components during shift transitions." + abstract: true # Never reaches Direct confidence + + - fact_id: "contraband.lattice_components" + description: "The specific contraband: unlicensed lattice components" + discoverable_by: ["smuggler", "detective"] + progression: + Suspects: "That container's marked as standard, but the mass is wrong." + KnowsOf: "Unlicensed lattice components. Aftermarket mods." + KnowsDetails: "Medical-grade neural lattice replacements plus enhanced capability mods." + Direct: "I'm looking at a crate of lattice components right now." + abstract: false # Can reach Direct if player sees contraband +``` + +### Schema: Schedule + +```yaml +# content/districts/sova-transit/routines/schedules.yaml +schedules: + - npc: "npc:kael-davan" + entries: + - phase: Morning + location: "loc:sova-transit:the-terminal" + tile: { x: 42, y: 18, z: 0 } + activity: "working" + - phase: Afternoon + location: "loc:sova-transit:the-terminal" + tile: { x: 45, y: 20, z: 0 } + activity: "working" + - phase: Evening + location: "loc:sova-transit:the-last-shift" + tile: { x: 80, y: 55, z: 0 } + activity: "drinking" + - phase: Night + location: "loc:sova-transit:residential" + tile: { x: 30, y: 70, z: 0 } + activity: "sleeping" + deviations: + - trigger: "friend_arc.doubt" + phase: Evening + override_location: "loc:sova-transit:maintenance-corridors" + override_tile: { x: 15, y: 8, z: -1 } + override_activity: "meeting_contact" + description: "Kael meets unauthorized contact during shift transition" +``` + +The `deviations` key is load-bearing for THE FRIEND arc. It tells the routine scheduler: "When the FRIEND arc reaches phase X, change Kael's evening location to Corridor B-7." This is how spatial staging works without scripting — the schedule system handles it as a conditional routine override. + +### Validation Pipeline (Finalized) + +``` +AUTHORING TIME (copy team) + └── Author writes YAML in content/ directory + └── IDE/editor with JSON Schema autocomplete (optional) + +BUILD TIME (make content-validate) + ├── JSON Schema validation: check YAML structure against schema files + ├── Cross-reference validation: + │ ├── All canonical_id references resolve to existing files + │ ├── All FactIds in prerequisites reference defined facts + │ ├── All relationship targets exist + │ ├── No duplicate canonical_ids across all files + │ └── Enum values match global/enums/ definitions + └── FAIL if any error — list all errors, don't stop at first + +LOAD TIME (server startup) + ├── serde_yaml deserialization: YAML → Rust structs + │ └── Type mismatch = load failure (serde catches schema drift) + ├── Semantic validation: + │ ├── StableId assignment (deterministic from sorted canonical_ids) + │ ├── Relationship wiring (canonical_id → StableId resolution) + │ └── FriendArc bonding (character reference → StableId) + └── FAIL FAST on any error — no partial loads +``` + +--- + +## Task 2: Multi-Verb Interaction Architecture + +The lead's decision: v0.1 ships single context-sensitive action, but the server models N available actions per entity from day one. Here's how. + +### Core Design: `AvailableActions` per Entity + +```rust +/// What a player can do with a nearby entity. +/// Server computes the full list every tick for entities in interaction range. +/// v0.1 client reads only the primary (priority 0). +/// v0.2 client renders all enabled actions as a verb menu. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct AvailableActions { + /// StableId of the target entity + pub target_id: u64, + /// Display name (as known to this observer) + pub display_name: String, + /// All available actions, sorted by priority (0 = highest) + pub actions: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActionOption { + /// What this action does + pub kind: ActionKind, + /// Prompt text shown to the player: "Talk", "Observe", "Examine" + pub label: String, + /// Sort priority. 0 = primary action (v0.1 default). Lower = higher priority. + pub priority: u8, + /// Is this action currently available? + pub enabled: bool, + /// Why it's disabled (shown as tooltip in v0.2+) + pub disabled_reason: Option, +} + +/// Action types the player can perform on entities. +/// Extensible enum — new variants added in future versions. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ActionKind { + // v0.1 actions + Talk, + ExamineNpc, + ExamineObject, + // v0.2+ actions (defined now, not surfaced) + // Trade, + // Give, + // Accuse, + // Follow, + // UseOn, +} +``` + +### Priority Resolution (Server-Side) + +The server computes priorities based on entity type, observer state, and game context. This is the "context-sensitive" logic: + +```rust +/// Determine available actions for a target entity given the observer's state. +/// Returns actions sorted by priority (0 = most relevant). +fn compute_actions( + target: &TargetInfo, + observer: &ObserverInfo, + game_state: &GameState, +) -> Vec { + let mut actions = Vec::new(); + + match target.entity_type { + EntityType::Npc => { + // Talk is available if NPC is interactable + if target.is_interactable && !target.is_hostile { + actions.push(ActionOption { + kind: ActionKind::Talk, + label: compute_talk_label(observer, target), + priority: 0, // default primary for NPCs + enabled: true, + disabled_reason: None, + }); + } + + // Examine is available if NPC is in LOS + if target.in_los { + let examine_priority = if observer.has_investigation_context(target) { + 0 // Detective with suspicion → Examine becomes primary + } else { + 1 // Normal → Examine is secondary + }; + actions.push(ActionOption { + kind: ActionKind::ExamineNpc, + label: "Observe".to_string(), + priority: examine_priority, + enabled: true, + disabled_reason: None, + }); + } + + // If NPC is hostile, Talk is disabled but visible + if target.is_hostile { + actions.push(ActionOption { + kind: ActionKind::Talk, + label: "Talk".to_string(), + priority: 1, + enabled: false, + disabled_reason: Some("Hostile".to_string()), + }); + } + } + + EntityType::Object => { + actions.push(ActionOption { + kind: ActionKind::ExamineObject, + label: compute_examine_label(target), + priority: 0, + enabled: true, + disabled_reason: None, + }); + } + } + + // Sort by priority, stable ordering for determinism + actions.sort_by_key(|a| a.priority); + actions +} + +/// Context-sensitive talk label. +/// Changes based on relationship state and investigation progress. +fn compute_talk_label(observer: &ObserverInfo, target: &TargetInfo) -> String { + match observer.relationship_to(target) { + RelationshipState::PersonOfInterest => "Question".to_string(), + RelationshipState::Hostile => "Confront".to_string(), + _ => "Talk".to_string(), + } +} +``` + +### How the Detective's Loop Works + +Gestalt's design: detective examines first (accumulate tells), talks later (use evidence). The priority system supports this naturally: + +1. **Default state:** Talk = priority 0, Observe = priority 1. Player approaches NPC, sees "[E] Talk". +2. **After knowledge graph flags PersonOfInterest:** `has_investigation_context()` returns true. Observe = priority 0, Talk (now labeled "Question") = priority 1. Player approaches flagged NPC, sees "[E] Observe". +3. **v0.1:** Client shows only priority 0. The context switch happens automatically. +4. **v0.2:** Client shows both. Player chooses. + +The key insight: **the context-sensitive switch IS the v0.2 multi-verb system operating with a filter.** No throwaway code. v0.2 removes the filter; the rest works. + +### PlayerAction Extension + +```rust +/// Semantic player actions — v3 protocol. +/// InteractPrimary is the v0.1 action (do whatever priority 0 says). +/// InteractWith is the v0.2 action (do a specific thing to a specific target). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum PlayerAction { + // Movement (unchanged) + MoveNorth, + MoveSouth, + MoveEast, + MoveWest, + MoveNortheast, + MoveNorthwest, + MoveSoutheast, + MoveSouthwest, + + // Interaction (v3) + /// Do the primary (priority 0) action on the nearest interactable target. + /// v0.1 default. Maps to [E] key. + InteractPrimary, + /// Do a specific action on a specific target (v0.2+). + /// Target is StableId. Action is the ActionKind. + InteractWith { target_id: u64, action: ActionKind }, + /// Select a dialogue topic while in active conversation. + DialogueSelect { topic: String }, + /// End the current dialogue. + DialogueEnd, + /// Acknowledge/advance examine result text. + ExamineAdvance, + + // System (unchanged) + UsePerceptionMode(String), + Pause, + Unpause, +} +``` + +**Removed: the old `Interact` variant.** `InteractPrimary` replaces it with the same behavior but clearer semantics. `InteractWith` is defined now but v0.1 client never emits it. + +### Server-Side Pipeline + +``` +Player presses [E] + → Client sends InteractPrimary + → Server: find nearest interactable entity within range + → Compute AvailableActions for that entity + → Execute priority-0 action: + → ActionKind::Talk → enter dialogue state, run D-035 selection pipeline + → ActionKind::ExamineNpc → emit tell observation event, queue examine result text + → ActionKind::ExamineObject → emit examine event, queue description text + → Pack results into ObserverSnapshot v3 + → Client renders result (dialogue panel, examine text, monologue) +``` + +--- + +## Task 3: RON Converter + +### Architecture Decision + +**Dual-format loader with optional build-time compilation.** + +The server content loader supports both YAML and RON via serde. The format is determined by file extension. Content authors write YAML (human-friendly). An optional build step compiles YAML → RON for faster load times. + +### Where It Lives + +``` +server/src/content/ + loader.rs # Content loader — reads YAML or RON based on extension + schema.rs # Rust serde structs (NpcProfile, DialogueLine, etc.) + registry.rs # ContentRegistry: canonical_id → StableId mapping + validate.rs # Semantic validation (cross-references, enum checks) + +tooling/content-tools/ + Cargo.toml # Small Rust binary + src/ + main.rs # CLI: content-tools validate|compile|stats + compile.rs # YAML → RON compiler + validate.rs # Schema validation against JSON Schema + stats.rs # Content statistics (line counts, coverage) +``` + +### When It Runs + +| Context | What Happens | Format Loaded | +|---|---|---| +| **Development (hot-reload)** | Server loads YAML directly. File watcher detects changes, reloads. | YAML | +| **CI / `make content-validate`** | `content-tools validate` runs JSON Schema + serde checks. No format conversion. | YAML (validation only) | +| **Build / `make content-compile`** | `content-tools compile` reads all YAML, writes RON to `content/.cache/`. | YAML → RON | +| **Production / release** | Server loads from `content/.cache/*.ron` if present, falls back to YAML. | RON (preferred) or YAML | + +### Implementation + +The loader is format-agnostic. Approximately 20 lines handle the dual format: + +```rust +use std::path::Path; + +/// Load a content file, auto-detecting format from extension. +pub fn load_content(path: &Path) -> Result { + let bytes = std::fs::read(path)?; + match path.extension().and_then(|e| e.to_str()) { + Some("yaml" | "yml") => { + serde_yaml::from_slice(&bytes).map_err(ContentError::Yaml) + } + Some("ron") => { + ron::de::from_bytes(&bytes).map_err(ContentError::Ron) + } + _ => Err(ContentError::UnsupportedFormat(path.to_path_buf())), + } +} + +/// Load content with RON cache fallback. +/// Checks content/.cache/ for compiled RON first, falls back to YAML. +pub fn load_with_cache( + yaml_path: &Path, + cache_dir: &Path, +) -> Result { + // Try RON cache first + let ron_path = cache_dir.join( + yaml_path.with_extension("ron").file_name().unwrap() + ); + if ron_path.exists() { + return load_content(&ron_path); + } + // Fall back to YAML + load_content(yaml_path) +} +``` + +### Makefile Integration + +```makefile +content-validate: ## Validate content files against schemas + cargo run --manifest-path tooling/content-tools/Cargo.toml -- validate content/ + +content-compile: ## Compile YAML content to RON cache + cargo run --manifest-path tooling/content-tools/Cargo.toml -- compile content/ content/.cache/ + +content-stats: ## Print content statistics + cargo run --manifest-path tooling/content-tools/Cargo.toml -- stats content/ +``` + +### Why Not Build-Time Only? + +Dudley builds with fixture data while copy authors real content. If compilation is required, Dudley can't test until copy delivers YAML AND it gets compiled. Direct YAML loading removes that gate. The RON cache is a performance optimization, not a correctness requirement. + +The `content/.cache/` directory is gitignored. It's local build output, not source content. + +--- + +## Task 4: ObserverSnapshot v2 → v3 Protocol Extension + +### Protocol Evolution Strategy + +The snapshot already has a `version: u8` field. The protocol evolves by: + +1. Incrementing the version number +2. Adding new fields as `Vec` (empty = not present) or `Option` +3. Never removing or reordering existing fields +4. Client checks version, handles unknown fields gracefully + +MessagePack (rmp-serde) handles this naturally — it serializes fields by name, so adding fields doesn't break existing clients. A v2 client reading a v3 snapshot ignores unknown fields. A v3 client reading a v2 snapshot gets empty defaults for new fields. + +### v3 Snapshot Definition + +```rust +/// ObserverSnapshot v3 — extends v2 with interaction, dialogue, monologue, and speed data. +/// The ONLY data structure crossing the client-server boundary (D-020). +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ObserverSnapshot { + /// Protocol version. v3 = 3. + pub version: u8, + /// Simulation tick when this snapshot was produced. + pub tick: u64, + /// Game time data for client HUD display (D-031). + pub game_time: GameTime, + /// Player character's facing direction for vision cone (D-015). + pub player_facing: FacingDirection, + + // --- v2 fields (unchanged) --- + /// All entities visible to the observer (filtered by LOS + vision cone). + pub entities: Vec, + /// Tiles visible to the observer for fog rendering. + pub visible_tiles: Vec, + + // --- v3 additions --- + + /// Current simulation speed multiplier. + /// 1.0 = normal, 0.5 = UI overlay slow-mo (D-PAUSE), 0.0 = paused. + #[serde(default = "default_sim_speed")] + pub sim_speed: f32, + + /// Entities within interaction range with available actions. + /// Empty if no interactable targets nearby. + #[serde(default)] + pub nearby_interactions: Vec, + + /// Active dialogue state. None if not in conversation. + #[serde(default)] + pub active_dialogue: Option, + + /// Monologue lines triggered this tick. + /// Usually 0-1 per tick. Multiple only if pacing allows it. + #[serde(default)] + pub monologue: Vec, + + /// Overheard conversation fragments (proximity-based, D-018). + #[serde(default)] + pub overheard: Vec, + + /// Knowledge panel updates (delta, not full state). + /// Only includes changes since last snapshot the client acknowledged. + #[serde(default)] + pub knowledge_updates: Vec, + + /// Examine result text (if player examined an entity/object this tick). + #[serde(default)] + pub examine_result: Option, + + /// News ticker headlines visible at current location. + /// Empty if not near a Meridian display. + #[serde(default)] + pub ticker_headlines: Vec, +} + +fn default_sim_speed() -> f32 { 1.0 } + +// --- v3 types --- + +/// Interactive entity within range. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct NearbyInteraction { + /// StableId of the interactable entity. + pub entity_id: u64, + /// Display name as known to the observer. + pub display_name: String, + /// Available actions, sorted by priority (0 = primary). + pub actions: Vec, +} + +/// Re-exported from interaction module for wire format. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActionOption { + pub kind: ActionKind, + pub label: String, + pub priority: u8, + pub enabled: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub disabled_reason: Option, +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ActionKind { + Talk, + ExamineNpc, + ExamineObject, +} + +/// Active dialogue state sent to client each tick during conversation. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ActiveDialogue { + /// StableId of the NPC in conversation. + pub npc_id: u64, + /// NPC display name. + pub npc_name: String, + /// Relationship color for the speaker name (D-033). + pub relationship_state: RelationshipState, + /// Current NPC line to display. None if waiting for player input. + pub current_line: Option, + /// Available topics the player can raise. Empty if no choices. + pub available_topics: Vec, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct DialogueLineDisplay { + pub line_id: String, + pub text: String, + pub mood: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TopicOption { + pub topic_id: String, + pub label: String, + pub enabled: bool, +} + +/// Monologue line for client display. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct MonologueDisplay { + pub line_id: String, + pub text: String, + pub trigger: String, + /// true for anomaly/contradiction observations → urgent chime (D-038). + pub urgent: bool, +} + +/// Overheard conversation fragment. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct OverheardFragment { + /// StableId of the speaker (if known to observer). + pub speaker_id: Option, + /// Speaker name (if known). "Someone" if unknown. + pub speaker_name: String, + /// Fragment text — NOT the full line. What the listener could make out. + pub fragment: String, +} + +/// Knowledge panel delta update. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct KnowledgeUpdate { + pub update_type: KnowledgeUpdateType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum KnowledgeUpdateType { + /// Learned a new fact or upgraded confidence. + FactLearned { + fact_id: String, + confidence: String, + /// Human-readable description for the knowledge panel. + display_text: String, + }, + /// Learned something new about an entity. + AttributeLearned { + entity_id: u64, + entity_name: String, + key: String, + value: String, + }, + /// Relationship state changed. + RelationshipChanged { + entity_id: u64, + entity_name: String, + old_state: RelationshipState, + new_state: RelationshipState, + }, +} + +/// Result of examining an entity or object. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ExamineResult { + pub target_id: u64, + pub target_name: String, + /// Examine description text — what the character notices. + pub text: String, + /// Knowledge grants from the examination. + pub knowledge_updates: Vec, +} + +/// News ticker headline for Meridian display. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct TickerHeadline { + pub headline_id: String, + pub text: String, + /// Does this headline trigger a monologue? Server handles trigger separately. + pub triggers_monologue: bool, +} +``` + +### Wire Size Estimate + +| Field | Typical v3 Size | Notes | +|---|---|---| +| v2 fields (entities, tiles) | ~2-8 KB | Already measured | +| sim_speed | 4 bytes | Float | +| nearby_interactions | ~50-200 bytes | 0-3 entities × 1-2 actions each | +| active_dialogue | ~100-300 bytes | Only during conversation | +| monologue | ~0-200 bytes | Usually 0-1 per tick | +| overheard | ~0-150 bytes | Rare | +| knowledge_updates | ~0-200 bytes | Delta, usually empty | +| examine_result | ~0-300 bytes | Only when examining | +| ticker_headlines | ~0-400 bytes | Only near Meridian display | +| **v3 overhead** | **~50-1500 bytes** | **On top of v2's 2-8 KB** | + +Total v3 snapshot: ~2-10 KB per tick. At 10 tps, that's 20-100 KB/s over the IPC bridge. Well within budget — localhost MessagePack serialization handles this trivially. Even network (future multiplayer) is fine. + +### What Stig Needs to Know + +**New fields the client should handle in v3:** + +| Field | Client Action | Priority | +|---|---|---| +| `sim_speed` | Display pause indicator if 0.0; dim edges if 0.5 | P0 | +| `nearby_interactions` | Show interaction prompt for `actions[0]` (priority 0 only in v0.1) | P0 | +| `active_dialogue` | Show dialogue panel with NPC name, line text, topic options | P0 | +| `monologue` | Show monologue text, fire chime (urgent flag → urgent chime) | P0 | +| `overheard` | Show as faded text near speaker position (world-space) | P1 | +| `knowledge_updates` | Update knowledge panel if open; flash minimap dot for new entities | P1 | +| `examine_result` | Show examine text in monologue-style display (same panel, different color?) | P0 | +| `ticker_headlines` | Render on world-space Meridian display entity | P1 | + +**Client protocol handling:** +``` +if snapshot.version >= 3: + process nearby_interactions → update interaction prompt + process active_dialogue → update dialogue panel + process monologue → queue monologue display + process overheard → queue overheard display + process knowledge_updates → update knowledge panel + process examine_result → show examine text + process ticker_headlines → update ticker display +``` + +v2 snapshots still work — all new fields have serde defaults (empty vecs, None optionals). + +--- + +## Summary + +1. **Content schemas finalized.** JSON Schema for NPC profiles, dialogue pools, monologue pools. The schema IS the contract between copy and server. Serde validates on load. Copy team can start writing YAML against these schemas immediately. + +2. **Multi-verb architecture designed.** `AvailableActions` per entity with priority-sorted `ActionOption` list. v0.1 client picks priority 0 (context-sensitive single action). v0.2 removes the filter and shows all options. No throwaway code — the full pipeline exists, only the client-side rendering is simplified for v0.1. + +3. **RON converter = dual-format loader + optional build-time compilation.** Loader supports both YAML and RON via serde. `tooling/content-tools` CLI provides `validate`, `compile`, `stats`. Development loads YAML directly (fast iteration). Production ships compiled RON in `.cache/`. ~20 lines of loader code, small CLI tool. + +4. **ObserverSnapshot v3 defined.** Adds interaction options, dialogue state, monologue lines, overheard fragments, knowledge deltas, examine results, ticker headlines, simulation speed. Wire overhead: ~50-1500 bytes per tick on top of v2. Backward compatible via serde defaults. Protocol version bumped to 3. + +Everything here is additive and backward-compatible. The architecture supports v0.1's single-action simplicity while being ready for v0.2's multi-verb expansion. No throwaway. diff --git a/docs/workshops/v01-content-scoping/si-ticket-changes.md b/docs/workshops/v01-content-scoping/si-ticket-changes.md new file mode 100644 index 000000000..d2549d5b2 --- /dev/null +++ b/docs/workshops/v01-content-scoping/si-ticket-changes.md @@ -0,0 +1,295 @@ +# v0.1 Content Scoping — Ticket List (Final) + +**Author:** SI (Project Manager) +**Source:** v0.1 Content Scoping Workshop (Rounds 1-2), Wiki Review Workshop (Round 2 ticket list superseded) +**Status:** Proposals only — awaiting lead approval before database creation + +**Corrections applied (closing round):** +1. C9 → Sprint 3 (same crate as C3) +2. #326 split into Phase 1 (Sprint 3) + Phase 2 (Sprint 4) +3. NEW-9 expanded: proximity detection + `verbs[]` +4. NEW-12 killed (pause is server-only; client sends IPC command) +5. NEW-10 stays separate from #240 (one ticket per team, no shared tickets) +6. NEW-14 added: UI microcopy +7. `verbs[]` rename folded into NEW-7 +8. #237 → #88 dependency added +9. A2 file count: ~16 files (Paula's full cascade) +10. A7 description: specific 4 new keys + 2 renames, 16 total +11. B2 acceptance criteria added +12. NEW-1: Gestalt-Paula synthesis (not Gestalt-only) +13. C1: mod-compatible conventions (no overlay implementation) +14. #86 ECS component list updated to workshop output + +--- + +## Existing Ticket Updates + +| ID | Title | Action | Sprint | +|----|-------|--------|--------| +| #368 | Knowledge vocabulary for v0.1 content | **Close** — wiki at `docs/wiki/` is the delivered output. | Sprint 2 (done) | +| #369 | Wiki Review Workshop Outputs (epic) | **Repurpose** as parent epic for all v0.1 content scoping tickets. Update description to reflect this workshop's scope. | — | +| #301 | Wiki taxonomy and structure | **Assign to copy.** Downscope: Sova district structure only for v0.1. | Sprint 3 | +| #319 | Krenn System wiki entry | **Merge with A3.** Expanded: wiki page + seed.yaml reference implementation. Assign to copy. | Sprint 4 | +| #261 | Dual Lens Authoring Guide | **CRITICAL PATH ROOT.** Assign to copy, owner Paula. Blocks 9 downstream tickets. | Sprint 3 (P0) | +| #310 | Character voice speech patterns | **Subsume into B4.** Close #310 when B4 lands. | Sprint 3 (via B4) | +| #306 | News ticker / Meridian feed content | **Confirm scope:** ~20 dual-lens headlines. Assign to copy. | Sprint 4 | +| #326 | YAML content loader | **Split into two tickets.** Phase 1: loader skeleton + fixture data (Sprint 3, server, blocked by C1+C2+C3). Phase 2: real content loading (Sprint 4, server, blocked by C4-C8 + NEW-3). | Sprint 3 / 4 | +| #86 | Core NPC components | **Update ECS component list** to workshop output: ThematicPattern, FunctionalMotivation, FriendArc, ContentTier, AccessTierMap, TrustLevels, NpcRoutine, MoodState, TriangleMembership, InteractionLog. | Sprint 3 | +| #88 | Daily routine system | **Add dependency:** blocked by #237 (A* pathfinding). Needs NPC routine YAML from content conversion. | Sprint 3 | + +--- + +## New Tickets + +### A. Wiki Content Fixes (copy team) + +| ID | Title | Type | Pri | Sprint | Blocked By | Description | +|----|-------|------|-----|--------|------------|-------------| +| A1 | Apply canonical full names to all NPC wiki pages | task | med | 3 | — | Apply Miri's 12 full names to NPC wiki pages. Update Core Identity blocks, environmental text references (manifests, shift rosters, Commission kiosk). | +| A2 | Rename Hael → Naia Tamm across all wiki files | task | med | 3 | — | Phonetic collision fix (Kael/Hael). Find-and-replace across ~16 files (Paula's full cascade). Triangle 4 becomes Naia-Kael-Sera. Update all relationship references, triangle diagrams, cross-references. | +| A6 | Add smuggler-perspective FactId progression text | task | high | 4 | B4 | Add smuggler-path text for all FactIds where smuggler starts at KnowsDetails or has different knowledge path than detective. | +| A7 | Update entity-attributes.md — smuggler role-perspective keys | task | high | 3 | — | Add 4 new role-perspective keys: `risk_assessment`, `loyalty_assessment`, `position_integrity`, `moral_weight`. Rename `secret_held` → `leverage_held` (broader scope: secrets, debts, promises, obligations). Rename `secret_confidence` → `leverage_confidence`. Update `trust_level` description to absorb `trust_read` scope. All keys are observer→target. Total after update: 16 canonical keys. | +| A8 | Standardize location shortcodes in monologue-guide.md | task | med | 3 | — | Fix hub_m_ → terminal_m_ inconsistency. Canonical shortcodes: terminal, bar, corridor. Add shortcode reference table. Add situation overlap rules and mood exclusivity rules. | +| A10 | Stub NPC profile for Nils Davan (off-stage) | task | med | 3 | — | Minimal off-stage profile: Core Identity, relationship to Kael (siblings), role in ring (upstream pressure), known attributes by other NPCs. | +| A11 | Fix Triangle 1 (Hub Power) — Volume Escalation decision | story | high | 3 | A10 | Add smuggler decision fork: side with Nils (louder signal), side with Voss (internal tension), or split the route (new exposure surface). Each option cascades within T1 only — self-contained, no cross-triangle cascade (A-17). Includes Nils partial presence via lattice message (~5-8 lines). | +| A12 | Create environmental text examples file | task | med | 4 | A3/#319 | Create wiki/authoring/environmental-text-examples.md. 4 text types: bar menu, safety notice, news ticker cycle, corridor graffiti. Krenn-voiced with cultural notes + monologue pairings (smuggler + detective). Needed for wow moment #1 (Arrival). | + +### B. Style Guides & Specs (copy team) + +| ID | Title | Type | Pri | Sprint | Blocked By | Description | +|----|-------|------|-----|--------|------------|-------------| +| B1 | NPC Authoring Style Guide | story | crit | 5 | A3, A4, A7, B2, B4 | Combined document: tier templates (Paula), authoring rules (Mellanie conventions, Gestalt constraints, Ozzie anchor line), regional culture (Miri), content pipeline (Tyre). Reframed as generator specification — this IS the NPC generator input format. Most complex ticket; many blockers. | +| B2 | THE MIRROR pattern spec | story | high | 3 | — | Define honest-character NPC pattern. No contradiction arc, no secret, no tell. Tier 1 emotional depth. **v0.1 acceptance criteria:** Pattern definition with mechanical rules, content requirements (50-70 lines, 10-axis profile), tell system rules (MIRROR has none), Naia Tamm as reference implementation. Output becomes generator template. | +| B3 | PC-as-NPC unified spec | story | high | 3 | A7 | Unified spec: starting knowledge graph, starting relationship map, reverse knowledge (bidirectional — Gestalt's critical insight), orientation monologue (Ozzie's first 60 seconds), agency boundaries, authored relationship stubs (Gore's requirement), tell inversion. 9-step conversion checklist for content authors. v0.1 scope: 2 PC briefs (smuggler, detective). | +| B4 | Smuggler voice card + moral arc spec | story | high | 3 | A7 | Two deliverables: (1) Smuggler voice card — paired examples with detective showing dual-lens register differences. Thinks in people vs data, first names vs surnames, fragments vs complete sentences, Krenn substrate vocabulary. (2) Smuggler moral arc — 4 phases (Comfort→Doubt→Reckoning→Compromise), smuggler-specific FactIds, monologue triggers. Subsumes #310 (Character voice speech patterns). | +| B5 | NPC-format briefs for both PCs — Concept A v0.1 | story | high | 4 | B3 | When you play the smuggler, the detective exists as an NPC (and vice versa). NPC-mode behavior scripts (5-7 routine entries each), reverse dual-lens notes, observation monologue for PC seeing other-PC-as-NPC (3-5 lines per character). ~20-30 authored items total. | + +### C. Content Directory & Schema (split teams) + +| ID | Title | Type | Pri | Sprint | Team | Blocked By | Description | +|----|-------|------|-----|--------|------|------------|-------------| +| C1 | Content directory structure design doc | story | high | 3 | copy | — | Document converged structure: `_meta/` + `_schema/` (infrastructure, underscore prefix), `global/` + `districts/` (game content). Canonical_id format (`{system}.{station}.{district}.{type}.{slug}`). Mod-compatible directory conventions (no overlay implementation). District as atomic pack unit. Informs all content implementation. | +| C2 | Create content/ directory skeleton | task | high | 3 | server | C1 | Create directory tree with empty YAML stubs. `_meta/`, `_schema/`, `global/` (factions, technology, contraband, knowledge, enums, regions), `districts/sova-transit/` (district.yaml, npcs/, locations/, templates/, triangles/, lines/). | +| C3 | Content schema definitions (YAML Schema files) | story | high | 3 | server | C1 | Create `_schema/` with validation schemas: npc.schema.yaml (tier-conditional rules), location.schema.yaml, faction.schema.yaml, template.schema.yaml, fact.schema.yaml, dialogue.schema.yaml, monologue.schema.yaml, district.schema.yaml, pool.schema.yaml. | +| C4 | Create global/enums/ YAML files from D-035 | task | med | 4 | server | C2 | Populate enums: situations (13), topics (9), moods (8), triggers (9), access-tiers (5), activities (new — from Gestalt routine format + Miri cultural patterns). | +| C5 | Create global/knowledge/entity-attributes.yaml | task | med | 4 | server | C2 | Machine-readable entity attribute definitions. 16 canonical keys after A7 update (14 original, 4 new, 2 renamed). | +| C6 | Create pools.yaml for v0.1 seed-time selection | task | med | 4 | server | C2 | Sova single-candidate pools: friend_smuggler (kael-davan), friend_detective (sera-venn), bar_regulars, compromised_inspector, primary_contraband. Architecture supports N candidates; v0.1 has 1 each. | +| C7 | Create template YAML files with role slot definitions | task | med | 4 | server | C2 | 3 social sites: logistics-hub.yaml, bar.yaml, smuggling-ring.yaml. Each declares named role slots with count ranges, required flags, pool references, triangle constraints. | +| C8 | Create triangle YAML files for 5 v0.1 triangles | task | med | 4 | server | C2 | 5 triangles: hub-power (T1, active fork), worried-knowledge (T2, active fork), bar-tensions (T3, passive), worried-partner (T4, active fork), informant-question (T5, passive). Self-contained forks — no cross-triangle cascade (A-17). | +| C9 | Implement make validate-content CLI | story | high | 3 | server | C3 | Structural + tier-conditional content validation. Same crate as C3 schemas. Reads `_schema/` definitions, validates all YAML in `content/`. v0.1 scope: structural validation (required fields, type checks, enum matching) + tier-conditional rules. v0.2: cross-reference validation (FactId existence, NPC slug resolution). | +| C10 | Pre-commit FactId typo check | task | med | 4 | ci | — | Grep-based pre-commit check validating FactId references in YAML content files against canonical facts.yaml. Cheap v0.1 bridge until full cross-reference validation lands in C9 v0.2. | + +### D. Design Specs + +| ID | Title | Type | Pri | Sprint | Team | Blocked By | Description | +|----|-------|------|-----|--------|------|------------|-------------| +| D2 | Seed configuration schema design | story | med | 4 | server | — | Schema for what the randomizer produces at game-start: FRIEND selections, social site population, compromised NPC, contraband type, entanglement overrides. v0.1 has single-candidate pools but architecture must support N. Saved with game state. Location: runtime/ (not content/). Constrained by D-029 ratio (30/50/20). | +| D4 | News ticker headline pool | task | med | 4 | copy | — | ~20 dual-lens headlines for Meridian feed at Lera's. Mix: freight/commodity data, Assembly politics, local infrastructure, sports, Commission advisories. Each headline works as a dual-lens monologue trigger. | + +### NEW. Tickets from Workshop Rounds 1-2 + +| ID | Title | Type | Pri | Sprint | Team | Blocked By | Description | +|----|-------|------|-----|--------|------|------------|-------------| +| NEW-1 | Apply Round 2 NPC pattern/motivation mapping | task | high | 3 | copy | — | Apply the Round 2 finalized NPC pattern/motivation mapping (Gestalt-Paula synthesis) to wiki and content files. 17 NPCs × (pattern + motivation). Includes Harek's structural role formalization (Paula Gap 7). | +| NEW-2 | Drin Tier 2 profile expansion | task | med | 3 | copy | NEW-1 | Drin confirmed promoted to Tier 2 (A-01 consensus). Expand wiki profile to full Tier 2 depth: all 10 profile axes, smuggler lens section, triangle membership formalized for T2 and T5. | +| NEW-3 | Wiki → YAML content conversion (Sova NPCs) | story | high | 4 | copy | C1, C2, C3 | Convert 17 NPC wiki profiles to engine-readable YAML in content/districts/sova-transit/npcs/. Includes routine YAML layer (Paula Gap 3), triangle fork decision points (Gap 4), location smuggler detail (Gap 6). Must match schemas from C3. Tyre estimates 2-3 full days. | +| NEW-4 | Monologue content pool — minimum viable (~200 lines) | story | high | 4 | copy | #261, B4 | ~100 lines per character. Categories: arrival observations, NPC routine commentary, environmental flavor, contradiction hints, idle reflections, tutorial/orientation. Follows Mellanie's authoring order (FRIEND packs first, then fill to ~200). | +| NEW-5 | v0.1 wow moment content checklist | task | high | 3 | copy | — | Map D-039's 6 wow moments to specific content deliverables + implementation tickets. Track completion. Acceptance: every moment has identified content + code backing it. | +| NEW-6 | PC-as-NPC content authoring | story | high | 5 | copy | B3, B5, #297, #298 | Author the actual PC-as-NPC lines: 5-7 routine entries per character, 3-5 observation monologue lines, reverse dual-lens notes. ~25 authored items (Mellanie's budget). Blocked by FRIEND packs — must know who the characters ARE before writing how they appear as NPCs. | +| NEW-7 | v0.1 interaction verb spec + `verbs[]` terminology | task | med | 3 | copy | — | Document the 7 interaction types (A-04: Move, Look, Monologue, Examine Object, Examine NPC, Talk, Overhear). Define triggers, UI needs, server events per type. v0.1 surfaces single context-sensitive action; architecture supports N verbs via `verbs[]`. Includes wiki + decisions rename: "interaction" → "`verbs[]`" terminology where appropriate. Soft dependency for NEW-9. | + +### NEW-R2. Tickets from Lead Round 2 Decisions + +| ID | Title | Type | Pri | Sprint | Team | Blocked By | Description | +|----|-------|------|-----|--------|------|------------|-------------| +| NEW-8 | YAML → RON converter tool | task | med | 4 | server | C1 | Build-time converter: reads content YAML, emits RON. Lives in `tooling/`. Runs as make target (`make content-ron`). Not on critical path — engine consumes YAML in v0.1; RON is future-proofing for engine performance if needed. | +| NEW-9 | Proximity detection + multi-verb InteractionOptions (`verbs[]`) | story | high | 3 | server | — | Proximity detection system + server-side `InteractionOptions` component modeling N available actions per entity via `verbs[]`. Without proximity, InteractionOptions has no trigger. v0.1 client picks first/best verb; v0.2 client shows menu. Must not be throwaway. Includes ObserverSnapshot v2 → v3 protocol extension for interaction data. Soft dependency on NEW-7 (verb definitions). | +| NEW-10 | Interaction prompt — client extensible prompt system | story | high | 3 | client | NEW-9 | Client-side interaction prompt designed for extensibility. v0.1: single context-sensitive E prompt. Architecture supports v0.2 multi-verb menu. Prompt system reads `InteractionOptions` from server state. Separate from #240 (Player interaction system and dispatcher) — different team, different definition of done. | +| NEW-11 | Pause system — server tick rate scaling | story | med | 3 | server | — | Tick rate supports 3 states: full speed (normal play), 50% speed (UI overlay open — knowledge panel, dialogue), 0% (spacebar pause). Server receives pause/speed commands from client via IPC. | +| NEW-13 | Line previewer CLI | task | med | 4 | server | C3, C9 | CLI tool to preview monologue/dialogue line selection for a given game state. Lives in `tooling/`. Helps content authors test prerequisite logic without running the full game. Tyre's recommendation for content tuning. | +| NEW-14 | UI microcopy | task | med | 5 | client | Stig UI spec | ~50 strings: interaction prompt labels, knowledge panel labels, relationship state descriptors, HUD labels, tutorial text. Blocked by Stig's UI element specs from Sprint 3. | + +--- + +## Sprint Plan + +### Sprint 3: Know — Copy Team (Content Foundation) + +**Goal:** Deliver content specs and wiki fixes that unblock server implementation and content authoring. + +| Pri | Ticket | Title | Owner Hint | Effort | +|-----|--------|-------|------------|--------| +| **P0** | **#261** | **Dual Lens Authoring Guide** | **Paula** | **Large** | +| **P0** | **C1** | **Content directory structure design** | **Tyre/Gestalt** | **Medium** | +| P0 | A1 | Apply canonical full names | Paula | Small | +| P0 | A2 | Hael → Naia Tamm rename (~16 files) | Paula | Small | +| P0 | A7 | Smuggler role-perspective keys (16 total) | Paula | Small | +| P1 | B2 | MIRROR pattern spec + Naia reference impl | Gestalt/Gore | Medium | +| P1 | B4 | Smuggler voice + moral arc (subsumes #310) | Gore/Mellanie | Medium | +| P1 | NEW-1 | NPC pattern/motivation mapping (Gestalt-Paula synthesis) | Gestalt | Small | +| P1 | NEW-5 | Wow moment content checklist | SI | Small | +| P1 | A10 | Nils Davan stub | Paula | Small | +| P1 | A11 | Triangle 1 fix (Volume Escalation) | Gestalt/Paula | Medium | +| P2 | B3 | PC-as-NPC spec | Gestalt/Paula | Medium | +| P2 | A8 | Shortcode fix | Mellanie | Small | +| P2 | NEW-2 | Drin Tier 2 profile expansion | Paula | Small | +| P2 | NEW-7 | Interaction verb spec + `verbs[]` terminology | Gestalt | Small | + +**Copy Sprint 3 total: 15 tickets (5 P0, 6 P1, 4 P2)** + +### Sprint 3: Know — Server Team (Content-Adjacent Additions) + +| Pri | Ticket | Title | Effort | +|-----|--------|-------|--------| +| P1 | C2 | Content/ directory skeleton | Small | +| P1 | C3 | Content schema definitions | Medium | +| P1 | C9 | validate-content CLI (same crate as C3) | Medium | +| P1 | NEW-9 | Proximity detection + InteractionOptions (`verbs[]`) | Medium | +| P1 | #326-P1 | Content loader Phase 1 — skeleton + fixtures | Medium | +| P2 | NEW-11 | Pause system — tick rate scaling | Small | + +**Server Sprint 3 additions: 6 tickets (in addition to existing 10)** + +### Sprint 3: Know — Client Team (Additions) + +| Pri | Ticket | Title | Effort | +|-----|--------|-------|--------| +| P1 | NEW-10 | Interaction prompt — extensible prompt system | Medium | + +**Client Sprint 3 addition: 1 ticket** + +### Sprint 4: Content Conversion + Authoring Begins + +| Ticket | Title | Team | Blocked By | +|--------|-------|------|------------| +| NEW-3 | Wiki → YAML conversion (17 NPCs) | copy | C1, C2, C3 | +| C4 | Enum YAMLs from D-035 | server | C2 | +| C5 | Entity-attributes YAML | server | C2 | +| C6 | Pools YAML | server | C2 | +| C7 | Social site template YAMLs | server | C2 | +| C8 | Triangle YAMLs | server | C2 | +| C10 | Pre-commit FactId check | ci | — | +| A6 | Smuggler FactId progression text | copy | B4 | +| A12 | Environmental text examples | copy | A3/#319 | +| B5 | PC NPC-format briefs | copy | B3 | +| D4 | News ticker headlines (~20) | copy | — | +| #306 | News ticker content (align with D4) | copy | — | +| D2 | Seed configuration schema | server | — | +| NEW-4 | Monologue content pool (~200 lines) | copy | #261, B4 | +| NEW-8 | YAML → RON converter | server | C1 | +| NEW-13 | Line previewer CLI | server | C3, C9 | +| #326-P2 | Content loader Phase 2 — real content | server | C4-C8, NEW-3 | +| A3/#319 | Krenn brief + seed.yaml reference | copy | — | + +### Sprint 5+ (Content at Scale) + +| Ticket | Title | Team | Blocked By | +|--------|-------|------|------------| +| NEW-6 | PC-as-NPC content authoring (~25 items) | copy | B3, B5, #297, #298 | +| NEW-14 | UI microcopy (~50 strings) | client | Stig UI spec | +| B1 | NPC Authoring Style Guide (generator spec) | copy | A3, A4, A7, B2, B4 | +| A4 | Regional style guide / seed.yaml template | copy | — | +| A9 | Thematic Question field (Tier 1 profiles) | copy | B2 | +| #297 | THE FRIEND: Kael content pack | copy | #261 | +| #298 | THE FRIEND: Sera content pack | copy | #261 | +| #190 | Workplace content pack | copy | #261 | +| #191 | Bar content pack | copy | #261 | +| #192 | Smuggling ring content pack | copy | #261 | + +--- + +## Dependency Graph + +``` +SPRINT 3 — Three Parallel Chains +================================== + +Chain 1: Content Authoring Pipeline (copy) +------------------------------------------- +#261 (Dual Lens Guide) [P0, CRITICAL PATH ROOT] + ├─► B4 (Smuggler voice + moral arc) [Sprint 3] + │ ├─► A6 (Smuggler FactId text) [Sprint 4] + │ └─► NEW-4 (Monologue pool) [Sprint 4] + ├─► #297 (FRIEND: Kael) [Sprint 5] + │ └─► NEW-6 (PC-as-NPC authoring) [Sprint 5] + ├─► #298 (FRIEND: Sera) [Sprint 5] + │ └─► NEW-6 (PC-as-NPC authoring) [Sprint 5] + ├─► #299 (Opening hook: smuggler) [Sprint 5] + ├─► #300 (Opening hook: detective) [Sprint 5] + └─► #190, #191, #192 (Content packs) [Sprint 5] + +A7 (Smuggler attributes) [P0, Sprint 3] + ├─► B4 (Smuggler voice) [Sprint 3] + └─► B3 (PC-as-NPC spec) [Sprint 3] + └─► B5 (PC briefs) [Sprint 4] + └─► NEW-6 (PC-as-NPC authoring) [Sprint 5] + +Chain 2: Content Architecture Pipeline (server) +------------------------------------------------- +C1 (Content dir design) [P0, Sprint 3, copy] + ├─► C2 (skeleton) [Sprint 3, server] + │ ├─► C4 (enums) [Sprint 4] + │ ├─► C5 (entity attrs) [Sprint 4] + │ ├─► C6 (pools) [Sprint 4] + │ ├─► C7 (templates) [Sprint 4] + │ ├─► C8 (triangles) [Sprint 4] + │ ├─► NEW-3 (wiki→YAML) [Sprint 4, copy] + │ └─► #326-P2 (loader Phase 2) [Sprint 4] + ├─► C3 (schemas) [Sprint 3, server] + │ ├─► C9 (validator) [Sprint 3, server] + │ ├─► NEW-3 (wiki→YAML) [Sprint 4, copy] + │ └─► NEW-13 (line previewer) [Sprint 4] + ├─► #326-P1 (loader Phase 1) [Sprint 3, server] + └─► NEW-8 (RON converter) [Sprint 4] + +Chain 3: Interaction Pipeline (server + client) +------------------------------------------------- +NEW-7 (Verb spec) [Sprint 3, copy] ···soft···> NEW-9 +NEW-9 (Proximity + InteractionOptions) [Sprint 3, server] + └─► NEW-10 (Extensible prompt) [Sprint 3, client] + +Independent (Sprint 3) +------------------------ +B2 (MIRROR spec) ─► A9 (Thematic Question) [Sprint 4] +A10 (Nils stub) ─► A11 (Triangle 1 fix) [Sprint 3] +NEW-11 (Tick rate scaling) [Sprint 3, server] — no downstream blockers + +Existing Server Dependencies +------------------------------ +#237 (A* pathfinding) ─► #88 (Daily routine system) [both Sprint 3] +``` + +--- + +## Ticket Count + +| Category | Count | Teams | +|----------|-------|-------| +| Wiki fixes (A-series) | 8 | copy | +| Style guides & specs (B-series) | 5 | copy | +| Content directory & schema (C-series) | 10 | copy (1), server (8), ci (1) | +| Design specs (D-series) | 2 | server (1), copy (1) | +| Workshop tickets (NEW 1-7) | 7 | copy | +| Lead decision tickets (NEW 8-14, excl. killed NEW-12) | 6 | server (4), client (2) | +| **Total new tickets** | **38** | **copy (21), server (13), client (2), ci (1)** | +| Existing ticket updates | 10 | — | +| **Killed from Round 2** | **1** | NEW-12 (client pause — not a ticket) | + +*Note: count is 38, not 37 — NEW-14 (UI microcopy) was added in closing corrections. 38 original − 1 killed (NEW-12) + 1 added (NEW-14) = 38.* + +--- + +## Risks + +1. **#261 is the critical path root.** Blocks 9 tickets directly. If it slips one sprint, the entire content authoring pipeline delays into Sprint 5+. Recommend: assign Paula immediately, time-box to 5 working days. + +2. **Sprint 3 server team is heavy.** 10 existing tickets + 6 new content-adjacent additions = 16. Mitigation: C2, C3, C9, #326-P1 are all blocked by C1 (copy team) — they start mid-sprint. NEW-9 and NEW-11 are independent and can begin immediately. + +3. **Content conversion (NEW-3) is a Sprint 4 bottleneck.** Tyre estimates 2-3 full days for 17 NPC conversions. Blocks server from testing with real data. Recommend: start with 3 Tier 1 NPCs (Kael, Sera, Naia) as soon as C2 lands; don't wait for all 17. + +4. **#237 → #88 server dependency.** Pathfinding must land before routines. Both are Sprint 3. If #237 slips, NPC routines slip, which delays routine deviation detection (#243), which delays wow moment #2 (The Character's Eye). + +--- + +*Proposals only — no tickets created in database. Awaiting lead approval.* +*Compiled by SI. Source: v0.1 Content Scoping Workshop Rounds 1-2, lead closing corrections, Qatux tracking doc.* diff --git a/docs/workshops/v01-content-scoping/v01-content-scoping-workshop-brief.md b/docs/workshops/v01-content-scoping/v01-content-scoping-workshop-brief.md new file mode 100644 index 000000000..810146e72 --- /dev/null +++ b/docs/workshops/v01-content-scoping/v01-content-scoping-workshop-brief.md @@ -0,0 +1,227 @@ +# Workshop Brief: v0.1 Content Scoping +## What Actually Ships in the Vertical Slice? + +**Project:** The Settled Reach (D-021) +**Date:** 2026-02-12 +**Called by:** Jeroen +**Prerequisite:** Wiki Review workshop established long-term content strategy (300-world generator model, cultural ingredients menu, three-system NPC architecture, Sacred/Profane/Middle Kingdom). This workshop applies that strategy to v0.1. +**Participants:** GESTALT, PAULA, TYRE, MELLANIE, STIG, DUDLEY + SI (tickets) + QATUX (documenter) + +--- + +## Context + +The Wiki Review workshop (4 rounds + lead interview) produced a comprehensive long-term content strategy documented in `docs/workshops/wiki-review/SUMMARY.md`. The key outcome: the full game targets 300 worlds populated by generators fed by style briefs. But **v0.1 is NOT a generator test. v0.1 is proof of the interaction model** — 1 district, hand-authored, proving that the player can touch the world and the world can touch back. + +The existing wiki (`docs/wiki/`, 45 files, ticket #368) contains the content for Sova Transit District: 17 NPCs across 3 tiers, 5 triangles, 3 social sites, knowledge vocabulary (FactIds, entity attributes, relationship states), and an authoring guide. This content has known gaps and issues identified in Rounds 1-2 of the Wiki Review. + +**This workshop scopes and prioritizes the content work needed to ship v0.1.** + +**Key constraint:** v0.1 must be architecturally compatible with the 300-world future. Hand-authored Sova content becomes the benchmark output that generators must match. Every structure we create now is a generator template later. Don't build anything that only works for 1 district. + +**Design constraints from existing decisions:** +- D-005: Single-character perspective +- D-020: Godot 4 client + Rust/bevy_ecs server +- D-027: Smuggler + detective vertical slice, two-character proof +- D-034: THE FRIEND production-level NPC pattern (Kael, Sera) +- D-035: Monologue system with tagged prerequisites +- D-036: Krenn System / Sova Transit District setting +- D-037: Contraband types +- D-039: 6 wow moments in a 30-minute session +- D-041: Knowledge graph architecture +- Wiki Review: WASD direct control, context-sensitive interaction + +--- + +## Required Reading + +Before responding, read: +1. `docs/workshops/wiki-review/SUMMARY.md` — the long-term content strategy (essential context) +2. `docs/workshops/wiki-review/lead-interview.md` — lead's decisions on cultural ingredients, NPC composition, archetypes, randomization, scale +3. `docs/wiki/index.md` — current wiki structure +4. `docs/wiki/npcs/index.md` — NPC roster and triangles +5. `decisions/content.md` — D-028, D-034, D-035, D-036, D-037 +6. `decisions/scope.md` — D-027, D-039 + +--- + +## Topic 1: Wiki Fixes — Known Issues from Rounds 1-2 + +**What needs fixing in the existing wiki before it can drive implementation?** + +The Wiki Review identified these specific issues: + +### 1a. Hael → Naia Tamm rename +Unanimous decision. Resolves Kael/Hael sonic collision (T-03). Hael becomes Naia Tamm, following Krenn naming conventions. This NPC is also the first instance of THE MIRROR pattern — honest, no contradiction arc, no secret. + +- **Paula:** Execute the rename across all wiki files. Update relationship references, triangle diagrams, cross-references. +- **Mellanie:** Review Naia Tamm's voice — does she need voice samples now that she's a MIRROR (Tier 1 emotional depth)? + +### 1b. Smuggler lens parity +The wiki is detective-shaped. The smuggler perspective needs equal depth: +- Smuggler-specific NPC attributes: `trust_read`, `exposure_risk`, `loyalty_assessment`, `leverage_held`, `social_debt`, `moral_weight`, `cover_integrity`, `loyalty_conflict` +- Moral arc phases for smuggler: Comfort → Doubt → Reckoning → Compromise +- Every NPC profile needs a smuggler-lens section with the same depth as the detective-lens section + +- **Paula:** Propose the smuggler attribute vocabulary and add smuggler-lens sections to all NPC profiles. +- **Gestalt:** How do smuggler attributes map to EntityKnowledge keys in the knowledge graph? + +### 1c. Triangle 1 (Hub Power) strengthening +Weakest triangle. Smuggler role is passive. Needs a concrete fork — the Nils-vs-Voss escalation decision. +- Nils needs a stub wiki page at minimum +- The player needs a decision point, not just observation + +- **Gestalt:** Design the Triangle 1 fork. What does the smuggler decide? What does the detective observe? +- **Paula:** Write the Nils stub page. Define Triangle 1's escalation sequence. + +### 1d. Drin tier promotion +Drin is doing Tier 2 work (Triangle 2, Triangle 5, compromised by Harek) in a Tier 3 profile. + +- **Gestalt:** Should Drin be promoted to Tier 2? What are the mechanical implications? +- **Paula:** If promoted, expand the profile to full Tier 2 depth. + +### 1e. Full canonical names +Miri generated 12 Krenn-style names in Round 1. Apply them to NPCs currently using single names. + +- **Paula:** Apply Miri's names where appropriate. Check naming consistency. + +**Questions:** +1. **Paula:** Are there other wiki gaps not listed above? What did you discover during Round 1-2 review that still needs fixing? +2. **Gestalt:** Do any triangles need restructuring beyond Triangle 1? +3. **Mellanie:** Are the voice samples in Tier 1 profiles (Kael, Sera) distinct enough for monologue authoring? + +--- + +## Topic 2: v0.1 NPC Architecture + +**Which subset of the three-system NPC model ships in v0.1?** + +The long-term strategy defines three NPC systems: +- **System A:** 9 thematic patterns (FRIEND, MIRROR, ANCHOR, GHOST, CATALYST, THRESHOLD, REMNANT, SYSTEM, NOBODY) +- **System B:** 6 functional motivations (HANDLER, WITNESS, TURNCOAT, CIVILIAN, OPERATOR, SKEPTIC) +- **System C:** Composition rules (pattern x motivation per NPC) + +v0.1 has 17 NPCs across 3 tiers. Not all patterns and motivations need to be present. + +**Questions:** +1. **Gestalt:** Which patterns and motivations are REQUIRED for v0.1? Which can be deferred? Map the existing 17 NPCs to pattern + motivation assignments. +2. **Paula:** Do the existing NPC profiles already implicitly match patterns/motivations? Make the mapping explicit. +3. **Gestalt:** Does THE NOBODY ship in v0.1, or is dynamic tier promotion a v0.2 feature? If v0.1, which NPCs are NOBODYs? +4. **Dudley:** What server-side entity data does each pattern/motivation require? What's the ECS component design? + +--- + +## Topic 3: v0.1 Content Directory Structure + +**How does wiki content become engine-readable data?** + +The wiki (`docs/wiki/`) is the authoring source. The engine needs content in a structured format the server can load. The directory structure must: +- Support the v0.1 content (1 district, hand-authored) +- Be architecturally compatible with the 300-world generator output (same schema, same addressing) +- Allow hot-reloading during development +- Support content validation + +**Questions:** +1. **Tyre:** Propose the `content/` directory structure for v0.1. What format — YAML? RON? How does it mirror the wiki structure? +2. **Tyre:** How does content addressing work? `canonical_id` per entity? How do wiki pages map to content files? +3. **Dudley:** What does the server content loader need? What's the load sequence? How does an NPC profile become a spawned entity? +4. **Gestalt:** What's per-district vs. global? Where does the FactId catalog live? Where do NPC profiles live? Where do triangle definitions live? +5. **Stig:** What does the client need from content files? NPC display names? Dialogue text? Location descriptions? What format makes client loading simple? + +--- + +## Topic 4: Interaction Model Basics for v0.1 + +**What's the minimum viable interaction for the vertical slice?** + +The Control & Interaction workshop will design the full system. But v0.1 needs SOMETHING — the player must be able to: +- Walk up to an NPC and initiate dialogue +- Receive monologue observations +- Examine objects/environment +- Overhear conversations (proximity-based) + +This topic scopes the v0.1 minimum, not the full design. + +**Questions:** +1. **Gestalt:** What's the minimum interaction set that proves the core loop? Walk + talk + observe + monologue? Or does v0.1 need more? +2. **Stig:** What UI elements does the minimum interaction model need? Interaction prompt? Dialogue box? Monologue display? Knowledge panel? +3. **Dudley:** What server events support the minimum interaction? Proximity triggers? Interaction requests? Dialogue state? +4. **Mellanie:** How much monologue content does v0.1 need? What's the minimum pool size before it feels repetitive? + +--- + +## Topic 5: v0.1 Scope Boundary + +**What's IN v0.1 and what's explicitly OUT?** + +v0.1 proves the interaction model. It does NOT prove generators, randomization, or cultural variety. Be ruthless about scope. + +**Candidates for IN:** +- WASD movement + collision +- Context-sensitive interaction (approach NPC → prompt → dialogue) +- Monologue system (tagged triggers, FactId prerequisites) +- Knowledge graph (player learns facts, relationships change) +- 2 playable characters (smuggler, detective) seeing same world differently +- THE FRIEND arc (Kael for smuggler, Sera for detective) +- 5 triangles with at least 1 fork each +- 6 wow moments (D-039) +- 3 social sites (Terminal, Last Shift, Maintenance Corridors) +- NPC routines (schedule-based movement) + +**Candidates for OUT (v0.2+):** +- THE NOBODY / dynamic tier promotion +- Archetype transitions +- Multiple FRIEND candidates per character +- Secondary contraband variation +- Gate network / multi-world +- Content generation / randomization +- Modding support + +**Questions:** +1. **Gestalt:** Is this IN/OUT split correct? Anything missing from IN that's essential for the 30-minute session? Anything in IN that's scope creep? +2. **Tyre:** What's the technical risk in the IN list? What might take longer than expected? +3. **Dudley:** What server features are blocked by content that doesn't exist yet? +4. **SI:** Map the IN list to tickets. What already has tickets? What needs new ones? + +--- + +## Topic 6: Ticket Generation + +**What implementation tickets come out of this workshop?** + +SI had a preliminary list of 28 tickets from Wiki Review Round 2, but the 300-world reframe makes some obsolete and creates new ones. + +**Expected ticket categories:** +- Wiki fixes (rename, smuggler lens, triangle strengthening, Drin promotion, canonical names) +- Content file creation (wiki → YAML/RON conversion for engine) +- Content directory structure setup +- Content loader implementation (server-side) +- Content validation schema +- Minimum interaction model (client UI elements) +- Monologue content pool (minimum viable) +- PC-as-NPC content (the other character exists as an NPC) + +**Questions:** +1. **SI:** Review the Round 2 ticket list (`docs/workshops/wiki-review/si-ticket-changes.md`). Which survive? Which are obsolete? What's new? +2. **SI:** Which tickets belong to Sprint 3 (Know) vs. later sprints? What's the dependency chain? +3. **Tyre:** What tickets should be split between server/client/copy teams? + +--- + +## Workshop Format + +**1-2 rounds.** + +**Round 1:** Topic responses. Each participant addresses their assigned questions with concrete proposals. Wiki fixes should include actual content (not just "we should fix this"). Directory structures should be actual file trees. Ticket proposals should have title, description, and team assignment. + +**Round 2:** Integration + ticket finalization. Resolve disagreements, finalize the IN/OUT boundary, produce the ticket list. SI creates tickets. Qatux documents decisions. + +## Expected Outputs + +- **Wiki fix list** with assignments and content ready to apply +- **NPC pattern/motivation mapping** for all 17 v0.1 NPCs +- **Content directory structure** with schema proposal +- **v0.1 interaction model minimum** — enough for Stig/Dudley to implement +- **Scope boundary document** — clear IN/OUT with rationale +- **Ticket list** — implementation tickets with team assignments and sprint targets +- **Decision amendments** — updates to existing decisions based on scoping