Merge remote-tracking branch 'origin/main' into visual

# Conflicts:
#	docs/design/wireframes/menus/v01-save-load.png
This commit is contained in:
2026-02-27 19:29:17 +01:00
260 changed files with 43045 additions and 1104 deletions
+100
View File
@@ -0,0 +1,100 @@
# Line ID Authoring Guide
**Decision:** D-084 (dual-namespace line ID scheme)
**Resolves:** Q-028 (collision-resistant IDs for auto-generated NPCs)
**Ticket:** #544
---
## The Short Version
- **Role pool lines:** Use `{role-slug}_d_{###}` — e.g., `dock-worker_d_001`. These lines are shared by all NPCs with that role. This is the default for all auto-generated NPC content.
- **Named NPC lines:** Use `{npc-slug}_d_{###}` — e.g., `kael-davan_d_001`. Unchanged from current practice.
- **Instance-specific lines (rare):** Use `{role-slug}-{counter}_d_{###}` — e.g., `dock-worker-07_d_001`. Only needed when a specific generated NPC needs content different from the role pool.
---
## How Line IDs Work
A line ID identifies **content**, not speaker. The speaker is identified by their `StableId` in the history log. So `dock-worker_d_001` being said by 40 different dock workers is correct: the log records `(StableId: 12, dock-worker_d_001)`, `(StableId: 37, dock-worker_d_001)`, etc. No collision.
This means the role pool approach already handles most cases — the "collision problem" is mainly a concern for the rare case where you want a specific generated NPC to say something *different* from others of the same role.
---
## Namespace Reference
### Named NPC lines (Tier 1 and Tier 2 authored NPCs)
```
Format: {npc-slug}_{content-type}_{###}
Example: kael-davan_d_001 (Kael's dialogue line 1)
sera-venn_d_015 (Sera's dialogue line 15)
pc-smuggler_m_s_001 (Smuggler monologue line 1)
```
File location: One file per NPC (e.g., `dialogue/maintenance-corridors/kael-davan.yaml`)
Numbering: Sequential within the file. Gaps are acceptable (deleted lines leave permanent gaps). Never reuse a number.
---
### Role pool lines (auto-generated NPCs, Tier 3 flat, Tier 2 mundane)
```
Format: {role-slug}_{content-type}_{###}
Example: dock-worker_d_001 (any dock worker, dialogue line 1)
bar-regular_d_008 (any bar regular, dialogue line 8)
transit-worker_d_003 (any transit worker, dialogue line 3)
```
File location: One file per role-at-location (e.g., `dialogue/the-terminal/dock-worker.yaml`)
These lines are shared by **all instances** of the role. Write them to suit any dock worker, not a specific one.
---
### Instance-specific lines (opt-in, rare)
Use only when the generation system has flagged a specific NPC as needing content that differs from the role pool. Examples: a generated dock worker who is also a triangle member with a specific tell; a generated bar regular who witnessed a specific event.
```
Format: {role-slug}-{zero-padded counter}_{content-type}_{###}
Example: dock-worker-07_d_001 (instance 7 of dock-worker role, line 1)
bar-regular-02_d_005 (instance 2 of bar-regular role, line 5)
```
The counter (01, 02, ... N) is assigned by the generation system in world-seed-deterministic order. The NPC's generated profile file will tell you which counter to use.
File location: Same directory as the role pool file, separate file with instance slug as name (e.g., `dialogue/the-terminal/dock-worker-07.yaml`)
---
## Quick Decision Guide
| Situation | ID format to use |
|-----------|------------------|
| Named authored NPC (Kael, Sera, Voss...) | `{npc-slug}_d_{###}` |
| Lines any dock worker can say | `dock-worker_d_{###}` |
| Lines any bar regular can say | `bar-regular_d_{###}` |
| Generated NPC with specific triangle role | `{role-slug}-{counter}_d_{###}` |
| Generated NPC who's just background | `{role-slug}_d_{###}` — no instance ID needed |
---
## Schema Compatibility
The existing ID regex `^[a-z][a-z0-9-]*_[dme]_\d{3}$` accepts all three formats. No schema change is required. The content validator (`make validate-content`) checks for duplicate IDs across all files in a district.
---
## Numbering Rules
1. Start at `001`, increment by 1 for each new line.
2. Never reuse a number, even if a line is deleted. Gaps are fine.
3. Lines within a single file have a contiguous prefix — `dock-worker_d_001` through `dock-worker_d_042`, etc.
4. Cross-file: `kael-davan.yaml` at the terminal and `kael-davan.yaml` at maintenance corridors both use the `kael-davan_d_###` namespace. Continue numbering from where the other file left off (check the existing files first, use a fresh sequence if the NPC is new to a location).
---
*D-084 — authored by Gestalt, Sprint 18*
+4 -4
View File
@@ -626,7 +626,7 @@ In a world where everyone is hiding something, the one person who isn't becomes
**End Pattern Specification.**
**Files referenced:**
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/wiki/npcs/naia-tamm.md` — Reference implementation
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/decisions/content.md` — D-024 (10-axis model), D-028 (dialogue architecture), D-029 (entanglement ratio), D-032 (separate monologue pools), D-034 (THE FRIEND pattern), D-035 (tag taxonomy)
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/docs/workshops/v01-content-scoping/round2-gestalt.md` — Pattern definitions and NPC mapping
- `/var/home/jeroenschweitzer/Projects/settled-reach/copy/docs/workshops/v01-gap-analysis/round2-gestalt.md` — Unified observation system, character identity integration
- `/var/mnt/data/projects/settled-reach/copy/wiki/npcs/naia-tamm.md` — Reference implementation
- `/var/mnt/data/projects/settled-reach/copy/decisions/content.md` — D-024 (10-axis model), D-028 (dialogue architecture), D-029 (entanglement ratio), D-032 (separate monologue pools), D-034 (THE FRIEND pattern), D-035 (tag taxonomy)
- `/var/mnt/data/projects/settled-reach/copy/docs/workshops/v01-content-scoping/round2-gestalt.md` — Pattern definitions and NPC mapping
- `/var/mnt/data/projects/settled-reach/copy/docs/workshops/v01-gap-analysis/round2-gestalt.md` — Unified observation system, character identity integration
+3 -1
View File
@@ -144,7 +144,9 @@ NPCs may reference locations and entities beyond Sova Station. These are real bu
**Other Krenn System stations:** The Krenn System has two other smaller orbital facilities (mining support station and an administrative relay). They're referenced occasionally in news tickers and operational scheduling. Not relevant to v0.1.
**The horizon gate:** Sova Station has a connection to the Reach's horizon gate network — the interstellar transport infrastructure. The horizon gate terminal is in the Administrative Hub district, not the Transit District. Characters with legitimate need can book transit to other systems. This connection is what makes Sova relevant to a larger smuggling network; contraband doesn't originate in-system, it comes from elsewhere via horizon gate and moves through Sova's span gate to Velen.
**The Krenn Ring (horizon station):** The Krenn System's interstellar connection is the Krenn Ring — a horizon station at approximately 800 AU from the Krenn star, accessible by system vessel (~4–6 days from Station Sova). Horizon stations are ancient orbital installations of unknown origin, self-maintaining, each containing multiple gate apertures connecting to other star systems. The Krenn Ring is not on Station Sova; it is a separate installation in the outer system.
**The Administrative Hub's interstellar transit facility:** Sova Station's Administrative Hub district houses the transit processing facility for interstellar travel — customs clearance, booking offices, and the shuttle dock for vessels heading to the Krenn Ring. When NPCs or documents refer to "the horizon gate terminal," they mean this processing facility, not a gate aperture on the station itself. Characters with legitimate need book transit here, then travel by shuttle to the Krenn Ring to board. This connection is what makes Sova relevant to a larger smuggling network; contraband doesn't originate in-system, it comes from elsewhere via horizon gate and moves through Sova's span gate to Velen.
---
+316
View File
@@ -0,0 +1,316 @@
# Spatial Layout: Gate Cluster (Span Gate Processing Facility)
**Ticket:** #153
**Date:** 2026-02-25
**Author:** Araminta (Visual Designer)
**Status:** v0.1 — wireframe quality, unblocks copy and gate cluster NPC authoring
**Grid:** 1 cell = 1m visual tile (visual grammar §2.1). Simulation operates at 0.5m; each visual tile = 2×2 sim tiles.
**Map area:** 40m wide × 32m deep (40×32 visual tiles) + observation gallery on z=2
**Zone palette:** Cool institutional grey — Era 3 construction, Commission-grade maintenance (visual grammar §1.1)
---
## Spatial Character
The gate cluster is the newest structure in the Transit District. Era 3 construction: clean sightlines, uniform LED-white overhead lighting, minimal accumulated grime. Commission-monitored and Commission-maintained. Where the Terminal reads as institutional but worn, and the Bar as warm and accumulated, the gate cluster reads as **administered**. The architecture communicates that someone is watching.
The building is a funnel. The span gate aperture (~15–20m diameter ring) determines the widest point; the passenger and freight flows narrow through processing stages; they emerge into the gate concourse, which opens outward as public space. The spatial logic is deliberate: volume at intake, compression through customs, expansion at public exit.
Dual-use scheduling is the spatial and operational premise (D-095): freight windows and passenger windows share the single aperture. The "flicker" (90-second mode transition between sequences) is legible to experienced travelers — different lighting cues on the aperture chamber walls mark which mode is active.
**G-11 entry note:** The detective arrives via this cluster from a Commission shuttle. Workers arrive via the transit platform on the bar side. These are structurally separated entry vectors. The gate cluster is the detective's first experience of the district.
---
## Floor Plan
### z=1 (Ground Floor)
```
N (span gate aperture — external, connects to The Ring)
|
1111111111222222222233333333334444444
1234567890123456789012345678901234567890
############[APERTURE RING]######### row 01 <- span gate ring (structural boundary)
# . . . APERTURE CHAMBER . . . # row 02
# . . . . . . . . . . . . . . # row 03 ACCESS: RESTRICTED
# . . . . . . . . . . . . . . # row 04 (airlock/transition zone)
########[D]##########[D]############ row 05 <- chamber exit doors (freight W, passenger E)
############################[D]###### row 06 <- freight staging north wall (east door = PAB)
# FREIGHT STAGING # PAB # row 07
# [FK][FK] [FK][FK] . # . # row 08 ACCESS: private (freight) / semi-public (PAB)
# [FK][FK] [FK][FK] . # . # row 09 PAB = Passenger Arrival Buffer
# [FK][FK] [FK][FK] . # . # row 10
# . . . . . . . . [CT][CT] # . # row 11 <- CT = cargo transporter dock points
# . . . . . . . . [CT][CT] # . # row 12 <- PAB merges south into customs at row 13
#####[D]####################[D]###### row 13 <- into customs lanes
###################[D]############### row 14 <- freight customs north entry
# FCL | FCL | FCL | FCL | FCL # row 15 ACCESS: semi-private (freight customs)
# [TS] | [TS] | [TS] | [TS] | [TS] # row 16 FCL = freight customs lane (5 lanes × 4vt)
# || | || | || | || | || # row 17 TS = terminal/scanner station per lane
# || | || | || | || | || # row 18 || = cargo lane (4vt wide, column breaks Q4)
# [P] | [P] | [P] | [P] | [P] # row 19 P = pillar/LOS anchor (4-tile interval)
# . . .|. . . |. . . |. . . |. . . # row 20 <- inspection floor
#######|#######[D]####[D]###|######## row 21 <- customs south wall; PCL entry
# PCL PCL PCL PCL PCL # row 22 ACCESS: semi-public (pedestrian customs)
# [TS] [TS] [TS] [TS] [TS] . . # row 23 PCL = pedestrian customs lanes (3 lanes × 2vt)
# [P] . . [P] . . [P] . . # row 24 <- queue markers + pillar anchors
# . . . . . . . . . . . . . . . # row 25
# . . . . . . . . . . . . . . . # row 26
############[D]####[D]############### row 27 <- customs south doors to concourse
#################################### row 28 <- concourse north wall
# . . [B] [B] . . [NT][NT] # row 29 ACCESS: public
# . . . . . . . . . . # row 30 B = bench, NT = news ticker
# . . [B] [B] . . . . . # row 31
# . . . . . . . . [D]SC # row 32 <- staircase (SC) east end; Commission entry
#################################### row 33 <- concourse south wall (district entry facade)
|
S (district interior — Terminal forecourt, transition corridor)
```
**Legend:**
```
# Wall (solid, blocks LOS and movement)
. Open walkable floor
[D] Doorway (traversable)
[APERTURE RING] Span gate ring structure (impassable during transit; open between sequences)
FCL Freight customs lane
PCL Pedestrian customs lane
[TS] Terminal/scanner station (customs clerk workstation)
[FK] Freight staging kiosk / forwarder terminal
[CT] Cargo transporter dock point (loading/unloading position)
[B] Bench (public seating)
[NT] News ticker display (wall-mounted)
[P] Structural pillar (LOS anchor, column break, gallery support above)
SC Staircase to z=2 observation gallery (east end of concourse)
PAB Passenger Arrival Buffer (east of freight staging, rows 06–12)
```
---
### z=2 (Observation Gallery) — Commission-only
```
N
|
(above customs lanes — rows 14–27 below)
1111111111222222222233333333334444
1234567890123456789012345678901234567
[GALLERY NORTH RAIL — partial glass/grating]
################################# <- gallery west and east walls
# . . . . GALLERY FLOOR . . . # Commission-only: pristine near-white
# [DK][DK] . . . [DK][DK] # DK = observation desk / surveillance kit
# . . . . . . . . . . . . . . # floor: #d4d8dc
# . . . . . . . . . . . . . . # walls: #e0e4e8
# [DK][DK] . . . [DK][DK] #
# . . . . . . . . . . . . . . #
# . . . . . . . . . . . . . . #
# . . . . . . . . . . . . . . #
################################# <- gallery south rail (partial glass/grating)
|
[SC] staircase descends to z=1 concourse east end
|
S
```
**Gallery dimensions:** 32m wide × 10m deep (32×10 visual tiles). Positioned above the customs lanes (z=1 rows 14–27) and NOT above the concourse or staging zones.
**Cross-z LOS:** Gallery rail is transparent low wall (glass or metal grating). Observer on z=2 has LOS downward to z=1 customs lanes. Upward LOS from z=1 is blocked except at the staircase opening. Players cannot see gallery occupants from the customs floor unless standing at the staircase.
**Gallery floor is the customs ceiling** — approximately 4m structural clearance below.
---
## Zone Breakdown
### Zone 1 — Aperture Chamber (rows 01–05)
**Dimensions:** 40×4 visual tiles
**Access tier:** Restricted (Commission control + gate authority; no public entry)
**Purpose:** The transition space between the span gate aperture and the main processing facility. All passengers and freight pass through here immediately after emerging from the span gate. The aperture ring is the physical gate structure — when a transit sequence is active, the ring glows with transit residue (lighting cue for mode). Between sequences, the ring is dark and cold.
**NPC traffic:** Gate authority staff (2–3 stationed here per sequence). Arrivals flow through continuously during an active sequence; zero traffic between sequences.
**LOS notes:** The chamber is enclosed. No LOS to any other zone except the two exit doors (row 05). Gate authority staff can observe the full chamber volume. No LOS from the staging zones into the chamber.
**Key feature:** The "flicker" — the 90-second mode transition between freight and passenger sequences — is physically visible here. Lighting shifts, personnel rotate, cargo equipment is cleared or staged. An observer in the gate concourse (south) can hear the mode change but not see it.
### Zone 2 — Freight Staging (rows 06–13, west 24 tiles)
**Dimensions:** 24×8 visual tiles
**Access tier:** Private (authorized freight operators and customs personnel only)
**Purpose:** Where inbound freight is offloaded, registered, and staged for the customs inspection lanes. [FK] forwarder terminals are where freight agents log their manifest declarations before the cargo moves to the lanes. [CT] dock points are active during freight windows; they are dormant (low power, no staff) during passenger windows.
**NPC traffic:** Busy during freight windows. The operations manager (Triangle NPC) is typically here during active freight sequences — their role is coordinating flow from aperture to customs. Senior freight handlers work the dock points. Sparse during passenger windows.
**LOS notes:** Full LOS across the staging floor from the forwarder terminals. The freight customs entry door (row 13) is visible from the staging area. The PAB door (east) is visible but the PAB interior is not.
**Key feature:** The operations manager's position here — with LOS to the aperture chamber exits, the staging floor, and the customs entry — is the spatial expression of their authority. They see everything that comes in.
### Zone 3 — Passenger Arrival Buffer (rows 06–12, east 12 tiles)
**Dimensions:** 12×8 visual tiles
**Access tier:** Semi-public (arriving passengers only; no unauthorized entry from district side)
**Purpose:** Where passengers emerging from the span gate are held in a staging queue before processing through pedestrian customs. Separate from freight staging — the physical separation is the architectural enforcement of D-095's dual-use windows. During a freight window, the PAB is closed; during a passenger window, it fills.
**NPC traffic:** Moderated by gate sequence. Full during a passenger window; empty between or during freight windows.
**LOS notes:** LOS within the PAB is full. No direct LOS from the district concourse into the PAB — the customs lanes form a visual barrier. A player in the concourse sees only the south face of the customs lane structure.
**Key feature:** The detective's entry experience begins here (G-11). Arriving via Commission shuttle during a passenger window, they queue briefly before being waved through customs (or escorted directly to the observation gallery — see staircase at z=1 east end).
### Zone 4 — Freight Customs Lanes (rows 14–21, west 20 tiles)
**Dimensions:** 20×10 visual tiles (5 lanes × 4vt each, within a 20vt-wide zone, rows 14–21)
**Access tier:** Semi-private (freight operators entering the district; customs clerk staff)
**Purpose:** Processing incoming freight through customs inspection. Five lanes, each 4 visual tiles wide, each staffed by a customs clerk at a [TS] terminal station. [P] pillars at 4-tile intervals serve dual purpose: LOS anchors for the customs floor and structural supports for the observation gallery above.
**NPC traffic:** Customs clerks (5, one per lane) are stationed here during freight windows. The Commission inspector (Triangle NPC) circulates among lanes — their social dynamic with the clerks is expressed spatially by where they position themselves during inspections. During passenger windows, lanes are closed (screens down, no staff).
**LOS notes:** Clear LOS along each lane from north wall to south wall. The pillar breaks ([P]) interrupt cross-lane LOS at 4-tile intervals — an observer cannot see continuously across all 5 lanes. The gallery rail above (z=2) allows the Commission inspector to observe all lanes simultaneously from elevation. This is the critical asymmetry: floor-level observers have partial LOS; gallery observers have full LOS.
**Observation note:** The social triangle's power dynamic is visible in sightlines. The Commission inspector from the gallery sees the customs clerks in their entirety, including which freight forwarders are waved through vs. searched. The floor-level operations manager sees individual lanes but not the full picture. The detective, arriving from the gallery, can observe the customs floor before descending.
### Zone 5 — Pedestrian Customs Lanes (rows 22–27, east 12 tiles)
**Dimensions:** 12×10 visual tiles (3 lanes × 2vt each, with queue space to east, rows 22–27)
**Access tier:** Semi-public (arriving passengers processing into the district)
**Purpose:** Processing arriving passengers through customs. Three lanes, each 2 visual tiles wide, each with a [TS] scanner station. Queue space runs east of the lane structure. Simpler operation than freight customs — personal items scan, biometric check, manifest tag if applicable.
**NPC traffic:** Active only during passenger windows. During freight windows, the customs clerks from PCL rotate to assist with FCL overflow. The Commission inspector may operate from PCL during passenger windows if intelligence suggests surveillance value.
**LOS notes:** Narrower lanes mean LOS is more constrained. An observer in the queue can see only the lane directly ahead. From the gate concourse (south), the south face of the customs structure presents as a low partition — passengers emerging from customs are visible from the concourse immediately on exit.
**Key feature:** This is where observable inequity happens. The Commission inspector (or a directive they issue) results in one class of traveler being waved through while another is searched. This is visible to anyone in the concourse queue area — including the detective. The spatial proximity of the PCL south wall to the concourse benches [B] means concourse passengers witness the processing of arrivals.
### Zone 6 — Gate Concourse (rows 28–33)
**Dimensions:** 40×8 visual tiles (full building width)
**Access tier:** Public (all district residents, workers, and new arrivals)
**Purpose:** The public-facing terminus of the gate cluster. Benches [B] for waiting passengers, news ticker [NT] on the east wall for transit schedules and general news, and the primary facade opening to the district south. The staircase (SC) at the east end is the access point to the observation gallery — it is Commission-coded at the base (a discreet panel, not a visible barrier).
**NPC traffic:** Variable. Busy when a passenger sequence has just completed (arrivals dispersing). Sparse during freight windows (only workers and officials). The concourse is the natural convergence zone for all district-side personnel who have business at the gate cluster.
**LOS notes:** Full east-west LOS across the concourse. The [P] pillars from the customs lanes above do not extend to the concourse floor — the south edge of the customs structure is a visual wall at row 27. From the benches, observers can see the customs exit doors (row 27) and watch arrivals emerge. Cannot see into customs lanes from bench positions.
**Key feature:** The social reading zone on arrival. New arrivals (including the detective on their first visit) experience the concourse before moving into the district. The news ticker is a topic generator. The Commission staircase (east end) is present but low-key — coded access does not broadcast itself in Commission-grade facilities.
### Zone 7 — Observation Gallery (z=2, above zones 4–5)
**Dimensions:** 32×10 visual tiles (above the full customs lane section)
**Access tier:** Commission-only (staircase coded at z=1 east end of concourse)
**Purpose:** The gallery is where the Commission inspector works during active processing sequences. From here, all freight and pedestrian customs lanes are simultaneously observable. Observation desks [DK] with surveillance kit allow real-time customs monitoring, camera feed access, and communication with gate authority staff below. This is the institutional oversight position — the spatial embodiment of Commission authority over district entry.
**NPC traffic:** The Commission inspector during work hours. Possibly a second Commission observer (junior) — but sparse. This is not a social space; it is a surveillance position.
**LOS notes:** Full LOS down to all customs lanes (z=2 → z=1, through gallery rail). Partial LOS to freight staging (row 13 door visible from gallery north rail). NO LOS to aperture chamber (wall blocks), NO LOS to concourse (gallery south rail is opaque below rail height). Gallery interior has no LOS from the customs floor below — the cross-z asymmetry is deliberate and complete.
**Key feature:** The detective's first introduction to this space is via escort through the staircase. The experience of descending from gallery (full picture) to concourse (partial picture) is the spatial tutorial for the information asymmetry theme.
---
## Key Observation Positions
| Position | Code | LOS coverage | Why it matters |
|----------|------|-------------|----------------|
| Observation gallery (z=2, center) | POS-G1 | All freight + pedestrian customs lanes simultaneously | Commission inspector's domain. Highest-information position in the gate cluster. Asymmetric — not visible from below. |
| Freight staging floor (center) | POS-G2 | Aperture chamber exits, freight staging, customs entry door | Operations manager's natural position. Sees intake and output but not gallery or pedestrian lanes. |
| Gate concourse benches (rows 29-30, west) | POS-G3 | Customs exit doors (row 27), staircase base (east), full concourse | Player's first investigation position. Passive observation of arrivals emerging from customs and who accesses the staircase. |
| Pedestrian customs queue (row 22, east) | POS-G4 | PCL lanes, customs exit direction | Observer in queue can watch the customs clerks processing arrivals. Visible inequity in who is waved through vs. searched. |
| Concourse east end (near staircase) | POS-G5 | Staircase access panel, anyone ascending/descending | Monitoring staircase access reveals Commission movement. Coded panel is discreet but observable. |
| Gallery north rail (z=2) | POS-G6 | Freight staging floor through rail, customs lane north entries | Extended north-viewing position from gallery — tracks cargo from aperture exit to lane entry. |
---
## Sightline Analysis
```
FROM → Aperture Frt.Stg. PAB Frt.Cust. Ped.Cust. Concourse Gallery
TO ↓
Aperture SELF via door via door NO NO NO NO
Frt.Staging via door SELF via door via door NO NO NO
PAB via door via door SELF NO NO NO NO
Frt.Customs NO via door NO SELF NO via door rail(z2→z1)
Ped.Customs NO NO NO NO SELF via door rail(z2→z1)
Concourse NO NO NO via door via door SELF NO
Gallery NO rail(N) NO rail(full) rail(full) NO SELF
rail(z2→z1) = LOS from gallery down through transparent rail/grating
rail(N) = gallery north rail has partial LOS to freight staging floor
NO = wall or z-gap blocks
via door = LOS when door open
```
**Critical sightline: Gallery → all customs lanes**
The Commission inspector on z=2 has full LOS over every freight and pedestrian customs lane simultaneously. No position on the z=1 customs floor achieves equivalent coverage. This asymmetry is the spatial expression of institutional oversight.
**Critical sightline gap: Concourse → customs interior**
The concourse benches are south of the customs structure. The customs south wall (rows 14–21 for freight, 22–27 for pedestrian) presents as a visual barrier. A player on the benches sees the customs exit doors and emerging arrivals — but not what happens inside the lanes. Investigation of customs behavior requires entering the lanes or reaching the gallery.
**Critical sightline gap: Gallery → concourse**
The gallery south rail is opaque below the rail height. The Commission inspector cannot observe the concourse from the gallery without descending. The gallery is a surveillance position for entry processing, not for the public space.
---
## Access Tier Map
```
RESTRICTED PRIVATE SEMI-PRIVATE SEMI-PUBLIC PUBLIC
────────── ─────── ──────────── ─────────── ──────
Aperture Freight staging Freight customs Ped. customs Concourse
chamber (auth. operators) lanes lanes (all)
(clerks + (arriving
[Gallery z=2: freight ops) passengers)
Commission-only]
```
Sequential access enforcement (H-06): A freight operator moving from aperture to district must pass through freight staging → freight customs → concourse. No spatial path skips a tier. Pedestrian arrivals pass through PAB → pedestrian customs → concourse. The two flows are physically separated (west half vs. east half of the building) and join only at the concourse.
---
## NPC Traffic Density Annotations
| Time | Aperture | Frt. Staging | PAB | Frt. Customs | Ped. Customs | Concourse | Gallery |
|------|----------|-------------|-----|-------------|-------------|-----------|---------|
| Dawn (05-07) | very sparse | very sparse | closed | closed | closed | very sparse | — |
| Freight window 1 (07-12) | busy (freight) | busy | closed | busy | closed | moderate | inspector |
| Passenger window (12-14) | moderate (pax) | sparse | moderate | closed | moderate | busy | inspector |
| Freight window 2 (14-19) | busy (freight) | busy | closed | busy | closed | moderate | inspector |
| Passenger window (19-20) | moderate (pax) | sparse | moderate | closed | moderate | busy | inspector |
| Evening sparse (20-23) | sparse | sparse | closed | sparse | closed | sparse | varies |
| Night (23-05) | very sparse | very sparse | closed | closed | closed | very sparse | — |
**Flicker windows:** 90-second transition between freight and passenger modes. During this window:
- Aperture chamber resets (personnel exchange, lighting shifts, cargo equipment cleared or staged)
- All customs lanes briefly closed
- Concourse becomes transiently busier as travelers waiting for mode completion gather
- The operations manager is most exposed — coordinating the reset, moving between staging and customs entry
**Detective entry:** Commission shuttles arrive during passenger windows as a matter of protocol. First contact with the district begins in the aperture chamber, proceeds to the PAB, and typically diverts to the gallery staircase before customs processing is required.
---
## Narrative Triangle Service Notes
### Triangle 5 — Gate Authority (Operations Manager – Senior Freight Handler – Commission Inspector)
This is an institutional-oversight triangle, structurally different from the Terminal's knowledge-and-leverage triangles (1–2) and the Bar's social-loyalty triangles (3–4).
- **Operations manager:** Their domain is the freight flow — aperture to staging to customs. They have private-tier access everywhere on the z=1 floor. They are measured by throughput: how much cargo clears customs in a window. They have an accommodation relationship with certain freight forwarders (see customs inequity below).
- **Senior freight handler:** The forwarder who benefits from that accommodation. They know what they get, they know why, and they know the operations manager knows they know. This is the stable complicity leg of the triangle.
- **Commission inspector:** Their domain is the gallery. They watch the customs floor from above. They may know about the accommodation, or may be about to discover it, or may be using it as leverage already. Their relationship to the operations manager is formally collaborative, actually adversarial.
**Spatial expression:** The operations manager never goes to the gallery. The Commission inspector rarely comes to the floor. The senior freight handler is on the floor. The triangle's tension is mediated by the cross-z sightline — the inspector can see the forwarder being waved through, and the operations manager knows the inspector is watching, but neither will acknowledge it in the same zone at the same time.
**Observable inequity (investigation entry point):** A player watching from the concourse benches (POS-G3) or from the pedestrian customs queue (POS-G4) can observe a freight forwarder being waved through freight customs without search while a commuter on the pedestrian side receives a full scan. This is not dramatic — it reads as normal. The player has to make the connection: waved through = known cargo = manifested incorrectly = this is where the lattice components enter.
**Tension staging locations:**
- Freight staging floor (operations manager's ground; conversations here are authority-neutral)
- Gallery (inspector's ground; a summons to the gallery is pressure)
- Customs lane (the observable action space; what clerks actually do is determined by unspoken directives from above)
- Concourse east end near staircase (the transition space; anyone ascending the staircase must pass anyone watching the staircase)
---
## Z-Level Notes
**z=0:** Not present in the gate cluster. Maintenance access to the gate cluster, if any, is via the district maintenance spine (z=0 elsewhere in district) and does not extend into the gate cluster interior. Era 3 construction has no maintenance corridor integration — maintenance occurs from above via service panels.
**z=1:** All gate cluster zones: aperture chamber, freight staging, PAB, freight customs, pedestrian customs, concourse. All NPCs and player movement on this level.
**z=2:** Observation gallery only. Staircase is the sole connection point (z=1 concourse east end ↔ z=2 gallery). Commission-coded access panel at base of staircase is present but low-profile (no visible lock, no visible panel labeling in Era 3 style — access is granted by insertion of Commission neural-tag proximity, not a key).
**Cross-z visibility rules:**
- Gallery → customs lanes: full downward LOS through transparent rail/grating
- Customs lanes → gallery: no upward LOS (gallery floor solid except rail; rail height above standing head height)
- Staircase opening: local LOS only (see who is at the staircase base or top, not into gallery interior)
---
## Notes for Copy Team
1. **Aperture chamber is the in-world ritual.** Arriving via span gate is Commonwealth-mundane but the aperture ring has residual energy effects — ambient hum, slight color temperature shift as light normalizes from transit. Monologue lines for the detective's arrival should note this. Standard sensory detail for immersive-world arrivals.
2. **The customs inequity is not dramatic.** When the senior freight handler is waved through, it should read as routine from NPC behavior — a nod, a scan confirmed, the lane opens. Overheard dialogue, if any, should be procedural: manifest check language, not conversational. The player learns that something is wrong from the pattern, not from a flagrant scene.
3. **The Commission inspector's gallery is their professional comfort zone.** Dialogue set in the gallery (if the detective accesses it) should reflect this — the inspector is at ease up here, slightly less guarded. On the floor, they are performing authority. In the gallery, they are just watching.
4. **The operations manager on the freight staging floor.** This is their element. Logistics language, shorthand with the senior freight handler. Any casual conversation with the detective here is the operations manager on home turf — helpful enough, not forthcoming.
5. **The flicker is an ambient event.** Travelers who know the schedule stop and wait. Travelers who don't know find themselves in a 90-second limbo — nothing is moving, customs is closed. The concourse fills briefly. Use this as a social compression beat: forced proximity, idle waiting, overheard conversations that wouldn't happen mid-flow.
6. **The staircase is visible, not obvious.** In Era 3 design language, it is clean, architectural, slightly more refined than the surrounding fittings. It doesn't broadcast Commission. Players who are paying attention will notice it; players who aren't will miss it. Second visit = "wait, I didn't see that last time."
+450
View File
@@ -0,0 +1,450 @@
# 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/krenn/` |
| Storyteller stub | `server/src/storyteller/mod.rs` |
---
*Ticket #158 — Tier 1 drama module schema. Paula (dramatic structure), Gestalt (schema format), Mellanie (authoring review).*
+1 -1
View File
@@ -60,7 +60,7 @@ Main menu, pause, save/load, options.
|-----------|---------|-------------------|
| [v01-main-menu](menus/v01-main-menu.png) | v0.1 | [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (functional warmth style), [D-027](../../../decisions/scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof) (two-character proof — character select) |
| [v01-pause-menu](menus/v01-pause-menu.png) | v0.1 | [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (visual style) |
| [v01-save-load](menus/v01-save-load.png) | v0.1 | [D-027](../../../decisions/scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof) (vertical slice), [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (visual style) |
| [v01-save-load](menus/v01-save-load.png) | v0.1 | [D-085](../../../decisions/architecture.md#d-085-per-game-save-directory-structure) (per-game save dirs), [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (visual style), [D-027](../../../decisions/scope.md#d-027-vertical-slice--smuggler--detective-two-character-proof) (vertical slice) |
| [v10-main-menu](menus/v10-main-menu.png) | v1.0 | [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (visual style), [D-036](../../../decisions/content.md#d-036-sova-transit-district--krenn-system-as-v01-setting) (setting — Sova Transit District), [D-013](../../../decisions/scope.md#d-013-diegetic-insertpoi-navigation-system) (diegetic insert — in-fiction menu) |
| [v10-options-full](menus/v10-options-full.png) | v1.0 | [D-043](../../../decisions/perception.md#d-043-art-direction--visual-style-functional-warmth) (visual style), [D-068](../../../decisions/architecture.md#d-068-5-bus-audio-architecture) (5-bus audio — per-bus volume controls), [D-069](../../../decisions/perception.md#d-069-audio-dip-profiles-for-dialogue-and-confrontation) (audio dip profiles) |
+136 -72
View File
@@ -37,156 +37,220 @@
"tab-save": {
"type": "Rectangle",
"left": 140, "top": 116, "width": 120, "height": 32,
"fillColor": "#1a2030",
"strokeColor": "#c8d0e0",
"fillColor": "#0d1018",
"strokeColor": "#333340",
"corners": [2, 2, 0, 0]
},
"tab-save-text": {
"type": "Text",
"left": 156, "top": 125,
"text": "SAVE",
"fontColor": "#c8d0e0",
"fontColor": "#556677",
"fontSize": 13
},
"tab-load": {
"type": "Rectangle",
"left": 264, "top": 116, "width": 120, "height": 32,
"fillColor": "#0d1018",
"strokeColor": "#333340",
"fillColor": "#1a2030",
"strokeColor": "#c8d0e0",
"corners": [2, 2, 0, 0]
},
"tab-load-text": {
"type": "Text",
"left": 280, "top": 125,
"text": "LOAD",
"fontColor": "#556677",
"fontColor": "#c8d0e0",
"fontSize": 13
},
"save-slot-1-active": {
"game-1-header": {
"type": "Rectangle",
"left": 140, "top": 156, "width": 860, "height": 72,
"left": 140, "top": 156, "width": 860, "height": 32,
"fillColor": "#151a24",
"strokeColor": "#333340",
"corners": [2, 2, 0, 0]
},
"game-1-title": {
"type": "Text",
"left": 152, "top": 165,
"text": "\u25bc DETECTIVE \u2014 Day 3 // Sova Transit // last played: today",
"fontColor": "#c8d0e0",
"fontSize": 12
},
"game-1-count": {
"type": "Text",
"left": 920, "top": 165,
"text": "3 saves",
"fontColor": "#556677",
"fontSize": 11
},
"qs-row": {
"type": "Rectangle",
"left": 158, "top": 192, "width": 842, "height": 62,
"fillColor": "#1a2030",
"strokeColor": "#c8d8f0",
"corners": [2, 2, 2, 2]
},
"save-slot-1-accent": {
"qs-accent": {
"type": "Rectangle",
"left": 140, "top": 156, "width": 3, "height": 72,
"left": 158, "top": 192, "width": 3, "height": 62,
"fillColor": "#c8d8f0",
"strokeColor": "#c8d8f0"
},
"slot-1-date": {
"qs-label": {
"type": "Text",
"left": 152, "top": 162,
"text": "AUTOSAVE // Day 1, 07:42",
"left": 170, "top": 200,
"text": "QUICKSAVE // Day 3, 14:22",
"fontColor": "#c8d0e0",
"fontSize": 13
},
"qs-location": {
"type": "Text",
"left": 170, "top": 218,
"text": "The Terminal \u2014 afternoon shift",
"fontColor": "#8899aa",
"fontSize": 12
},
"qs-timestamp": {
"type": "Text",
"left": 170, "top": 234,
"text": "saved: 2026-02-25 16:31",
"fontColor": "#556677",
"fontSize": 11
},
"qs-actions": {
"type": "Text",
"left": 840, "top": 212,
"text": "[Enter] Load\n[F6] Quickload",
"fontColor": "#c8d8f0",
"fontSize": 11
},
"auto-row": {
"type": "Rectangle",
"left": 158, "top": 260, "width": 842, "height": 62,
"fillColor": "#0e1118",
"strokeColor": "#333340",
"corners": [2, 2, 2, 2]
},
"auto-label": {
"type": "Text",
"left": 170, "top": 268,
"text": "AUTOSAVE // Day 3, 13:45",
"fontColor": "#8899aa",
"fontSize": 13
},
"auto-location": {
"type": "Text",
"left": 170, "top": 286,
"text": "Corridor B-7",
"fontColor": "#556677",
"fontSize": 12
},
"auto-timestamp": {
"type": "Text",
"left": 170, "top": 302,
"text": "saved: 2026-02-25 16:15",
"fontColor": "#3a4455",
"fontSize": 11
},
"slot-1-row": {
"type": "Rectangle",
"left": 158, "top": 328, "width": 842, "height": 62,
"fillColor": "#0e1118",
"strokeColor": "#333340",
"corners": [2, 2, 2, 2]
},
"slot-1-label": {
"type": "Text",
"left": 170, "top": 336,
"text": "SLOT 1 // Day 2, 22:10",
"fontColor": "#8899aa",
"fontSize": 13
},
"slot-1-location": {
"type": "Text",
"left": 152, "top": 180,
"text": "The Terminal — morning shift // Detective",
"fontColor": "#8899aa",
"left": 170, "top": 354,
"text": "Hab quarters \u2014 evening",
"fontColor": "#556677",
"fontSize": 12
},
"slot-1-timestamp": {
"type": "Text",
"left": 152, "top": 198,
"text": "saved: 2026-02-23 14:31",
"fontColor": "#556677",
"left": 170, "top": 370,
"text": "saved: 2026-02-25 14:48",
"fontColor": "#3a4455",
"fontSize": 11
},
"slot-1-actions": {
"type": "Text",
"left": 860, "top": 175,
"text": "[Enter] Overwrite / Load",
"fontColor": "#c8d8f0",
"fontSize": 12
},
"save-slot-2": {
"game-2-header": {
"type": "Rectangle",
"left": 140, "top": 236, "width": 860, "height": 72,
"fillColor": "#0e1118",
"left": 140, "top": 404, "width": 860, "height": 32,
"fillColor": "#111520",
"strokeColor": "#333340",
"corners": [2, 2, 2, 2]
},
"slot-2-date": {
"game-2-title": {
"type": "Text",
"left": 152, "top": 250,
"text": "SLOT 2 // Day 1, 06:15",
"left": 152, "top": 413,
"text": "\u25b6 SMUGGLER \u2014 Day 1 // The Terminal // last played: yesterday",
"fontColor": "#8899aa",
"fontSize": 13
},
"slot-2-location": {
"type": "Text",
"left": 152, "top": 268,
"text": "Arrival — entering Sova Transit // Detective",
"fontColor": "#556677",
"fontSize": 12
},
"slot-2-timestamp": {
"game-2-count": {
"type": "Text",
"left": 152, "top": 286,
"text": "saved: 2026-02-23 13:10",
"left": 920, "top": 413,
"text": "2 saves",
"fontColor": "#3a4455",
"fontSize": 11
},
"save-slot-3": {
"game-3-header": {
"type": "Rectangle",
"left": 140, "top": 316, "width": 860, "height": 72,
"fillColor": "#0e1118",
"left": 140, "top": 444, "width": 860, "height": 32,
"fillColor": "#111520",
"strokeColor": "#333340",
"corners": [2, 2, 2, 2]
},
"slot-3-date": {
"game-3-title": {
"type": "Text",
"left": 152, "top": 330,
"text": "SLOT 3 // Day 1, 07:30",
"left": 152, "top": 453,
"text": "\u25b6 DETECTIVE \u2014 Day 7 // Sova Transit // last played: Feb 20",
"fontColor": "#8899aa",
"fontSize": 13
},
"slot-3-location": {
"type": "Text",
"left": 152, "top": 348,
"text": "Corridor B-7 — Kael spotted // Smuggler",
"fontColor": "#556677",
"fontSize": 12
},
"slot-3-timestamp": {
"game-3-count": {
"type": "Text",
"left": 152, "top": 366,
"text": "saved: 2026-02-23 12:48",
"left": 920, "top": 453,
"text": "5 saves",
"fontColor": "#3a4455",
"fontSize": 11
},
"empty-slots-label": {
"footer-note": {
"type": "Text",
"left": 140, "top": 400,
"text": "SLOTS 4-8 — empty",
"fontColor": "#2a3040",
"fontSize": 12
},
"save-note": {
"type": "Text",
"left": 140, "top": 660,
"text": "Autosave on: zone transitions, conversation ends, significant events",
"left": 140, "top": 660, "width": 860,
"text": "Autosave: zone transitions, conversation ends, significant events // F5 quicksave // F6 quickload",
"fontColor": "#3a4455",
"fontSize": 11
"fontSize": 11,
"wordWrap": true
},
"annotation-title": {
"type": "Text",
"left": 16, "top": 720,
"text": "v0.1 SAVE / LOAD SCREEN",
"text": "v0.1 SAVE / LOAD \u2014 LOAD TAB (D-085)",
"fontColor": "#556677",
"fontSize": 11
},
"annotation-notes": {
"type": "Text",
"left": 16, "top": 734, "width": 1100,
"text": "Tab UI: Save / Load. Slots show: timestamp, in-game time+location, character. Autosave shown separately. Active slot has accent bar. No screenshots in v0.1.",
"text": "Games grouped by directory (D-085). Expand to see saves. QUICKSAVE + AUTOSAVE are system slots; manual slots below. SAVE tab shows current game only. F5/F6 global hotkeys for quicksave/quickload.",
"fontColor": "#3a4455",
"fontSize": 10,
"wordWrap": true
}
}
}
}