# Conflicts: # CHANGELOG.md # content/_meta/README.md # content/_meta/npc-authoring-style-guide.md # wiki/_templates/cultural-group.md # wiki/_templates/institution.md # wiki/_templates/star-system.md # wiki/characters/devra.md # wiki/characters/drin.md # wiki/characters/harek.md # wiki/characters/lera-sessik.md # wiki/characters/maret-korr.md # wiki/characters/naia-tamm.md # wiki/characters/nils-davan.md # wiki/characters/pell.md # wiki/characters/renn.md # wiki/characters/resha.md # wiki/characters/sabel.md # wiki/characters/sera-venn.md # wiki/characters/torek-lintar.md # wiki/characters/voss.md # wiki/star-systems/krenn/index.md
451 lines
24 KiB
Markdown
451 lines
24 KiB
Markdown
# Tier 1 Drama Module — Authoring Guide
|
||
|
||
**Schema:** `content/schemas/drama_module.schema.yaml`
|
||
**Module pool:** `content/modules/tier1/*.yaml`
|
||
**Decisions:** D-023 (three-tier model), D-027 (vertical slice), D-029 (30/50/20 population), D-034 (FRIEND pattern)
|
||
**Vertical slice reference:** `content/modules/tier1/smuggling_ring_v0_1.yaml`
|
||
|
||
---
|
||
|
||
## What Is a Tier 1 Drama Module?
|
||
|
||
Tier 1 is the authored conspiracy layer of D-023. Drama modules are the things that can go wrong — or go very right, or simply happen — beneath the surface of daily life in Sova Transit. They are:
|
||
|
||
- **Hand-authored.** Every event sequence, every NPC role, every outcome was written by a person.
|
||
- **Pool-based.** Multiple modules exist. The storyteller draws from the pool at game start and activates a subset based on the district and the storyteller's pacing decisions.
|
||
- **Optional from the player's perspective.** The player can play 60 minutes without engaging the ring. The ring happens anyway. D-027 criterion #4: the observe→notice→follow→discover sequence must emerge from *systems*, not *scripts*.
|
||
- **Dual-lens.** Every module must be experienced differently by the smuggler and detective characters. Same world, different keyholes.
|
||
|
||
What they are **not:**
|
||
- Not quests with markers or objectives.
|
||
- Not scripted cutscenes.
|
||
- Not balanced challenge encounters.
|
||
|
||
The storyteller uses the module as a *schedule* — a series of world events it will fire, and conditions it monitors to determine how the world resolves. The player is a witness and agent in a world that moves with or without them.
|
||
|
||
---
|
||
|
||
## File Structure
|
||
|
||
```
|
||
content/
|
||
schemas/
|
||
drama_module.schema.yaml ← Schema reference (this file validates against it)
|
||
modules/
|
||
tier1/
|
||
smuggling_ring_v0_1.yaml ← The v0.1 vertical slice module
|
||
future_module_v0_1.yaml ← Future modules go here
|
||
```
|
||
|
||
One `.yaml` file per drama module. The storyteller's content loader scans `content/modules/tier1/` at startup and adds all valid modules to the pool.
|
||
|
||
---
|
||
|
||
## Field Reference
|
||
|
||
### Identity Fields
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `module_id` | Yes | Stable slug: `{name}_v{major}_{minor}`. Never reuse. Increment on breaking structural change. |
|
||
| `display_name` | Yes | Human-readable title for dev tooling. Not shown in-game. |
|
||
| `version` | Yes | Authoring version: `{major}.{minor}`. |
|
||
| `tier` | Yes | Always `1`. |
|
||
| `description` | No | One-paragraph design summary. Authoring-only. |
|
||
| `notes` | No | Design rationale, cross-references. Ignored at load time. |
|
||
| `dual_lens` | No | How smuggler vs detective experience this module. Authoring-only. **Write this first** — it disciplines the design. |
|
||
|
||
---
|
||
|
||
### Pool Metadata
|
||
|
||
Controls how the storyteller samples this module.
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `pool.weight` | Yes | Selection probability 1–10. Higher = more likely per playthrough. Default 5. |
|
||
| `pool.compatible_districts` | No | District slugs. Omit for "any". |
|
||
| `pool.incompatible_with` | No | Module IDs that can't run concurrently. |
|
||
| `pool.max_concurrent` | No | Almost always 1. |
|
||
|
||
**Design note on weight:** Use weight to tune narrative variety, not difficulty. A weight-1 module is a rare playthrough surprise. A weight-8 module like the smuggling ring is "this is usually what's happening in Sova Transit."
|
||
|
||
---
|
||
|
||
### Entry Conditions
|
||
|
||
Defines when the module becomes eligible for activation. ALL world-state conditions must be true. The activation trigger determines *how* it fires.
|
||
|
||
#### World-State Condition Types
|
||
|
||
| Type | Required Fields | Use When |
|
||
|------|----------------|----------|
|
||
| `npc_present` | `role` | The module requires a specific NPC to be in the district. |
|
||
| `location_accessible` | `location` | The module requires a location the player can physically reach. |
|
||
| `fact_not_known` | `fact_id` | Module shouldn't activate if a precondition has already been discovered. |
|
||
| `no_active_module` | `module_id` | Prevents two incompatible modules running at once. |
|
||
| `fact_known` | `fact_id`, `known_by` | Module requires prior knowledge to make sense. |
|
||
|
||
#### Player Conditions (Optional)
|
||
|
||
Player conditions are *optional* — modules can and should activate without player engagement as a prerequisite. Use player conditions sparingly, only when the module literally cannot function without a minimum relationship state.
|
||
|
||
#### Activation Triggers
|
||
|
||
| Trigger | When to Use |
|
||
|---------|-------------|
|
||
| `storyteller_push` | Default. Storyteller activates on its own pacing. Most Tier 1 modules. |
|
||
| `proximity` | Module activates when player wanders near a key location. Useful for "stumble-upon" conspiracies. |
|
||
| `player_action` | Reserved for modules that require player initiation. Use rarely. |
|
||
|
||
**The `min_play_ticks` field is load-bearing for D-027 criterion #1.** At approximately 1 tick/second, 30 minutes of play ≈ 1800 ticks. Set `min_play_ticks` to at least 1800. The vertical slice uses 2100 to give extra breathing room.
|
||
|
||
---
|
||
|
||
### NPC Requirements
|
||
|
||
Each module specifies its NPC slots. Roles are internal slugs used throughout the rest of the document.
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `role` | Yes | Module-internal slug. Kebab-case. Used in event triggers and outcome conditions. |
|
||
| `display_hint` | No | Authoring note: who this role is narratively. |
|
||
| `binding` | Yes | `named` (specific authored NPC) or `generated` (any matching NPC). |
|
||
| `named_npc` | Conditional | Required when `binding: named`. Short-form canonical ID: `npc:{slug}`. |
|
||
| `axes` | Conditional | Required when `binding: generated`. Axis constraints the NPC must satisfy. |
|
||
| `must_have_pattern` | No | Optional NPC pattern (D-024 System A). |
|
||
| `must_have_motivation` | No | Optional NPC motivation (D-024 System B). |
|
||
| `is_optional` | No | Default false. If true, module runs without this slot filled (degraded experience). |
|
||
|
||
#### Named vs. Generated Bindings
|
||
|
||
**Named bindings** reference specific hand-authored NPCs from the district. All v0.1 roles are named. This is the right choice for:
|
||
- THE FRIEND NPCs (D-034) — they have authored arcs, not generic behavior
|
||
- NPCs with unique relationships in the 5-triangle web
|
||
- Roles where voice, history, and moral weight matter
|
||
|
||
**Generated bindings** are for future modules set in different districts or using procedurally generated NPCs. They use axis constraints:
|
||
|
||
```yaml
|
||
axes:
|
||
- axis: secret
|
||
constraint: has_major_secret
|
||
- axis: contentment
|
||
constraint: min_contentment_-3 # Discontented, susceptible to opportunity
|
||
```
|
||
|
||
Constraint conventions: `has_{value}`, `min_{N}`, `not_{value}`. The server's NPC filter system interprets these.
|
||
|
||
#### What "Roles" Are Not
|
||
|
||
NPC roles in a drama module are **not** the same as NPC patterns (FRIEND, MIRROR, etc.) or motivations (HANDLER, WITNESS, etc.). Module roles are:
|
||
- Functional slots within the module's narrative (ring-leader, witness, evidence-holder)
|
||
- Module-local: "ring-leader" in the smuggling ring module ≠ "ring-leader" in any other module
|
||
- Used to reference the same NPC across events and outcomes without hardcoding the NPC slug
|
||
|
||
#### NPC Pattern and Motivation Reference
|
||
|
||
Patterns (System A, `must_have_pattern`) encode the NPC's thematic function in the player's experience:
|
||
|
||
| Pattern | What It Means |
|
||
|---------|---------------|
|
||
| `FRIEND` | Emotionally complex anchor; the contradiction arc lives here (D-034) |
|
||
| `MIRROR` | Reflects the player character's own path back at them |
|
||
| `ANCHOR` | Reliable presence; stability the player can always return to |
|
||
| `GHOST` | Presence felt more than seen; past hangs over current events |
|
||
| `CATALYST` | Actions cause cascading effects on other NPCs |
|
||
| `THRESHOLD` | Gatekeeper; controls access to deeper information or relationships |
|
||
| `REMNANT` | Survivor of a prior event; carries knowledge others want buried |
|
||
| `SYSTEM` | Embodies an institution or faction rather than personal stakes |
|
||
| `NOBODY` | Genuinely flat; texture and atmosphere, no arc |
|
||
|
||
Motivations (System B, `must_have_motivation`) encode why the NPC acts within the module's conspiracy:
|
||
|
||
| Motivation | What It Means |
|
||
|------------|---------------|
|
||
| `HANDLER` | Organizes or directs others; the operational center |
|
||
| `WITNESS` | Knows something they haven't decided to act on |
|
||
| `TURNCOAT` | Wants out, or has already switched allegiance |
|
||
| `CIVILIAN` | No conspiracy involvement; proximity creates moral weight |
|
||
| `OPERATOR` | Executes tasks; functional cog in the system |
|
||
| `SKEPTIC` | Doubts the conspiracy exists; useful foil for investigation |
|
||
|
||
**Full definitions and canonical usage:** `decisions/content.md` D-024.
|
||
|
||
---
|
||
|
||
### Events
|
||
|
||
Events are world-state changes the storyteller fires. They are not scripted player experiences — they happen in the world, and the player may or may not observe them.
|
||
|
||
#### Sequences vs. Pools
|
||
|
||
| Structure | Use For |
|
||
|-----------|---------|
|
||
| **Sequence** | Ordered narrative beats. Step N+1 becomes eligible only after step N fires. Use for character arcs. |
|
||
| **Pool** | Unordered ambient activity. The storyteller fires any eligible event at any time. Use for texture and background. |
|
||
|
||
The vertical slice uses:
|
||
- `kael_exit_arc` (sequence) — Kael's ordered character arc
|
||
- `investigation_pressure` (sequence) — Parallel pressure escalation
|
||
- `ambient_ring_activity` (pool) — Background ring business that runs throughout
|
||
|
||
Most modules should have 1-2 sequences plus 1 pool.
|
||
|
||
#### Event Step Fields
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `event_id` | Yes | Unique within module. Used in outcome conditions and `ticks_since_event` triggers. |
|
||
| `label` | No | Short human-readable label for dev tooling. |
|
||
| `description` | No | What happens narratively. Write this first — events should have a clear observable presence. |
|
||
| `triggers` | Yes | ANY trigger being true fires the event. Multiple triggers = OR logic. |
|
||
| `effects` | No | What changes in the world. |
|
||
| `once` | No | Default `true`. Set `false` for repeating events (ambient discrepancies, etc.). |
|
||
| `sets_flag` | No | Module-internal flag set when event fires. Used in outcome conditions. |
|
||
|
||
#### Trigger Types
|
||
|
||
| Type | Fires When | Key Fields |
|
||
|------|-----------|------------|
|
||
| `ticks_since_activation` | N ticks after module activated | `ticks` |
|
||
| `ticks_since_event` | N ticks after a previous event fired | `after_event`, `ticks` |
|
||
| `player_proximity` | Player near NPC/location | `target_type`, `target`, `radius_tiles` |
|
||
| `player_action` | Player interacts with target | `action`, `target_role` |
|
||
| `fact_known_by_player` | Player has discovered a fact | `fact_id` |
|
||
| `flag_set` | A module flag has been set | `flag` |
|
||
| `npc_mood` | NPC enters a mood state | `npc_role`, `mood` |
|
||
|
||
**Design principle: events should fire without the player.** Every event must have at least one tick-based trigger (`ticks_since_activation` or `ticks_since_event`). Proximity and action triggers are secondary paths that fire the event *earlier* if the player engages. The world moves at its own pace; the player accelerates or delays, not controls.
|
||
|
||
#### Effect Types
|
||
|
||
| Type | Use For |
|
||
|------|---------|
|
||
| `npc_routine_deviation` | Visible NPC behavior change. Write this descriptively — it's what the player sees. |
|
||
| `fact_becomes_discoverable` | Gates a fact into the knowledge graph at Rumoured confidence. |
|
||
| `tell_intensify` | NPC's tell behavior becomes more frequent/pronounced. |
|
||
| `flag_set` | Internal state tracking. Not visible to player. |
|
||
| `location_state` | Something visible changes in a location. |
|
||
| `npc_knowledge_update` | An NPC learns something new. |
|
||
|
||
**On `fact_becomes_discoverable`:** This makes a fact discoverable, not known. The player still has to find it — through proximity, examination, dialogue, or observation. The `discovery_method` field is an authoring note for how: be specific enough that a Mellanie can write the dialogue or monologue that surfaces it, and a Gestalt can define the trigger condition in the fact catalog.
|
||
|
||
**Fact ID convention:** Use `{module-slug}.{fact_name}` — e.g., `ring.kael_unauthorized_corridor_access`. The module slug prefix namespaces the fact to avoid collisions across modules. Before creating a new fact ID, check `content/global/knowledge/` to see if an equivalent fact already exists; reuse it rather than creating a duplicate.
|
||
|
||
**Mapping `discovery_method` to D-035 trigger types:** The `discovery_method` note should describe exactly how the player triggers fact discovery. This maps directly to the D-035 monologue trigger taxonomy (full list in `decisions/content.md` D-035 and `content/global/enums/triggers.yaml`):
|
||
|
||
| If discovery happens via… | D-035 trigger type | What to author |
|
||
|--------------------------|-------------------|----------------|
|
||
| Player enters the location where something is visible | `enter_location` | Monologue line flagging the anomaly on arrival |
|
||
| Player watches an NPC doing something unusual | `observe_npc` | Monologue line on NPC observation; dialogue option unlocks |
|
||
| Player examines an object or terminal | `observe_anomaly` | Examine verb interaction; monologue on result |
|
||
| Player witnesses two NPCs interacting | `witness_interaction` | Monologue line; trust-gated gossip unlock |
|
||
| Player finishes a conversation with the relevant NPC | `post_conversation` | Monologue beat after talking to the NPC |
|
||
| Player discovers a physical object (cargo, message) | `discover_evidence` | Examine verb; monologue on discovery |
|
||
| Player returns to a location they've been before | `return_visit` | Monologue on changed state vs. prior visit |
|
||
|
||
Write the `discovery_method` note to specify which of these applies — ideally two methods for redundancy (e.g., `enter_location` plus `observe_anomaly`) so players aren't funneled into a single approach.
|
||
|
||
---
|
||
|
||
### Outcomes
|
||
|
||
Outcomes are resolution states. The storyteller checks all outcome conditions each tick after the module activates. The first matching outcome is applied.
|
||
|
||
**Every module must include:**
|
||
- At least one terminal outcome that represents "the investigation succeeded"
|
||
- At least one terminal outcome that represents "the conspiracy ran its course"
|
||
- Exactly one expiry outcome (`is_expiry: true`) for quiet player non-engagement
|
||
|
||
#### Outcome Fields
|
||
|
||
| Field | Required | Description |
|
||
|-------|----------|-------------|
|
||
| `outcome_id` | Yes | Unique slug. |
|
||
| `label` | Yes | Short label. |
|
||
| `is_terminal` | Yes | `true` = module ends. `false` = transitional state (module can continue evolving). |
|
||
| `is_expiry` | No | `true` = this is the quiet-exit outcome. One per module. |
|
||
| `conditions` | No | ALL conditions must be true. See below. |
|
||
| `effects` | No | World changes when outcome is reached. |
|
||
|
||
**On `is_terminal: false`:** A non-terminal outcome fires its effects and applies its label, but the module remains active — the storyteller keeps checking for the next matching outcome. Use this for intermediate states where the world has visibly shifted but the situation hasn't resolved: the `ring_splinters` outcome in the vertical slice is non-terminal because the ring going quiet is a change of state, not a conclusion. A module with only non-terminal outcomes will run forever; always ensure there is a reachable terminal outcome (or expiry) downstream.
|
||
|
||
#### Outcome Conditions
|
||
|
||
| Condition | Description |
|
||
|-----------|-------------|
|
||
| `facts_known` | Player must know all listed facts. |
|
||
| `facts_not_known` | Player must NOT know any listed facts. |
|
||
| `flags_set` | All listed module flags must be set. |
|
||
| `flags_not_set` | None of listed flags may be set. |
|
||
| `events_fired` | All listed events must have fired. |
|
||
| `ticks_since_activation` | Module has been running for at least N ticks. |
|
||
|
||
#### Outcome Effects
|
||
|
||
| Type | Description |
|
||
|------|-------------|
|
||
| `npc_disposition` | NPC's relationship state with player shifts. |
|
||
| `faction_reaction` | Faction reputation change. |
|
||
| `location_access_change` | Location becomes restricted, locked, or open. |
|
||
| `fact_state` | Fact is permanently known, hidden, or destroyed. |
|
||
| `npc_exit` | NPC leaves the district or becomes inaccessible. |
|
||
|
||
---
|
||
|
||
## Design Principles for Tier 1 Modules
|
||
|
||
### 1. The World Moves First
|
||
|
||
Events happen on a tick schedule. The player is a witness who can accelerate, delay, or redirect — not a trigger. If your module can only function if the player takes specific actions, it's a quest, not a drama module.
|
||
|
||
### 2. Both Characters Must Have a Story
|
||
|
||
Every event and outcome must mean something different to the smuggler and the detective. Write the `dual_lens` authoring field first. If you can't write both lenses, the module is character-agnostic filler — not Tier 1.
|
||
|
||
### 3. No Clean Resolutions
|
||
|
||
D-034 and D-027 both require moral ambiguity. The smuggling ring doesn't have a "good" ending. The detective arresting Kael is not obviously better than letting him go. Every outcome must have a cost. If one outcome is obviously correct, you've failed the design.
|
||
|
||
### 4. THE FRIEND Contradiction Is the Pivot
|
||
|
||
If your module involves a FRIEND-pattern NPC, the observable contradiction (D-034) must be:
|
||
- **Observable from spatial positioning** — not from dialogue, not from menus
|
||
- **Ambiguous before context** — the player sees the behavior before they understand what it means
|
||
- **Irreversible once witnessed** — seeing changes the relationship, even if the player does nothing
|
||
|
||
The secret meeting in corridor B-7 is the canonical example. After witnessing it, neither character can pretend they don't know what they saw.
|
||
|
||
### 5. Expiry Is Not Failure
|
||
|
||
The `module_abandoned` expiry outcome should feel like a natural ending, not a penalty. The world closes around this conspiracy without the player. That's the 70% mundane reality (D-029): most conspiracies don't get protagonists. Write the expiry description to feel melancholy but not punitive.
|
||
|
||
### 6. Facts, Not Flags, Drive Investigation
|
||
|
||
Facts (from `global/knowledge/`) are the player's knowledge graph. Flags are the storyteller's internal state tracking. The key design question: "Is this something the player knows, or is this something the storyteller tracks?" If the player knows it, it's a fact. If the storyteller tracks it, it's a flag.
|
||
|
||
Facts should be discoverable through multiple methods (observation, dialogue, examination, proximity). Never require a single specific action to surface a critical fact.
|
||
|
||
---
|
||
|
||
## Validation and Format Rules (Gestalt)
|
||
|
||
These rules cover the schema's format constraints and the validation gaps that JSON Schema cannot enforce. All of these are also caught by Tier 2 build-time validation (`make validate-content`), but catching them during authoring saves a pipeline run.
|
||
|
||
### ID and Slug Formats
|
||
|
||
| Field | Regex | Example |
|
||
|-------|-------|---------|
|
||
| `module_id` | `^[a-z][a-z0-9-]*_v[0-9]+_[0-9]+$` | `smuggling_ring_v0_1` |
|
||
| `sequence_id`, `pool_id` | `^[a-z][a-z0-9_-]*$` | `kael_exit_arc` |
|
||
| `event_id` | `^[a-z][a-z0-9_-]*$` | `kael_goes_cold` |
|
||
| `outcome_id` | `^[a-z][a-z0-9_-]*$` | `ring_exposed` |
|
||
| `sets_flag` / flag references | `^[a-z][a-z0-9_-]*$` | `kael_behavior_changed` |
|
||
| `role` (npc slot) | `^[a-z][a-z0-9-]*$` | `ring-member-exiting` |
|
||
| `named_npc` | `^npc:[a-z][a-z0-9-]*$` | `npc:kael-davan` |
|
||
| `version` | `^[0-9]+\\.[0-9]+$` | `0.1` |
|
||
|
||
Note the difference: `event_id`, `outcome_id`, `sequence_id`, and flags use underscores and hyphens (`[a-z0-9_-]*`). NPC `role` slugs use hyphens only (`[a-z0-9-]*`). Mixing them in wrong fields will fail schema validation.
|
||
|
||
### Flag Naming Convention
|
||
|
||
Flags are module-internal state. Every flag name that appears in `sets_flag` on an event **must** also appear in at least one outcome's `flags_set` or `flags_not_set` condition — or the flag serves no purpose. Convention:
|
||
|
||
- Use `snake_case` with underscores: `kael_behavior_changed`, `voss_pressure_applied`
|
||
- Name by what happened, not what it enables: `handler_pressure_applied` not `kael_ready_to_flee`
|
||
- Flags set by events accumulate — they are never automatically cleared
|
||
- A flag set by a time-triggered event (not player-triggered) cannot be used as an expiry gate (see "Common Mistakes" below)
|
||
|
||
### Axis Constraint Syntax (Generated NPC Bindings)
|
||
|
||
The `constraint` field in `axes` is a freeform string. The storyteller's NPC filter interprets it. Convention (author responsibility — schema does not enforce):
|
||
|
||
| Prefix | Example | Meaning |
|
||
|--------|---------|---------|
|
||
| `has_` | `has_major_secret` | NPC axis value includes this descriptor |
|
||
| `min_contentment_` | `min_contentment_-3` | Contentment axis value ≤ N (more discontented) |
|
||
| `not_` | `not_combat_trained` | Axis value does NOT include this descriptor |
|
||
| `is_` | `is_ring_member` | Boolean flag set on NPC profile |
|
||
|
||
### What JSON Schema Cannot Validate (Tier 2 Catches These)
|
||
|
||
| Issue | Where to Look | Impact |
|
||
|-------|--------------|--------|
|
||
| `fact_id` not defined in `global/knowledge/` | Effect `fact_becomes_discoverable`, outcome `facts_known` | Fact silently never becomes discoverable |
|
||
| `sets_flag` name not referenced in any outcome condition | Event `sets_flag` | Flag is set but never meaningful |
|
||
| `flags_set`/`flags_not_set` reference flag never set by any event | Outcome conditions | Condition permanently true or false |
|
||
| `ticks_since_event.after_event` references unknown event_id | Event trigger | Trigger never fires |
|
||
| `named_npc` ID doesn't exist in district NPC profiles | NPC requirements | Load-time failure |
|
||
| Multiple outcomes have `is_expiry: true` | Outcomes list | Undefined storyteller behavior |
|
||
| `faction` in outcome effects not in `global/factions/` | Outcome effects | Effect silently ignored |
|
||
|
||
### The Expiry Condition Pitfall
|
||
|
||
This is the most common authoring mistake for expiry outcomes. **The expiry condition must use `facts_not_known`, not `flags_not_set`.** Reason:
|
||
|
||
Events with `ticks_since_activation` triggers fire automatically without player engagement. If an auto-firing event sets a flag, and your expiry checks `flags_not_set: [that_flag]`, the expiry condition becomes permanently false after the event fires — the module can never expire quietly.
|
||
|
||
**Wrong:**
|
||
```yaml
|
||
# kael_goes_cold fires automatically at tick 300, sets kael_behavior_changed
|
||
# This expiry can never fire after tick 300
|
||
- outcome_id: module_abandoned
|
||
is_expiry: true
|
||
conditions:
|
||
flags_not_set:
|
||
- kael_behavior_changed # This flag is always set by tick 300
|
||
ticks_since_activation: 5400
|
||
```
|
||
|
||
**Correct:**
|
||
```yaml
|
||
# facts_not_known gates on player investigative action, not auto-fired events
|
||
- outcome_id: module_abandoned
|
||
is_expiry: true
|
||
conditions:
|
||
facts_not_known:
|
||
- "ring.cargo_discrepancy_pattern" # Only known if player examined terminal
|
||
- "ring.kael_unauthorized_corridor_access" # Only known if player observed Kael
|
||
ticks_since_activation: 5400
|
||
```
|
||
|
||
---
|
||
|
||
## Checklist Before Submitting a New Module
|
||
|
||
- [ ] `module_id` uses correct format and doesn't collide with existing modules
|
||
- [ ] `dual_lens` is written and shows clearly different experiences per character
|
||
- [ ] `min_play_ticks` ≥ 1800 (30 minutes at 1 tick/second)
|
||
- [ ] Every event sequence step has at least one tick-based trigger
|
||
- [ ] Every `fact_becomes_discoverable` effect has a `discovery_method` note
|
||
- [ ] The module includes at least one named FRIEND-pattern NPC (for v0.1 modules)
|
||
- [ ] Expiry outcome is present (`is_expiry: true`) with conditions gated on `facts_not_known`, NOT `flags_not_set`
|
||
- [ ] All outcomes have been reviewed for moral ambiguity — no "obviously correct" resolution
|
||
- [ ] `npc_requirements` covers every role referenced in events and outcomes
|
||
- [ ] All fact IDs used in effects/conditions exist in `global/knowledge/`
|
||
- [ ] All `sets_flag` names appear in at least one outcome condition
|
||
- [ ] All `flags_set`/`flags_not_set` names are set by at least one event's `sets_flag`
|
||
- [ ] `make validate-content` passes
|
||
|
||
---
|
||
|
||
## Cross-References
|
||
|
||
| Topic | Location |
|
||
|-------|----------|
|
||
| Three-tier content model | `decisions/content.md` D-023 |
|
||
| NPC 10-axis model | `decisions/content.md` D-024 |
|
||
| Vertical slice scope | `decisions/scope.md` D-027 |
|
||
| Population ratios | `decisions/content.md` D-029 |
|
||
| THE FRIEND pattern | `decisions/content.md` D-034 |
|
||
| Smuggling ring module | `content/modules/tier1/smuggling_ring_v0_1.yaml` |
|
||
| Drama module schema | `content/schemas/drama_module.schema.yaml` |
|
||
| Fact catalog | `content/global/knowledge/` |
|
||
| NPC profiles (v0.1) | `content/campaigns/main/systems/van-maanens-star/` |
|
||
| Storyteller stub | `server/src/storyteller/mod.rs` |
|
||
|
||
---
|
||
|
||
*Ticket #158 — Tier 1 drama module schema. Paula (dramatic structure), Gestalt (schema format), Mellanie (authoring review).*
|