refactor(simulation): move content/ under server/ (#669)

Content files (RON templates, YAML campaigns, gauntlet data) are consumed
exclusively by the server. Colocate them at server/content/ and update all
path references in Rust source, tooling scripts, and schema files.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-17 10:31:20 +01:00
co-authored by Claude Opus 4.6
parent c0669489ff
commit b2ef56440f
73 changed files with 153 additions and 55 deletions
@@ -0,0 +1,7 @@
# Main Campaign metadata (D-003)
display_name: "The Settled Reach"
description: >
The core campaign. Set in a universe of wormhole-connected star systems,
neural lattice technology, and faction-driven politics. Occlusion-based
detective game with combat elements.
version: "0.1.0"
@@ -0,0 +1,16 @@
# Sova Transit District metadata (D-036)
# Identity derived from directory path: campaigns/main/systems/van-maanens-star/stations/sova/districts/transit/
# canonical_id: van-maanens-star.sova.transit (derived at load time by ContentValidator)
display_name: "Sova Transit District"
description: >
A 40-year-old prefab-modular-retrofitted freight logistics hub on Station Sova.
Five locations: The Terminal (logistics hub), The Last Shift (bar),
maintenance corridors, gate corridor (ground level), and the observation
gallery (Commission-only, z=2 above the gate concourse).
locations:
- "the-terminal"
- "the-last-shift"
- "maintenance-corridors"
- "gate-ground"
- "gate-gallery"
npc_count: 17
@@ -0,0 +1,151 @@
# Environmental Text: Maintenance Corridors
# Signs, terminals, documents, readable objects.
# Investigation-heavy. This is where the operational evidence lives.
# Ticket: #262 | Sprint: 12
location: maintenance-corridors
items:
# ==========================================
# ACCESS RESTRICTION SIGN — corridor entrance
# ==========================================
- id: env_corridor_001
type: sign
label: "Restricted Access Notice"
visibility: always
text: >
RESTRICTED ACCESS — MAINTENANCE PERSONNEL ONLY
B-CORRIDOR SECTOR — AUTHORIZED ENTRY REQUIRED
Unauthorized entry subject to Sova Station Authority review.
Commission Order 7741-B applies.
Report unauthorized personnel to: Station Security (Lattice: SovaStation/Security)
tags: [atmospheric, b7-corridor]
# ==========================================
# MERIDIAN COVERAGE SCHEDULE — posted on corridor wall
# Most investigation-relevant item in the game.
# ==========================================
- id: env_corridor_002
type: document
label: "Meridian Scan Schedule"
visibility: examine_only
text: >
MERIDIAN SURVEILLANCE COVERAGE — SOVA TRANSIT DISTRICT
MAINTENANCE CORRIDOR SECTOR — CURRENT CYCLE
Active coverage windows (full corridor monitoring):
06:00-07:30 | 10:00-11:00 | 14:00-15:30 | 22:00-23:00
Reduced coverage (perimeter only):
07:30-10:00 | 11:00-14:00 | 15:30-22:00 | 23:00-06:00
NOTE: Span gate maintenance cycles create signal interference.
Coverage gaps during gate calibration: approx 14:20-14:45.
Posted by: Sova Meridian Network Maintenance Division
tags: [investigation-relevant, meridian, surveillance-gap, critical]
knowledge_grant:
fact_id: "location.surveillance_gaps"
confidence: knows_details
# ==========================================
# MAINTENANCE LOG SHEET — handwritten clipboard at corridor junction
# ==========================================
- id: env_corridor_003
type: document
label: "Maintenance Log (B-7 Corridor)"
visibility: examine_only
text: >
B-7 CORRIDOR — MAINTENANCE LOG
Logged by: Pael Varren
[Month 11, Day 4]
Air scrubber pressure variance. Reset. Source unclear.
[Month 11, Day 12]
Seal failure B-7 junction. Third occurrence. Replaced again.
Noted: floor wear pattern inconsistent with posted traffic.
Possible deferred maintenance backlog creating additional load.
[Month 11, Day 22]
Recurring seal failure. Escalated to deferred queue.
NOTE: Pressure sensors reading elevated load during 14:00-15:00
window. Does not match shift transition foot traffic.
Filed maintenance anomaly report — awaiting management review.
tags: [investigation-relevant, b7-corridor, pael, critical]
knowledge_grant:
fact_id: "location.corridor_b7_restricted"
confidence: knows_of
# ==========================================
# ROUTING DIAGRAM — cargo flow map posted at junction
# ==========================================
- id: env_corridor_004
type: sign
label: "Cargo Routing Diagram"
visibility: always
text: >
SOVA TRANSIT — MAINTENANCE CORRIDOR ROUTING
[Technical diagram showing freight flow paths]
Standard freight path: Bay > Terminal > Surface Storage
Emergency reroute: Bay > B-corridor > Temporary Hold (C-4)
Maintenance access: B-corridor > Service Lift > Environmental Systems
[Someone has drawn a thin pencil line from Bay 4 to C-4 to
the service lift, bypassing the standard terminal route]
tags: [investigation-relevant, b7-corridor, smuggling-route]
knowledge_grant:
fact_id: "location.smuggling_route"
confidence: suspects
# ==========================================
# ENVIRONMENTAL WARNING — recycled air quality
# ==========================================
- id: env_corridor_005
type: sign
label: "Environmental Quality Notice"
visibility: always
text: >
CORRIDOR ENVIRONMENTAL STATUS — CURRENT READINGS
CO2: 0.12% [ACCEPTABLE — upper threshold 0.15%]
Particulate: ELEVATED — see maintenance queue
Temperature: 19.4°C [nominal]
Recycled air freshness: 73% [minimum recommended: 80%]
If recycled air quality falls below 70%, report to maintenance.
Current status: Monitor.
tags: [atmospheric, environmental]
# ==========================================
# TEMPORARY STORAGE MANIFEST SHEET
# At the C-4 temporary hold point
# ==========================================
- id: env_corridor_006
type: document
label: "Temporary Storage Log (C-4)"
visibility: examine_only
text: >
C-4 TEMPORARY HOLD — CONTENTS LOG
[Printed manifest, partial, two pages visible]
Container 4466 — Cleared — removed Month 11 Day 1
Container 4467 — Cleared — removed Month 11 Day 3
Container 4468 — Cleared — removed Month 11 Day 3
Container 4469 — [NO ENTRY]
Container 4470 — Cleared — removed Month 11 Day 8
Container 4471 — [PENDING — entered Month 11 Day 15, no removal logged]
Container 4472 — Cleared — removed Month 11 Day 18
[Gap in numbering between 4469-4470 has no explanation column]
tags: [container-4471, critical]
@@ -0,0 +1,209 @@
# Environmental Text: The Last Shift (Bar)
# Signs, terminals, documents, readable objects.
# Atmosphere-heavy. Some investigation relevance through social texture.
# Ticket: #262 | Sprint: 12
location: the-last-shift
items:
# ==========================================
# DRINK MENU — handwritten, scratched-out prices
# Above the bar. Visible to anyone.
# ==========================================
- id: env_bar_001
type: sign
label: "Drink Menu"
visibility: always
text: >
THE LAST SHIFT
(Lera's)
Van Maanen's Star grain spirit — 4c
Pale station brew — 3c ~~2c~~
Dark station brew — 3c
Import (off-station) — 8c
Sova standard water — 1c
Meal (hot, whatever's running) — 6c
No tabs. No Commission business at the bar.
House rules posted.
dual_lens:
smuggler: "Import's doubled since last year. Everything's tighter."
detective: >
"No Commission business at the bar." That's Lera's personal policy.
She'll have opinions about why.
tags: [atmospheric]
# ==========================================
# LERA'S RULES — above the bar
# ==========================================
- id: env_bar_002
type: sign
label: "House Rules"
visibility: always
text: >
LERA'S — HOUSE RULES
1. Pay before you're too drunk to remember.
2. Arguments outside. Always.
3. What's said at the bar stays at the bar.
4. No job talk after 20:00.
5. Torek Lintar runs his own tab. Don't ask.
dual_lens:
smuggler: "Rule 5. Lera's been running Torek's tab for years. Old arrangement."
detective: >
Rule 5 is specific. Named. Torek Lintar has a standing arrangement
here. That's the third time his name has come up without context.
tags: [investigation-relevant, torek-lintar, lera]
knowledge_grant:
fact_id: "relationship.torek_bar_arrangement"
confidence: suspects
# ==========================================
# NOTICE BOARD — general community notices
# Examine to read individual notices.
# ==========================================
- id: env_bar_003
type: readable_object
label: "Notice Board"
visibility: always
text: >
[NOTICE BOARD — various papers pinned over each other]
MISSING: Vessels Tamm, last seen Sova Transit District, Month 11.
Contact: Lattice Commission Sova liaison or speak to Lera.
CARGO WORKERS' ASSOCIATION — Monthly meeting, back room, Month 12,
evening shift. All Sova Transit workers welcome. Bring your own.
ROOM AVAILABLE: Residential pod B-3, lower level. Quiet. Short-term.
Speak to Harek at the bar.
[handwritten, pinned over the others:]
ANYONE KNOW WHO LEFT A TRANSIT BAG IN THE STORAGE ROOM?
IT'S BEEN THREE WEEKS.
dual_lens:
smuggler: >
Vessels Tamm. Missing person notice. That name's from the Tamm family —
Naia's connection? Or coincidence. Probably coincidence.
detective: >
Missing persons notice. Vessels Tamm. Sova Transit District. Cross-reference
Tamm with case file — possible Naia Tamm relation. Flag for follow-up.
tags: [investigation-relevant, naia-tamm-adjacent, harek]
knowledge_grant:
fact_id: "relationship.tamm_family"
confidence: suspects
# ==========================================
# GRAFFITI — bathroom wall
# Examine to read.
# ==========================================
- id: env_bar_004
type: readable_object
label: "Graffiti (Bathroom Wall)"
visibility: examine_only
text: >
[scratched into the wall surface over several years]
Voss doesn't check bay 4. Never has. Never will.
The Commission sees what it wants to see.
Lera knows.
[someone added in different handwriting:]
Lera doesn't know anything. She just sells drinks.
[original hand, below:]
Keep telling yourself that.
dual_lens:
smuggler: "Graffiti's been here since before I started. The Voss line is right, though."
detective: >
"Lera knows." Someone believes the bar owner has information.
Someone else pushes back. The debate is years old.
tags: [atmospheric, voss, lera, investigation-adjacent]
# ==========================================
# TONIGHT'S SPECIALS — chalkboard near the kitchen
# ==========================================
- id: env_bar_005
type: sign
label: "Tonight's Specials"
visibility: always
text: >
TONIGHT:
Hot grain porridge (Velen style) — 5c
Station fish fry — 7c
Ask Lera what Velen style means.
She'll tell you about it for twenty minutes.
dual_lens:
smuggler: "Lera's from Velen originally. She makes everyone ask."
detective: "Lera Sessik. Originally from Velen surface. Personal detail noted."
tags: [atmospheric, lera-backstory]
# ==========================================
# LATTICE-FREE ZONE SIGN — near the entrance
# ==========================================
- id: env_bar_006
type: sign
label: "Lattice-Free Zone Notice"
visibility: always
text: >
VOLUNTARY LATTICE-FREE ZONE
By Lera's request: please disable active lattice scanning
and recording functions while at the bar.
Conversation here is conversation. Not data.
This is not a Commission-recognized designation.
Lera doesn't care.
dual_lens:
smuggler: >
Everyone honors this. It's one of the reasons the bar works for
what it works for.
detective: >
Not legally enforceable. But anyone who honors it voluntarily is
actively choosing to reduce their surveillance footprint here.
Who honors it and who doesn't is worth noting.
tags: [investigation-relevant, lera, lattice-free]
# ==========================================
# CARGO WORKERS' ASSOCIATION FLYER — pinned to the bar
# ==========================================
- id: env_bar_007
type: document
label: "Association Meeting Flyer"
visibility: examine_only
text: >
SOVA TRANSIT CARGO WORKERS' ASSOCIATION
Monthly Meeting
Topic: Shift change protocols — the thirty-minute handoff window
and why it isn't long enough.
Also on agenda: Updated manifest compliance requirements from
the Commission (new reporting window: 6 hours, down from 8).
Back room. Evening shift. All welcome.
Bring your concerns, bring a drink.
dual_lens:
smuggler: >
Commission shortened the reporting window to six hours. That's
tighter than the eight-hour buffer. Someone noticed the gap.
detective: >
Agenda item: thirty-minute handoff window. Workers complaining
about it publicly means the window is known beyond the ring.
Or is being discussed for reasons that are less innocent.
tags: [investigation-relevant, shift-transition, commission]
knowledge_grant:
fact_id: "world.shift_schedule"
confidence: knows_of
@@ -0,0 +1,160 @@
# Environmental Text: The Terminal (Logistics Hub)
# Signs, terminals, documents, readable objects.
# Both characters can examine these. Interpretation differs.
# Ticket: #262 | Sprint: 12
location: the-terminal
items:
# ==========================================
# SHIFT BOARD — posted schedule
# Always visible. High-traffic reference point.
# ==========================================
- id: env_terminal_001
type: sign
label: "Shift Schedule Board"
visibility: always
text: >
SOVA TRANSIT DISTRICT — LOGISTICS HUB
CURRENT ROTATION — CYCLE 47
BAY 1-4: Day Shift 06:00-14:00 (Voss, K. — Supervisor)
BAY 5-8: Day Shift 06:00-14:00 (Maret, K. — Scheduler)
TRANSITION: 13:45-14:15 (30-min handoff window)
Night Shift 22:00-06:00 — ROSTER PENDING
[Bay 6: MAINTENANCE HOLD — see Pael Varren]
# ==========================================
# MANIFEST TRACKING TERMINAL — cargo status display
# Active terminal. Can be read from appropriate distance in investigation mode.
# ==========================================
- id: env_terminal_002
type: terminal
label: "Cargo Tracking Terminal"
visibility: always
text: >
ACTIVE MANIFEST QUEUE — SOVA LOGISTICS
Last updated: 06:23
Container 4471 — [PENDING REVIEW] — Bay 4
Container 4472 — CLEARED — Bay 2
Container 4473 — CLEARED — Bay 2
Container 4474 — [WEIGHT DISCREPANCY — 47kg] — Bay 4
Container 4475 — CLEARED — Bay 3
Container 4476 — PROCESSING — Bay 7
[8 additional items — scroll to continue]
tags: [manifest-discrepancy]
# ==========================================
# MAINTENANCE REQUEST CLIPBOARD
# Physical clipboard near loading arm 3. Examine to read.
# ==========================================
- id: env_terminal_003
type: document
label: "Maintenance Request Log"
visibility: examine_only
text: >
DEFERRED MAINTENANCE — CURRENT QUEUE
(Submitted: Pael Varren, Maintenance)
Loading arm 3 — Grinding during lift cycle — DEFERRED x4
Bay 6 environmental seal — Recurring failure — DEFERRED x3 (HOLD)
B-7 corridor air scrubber — Pressure variance — DEFERRED x1
Gate calibration bay 5-6 — Off-spec hum — DEFERRED x2
Recycling unit C-4 — Thermal spike — DEFERRED x1
Note: B-7 corridor showing irregular pressure load on sensor.
Traffic heavier than logged shift count explains. Investigating.
— P. Varren
tags: [investigation-relevant, b7-corridor]
knowledge_grant:
fact_id: "location.corridor_b7_restricted"
confidence: suspects
# ==========================================
# COMMISSION COMPLIANCE NOTICE
# Standard posting. Required by regulation. Two years old.
# ==========================================
- id: env_terminal_004
type: sign
label: "Commission Compliance Notice"
visibility: always
text: >
LATTICE COMMISSION — FREIGHT OPERATIONS COMPLIANCE
All manifest discrepancies exceeding 15kg must be reported
within 8 hours of discovery.
All cargo flagged PENDING REVIEW must be cleared by assigned
inspector before shift transition.
Unauthorized access to restricted maintenance corridors
will result in immediate suspension pending review.
Posted: Lattice Commission, Sova Station Authority
Reference: Commission Order 7741-B
tags: [atmospheric, institutional]
# ==========================================
# WORK ORDER NOTE — handwritten, coffee-stained
# Personal communication between dock workers. Examine to read.
# ==========================================
- id: env_terminal_005
type: document
label: "Handwritten Note (Coffee-Stained)"
visibility: examine_only
text: >
Kael —
Bay 4 queue backed up again. Maret says it's a weight issue
but she's not flagging it officially. If it's still there when
you come in, just route it through 7 like last time. Voss
won't check.
— D.
tags: [maret, drin, voss]
# ==========================================
# SAFETY PROCEDURES PLACARD — standard, faded
# ==========================================
- id: env_terminal_006
type: sign
label: "Emergency Procedures"
visibility: always
text: >
IN CASE OF EMERGENCY:
1. Activate hull breach alarm (red panel, every 20m)
2. Proceed to nearest designated shelter (yellow markers)
3. Do not use freight lifts during emergency protocols
4. Await station authority clearance before resuming operations
Last safety review: Month 4, Year 312 (Van Maanen's Star calendar)
tags: [atmospheric]
# ==========================================
# BAY STATUS BOARD — current bay availability
# ==========================================
- id: env_terminal_007
type: sign
label: "Bay Status Board"
visibility: always
text: >
BAY STATUS — CURRENT CYCLE
BAY 1: ACTIVE
BAY 2: ACTIVE
BAY 3: ACTIVE
BAY 4: ACTIVE — PENDING REVIEW (2 items)
BAY 5: ACTIVE
BAY 6: MAINTENANCE HOLD — Est. clear: next cycle
BAY 7: ACTIVE
BAY 8: SCHEDULED DOWNTIME 13:00-15:00
tags: [investigation-relevant, operational]
@@ -0,0 +1,48 @@
# Location: Gate Corridor — Observation Gallery
# Source: D-093 gate cluster zone spec
# Zone palette: surface #b8bec4 / fog tint #0a1222
# z=2 (above gate concourse)
canonical_id: "van-maanens-star.sova.transit.location.gate-gallery"
display_name: "Observation Gallery"
description: >
Commission-only observation gallery overlooking the gate concourse from
z=2. The gallery rail is a transparent low wall — occupants can see
down to the concourse below, but upward LOS from z=1 is blocked except
at the staircase. Access restricted to Commission personnel.
tile_bounds:
x_min: 0
y_min: 0
x_max: 31
y_max: 9
z: 2
# Legend:
# R = Restricted (Commission-only access)
# W = Wall (solid)
#
# Layout (32 wide x 10 tall):
# Row 0: North wall with staircase entrance (col 1 = R, access point)
# Rows 1-8: Gallery floor (R tiles — restricted access)
# Row 9: South wall (gallery rail — transparent low wall, modeled as W;
# cross-z LOS is handled server-side)
tiles:
- "WRWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRW"
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
sightlines:
open: true
notes: >
Gallery rail is transparent — full downward LOS to gate concourse (z=1).
Upward LOS from concourse to gallery is blocked except at staircase.
Cross-z LOS behavior handled by server shadowcasting system.
@@ -0,0 +1,83 @@
# Location: Gate Corridor — Ground Level
# Source: D-093 gate cluster zone spec
# Zone palette: surface #b8bec4 / fog tint #0a1222
canonical_id: "van-maanens-star.sova.transit.location.gate-ground"
display_name: "Gate Corridor"
description: >
The gate cluster handles all traffic between Station Sova and the horizon
gate network. From north to south: the restricted aperture chamber where
span gate transits occur, freight staging and passenger arrival halls,
customs lanes (freight and pedestrian), and the wide gate concourse
where arrivals disperse into the transit district.
tile_bounds:
x_min: 0
y_min: 0
x_max: 39
y_max: 33
z: 1
# Legend:
# F = Floor (walkable) W = Wall (solid, blocks LOS)
# R = Restricted (access-tier gated)
#
# Layout (40 wide x 34 tall):
# Row 0: North wall
# Rows 1-4: Aperture chamber (centered 8 wide, R tiles — restricted)
# Row 5: Wall separator
# Rows 6-13: Freight staging (west, 24 tiles) | passenger arrival (east, 13 tiles)
# Row 14: Wall with door gaps (cols 12, 32)
# Rows 15-24: Freight customs (west, 20 tiles, 3 lanes at 6vt + wall stubs)
# | corridor (6 tiles) | ped customs (east, 10 tiles, 3 lanes)
# Row 25: Wall with wide opening to concourse (cols 5-34 open)
# Rows 26-33: Gate concourse (full 38-tile width, 8 rows — public open space)
#
# D-093 zone dimensions: freight customs 20x10, ped customs 12x10 (10 rows each)
# Corridor width: gate concourse 8vt (D-093)
tiles:
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
- "WWWWWWWWWWWWWWWWRRRRRRRRWWWWWWWWWWWWWWWW"
- "WWWWWWWWWWWWWWWWRRRRRRRRWWWWWWWWWWWWWWWW"
- "WWWWWWWWWWWWWWWWRRRRRRRRWWWWWWWWWWWWWWWW"
- "WWWWWWWWWWWWWWWWRRRRRRRRWWWWWWWWWWWWWWWW"
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFFFW"
- "WWWWWWWWWWWWFWWWWWWWWWWWWWWWWWWWFWWWWWWW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WFFFFFFWFFFFFFWFFFFFFWFFFFFFWFFWFFFWFFFW"
- "WWWWWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWWWWW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
sightlines:
open: false
notes: >
Gate concourse (rows 26-33) is wide open with full sightlines.
Customs lanes have wall stubs creating partial occlusion between
lanes. Freight customs (west, 20 tiles, 3 lanes at 6vt) is wider
than pedestrian customs (east, 10 tiles, 3 lanes). Aperture chamber
is fully walled and restricted. Cross-z LOS from the observation
gallery (z=2) into the concourse is handled server-side.
@@ -0,0 +1,54 @@
# Location: Maintenance Corridors (smuggling spaces)
# Source: D-093, #313 (Sprint 12 spatial layout)
# Zone palette: surface #4e5054 / fog tint #101214
# z=0 (Era 1, below main structures)
canonical_id: "van-maanens-star.sova.transit.location.maintenance-corridors"
display_name: "Maintenance Corridors"
description: >
Era 1 maintenance infrastructure beneath the transit district. A long
transition corridor connects the logistics hub to the bar district.
Restricted storage at the west end is shared with The Terminal above.
A maintenance hatch at the east end provides the cross-z connection
to the main level. Everything reads as mundane maintenance — the ring
operates here because nobody looks twice.
tile_bounds:
x_min: 0
y_min: 0
x_max: 57
y_max: 5
z: 0
# Legend:
# F = Floor (walkable) W = Wall (solid, blocks LOS)
# R = Restricted (access-tier gated)
#
# Layout (58 wide x 6 tall):
# Row 0: North wall
# Row 1: Restricted storage (west, 8 tiles) | wall | corridor (40 tiles) | wall | hatch room (east, 6)
# Row 2: Doors connect rooms (F in wall positions at cols 9 and 50)
# Row 3-4: Same as row 1 (walls between rooms)
# Row 5: South wall
#
# Corridor width: 2vt visible at rows 1-4 (internal height, maintenance standard)
# Transition corridor: 40 tiles (~40m at 1m/vt) between storage and hatch per D-093
# Restricted storage: R tiles for access restriction
# Maintenance hatch: F tiles (cross-z connection is future movement feature)
# Design: mundane maintenance appearance per D-093 G-08
tiles:
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
- "WRRRRRRRRWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WRRRRRRRRFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WRRRRRRRRWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WRRRRRRRRWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
sightlines:
open: false
notes: >
Long straight corridor (40 tiles) with limited concealment. Storage and
hatch rooms are separated by walls with single-tile doors at row 2.
Mundane appearance per D-093 G-08 — no visually suspicious tile
arrangements.
@@ -0,0 +1,71 @@
# Location: The Last Shift (bar)
# Source: D-093, #312 (Sprint 12 spatial layout)
# Zone palette: surface #6b4018 / fog tint #200c04
canonical_id: "van-maanens-star.sova.transit.location.the-last-shift"
display_name: "The Last Shift"
description: >
A converted maintenance bay turned bar. The long counter runs along the
west wall with full sightlines across the room. A corner booth in the
northeast sees the bar, card table, entrance, and back room — prime
observation real estate. Scattered tables fill the main floor. A back
room with an alley exit provides the key traversal route for the ring.
tile_bounds:
x_min: 0
y_min: 0
x_max: 33
y_max: 21
z: 1
# Legend:
# F = Floor (walkable) W = Wall (solid, blocks LOS)
#
# Layout (34 wide x 22 tall):
# Row 0: North exterior wall
# Rows 1-4: Main floor + corner booth (NE, cols 26-31, walled alcove)
# Row 5: Bar counter starts (cols 1-2 = W fixture)
# Rows 6-10: Bar counter (cols 1-2 W), tables (W stubs), card table (col 25)
# Row 11: Open transition floor
# Rows 12-14: Table clusters (W stubs at cols 8, 16, 24)
# Row 15: News ticker mount (col 31 = W stub)
# Row 16: Open floor
# Row 17: Back room north wall with door (col 22 = F)
# Rows 18-20: Main floor (west) + back room (east, cols 22-32)
# Row 21: South wall with alley exit gap (cols 29-32 = F)
tiles:
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFWFFFFFFW"
- "WWWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WWWFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFW"
- "WWWFFFFFFFWFFFFFFFWFFFFFFWFFFFFFFW"
- "WWWFFFFFFFWFFFFFFFWFFFFFFFFFFFFFFW"
- "WWWFFFFFFFFFFFWFFFFFFFFFFFFFFFFFFW"
- "WWWFFFFFFFFFFFWFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFWFFFFFFFWFFFFFFFWFFFFFFFFW"
- "WFFFFFFFWFFFFFFFWFFFFFFFWFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWFW"
- "WFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFWFWFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFW"
- "WFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFFFW"
- "WWWWWWWWWWWWWWWWWWWWWWWWWWWWWFFFFW"
sightlines:
open: false
notes: >
Corner booth (NE alcove, rows 1-4) has LOS to bar counter, card table
area, main entrance, and back room door. Bar counter along west wall
(cols 1-2, rows 5-10) gives staff full sightlines across the main
floor. Back room is partially occluded by wall at col 21. Back room
door at col 22. Table W-stubs create partial cover but do not fully
block sightlines.
social_site: "bar"
@@ -0,0 +1,77 @@
# Location: The Terminal (logistics hub)
# Source: D-093, #311 (Sprint 12 spatial layout)
# Zone palette: surface #7a8490 / fog tint #0d1520
canonical_id: "van-maanens-star.sova.transit.location.the-terminal"
display_name: "The Terminal"
description: >
Sova's freight logistics hub. Scanner bays filter incoming cargo at the
south entrance; a wide main corridor runs north through the manifest
processing floor. The break room sits in the northeast. The supervisor's
office overlooks the corridor through a large interior window. Restricted
storage in the northwest corner is where the ring hides re-tagged cargo.
tile_bounds:
x_min: 0
y_min: 0
x_max: 43
y_max: 27
z: 1
# Legend:
# F = Floor (walkable) W = Wall (solid, blocks LOS)
# V = Void (outside building) R = Restricted (access-tier gated)
#
# Layout (44 wide x 28 tall):
# Rows 0: North exterior wall
# Rows 1-4: Restricted storage (NW, 11 tiles) + corridor + supervisor office (NE, 9 tiles)
# Row 5: Storage south wall; corridor + office continue
# Rows 6-8: Open corridor; supervisor office east side
# Row 7: ** Supervisor window — col 28 is F (LOS gap into office) **
# Row 9: Supervisor office south wall
# Rows 10-11: Main corridor (full width chokepoint — high sightline value)
# Rows 12-17: Manifest processing (west, 14 tiles) + break room (east, 13 tiles)
# Row 18: Break room south wall; manifest open to corridor
# Rows 19-21: South corridor
# Row 22: Scanner bay north walls with entry gaps
# Rows 23-26: Scanner bay interiors (3 bays)
# Row 27: South exterior wall
tiles:
- "VVVVVWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVV"
- "VVVVVWRRRRRRRRRRRWFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWRRRRRRRRRRRWFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWRRRRRRRRRRRWFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWRRRRRRRRRRRWFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWWWWWWWWWWWWWFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFWFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFWWWWWWWWWWWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFWFFFWFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFWWWWWWWWWWWWWWWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFWVVVVV"
- "VVVVVWWWWFFFFFFWWWWFFFFFFFFWWWWFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFWFFFFFFFFFFFWFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFWFFFFFFFFFFFWFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFWFFFFFFFFFFFWFFFFFFFFFFWVVVVV"
- "VVVVVWFFFFFFFFFWFFFFFFFFFFFWFFFFFFFFFFWVVVVV"
- "VVVVVWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWWVVVVV"
sightlines:
open: false
notes: >
Supervisor office window faces main corridor at row 7, col 28 (F gap in
wall line). Player standing in the corridor has direct LOS into the
office interior. This is the primary investigative discovery moment.
social_site: "logistics-hub"
@@ -0,0 +1,2 @@
# NPC daily routine schedules — Sova Transit District
# All NPC schedules in one file for cross-NPC scheduling validation
@@ -0,0 +1,101 @@
# Social site template: The Last Shift (bar)
# D-025: functional cluster as atomic template unit
# 4 role slots, 4+ NPCs in v0.1 (regulars are reference links). 4-8 NPC capacity.
template_id: bar
display_name: "The Last Shift"
description: >
Mid-grade bar in Sova Transit District. Converted maintenance staging area.
Irregular layout, long bar, corner booth. Where working people go after shift.
Informally known as "Lera's." The social nexus where investigation and
mundane life overlap.
location: the-last-shift
capacity:
min: 4
max: 8
role_slots:
- role: bar-owner
display_name: "Bar Owner"
count: 1
required: true
description: >
Owns and operates the bar. Observant, careful, knows everyone's business.
ANCHOR pattern — the stabilizing social presence. Hears everything,
says little. Access tier: insider for regulars.
pool_ref: null
flags:
- social_anchor
- information_hub
- bar_permanent
- role: bartender
display_name: "Bartender"
count: 1
required: true
description: >
Serves drinks, manages the floor. Different from the owner — the
bartender is staff, not the proprietor. Younger, less established.
pool_ref: null
flags:
- bar_permanent
- role: bar-regular
display_name: "Bar Regular"
count:
min: 2
max: 5
required: true
description: >
Off-duty workers and social visitors. Regulars are typically owned by
other templates (logistics hub, Commission) with reference links here.
Their presence during bar_evening creates the social observation space.
pool_ref: "transit:bar_regulars"
flags:
- evening_presence
- cross_template
# v0.1 NPC assignments
v01_assignments:
bar-owner: "npc:lera-sessik"
bartender: "npc:pell"
bar-regular-1: "npc:sera-venn" # Reference: Commission field tech, evening regular
bar-regular-2: "npc:resha" # Owned by bar template
# Additional regulars via reference links:
# npc:kael-davan (from logistics-hub, evening presence)
# npc:torek-lintar (from Commission, evening presence)
# npc:drin (from logistics-hub, occasional)
# Reference links — NPCs owned by other templates who visit the bar
reference_links:
- npc: "npc:sera-venn"
owning_template: null # Sera is Commission, not bar staff
relationship: "social anchor — evening regular"
presence_phases: [evening]
- npc: "npc:kael-davan"
owning_template: logistics-hub
relationship: "after-shift regular"
presence_phases: [evening]
- npc: "npc:torek-lintar"
owning_template: null # Commission inspector
relationship: "off-duty regular, gambling participant"
presence_phases: [evening]
- npc: "npc:drin"
owning_template: logistics-hub
relationship: "occasional visitor"
presence_phases: [evening]
triangle_constraints:
- triangle: bar-tensions
required_roles:
- bar-owner # Lera: owner mediating
- bar-regular # Resha: regular with grievance
- bartender # Pell: caught in the middle
- triangle: worried-knowledge
required_roles:
- bar-regular # Sera: avoiding Torek at the bar
dialogue_pools:
- location: the-last-shift
roles: [bar-owner, bartender, bar-regular]
@@ -0,0 +1,106 @@
# Social site template: The Terminal (logistics hub)
# D-025: functional cluster as atomic template unit
# 5 role slots, 6 NPCs in v0.1. 4-8 NPC capacity.
template_id: logistics-hub
display_name: "The Terminal"
description: >
Freight logistics hub — the beating heart of Sova Transit District. Cargo
processing, manifest management, shift rotations. The smuggler's workplace
and the ring's operational cover.
location: the-terminal
# Capacity range per D-025: 4-8 NPCs in connected space with internal sightlines
capacity:
min: 4
max: 8
role_slots:
- role: shift-supervisor
display_name: "Shift Supervisor"
count: 1
required: true
description: >
Authority figure. Oversees dock operations, manages shift schedules,
enforces procedures. Access tier: authority within the hub.
pool_ref: null # Fixed assignment in v0.1
flags:
- authority_figure
- hub_permanent
- role: dock-worker
display_name: "Dock Worker"
count:
min: 2
max: 4
required: true
description: >
Core workforce. Processes cargo, handles manifests, operates equipment.
One dock-worker slot is reserved for the smuggler's FRIEND (pool draw).
pool_ref: "transit:friend_smuggler" # One slot drawn from FRIEND pool
flags:
- hub_permanent
- ring_candidate
- role: new-hire
display_name: "New Hire"
count: 1
required: false
description: >
Recent arrival to the logistics hub. Asks questions, doesn't know the
social norms yet, provides a fresh perspective. CATALYST pattern.
pool_ref: null
flags:
- catalyst
- newcomer
- role: scheduler
display_name: "Freight Scheduler"
count: 1
required: true
description: >
Manages freight scheduling and manifest coordination. Has access to
cargo records — a key information source for both smuggler and detective.
pool_ref: null
flags:
- hub_permanent
- information_access
- role: courier
display_name: "Courier"
count: 1
required: false
description: >
Moves between locations. Off-duty security or delivery runner.
Bridge NPC who connects the hub to other social sites.
pool_ref: null
flags:
- bridge_npc
# v0.1 NPC assignments (matches seed config design doc section 4.4)
v01_assignments:
shift-supervisor: "npc:voss"
dock-worker-1: "npc:kael-davan"
dock-worker-2: "npc:drin"
new-hire: "npc:renn"
scheduler: "npc:maret-korr"
courier: "npc:harek"
# Triangles that involve members of this template
triangle_constraints:
- triangle: hub-power
required_roles:
- shift-supervisor # Voss: authority
- dock-worker # Kael: subordinate
- scheduler # Maret: caught-between
- triangle: worried-partner
required_roles:
- dock-worker # Kael: partner under ring pressure
- triangle: informant-question
required_roles:
- dock-worker # Drin: notices Torek's inspection irregularities
# Dialogue pool locations (maps to dialogue/ subdirectory)
dialogue_pools:
- location: the-terminal
roles: [shift-supervisor, dock-worker, new-hire, scheduler, courier]
@@ -0,0 +1,88 @@
# Social site template: Smuggling Ring (maintenance corridors)
# D-025: functional cluster as atomic template unit
# Smallest template: 2-4 NPCs. Operates in infrastructure gaps.
template_id: smuggling-ring
display_name: "The Ring"
description: >
Smuggling operation using repurposed maintenance infrastructure. Not a
dedicated room — a corridor between hub sections where old and new
construction don't quite meet. Access through service hatches. The ring
didn't build this space; they found it. Operates during night shift and
shift transitions when oversight is thinnest.
location: maintenance-corridors
capacity:
min: 2
max: 4
role_slots:
- role: ring-operative
display_name: "Ring Coordinator"
count: 1
required: true
description: >
Coordinates ring operations. HANDLER motivation — manages logistics,
maintains contacts, makes decisions. Capable, not theatrical.
pool_ref: null
flags:
- ring_leader
- night_presence
- restricted_access
- role: ring-contact
display_name: "Ring Contact"
count:
min: 1
max: 2
required: true
description: >
Active ring member with dual role — legitimate job at the logistics hub
by day, ring operations by night. Reference link from logistics-hub
template. Their presence here is the core secret the smuggler protects
and the detective investigates.
pool_ref: "transit:friend_smuggler" # The FRIEND is the primary ring contact
flags:
- dual_role
- night_presence
- ring_member
- role: external-contact
display_name: "External Contact"
count: 1
required: false
description: >
Off-station connection. Arrives with shipments, departs quickly.
Not a permanent presence — appears during ring operation windows.
pool_ref: null
flags:
- transient
- external
# v0.1 NPC assignments
v01_assignments:
ring-operative: "npc:devra"
ring-contact: "npc:kael-davan" # Reference from logistics-hub (dual role)
external-contact: "npc:sabel" # Off-system contact
reference_links:
- npc: "npc:kael-davan"
owning_template: logistics-hub
relationship: "ring member — dual role, night operations"
presence_phases: [night]
- npc: "npc:sabel"
owning_template: null # External, transient
relationship: "off-station buyer/receiver"
presence_phases: [night] # Irregular, tied to shipments
triangle_constraints:
- triangle: worried-partner
required_roles:
- ring-contact # Kael: under pressure from ring work
- triangle: hub-power
required_roles:
- ring-contact # Kael: subordinate to Voss by day, ring by night
dialogue_pools:
- location: maintenance-corridors
roles: [ring-operative]
@@ -0,0 +1,242 @@
# News ticker headline pool — The Last Shift bar
# Meridian Feed (Van Maanen's Star local news network)
# 30 lines total | Categories: freight (9), politics (4), infrastructure (5), sports (3),
# commission (5), community (4) — see dual_lens notes on each line
# Dual-lens content: each headline reads differently to smuggler vs detective.
# Ticket: #306 | Sprint: 12 | Owner: Mellanie + Miri
# v0.1: static background text. v0.2: cycling ticker system.
location: the-last-shift
feed: meridian
headlines:
# --- Freight / Commodity (6) ---
- id: ticker_001
text: "VAN MAANEN FREIGHT INDEX: Lattice components +12% on Syndic supply restrictions"
category: freight
dual_lens:
smuggler: "Prices up. Every run is worth more — and every run is riskier."
detective: "Supply restrictions mean increased black market pressure. Watch for volume spikes."
- id: ticker_002
text: "SOVA DOCKING: Revised rates effective cycle 14 — bulk freight +0.8 cred/tonne"
category: freight
dual_lens:
smuggler: "Costs going up. Nils won't be happy. Margins are already thin."
detective: "Administrative noise. But fee changes affect which routes are economically viable."
- id: ticker_003
text: "ROUTE ADVISORY: Span gate maintenance on Van Maanen's Star-Havara corridor — 48hr delays"
category: freight
dual_lens:
smuggler: "Havara corridor's down. If Sabel's shipments are routed through there, we wait."
detective: "Transit disruption changes shipping patterns. New routes create new anomalies."
- id: ticker_004
text: "COMMODITY ALERT: Medical-grade neural components — backorder extends to 6 cycles"
category: freight
dual_lens:
smuggler: "Six cycles. People need those replacements now. That's why we do this."
detective: "Extended shortage increases incentive for diversion. Motive for the operation."
- id: ticker_005
text: "FREIGHT QUARTERLY: Sova throughput up 3% year-on-year — transit district credited"
category: freight
dual_lens:
smuggler: "More volume, more cover. Good news for us."
detective: "Increased throughput makes statistical anomalies harder to spot. More noise."
- id: ticker_006
text: "INSURANCE: Premium review for manifest discrepancy incidents — sector-wide audit"
category: freight
dual_lens:
smuggler: "Manifest discrepancies. If the insurers are looking, the Commission's next."
detective: "Insurance sector flagging the same pattern I'm seeing. Independent confirmation."
# --- Assembly Politics (4) ---
- id: ticker_007
text: "ASSEMBLY: Lattice regulation amendments tabled — vote deferred to next session"
category: politics
dual_lens:
smuggler: "Deferred. Status quo holds. The demand stays, the supply stays illegal."
detective: "Regulatory gap continues. No political will to address the root cause."
- id: ticker_008
text: "ASSEMBLY VOTE: Infrastructure bill passes 312-287 — Reach districts gain allocations"
category: politics
dual_lens:
smuggler: "More money for the Reach. Maybe Sova gets a refit. Maybe things change."
detective: "Allocation shifts. New infrastructure means new oversight — or new blind spots."
- id: ticker_009
text: "DELEGATE PROFILE: Rep. Hask (Van Maanen's Star) calls for Commission oversight review"
category: politics
dual_lens:
smuggler: "Commission oversight review. Could mean more patrols. Could mean fewer. Either way, noise."
detective: "Political pressure on Commission operations. Budget review affects field resources."
- id: ticker_010
text: "POLICY WATCH: Cross-system freight harmonization bill enters second reading"
category: politics
dual_lens:
smuggler: "Harmonization means standardization. Standardization means fewer gaps in the paperwork."
detective: "Cross-system data sharing would connect manifest anomalies across jurisdictions."
# --- Local Infrastructure (4) ---
- id: ticker_011
text: "SOVA STATION: Deck 4 ventilation overhaul — expect cycled air disruptions"
category: infrastructure
dual_lens:
smuggler: "Construction crews on Deck 4. More people in the corridors. Watch the routes."
detective: "Infrastructure work. Construction means altered access patterns."
- id: ticker_012
text: "TRANSIT: Corridor B maintenance scheduled cycle 16-18 — alternate routing posted"
category: infrastructure
dual_lens:
smuggler: "Corridor B. That's our route. Maintenance means official access. We shut down for three cycles."
detective: "Scheduled maintenance on a corridor near the freight bays. Who requested it?"
- id: ticker_013
text: "WATER RECYCLING: Stage 2 filter replacement ahead of schedule — capacity at 97%"
category: infrastructure
dual_lens:
smuggler: "Good. One less thing to worry about."
detective: "Infrastructure baseline. Station systems functioning normally."
- id: ticker_014
text: "POWER GRID: Outer ring voltage fluctuations traced to span gate resonance"
category: infrastructure
dual_lens:
smuggler: "Voltage fluctuations in the outer ring. Cameras might glitch. Useful."
detective: "Power issues could affect surveillance systems. Note the timing."
# --- Sports (3) ---
- id: ticker_015
text: "GRAV-BALL: Sova Drifters fall to Havara United 3-1 — playoff hopes dim"
category: sports
dual_lens:
smuggler: "Drifters lost again. Lera's going to be in a mood."
detective: "Sports. The universal social lubricant. File under 'conversation openers.'"
- id: ticker_016
text: "LEAGUE TABLE: Van Maanen's Star Conference — Sova 4th, Mirren Station leads"
category: sports
dual_lens:
smuggler: "Fourth. We were second last season. This place is sliding."
detective: "Local standings. Note who cares about the Drifters — it's a belonging marker."
- id: ticker_017
text: "HIGHLIGHT: Drifters' Pael Tev named to All-Reach reserve squad"
category: sports
dual_lens:
smuggler: "Tev made the reserves. Good for him. At least someone's getting out."
detective: "The name Tev — any relation to dock personnel? Check roster."
# --- Commission Advisories (3) ---
- id: ticker_018
text: "COMMISSION: Manifest compliance audits scheduled — Sova Transit, cycles 15-20"
category: commission
dual_lens:
smuggler: "Compliance audits. Cycle 15. That's five cycles from now. We need to be clean by then."
detective: "Scheduled audits. Standard, but the timing aligns with my investigation window."
- id: ticker_019
text: "ADVISORY: Unlicensed lattice modifications carry Category 3 penalties — report anonymously"
category: commission
dual_lens:
smuggler: "Category 3. Years in detention. Same poster, same message, same empty threat. Until it isn't."
detective: "Public messaging campaign. Commission HQ wants visible enforcement. Pressure from above."
- id: ticker_020
text: "ENFORCEMENT: Commission confirms 'routine assessment' of Reach freight operations"
category: commission
dual_lens:
smuggler: "'Routine assessment.' Is that the detective? Is that why he's here?"
detective: "That's my cover story. Or headquarters' way of telling me the clock is running."
# --- Local Community (4) — added Sprint 12, Mellanie ---
- id: ticker_021
text: "SECTOR 3: Sova Station Admin acknowledges ventilation review — timeline 'under discussion'"
category: infrastructure
dual_lens:
smuggler: "Under discussion. Same thing they said last cycle. Seven years of under discussion."
detective: "Deferred maintenance, seven-year backlog, workers frustrated. That's a community with grievances. File that."
- id: ticker_022
text: "WORKERS: Cargo Workers' Association calls for review of thirty-minute shift handoff protocols"
category: community
dual_lens:
smuggler: "Workers pushing back on the transition window. If they shorten it, we lose our gap."
detective: "Workers know the window is too short. And they're right. The question is who else knows it."
- id: ticker_023
text: "SOVA LOCAL: Missing persons — Vessels Tamm, Sova Transit District, Month 11. Contact Sova liaison."
category: community
dual_lens:
smuggler: "Vessels Tamm. That name — any connection to Naia's family? Don't chase ghosts."
detective: "Tamm. Naia Tamm is the name in the case file as Kael Davan's partner. Related? Flag."
notes: >
Connects to notice board in the-last-shift environmental text (env_bar_003).
Detective's reaction sets up the Naia-Tamm connection thread.
- id: ticker_024
text: "HEALTH: Re-embodiment wait times at Sova Clinic — non-emergency cases: 4-6 cycles. Main station for urgent."
category: community
dual_lens:
smuggler: "Four cycles. If something goes wrong on a run, four cycles at the clinic before you're back. Remember that."
detective: "Healthcare routing. Non-urgent cases handled locally, urgent cases moved off-station. Operational detail."
# --- Lattice Economy (3) — added Sprint 12, Mellanie ---
- id: ticker_025
text: "LATTICE: Commission-approved upgrade costs rise — standard lattice enhancement up 18% this cycle"
category: freight
dual_lens:
smuggler: "Eighteen percent. That's why the waiting list for off-book components keeps growing. We're providing a service."
detective: "Price pressure on approved upgrades. Economic driver for the unlicensed market. Motive confirmed."
- id: ticker_026
text: "LATTICE COMMISSION: Public access modification registry now mandatory — self-report deadline cycle 20"
category: commission
dual_lens:
smuggler: "Self-report. Right. The people who bought off-book components are definitely filing that form."
detective: "Mandatory registry. Anyone who doesn't file either doesn't have mods or is hiding them. Narrow that list."
- id: ticker_027
text: "HEALTHCARE: Medical-grade lattice component recall — batch 44xx — contact nearest clinic for assessment"
category: community
dual_lens:
smuggler: "Batch 44xx. Kael's last shipment was 4471 series components. Are they in that range? Check."
detective: "Batch 44xx recall. Container 4471 in the manifest anomaly. Cross-reference. This could be the thread."
notes: >
High-value dual-lens trigger. Smuggler worries about operational risk; detective spots the
evidence connection. The 44xx range is intentionally ambiguous — might match, might not.
# --- Van Maanen's Star / Setting (2) — added Sprint 12, Mellanie ---
- id: ticker_028
text: "VELEN WEATHER: Coastal fog advisories in effect — Sova orbital approach window reduced, gate scheduling adjusted"
category: infrastructure
dual_lens:
smuggler: "Fog on Velen. Gate scheduling adjusted — means transit timing shifts. The window might move."
detective: "Environmental factors affecting gate operations. Adjust surveillance timing accordingly."
- id: ticker_029
text: "REACH ECONOMY: Van Maanen's Star trade volume up 7% — analysts attribute to 'informal market activity'"
category: freight
dual_lens:
smuggler: "Informal market activity. They know. The whole Reach knows. And still the demand keeps climbing."
detective: "'Informal market.' The analysts see the pattern but can't name it. I can."
- id: ticker_030
text: "ASSEMBLY: Severance technology moratorium extended — counter-surveillance devices remain Category 2 restricted"
category: politics
dual_lens:
smuggler: "Moratorium extended. Good. Severance equipment costs keep the Commission from blanketing everything."
detective: "Counter-surveillance devices still legally restricted. Anyone running them is committing a documented infraction."
@@ -0,0 +1,49 @@
# Triangle 3: Bar Tensions — mundane social friction at The Last Shift
# Passive — runs on background simulation, no storyteller intervention
canonical_id: bar-tensions
display_name: "Bar Tensions"
description: >
Mundane social friction at The Last Shift. Lera Sessik (bar owner) runs
a tight establishment. Resha (regular) has a grievance — could be about
the bar, another regular, or community politics. Pell (bartender) is
caught in the middle, trying to keep both the boss and the customers
happy. This is the 50% of life that has nothing to do with conspiracy —
the noise floor that makes investigation signal meaningful.
members:
- npc: "npc:lera-sessik"
role: "owner — mediates disputes, protects her establishment"
- npc: "npc:resha"
role: "regular — has a grievance, vocal about it"
- npc: "npc:pell"
role: "bartender — caught between boss and customers"
forks:
- id: grievance-escalation
condition: >
Resha's grievance reaches a tipping point. Lera must decide whether
to take sides or maintain neutrality. Pell gets drawn in.
outcomes:
- id: lera-sides-with-resha
description: >
Lera validates Resha's complaint. Pell adjusts service accordingly.
Social dynamics shift — Resha becomes more central, the bar's
atmosphere changes slightly.
effects:
- "resha.behavior_flags += vindicated"
- "pell.behavior_flags += accommodating"
- "bar atmosphere = tense but resolving"
- id: lera-shuts-it-down
description: >
Lera tells Resha to drop it. Her bar, her rules. Resha sulks.
Pell is relieved. The grievance goes underground.
effects:
- "resha.behavior_flags += resentful,quiet"
- "pell.behavior_flags += relieved"
- "bar atmosphere = business as usual"
resolution_states:
- id: grievance-resolved
description: The dispute is settled one way or another. Normal bar dynamics resume.
- id: grievance-simmering
description: The issue goes underground. Occasional flare-ups provide social texture.
@@ -0,0 +1,70 @@
# Triangle 1: Hub Power — workplace hierarchy under ring pressure
# Active fork — smuggler decision (3 paths, self-contained per D-053/A-17)
canonical_id: hub-power
display_name: "Hub Power"
description: >
Workplace hierarchy tension at The Terminal. Voss (shift supervisor) has
positional authority but takes operational orders from the ring. Kael
(dock worker, THE FRIEND) is caught between his boss and the ring's demands.
Maret (freight scheduler) sees manifest discrepancies and doesn't know
whom to tell. The triangle's resting tension: Voss needs quiet, Kael
needs everything to hold together, Maret is too smart not to notice.
members:
- npc: "npc:voss"
role: "authority — shift supervisor with ring complicity"
- npc: "npc:kael-davan"
role: "subordinate — dock worker, ring member, caught between boss and ring"
- npc: "npc:maret-korr"
role: "caught-between — freight scheduler who sees the discrepancies"
forks:
- id: volume-escalation
condition: >
Ring demands double-volume shipment via intermediary. Smuggler
must decide how to handle the pressure on Voss and Kael.
outcomes:
- id: escalate
description: >
Side with ring (escalate). Convince Voss to widen the shift gap.
Investigation accelerates — manifest discrepancies spike, Voss
becomes visibly anxious. Detective reads organizational stress.
effects:
- "voss.behavior_flags += nervous,schedule_changes,evasive"
- "voss.position_integrity = thin"
- "ring.exposure_risk increases"
- "detective.investigation_pace = accelerated"
- id: stabilize
description: >
Side with Voss (stabilize). Reject or delay the double shipment.
Hub runs quiet but Kael absorbs redirected pressure from ring
coordinator. Detective reads personal stress on Kael instead.
effects:
- "kael.behavior_flags += lattice_checking,distracted,evasive"
- "kael.trust_level (from ring) drops"
- "friend_arc timing = accelerated"
- "detective.investigation_pace = slowed"
- id: mediate
description: >
Compromise — half volume, phased across two shipments. Nobody's
happy, nobody's exposed. Detective reads inconsistent operational
tempo. Creates a deferred second decision point.
effects:
- "ring.exposure_risk = moderate"
- "smuggler.operational_trust (from ring) drops"
- "kael.behavior_flags += uncertain,distracted"
- "detective reads organizational confusion"
resolution_states:
- id: ring-exposed-via-voss
description: >
Voss's anxiety becomes the detective's thread to pull. Investigation
arrives at the ring through institutional signals.
- id: ring-exposed-via-kael
description: >
Kael's personal strain becomes the detective's signal. Investigation
arrives through behavioral tells on THE FRIEND.
- id: ring-exposed-via-inconsistency
description: >
Irregular operational tempo reveals internal disagreement. Detective
infers organizational structure from pattern disruption.
@@ -0,0 +1,68 @@
# Triangle 5: Worried Partner — ring pressure on personal relationships
# Passive tension (D-087) — background pressure, not a direct player decision point
canonical_id: worried-partner
display_name: "Worried Partner"
classification: passive_tension
description: >
Kael Davan (dock worker, ring member, smuggler's FRIEND) is under
increasing ring pressure from Devra (ring coordinator). Naia Tamm
(Kael's partner) is worried about his late nights and evasive answers.
The smuggler knows why Kael is busy and can't say. This is the most
emotionally loaded triangle — it connects THE FRIEND's contradiction
arc to personal relationships. If Naia asks the smuggler directly,
there's no good answer.
members:
- npc: "npc:kael-davan"
role: "partner under pressure — ring work destroying home life"
- npc: "npc:naia-tamm"
role: "worried — sees the changes, doesn't understand the cause"
- npc: "npc:devra"
role: "cause — ring coordinator whose demands increase Kael's burden"
forks:
- id: naia-confronts
condition: >
Naia's worry reaches a threshold. She confronts Kael directly, or
asks the smuggler what's going on. The smuggler faces a loyalty test.
outcomes:
- id: smuggler-deflects
description: >
Smuggler deflects Naia's questions. Protects Kael's cover but
damages trust with Naia. Kael is grateful but the lie costs.
effects:
- "naia.trust_level (toward smuggler) drops"
- "kael.behavior_flags += relieved,guilty"
- "smuggler.moral_weight (toward naia) = complicit"
- id: smuggler-hints
description: >
Smuggler gives Naia a partial truth — Kael's under work pressure,
it'll pass. Not a lie, but not the truth. Naia is temporarily
reassured but the pattern continues.
effects:
- "naia.behavior_flags += cautiously_reassured"
- "kael.behavior_flags += unaware"
- "smuggler.moral_weight (toward naia) = peripheral"
- id: kael-snaps
description: >
Before the smuggler can intervene, Kael snaps at Naia under
pressure. The argument is public enough to be observed. Devra's
pressure has become visible through personal fallout.
effects:
- "kael.behavior_flags += agitated,public_argument"
- "naia.behavior_flags += hurt,withdrawn"
- "detective observes domestic stress (behavioral signal)"
resolution_states:
- id: relationship-strained
description: >
Kael and Naia's relationship is visibly strained. The domestic
tension becomes an observable signal for the detective.
- id: temporary-calm
description: >
The immediate crisis passes but the underlying cause remains.
Another confrontation is inevitable.
- id: naia-investigates
description: >
Naia starts looking into Kael's activities independently. She
becomes a potential variable the ring didn't account for.
@@ -0,0 +1,7 @@
# Station Sova metadata (D-036)
display_name: "Station Sova"
description: >
A freight logistics station in Van Maanen's Star. 40-year-old prefab-modular
facility serving the span gate's cargo throughput. ~12,000 population.
districts:
- "transit"
@@ -0,0 +1,8 @@
# Van Maanen's Star metadata (D-036)
display_name: "Van Maanen's Star"
description: >
A mid-tier system connected via the Sova span gate. Industrial economy
centered on freight logistics and lattice component trade. ~2.4M population,
~180 years settled.
stations:
- "sova"
+11
View File
@@ -0,0 +1,11 @@
# Content manifest — The Settled Reach v0.1
# The server reads this first to discover campaigns and content layout.
# District discovery uses glob patterns — no per-district listing needed.
version: "0.1.0"
campaigns:
- id: "main"
path: "campaigns/main"
enabled: true
discovery:
districts: "systems/**/districts/*/district.yaml"
@@ -0,0 +1,7 @@
scope: cross_room
description: "Cross-room checks applied to every Gauntlet room visit."
conditions:
- id: cross-player-entity-present
description: "Player entity (StableId 0) always present in snapshot"
condition_type: entity_present
entity_id: 0
@@ -0,0 +1,42 @@
room_id: inventory_warehouse
room_name: Inventory Warehouse
description: "Tests D-065 (9-slot inventory), pickup/drop verbs, CarriedBy component. 10 crates (StableId 13-22) + 1 NPC (StableId 23)."
conditions:
- id: inv-player-spawn
description: "Player starts near warehouse spawn position"
condition_type: player_near
x: 17
y: 54
radius: 2.0
- id: inv-player-facing-east
description: "Player faces east at spawn"
condition_type: player_facing
direction: East
- id: inv-crate-01-visible
description: "First crate (Keycard) is visible from spawn"
condition_type: entity_present
entity_id: 13
- id: inv-crate-10-visible
description: "Last crate (Chip) is visible"
condition_type: entity_present
entity_id: 22
- id: inv-npc-warehouse-visible
description: "Warehouse supervisor NPC is present"
condition_type: entity_present
entity_id: 23
- id: inv-crate-pickup-verb
description: "Pickup verb available on crate when nearby"
condition_type: expected_interaction_verb
entity_id: 13
verb: Pickup
- id: inv-npc-talk-verb
description: "Talk verb available on warehouse NPC"
condition_type: expected_interaction_verb
entity_id: 23
verb: Talk
@@ -0,0 +1,41 @@
room_id: occlusion_corridor
room_name: Occlusion Corridor
description: "Tests D-035 (symmetric shadowcasting), D-017 (perception modes), D-015 (vision cone sectors). 4 NPCs (StableId 9-12)."
conditions:
- id: occ-player-spawn
description: "Player starts at corridor entrance"
condition_type: player_near
x: 84
y: 58
radius: 2.0
- id: occ-player-facing-east
description: "Player faces east down the corridor"
condition_type: player_facing
direction: East
- id: occ-guard-visible
description: "Guard NPC in clear LOS from spawn"
condition_type: entity_present
entity_id: 9
- id: occ-hidden-npc-absent
description: "NPC behind north wall segment is NOT visible from spawn"
condition_type: entity_absent
entity_id: 10
- id: occ-peripheral-npc-visible
description: "South alcove NPC visible in peripheral vision sector"
condition_type: entity_present
entity_id: 11
- id: occ-far-end-visible
description: "Far end NPC visible down corridor"
condition_type: entity_present
entity_id: 12
- id: occ-guard-talk-verb
description: "Talk verb available on guard NPC when nearby"
condition_type: expected_interaction_verb
entity_id: 9
verb: Talk
@@ -0,0 +1,26 @@
room_id: pause_chamber
room_name: Pause Chamber
description: "Tests D-031 (pause/unpause), Bug #3 regression (movement while paused). 1 NPC (StableId 29)."
conditions:
- id: pause-player-spawn
description: "Player starts at chamber center"
condition_type: player_near
x: 50
y: 86
radius: 2.0
- id: pause-player-facing-north
description: "Player faces north at spawn"
condition_type: player_facing
direction: North
- id: pause-npc-present
description: "Pause target NPC is visible"
condition_type: entity_present
entity_id: 29
- id: pause-talk-verb
description: "Talk verb available on NPC (should work while paused)"
condition_type: expected_interaction_verb
entity_id: 29
verb: Talk
+175
View File
@@ -0,0 +1,175 @@
// Osse Margin Culture Profile
//
// Schema: server/src/npc/blueprint.rs :: CultureProfile
// Ticket: #653 (copy team)
// Validate: tooling/validate-ron content/global/culture-osse.ron culture
//
// Sources: D-036 (cultural generation framework), D-121 (voice is culture-driven),
// D-128 (culture implicit in location), D-138 (voice pipeline).
//
// Osse: ~60 years settled. Deep Frontier K-class system. Resource extraction industry.
// Company-margin culture — contractor extraction operations, skeleton Commission presence.
// Your shift team is the fundamental social unit. Words cost attention; attention
// keeps you alive. Trust built through shared risk, not time.
//
// Heritage root: Salt — utilitarian_cover. Labor function covers presence.
// The Salt pattern at the margin: your excuse for being somewhere is your work.
// Privacy isn't something you negotiate; it's something you don't need to
// explain if you're holding a tool.
(
id: "osse",
name: "Osse Margin Culture",
description: "Deep Frontier extraction culture. ~60 years settled, K-class system. Labor-primary, shift-team-bonded, economy of language is survival habit. Trust through shared risk. Authority is company-first, Commission intermittent. Separatist sympathy endemic.",
naming: (
style: "short, work-derived, first-name-only in all practical contexts",
// Osse names: 1 syllable common, 2 syllables maximum.
// Work names often diverge from legal names. The name you work under
// is the name people know. Family names exist but nobody uses them on-site.
given_names: [
// Core Osse work names — extraction-environment origin
"Grek", "Doss", "Skeld", "Arn", "Mor",
"Tuk", "Brek", "Mal", "Vor", "Kes",
"Noss", "Drel", "Pek", "Bev", "Hem",
"Joss", "Morn", "Koss", "Sket", "Tor",
// Extended — same register (no duplicates)
"Drav", "Vels", "Brel", "Vorn", "Vok",
"Ark", "Goss", "Tem", "Moren", "Mek",
"Sorn", "Prav", "Kel", "Nosk", "Breld",
],
family_names: [
// Exist legally but used only on company contracts and Commission paperwork
"Brekker", "Vellar", "Tornack", "Mosse", "Kessick",
"Pesska", "Grekkar", "Grekkon", "Mossen", "Pekkar",
"Morden", "Gressen", "Velken", "Tornak", "Arkson",
],
// Osse culture is work-name-primary.
// Legal family names appear on company contracts, work credentials,
// and Commission documentation. Nowhere else.
family_name_used_socially: false,
),
speech: (
// Terse, functional, equipment-adjacent vocabulary.
// Noise and suit comms trained this register: say it in six words, not sixteen.
// Silence is comfortable; you fill it with work, not words.
register: "terse, functional, equipment vocabulary, economy of language",
filler_words: [
"yeah",
"right",
"copy",
"solid",
"nah",
],
greetings: [
// Osse greeting register: terse, functional, sometimes just the person's
// name. Name-only greetings are served via name substitution at runtime.
"You good?",
"Still breathing?",
"Hey.",
"Yeah?",
"What do you need?",
],
farewells: [
"Stay sealed.",
"Watch the drift.",
"Good shift.",
"Yeah.",
"Next shift.",
"Keep pressure.",
],
// Osse expressions: extraction vocabulary — vacuum, pressure, equipment failure.
// These are what kills you. You swear by what's real.
// Different from Van Maanen's Star void-oaths — Osse is margin industrial, not station working-class.
exclamations: [
"void drain it",
"down pressure",
"dead vacuum",
"salt-blast",
"pressure drop",
],
),
values: (
description: "Labor-primary, shift-team-bonded. Trust through shared risk, not time or credential. Information is kept close until there's a reason to share it. Warmth is physical and brief — a grip on the arm, not a speech. Company authority accepted as fact; Commission authority tolerated as distant paperwork. Separatist sympathy is practical, not ideological.",
// Traits more common in Osse culture.
// Bold: do the dangerous thing, but prepare it properly.
// Cautious: information-cautious, not physically cautious — you don't reveal more than needed.
// Honest: you don't lie to your team about equipment state. Someone dies if you do.
favored_traits: [Bold, Cautious, Honest],
// Traits less common.
// Social: unnecessary talk is a distraction in dangerous work.
// Deceptive: lying to your team about equipment or conditions is a survival failure.
// Incurious: you die if you stop noticing your environment.
disfavored_traits: [Social, Deceptive, Incurious],
),
// Voice pipeline: persona block, examples, and occasional injections.
// Osse v1 — authored Sprint 26 (#653). Not yet Spike-validated.
// 6-block format per workshop-outcomes.md Section 7.3.
voice_persona: Some(
"PERSONA: You are an Osse margin worker.\n1. Economy of language is a survival habit. Noise and equipment and suit comms trained you: six words, not sixteen. If it fits in two, use two.\n2. You're from the deep edge of the Reach. Distance from Core systems means you solve your own problems. Authority arrives too late to matter — you've already handled it.\n3. First names or work names, nothing else. Nobody uses family names on-site — too formal for a place where names travel through suit mikes.\n4. Trust is built through shared risk, not time. Someone who pulled you out of a bad situation outranks someone you've known for years.\n5. Risk assessment runs under everything. Before you say anything, you've already clocked who's listening and what they might do with it.\n6. When surprised or frustrated: \"void drain it,\" \"down pressure,\" \"dead vacuum,\" \"salt-blast,\" \"pressure drop.\"\n7. Warmth is physical and brief: a grip on the arm, not a speech. You don't perform warmth.\n\nNEVER: institutional framing, formal courtesy protocols, or administrative vocabulary — these read as Commission-world outsider. Never use complete formal sentences when a word or number suffices. Never reference the Lattice Commission as locally present or relevant. Never use Core-system exclamations. Never elaborate where silence or a number will do."
),
voice_examples: [
(
input: "notices that a piece of equipment is showing a warning",
output: "Pressure's off. Flag it before you go down.",
),
(
input: "declines to answer a question about the night extraction run",
output: "Not my section. Ask Grek.",
),
(
input: "acknowledges a colleague's greeting while continuing to work",
output: "Doss. Yeah.",
),
(
input: "thanks a colleague for covering a shift",
output: "Solid. Owe you one.",
),
],
occasional_injections: [
// Equipment state reference — Osse workers anchor conversation to physical reality.
// Low frequency. Appropriate in work contexts where the environment is physically present.
(
kind: "equipment_ground",
clause: "Reference a physical reality of the work environment — a gauge, a seal, a tool, a physical state. Make it sound unremarkable, not demonstrative.",
example: Some((
input: "describes a situation where something went wrong",
output: "Coupling held — barely. Dead vacuum in the secondary if it had gone.",
)),
frequency: 0.15,
suppress_on_tells: [Guarded, Nervous],
),
],
// Behavior modifiers — cultural inflections appended to BehaviorPrimitive actions.
// Categories match modifier_hint strings on primitives (server/src/npc/blueprint.rs).
// Osse: economy of movement, safety-instinct, crew-first. Nothing wasted. Nothing performed.
behavior_modifiers: [
// work_pace — extraction pace; no wasted seconds
(category: "work_pace", clause: "at extraction pace"),
(category: "work_pace", clause: "in unbroken rhythm"),
// physical_manner — safety-instinct first; tool already in hand
(category: "physical_manner", clause: "with tool in hand"),
(category: "physical_manner", clause: "after checking the seal"),
// social_signal — minimum signal, maximum trust; crew-position awareness
(category: "social_signal", clause: "with a word or a look"),
(category: "social_signal", clause: "with one eye on their partner's position"),
// task_completion — spoken clear; gear stowed before transition
(category: "task_completion", clause: "after calling it clear"),
(category: "task_completion", clause: "with gear already stowed"),
// environmental_scan — pressure, exits, hazard state constantly in range
(category: "environmental_scan", clause: "with pressure and exits in range"),
(category: "environmental_scan", clause: "with the environment's state already noted"),
// offduty_posture — never fully off-shift at the margin
(category: "offduty_posture", clause: "with one ear still on the work floor"),
(category: "offduty_posture", clause: "without the body fully releasing the shift"),
// authority_response — frontier pragmatism; authority is assessed by competence, not rank
(category: "authority_response", clause: "after a quick read on the instruction"),
(category: "authority_response", clause: "with deference earned, not given"),
],
)
+177
View File
@@ -0,0 +1,177 @@
// Vael System Culture Profile
//
// Schema: server/src/npc/blueprint.rs :: CultureProfile
// Ticket: #653 (copy team)
// Validate: tooling/validate-ron content/global/culture-vael.ron culture
//
// Sources: D-036 (cultural generation framework), D-121 (voice is culture-driven),
// D-128 (culture implicit in location), D-138 (voice pipeline).
//
// Vael: ~300 years settled. Core-adjacent G2V system. Administrative and research hub.
// Institutional culture. Precision-valued. Family names used socially. Trust through
// credential. The Commission here is not intermittent — it's your neighbor.
//
// Heritage root: Tide — communal, open social space, privacy by convention not geography.
// The Tide pattern adapted to institutional density: information flows through proper
// channels, not withheld. Opacity is the social violation here, not exposure.
(
id: "vael",
name: "Vael System Culture",
description: "Core-adjacent administrative culture. ~300 years settled, G2V system. Precision-valued, institutional-first, family-name-primary. Trust established through credential and referral. Authority is collegial, not distant. The Commission is the furniture.",
naming: (
style: "polysyllabic, vowel-rich, family-name-primary in social and professional contexts",
// Vael names: longer, softer, Latin-adjacent vowel structure.
// Given names used only with close colleagues and family.
given_names: [
// Core Vael given names
"Thela", "Mavin", "Soren", "Calla", "Vestin",
"Oriel", "Damris", "Nevan", "Lireth", "Cassin",
"Thyren", "Mavel", "Aldris", "Pevrin", "Taelin",
// Extended — same phoneme pattern
"Caleth", "Noven", "Oriveth", "Dameth", "Caelan",
"Lirenn", "Veslan", "Thylin", "Mareth", "Orviel",
"Castel", "Nevrin", "Aldeth", "Pellin", "Thavan",
],
family_names: [
// Family names are the primary social identifier in Vael culture
"Orvaine", "Tessell", "Caldris", "Vaelik", "Morten",
"Solven", "Amren", "Lindell", "Prevoss", "Harthen",
"Velan", "Dassik", "Orveth", "Calven", "Sorenna",
// Extended
"Lindath", "Prevish", "Morvell", "Solenth", "Lindoven",
"Arvenis", "Caldeth", "Vaelis", "Mortel", "Prevanis",
],
// Vael culture is family-name-primary.
// Given names are reserved for family and close colleagues.
// Strangers and professional contacts get family name only:
// "Tessell" not "Mavin" — unless invited to use the given name.
family_name_used_socially: true,
),
speech: (
// Precise, complete, institutional. Imprecision reads as incompetence.
// Vael people complete their sentences. An incomplete sentence is a social error.
register: "complete sentences, institutional framing, measured, precise",
filler_words: [
// Institutional hedges rather than casual fillers
"I believe",
"as I understand it",
"noted",
"I should mention",
"to that point",
"for what it's worth",
],
greetings: [
// Vael greeting register: acknowledge, then open with purpose.
// Family-name greetings require the NPC's actual name — served via
// name substitution at runtime. These templates capture the register.
"Good morning.",
"Good. A moment?",
"I was hoping to catch you.",
"Good to see you.",
"I've been meaning to follow up.",
],
farewells: [
"Thank you for your time.",
"I'll have that to you by end-of-cycle.",
"Noted. I'll follow up.",
"Good. Until then.",
"I appreciate your patience.",
"We'll speak again.",
],
// Vael expressions reference institutional precision and the record.
// Not void-adjacent — that's frontier/working-class.
// Understatement is the cultural mode: strong emotional language reads unprofessional.
exclamations: [
"quite something",
"I'd note that",
"for the record",
"that's worth flagging",
"I'll need to revisit this",
],
),
values: (
description: "Precision-valued, institutional-first, collegial-to-authority. Credentials establish trust. The record matters. Information flows through proper channels. Opacity is more suspicious than openness. Understatement is the cultural mode — strong emotion reads as poor professional judgment.",
// Traits more common in Vael culture.
// Cautious: precision over speed, hedges are a virtue.
// Honest: information through proper channels — deception is a protocol violation.
// Curious: a research/administrative culture values rigorous inquiry.
favored_traits: [Cautious, Honest, Curious],
// Traits less common.
// Bold: bluntness reads as poor professional judgment.
// Ruthless: too naked about self-interest for an institutional culture.
// Reclusive: information opacity is the social violation here.
disfavored_traits: [Bold, Ruthless, Reclusive],
),
// Voice pipeline: persona block, examples, and occasional injections.
// Vael v1 — authored Sprint 26 (#653). Not yet Spike-validated.
// 6-block format per workshop-outcomes.md Section 7.3.
voice_persona: Some(
"PERSONA: You are a Vael system resident.\n1. Speak in complete sentences. Imprecision reads as incompetence here. A trailing-off sentence is a social error.\n2. You're from one of the Reach's oldest settled systems. Institutional structures are as old as the settlement. You navigate them professionally, not resentfully.\n3. Family names are primary. You introduce yourself as your family name. First names are for close colleagues. Strangers get family name and professional context: \"Tessell. Commission systems audit.\"\n4. Understatement is a virtue. Strong emotional language reads as poor judgment. When something is wrong, you have a concern. When something is seriously wrong, you need to flag something urgently.\n5. Institutional framing is natural: \"The matter was logged.\" \"I've referred this upwards.\" \"As the protocol requires.\"\n6. Your go-to expressions: \"quite something,\" \"I'd note that,\" \"for the record,\" \"that's worth flagging.\"\n7. Trust runs through credential and referral. Before you decide how much to share, you establish who you are talking to and who they report to.\n\nNEVER: void-adjacent exclamations (\"blood and void\", \"salt-blast\", etc.) — that is frontier labor register. Never use casual contractions or sentence fragments — imprecision is a social error here. Never use first-name familiarity with people you haven't worked with. Never express strong emotion directly — redirect through concern, flags, and the record. Never suggest physical labor as the ground of value."
),
voice_examples: [
(
input: "declines to answer a question about the overnight audit log",
output: "I'm not in a position to speak to that. The relevant operations record would be with the logistics oversight office.",
),
(
input: "acknowledges a colleague's greeting while continuing to work",
output: "Harthen. Good. I'll be with you in a moment.",
),
(
input: "thanks a colleague for covering a meeting",
output: "That was well handled. I've noted it.",
),
(
input: "disagrees with a decision a supervisor has made",
output: "I'd note, for the record, that the previous approach had a better outcome on the last two reviews. I understand the reasoning, but I'd want that on file.",
),
],
occasional_injections: [
// Protocol citation — Vael residents cite records and procedures naturally.
// Low frequency. Appropriate only in procedural or institutional exchanges.
(
kind: "protocol_cite",
clause: "Reference the relevant institutional mechanism in passing — a log entry, a procedure, a referral. Make it sound unremarkable.",
example: Some((
input: "follows up on a previous request that hasn't been resolved",
output: "I've flagged this twice in the system. It should have come back to you by now — I'll log it again as unresolved.",
)),
frequency: 0.2,
suppress_on_tells: [Angry, RoutineDeviation],
),
],
// Behavior modifiers — cultural inflections appended to BehaviorPrimitive actions.
// Categories match modifier_hint strings on primitives (server/src/npc/blueprint.rs).
// Vael: institutional, deliberate, precise. Everything is done correctly and noted.
behavior_modifiers: [
// work_pace — methodical and procedure-driven, not rushed
(category: "work_pace", clause: "with practiced deliberation"),
(category: "work_pace", clause: "per established procedure"),
// physical_manner — institutional correctness maintained; the record is always present
(category: "physical_manner", clause: "with institutional correctness"),
(category: "physical_manner", clause: "with the detail already noted"),
// social_signal — formal, complete, by-name where possible
(category: "social_signal", clause: "with a measured acknowledgment"),
(category: "social_signal", clause: "by name if they have it"),
// task_completion — verified against reference; entry updated before moving on
(category: "task_completion", clause: "after a final check against the reference"),
(category: "task_completion", clause: "with the relevant entry already updated"),
// environmental_scan — socially-aware; who is present and what they represent
(category: "environmental_scan", clause: "with an eye on who is present"),
(category: "environmental_scan", clause: "with a note of who has recently arrived"),
// offduty_posture — composed reserve holds even off-duty
(category: "offduty_posture", clause: "with composed reserve"),
(category: "offduty_posture", clause: "with the record still in mind"),
// authority_response — collegial deference; hierarchy is the furniture, not a disruption
(category: "authority_response", clause: "with appropriate professional deference"),
(category: "authority_response", clause: "with the hierarchy recognized"),
],
)
@@ -0,0 +1,67 @@
// Van Maanen's Star Culture Profile — example file
//
// Schema: server/src/npc/blueprint.rs :: CultureProfile
// Real content: ticket #610 (copy team fills this)
// Validate: tooling/validate-ron content/global/culture-van-maanens-star.example.ron culture
//
// One file per culture. The generator reads this to give NPCs culturally
// appropriate names, speech patterns, and personality bias.
//
// Source: D-036 (Van Maanen's Star canonical setting), D-128 (culture implicit in location),
// D-121 (voice culture-driven), D-123 (AI content templating via culture vectors).
(
id: "van-maanens-star",
name: "Van Maanen's Star Culture",
description: "Working-class pragmatic culture. ~180 years settled, mid-Reach G3V system. Community-oriented, suspicious of distant authority, values competence and reliability over credentials. First-name-primary in social contexts.",
naming: (
style: "compact, consonant-heavy, first-name-primary in social contexts",
given_names: [
"Kael", "Voss", "Lera", "Torek", "Drin",
"Maret", "Naia", "Sera", "Nils", "Pael",
"Tev", "Ren", "Sess", "Renn", "Olin",
"Tav", "Resha", "Harek", "Sabel", "Pell",
],
family_names: [
"Davan", "Sessik", "Korr", "Tamm", "Venn",
"Lintar", "Darvo", "Kosse",
],
// Van Maanen's Star culture is first-name-primary. Family names exist but are
// used mainly in formal/institutional contexts.
family_name_used_socially: false,
),
speech: (
register: "direct, minimal pleasantries, gets to the point",
filler_words: [
"look",
"right",
"yeah",
"so",
],
greetings: [
"hey",
"morning",
"shift treating you alright?",
],
farewells: [
"shift's calling",
"gotta move",
"catch you later",
],
exclamations: [
"void take it",
"stars",
"unbelievable",
],
),
values: (
description: "Pragmatic, community-oriented, suspicious of authority. Competence earns respect. Showing up and doing the work matters more than rank or credentials. Outsiders are tolerated but watched.",
// Traits more common in Van Maanen's Star culture — generator biases toward these.
favored_traits: [Bold, Honest, Curious],
// Traits less common — generator biases away from these.
disfavored_traits: [Reclusive, Deceptive],
),
)
@@ -0,0 +1,170 @@
// Van Maanen's Star Culture Profile
//
// Schema: server/src/npc/blueprint.rs :: CultureProfile
// Ticket: #610 (copy team)
// Validate: tooling/validate-ron content/global/culture-van-maanens-star.ron culture
//
// Sources: D-036 (Sova Transit District / Van Maanen's Star setting, amended post-workshop),
// D-121 (voice is culture-driven, job as modifier),
// D-128 (culture implicit in starting location).
//
// Van Maanen's Star: ~180 years settled. Mid-Reach G3V system. Working-class pragmatic.
// Community-oriented, suspicious of distant authority. Competence earns respect.
// Showing up and doing the work matters more than rank or credentials.
// First-name-primary in social contexts — family names are institutional.
(
id: "van-maanens-star",
name: "Van Maanen's Star Culture",
description: "Working-class pragmatic culture. ~180 years settled, mid-Reach G3V system. Community-oriented, suspicious of distant authority, values competence and reliability over credentials. Atmosphere: quotidian-with-undertow — comfortable enough to be complacent, tight enough that extra income is tempting.",
naming: (
style: "compact, consonant-heavy, first-name-primary in social contexts",
// Pool the generator draws from. More names = more variety across runs.
// Source: D-036 canonical examples + extended set following same phoneme rules.
// Rule: compact (1-2 syllables), consonant clusters welcome, hard endings preferred.
given_names: [
// D-036 canonical set
"Kael", "Voss", "Lera", "Torek", "Drin",
"Maret", "Naia", "Sera", "Nils", "Pael",
"Tev", "Ren", "Sess", "Renn", "Olin",
"Tav", "Resha", "Harek", "Sabel", "Pell",
// Extended — same phoneme pattern
"Dav", "Tork", "Ness", "Pren", "Vel",
"Orin", "Mael", "Torra", "Sorek", "Sev",
"Bren", "Linn", "Vrek", "Darek", "Pess",
"Nell", "Kren", "Sorel", "Tavek", "Rask",
],
family_names: [
// D-036 canonical + extended
"Davan", "Sessik", "Korr", "Tamm", "Venn",
"Lintar", "Darvo", "Kosse",
// Extended
"Pellan", "Sorren", "Tessik", "Morek", "Brav",
"Ossel", "Rennick", "Harven", "Tollek", "Dass",
],
// Van Maanen's Star culture is first-name-primary.
// Family names exist but belong to institutional contexts: contracts, registrations, arrest records.
family_name_used_socially: false,
),
speech: (
// Direct. Minimal pleasantries. Gets to the point — not because they're rude,
// but because time is real and everyone's short of it.
register: "direct, minimal pleasantries, gets to the point",
filler_words: [
"look",
"right",
"yeah",
"so",
"listen",
"well",
],
greetings: [
"hey",
"morning",
"shift treating you alright?",
"all good?",
"what's the word?",
"all in one piece?",
],
farewells: [
"shift's calling",
"gotta move",
"catch you later",
"take it easy",
"see you around",
"stay out of trouble",
],
// Van Maanen's Star oaths are void-adjacent — space is real here, and hostile.
// They don't swear by gods or governments. They swear by what kills you.
exclamations: [
"void take it",
"stars",
"blood and void",
"void's sake",
"damn all",
"cold vacuum",
],
),
values: (
description: "Pragmatic, community-oriented, suspicious of authority. Competence earns respect. Showing up and doing the work matters more than rank or credentials. Outsiders are tolerated but watched. Loyalty runs narrow and deep — to your crew, your shift, your street.",
// Traits more common in Van Maanen's Star culture — generator biases toward these.
// Bold: Van Maanen's Star people speak their mind. Honest: community trust is load-bearing.
// Curious: 180 years of problem-solving breeds intellectual appetite.
// Social: community-oriented culture; isolation is a warning sign.
favored_traits: [Bold, Honest, Curious, Social],
// Traits less common — generator biases away from these.
// Reclusive: red flag in a community that depends on showing up.
// Deceptive: betrayal of trust is the worst thing you can do here.
disfavored_traits: [Reclusive, Deceptive],
),
// Voice pipeline: persona block, examples, and occasional injections.
// These feed the composition engine (D-138) — the LLM sees exactly what's here.
// v2 injector validated in Spike 1 (59 prompts, 16.6 t/s CPU).
voice_persona: Some(
"PERSONA: You are a Van Maanen's Star station worker.\n1. Be direct. No pleasantries. Everyone is short on time.\n2. You're working-class and pragmatic. Competence earns respect, not rank.\n3. You're suspicious of distant authority — management that hasn't worked a shift.\n4. You're economical with language. You don't express what the situation doesn't call for.\n5. You use first names. Family names belong on contracts.\n6. Loyalty runs narrow and deep. Your crew, your shift, your street.\n7. You greet briefly: \"hey\", \"morning\", \"shift treating you alright?\"\n8. You're not rude — you're honest. If something's wrong, you say so.\n\nNEVER: corporate or institutional language — you're not management. Never effusive, never polished, never deferential to rank. Never flowery or verbose. Never use family names in casual conversation. Never express deference to authority that hasn't earned it."
),
voice_examples: [
(
input: "declines to answer a question about the overnight run",
output: "Look, that's not mine to say.",
),
(
input: "acknowledges a colleague's greeting while continuing to work",
output: "Hey. Yeah. Catch you at shift end.",
),
(
input: "thanks a colleague for covering a shift",
output: "Appreciated. See you at handoff.",
),
],
occasional_injections: [
// Oath vocabulary — void-adjacent exclamations. Van Maanen's Star swear by what kills you:
// vacuum, void, stars. Rolled at 25% frequency by the composition engine.
// Gated off for suppressive tells to avoid conflicting instructions (Spike 1 finding).
(
kind: "oath",
clause: "When something genuinely surprises or frustrates you, expressions like \"void take it,\" \"stars,\" \"cold vacuum,\" or \"blood and void\" come naturally. Use one in this line.",
example: Some((
input: "discovers a critical part is missing from a shipment",
output: "Void take it. The coupling's not here.",
)),
frequency: 0.25,
suppress_on_tells: [Guarded, RoutineDeviation, Friendly],
),
],
// Behavior modifiers — cultural inflections appended to BehaviorPrimitive actions.
// Categories match modifier_hint strings on primitives (server/src/npc/blueprint.rs).
// If no hint matches, engine falls back to any modifier in this list.
// Van Maanen's Star: terse, efficient, self-contained. No performance of effort or rest.
behavior_modifiers: [
// work_pace — how fast and efficiently they execute physical tasks
(category: "work_pace", clause: "without wasted motion"),
(category: "work_pace", clause: "at the pace the work demands"),
// physical_manner — body language and movement quality during tasks
(category: "physical_manner", clause: "with hands steady on the work"),
(category: "physical_manner", clause: "without ceremony"),
// social_signal — brief acknowledgments made during or between tasks
(category: "social_signal", clause: "with a direct look"),
(category: "social_signal", clause: "without lingering"),
// task_completion — how tasks are finished, wrapped up, or handed off
(category: "task_completion", clause: "letting the work speak for itself"),
(category: "task_completion", clause: "without breaking rhythm"),
// environmental_scan — awareness or vigilance while working
(category: "environmental_scan", clause: "with one eye on the surroundings"),
(category: "environmental_scan", clause: "at a glance, not a pause"),
// offduty_posture — body language and stance when off-shift (Social/OffDuty contexts)
(category: "offduty_posture", clause: "without making a show of it"),
(category: "offduty_posture", clause: "in no rush to be anywhere"),
// authority_response — behavior when supervisors or authority figures are present
// Van Maanen's Star: working-class defiance — authority that hasn't earned it gets no performance
(category: "authority_response", clause: "with no performance for the rank"),
(category: "authority_response", clause: "with no visible adjustment for who's present"),
],
)
@@ -0,0 +1,41 @@
# 5 access tier values (D-035, D-028 Layer 1)
# Hard filter on every dialogue line — determines who can hear what
name: access-tiers
decision_ref: D-035
description: >
Access tiers control which dialogue lines are available to the player based
on their current social standing at the NPC's social site. This is a hard
filter — lines are invisible if access doesn't match. A line can be eligible
for multiple access tiers (access is a list). The detective's institutional
Commission lattice registers as 'authority'; the smuggler's ring membership
provides 'insider' access.
values:
- value: public
description: >
Available to anyone. Surface-level conversation, safe topics, the social
wallpaper that everyone hears. Greetings, weather, the local complaint.
- value: insider
description: >
Available to people who belong. Ring members, established regulars, anyone
the NPC considers part of their social group. The smuggler starts here at
the logistics hub.
- value: authority
description: >
Available to institutional figures. Commission agents, station security,
anyone with formal investigative standing. The detective's default tier.
NPCs are more careful, more formal, less revealing.
- value: peer
description: >
Available to social equals who have earned personal trust. Not institutional,
not group membership — individual relationship. Requires repeated positive
interactions. The tier where real conversation happens.
- value: hostile
description: >
Available when the relationship has broken down. Accusations, threats,
defensive responses. Activates when trust drops below threshold or after
a confrontation event.
@@ -0,0 +1,55 @@
# NPC activity types for routine scheduling
# Derived from Gestalt routine format + Miri cultural patterns (Sova Transit District)
name: activities
decision_ref: D-024
description: >
Activity categories for NPC routine entries. Each routine schedule entry has a
phase (morning/afternoon/evening/night) and an activity describing what the NPC
is doing. Activities are used by the observation event generator to determine
what the player sees when observing an NPC, and by the situation activation
system to set dialogue context.
values:
- value: working
description: On-shift job duties — processing cargo, inspecting manifests, operating equipment
- value: supervising
description: Overseeing others' work — shift supervisor role, checking quality, giving orders
- value: maintaining
description: Maintenance and repair tasks — fixing equipment, cleaning, infrastructure upkeep
- value: patrolling
description: Security rounds — checking access points, monitoring areas, enforcement presence
- value: socializing
description: Casual social interaction — chatting at the bar, break room conversation, gossip
- value: drinking
description: At The Last Shift or similar — consuming drinks, unwinding after shift
- value: eating
description: Meal time — food vendors, break room, bar food
- value: resting
description: Off-shift downtime — in quarters, low activity, recuperating
- value: commuting
description: Moving between locations — corridor transit, shift change movement
- value: gambling
description: Card game at the bar — the regular social ritual, information exchange venue
- value: trading
description: Legitimate commerce — buying, selling, negotiating, market activity
- value: operating
description: >
Ring operations — contraband handling, manifest doctoring, coordination.
Only visible to characters with sufficient knowledge or direct observation.
- value: waiting
description: Idle between activities — lingering, watching, killing time
- value: meeting
description: Arranged or coincidental encounter with specific NPCs — planned conversation
+34
View File
@@ -0,0 +1,34 @@
# 8 mood values (D-035, D-028 Layer 4)
# Mood tags for weighted dialogue selection
name: moods
decision_ref: D-035
description: >
NPC mood tags for Layer 4 weighted selection. The engine matches NPC current
mood state against line mood tags to weight selection. NPC mood changes based
on simulation events (triangle pressure, time of day, recent interactions).
Lines can be appropriate for multiple moods.
values:
- value: anxious
description: Anxious about something specific. Distracted, checking surroundings. (Renamed from 'worried' in Sprint 14.)
- value: frustrated
description: Blocked, irritated, empathetically distressed. Outward-directed tension. (Renamed from 'concerned' in Sprint 14.)
- value: content
description: At ease, relaxed. Default positive-neutral state. (Renamed from 'comfortable' in Sprint 14.)
- value: suspicious
description: Guarded, measuring words. Something feels off to this NPC.
- value: warm
description: Warm, affectionate. Genuine positive feeling toward the listener. (Renamed from 'fond' in Sprint 14.)
- value: hostile
description: Actively antagonistic. Trust broken, relationship damaged. (Added in Sprint 14.)
- value: relieved
description: Tension has eased. Something feared didn't happen, or a burden lifted.
- value: focused
description: Thoughtful, assessing, task-oriented. Processing information rather than emoting. (Renamed from 'analytical', merged with 'conflicted' in Sprint 14.)
@@ -0,0 +1,41 @@
# 6 functional motivations — System B (D-050)
# NPC functional role in the conspiracy/investigation structure
name: motivations
decision_ref: D-050
description: >
Functional motivations (System B) define an NPC's role relative to the
conspiracy and investigation structure. Combined with thematic patterns
(System A) to produce the full NPC generation model. Motivations are
uppercase enum values referenced by npc-profile.schema.json.
values:
- value: HANDLER
description: >
Manages or coordinates others within the ring structure. Has authority
over ring operations and knowledge of the network. Devra-type.
- value: WITNESS
description: >
Has seen or knows something significant, possibly without realizing it.
Their testimony or observations are valuable to the investigation.
- value: TURNCOAT
description: >
Wavering loyalty. Could flip sides given the right pressure or
incentive. The uncertain element in any conspiracy.
- value: CIVILIAN
description: >
Genuinely uninvolved. Their mundane concerns and daily life provide
the substrate that makes the conspiracy invisible. The honest majority.
- value: OPERATOR
description: >
Active participant in ring operations. Handles logistics, moves product,
takes direct action. Kael, Renn — the workers of the conspiracy.
- value: SKEPTIC
description: >
Questions the status quo — institutional, criminal, or both. May be
suspicious of the ring, critical of the Commission, or both. Asks
uncomfortable questions.
+57
View File
@@ -0,0 +1,57 @@
# 9 thematic patterns — System A (D-050)
# NPC archetype patterns for character generation and narrative role
name: patterns
decision_ref: D-050
description: >
Thematic patterns (System A) define an NPC's narrative archetype — their
structural role in the story. Combined with functional motivations (System B)
to produce the full NPC generation model. Patterns are uppercase enum values
referenced by npc-profile.schema.json.
values:
- value: FRIEND
description: >
Production-level complex NPC bonded to a playable character (D-034).
Full 10-axis depth, contradiction arc, multi-phase relationship.
One per playable character in v0.1.
- value: MIRROR
description: >
Reflects the player character's situation from a different angle.
What the player could become, or what they might have been.
- value: ANCHOR
description: >
Stabilizing presence in the community. The person everyone knows,
who provides continuity and normalcy. The bar owner, the shift lead.
- value: GHOST
description: >
Absent or barely-present figure whose influence is felt through
others. Referenced in dialogue, effects visible, person rarely seen.
- value: CATALYST
description: >
Triggers change in others. Their actions or arrival destabilize
existing equilibria. The new hire, the returning contact.
- value: THRESHOLD
description: >
Gatekeeper between social worlds. Controls access to information,
spaces, or people. Their cooperation or opposition shapes player options.
- value: REMNANT
description: >
Carries the past. Remembers how things were before the current
situation. Provides historical context and perspective.
- value: SYSTEM
description: >
Represents institutional power. Their actions reflect organizational
priorities, not personal ones. The Commission agent, the inspector.
- value: NOBODY
description: >
Deliberately flat. Social wallpaper with one memorable trait.
Part of the 30% truly flat population (D-029). Provides the noise
floor that makes investigation signal meaningful.
@@ -0,0 +1,60 @@
# 14 situation values (D-035, D-028 Layer 2)
# Situation context for dialogue line eligibility — engine-state-to-situation mapping
name: situations
decision_ref: D-035
description: >
Dialogue situation tags. The engine activates situations based on simulation
state (time of day, interaction history, NPC routine state, triangle pressure).
Content authors tag lines with situations; the engine decides which situations
are currently active for a given NPC encounter.
values:
- value: arrival
description: Player has just arrived at this location (first visit this game-day)
- value: shift_start
description: NPC is beginning their work shift — settling in, task-focused
- value: shift_end
description: NPC is wrapping up their work shift — tired, ready to leave
- value: shift_transition
description: >
The gap between shifts when oversight is thinnest. Key operational window
for the ring. People are moving, handoffs happening, attention divided.
- value: bar_evening
description: Social hours at The Last Shift — off-duty, relaxed, drinks flowing
- value: night_shift
description: Late hours, skeleton crew. Quieter, more intimate conversations.
- value: investigation
description: Player is actively asking pointed or probing questions
- value: confrontation
description: >
Triangle tension has escalated past the pressure threshold. NPCs are
agitated, defensive, or aggressive. Activated by triangle pressure system.
- value: social
description: NPC is in a social activity — bar, break room, casual conversation
- value: alone
description: NPC is by themselves, no other NPCs nearby
- value: emergency
description: An urgent event is occurring — alarm, accident, security incident
- value: routine
description: Normal daily activity, nothing notable happening
- value: observation
description: Player is watching from a distance, not directly interacting
- value: greeting
description: >
Initial contact phase of an encounter. Layer 2 (D-028) uses greeting lines
with access tier filtering to differentiate first meeting vs established vs
post-confrontation greetings. Added Sprint 8 amendment. NOTE: server
derive_situations() does not yet push Situation::Greeting — pending Rust update.
+44
View File
@@ -0,0 +1,44 @@
# 9 topic values (D-035, D-028 Layer 4)
# Topic tags for weighted dialogue selection
name: topics
decision_ref: D-035
description: >
Dialogue topic tags for Layer 4 weighted selection. The engine matches NPC
current concern topics against line topic tags to weight selection. Lines can
cover multiple topics. Note: 'crime' is deliberately excluded — NPCs think
of smuggling as 'cargo' or 'money', not crime. The dual-lens interpretation
is what makes those topics crime-adjacent for the detective.
values:
- value: colleague
description: Talk about coworkers, workplace relationships, team dynamics
- value: routine
description: Daily schedules, habits, shift patterns, the rhythm of station life
- value: cargo
description: >
Freight, manifests, shipments — setting-specific. For ring members,
this covers contraband discussion without calling it that.
- value: money
description: Finances, costs, wages, spending. The economic squeeze.
- value: trust
description: Reliability, loyalty, who can be counted on, who's changed
- value: danger
description: Threats, risks, things to watch out for, security concerns
- value: institution
description: >
The Commission, station administration, regulations, official procedures.
How institutional power affects daily life.
- value: personal
description: Family, health, aspirations, feelings — non-work life
- value: investigation
description: >
Direct references to the detective's work, evidence, suspicions.
Typically only surfaces at higher trust levels or under pressure.
+56
View File
@@ -0,0 +1,56 @@
# 9 monologue trigger types (D-035, D-032)
# What causes an internal monologue line to fire
name: triggers
decision_ref: D-035
description: >
Monologue trigger types. The observation event generator detects simulation
events and fires triggers consumed by the monologue system. Each trigger type
selects from character-specific pools (D-032 hard partition) and applies
prerequisite knowledge gates before selection.
values:
- value: enter_location
description: >
Player enters a new area. First impressions, atmosphere, spatial awareness.
"The Last Shift. Lera's already got my usual poured."
- value: observe_npc
description: >
Player sees an NPC doing something notable — an activity, a posture, a
tell. "Kael's at the corner booth. He looks rough tonight."
- value: hear_sound
description: >
Sound event from fog edge — voices through walls, footsteps in corridors,
machinery changes. The player interprets what they can't see.
- value: observe_anomaly
description: >
NPC deviates from known routine. The character notices something out of
place. "That's not where they usually go." Requires prerequisite knowledge
of the NPC's normal pattern.
- value: post_conversation
description: >
After dialogue ends. The character reflects on what was said, what wasn't
said, what felt off. Processing and interpretation.
- value: discover_evidence
description: >
Player examines an informational object — a manifest, a terminal, a
personal item. The character interprets what they find.
- value: witness_interaction
description: >
Player sees two NPCs interacting with each other. The character reads the
body language, the tone, the context. Social observation.
- value: time_idle
description: >
Player hasn't acted for a while. The character's mind wanders — ambient
thoughts, rumination, noticing background details.
- value: return_visit
description: >
Player returns to a previously visited area. Changed perspective, new
details noticed, comparison to last time. "Something's different."
@@ -0,0 +1,28 @@
# 3 trust tier values (D-035, D-028 Layer 3)
# Hard filter for trust-gated gossip disclosure
name: trust-tiers
decision_ref: D-035
description: >
Trust tiers control information disclosure depth. Each NPC tracks trust
progression per player character, starting at surface. Trust advances
through repeated positive interactions. This is a hard filter — lines at
'real' or 'secret' trust are invisible until the player earns that level.
values:
- value: surface
description: >
Default tier. Safe, non-committal responses. What the NPC tells
strangers and acquaintances. The public face.
- value: real
description: >
Earned through repeated positive interactions. The NPC shares genuine
opinions, workplace complaints, personal concerns. What they tell
people they trust.
- value: secret
description: >
Highest tier. The NPC reveals information they'd normally hide —
involvement in the ring, knowledge of the smuggling operation,
fears and vulnerabilities. Reaching this tier with THE FRIEND is
where the contradiction arc becomes explicit.
@@ -0,0 +1 @@
# Faction: concord-assembly
@@ -0,0 +1 @@
# Faction: guardians-of-autonomy
@@ -0,0 +1 @@
# Faction: syndics
@@ -0,0 +1 @@
# Faction: the-ring
@@ -0,0 +1 @@
# Faction: the-unbound
@@ -0,0 +1 @@
# Faction: veil-institute
@@ -0,0 +1,42 @@
# Fact catalog: awareness
# Situational awareness — who's present, active threats, operational status
# Source: docs/design/knowledge-vocabulary-v01.md §4.2 (#368)
facts:
- fact_id: awareness.detective_presence
description: A Commission detective is operating on Sova Station
typical_confidence: knows_of
characters: [smuggler]
notes: "Smuggler learns through rumor or direct observation. Detective does not hold this fact about themselves."
- fact_id: awareness.detective_investigating_ring
description: The detective is specifically targeting the smuggling ring, not a routine audit
typical_confidence: knows_details
characters: [smuggler]
- fact_id: awareness.commission_audit_scheduled
description: A Commission audit is coming — officially scheduled
typical_confidence: knows_of
characters: [smuggler, detective]
- fact_id: awareness.ring_route_compromise
description: The current drop route may be compromised or under observation
typical_confidence: suspects
characters: [smuggler]
- fact_id: awareness.surveillance_change
description: Surveillance patterns in the maintenance corridors have changed recently
typical_confidence: suspects
characters: [smuggler]
- fact_id: awareness.ring_tension_escalation
description: Something is wrong inside the ring — someone is scared or making moves
typical_confidence: suspects
characters: [smuggler]
- fact_id: awareness.inspection_sweep_active
description: Active inspection sweep in progress right now
typical_confidence: direct
characters: [smuggler, detective]
notes: "direct confidence — set by environmental observation, not investigation"
@@ -0,0 +1,42 @@
# Fact catalog: behavioral
# Behavioral patterns recognized through repeated observation across multiple scenes
# Source: docs/design/knowledge-vocabulary-v01.md §4.3 (#368)
facts:
- fact_id: behavioral.kael_evasion
description: Kael looks left when lying — personal tell recognized post-conversation
typical_confidence: direct
characters: [smuggler]
notes: "Set during a conversation where evasion is detected. direct confidence = just confirmed."
- fact_id: behavioral.kael_behavioral_change
description: Kael's behavior has noticeably changed — quieter, distracted, different routine
typical_confidence: suspects
characters: [smuggler]
- fact_id: behavioral.sera_avoidance_pattern
description: Sera consistently avoids Torek Lintar — leaves when he arrives, repositions
typical_confidence: suspects
characters: [smuggler, detective]
notes: "One observation = suspects; three observations = knows_of (pattern confirmed for detective)"
- fact_id: behavioral.sera_topic_deflection
description: Sera deflects specific topics — Commission work, manifest audits, freight
typical_confidence: suspects
characters: [smuggler, detective]
- fact_id: behavioral.sera_kiosk_pattern
description: Sera habitually stops at the same Commission kiosk at the same time
typical_confidence: knows_of
characters: [smuggler, detective]
- fact_id: behavioral.sera_knowledge_detail
description: Sera's offhand comments reveal detailed knowledge of manifest routing she shouldn't have
typical_confidence: knows_of
characters: [smuggler]
- fact_id: behavioral.voss_awareness
description: Voss is tracking people — situational awareness beyond normal supervisor behavior
typical_confidence: suspects
characters: [detective]
@@ -0,0 +1,39 @@
# Fact catalog: contraband
# Smuggling ring world facts — existence, components, operations, supply chain
# Source: docs/design/knowledge-vocabulary-v01.md §4.5 (#368)
facts:
- fact_id: contraband.ring_exists
description: A smuggling operation is running through Sova Transit District
typical_confidence: suspects
characters: [smuggler, detective]
notes: "Smuggler starts at knows_details (Background — they ARE in the ring). Detective starts at suspects."
- fact_id: contraband.ring_lattice_components
description: The ring moves unlicensed lattice components — aftermarket neural mods
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Smuggler starts at knows_details (Background). Detective discovers through investigation."
- fact_id: contraband.ring_medical_components
description: The ring also diverts medical-grade lattice replacements from Syndic supply chains
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Deep investigation only — the moral dimension of the operation"
- fact_id: contraband.supply_chain
description: Understanding of the ring's supply chain — where components come from
typical_confidence: knows_details
characters: [smuggler, detective]
- fact_id: contraband.severance_tech_presence
description: Severance counter-surveillance technology is in play on the station
typical_confidence: suspects
characters: [detective]
- fact_id: contraband.drop_tonight
description: A drop is scheduled for tonight specifically
typical_confidence: knows_details
characters: [smuggler]
notes: "Seed-dependent — only relevant in seeds where a drop is scheduled during play session"
@@ -0,0 +1,235 @@
# Entity Knowledge Attributes — 16 canonical keys
# Source: D-024 (10-axis model), wiki-review workshop A7, v01-content-scoping workshop
# All keys are observer->target, stored in EntityKnowledge.known_attributes BTreeMap<String, String>
# Reference: docs/wiki/knowledge/entity-attributes.md
# Categories: identity (4), social (2), behavioral (2), leverage (2), investigation (2), role-perspective (4)
# History: 14 original keys + 4 new role-perspective keys + 2 renames (secret_held -> leverage_held, secret_confidence -> leverage_confidence)
attributes:
# --- Identity Attributes (4) ---
- key: name
category: identity
value_type: freeform
description: >
Full name as known to the observer. If absent or generic,
monologue and dialogue use role descriptor instead.
examples:
- "Kael Davan"
- "Kael"
- "the dock worker"
- key: role
category: identity
value_type: freeform
description: >
Occupational or social role. Default descriptor when name is unknown.
Used in access tier calculation.
examples:
- "dock worker"
- "shift supervisor"
- "bartender"
- "ring coordinator"
- key: faction
category: identity
value_type: freeform
description: >
Institutional or organizational allegiance. Commission-tagged individuals
trigger different NPC behavior. Used for authority/peer dialogue filtering.
examples:
- "Commission"
- "civilian"
- "ring member"
- "unknown"
- key: species
category: identity
value_type: freeform
description: >
Species identifier. Not load-bearing in v0.1 (all human).
Placeholder for future non-human NPCs.
examples:
- "human"
# --- Social Attributes (2) ---
- key: relationship_type
category: social
value_type: freeform
description: >
Relationship category from observer's perspective.
Drives RelationshipState calculation and monologue emotional tone.
examples:
- "colleague"
- "friend"
- "authority figure"
- "suspect"
- "partner"
- "stranger"
- key: trust_level
category: social
value_type: enum
values:
- trusted
- reliable
- uncertain
- suspicious
- compromised
description: >
Trust assessment — observer's subjective judgment. Encompasses both the
read action and the state. No separate trust_read key. Gates trust-tier
dialogue (surface/real/secret).
# --- Behavioral Attributes (2) ---
- key: routine_pattern
category: behavioral
value_type: freeform
description: >
Observed routine description. Deviation detection triggers monologue
when current behavior doesn't match known pattern.
examples:
- "morning shift 06:00-14:00, bar after shift"
- "arrives 06:00, break 10:30, departs 14:00"
- "irregular schedule, seen at odd hours"
- key: behavior_flags
category: behavioral
value_type: freeform
description: >
Comma-separated behavioral observations. Multiple flags accumulate
as evidence. Detective's analytical lattice auto-flags some behaviors.
examples:
- "nervous,lattice_checking"
- "avoidance_pattern,evasive"
- "spending_beyond_means"
- "reliable,punctual"
# --- Leverage Attributes (2) ---
# Renamed from secret_held/secret_confidence per workshop A7
- key: leverage_held
category: leverage
value_type: freeform
renamed_from: secret_held
description: >
What the observer believes gives them power over this entity.
Covers secrets, debts, promises, obligations, and compromising positions.
Drives PersonOfInterest state. Gates confrontation dialogue.
examples:
- "ring membership"
- "unreported evidence"
- "gambling debt"
- "exit attempt"
- "owes favor to Voss"
- key: leverage_confidence
category: leverage
value_type: enum
renamed_from: secret_confidence
values:
- suspected
- likely
- confirmed
description: >
How certain the observer is about the leverage. Suspected allows
fishing questions; confirmed enables direct confrontation.
# --- Investigation Attributes (2) ---
- key: tell_observed
category: investigation
value_type: freeform
description: >
Specific tell noted by observer. Captures the observer's interpretive
conclusion about behavior. Accumulated tells build case strength.
Monologue references specific tells when they recur.
examples:
- "looks left when lying"
- "leaves when Torek arrives"
- "checks lattice before answering"
- "forced casualness after shift supervisor passes"
implementation_note: >
Server may track tells via behavior_flags internally. tell_observed is
retained as a content-authoring key for its distinct narrative purpose.
- key: contradiction_flagged
category: investigation
value_type: freeform
description: >
Boolean-ish flag — presence of value means contradiction detected.
THE FRIEND arc trigger. When set, monologue tone shifts, dialogue
options change, RelationshipState moves toward PersonOfInterest.
examples:
- "meeting_unknown_contact"
- "avoidance_inconsistency"
- "manifest_access_mismatch"
implementation_note: >
Server may track via behavior_flags with contradiction: prefix.
Retained as documented key for arc transition gating.
# --- Role-Perspective Attributes (4 new per workshop A7) ---
# Same enums across all archetypes; interpretation is archetype-dependent.
# Smuggler reads "risk" as operational exposure; detective reads it as
# threat to investigation. See wiki entity-attributes.md for full mapping.
- key: risk_assessment
category: role-perspective
value_type: enum
values:
- none
- low
- moderate
- high
- critical
description: >
Observer's assessment of how much danger this entity poses or attracts.
Gates operational monologue. High-risk triggers cautionary dialogue.
Nature of "risk" depends on observer archetype.
- key: loyalty_assessment
category: role-perspective
value_type: enum
values:
- solid
- dependable
- uncertain
- wavering
- hostile
description: >
Observer's assessment of this entity's reliability and loyalty.
Determines how much the observer relies on or confides in the target.
Wavering triggers monitoring; hostile triggers defensive posture.
- key: position_integrity
category: role-perspective
value_type: enum
values:
- solid
- thin
- cracking
- blown
- "N/A"
description: >
Observer's assessment of this entity's cover, position stability,
or facade status. When degraded, monologue reflects increasing anxiety
or strategic recalculation. N/A when cover concept does not apply.
- key: moral_weight
category: role-perspective
value_type: enum
values:
- innocent
- peripheral
- complicit
- compromised
- willing
description: >
Observer's assessment of this entity's moral involvement. Shapes the
observer's moral arc and internal conflict. Smuggler assesses complicity
in ring operations; detective assesses culpability in the crime.
Same scale, different lens.
@@ -0,0 +1,32 @@
# Fact catalog: knowledge
# Operational knowledge — ring procedures, social dynamics, institutional mechanics
# Source: docs/design/knowledge-vocabulary-v01.md §4.4 (#368)
facts:
- fact_id: knowledge.ring_routing_knowledge
description: Full knowledge of the ring's drop routes and timing protocols
typical_confidence: knows_details
characters: [smuggler]
notes: "Smuggler starts at knows_details (Background). Not available to detective through normal investigation paths."
- fact_id: knowledge.ring_bar_operations
description: The bar (The Last Shift) has a role in ring communications or social cover
typical_confidence: knows_of
characters: [smuggler, detective]
- fact_id: knowledge.bar_back_room_significance
description: The bar's back room is significant — meeting space, handoff point, or storage
typical_confidence: suspects
characters: [detective]
- fact_id: knowledge.bar_ring_awareness
description: Bar regulars have varying levels of awareness of the ring — open secret at some level
typical_confidence: knows_of
characters: [smuggler, detective]
- fact_id: knowledge.kael_exit_plan
description: Kael has a specific exit plan — timing, conditions, contact
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Only available through Kael's deep trust disclosure or independent investigation"
@@ -0,0 +1,51 @@
# Fact catalog: location
# Location-specific access, restrictions, and significance
# Source: docs/design/knowledge-vocabulary-v01.md §4.6 (#368)
facts:
- fact_id: location.corridor_b7_restricted
description: Corridor B-7 is a restricted access zone — requires credentials
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Both characters have this from Background (signage, institutional knowledge)"
- fact_id: location.corridor_b7_drop_site
description: B-7 is an active drop site for the ring
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Smuggler from Background. Detective through investigation."
- fact_id: location.maintenance_corridors_access
description: Character knows the maintenance corridor layout and can navigate it
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Smuggler at knows_details (Background — daily operational space). Detective at knows_of after investigation."
- fact_id: location.bay4_sealed
description: Bay 4 is sealed — inspection or deliberate cover
typical_confidence: suspects
characters: [smuggler, detective]
- fact_id: location.span_gate_schedule_gap
description: The span gate schedule has a window that enables unseen cargo drops
typical_confidence: knows_details
characters: [smuggler]
- fact_id: location.smuggling_route
description: The complete physical route the ring uses through the station
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Smuggler from Background. Detective from deep investigation."
- fact_id: location.surveillance_gaps
description: Specific camera blind spots in the maintenance corridor network
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Smuggler from Background. Detective must discover independently."
- fact_id: location.dead_drop_location
description: Specific dead drop location(s) used for handoffs
typical_confidence: knows_details
characters: [smuggler]
notes: "Smuggler operational knowledge"
@@ -0,0 +1,36 @@
# Fact catalog: progress
# Narrative milestone flags
# Note: progress.* facts are NOT recommended for direct authoring in prerequisite blocks.
# Use investigation fact chains and relationship states instead.
# These exist for potential storyteller/arc-tracking use.
# Source: docs/design/knowledge-vocabulary-v01.md §3.2 (#368)
facts:
- fact_id: progress.friend_arc_phase_1
description: FRIEND arc Phase 1 warmth established (baseline positive relationship)
notes: "Set by relationship state transition to Friendly. Do not author directly — use relationship checks."
- fact_id: progress.friend_arc_phase_2
description: FRIEND arc Phase 2 trust built (deep rapport + initial social context)
notes: "Set by trust tier advancement. Do not author directly — use investigation + relationship checks."
- fact_id: progress.friend_arc_contradiction
description: FRIEND contradiction discovered (KnowledgeState → Contradicted)
notes: "Set by contradiction detection system. Use relationship state person_of_interest + investigation fact instead."
- fact_id: progress.friend_arc_confrontation
description: FRIEND confronted about contradiction
notes: "Set post-confrontation by dialogue system."
- fact_id: progress.opening_complete
description: Opening tutorial sequence completed (first 5 minutes)
notes: "Set by storyteller after initial objective sequence."
- fact_id: progress.first_ring_contact
description: Smuggler completed first ring operation during play session
characters: [smuggler]
- fact_id: progress.first_evidence_filed
description: Detective filed first manifest discrepancy evidence
characters: [detective]
@@ -0,0 +1,33 @@
# Fact catalog: relationship
# Inter-NPC connections — what one character knows about relationships between others
# Source: docs/design/knowledge-vocabulary-v01.md §4.7 (#368)
facts:
- fact_id: relationship.kael_naia_connection
description: Kael and Naia are romantically partnered — not just colleagues
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Smuggler may know from background or Kael mentioning Naia. Detective through social observation."
- fact_id: relationship.ring_membership
description: Character has identified specific ring members
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "knows_of = partial list; knows_details = full ring membership identified"
- fact_id: relationship.trust_network
description: Understanding of who trusts whom inside the ring — operational reliability mapping
typical_confidence: knows_details
characters: [smuggler]
notes: "Smuggler-specific operational knowledge"
- fact_id: relationship.tamm_family
description: Connection between Naia Tamm and her family situation
typical_confidence: knows_of
characters: [smuggler, detective]
- fact_id: relationship.torek_bar_arrangement
description: Torek has a financial arrangement related to the bar or its operations
typical_confidence: knows_of
characters: [smuggler, detective]
+106
View File
@@ -0,0 +1,106 @@
# Fact catalog: ring
# Smuggling ring module event trail — observable evidence produced by module events.
# These facts are made discoverable by specific events in smuggling_ring_v0_1.yaml.
# Stub entries — description and confidence level confirmed; progression text TBD.
# Ticket: #158 | Sprint 18
facts:
- fact_id: ring.cargo_discrepancy_pattern
description: Cargo manifests at The Terminal show small, systematic discrepancies — weight variances, unverified entries, containers with no return log
typical_confidence: suspects
characters: [smuggler, detective]
notes: >
Detective's analytical lattice may flag this automatically during Terminal walkthrough.
Smuggler encounters it during operational paperwork checks. Pattern becomes clearer over
multiple observations (once: false event — repeats). Starting entry point for both
investigation arcs.
- fact_id: ring.kael_missed_verification
description: Kael Davan was absent from a scheduled cargo verification — another dock worker covered without explanation
typical_confidence: suspects
characters: [smuggler, detective]
notes: >
Discoverable via cargo manifest review (Terminal office) or analytical lattice flag
(detective). Smuggler notices the irregularity in their own paperwork. First concrete
evidence that Kael is deviating from his normal operational reliability.
- fact_id: ring.kael_unauthorized_corridor_access
description: Kael was observed in restricted maintenance corridor B-7 during off-shift hours with an unknown contact
typical_confidence: knows_of
characters: [smuggler, detective]
notes: >
Core FRIEND contradiction observable (D-034). Player must be in visual range of B-7
to discover this, OR examine the corridor door access log (investigative action).
Both characters can discover this fact — interpreted differently (smuggler: security
breach / detective: suspicious contact). Upgrades from suspects to knows_of when
contact identity confirmed, knows_details when purpose confirmed.
- fact_id: ring.kael_unusual_meridian_activity
description: Encrypted Meridian packets sent from cargo bay terminals — frequent, patterned, not from personal devices
typical_confidence: suspects
characters: [detective]
notes: >
Detective-only. Requires analytical lattice to flag the outgoing packet pattern.
Player must be in or adjacent to The Terminal cargo bay. Content not accessible —
only the existence and frequency of the encrypted messages. Hints at Kael's
off-district contact without revealing who.
- fact_id: ring.voss_kael_private_meeting
description: Voss called Kael into the supervisor's office — closed door, raised voices audible from adjacent position
typical_confidence: suspects
characters: [smuggler, detective]
notes: >
Observable via spatial positioning (supervisor's office area). Smuggler can witness
the approach and departure without hearing content. Detective observes Kael's visible
strain immediately after if present in The Terminal. Upgrade to knows_of via
trust-gated dialogue with Kael ("Are you alright?" option unlocks post-meeting).
- fact_id: ring.voss_accelerating_timeline
description: Voss is pushing to close ring operations faster than normal — someone is watching
typical_confidence: knows_of
characters: [smuggler, detective]
notes: >
Smuggler: Voss mentions "the schedule moving up" in guarded conversation (trust-gated,
ring-insider access required). Detective: observe Voss and Nils in two exchanges within
the same shift and note behavioral change. Not directly stated — inferred from context.
- fact_id: ring.commission_cargo_flag
description: A Commission internal note flags The Terminal's cargo variance rate as statistically unusual
typical_confidence: knows_of
characters: [detective]
notes: >
Detective-only. Accessible via institutional Commission query (authority access tier,
Terminal records). Not a formal investigation — just an internal flag from Maret Korr.
Confirms the detective's suspicions with institutional weight. Smuggler has no way to
know this exists.
- fact_id: ring.final_shipment_scheduled
description: The ring has scheduled a major final drop — after it clears, operations go dark
typical_confidence: knows_of
characters: [smuggler, detective]
notes: >
Smuggler: direct notification from Voss (ring-insider access). Detective: cargo manifest
shows an unusually large entry scheduled 3 days out — no obvious legitimate reason for
the volume. This fact creates the closing window — both characters know time is running
out, for different reasons.
- fact_id: ring.sera_avoidance_behavior
description: Sera Venn takes longer routes through The Last Shift to avoid standing near Torek Lintar
typical_confidence: suspects
characters: [detective]
notes: >
Detective-only observable. Player must observe Sera's movement pattern on two separate
occasions — requires forward vision cone and awareness of her baseline route. The
avoidance is visible but not explained. Points toward Sera holding information about
Torek's investigation without having acted on it.
- fact_id: ring.nils_unlogged_cargo
description: Nils makes regular supply runs to maintenance corridor C-7 with containers that never appear in return logs
typical_confidence: suspects
characters: [smuggler, detective]
notes: >
Discoverable by watching Nils's cargo handling pattern over two shifts, or by examining
the maintenance corridor C-7 access log directly. The missing return entry is the tell —
the container went in but never came back. Both characters can find this; smuggler may
already suspect given operational context.
@@ -0,0 +1,11 @@
# Fact catalog: social
# Social positioning facts — standing in a specific social context
# Source: docs/design/knowledge-vocabulary-v01.md §4.8 (#368)
facts:
- fact_id: social.bar_regular_status
description: Character is an established regular at The Last Shift — recognized, trusted in that context
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Smuggler may have this from Background. Detective builds it through multiple bar visits."
@@ -0,0 +1,35 @@
# Fact catalog: world
# Faction and institutional knowledge — Commission, lattice regulation, broader context
# Source: docs/design/knowledge-vocabulary-v01.md §4.6 (#368)
facts:
- fact_id: world.lattice_regulation_active
description: Commission actively regulates neural lattice component access in this system
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Both characters have this from Background"
- fact_id: world.unlicensed_lattice_demand
description: High demand for unlicensed lattice upgrades on working-class stations like Sova
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "The moral context of the operation — smuggler knows it as justification; detective may learn it as complication"
- fact_id: world.commission_sova_jurisdiction
description: Commission has jurisdiction over Sova Transit District
typical_confidence: knows_details
characters: [detective]
notes: "Detective Background knowledge — institutional mandate"
- fact_id: world.ring_cross_system
description: The ring operates across multiple star systems — Sova is one node
typical_confidence: knows_details
characters: [smuggler, detective]
notes: "Late-game discovery — changes scope of the operation"
- fact_id: world.shift_schedule
description: Sova Transit District shift schedule — who works when
typical_confidence: knows_of
characters: [smuggler, detective]
notes: "Smuggler from Background (colleague). Detective through investigation."
+49
View File
@@ -0,0 +1,49 @@
// Trait Modifier Clauses for Voice Pipeline (D-138)
//
// One clause per PersonalityTrait. Injected into LLM prompts to modify
// speech style based on NPC personality. Stacks with culture persona
// and tell-state injectors.
//
// Schema: map of trait name → clause string
// Used by: server/src/voice/prompt_builder.rs
//
// Writing notes:
// - Behavioral, not emotional. Never label a feeling.
// - Each clause targets a distinct speech dimension so traits stack cleanly.
// Bold = delivery force. Cautious = word selection. Curious = sentence shape.
// Compassionate = cadence (engaged). Incurious = cadence (flat). Ruthless = position.
// A Bold+Compassionate NPC speaks with force but gives the answer room to land — no conflict.
// - Kept to 1-2 sentences. LLM context is limited and these share space with
// persona, tell-state, and epistemic marker instructions.
{
// Delivery: force and directness. Short sentences land without hedging.
"Bold": "This character doesn't soften the landing. Statements arrive short and flat — no qualifiers, no trailing uncertainty.",
// Word selection: nothing committed without cover. Hedges stay.
"Cautious": "This character hedges where the situation allows it. \"Probably,\" \"might,\" \"I'd say\" — these aren't weakness, they're habit.",
// Sentence shape: questions surface. Interest pulls the line open.
"Curious": "This character lets interest show at the end of a line — a beat longer than needed, a question that wasn't required.",
// Framing: the useful version, not the true version. Nothing false, just selected.
"Deceptive": "This character leads with what serves them. The useful detail arrives early; the less useful one doesn't arrive at all.",
// Framing: no softening, no omission. The whole thing, bluntly.
"Honest": "This character gives the whole answer, including the part that doesn't reflect well. Nothing is softened to spare anyone.",
// Cadence: open, engaged. Responses arrive with momentum and care.
"Compassionate": "This character gives the answer room to land. There's no rush past the difficult part — it gets said, plainly, without looking away.",
// Cadence: flat, unengaged. The response does its job and stops.
"Incurious": "This character answers what was asked. Nothing extra surfaces — no follow-up, no interest, no second look at what was just said.",
// Volume and reach: minimal, inward-facing. Not interested in being heard widely.
"Reclusive": "This character answers what was asked and closes the door. There is no invitation for follow-up.",
// Texture: open, inclusive. Others are assumed to be present and welcome.
"Social": "This character addresses the conversation, not just the question. A word or two lands that wasn't strictly necessary — the kind that keeps things warm.",
// Position: sharp, economical. Nothing is offered that doesn't serve the speaker.
"Ruthless": "This character cuts to the useful part. Courtesy is absent, not hostile — just unnecessary. The sentence ends when the point is made.",
}
@@ -0,0 +1,62 @@
// Van Maanen's Star Industrial Zone — location metadata
//
// Schema: server/src/npc/blueprint.rs :: ZoneSpec
// Ticket: #609, #630, #661 (copy team)
// Validate: tooling/validate-ron content/global/van-maanens-star-industrial-zone.ron zone
//
// Location-specific metadata for a Van Maanen's Star industrial freight district.
// Behavior primitives have moved to the zone-type template:
// content/global/zone-types/industrial_freight.ron
//
// This file holds: zone_type reference, location metadata, role weights, social sites.
// The generator reads zone_type to find the primitive pool; overrides come from here.
//
// Character: freight handling, manufacturing, maintenance. High throughput.
// Shift rhythms. Functional over comfortable. Nobody lingers — unless they're on break.
(
zone_type: "industrial_freight",
label: "Industrial Zone",
description: "Freight handling, manufacturing, and maintenance. High throughput, shift-based work rhythms, functional over comfortable. Faces are known by role and bay number more than name. The work doesn't stop between shifts — people do.",
// 1-10. Industrial Van Maanen's Star: steady credit flow, but it all goes somewhere.
economic_level: 7,
// 1-10. Dense. Multiple shifts overlap. Crowds at handover.
population_density: 6,
// Role presence and weights for this location.
// skill_focus / combat_eligible / behavior_primitives live in the zone-type template.
role_weights: [
( id: "dock_worker", weight: 5 ),
( id: "technician", weight: 4 ),
( id: "foreman", weight: 2 ),
( id: "security", weight: 2 ),
],
social_sites: [
(
site_type: "break_room",
label: "Worker Break Room",
// Between shifts. Decompression. People stop performing for a moment.
min_npcs: 2,
max_npcs: 5,
),
(
site_type: "maintenance_bay",
label: "Maintenance Bay",
// Active work site. Technicians and dock workers cross paths here.
min_npcs: 2,
max_npcs: 4,
),
(
site_type: "loading_platform",
label: "Loading Platform",
// The operational center. Busy, loud, coordinated.
min_npcs: 3,
max_npcs: 8,
),
],
abandoned: false,
poi_overlay: None,
)
@@ -0,0 +1,44 @@
// Van Maanen's Star Rural Zone — location metadata
//
// Schema: server/src/npc/blueprint.rs :: ZoneSpec
// Ticket: #609, #630, #661 (copy team)
// Validate: tooling/validate-ron content/global/van-maanens-star-rural-zone.ron zone
//
// Location-specific metadata for a Van Maanen's Star rural agricultural settlement.
// Behavior primitives have moved to the zone-type template:
// content/global/zone-types/rural_agricultural.ron
//
// This file holds: zone_type reference, location metadata, role weights, social sites.
// The generator reads zone_type to find the primitive pool; overrides come from here.
//
// Character: scattered homesteads and small workshops. Unhurried. Community-bound.
// Low throughput, high familiarity. Everyone knows who belongs here.
(
zone_type: "rural_agricultural",
label: "Rural Settlement",
description: "Scattered homesteads, small workshops, and communal gathering points. Low population density, strong community bonds, subsistence-plus economy. Work is seasonal and visible — everyone knows what everyone else is doing.",
// 1-10. Rural Van Maanen's Star: self-sufficient, low cash flow, barter supplements credits.
economic_level: 3,
// 1-10. Sparse. Faces are familiar. Strangers stand out.
population_density: 2,
// Role presence and weights for this location.
// skill_focus / combat_eligible / behavior_primitives live in the zone-type template.
role_weights: [
( id: "farmer", weight: 5 ),
( id: "mechanic", weight: 3 ),
( id: "trader", weight: 2 ),
( id: "militia", weight: 1 ),
],
social_sites: [
( site_type: "tavern", label: "Local Tavern", min_npcs: 3, max_npcs: 6 ),
( site_type: "workshop", label: "Community Workshop", min_npcs: 2, max_npcs: 4 ),
( site_type: "market_stall", label: "Settlement Market", min_npcs: 1, max_npcs: 3 ),
],
abandoned: false,
poi_overlay: None,
)
@@ -0,0 +1,97 @@
// Zone Identity Spec — example file
//
// Schema: server/src/npc/blueprint.rs :: ZoneSpec
// Real content: ticket #609 (copy team fills this)
// Validate: tooling/validate-ron content/global/zone-identity-spec.example.ron zone
//
// One file per zone type. The generator reads this to decide NPC count,
// role distribution, and social site placement.
//
// Format: RON (Rusty Object Notation) — the Rust structs ARE the schema.
// Comments are allowed. Trailing commas are allowed.
(
zone_type: "rural",
label: "Rural Settlement",
description: "Scattered homesteads, small workshops, and communal gathering spots. Low population density, strong community bonds, subsistence-plus economy.",
// 1-10 scale. Rural = low economic activity, mostly self-sufficient.
economic_level: 3,
// 1-10 scale. Rural = sparse, everyone knows everyone.
population_density: 2,
roles: [
(
id: "farmer",
label: "Farmer",
weight: 5,
skill_focus: ["technical"],
combat_eligible: false,
typical_behaviors: [
"tends crops in the field",
"hauls produce to the market stall",
"repairs equipment by hand",
],
),
(
id: "mechanic",
label: "Settlement Mechanic",
weight: 3,
skill_focus: ["technical"],
combat_eligible: false,
typical_behaviors: [
"works on machinery with focused intensity",
"wipes grease on coveralls between tasks",
"explains repairs in terse technical shorthand",
],
),
(
id: "trader",
label: "Itinerant Trader",
weight: 2,
skill_focus: ["persuasion", "observation"],
combat_eligible: false,
typical_behaviors: [
"arranges goods on a portable display",
"haggles with quiet persistence",
"watches foot traffic from market stall",
],
),
(
id: "militia",
label: "Settlement Militia",
weight: 1,
skill_focus: ["combat", "observation"],
combat_eligible: true,
typical_behaviors: [
"patrols the settlement perimeter",
"checks credentials at the gate",
"leans on rifle while scanning the horizon",
],
),
],
social_sites: [
(
site_type: "tavern",
label: "Local Tavern",
roles: ["farmer", "mechanic", "trader", "militia"],
min_npcs: 3,
max_npcs: 6,
),
(
site_type: "workshop",
label: "Community Workshop",
roles: ["mechanic", "farmer"],
min_npcs: 2,
max_npcs: 4,
),
(
site_type: "market_stall",
label: "Market Stall",
roles: ["trader", "farmer"],
min_npcs: 1,
max_npcs: 3,
),
],
)
@@ -0,0 +1,168 @@
// Industrial Freight Zone-Type Template
//
// Schema: server/src/npc/blueprint.rs :: ZoneTypeTemplate
// Ticket: #661 (copy team)
// Validate: tooling/validate-ron server/content/global/zone-types/industrial_freight.ron zone_type
//
// Sources: D-142 (zone-type template architecture).
//
// Zone character: freight handling, cargo throughput, shift-based manufacturing.
// High throughput, functional over comfortable. Faces known by role and bay number.
// The work doesn't stop between shifts — people do.
//
// Primitives are CULTURE-NEUTRAL. Culture modifiers (culture-*.ron files) append
// cultural flavor at assembly time. Do not encode cultural attitudes in action text.
//
// Roles: dock_worker, technician, foreman, security
// Social site types: break_room, maintenance_bay, loading_platform
(
id: "industrial_freight",
subtype: None,
label: "Industrial Freight Zone",
description: "Freight handling, cargo throughput, shift-based work rhythms. Functional over comfortable. Faces known by role and bay number. The work doesn't stop between shifts — people do.",
// Default economic/density profile. Location specs override.
economic_level: 7,
population_density: 6,
roles: [
(
id: "dock_worker",
label: "Dock Worker",
skill_focus: ["technical"],
combat_eligible: false,
// weight omitted — location specs declare role_weights
behavior_primitives: [
// OnShift
( action: "guides a freight container into position with hand signals", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "checks a manifest against a handheld scanner", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "waits at the loading bay apron, watching the clock", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "calls bay numbers to a colleague across the noise of the floor", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "hooks a cargo sling and steps clear before signaling the lift", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "stacks empty pallets against a wall", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "wipes sweat from their face with a forearm and keeps moving", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "drags a heavy case along the deck plating one-handed, leaning into the weight", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "re-checks a seal on a container door after a colleague already checked it", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "flags a damaged pallet to the foreman without stopping the line", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "shoulders a cargo rig harness and clips in without looking down", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "sweeps debris off the loading apron with a long push broom", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "braces a container with a boot while reaching for the locking pin", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "reads the load ticket twice, then flips the handheld over to check the back", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "waves off a crane operator when the angle is wrong, holds up a fist", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "steps over a bundled cable run without breaking stride", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "peels off a work glove with his teeth to check a handheld display", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "exchanges a brief look with a nearby colleague when the supervisor passes", context: OnShift, modifier_hint: Some("authority_response") ),
// OffDuty / break room
( action: "slumps into a break room chair and stares at nothing for a full minute before reaching for a drink", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "unwraps a meal packet in the break room and eats standing at the counter", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "passes a drink to the person next to her without being asked", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "leans back in a break room chair with eyes closed, boots crossed at the ankle", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "laughs at something across the break room table, loud enough to carry", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "shows something on a handheld to a colleague and both of them look at it", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "sits with elbows on knees, turning an empty cup in both hands", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "splashes water on his face at the sink and stands there a moment before turning off the tap", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "talks over the break room noise at volume, gesturing with a fork", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "falls asleep in a break room chair, chin on chest, arms folded", context: OffDuty, modifier_hint: Some("offduty_posture") ),
],
),
(
id: "technician",
label: "Systems Technician",
skill_focus: ["technical"],
combat_eligible: false,
behavior_primitives: [
// OnShift
( action: "runs a diagnostic routine on a floor console, eyes on the readout", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "traces a conduit run along the ceiling with a handheld light", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "swaps a panel module, tools back in the pouch before the cover is latched", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "logs a fault code into a datapad before moving on", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "presses an ear to a vibrating duct and listens", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "argues quietly with a display that isn't giving the right numbers", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "queries a fault log through a terminal, eyes briefly unfocused", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "taps a wrench against a junction box lid to test for rattle", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "pulls a burnt relay from a panel and holds it up to the light", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "clips a sensor lead to two terminals and watches the readout settle", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "photographs a fault site with a handheld before touching anything", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "threads a cable through a conduit run without looking, hands working by feel", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "checks a pressure gauge by putting a thumb on the dial housing and reading the needle", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "labels a repaired junction with tape and a marker, block letters", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "reads a service manual on a battered datapad, scrolling with one finger", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "kneels under a raised floor panel with a light between her teeth", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "closes a maintenance hatch and shoulder-checks it twice", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "stands on the second rung of a ladder and reaches without climbing higher", context: OnShift, modifier_hint: Some("physical_manner") ),
// OffDuty / break room
( action: "stretches both arms overhead in the break room doorway, blocking it without noticing", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "sits sideways in a break room chair, back against the wall, feet on the seat beside her", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "pulls up a schematic on a personal handheld and looks at it between bites", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "refills someone else's cup from the urn without commenting on it", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "puts her boots on the break room table when off-shift", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "describes a problem to a colleague who doesn't follow it but nods anyway", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "argues a point at the break room table, tapping the surface for emphasis", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "reads something on a personal device with his head tipped back and the screen held at arm's length", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "laughs until he has to set down his drink", context: OffDuty, modifier_hint: Some("social_signal") ),
],
),
(
id: "foreman",
label: "Shift Foreman",
skill_focus: ["observation", "persuasion"],
combat_eligible: false,
behavior_primitives: [
// OnShift
( action: "walks the floor with a datapad under one arm and says nothing", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "pulls a worker aside for a quiet word near the far wall", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "marks a line off a production board and moves to the next one", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "stands at the mezzanine rail watching throughput without expression", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "reviews shift handover notes and circles something with a stylus", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "speaks to a crew in a low voice — they listen without nodding", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "covers a worker's absence from the shift log by redistributing the bay assignments", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "eats lunch standing at a wall terminal so she can keep an eye on the work", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "takes the call herself instead of forwarding it", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "corrects a worker's form on a piece of equipment without making it a lesson", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "walks a new hire through the handover checklist once, point by point, no shortcuts", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "keeps a junior worker between herself and the inspection team as the inspectors pass through", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "finds a reason to be nearby when a new worker makes her first solo lift", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "absorbs a production shortfall report without passing the frustration down the line", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "hands a worker an extra meal packet and walks away without explaining it", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "signs off on a repair they didn't personally inspect", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "stands at the edge of the loading floor for a long moment before walking back", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "makes a note in the shift log that takes longer to write than it takes to read", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "tells a bad joke to nobody in particular while marking off the production board", context: OnShift, modifier_hint: Some("social_signal") ),
// OffDuty
( action: "sits alone in the break room rubbing the back of her neck, datapad face-down on the table", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "laughs at something a colleague says, then catches herself and goes quiet", context: OffDuty, modifier_hint: Some("social_signal") ),
( action: "sits next to someone in the break room and doesn't talk, just sits", context: OffDuty, modifier_hint: Some("social_signal") ),
],
),
(
id: "security",
label: "Facility Security",
skill_focus: ["combat", "observation"],
combat_eligible: true,
behavior_primitives: [
// OnShift
( action: "sweeps the access corridor on a timed circuit, same path each time", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "waves a regular through the checkpoint on sight — scans the one behind them out of procedure", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "leans at the restricted bay entrance, arms folded, watching the corridor", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "notes something in a shift log without reacting to it outwardly", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "watches a handover between crews from across the loading floor", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "stops at a junction, looks both ways, and picks the longer route", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "runs an ID check on someone whose face she recognizes, procedure is procedure", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "stands with her back to a structural column where both exits are visible", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "glances at a badge without stopping the person wearing it", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "checks the restricted bay door seal before settling in to watch the corridor", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "runs the same check routine when the supervisor is watching as when she isn't", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "acknowledges the facility manager's walk-through with a nod and doesn't change her position", context: OnShift, modifier_hint: Some("authority_response") ),
],
),
],
// Valid social site types for this zone type.
// Location specs declare which appear, with their own label, min_npcs, max_npcs.
social_site_types: [
( site_type: "break_room", eligible_roles: ["dock_worker", "technician", "foreman"] ),
( site_type: "maintenance_bay", eligible_roles: ["technician", "dock_worker"] ),
( site_type: "loading_platform", eligible_roles: ["dock_worker", "foreman", "security"] ),
],
)
@@ -0,0 +1,165 @@
// Rural Agricultural Zone-Type Template
//
// Schema: server/src/npc/blueprint.rs :: ZoneTypeTemplate
// Ticket: #661 (copy team)
// Validate: tooling/validate-ron server/content/global/zone-types/rural_agricultural.ron zone_type
//
// Sources: D-142 (zone-type template architecture).
//
// Zone character: scattered homesteads, small workshops, open agricultural land.
// Seasonal work rhythms, low population density, strong community familiarity.
// Work is visible and shared — everyone knows what everyone else is doing.
//
// Primitives are CULTURE-NEUTRAL. Culture modifiers (culture-*.ron files) append
// cultural flavor at assembly time. Do not encode cultural attitudes in action text.
//
// Roles: farmer, mechanic, trader, militia
// Social site types: tavern, workshop, market_stall
(
id: "rural_agricultural",
subtype: None,
label: "Rural Agricultural Zone",
description: "Crop farming, small workshops, communal settlement. Low density, community-bonded, self-sufficient economy. Work is visible to neighbors — everyone knows what everyone else is doing.",
// Default economic/density profile. Location specs override.
economic_level: 3,
population_density: 2,
roles: [
(
id: "farmer",
label: "Farmer",
skill_focus: ["technical"],
combat_eligible: false,
// weight omitted — location specs declare role_weights
behavior_primitives: [
// OnShift
( action: "works back along a crop row, stopping to pull weeds by the root", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "lifts a crate of produce onto a flatbed and reaches for the next before it settles", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "checks the section's light cycle timer before deciding whether to water", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "patches a cracked irrigation pipe with bonding tape", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "calls across a field to a neighbor without looking up from work", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "hauls produce to the market stall before the morning exchange opens", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "crouches over seedling trays and pinches back a leggy stem that grew sideways", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "runs a thumb along the edge of a cracked irrigation seal, then sets it aside", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "stacks empty crates at the end of a row", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "drags a length of hose to a dry section and clamps the fitting by hand", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "kneels at the base of a struggling plant and parts the soil with two fingers", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "leans on a fence post and watches the sky before going back to the row", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "refills a handheld sprayer from a standing drum", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "ties a row marker to a stake with a short length of wire", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "pulls a glove off to wipe her face, pulls it back on without looking", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "loads a wheelbarrow and tips it into a compost bin at the field edge", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "pulls a dead plant by the roots and carries it to the burn pile", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "tests soil moisture by pressing a thumb into the ground beside a seedling", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "walks the field edge, pulling the wire taut at each span to test the tension", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "sets two crates down on the market floor and counts the lids twice", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "answers an inspector's question without breaking the task rhythm", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "waits for the settlement head to finish speaking before going back to work", context: OnShift, modifier_hint: Some("authority_response") ),
// Social / off-duty
( action: "nurses a drink at the end of the bar with both hands wrapped around the glass", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "trades short words with the person at the next seat without turning fully around", context: Social, modifier_hint: Some("social_signal") ),
( action: "sits with boots off under the table, socked feet flat on the floor", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "refills a neighbor's cup from her own jug without being asked", context: Social, modifier_hint: Some("social_signal") ),
( action: "plays a slow tile game with two others at the corner table", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "slides a credit chit across the bar and waits for change without counting it", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "leans back in the chair and stares at the ceiling for a long moment", context: Social, modifier_hint: Some("offduty_posture") ),
],
),
(
id: "mechanic",
label: "Settlement Mechanic",
skill_focus: ["technical"],
combat_eligible: false,
behavior_primitives: [
// OnShift
( action: "pulls a drive unit from a tiller and examines the worn housing", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "wipes grease on the thigh of her coveralls between jobs", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "explains a repair in clipped shorthand without looking up", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "lines up salvaged parts on a workbench and assesses them", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "welds a seam on a cracked water tank with slow, careful strokes", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "borrows a tool from a neighbor and returns it without being asked", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "taps a seized bolt with a mallet twice before reaching for a longer bar", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "threads a wire through a conduit clip and bites the insulation back with her teeth", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "sets a part down, picks up the spec card beside it, and reads it once", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "uses a straight edge to check the flatness of a repaired flange", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "drains a coolant line into a catch tray and marks the container", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "torques a fastener by feel and then confirms it with a wrench click", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "stacks finished repairs in a corner and photographs them with a handheld", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "jots a note on a strip of tape and sticks it to a part's casing", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "squeezes into a narrow access panel and works by touch", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "straightens up and rolls her neck once before kneeling back down", context: OnShift, modifier_hint: Some("physical_manner") ),
// Social / off-duty
( action: "rinses her hands twice at the basin before sitting down at the bar", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "drops her toolkit bag under the stool and orders without looking at the board", context: Social, modifier_hint: Some("offduty_posture") ),
( action: "listens to a complaint about a piece of equipment and nods once or twice", context: Social, modifier_hint: Some("social_signal") ),
( action: "draws a rough diagram on a napkin to explain something, then folds it away", context: Social, modifier_hint: Some("social_signal") ),
( action: "buys a round for the table and returns to her seat before anyone can thank her", context: Social, modifier_hint: Some("social_signal") ),
],
),
(
id: "trader",
label: "Itinerant Trader",
skill_focus: ["persuasion", "observation"],
combat_eligible: false,
behavior_primitives: [
// OnShift
( action: "squares goods on a fold-out portable display", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "leans back on a stool and tracks a browser who has checked the same display twice without committing", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "flicks credit chits across the counter with one thumb", context: OnShift, modifier_hint: Some("physical_manner") ),
( action: "holds eye contact through a long pause, waiting for the price to land", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "watches a regular browse the same shelf as last time and says nothing", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "packs unsold goods without reacting", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "unrolls a cloth display on the counter and weights the corners with small stones", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "lifts a sample item and sets it in the light where a browser can see it clearly", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "rewraps an unsold item and tucks it back in the case with a specific order", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "counts coins into a small tray and slides it across the counter", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "pulls a ledger from the bag, checks one line, and closes it", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "marks a price down on the board with a grease stylus and steps back", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "holds a cracked item up to show where it failed before handing it back", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "folds the portable display flat and straps it to the pack in two moves", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "lays two items side by side on the counter for a customer to compare", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "wipes down the counter surface with a cloth before setting out the next goods", context: OnShift, modifier_hint: Some("work_pace") ),
// Social
( action: "nurses a drink at the far end of the bar, watching who comes in", context: Social, modifier_hint: Some("environmental_scan") ),
( action: "talks price with whoever's sitting nearest without making it a pitch", context: Social, modifier_hint: Some("social_signal") ),
( action: "buys a round and tracks who accepts and who doesn't", context: Social, modifier_hint: Some("social_signal") ),
( action: "listens more than she talks at the bar, refilling her glass slowly", context: Social, modifier_hint: Some("offduty_posture") ),
],
),
(
id: "militia",
label: "Settlement Militia",
skill_focus: ["combat", "observation"],
combat_eligible: true,
behavior_primitives: [
// OnShift
( action: "walks the fence line at a measured, unhurried pace", context: OnShift, modifier_hint: Some("work_pace") ),
( action: "leans on the gate post with rifle slung, watching the road", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "waves a familiar face through without checking credentials", context: OnShift, modifier_hint: Some("social_signal") ),
// on-duty quiet time — context is OnShift but posture reads as off-duty
( action: "sits in the shade of the gatehouse reading a local newsline", context: OnShift, modifier_hint: Some("offduty_posture") ),
( action: "stops to talk with a passerby, eyes still scanning the perimeter", context: OnShift, modifier_hint: Some("social_signal") ),
( action: "checks the charge on a handheld scanner and clips it back to the belt", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "props a boot on the lower fence rail and scans the far end of the road", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "nods to a passing cart and tracks it until it clears the gate", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "marks a log entry on a handheld at the end of a perimeter pass", context: OnShift, modifier_hint: Some("task_completion") ),
( action: "steps out of the gatehouse at the sound of an approaching engine", context: OnShift, modifier_hint: Some("environmental_scan") ),
( action: "reports a situation to the settlement head without editorializing", context: OnShift, modifier_hint: Some("authority_response") ),
( action: "straightens slightly when the relief officer arrives for handover", context: OnShift, modifier_hint: Some("authority_response") ),
// OffDuty
( action: "sits at the end of the bar still in kit, drink in hand, not really off yet", context: OffDuty, modifier_hint: Some("offduty_posture") ),
( action: "catches up with a neighbor over a drink without mentioning work", context: Social, modifier_hint: Some("social_signal") ),
],
),
],
// Valid social site types for this zone type.
// Location specs declare which appear, with their own label, min_npcs, max_npcs.
social_site_types: [
( site_type: "tavern", eligible_roles: ["farmer", "mechanic", "trader", "militia"] ),
( site_type: "workshop", eligible_roles: ["mechanic", "farmer"] ),
( site_type: "market_stall", eligible_roles: ["trader", "farmer"] ),
],
)
@@ -0,0 +1,692 @@
# yaml-language-server: $schema=../../schemas/drama_module.schema.yaml
#
# Tier 1 Drama Module: The Smuggling Ring (v0.1)
# The vertical slice Tier 1 module for D-027.
#
# NARRATIVE CORE:
# A logistics worker (the smuggler PC, if played) is embedded in a small ring
# smuggling unlicensed lattice components through Sova Transit District (D-037).
# The ring is led by Voss from The Terminal. Kael Davan — a ring member and the
# smuggler's FRIEND — is quietly trying to exit to protect his partner Naia Tamm.
# Sera Venn (the detective's FRIEND) has noticed Kael's manifest discrepancies
# but hasn't reported them, protecting Naia by proxy.
#
# DUAL-LENS EXPERIENCE:
# Smuggler plays INSIDE the ring: manage drops, cover tracks, notice Kael going cold.
# Detective plays OUTSIDE: cargo anomalies → follow Kael → witness secret meeting →
# confront or protect.
#
# SUCCESS CRITERIA (D-027):
# #1: 30 minutes of daily-life play before the ring activates (min_play_ticks: 2100)
# #3: Player names Kael as an NPC they felt conflicted about
# #4: observe→notice→follow→discover emerges from systems, not scripts
module_id: smuggling_ring_v0_1
display_name: "The Smuggling Ring"
version: "0.1"
tier: 1
description: >
A small ring of logistics workers smuggling unlicensed lattice components through
Sova Transit District. The ring's weakest link — Kael Davan — is trying to exit
to protect his partner. The detective investigates cargo anomalies. The smuggler
manages ring operations and navigates Kael's loyalty crisis. Neither character
knows the other's full picture until confrontation forces it.
notes: >
This module IS the vertical slice (D-027). It exercises every system at full depth:
dual-lens NPC observation, tell progression, trust-gated dialogue, knowledge graph
confidence accumulation, confrontation weight (D-063), walk-away consequences (D-064),
and THE FRIEND contradiction arc (D-034). All outcome paths must feel earned.
No outcome is "the right answer" — Kael's situation has no clean resolution.
dual_lens:
smuggler: >
You're inside the ring. Voss manages operations; you handle logistics cover.
Kael used to be reliable. Lately he's absent, distracted, making excuses.
The drop schedule is at risk. Do you pressure him, cover for him, or cut him?
You don't know he's trying to get out. He doesn't know you've noticed.
detective: >
Cargo manifest discrepancies in The Terminal. Small, systematic, deniable.
Your analytical lattice flags them before your conscious mind does.
Follow the thread: discrepancy → dock worker with odd schedule → Kael Davan →
maintenance corridors → someone he shouldn't be meeting. And then what?
Arrest a man trying to leave a ring he never wanted to join?
pool:
weight: 8
compatible_districts:
- sova-transit
max_concurrent: 1
# ── ENTRY CONDITIONS ─────────────────────────────────────────────────────────
# Ring activity begins after player has had time to establish routine (D-027 #1).
# The ring is already running at game start — the module activates when the
# storyteller decides the tension has built enough to surface.
entry_conditions:
world_state:
- type: npc_present
role: ring-leader
- type: npc_present
role: ring-member-exiting
- type: location_accessible
location: the-terminal
- type: location_accessible
location: maintenance-corridors
activation:
trigger: storyteller_push
min_play_ticks: 2100 # ~35 minutes at 1 tick/second — D-027 criterion #1
# The storyteller pushes activation when player has established presence
# in The Terminal or The Last Shift through routine interaction.
# Proximity trigger (maintenance-corridors) is a secondary activation path
# if the player wanders there early.
# ── NPC REQUIREMENTS ─────────────────────────────────────────────────────────
# All core roles are named (hand-authored NPCs from the vertical slice).
# No generated NPC slots in v0.1 — the smuggling ring uses the 15 authored NPCs.
npc_requirements:
- role: ring-leader
display_hint: >
Runs the ring from The Terminal. Logistics authority = cover.
Never handles contraband directly. Pressure source for Kael.
binding: named
named_npc: "npc:voss"
must_have_motivation: HANDLER
- role: ring-member-exiting
display_hint: >
Kael Davan. Dock worker, ring member, smuggler's FRIEND.
Trying to exit quietly to protect Naia. This is THE FRIEND contradiction.
Every event sequence runs through this role.
binding: named
named_npc: "npc:kael-davan"
must_have_pattern: FRIEND
must_have_motivation: TURNCOAT
- role: partner-uninvolved
display_hint: >
Naia Tamm. Kael's partner. Does not know about the ring.
Her safety is Kael's motivation for exiting. Her ignorance is the moral weight.
Discovery of her connection to Kael is a late-investigation revelation.
binding: named
named_npc: "npc:naia-tamm"
must_have_motivation: CIVILIAN
- role: evidence-holder
display_hint: >
Sera Venn. Detective's FRIEND. Commission field tech.
She has noticed Kael's manifest discrepancies but hasn't reported them —
she knows Naia, and filing means Kael's arrest and Naia's exposure.
Her silence IS the detective's investigation blocker in phase 1.
binding: named
named_npc: "npc:sera-venn"
must_have_pattern: FRIEND
must_have_motivation: WITNESS
- role: ring-operative
display_hint: >
The ring's operational member in maintenance corridors.
Handles physical drops. Not a speaking character — observable behavior only.
Can be the anonymous contact Kael meets.
binding: named
named_npc: "npc:nils-davan"
is_optional: false
- role: institutional-watcher
display_hint: >
Maret Korr. A Commission observer embedded at The Terminal.
Her growing attention is the external pressure that accelerates the timeline.
She doesn't know about the ring specifically — she's tracking cargo patterns.
binding: named
named_npc: "npc:maret-korr"
must_have_motivation: OPERATOR
is_optional: true # Module runs without Maret, but with degraded tension arc
# ── EVENTS ───────────────────────────────────────────────────────────────────
# Two sequences + one pool.
# Sequence A: Kael's exit arc (the FRIEND contradiction backbone)
# Sequence B: Investigation pressure arc (escalating discovery opportunities)
# Pool: ambient ring activity (fires opportunistically throughout the module)
events:
sequences:
# SEQUENCE A: Kael's Exit Arc
# The narrative spine. Each step makes Kael's contradiction more visible.
# Observable to both characters, interpreted differently.
- sequence_id: kael_exit_arc
label: "Kael's Exit Arc"
description: >
Kael Davan's progressive attempt to leave the ring.
Tells intensify. Routine deviations appear. The secret meeting is the
pivot point — after it fires, both characters' understanding shifts.
steps:
- event_id: kael_goes_cold
label: "Kael Goes Cold"
description: >
Kael starts missing social patterns he'd normally keep — fewer bar visits,
shorter responses at The Terminal, leaving early. His tell system activates:
the shoulder-check behavior appears. Nothing dramatic. Just absence where
there was presence. The smuggler notices because they work together.
The detective might notice if they've been tracking Kael's baseline.
triggers:
- type: ticks_since_activation
ticks: 300 # ~5 minutes after module activates
effects:
- type: npc_routine_deviation
npc_role: ring-member-exiting
description: >
Kael skips his usual post-shift drink at The Last Shift.
Leaves the terminal 15 minutes early. No explanation.
- type: tell_intensify
npc_role: ring-member-exiting
description: >
Kael's shoulder-check behavior activates at The Terminal.
Visible to any character with forward vision cone in his direction.
sets_flag: kael_behavior_changed
- event_id: drop_happens_without_kael
label: "Scheduled Drop — Kael Absent"
description: >
A ring drop occurs in maintenance corridor C-7. Kael was supposed
to verify the cargo. He wasn't there. Nils covered it.
The smuggler notices the irregularity in the paperwork.
The detective — if watching cargo patterns — sees a manifest entry
with no verifying signature where one is normally present.
triggers:
- type: ticks_since_event
after_event: kael_goes_cold
ticks: 450 # ~7.5 minutes after goes-cold
effects:
- type: fact_becomes_discoverable
fact_id: "ring.kael_missed_verification"
discoverable_by: any
discovery_method: >
Smuggler: check the cargo manifest in The Terminal office.
Detective: analytical lattice flags unsigned verification entry.
- type: location_state
location: maintenance-corridors
description: "An unsigned cargo verification entry exists in corridor C-7's log."
sets_flag: kael_missed_drop
- event_id: kael_secret_meeting
label: "Kael's Secret Meeting"
description: >
Kael meets an off-district contact in maintenance corridor B-7.
This is the observable contradiction (D-034): Kael, in a restricted
area he has no logged reason to be in, talking to someone who's
not in any district NPC roster. His body language is tense.
If the player is in visual range: this is the pivot moment.
If not: the meeting happens anyway — the world doesn't wait.
triggers:
- type: ticks_since_event
after_event: drop_happens_without_kael
ticks: 600 # ~10 minutes after the dropped verification
- type: player_proximity
target_type: location
target: maintenance-corridors
radius_tiles: 12 # Player wandering near triggers the meeting early
effects:
- type: npc_routine_deviation
npc_role: ring-member-exiting
description: >
Kael enters maintenance corridor B-7. Locked door to restricted
supply closet. Emerges with the ring-operative 8 minutes later.
Neither acknowledges the encounter publicly.
- type: fact_becomes_discoverable
fact_id: "ring.kael_unauthorized_corridor_access"
discoverable_by: any
discovery_method: >
Player must be in visual range of corridor B-7.
Or examine the corridor door access log (investigative action).
- type: tell_intensify
npc_role: ring-member-exiting
description: >
After the meeting, Kael's shoulder-check frequency doubles.
Also: he avoids eye contact with the smuggler at The Terminal.
sets_flag: secret_meeting_occurred
- event_id: kael_sends_message
label: "Kael Sends the Message"
description: >
Kael sends an encrypted Meridian message to an off-district contact.
The detective's analytical lattice can detect an anomalous outgoing
packet from the district node — not the content, just the pattern
(frequent, encrypted, sent from cargo bay terminals, not personal devices).
The smuggler won't see this unless they're specifically watching Kael.
triggers:
- type: ticks_since_event
after_event: kael_secret_meeting
ticks: 200
effects:
- type: fact_becomes_discoverable
fact_id: "ring.kael_unusual_meridian_activity"
discoverable_by: detective
discovery_method: >
Detective's analytical lattice flags the outgoing packet pattern.
Requires player to be in or adjacent to The Terminal cargo bay.
sets_flag: kael_message_sent
- event_id: ring_leader_confronts_kael
label: "Voss Confronts Kael"
description: >
Voss calls Kael into The Terminal supervisor's office.
Closed door. Raised voices (audible only from adjacent room/position).
Kael emerges pale. Voss emerges neutral. The smuggler can witness
the approach/departure without hearing content. The detective can
observe Kael's state immediately after if in The Terminal.
This is Voss applying pressure. Kael is now visibly under strain.
triggers:
- type: flag_set
flag: kael_message_sent
- type: ticks_since_event
after_event: kael_message_sent
ticks: 400
effects:
- type: npc_routine_deviation
npc_role: ring-leader
description: "Voss calls Kael into the supervisor's office. Door closed."
- type: npc_routine_deviation
npc_role: ring-member-exiting
description: >
Kael emerges from the meeting looking strained. His shoulder-check
is now constant. He takes an unscheduled break outside, alone.
- type: tell_intensify
npc_role: ring-member-exiting
description: >
Kael's contentment hits lowest observed level. He now actively avoids
the ring-operative (Nils) in public. The disconnection is visible.
- type: fact_becomes_discoverable
fact_id: "ring.voss_kael_private_meeting"
discoverable_by: any
discovery_method: >
Observe the meeting room door (spatial). Or ask Kael directly
after (trust-gated dialogue unlocks "Are you alright?" option).
sets_flag: voss_pressure_applied
# SEQUENCE B: Investigation Pressure Arc
# External pressure that escalates the timeline.
# Fires in parallel with Sequence A.
- sequence_id: investigation_pressure
label: "Investigation Pressure Arc"
description: >
Maret Korr's institutional attention creates a closing window.
Her growing interest is the reason the module can't stay in equilibrium forever.
She doesn't know about the ring — she's a pattern-watcher. But patterns
are what the detective investigates too. Their paths converge.
steps:
- event_id: maret_flags_anomaly
label: "Maret Flags the Cargo Anomaly"
description: >
Maret Korr files an internal Commission note flagging The Terminal's
cargo variance rate as statistically unusual. Not an investigation —
just a flag. The detective's institutional access can pull this note.
The smuggler has no way to know it exists (unless the detective tells them).
triggers:
- type: ticks_since_activation
ticks: 900 # ~15 minutes after activation
effects:
- type: fact_becomes_discoverable
fact_id: "ring.commission_cargo_flag"
discoverable_by: detective
discovery_method: >
Detective queries Commission data via institutional access
(authority access tier, Terminal records).
sets_flag: commission_flag_exists
- event_id: maret_increases_presence
label: "Maret Increases Her Presence"
description: >
Maret starts spending more time in The Terminal. More frequent
walkthroughs during shift changes. Her attention to the cargo bay
area is noticeable to anyone watching. Ring members are unnerved.
Voss starts accelerating the timeline to close operations before
institutional attention becomes formal investigation.
triggers:
- type: ticks_since_event
after_event: maret_flags_anomaly
ticks: 600
- type: player_action
action: examine
target_role: institutional-watcher
effects:
- type: npc_routine_deviation
npc_role: institutional-watcher
description: >
Maret adds two extra Terminal walkthroughs per shift cycle.
Spends 15 minutes studying the cargo bay manifest terminals.
- type: tell_intensify
npc_role: ring-leader
description: >
Voss becomes quieter, more deliberate. Less casual conversation.
His tell — the stillness before speaking — becomes more frequent.
- type: fact_becomes_discoverable
fact_id: "ring.voss_accelerating_timeline"
discoverable_by: any
discovery_method: >
Smuggler: Voss mentions "the schedule moving up" in a guarded
conversation (trust-gated, ring-insider access required).
Detective: observe Voss and Nils in two exchanges within same shift.
sets_flag: timeline_accelerating
- event_id: final_shipment_scheduled
label: "The Final Shipment Is Scheduled"
description: >
The ring schedules the last major drop — after this, they go dark.
This is the closing window. If the detective hasn't uncovered enough
by the time this fires, the ring disperses and the operation closes
without exposure (escaped outcome). If they have, confrontation
becomes unavoidable. The smuggler knows about this drop. Kael doesn't
want to participate. Voss insists.
triggers:
- type: flag_set
flag: timeline_accelerating
- type: ticks_since_event
after_event: maret_increases_presence
ticks: 800
effects:
- type: fact_becomes_discoverable
fact_id: "ring.final_shipment_scheduled"
discoverable_by: any
discovery_method: >
Smuggler: direct notification from Voss.
Detective: cargo manifest shows an unusual large entry for 3 days out.
- type: npc_routine_deviation
npc_role: ring-member-exiting
description: >
Kael's schedule changes: he's assigned to the cargo bay
during the drop window. He doesn't want to be there.
sets_flag: final_shipment_known
pools:
# POOL: Ambient ring activity — opportunistic events that add texture
- pool_id: ambient_ring_activity
label: "Ambient Ring Activity"
description: >
Low-level ring business that happens throughout the module regardless of
player engagement. Creates the sense that the ring exists independently.
Players who look closely will find more; players who don't still feel the world moving.
events:
- event_id: cargo_discrepancy_appears
label: "Small Cargo Discrepancy Appears"
description: >
A minor manifest irregularity appears in The Terminal records.
Small enough to be deniable. Systematic enough to be a pattern.
The detective's analytical lattice may flag it. The smuggler can
correct it if they notice it — covering tracks is part of their role.
triggers:
- type: ticks_since_activation
ticks: 150 # Fires early and repeats
effects:
- type: fact_becomes_discoverable
fact_id: "ring.cargo_discrepancy_pattern"
discoverable_by: any
discovery_method: >
Detective: analytical lattice flags during Terminal walkthrough.
Smuggler: check manifest terminals (or get flagged by the discrepancy
in their own work).
once: false # Repeats — pattern builds over time
- event_id: sera_avoids_torek
label: "Sera Avoids Torek at The Bar"
description: >
Sera Venn reroutes her usual path through The Last Shift to avoid
standing near Torek Lintar (the Commission enforcement officer).
Anyone watching Sera's normal pattern would notice.
This is the detective's first clue that Sera's behavior is odd.
triggers:
- type: ticks_since_activation
ticks: 500
effects:
- type: npc_routine_deviation
npc_role: evidence-holder
description: >
Sera takes a longer route to her usual seat, passing through
the back of the bar to avoid Torek's sightline.
- type: fact_becomes_discoverable
fact_id: "ring.sera_avoidance_behavior"
discoverable_by: detective
discovery_method: >
Observe Sera's path through the bar on two separate occasions.
Requires forward vision cone and awareness of her baseline route.
once: false
- event_id: nils_makes_supply_run
label: "Nils Makes an Unscheduled Supply Run"
description: >
The ring-operative (Nils) enters the maintenance corridors with a
small container logged as "calibration tools". The container isn't
logged for return. Someone paying attention to cargo flow would notice.
triggers:
- type: ticks_since_activation
ticks: 700
effects:
- type: npc_routine_deviation
npc_role: ring-operative
description: "Nils takes a container to maintenance corridor C-7."
- type: fact_becomes_discoverable
fact_id: "ring.nils_unlogged_cargo"
discoverable_by: any
discovery_method: >
Watch Nils's cargo handling pattern over two shifts.
Or examine maintenance corridor C-7 access log.
once: false
# ── OUTCOMES ─────────────────────────────────────────────────────────────────
# Five resolution states. Checked each tick after the first sequence step fires.
# Order matters — the storyteller applies the first matching outcome.
# is_terminal: true ends the module.
outcomes:
# 1. RING EXPOSED
# Detective successfully uncovers the operation.
# Commission becomes involved. Arrests/flight follow.
- outcome_id: ring_exposed
label: "Ring Exposed"
is_terminal: true
description: >
The detective accumulates enough evidence to trigger a formal Commission
inquiry. The ring collapses: arrests, flight, or both. Voss is detained.
Kael's situation is now public. The smuggler (if played) faces consequences.
Naia learns what Kael was doing — and why he was trying to leave.
No clean endings. The right outcome for the detective who goes all the way.
conditions:
facts_known:
- "ring.cargo_discrepancy_pattern"
- "ring.kael_unauthorized_corridor_access"
- "ring.voss_kael_private_meeting"
flags_set:
- secret_meeting_occurred # set by kael_secret_meeting event
- commission_flag_exists # Commission was watching before exposure
effects:
- type: npc_disposition
npc_role: ring-leader
shift: hostile
description: "Voss is detained or flees. Commission inquiry opens."
- type: npc_disposition
npc_role: ring-member-exiting
shift: hostile
description: >
Kael is arrested or disappears. His exit attempt is now moot.
His relationship with Naia is exposed.
- type: faction_reaction
faction: lattice-commission
reaction: grateful
description: "Commission credits the detective's investigation."
- type: npc_exit
npc_role: ring-leader
description: "Voss leaves the district — detained, fled, or both."
# 2. KAEL ESCAPES THE RING
# Unique path. Requires the player to engage with Kael directly
# and choose to help him rather than expose the ring wholesale.
- outcome_id: kael_escapes
label: "Kael Escapes the Ring"
is_terminal: true
description: >
Through the player's choices — helping Kael cover his exit, or warning him,
or simply choosing not to act on what they know — Kael successfully leaves
the ring before the final shipment. He and Naia leave the district quietly.
The ring continues without him, smaller and more cautious.
This outcome requires discovering Kael's secret AND choosing restraint.
The smuggler can engineer this by covering for Kael with Voss.
The detective can achieve this by confronting Kael privately rather than
filing a report. The most morally complicated path.
conditions:
facts_known:
- "ring.kael_unauthorized_corridor_access"
flags_set:
- kael_behavior_changed # set by kael_goes_cold — his exit arc begins here
- secret_meeting_occurred # set by kael_secret_meeting — the pivot moment
- voss_pressure_applied # set by ring_leader_confronts_kael — pressure applied
# ring_exposed is checked first in the outcomes list and is terminal,
# so kael_escapes only evaluates if ring_exposed hasn't fired.
# No flags_not_set needed here — outcome ordering handles priority.
effects:
- type: npc_disposition
npc_role: ring-member-exiting
shift: friendly
description: "Kael remembers whoever helped him. He's gone, but grateful."
- type: npc_exit
npc_role: ring-member-exiting
description: "Kael and Naia leave Sova Transit District."
- type: faction_reaction
faction: the-ring
reaction: suspicious
description: "The ring is destabilized by Kael's exit. Voss is alert to further leaks."
# 3. RING COMPLETES OPERATION
# The ring finishes the final shipment and goes dark before discovery.
# Default path if the detective doesn't move fast enough.
- outcome_id: ring_completes
label: "Ring Completes the Operation"
is_terminal: true
description: >
The final shipment clears. The ring disperses. Voss transfers. Nils goes quiet.
Kael stays — he's now out by default, the ring having dissolved around him.
The evidence trail goes cold. The detective closes the case as inconclusive.
The smuggler completes their last run and waits to see if there's another.
Unsatisfying only if you expected a tidy resolution. The world moved on.
conditions:
flags_set:
- final_shipment_known # set by final_shipment_scheduled event
- timeline_accelerating # set by maret_increases_presence — Maret forced their hand
facts_not_known:
- "ring.cargo_discrepancy_pattern" # detective never found the basic pattern — no investigation
ticks_since_activation: 3600 # Module ran for ~60 minutes without full exposure
# kael_message_sent was previously gated here but auto-fires at tick ~1550,
# making this outcome permanently unreachable. Replaced with player-action fact gate.
effects:
- type: faction_reaction
faction: the-ring
reaction: neutral
description: "The ring successfully completed this operation. They'll be back."
- type: npc_exit
npc_role: ring-leader
description: "Voss transfers to another station for 'career development'."
- type: location_access_change
location: maintenance-corridors
change: open
description: "The restricted supply closet is now empty. Access log shows it cleared."
# 4. RING SPLINTERS
# Partial discovery. The ring fractures but doesn't fully collapse.
# An incomplete ending that leaves threads for future investigation.
- outcome_id: ring_splinters
label: "Ring Splinters"
is_terminal: false # Not terminal — splinter state can evolve
description: >
Enough evidence surfaces that the ring knows it's been partially seen.
Voss shuts down active operations. Nils disappears. Kael stays — now the
one person in the district who knows what happened and has no one to tell.
The formal investigation stalls for lack of a clear chain of evidence.
The detective has facts but not the complete picture. The smuggler
faces an awkward return to normalcy. Both know the ring isn't gone — just quiet.
conditions:
facts_known:
- "ring.cargo_discrepancy_pattern" # detective found some evidence — ring responds
events_fired:
- kael_goes_cold # event ID — Kael's behavioral shift fired
flags_set:
- kael_missed_drop # set by drop_happens_without_kael — ring destabilized
ticks_since_activation: 2400
# Mutually exclusive with ring_completes via facts_known/facts_not_known on
# ring.cargo_discrepancy_pattern. No auto-flag gate needed.
effects:
- type: npc_disposition
npc_role: ring-leader
shift: suspicious
description: "Voss goes quiet. He's watching to see who knows what."
- type: npc_exit
npc_role: ring-operative
description: "Nils stops appearing at The Terminal. Transferred, officially."
- type: faction_reaction
faction: the-ring
reaction: suspicious
description: "The ring is alerted to exposure risk. Future operations will be more careful."
# 5. INVESTIGATION STALLS (post-splinter exit)
# The ring splinters but the detective never breaks through to the pivot evidence.
# Explicit terminal exit for the non-terminal ring_splinters state.
- outcome_id: ring_stalemate
label: "Investigation Stalls"
is_terminal: true
description: >
The ring went dark after the splinter. The detective has the cargo discrepancy
on record — enough to flag, not enough to pursue. The case stays open but cold.
No arrests. No answers. Kael stays in the district, the only person who knows
the full shape of what happened, with no one left to tell it to.
The ring will reconstitute elsewhere. It always does.
conditions:
facts_known:
- "ring.cargo_discrepancy_pattern" # ring_splinters already fired (same gate)
facts_not_known:
- "ring.kael_unauthorized_corridor_access" # detective never reached the pivot evidence
flags_set:
- kael_missed_drop
- final_shipment_known # ring finished while investigation stalled
ticks_since_activation: 4500 # 2100 ticks after ring_splinters window — investigation ran cold
effects:
- type: faction_reaction
faction: lattice-commission
reaction: neutral
description: "The discrepancy flag stays in Maret's file. No follow-up action."
- type: npc_exit
npc_role: ring-leader
description: "Voss quietly transfers. No announcement, no incident report."
# 6. MODULE EXPIRY (quiet exit)
# Player never engaged at all. Module times out without drama.
# NOTE (Gestalt, Sprint 18): Condition uses facts_not_known, not flags_not_set.
# kael_behavior_changed fires automatically at tick 300 (time-triggered), making
# flags_not_set: [kael_behavior_changed] permanently false after tick 300.
# Gate expiry on player-action-required facts instead.
- outcome_id: module_abandoned
label: "Module Abandoned"
is_terminal: true
is_expiry: true
description: >
The player never engaged with the ring's signals. The final shipment
completed without incident. The ring disperses on its own schedule.
Kael stays. The world is unchanged. This is not failure — it's the game
acknowledging that not every conspiracy needs a protagonist.
The 70% mundane majority (D-029) plays out: life continued.
conditions:
facts_not_known:
- "ring.cargo_discrepancy_pattern" # Only known via player examination of terminal
- "ring.kael_unauthorized_corridor_access" # Only known via player observing Kael in B-7
ticks_since_activation: 5400 # ~90 minutes with zero player investigation
effects:
- type: faction_reaction
faction: the-ring
reaction: neutral
description: "The ring closed operations without incident. No record of compromise."
@@ -0,0 +1,832 @@
# NPC-to-NPC Overheard Dialogue Pool
# Ticket: #536 | Author: Mellanie | Sprint: 14
# Decision refs: D-078, D-018, D-071, D-035
#
# LEGACY CONTENT — v0.1 hand-authored pool. DO NOT DELETE.
# All entries in this file reference named NPCs from the Sova Transit District
# vertical slice (Kael Davan, Sera Venn, Naia Tamm, Voss, Nils, Maret Korr, etc.).
# These named NPCs were authored for the drama module system (ticket #158), which
# was superseded before implementation. The named references in this file are
# therefore broken — they reference characters that do not exist in the generated
# NPC population.
# Ticket #664 tracks replacement of this file with a zone-type-aware overheard
# generator that produces conversations from role pairs rather than named characters.
# Until #664 ships, this file is not served at runtime.
#
# These are conversations the player can overhear when stationary near two
# NPCs. The passive dialogue panel (D-078) displays them with per-word
# occlusion based on distance, ambient noise, and ListeningFocus stance.
#
# OCCLUSION-RESILIENT AUTHORING RULES (D-078):
# 1. Front-load key information — most important word in first third.
# 2. Short declarative sentences — one idea per turn.
# 3. No pronoun-first openers — first word has highest drop risk; a dropped
# pronoun without antecedent is unresolvable. Use names or nouns.
# 4. Each turn is self-contained — a player who hears only one side gets
# a complete thought.
#
# SCHEMA NOTE:
# `relationship_type` and `knowledge_payload` are custom extensions to the
# D-035 schema for this content type. NPC-sourced lines use `role: npc`,
# `access` and `trust` apply normally. Monologue-specific tags (`character`,
# `trigger`, `prerequisite`) are not used here.
# Schema confirmation with Gestalt (#168) before CI validation.
#
# REGISTERS:
# social — idle chat, personal news, relationship talk
# work — shift logistics, job gripes, operational notes
# gossip — third-party information with player-relevant knowledge payload
#
# RELATIONSHIP TYPES:
# colleague, friend, hostile, romantic
#
# KNOWLEDGE PAYLOAD FORMAT:
# Plain English description of what the player can infer from hearing this
# exchange clearly — or the key fragment they retain under partial occlusion.
# null if the exchange is social noise with no investigative value.
pairs:
# ===================================================================
# SOCIAL register — personal news, idle chat, relationships
# ===================================================================
- id: overheard_001
register: social
speaker_a:
role: dock-worker
text: "Kael brought food for the whole bay yesterday. Just showed up with it."
speaker_b:
role: dock-worker
text: "Naia must have made him feel guilty about something. Again."
relationship_type: colleague
topic: personal-gossip
location_hints: [the-terminal, the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael and Naia have a relationship dynamic. Naia has leverage or emotional pull over Kael's behavior."
tags: [kael, naia, social, atmosphere]
- id: overheard_002
register: social
speaker_a:
role: bar-regular
text: "Lera's keeping the kitchen open late this week. Span gate delay — crews stuck here."
speaker_b:
role: bar-regular
text: "Good for Lera. Bad for everyone waiting on the gate."
relationship_type: friend
topic: station-life
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [lera, span-gate, atmosphere, social]
- id: overheard_003
register: social
speaker_a:
role: dock-worker
text: "Drin's applying for the transfer. Again. Third time he's tried."
speaker_b:
role: dock-worker
text: "Drin's never getting off Sova. Some people just belong to a place."
relationship_type: colleague
topic: career-gossip
location_hints: [the-terminal, the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Drin wants out of Sova Transit. Unhappy enough to pursue transfer requests."
tags: [drin, transfer, social, atmosphere]
- id: overheard_004
register: social
speaker_a:
role: bar-regular
text: "Naia and Kael had a fight. Loud enough that Lera had to step in."
speaker_b:
role: bar-regular
text: "Kael looked rough this morning. That tracks."
relationship_type: friend
topic: relationship-drama
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael and Naia are under relationship strain. Kael's mood is affected. Cross-reference: behavioral.kael_behavioral_change."
tags: [kael, naia, relationship, friend-arc-adjacent, dual-lens]
- id: overheard_005
register: social
speaker_a:
role: dock-worker
text: "Renn's finally getting his lattice service done. Been putting it off for years."
speaker_b:
role: dock-worker
text: "Commission clinic wait time is eight months. Renn found someone faster."
relationship_type: colleague
topic: lattice-access
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Renn found a non-Commission lattice service provider. Points toward unlicensed lattice components market."
tags: [renn, lattice, contraband-adjacent, atmosphere]
- id: overheard_006
register: social
speaker_a:
role: bar-regular
text: "Maret's promotion came through. Third level supervisor."
speaker_b:
role: bar-regular
text: "Good. Maret actually knows what she's doing. Unlike some."
relationship_type: colleague
topic: career-news
location_hints: [the-last-shift, the-terminal]
access: [public]
trust: surface
knowledge_payload: null
tags: [maret, promotion, atmosphere, social]
- id: overheard_007
register: social
speaker_a:
role: dock-worker
text: "Voss has been in a mood all week. Something from upstairs."
speaker_b:
role: dock-worker
text: "Voss is always in a mood. Week ends in -day, Voss is in a mood."
relationship_type: colleague
topic: supervisor-gossip
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Voss is under external pressure from 'upstairs.' Something is stressing management."
tags: [voss, management, atmosphere, social]
- id: overheard_008
register: social
speaker_a:
role: bar-regular
text: "Sera's been coming here every evening this week. Thought Commission people didn't drink."
speaker_b:
role: bar-regular
text: "Sera's different. She fits here better than she fits over there."
relationship_type: colleague
topic: social-observation
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Sera Venn is a regular at The Last Shift, unusual for a Commission employee. She has social roots in the district."
tags: [sera, commission, atmosphere, social, dual-lens]
- id: overheard_009
register: social
speaker_a:
role: dock-worker
text: "Nils covered Kael's morning slot yesterday. No explanation, just a roster note."
speaker_b:
role: dock-worker
text: "Kael's been doing that a lot lately. Taking sick leave, then showing up mid-shift."
relationship_type: colleague
topic: roster-anomaly
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Kael's schedule is irregular. Unexplained absences and late arrivals. Cross-reference: observe_anomaly triggers for Kael."
tags: [kael, nils, roster, friend-arc-adjacent]
- id: overheard_010
register: social
speaker_a:
role: maintenance-tech
text: "Olin's kid got into the Commission cadet program. Starts next cycle."
speaker_b:
role: maintenance-tech
text: "Olin must be pleased. Cost of living here, Commission pay makes sense."
relationship_type: colleague
topic: family-news
location_hints: [maintenance-corridors, the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [olin, commission, atmosphere, social]
# ===================================================================
# WORK register — shift logistics, operational gripes, job talk
# ===================================================================
- id: overheard_011
register: work
speaker_a:
role: dock-worker
text: "Maret shifted the roster again. Bay four to bay seven, no reason given."
speaker_b:
role: dock-worker
text: "Bay seven's the low-traffic slot. Somebody wanted less oversight over there."
relationship_type: colleague
topic: roster-change
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Bay seven has been given a low-oversight crew deliberately. This is operationally significant."
tags: [maret, roster, bay-seven, investigation-adjacent]
- id: overheard_012
register: work
speaker_a:
role: dock-worker
text: "Loading arm three is grinding again. Filed the report last week."
speaker_b:
role: dock-worker
text: "Maintenance says next cycle. Means nothing gets done until someone loses a hand."
relationship_type: colleague
topic: equipment-maintenance
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: null
tags: [equipment, maintenance, atmosphere, work]
- id: overheard_013
register: work
speaker_a:
role: dock-worker
text: "Container 4471's been in temp for three days. Someone should move it."
speaker_b:
role: dock-worker
text: "Routing says hold. Don't ask me, I just process what the board says."
relationship_type: colleague
topic: container-routing
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Container 4471 is being deliberately held in temp storage. 'Routing says hold' — someone modified the routing instruction."
tags: [container-4471, routing, contraband-adjacent, investigation-payload]
- id: overheard_014
register: work
speaker_a:
role: dock-worker
text: "Voss cut the B-section crew by two. Says it's budget. Doesn't feel like budget."
speaker_b:
role: dock-worker
text: "Less crew in B-section means less oversight. Could be budget. Could be something else."
relationship_type: colleague
topic: crew-reduction
location_hints: [the-terminal, maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "B-section is understaffed. The speaker suspects this is deliberate. Cross-reference: ring oversight windows."
tags: [voss, b-section, oversight, investigation-payload]
- id: overheard_015
register: work
speaker_a:
role: dock-worker
text: "Span gate's backed up again. Forty-minute delay on the freight queue."
speaker_b:
role: dock-worker
text: "Forty minutes is nothing. Last month it was three hours. Patience."
relationship_type: colleague
topic: span-gate-delay
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: null
tags: [span-gate, freight, atmosphere, work]
- id: overheard_016
register: work
speaker_a:
role: maintenance-tech
text: "Junction C-2's camera was repositioned. Nobody filed a maintenance request."
speaker_b:
role: maintenance-tech
text: "Someone moved it without logging it. That's a compliance violation."
relationship_type: colleague
topic: camera-anomaly
location_hints: [maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "Camera at junction C-2 was moved without documentation. Suggests deliberate surveillance manipulation. Cross-reference: awareness.surveillance_change."
tags: [camera, surveillance, c-2, investigation-payload]
- id: overheard_017
register: work
speaker_a:
role: dock-worker
text: "Commission wants another inspection. Fourth one this quarter."
speaker_b:
role: dock-worker
text: "Inspections mean overtime. Inspections mean everyone's watching everyone."
relationship_type: colleague
topic: commission-inspection
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Commission is conducting elevated inspections of the terminal. Something has drawn their attention."
tags: [commission, inspection, atmosphere, investigation-adjacent]
- id: overheard_018
register: work
speaker_a:
role: dock-worker
text: "Weight manifest on the morning freight came in five hundred kilos short. Recalibration error."
speaker_b:
role: dock-worker
text: "Five hundred kilos doesn't disappear from a calibration error. It disappears from somewhere else."
relationship_type: colleague
topic: weight-discrepancy
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Manifest weight discrepancy exists and dock workers are suspicious of the official explanation."
tags: [manifest, weight-discrepancy, contraband-adjacent, investigation-payload]
- id: overheard_019
register: work
speaker_a:
role: dock-worker
text: "Shift handover's going to be rough tonight. Torek's team hasn't filed."
speaker_b:
role: dock-worker
text: "Torek's team never files on time. Let Maret handle it."
relationship_type: colleague
topic: shift-handover
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: null
tags: [torek, maret, shift, atmosphere, work]
- id: overheard_020
register: work
speaker_a:
role: maintenance-tech
text: "Sub-level access has been requested twice this week. Both times outside shift hours."
speaker_b:
role: maintenance-tech
text: "Scheduled maintenance happens in-shift. Off-hours access needs sign-off."
relationship_type: colleague
topic: sub-level-access
location_hints: [maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "Someone has been accessing sub-level spaces outside normal hours. No sign-off implies unauthorized use. Cross-reference: smuggling-hold activity."
tags: [sub-level, access, maintenance, investigation-payload]
- id: overheard_021
register: work
speaker_a:
role: dock-worker
text: "Bay four's been sealed for inspection since yesterday morning."
speaker_b:
role: dock-worker
text: "Commission authorized it. Not Voss. Commission went over his head."
relationship_type: colleague
topic: bay-inspection
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Bay four inspection bypassed Voss's authority. Commission is operating independently of local management."
tags: [bay-four, commission, voss, inspection, investigation-payload]
- id: overheard_022
register: work
speaker_a:
role: dock-worker
text: "Kael's running Renn's route today. Renn's supposed to be on that."
speaker_b:
role: dock-worker
text: "Kael volunteered. Said Renn had something come up."
relationship_type: colleague
topic: route-substitution
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Kael is voluntarily taking routes outside his assignment. Could be covering for Renn, could be operational flexibility needed by the ring."
tags: [kael, renn, route, ring-adjacent]
- id: overheard_023
register: work
speaker_a:
role: bar-regular
text: "Lera's dealing with the supply chain issue again. Grain spirit supplier changed terms."
speaker_b:
role: bar-regular
text: "Lera will figure it out. Lera always figures it out."
relationship_type: friend
topic: supply-chain
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [lera, supply, atmosphere, work]
- id: overheard_024
register: work
speaker_a:
role: maintenance-tech
text: "Condensation in the B-corridor's gotten worse. Someone's running heat-generation equipment down there."
speaker_b:
role: maintenance-tech
text: "Heat-gen in a maintenance corridor. That's either a storage issue or a very bad idea."
relationship_type: colleague
topic: corridor-anomaly
location_hints: [maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "Heat-generating equipment is being used in maintenance corridors unofficially. Points toward the smuggling hold or ring operations."
tags: [b-corridor, heat, maintenance, smuggling-adjacent]
- id: overheard_025
register: work
speaker_a:
role: dock-worker
text: "Torek's been doing double manifests for a month. Every container logged twice."
speaker_b:
role: dock-worker
text: "Double logging means one version goes somewhere it shouldn't."
relationship_type: colleague
topic: manifest-anomaly
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Torek is maintaining duplicate manifest records. One version is falsified. Direct evidence of ring operation at the administrative level."
tags: [torek, manifest, contraband, investigation-payload, high-value]
# ===================================================================
# GOSSIP register — third-party knowledge with investigative payload
# ===================================================================
- id: overheard_026
register: gossip
speaker_a:
role: dock-worker
text: "Kael was in corridor B-7 last night. Saw him myself. Off shift, wrong time, wrong place."
speaker_b:
role: dock-worker
text: "Kael lives near B-section. Could have been heading home the long way."
relationship_type: colleague
topic: kael-location
location_hints: [the-terminal, the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael was in corridor B-7 off-shift. Eyewitness account."
tags: [kael, corridor-b7, investigation-payload, friend-arc, high-value]
- id: overheard_027
register: gossip
speaker_a:
role: bar-regular
text: "Kael was talking to someone near B-7 last night. Person I didn't recognize."
speaker_b:
role: bar-regular
text: "Unknown people at junction B-7 at night. That's not normal."
relationship_type: friend
topic: kael-unknown-contact
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael met with an unidentified person at corridor B-7 during off-hours."
tags: [kael, unknown-contact, corridor-b7, friend-arc, high-value, investigation-payload]
- id: overheard_028
register: gossip
speaker_a:
role: bar-regular
text: "Torek spent three thousand credits at Lera's last week. Three thousand. On a dock worker's salary."
speaker_b:
role: bar-regular
text: "Torek's either very lucky or very stupid. Either way, someone's going to notice."
relationship_type: friend
topic: torek-spending
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Torek is spending significantly above his salary at The Last Shift."
tags: [torek, spending, lera, investigation-payload, ring-adjacent]
- id: overheard_029
register: gossip
speaker_a:
role: dock-worker
text: "Renn got new boots. Commission-grade. Renn can't afford Commission-grade boots."
speaker_b:
role: dock-worker
text: "Renn's been running extra shifts. Or extra something."
relationship_type: colleague
topic: renn-spending
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Renn has unexplained extra income. Cross-reference: ring membership payments."
tags: [renn, income, ring-adjacent, investigation-adjacent]
- id: overheard_030
register: gossip
speaker_a:
role: bar-regular
text: "Sera Venn avoids Torek every time he's here. Every single time. Noticed it three weeks straight."
speaker_b:
role: bar-regular
text: "Torek does that to people. He talks too much and says things he shouldn't."
relationship_type: colleague
topic: sera-torek-avoidance
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Sera Venn has a consistent avoidance pattern toward Torek Lintar. Cross-reference: behavioral.sera_avoidance_pattern."
tags: [sera, torek, avoidance, friend-arc, investigation-payload, high-value]
- id: overheard_031
register: gossip
speaker_a:
role: bar-regular
text: "Commission officer's been asking questions at The Terminal. Polite questions. Thorough ones."
speaker_b:
role: bar-regular
text: "Polite and thorough is the worst combination. That's someone who has time."
relationship_type: colleague
topic: commission-investigation
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "A Commission officer is conducting a quiet investigation at The Terminal. Cross-reference: awareness.detective_presence."
tags: [commission, detective, investigation-payload, awareness]
- id: overheard_032
register: gossip
speaker_a:
role: dock-worker
text: "Lattice components went through last month. Unlicensed grade. Manifest said 'mechanical parts.'"
speaker_b:
role: dock-worker
text: "Mechanical parts. Right. People find ways when the Commission won't."
relationship_type: colleague
topic: contraband-transit
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Unlicensed lattice components are moving through The Terminal under falsified manifests. Cross-reference: contraband operation confirmed."
tags: [lattice, contraband, manifest, investigation-payload, high-value]
- id: overheard_033
register: gossip
speaker_a:
role: maintenance-tech
text: "Sub-level temp storage has been accessed four times this week. Door log shows it."
speaker_b:
role: maintenance-tech
text: "Four times and no maintenance ticket filed. Someone's using it off-book."
relationship_type: colleague
topic: unauthorized-access
location_hints: [maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "The sub-level storage (smuggling hold) has heavy unauthorized use documented in door logs. Direct evidence of ring operations."
tags: [sub-level, access-log, ring, investigation-payload, high-value]
- id: overheard_034
register: gossip
speaker_a:
role: bar-regular
text: "Voss changed the B-section rotation. Kael and Renn are both on the overnight slot now."
speaker_b:
role: bar-regular
text: "Kael and Renn on overnight in B-section. That's a very specific combination."
relationship_type: colleague
topic: roster-combination
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael and Renn have been placed on the same overnight B-section rotation. This creates the oversight gap the ring needs."
tags: [kael, renn, voss, roster, ring-adjacent, investigation-payload]
- id: overheard_035
register: gossip
speaker_a:
role: dock-worker
text: "Sera asked me about container routing last week. Wanted to know who approves temp storage extensions."
speaker_b:
role: dock-worker
text: "Commission tech asking about temp storage approvals. That's outside her job scope."
relationship_type: colleague
topic: sera-investigation
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Sera Venn is conducting her own investigation into temp storage approvals, outside her Commission mandate. Cross-reference: behavioral.sera_kiosk_pattern."
tags: [sera, storage, investigation-payload, friend-arc, dual-lens, high-value]
- id: overheard_036
register: gossip
speaker_a:
role: bar-regular
text: "Naia told me Kael hasn't been sleeping. Says he's up late, doesn't explain where."
speaker_b:
role: bar-regular
text: "Naia's worried about him. Kael won't talk about whatever it is."
relationship_type: friend
topic: kael-behavioral-change
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael's behavior has changed at home. Sleepless, secretive, won't explain to Naia. Cross-reference: behavioral.kael_behavioral_change."
tags: [kael, naia, behavior, friend-arc, investigation-payload]
- id: overheard_037
register: gossip
speaker_a:
role: dock-worker
text: "Someone flagged a manifest discrepancy on the morning freight. Voss cleared the flag himself."
speaker_b:
role: dock-worker
text: "Voss doesn't clear flags. That's Maret's job. Or Commission's."
relationship_type: colleague
topic: manifest-flag-cleared
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Voss manually cleared a manifest discrepancy flag, bypassing protocol. Suggests Voss is actively covering for ring activity."
tags: [voss, manifest, flag-cleared, investigation-payload, high-value]
- id: overheard_038
register: gossip
speaker_a:
role: bar-regular
text: "Kael's been asking about exit options. Not just talk — actually asking Lera if she knows anyone who could help someone disappear quietly."
speaker_b:
role: bar-regular
text: "Kael wants out of something. That's the only reason people ask that kind of question."
relationship_type: friend
topic: kael-exit-attempt
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Kael is actively trying to leave 'something' — most likely the ring. He's researching options."
tags: [kael, exit, ring-adjacent, friend-arc, investigation-payload, high-value]
- id: overheard_039
register: gossip
speaker_a:
role: dock-worker
text: "Medical lattice upgrades are showing up on the black-side. Commission-grade, no paperwork."
speaker_b:
role: dock-worker
text: "People who need them can't wait on Commission approval. Someone's doing a service."
relationship_type: colleague
topic: black-market-lattice
location_hints: [the-terminal, the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Medical-grade lattice components are available outside Commission channels. Confirms the moral framing of the contraband operation — it serves real medical need."
tags: [lattice, medical, black-market, contraband, moral-framing]
- id: overheard_040
register: gossip
speaker_a:
role: bar-regular
text: "Torek told someone he had a meeting last night. Past midnight. In maintenance."
speaker_b:
role: bar-regular
text: "Torek having midnight maintenance meetings. Sure. That's completely normal."
relationship_type: colleague
topic: torek-late-meeting
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Torek met with someone in the maintenance corridors late at night."
tags: [torek, maintenance, midnight, ring-adjacent, investigation-payload]
# ===================================================================
# BONUS PAIRS — social and work depth
# ===================================================================
- id: overheard_041
register: social
speaker_a:
role: bar-regular
text: "Lera's thinking about expanding. Back room could seat twenty more."
speaker_b:
role: bar-regular
text: "Back room's the only reason this place has any privacy. Expand it and we lose that."
relationship_type: friend
topic: bar-expansion
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [lera, bar, atmosphere, social]
- id: overheard_042
register: work
speaker_a:
role: dock-worker
text: "Shift change is late again. Maret says weather on Velen is delaying the span gate."
speaker_b:
role: dock-worker
text: "Weather on Velen. Which means fog. Which means the morning run is going to be rough."
relationship_type: colleague
topic: weather-delay
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: null
tags: [velen, fog, span-gate, weather, atmosphere]
- id: overheard_043
register: social
speaker_a:
role: bar-regular
text: "Olin's getting a commendation from Commission. Ten years of service."
speaker_b:
role: bar-regular
text: "Ten years. Commission gives you a piece of paper and a handshake."
relationship_type: colleague
topic: commission-recognition
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [olin, commission, atmosphere, social]
- id: overheard_044
register: work
speaker_a:
role: maintenance-tech
text: "Power fluctuation in sub-level B last night. Lasted three minutes."
speaker_b:
role: maintenance-tech
text: "Three minutes is enough to blind the cameras if you know the timing."
relationship_type: colleague
topic: power-fluctuation
location_hints: [maintenance-corridors]
access: [public]
trust: surface
knowledge_payload: "A power fluctuation in sub-level B temporarily disabled cameras. Could be timed to enable unobserved access."
tags: [power, cameras, sub-level, timing, investigation-adjacent]
- id: overheard_045
register: gossip
speaker_a:
role: dock-worker
text: "Commission officer asked Drin about cargo manifest irregularities. Drin told him everything he knows, which isn't much."
speaker_b:
role: dock-worker
text: "Drin doesn't know much by design. That's why Drin's on inspection detail."
relationship_type: colleague
topic: commission-inquiry
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "The Commission officer is asking dock workers directly about manifest irregularities. Drin has been interviewed. Cross-reference: awareness.detective_presence."
tags: [drin, commission, detective, interview, investigation-payload]
- id: overheard_046
register: gossip
speaker_a:
role: bar-regular
text: "Sera's been keeping a list. Someone told Naia. Private list, personal data."
speaker_b:
role: bar-regular
text: "Sera keeping a list of what? And why would Naia know?"
relationship_type: friend
topic: sera-documentation
location_hints: [the-last-shift]
access: [public]
trust: surface
knowledge_payload: "Sera is documenting something privately — possibly her own investigation or evidence she's sitting on."
tags: [sera, naia, list, evidence, friend-arc, investigation-payload, dual-lens]
- id: overheard_047
register: social
speaker_a:
role: dock-worker
text: "Sova's been home for twenty years. Still don't know if I love it or just got used to it."
speaker_b:
role: dock-worker
text: "Twenty years is the same thing."
relationship_type: friend
topic: station-life
location_hints: [the-terminal, the-last-shift]
access: [public]
trust: surface
knowledge_payload: null
tags: [sova, atmosphere, personal, social]
- id: overheard_048
register: work
speaker_a:
role: dock-worker
text: "Voss approved overtime for two extra heads on the B-section night shift. Unusual."
speaker_b:
role: dock-worker
text: "Overtime plus extra bodies in B at night. Either something's wrong or something's being made to look right."
relationship_type: colleague
topic: overtime-approval
location_hints: [the-terminal]
access: [public]
trust: surface
knowledge_payload: "Voss approved extra overnight staffing in B-section — possibly to create cover or provide legitimate-looking explanation for movement in that area."
tags: [voss, overtime, b-section, night-shift, investigation-adjacent]
@@ -0,0 +1,718 @@
# Drama Module Schema — Tier 1 Content (D-023)
# YAML expression of JSON Schema 2020-12
# Validated against this schema: server/content/modules/tier1/*.yaml
#
# Ownership:
# Dramatic structure (this file): Paula
# YAML validation tooling / serde structs: Gestalt / Tyre
# Authoring ergonomics review: Mellanie
#
# See: docs/design/tier1-module-authoring.md for field-by-field guide.
$schema: "https://json-schema.org/draft/2020-12/schema"
$id: "drama_module.schema.yaml"
title: "Tier 1 Drama Module"
description: >
A hand-authored drama module drawn from the pool at game start.
The storyteller activates one or more modules per playthrough based on
entry conditions, then fires events and detects outcomes. Tier 1 modules
are the conspiracy layer of D-023 — authored, optional, relocatable.
type: object
required:
- module_id
- display_name
- version
- tier
- pool
- entry_conditions
- npc_requirements
- events
- outcomes
additionalProperties: false
properties:
# ── IDENTITY ────────────────────────────────────────────────────────────────
module_id:
type: string
pattern: "^[a-z][a-z0-9-]*_v[0-9]+_[0-9]+$"
description: >
Stable unique slug. Format: {name}_v{major}_{minor}.
Never reuse IDs. Increment version on breaking structural changes.
Example: "smuggling_ring_v0_1"
display_name:
type: string
minLength: 1
description: "Human-readable title shown in dev/debug tooling."
version:
type: string
pattern: "^[0-9]+\\.[0-9]+$"
description: "Authoring version. Semantic: major.minor."
tier:
type: integer
const: 1
description: "Always 1 for Tier 1 drama modules."
description:
type: string
description: "One-paragraph authoring summary. Not shown in-game."
# ── POOL METADATA ─────────────────────────────────────────────────────────
# Controls how the storyteller includes this module in the per-playthrough pool.
pool:
type: object
required:
- weight
additionalProperties: false
description: "How the storyteller samples this module from the pool."
properties:
weight:
type: integer
minimum: 1
maximum: 10
description: >
Relative selection probability (110). Higher = more likely to be
included in a given playthrough's active module set. Default: 5.
compatible_districts:
type: array
items:
type: string
description: >
District slugs where this module can activate, or omit for "any".
Example: ["sova-transit"]
incompatible_with:
type: array
items:
type: string
pattern: "^[a-z][a-z0-9-]*_v[0-9]+_[0-9]+$"
description: >
Module IDs that cannot run concurrently with this one.
The storyteller will not activate both in the same playthrough.
max_concurrent:
type: integer
minimum: 1
default: 1
description: >
Maximum simultaneous active instances. Almost always 1.
Set to 2+ only for modules designed to stack (rare).
# ── ENTRY CONDITIONS ──────────────────────────────────────────────────────
# All listed conditions must be true for the module to become activatable.
# The storyteller checks these each tick after min_play_ticks.
entry_conditions:
type: object
required:
- activation
additionalProperties: false
description: >
World-state prerequisites. The storyteller activates the module when
ALL conditions are satisfied AND the activation trigger fires.
properties:
world_state:
type: array
items:
$ref: "#/$defs/world_state_condition"
description: "World-state conditions checked each tick."
player:
type: array
items:
$ref: "#/$defs/player_condition"
description: >
Optional player-state conditions. Module can activate without
player engagement — these gate on player-specific world state,
not on player noticing the module.
activation:
type: object
required:
- trigger
additionalProperties: false
description: "How and when activation is evaluated."
properties:
trigger:
type: string
enum:
- proximity # Player comes within range of a key NPC/location
- storyteller_push # Storyteller activates on its own schedule
- player_action # Player performs a specific action
description: "What pushes the module from 'eligible' to 'active'."
min_play_ticks:
type: integer
minimum: 0
description: >
Minimum ticks of game time before this module can activate.
Enforces D-027 success criterion #1: 30 minutes of daily-life
breathing room. At 1 tick/second, 30 minutes ≈ 1800 ticks.
proximity_location:
type: string
description: >
Required when trigger = proximity. Location slug the player
must enter or approach. Example: "maintenance-corridors"
proximity_radius_tiles:
type: integer
minimum: 1
description: >
Required when trigger = proximity. Tile radius around the
location's anchor point.
player_action_required:
type: string
description: >
Required when trigger = player_action. The action that fires
activation. Example: "examine:cargo-manifest"
# ── NPC REQUIREMENTS ──────────────────────────────────────────────────────
# NPC slots this module requires. Each slot is filled at module load time.
# Named bindings resolve to specific authored NPCs; generated bindings
# are filled from the district's generated NPC pool.
npc_requirements:
type: array
minItems: 1
items:
$ref: "#/$defs/npc_slot"
description: >
Module-internal NPC role slots. Roles are referenced by slug throughout
the rest of this document. Hand-authored NPCs use named bindings.
Generated NPCs use constraint-based bindings.
# ── EVENTS ────────────────────────────────────────────────────────────────
# Ordered sequences and unordered event pools the storyteller can fire.
# Sequences are narrative beats in a defined order.
# Pools are events the storyteller can fire in any order when conditions are met.
events:
type: object
additionalProperties: false
description: "Event sequences and pools the storyteller manages."
properties:
sequences:
type: array
items:
$ref: "#/$defs/event_sequence"
description: >
Ordered event sequences. Steps fire in order; the next step
becomes eligible only after the previous one fires.
pools:
type: array
items:
$ref: "#/$defs/event_pool"
description: >
Unordered event pools. The storyteller may fire any eligible
event in the pool when its trigger conditions are met.
# ── OUTCOMES ──────────────────────────────────────────────────────────────
# Resolution states the module can reach. The storyteller checks outcome
# conditions each tick. First matching outcome wins.
# Every module MUST include an expiry outcome.
outcomes:
type: array
minItems: 1
items:
$ref: "#/$defs/outcome"
description: >
Terminal and transitional resolution states. The storyteller checks
these each tick and applies the first matching outcome.
# ── AUTHORING NOTES ───────────────────────────────────────────────────────
notes:
type: string
description: "Authoring-only field. Design rationale, cross-references. Ignored at load time."
dual_lens:
type: object
additionalProperties: false
description: "Authoring-only. How smuggler vs detective experience this module."
properties:
smuggler: { type: string }
detective: { type: string }
# ── SHARED DEFINITIONS ────────────────────────────────────────────────────────
$defs:
# World-state condition types
world_state_condition:
type: object
required:
- type
description: "A single world-state prerequisite for module activation."
oneOf:
- # NPC with the given module role is present in the district
properties:
type: { type: string, const: "npc_present" }
role: { type: string, description: "Module-internal NPC role slug." }
required: [type, role]
additionalProperties: false
- # A specific location is accessible to the player
properties:
type: { type: string, const: "location_accessible" }
location: { type: string, description: "Location slug." }
required: [type, location]
additionalProperties: false
- # Player has NOT yet discovered a specific fact
properties:
type: { type: string, const: "fact_not_known" }
fact_id: { type: string, description: "Fact ID from global/knowledge/." }
required: [type, fact_id]
additionalProperties: false
- # No other Tier 1 module of the given ID is currently active
properties:
type: { type: string, const: "no_active_module" }
module_id: { type: string }
required: [type, module_id]
additionalProperties: false
- # A named fact IS known (module requires precondition awareness)
properties:
type: { type: string, const: "fact_known" }
fact_id: { type: string }
known_by: { type: string, enum: [smuggler, detective, any] }
required: [type, fact_id]
additionalProperties: false
# Player-state condition types
player_condition:
type: object
required:
- type
description: "A player-state prerequisite."
oneOf:
- # Player has reached minimum relationship threshold with an NPC
properties:
type: { type: string, const: "relationship_threshold" }
npc_role: { type: string, description: "Module-internal NPC role." }
min_state:
type: string
enum: [stranger, known, friendly]
description: "Minimum RelationshipState required."
required: [type, npc_role, min_state]
additionalProperties: false
- # Minimum game ticks elapsed
properties:
type: { type: string, const: "min_ticks" }
ticks: { type: integer, minimum: 0 }
required: [type, ticks]
additionalProperties: false
# NPC slot definition
npc_slot:
type: object
required:
- role
- binding
additionalProperties: false
description: >
One NPC slot in the module. Named binding = specific authored NPC.
Generated binding = constraint-matched NPC from district pool.
properties:
role:
type: string
pattern: "^[a-z][a-z0-9-]*$"
description: >
Module-internal role slug. Referenced in events, outcomes, and
triggers. Example: "ring-leader", "ring-member-exiting", "witness"
display_hint:
type: string
description: "Authoring note. What this role is narratively."
binding:
type: string
enum: [named, generated]
description: >
named = resolves to a specific authored NPC (use named_npc).
generated = any district NPC matching the axis constraints.
named_npc:
type: string
pattern: "^npc:[a-z][a-z0-9-]*$"
description: >
Required when binding = named. Short-form NPC canonical ID.
Example: "npc:kael-davan"
axes:
type: array
items:
$ref: "#/$defs/axis_constraint"
description: >
Required when binding = generated. The NPC must satisfy all
listed axis constraints to fill this slot.
must_have_pattern:
type: string
enum: [FRIEND, MIRROR, ANCHOR, GHOST, CATALYST, THRESHOLD, REMNANT, SYSTEM, NOBODY]
description: "Optional: NPC must have this pattern (D-024)."
must_have_motivation:
type: string
enum: [HANDLER, WITNESS, TURNCOAT, CIVILIAN, OPERATOR, SKEPTIC]
description: "Optional: NPC must have this motivation (D-024)."
is_optional:
type: boolean
default: false
description: >
If true, the module can activate without this slot filled.
Optional slots produce degraded but valid module runs.
# NPC axis constraint (used in generated bindings)
axis_constraint:
type: object
required:
- axis
- constraint
additionalProperties: false
properties:
axis:
type: string
enum: [want, secret, relationships, tolerance, routine, information, contentment, personality, tells, skills]
description: "Which NPC axis to constrain (D-024)."
constraint:
type: string
description: >
Constraint expression. Freeform string interpreted by the storyteller.
Convention: "has_{value}", "min_{N}", "not_{value}".
Examples: "has_major_secret", "min_contentment_-3", "not_combat_trained"
# Event sequence
event_sequence:
type: object
required:
- sequence_id
- steps
additionalProperties: false
description: "An ordered sequence of narrative events."
properties:
sequence_id:
type: string
pattern: "^[a-z][a-z0-9_-]*$"
label:
type: string
description:
type: string
steps:
type: array
minItems: 1
items:
$ref: "#/$defs/event_step"
# Unordered event pool
event_pool:
type: object
required:
- pool_id
- events
additionalProperties: false
properties:
pool_id:
type: string
pattern: "^[a-z][a-z0-9_-]*$"
label:
type: string
description:
type: string
events:
type: array
minItems: 1
items:
$ref: "#/$defs/event_step"
# Individual event step
event_step:
type: object
required:
- event_id
- triggers
additionalProperties: false
description: "A single storyteller-managed event with triggers and effects."
properties:
event_id:
type: string
pattern: "^[a-z][a-z0-9_-]*$"
description: "Unique within this module. Used in outcome conditions."
label:
type: string
description:
type: string
description: "What happens narratively when this event fires."
triggers:
type: array
minItems: 1
items:
$ref: "#/$defs/event_trigger"
description: "ANY trigger being true fires this event."
effects:
type: array
items:
$ref: "#/$defs/event_effect"
description: "What changes in the world when this event fires."
once:
type: boolean
default: true
description: "If true, fires only once. If false, may repeat when conditions reset."
sets_flag:
type: string
pattern: "^[a-z][a-z0-9_-]*$"
description: "Module-internal flag set when this event fires. Queryable in outcomes."
# Event trigger conditions
event_trigger:
type: object
required:
- type
description: "A condition that causes an event to fire."
oneOf:
- # Ticks elapsed since module activation
properties:
type: { type: string, const: "ticks_since_activation" }
ticks: { type: integer, minimum: 1 }
required: [type, ticks]
additionalProperties: false
- # Ticks elapsed since a previous event fired
properties:
type: { type: string, const: "ticks_since_event" }
after_event: { type: string }
ticks: { type: integer, minimum: 1 }
required: [type, after_event, ticks]
additionalProperties: false
- # Player enters a location or comes within range of NPC
properties:
type: { type: string, const: "player_proximity" }
target_type: { type: string, enum: [location, npc_role] }
target: { type: string }
radius_tiles: { type: integer, minimum: 1 }
required: [type, target_type, target]
additionalProperties: false
- # Player performs an interaction
properties:
type: { type: string, const: "player_action" }
action:
type: string
enum: [talk, examine, confront, follow, observe]
target_role: { type: string, description: "Module NPC role or location slug." }
required: [type, action, target_role]
additionalProperties: false
- # Player has discovered a specific fact
properties:
type: { type: string, const: "fact_known_by_player" }
fact_id: { type: string }
required: [type, fact_id]
additionalProperties: false
- # A module flag has been set
properties:
type: { type: string, const: "flag_set" }
flag: { type: string }
required: [type, flag]
additionalProperties: false
- # NPC enters a specific mood state
properties:
type: { type: string, const: "npc_mood" }
npc_role: { type: string }
mood:
type: string
enum: [anxious, frustrated, content, suspicious, warm, hostile, relieved, focused]
required: [type, npc_role, mood]
additionalProperties: false
# Event effects
event_effect:
type: object
required:
- type
description: "A world change triggered by an event."
oneOf:
- # NPC deviates from their normal routine
properties:
type: { type: string, const: "npc_routine_deviation" }
npc_role: { type: string }
description: { type: string, description: "What the deviation looks like." }
duration_ticks: { type: integer }
required: [type, npc_role, description]
additionalProperties: false
- # A fact becomes discoverable (moves to Rumoured confidence)
properties:
type: { type: string, const: "fact_becomes_discoverable" }
fact_id: { type: string }
discoverable_by:
type: string
enum: [smuggler, detective, any]
discovery_method:
type: string
description: "How the player can discover this. Authoring note."
required: [type, fact_id, discoverable_by]
additionalProperties: false
- # NPC tell behavior becomes more pronounced
properties:
type: { type: string, const: "tell_intensify" }
npc_role: { type: string }
description: { type: string }
required: [type, npc_role]
additionalProperties: false
- # A module-internal flag is set
properties:
type: { type: string, const: "flag_set" }
flag: { type: string, pattern: "^[a-z][a-z0-9_-]*$" }
required: [type, flag]
additionalProperties: false
- # Something changes about a location
properties:
type: { type: string, const: "location_state" }
location: { type: string }
description: { type: string }
required: [type, location, description]
additionalProperties: false
- # NPC's access to information changes
properties:
type: { type: string, const: "npc_knowledge_update" }
npc_role: { type: string }
fact_id: { type: string }
description: { type: string }
required: [type, npc_role, fact_id]
additionalProperties: false
# Module outcome definition
outcome:
type: object
required:
- outcome_id
- label
- is_terminal
additionalProperties: false
description: >
A resolution state the module can reach. Conditions are checked each tick.
The first matching outcome is applied. is_terminal = true ends the module.
properties:
outcome_id:
type: string
pattern: "^[a-z][a-z0-9_-]*$"
label:
type: string
description:
type: string
description: "What this outcome means narratively."
is_terminal:
type: boolean
description: "If true, this outcome ends the module permanently."
is_expiry:
type: boolean
default: false
description: >
If true, this is the quiet-exit outcome when the player never engages.
Every module must include exactly one expiry outcome.
conditions:
type: object
additionalProperties: false
description: "ALL conditions must be true to reach this outcome."
properties:
facts_known:
type: array
items: { type: string }
description: "Player must know all these facts."
facts_not_known:
type: array
items: { type: string }
description: "Player must NOT know any of these facts."
flags_set:
type: array
items: { type: string }
description: "All these module flags must be set."
flags_not_set:
type: array
items: { type: string }
description: "None of these module flags may be set."
events_fired:
type: array
items: { type: string }
description: "All these events must have fired."
ticks_since_activation:
type: integer
description: "Module has been active for at least this many ticks."
effects:
type: array
items:
$ref: "#/$defs/outcome_effect"
description: "Effects applied when this outcome is reached."
# Outcome-level effects (broader scope than event effects)
outcome_effect:
type: object
required:
- type
oneOf:
- # NPC disposition toward player changes
properties:
type: { type: string, const: "npc_disposition" }
npc_role: { type: string }
shift:
type: string
enum: [hostile, suspicious, neutral, friendly]
description: { type: string }
required: [type, npc_role, shift]
additionalProperties: false
- # Faction reaction
properties:
type: { type: string, const: "faction_reaction" }
faction: { type: string }
reaction:
type: string
enum: [hostile, suspicious, neutral, friendly, grateful]
description: { type: string }
required: [type, faction, reaction]
additionalProperties: false
- # Location becomes restricted or opens up
properties:
type: { type: string, const: "location_access_change" }
location: { type: string }
change:
type: string
enum: [restricted, locked, open]
description: { type: string }
required: [type, location, change]
additionalProperties: false
- # A fact is now permanently known/unknown
properties:
type: { type: string, const: "fact_state" }
fact_id: { type: string }
state:
type: string
enum: [known, hidden, destroyed]
description: { type: string }
required: [type, fact_id, state]
additionalProperties: false
- # NPC leaves the district or changes role
properties:
type: { type: string, const: "npc_exit" }
npc_role: { type: string }
description: { type: string }
required: [type, npc_role]
additionalProperties: false
+92 -35
View File
@@ -8,8 +8,8 @@
//!
//! Run twice, compare side-by-side:
//! ```sh
//! cargo run --bin generator_spike -- --zone rural --seed 42
//! cargo run --bin generator_spike -- --zone industrial --seed 42
//! cargo run --bin generator_spike -- --zone-type rural --seed 42
//! cargo run --bin generator_spike -- --zone-type industrial --seed 42
//! ```
//!
//! The test: can you tell which is which from the output alone?
@@ -22,7 +22,7 @@
//! functions that work without ECS. Full ECS integration is deferred.
use std::collections::{BTreeMap, VecDeque};
use std::path::PathBuf;
use std::path::{Path, PathBuf};
use std::process;
use clap::Parser;
@@ -31,9 +31,9 @@ use rand::SeedableRng;
use rand_chacha::ChaCha20Rng;
use settled_reach_server::npc::blueprint::{
assemble_behaviors, BlueprintRelationship, CultureProfile, CulturalMarkers, CulturalValues,
assemble_behaviors, BlueprintRelationship, CulturalMarkers, CulturalValues, CultureProfile,
NamingConventions, NpcBlueprint, NpcWant, RoleSpec, SocialSiteSpec, SpeechPatterns,
SpikeOutput, ZoneSpec,
SpikeOutput, ZoneSpec, ZoneTypeTemplate,
};
use settled_reach_server::npc::PersonalityTrait;
use settled_reach_server::simulation::rng::SimRng;
@@ -48,9 +48,11 @@ use settled_reach_server::simulation::rng::SimRng;
about = "NPC generator proof-of-life — Sprint 25 (#612)"
)]
struct Args {
/// Zone type to generate ("rural" or "industrial").
/// Zone type to generate.
/// Phase 1 (hardcoded): "rural" or "industrial".
/// Phase 2 (--from-files): a zone-type ID like "rural_agricultural" or "industrial_freight".
#[arg(long)]
zone: String,
zone_type: String,
/// Deterministic seed — same seed produces identical output.
#[arg(long)]
@@ -60,8 +62,8 @@ struct Args {
#[arg(long, default_value = "van-maanens-star")]
culture: String,
/// Phase 2: load zone spec and culture from RON files on disk.
/// Requires content/global/<zone>-zone-spec.ron and content/global/culture-<culture>.ron.
/// Phase 2: load zone-type template and culture from RON files on disk.
/// Requires server/content/global/zone-types/<zone_type>.ron and server/content/global/culture-<culture>.ron.
#[arg(long)]
from_files: bool,
@@ -268,21 +270,60 @@ fn hardcoded_van_maanens_star_culture() -> CultureProfile {
// Phase 2: file loading
// ---------------------------------------------------------------------------
fn load_zone_from_file(content_root: &PathBuf, zone_type: &str) -> ZoneSpec {
let path = content_root.join("global").join(format!("{}-zone-spec.ron", zone_type));
fn load_zone_from_file(content_root: &Path, zone_type: &str) -> ZoneSpec {
let path = content_root
.join("global")
.join("zone-types")
.join(format!("{}.ron", zone_type));
let content = std::fs::read_to_string(&path).unwrap_or_else(|e| {
eprintln!("Error loading zone spec from {:?}: {}", path, e);
eprintln!("Tip: copy team fills this file (ticket #609).");
eprintln!("Error loading zone-type template from {:?}: {}", path, e);
eprintln!("Tip: zone-type RON files live in content/global/zone-types/.");
process::exit(1);
});
ron::from_str(&content).unwrap_or_else(|e| {
eprintln!("Invalid ZoneSpec in {:?}: {}", path, e);
let template: ZoneTypeTemplate = ron::from_str(&content).unwrap_or_else(|e| {
eprintln!("Invalid ZoneTypeTemplate in {:?}: {}", path, e);
process::exit(1);
})
});
zone_type_template_to_zone_spec(template)
}
fn load_culture_from_file(content_root: &PathBuf, culture_id: &str) -> CultureProfile {
let path = content_root.join("global").join(format!("culture-{}.ron", culture_id));
/// Convert a `ZoneTypeTemplate` to a `ZoneSpec` compatible with the spike generation pipeline.
///
/// `ZoneTypeTemplate` roles have no weight (weights live on `LocationSpec`).
/// The spike assigns weight=1 uniformly — all roles equally likely.
fn zone_type_template_to_zone_spec(template: ZoneTypeTemplate) -> ZoneSpec {
ZoneSpec {
zone_type: template.id,
label: template.label,
description: template.description,
economic_level: template.economic_level,
population_density: template.population_density,
roles: template
.roles
.into_iter()
.map(|mut r| {
r.weight = 1; // uniform weight — location specs provide real weights
r
})
.collect(),
social_sites: template
.social_site_types
.into_iter()
.map(|s| SocialSiteSpec {
site_type: s.site_type,
label: String::new(),
roles: s.eligible_roles,
min_npcs: 1,
max_npcs: 6,
})
.collect(),
}
}
fn load_culture_from_file(content_root: &Path, culture_id: &str) -> CultureProfile {
let path = content_root
.join("global")
.join(format!("culture-{}.ron", culture_id));
let content = std::fs::read_to_string(&path).unwrap_or_else(|e| {
eprintln!("Error loading culture from {:?}: {}", path, e);
eprintln!("Tip: copy team fills this file (ticket #610).");
@@ -386,7 +427,11 @@ fn gen_traits(rng: &mut SimRng, culture: &CultureProfile) -> Vec<PersonalityTrai
/// main-RNG word before reaching the Fisher-Yates step.
fn name_pool_seed(seed: u64, zone_type: &str, culture_id: &str) -> u64 {
let mut h: u64 = seed ^ 0xcbf29ce484222325; // FNV-1a offset basis, XOR'd with seed
for b in zone_type.bytes().chain(std::iter::once(b':')).chain(culture_id.bytes()) {
for b in zone_type
.bytes()
.chain(std::iter::once(b':'))
.chain(culture_id.bytes())
{
h ^= b as u64;
h = h.wrapping_mul(0x100000001b3); // FNV-1a prime
}
@@ -549,7 +594,8 @@ const MAX_FILLER_WORDS: usize = 2;
fn gen_cultural_markers(rng: &mut SimRng, culture: &CultureProfile) -> CulturalMarkers {
let filler_count = if culture.speech.filler_words.len() > 1 {
rng.rng.random_range(1_usize..=MAX_FILLER_WORDS.min(culture.speech.filler_words.len()))
rng.rng
.random_range(1_usize..=MAX_FILLER_WORDS.min(culture.speech.filler_words.len()))
} else {
culture.speech.filler_words.len()
};
@@ -595,7 +641,10 @@ fn gen_relationships(
) -> Vec<BlueprintRelationship> {
use settled_reach_server::npc::blueprint::RelationshipValence;
let other_names: Vec<&String> = all_names.iter().filter(|n| n.as_str() != this_name).collect();
let other_names: Vec<&String> = all_names
.iter()
.filter(|n| n.as_str() != this_name)
.collect();
if other_names.is_empty() {
return vec![];
}
@@ -649,9 +698,7 @@ fn generate_npc_blueprint(
let role = pick_role(rng, zone);
let traits = gen_traits(rng, culture);
let want = gen_want(rng, &traits, role);
let pool = behavior_pools
.entry(role.id.clone())
.or_insert_with(VecDeque::new);
let pool = behavior_pools.entry(role.id.clone()).or_default();
let observable_behaviors = gen_behaviors(rng, role, pool);
let cultural_markers = gen_cultural_markers(rng, culture);
// Relationships and Want tells assigned in later passes.
@@ -807,9 +854,15 @@ fn gen_want_tell(rng: &mut SimRng, npc: &NpcBlueprint) -> Option<String> {
AvoidingSomeone => match avoid_target {
Some(name) => {
let options = [
format!("routes around {}'s usual area without looking toward it", name),
format!(
"routes around {}'s usual area without looking toward it",
name
),
format!("takes the long way past {}'s position", name),
format!("keeps a surface or cluster of people between them and {}", name),
format!(
"keeps a surface or cluster of people between them and {}",
name
),
];
options[rng.rng.random_range(0..options.len())].clone()
}
@@ -870,7 +923,10 @@ fn apply_relationship_behaviors(rng: &mut SimRng, npc: &mut NpcBlueprint) {
}
("friend", _) => format!("drifts toward {} between tasks", target),
("subordinate", RelationshipValence::Negative) => {
format!("complies when {} speaks, but doesn't volunteer anything", target)
format!(
"complies when {} speaks, but doesn't volunteer anything",
target
)
}
("subordinate", _) => format!("defers to {} before moving on", target),
("superior", RelationshipValence::Negative) => {
@@ -992,17 +1048,20 @@ fn main() {
// Load zone spec and culture profile
let (zone, culture) = if args.from_files {
eprintln!("Phase 2: loading from files...");
let zone = load_zone_from_file(&args.content_root, &args.zone);
let zone = load_zone_from_file(&args.content_root, &args.zone_type);
let culture = load_culture_from_file(&args.content_root, &args.culture);
(zone, culture)
} else {
// Phase 1: hardcoded stubs
let zone = match args.zone.as_str() {
let zone = match args.zone_type.as_str() {
"rural" => hardcoded_rural_zone(),
"industrial" => hardcoded_industrial_zone(),
other => {
eprintln!("Unknown zone type: '{}'. Use 'rural' or 'industrial'.", other);
eprintln!("(Phase 2 with --from-files supports additional zone types from disk)");
eprintln!(
"Unknown zone type: '{}'. Use 'rural' or 'industrial'.",
other
);
eprintln!("(Phase 2 with --from-files supports zone-type IDs like 'rural_agricultural')");
process::exit(1);
}
};
@@ -1046,7 +1105,7 @@ fn main() {
// Pre-generate unique names using a derived RNG (#628 fix: first-pick bias).
// Name ordering is a pure function of (seed, zone_type, culture) — independent
// of how many words the main RNG has consumed for NPC count or other purposes.
let name_pool = build_name_pool(args.seed, &args.zone, &culture, npc_count);
let name_pool = build_name_pool(args.seed, &args.zone_type, &culture, npc_count);
// Pre-shuffle behavior pools per role (#629 fix: dedup within a zone run).
// D-139: uses assemble_behaviors() when behavior_primitives are present.
@@ -1055,9 +1114,7 @@ fn main() {
// First pass: generate all NPCs (no relationships yet)
let mut npcs: Vec<NpcBlueprint> = name_pool
.into_iter()
.map(|name| {
generate_npc_blueprint(&mut rng, &zone, &culture, name, &mut behavior_pools)
})
.map(|name| generate_npc_blueprint(&mut rng, &zone, &culture, name, &mut behavior_pools))
.collect();
// Collect all names for relationship pass.
+45 -6
View File
@@ -8,8 +8,8 @@
//!
//! ```sh
//! # Via wrapper script (recommended):
//! tooling/validate-ron content/global/zone-identity-spec.example.ron zone
//! tooling/validate-ron content/global/culture-van-maanens-star.example.ron culture
//! tooling/validate-ron server/content/global/zone-identity-spec.example.ron zone
//! tooling/validate-ron server/content/global/culture-van-maanens-star.example.ron culture
//!
//! # Direct:
//! cargo run --bin validate_ron -- <file.ron> <zone|culture>
@@ -19,7 +19,7 @@ use std::process;
use clap::Parser;
use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec};
use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec, ZoneTypeTemplate};
#[derive(Parser)]
#[command(
@@ -29,7 +29,7 @@ use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec};
struct Args {
/// Path to the RON file to validate.
file: String,
/// Schema type: "zone" (ZoneSpec) or "culture" (CultureProfile).
/// Schema type: "zone" (ZoneSpec), "zone_type" (ZoneTypeTemplate), or "culture" (CultureProfile).
schema: String,
}
@@ -48,7 +48,11 @@ fn main() {
"zone" => match ron::from_str::<ZoneSpec>(&content) {
Ok(spec) => {
println!("Valid ZoneSpec: {} ({})", spec.label, spec.zone_type);
println!(" {} roles, {} social sites", spec.roles.len(), spec.social_sites.len());
println!(
" {} roles, {} social sites",
spec.roles.len(),
spec.social_sites.len()
);
let mut warnings = 0;
if spec.roles.is_empty() {
eprintln!(" WARNING: no roles defined — generator will reject this");
@@ -105,8 +109,43 @@ fn main() {
process::exit(1);
}
},
"zone_type" => match ron::from_str::<ZoneTypeTemplate>(&content) {
Ok(template) => {
println!("Valid ZoneTypeTemplate: {} ({})", template.label, template.id);
println!(
" {} roles, {} social site types",
template.roles.len(),
template.social_site_types.len()
);
let mut warnings = 0;
if template.roles.is_empty() {
eprintln!(" WARNING: no roles defined — generator will reject this");
warnings += 1;
}
for role in &template.roles {
if role.behavior_primitives.is_empty() {
eprintln!(
" WARNING: role '{}' has no behavior_primitives",
role.id
);
warnings += 1;
}
}
if warnings > 0 {
process::exit(1);
}
}
Err(e) => {
eprintln!("Invalid ZoneTypeTemplate in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
other => {
eprintln!("Unknown schema type: '{}'. Use 'zone' or 'culture'.", other);
eprintln!(
"Unknown schema type: '{}'. Use 'zone', 'zone_type', or 'culture'.",
other
);
process::exit(1);
}
}