Compare commits

..
18 Commits
Author SHA1 Message Date
jpmschweitzer 0561387de9 Merge remote-tracking branch 'origin/copy' 2026-02-23 23:25:12 +01:00
jpmschweitzerandClaude Opus 4.6 abff69a632 chore(meta): Sprint 17 planning — briefings, workshop brief, close Q-025
- Write sprint 17 briefings for server, client, copy, visual, joint
- Add Knowledge Flow & NPC Boundaries workshop brief
- Close Q-025 (KG cap/eviction not needed at current scale)
- Assign 9 tickets to Sprint 17: Tell

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 23:25:08 +01:00
jpmschweitzerandClaude Opus 4.6 0eafff89e0 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:37:24 +01:00
jpmschweitzerandClaude Opus 4.6 414b8ff0ec docs(decisions): register Q-028 collision-resistant line IDs for auto-gen NPCs
NPC-scoped line ID scheme (D-035) works for hand-authored content but
will produce slug collisions with auto-generated populations (D-029).
Tracked as open question with ticket #544.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:37:12 +01:00
jpmschweitzerandClaude Opus 4.6 56ed2c8810 fix(content): PR #59 review — stale moods, orphaned IDs, tenure, fact_id
Address review comments from Hoshe, Paula, and Miri:
- Update mood vocabulary in 3 docs (line-pool-format.md §6.5,
  style-guide §11, content-directory-structure.md Appendix B) from
  pre-Sprint 14 values to current D-035 enum
- Fix worked example IDs in line-pool-format.md §3.5 to match
  actual the-last-shift kael-davan sequence (_015, _024, _026)
- Fix Section 5.1 restart note to describe multi-location continuity
- Fix style-guide §16 worked example: dock-worker_d_071 → kael-davan_d_076
- Fix stale mood reference in style-guide §16 Step 3
- Fix orphaned the-terminal_d_040 in maintenance-tech.yaml comment
- Fix orphaned the-terminal_d_008/018 in smuggler-inventory.yaml
- Fix Lera tenure: twelve → eighteen years (bar-owner_d_018)
- Fix fact_id: location.surveillance_gaps → investigation.surveillance_gaps
  in ring-operative.yaml (2 occurrences)
- Fix NPC name: Lera Osk → Lera Sessik in bar-owner.yaml comment
- Fix mood line format example in style-guide §5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 22:35:12 +01:00
jpmschweitzerandClaude Sonnet 4.6 892491dea2 chore(meta): update changelog
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:04:55 +01:00
jpmschweitzerandClaude Sonnet 4.6 c056ea1a1c feat(content): migrate line IDs to NPC-scoped namespace (D-035)
Line IDs in all dialogue and monologue pool files renamed from the
old location-scoped format (e.g. the-terminal_d_039) to the new
NPC-scoped format (e.g. kael-davan_d_001) per the D-035 Sprint 15
amendment.

Changes:
- 20 dialogue pool files across 3 locations renamed
- 14 monologue pool files (detective + smuggler) renamed
- Schema descriptions updated in dialogue/monologue schema files
- Authoring style guide and design docs updated with new examples
- Multi-location NPCs (kael-davan, pc-detective, pc-smuggler) given
  globally unique cross-file sequences to satisfy XREF uniqueness check

The location-scoped scheme already caused a collision (the-terminal_d_039
appearing in multiple NPC files) and would not scale to procedurally
generated NPC populations (D-029). Zero content changes — pure ID
substitution.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-23 21:04:43 +01:00
jpmschweitzerandClaude Opus 4.6 41e3b4a9b3 chore(db): backup database after server merge
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:35:46 +01:00
jpmschweitzer 459f0f5e66 Merge remote-tracking branch 'origin/server'
# Conflicts:
#	CHANGELOG.md
2026-02-23 20:35:33 +01:00
jpmschweitzerandClaude Opus 4.6 f331916869 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:32:12 +01:00
jpmschweitzerandClaude Opus 4.6 a4d5f0895b chore(skills): keep sprint team alive through PR review lifecycle
sprint-start: add step 9 (post-work lifecycle) — commit, push, review,
fix-comments loop, approve, then shutdown. Team stays alive until PR is
accepted. Step 8e updated to reference the new lifecycle.

pr-push: add step 9 (team awareness) — don't shut down agents after
pushing, suggest /pr-review next.

pr-review: add step 8 (post-review team actions) — cross-references
sprint-start step 9c for dispatching review comments to agents on
CHANGES_REQUESTED and shutdown on APPROVED.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:31:47 +01:00
jpmschweitzer f2d006fddf Merge remote-tracking branch 'origin/server' 2026-02-23 20:25:39 +01:00
jpmschweitzerandClaude Opus 4.6 2189a9addd fix(simulation): address PR #56 review — pipeline extraction, ActiveDialogue, range check
Tyre #1: Extract run_dialogue_pipeline() shared helper — eliminates ~60
lines of duplication between process_talk_interaction and
process_dialogue_response (L1-L4 pipeline).

Hoshe #1: process_dialogue_response now updates ActiveDialogue with
current tick on follow-up selection — prevents stale started_tick.

Tyre #4: process_dialogue_response now updates InteractionMemory on
follow-up — multi-turn conversations are visible in history.

Hoshe #6 / Tyre #6: handle_dialogue_response adds server-side range
check (CLOSE_RANGE), matching Talk/Confront pattern (D-010 info
boundary).

Hoshe #2: Weighted selection fallback replaced with unreachable!() —
score_line always returns >= 1, so the fallback was dead code.

Hoshe #3: assert!(false, ...) → panic!() in serialization.rs (clippy).

Hoshe #4: SetFacing and TeleportToHub added to roundtrip test.

Hoshe #5: setup_dialogue_response_world inlined (trivial pass-through).

Tyre #2: Doc comment on DialogueCooldownTracker explains per-player-global
design choice (line IDs are NPC-scoped per D-035, no collision risk).

Tyre #3: CONFRONTATION_LINES comment updated with TODO for D-028/D-035
migration.

Tyre #5: DialogueResponse fixture added for cross-language GDScript
testing (input_dialogue_response.msgpack).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:20:00 +01:00
jpmschweitzerandClaude Opus 4.6 19f81d4887 fix(assets): PR #57 review — docs, uid, citation fixes
- Fix client sprite naming in README (files are _64.png, not .png)
- Clarify --path working directory (renderer/ from repo root)
- Remove /sprite-gen reference (skill not in branch yet)
- Add D-043 citation on outline color in render_export.gd
- Add uid to wall_bar_green.tscn for reproducible imports
- Note wall_bar_green has no rendered sprites yet
- Note npc_generic capsule symmetry is by design (D-044)
- Add outline + D-033 tint compatibility guidance

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:17:47 +01:00
jpmschweitzerandClaude Opus 4.6 35594b7f14 chore(meta): update changelog
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:02:04 +01:00
jpmschweitzerandClaude Opus 4.6 885ad21688 feat(assets): add test sprites for NPC and wall at D-019 angle (#541)
Runtime 64px sprites rendered through the new pipeline: generic NPC
(4 directions) and structural wall (4 directions). These unblock
client ticket #540 for sprite camera angle integration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:01:49 +01:00
jpmschweitzerandClaude Opus 4.6 8217ebc0fb feat(assets): 3D sprite render pipeline — camera, lighting, docs (#541)
Update Camera3D to exact D-019 angle (-72.5° from horizontal).
Replace single DirectionalLight with three-point studio rig
(key 1.0, fill 0.4, rim 0.3) for clean silhouettes without
baked shadows. Add generic NPC capsule model (24×32px footprint
per D-044). Document full pipeline spec in renderer/README.md.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:01:43 +01:00
jpmschweitzerandClaude Opus 4.6 efa2dc543f feat(simulation): sprint 16 dialogue server — response handler, trust gossip, variety tracker
Move dialogue system registrations from BridgePlugin to NpcPlugin (#538):
game logic that depends on NPC-layer resources now registers where it
belongs. BridgePlugin retains only wire protocol concerns.

Implement DialogueResponse verb handler (#539): new process_dialogue_response
system runs the full D-028 four-layer pipeline to select follow-up lines
when the player picks a dialogue option. Clears ActiveDialogue when no
candidates remain. Fix latent schedule ambiguity — emit_observation_events
now has explicit .before(advance_tick) constraint.

Verify trust-gated gossip pipeline (#171): confirmed process_talk_interaction
correctly passes KnowledgeConfidence through relationship_to_trust() per
D-075. Added integration tests for Secret-tier access (Friendly+KnowsDetails)
and Surface-only fallback (Friendly+Suspects).

Wire DialogueCooldownTracker into selection (#338): added regression test
confirming no line_id repeats within the 600-tick cooldown window across
10 consecutive Talk interactions.

Closes #538, #539, #171, #338

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 20:01:39 +01:00
80 changed files with 2522 additions and 913 deletions
+8
View File
@@ -134,6 +134,14 @@ Report which tickets were moved to review. Skip tickets that are
already `done`, `review`, `cancelled`, or `backlog` (only transition
`in_progress` → `review`).
### 9. Next steps
If a sprint team is active (you are the team lead), do NOT shut down
agents after pushing. The team should remain alive for PR review and
potential comment fixes.
Suggest: "PR created/updated. Run `/pr-review` to review before merge."
## Arguments
If the user passes arguments (e.g., `/pr-push "my title"`), use them as the
+16
View File
@@ -203,6 +203,22 @@ tea pr close --login schweitz --repo jpmschweitzer/settled-reach <PR_NUMBER>
Gitea does **not** auto-close PRs when you push a local merge — always close
manually with `tea pr close` after pushing.
### 8. Post-review team actions
If a sprint team is active and you are the team lead, handle the
review outcome:
**CHANGES_REQUESTED:**
The sprint-start lifecycle (step 9c) handles dispatching review
comments to agents. After presenting results, remind the lead:
"Review requested changes. Create tasks from the warnings/critical
issues and dispatch to idle agents, then re-push and re-review."
**APPROVED:**
The sprint-start lifecycle (step 9c) handles shutdown. After
presenting results, remind the lead: "Review approved. Proceed with
team shutdown per sprint-start step 9c."
## Tips from practice
- **Vendor code**: Explicitly note vendor code in the prompt so reviewers focus
+54
View File
@@ -325,3 +325,57 @@ Output to the user:
You are now the team lead. Agents work autonomously — monitor via
`TaskList`, communicate via `SendMessage`, and handle blockers as
they arise.
**When all tasks complete:** Do NOT shut down agents. The team stays
alive through the PR review cycle. Follow step 9 (post-work lifecycle).
### 9. Post-work lifecycle
When all tasks are complete (TaskList shows all completed):
#### 9a. Commit and push
Run `/git-commit` to commit all changes, then `/pr-push` to create or
update the PR. Do NOT shut down agents — the team stays alive for review.
#### 9b. Review
Run `/pr-review` to spawn temporary reviewers. Wait for results.
#### 9c. Handle review outcome
**If CHANGES_REQUESTED:**
1. Parse the review comment table (from the Gitea PR comment or the
review output). Extract each warning/critical issue with:
- File path and approximate line
- Severity (critical / warning / suggestion)
- Description
2. Create a task per warning/critical issue:
```
TaskCreate(
subject: "Review: {short description}",
description: "{full issue description from review table, including
file path, severity, and reviewer name}",
activeForm: "Fixing review comment: {short description}"
)
```
Skip suggestion-severity items unless they are trivial (1-line fixes).
3. Dispatch to idle agents: send each a message via SendMessage telling
them to check TaskList for new review-fix tasks. Agents claim and
work tasks as usual.
4. After all review-fix tasks are complete, re-run `/git-commit` then
`/pr-push` to update the PR. Then re-run `/pr-review`.
5. Repeat this loop until review returns APPROVED.
**If APPROVED:**
1. Send `shutdown_request` to all sprint agents.
2. Wait for all `shutdown_response` confirmations.
3. Call `TeamDelete` to clean up.
4. Report: "Sprint {N} {team} complete. PR #{X} approved and ready for
merge on main."
+31
View File
@@ -6,6 +6,37 @@ Format based on [Keep a Changelog](https://keepachangelog.com/).
## [Unreleased]
### Added
- 3D sprite render pipeline — Camera3D at D-019 angle (-72.5° from horizontal), three-point studio lighting rig, orthographic projection, resolution chain 1024→256→64
- Generic NPC capsule model (24×32px footprint per D-044) and structural wall model for pipeline validation
- Test sprites: 8 runtime 64px sprites (NPC + wall × 4 directions) deployed to client/assets/sprites/
- Pipeline documentation (renderer/README.md) — camera spec, lighting rig, resolution chain, model authoring guide
- DialogueResponse verb handler — players pick dialogue options and receive follow-up lines via full D-028 four-layer pipeline (#539)
- Trust-gated gossip verification — integration tests confirm Secret/Real/Surface tier gating per D-075 (#171)
- Line variety tracker wiring — DialogueCooldownTracker prevents repeat lines within 600-tick window (#338)
- DialogueResponse cross-language fixture for GDScript testing
- Sprint team lifecycle through PR review — teams stay alive for commit → push → review → fix loop → approve → shutdown
### Fixed
- PR #59 review: stale mood vocabulary updated in line-pool-format.md, style-guide, and content-directory-structure.md to post-Sprint 14 values
- PR #59 review: orphaned location-scoped IDs in maintenance-tech.yaml comments and smuggler-inventory.yaml cross-references updated to NPC-scoped
- PR #59 review: Lera Sessik tenure corrected from "twelve years" to "eighteen years", NPC header fixed
- PR #59 review: ring-operative.yaml fact_id corrected from `location.surveillance_gaps` to `investigation.surveillance_gaps`
- Dialogue systems moved from BridgePlugin to NpcPlugin — game logic registers where it belongs (#538)
- Schedule ambiguity: emit_observation_events now has explicit .before(advance_tick) constraint
- process_dialogue_response updates ActiveDialogue tick and InteractionMemory on follow-up
- DialogueResponse range check added (CLOSE_RANGE, matching Talk/Confront pattern)
- Weighted selection fallback replaced with unreachable!() — dead code removed
- assert!(false) → panic!() in serialization tests (clippy)
- SetFacing and TeleportToHub added to roundtrip test coverage
### Changed
- Shared run_dialogue_pipeline() helper eliminates ~60 lines of duplication between Talk and DialogueResponse systems
- Dialogue and monologue line IDs migrated from location-scoped (the-terminal_d_039) to NPC-scoped (kael-davan_d_001) namespace — each NPC has an independent sequence per D-035 (#542)
- DialogueCooldownTracker documented as per-player-global by design (NPC-scoped line IDs per D-035 prevent collision)
- CONFRONTATION_LINES marked TODO for migration to D-028/D-035 content pipeline
- pr-push and pr-review skills updated with team lifecycle awareness
## [v0.1.15] — 2026-02-23
### Added
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

@@ -0,0 +1 @@
うtickヘ,ヲaction�DialogueResponseげtarget_entity_id*ォresponse_idーkael-davan_d_001
+14 -14
View File
@@ -253,7 +253,7 @@ Every dialogue line requires 6 structural tags + up to 3 selection tags.
| Tag | Type | Format | Description |
|-----|------|--------|-------------|
| `id` | string | `{location}_{d}_{###}` | Stable line ID. Machine-parseable. Never reused. |
| `id` | string | `{npc-slug}_d_{###}` | Stable line ID. Machine-parseable. Never reused. NPC-scoped per D-035 Sprint 15 amendment. |
| `text` | string | The line | The authored dialogue text |
| `role` | enum | lowercase-slug | Template role, not NPC name. NPCs fill roles at runtime. |
| `access` | list | `[public, insider, ...]` | D-028 Layer 1 hard filter. Who can hear this. |
@@ -265,7 +265,7 @@ Every dialogue line requires 6 structural tags + up to 3 selection tags.
| Tag | Type | Format | Description |
|-----|------|--------|-------------|
| `topic` | list | `[colleague, cargo, ...]` | D-028 Layer 4 weighted selection. Defaults to `[routine]`. |
| `mood` | list | `[comfortable, worried, ...]` | D-028 Layer 4 weighted selection. Defaults to `[comfortable]`. |
| `mood` | list | `[content, anxious, ...]` | D-028 Layer 4 weighted selection. Defaults to untagged (neutral). |
| `tags` | list | freeform strings | Escape hatch for author intent. |
#### Authoring-Only Tags (Not Consumed by Engine)
@@ -352,7 +352,7 @@ Same trigger, different pool. That's how mirror moments work.
| Tag | Type | Description |
|-----|------|-------------|
| `id` | string | `{location}_m_{s\|d}_{###}` — `s` for smuggler, `d` for detective |
| `id` | string | `{npc-slug}_m_{s\|d}_{###}` — `s` for smuggler, `d` for detective. NPC-scoped per D-035 Sprint 15 amendment. |
| `text` | string | The monologue line (160 char max) |
| `character` | enum | `smuggler` or `detective` |
| `trigger` | enum | What causes this line to fire (see trigger types below) |
@@ -600,9 +600,9 @@ See `content/global/enums/topics.yaml`. Note: `crime` is deliberately excluded.
### Moods (8 values)
`fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved`
`anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`
See `content/global/enums/moods.yaml`.
See `content/global/enums/moods.yaml`. (Updated Sprint 14 amendment to D-035.)
### Monologue Triggers (9 values)
@@ -630,7 +630,7 @@ Before submitting any content, run through these checks. Items marked **HARD** w
### Dialogue Lines
- [ ] **HARD:** `id` matches pattern `{location}_d_{###}` (lowercase, underscore-separated)
- [ ] **HARD:** `id` matches pattern `{npc-slug}_d_{###}` (e.g. `kael-davan_d_001`) — NPC-scoped per D-035 Sprint 15 amendment
- [ ] **HARD:** `role` is a valid lowercase slug matching a template role
- [ ] **HARD:** `access` is a non-empty list of valid access tier enums
- [ ] **HARD:** `trust` is one of `surface`, `real`, `secret`
@@ -643,7 +643,7 @@ Before submitting any content, run through these checks. Items marked **HARD** w
### Monologue Lines
- [ ] **HARD:** `id` matches pattern `{location}_m_{s|d}_{###}`
- [ ] **HARD:** `id` matches pattern `{npc-slug}_m_{s|d}_{###}` (e.g. `pc-smuggler_m_s_001`) — NPC-scoped per D-035 Sprint 15 amendment
- [ ] **HARD:** `text` is 160 characters or fewer
- [ ] **HARD:** `trigger` is a valid trigger enum
- [ ] **HARD:** Character partition: smuggler lines in smuggler files, detective in detective files
@@ -755,9 +755,9 @@ All YAML content files must validate against their JSON Schema:
| Content Type | Pattern | Example |
|-------------|---------|---------|
| Dialogue | `{location}_d_{###}` | `transit_d_044` |
| Monologue (smuggler) | `{location}_m_s_{###}` | `transit_m_s_011` |
| Monologue (detective) | `{location}_m_d_{###}` | `transit_m_d_042` |
| Dialogue | `{npc-slug}_d_{###}` | `kael-davan_d_001` |
| Monologue (smuggler) | `{npc-slug}_m_s_{###}` | `pc-smuggler_m_s_001` |
| Monologue (detective) | `{npc-slug}_m_d_{###}` | `pc-detective_m_d_001` |
IDs are stable identifiers. Once assigned, never reused or reassigned. Number gaps are acceptable.
@@ -865,26 +865,26 @@ Every generated line must pass:
### Step 2: Assign Tags
```yaml
- id: transit_d_071
- id: kael-davan_d_076
text: "Morning. Voss kept the rotation thin -- we've got a clear window after 14:00."
role: dock-worker
access: [insider]
trust: real
situation: [shift_start]
topic: [cargo, routine]
mood: [comfortable]
mood: [content]
tags: [ring-operational]
```
### Step 3: Validate
- `id`: `transit_d_071` -- valid pattern.
- `id`: `kael-davan_d_076` -- valid pattern (NPC-scoped per D-035 Sprint 15 amendment). Continues from kael-davan's terminal sequence (_034-_075).
- `role`: `dock-worker` -- valid slug.
- `access: [insider]` -- only ring members hear this. Detective never sees it. Correct.
- `trust: real` -- requires earned trust. Not surface-level small talk. Correct for operational ring dialogue.
- `situation: [shift_start]` -- morning shift context. Correct.
- `topic: [cargo, routine]` -- covers both operational and schedule content. Correct.
- `mood: [comfortable]` -- Phase 1, baseline warm. Correct.
- `mood: [content]` -- Phase 1, baseline warm. Correct.
### Step 4: Dual-Lens Check
+1 -1
View File
@@ -10,7 +10,7 @@
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*_(d|m)_[0-9]{3}$",
"description": "Stable machine-parseable line ID: {template}_{d|m}_{###}. d = dialogue, m = monologue."
"description": "Stable machine-parseable line ID: {npc-slug}_{d|m}_{###} (e.g. kael-davan_d_001, pc-detective_m_d_001). d = dialogue, m = monologue. NPC-scoped per D-035 Sprint 15 amendment — each NPC has an independent sequence starting at _001."
},
"text": {
"type": "string",
+1 -1
View File
@@ -32,7 +32,7 @@
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*_d_[0-9]{3}$",
"description": "Stable line ID: {location_slug}_d_{###}"
"description": "Stable line ID: {npc-slug}_d_{###} (e.g. kael-davan_d_001). NPC-scoped per D-035 Sprint 15 amendment — each NPC has an independent sequence starting at _001."
},
"text": {
"type": "string",
+1 -1
View File
@@ -32,7 +32,7 @@
"id": {
"type": "string",
"pattern": "^[a-z][a-z0-9-]*_m_[sd]_[0-9]{3}$",
"description": "Stable line ID: {location_slug}_m_{s|d}_{###}. s = smuggler, d = detective."
"description": "Stable line ID: {npc-slug}_m_{s|d}_{###} (e.g. pc-smuggler_m_s_001, pc-detective_m_d_001). s = smuggler, d = detective. NPC-scoped per D-035 Sprint 15 amendment — each NPC has an independent sequence starting at _001."
},
"text": {
"type": "string",
@@ -13,7 +13,7 @@ lines:
# RING OPERATIONS — corridor handoffs
# ========================================
- id: maintenance-corridors_d_001
- id: kael-davan_d_001
text: "Package is in the junction locker. Renn picks it up in ten."
role: dock-worker
access: [insider]
@@ -23,7 +23,7 @@ lines:
topic: [cargo]
tags: [kael, ring-ops, renn, operational]
- id: maintenance-corridors_d_002
- id: kael-davan_d_002
text: "B-7 is clear. Camera cycles in forty seconds — move fast."
role: dock-worker
access: [insider]
@@ -35,7 +35,7 @@ lines:
fact_id: investigation.surveillance_gaps
confidence: knows_details
- id: maintenance-corridors_d_003
- id: kael-davan_d_003
text: "Chalk marks are fresh. Renn came through already. We're on schedule."
role: dock-worker
access: [insider]
@@ -45,7 +45,7 @@ lines:
topic: [cargo]
tags: [kael, ring-ops, renn, operational]
- id: maintenance-corridors_d_004
- id: kael-davan_d_004
text: "Don't use the C-section hatch tonight. Maintenance crew's working late."
role: dock-worker
access: [insider]
@@ -60,7 +60,7 @@ lines:
# When the smuggler finds Kael with the unknown contact
# ========================================
- id: maintenance-corridors_d_005
- id: kael-davan_d_005
text: "What are you — wait. How long have you been standing there?"
role: dock-worker
access: [insider]
@@ -70,7 +70,7 @@ lines:
topic: [danger]
tags: [kael, contradiction, phase-3]
- id: maintenance-corridors_d_006
- id: kael-davan_d_006
text: "That was nobody. Maintenance contact. It's handled."
role: dock-worker
access: [insider]
@@ -80,7 +80,7 @@ lines:
topic: [routine]
tags: [kael, deflection, lying, phase-4]
- id: maintenance-corridors_d_007
- id: kael-davan_d_007
text: "I don't have to explain every conversation I have. Drop it."
role: dock-worker
access: [insider]
@@ -94,7 +94,7 @@ lines:
# SECRET — corridor confessions
# ========================================
- id: maintenance-corridors_d_008
- id: kael-davan_d_008
text: "I'm trying to get out. That's all. I just want out."
role: dock-worker
access: [insider]
@@ -104,7 +104,7 @@ lines:
topic: [personal, trust]
tags: [kael, confession, phase-5]
- id: maintenance-corridors_d_009
- id: kael-davan_d_009
text: "Nils will kill me if he finds out. You know that. You know him."
role: dock-worker
access: [insider]
@@ -114,7 +114,7 @@ lines:
topic: [danger, trust]
tags: [kael, confession, nils, phase-5]
- id: maintenance-corridors_d_010
- id: kael-davan_d_010
text: "I found someone who can get me new papers. Off-station. Clean break."
role: dock-worker
access: [insider]
@@ -136,7 +136,7 @@ lines:
# ========================================
# first_meeting: InteractionMemory.count == 0. Any stranger in restricted corridors is a problem.
- id: maintenance-corridors_d_011
- id: kael-davan_d_011
text: "This is a restricted section. Who authorized you back here?"
role: dock-worker
access: [public]
@@ -148,7 +148,7 @@ lines:
notes: "Layer 2 greeting — first meeting in restricted corridors. Immediate challenge. Any stranger back here is suspicious regardless of intent."
# established — peer/smuggler context: count >= 3, RelationshipState: Known/Friendly → Peer/Insider access.
- id: maintenance-corridors_d_012
- id: kael-davan_d_012
text: "You're on time. Clear all the way to the junction."
role: dock-worker
access: [peer, insider]
@@ -160,7 +160,7 @@ lines:
notes: "Layer 2 greeting — established, operational. All business. Kael treats a trusted repeat visitor as crew — no pleasantries, just the essential handoff information."
# established — authority/detective context: count >= 3, RelationshipState: PersonOfInterest → Authority access.
- id: maintenance-corridors_d_013
- id: kael-davan_d_013
text: "You keep showing up back here. You have clearance for this section?"
role: dock-worker
access: [authority]
@@ -172,7 +172,7 @@ lines:
notes: "Layer 2 greeting — repeat with authority figure in restricted section. Professionally defensive. Kael challenges them but can't bar entry if they have clearance."
# post-confrontation: confrontation logged. Kael is cornered on his own operational ground.
- id: maintenance-corridors_d_014
- id: kael-davan_d_014
text: "You followed me here."
role: dock-worker
access: [peer, insider]
@@ -21,7 +21,7 @@ lines:
# SURFACE COVER — sounds like maintenance work
# ==========================================
- id: maintenance-corridors_d_100
- id: ring-operative_d_001
text: "Running maintenance on the B-7 conduit. Could be a while."
role: ring-operative
access: [public, peer]
@@ -35,7 +35,7 @@ lines:
detective: "Maintenance worker. Conduit repair. Routine."
notes: "'Running maintenance' is the canonical ring cover phrase."
- id: maintenance-corridors_d_101
- id: ring-operative_d_002
text: "Bay four access from here is faster than going around. If you're in a hurry."
role: ring-operative
access: [public, peer]
@@ -48,7 +48,7 @@ lines:
smuggler: "He's telling me the route is faster. This is spatial information, not just courtesy."
detective: "Ring operative casually establishes corridor-to-bay-four spatial relationship. Note it."
- id: maintenance-corridors_d_102
- id: ring-operative_d_003
text: "Quiet in here tonight. Good working conditions."
role: ring-operative
access: [public, peer]
@@ -61,7 +61,7 @@ lines:
smuggler: "Dead air confirmed. Meridian's not picking this up."
detective: "Routine corridor comment."
- id: maintenance-corridors_d_103
- id: ring-operative_d_004
text: "Pael was through here earlier. Seal replacement on the ventilation unit."
role: ring-operative
access: [public, peer]
@@ -74,7 +74,7 @@ lines:
smuggler: "Pael was through. Legitimate. Corridor's been active — that means traffic covers us."
detective: "References Pael's B-7 presence without suspicion. Maintenance workers know each other's routes."
- id: maintenance-corridors_d_104
- id: ring-operative_d_005
text: "C-4 access is at the far end. About three minutes from here."
role: ring-operative
access: [public, peer]
@@ -94,7 +94,7 @@ lines:
# OPERATIONAL COORDINATION — insider/real only
# ==========================================
- id: maintenance-corridors_d_105
- id: ring-operative_d_006
text: "Cans are ready. Bay four, per the schedule."
role: ring-operative
access: [insider]
@@ -107,7 +107,7 @@ lines:
smuggler: "Containers cleared. Bay four. We're on."
detective: "Never hears this line."
- id: maintenance-corridors_d_106
- id: ring-operative_d_007
text: "Manifest is clean. Window opens in eighteen minutes."
role: ring-operative
access: [insider]
@@ -120,7 +120,7 @@ lines:
smuggler: "Paperwork's set. Eighteen minutes. That's earlier than planned — adjust."
detective: "Never hears this line."
- id: maintenance-corridors_d_107
- id: ring-operative_d_008
text: "Usual pickup. Route's the same."
role: ring-operative
access: [insider]
@@ -133,7 +133,7 @@ lines:
smuggler: "Same route, same handoff. No variations."
detective: "Never hears this line."
- id: maintenance-corridors_d_108
- id: ring-operative_d_009
text: "The gap's clear. Scan's done at 14:20. Next window's yours."
role: ring-operative
access: [insider]
@@ -146,10 +146,10 @@ lines:
smuggler: "14:20 scan, then the window opens. Twenty-five minutes clear."
detective: "Never hears this line."
knowledge_grant:
fact_id: location.surveillance_gaps
fact_id: investigation.surveillance_gaps
confidence: knows_details
- id: maintenance-corridors_d_109
- id: ring-operative_d_010
text: "Drin was asking about B-7. Twice today."
role: ring-operative
access: [insider]
@@ -165,7 +165,7 @@ lines:
fact_id: investigation.drin_inspection_pattern
confidence: suspects
- id: maintenance-corridors_d_110
- id: ring-operative_d_011
text: "Dead air through here to the C-4 junction. No coverage."
role: ring-operative
access: [insider]
@@ -178,10 +178,10 @@ lines:
smuggler: "Full confirmation. Meridian doesn't reach this section."
detective: "Never hears this line."
knowledge_grant:
fact_id: location.surveillance_gaps
fact_id: investigation.surveillance_gaps
confidence: knows_details
- id: maintenance-corridors_d_111
- id: ring-operative_d_012
text: "Voss confirmed the hold clears at 1400. He'll run the bay himself."
role: ring-operative
access: [insider]
@@ -194,7 +194,7 @@ lines:
smuggler: "Voss is personally running bay four at 1400. That's the signal the window is clear."
detective: "Never hears this line."
- id: maintenance-corridors_d_112
- id: ring-operative_d_013
text: "Kael's on dock. He knows."
role: ring-operative
access: [insider]
@@ -211,7 +211,7 @@ lines:
# PRESSURE / WARNING SIGNALS
# ==========================================
- id: maintenance-corridors_d_113
- id: ring-operative_d_014
text: "Commission eyes on the terminal floor. Not here yet."
role: ring-operative
access: [insider]
@@ -224,7 +224,7 @@ lines:
smuggler: "Detective is visible on the main floor. Corridors are still clear."
detective: "Never hears this line."
- id: maintenance-corridors_d_114
- id: ring-operative_d_015
text: "Hold the run. Drin's in the corridor section."
role: ring-operative
access: [insider]
@@ -237,7 +237,7 @@ lines:
smuggler: "Stop. Drin's in position. The window's closed."
detective: "Never hears this line."
- id: maintenance-corridors_d_115
- id: ring-operative_d_016
text: "Maret filed again. Voss has it. Don't move anything tonight."
role: ring-operative
access: [insider]
@@ -250,7 +250,7 @@ lines:
smuggler: "Maret flagged something. Voss is managing it. Stand down tonight."
detective: "Never hears this line."
- id: maintenance-corridors_d_116
- id: ring-operative_d_017
text: "We're clean. Same time tomorrow."
role: ring-operative
access: [insider]
@@ -267,7 +267,7 @@ lines:
# IF DETECTIVE SOMEHOW ENTERS — surface only
# ==========================================
- id: maintenance-corridors_d_117
- id: ring-operative_d_018
text: "Restricted access back here. Service personnel only."
role: ring-operative
access: [authority, public]
@@ -277,7 +277,7 @@ lines:
mood: [suspicious]
tags: [ring-operative, detective-path, access-denial]
- id: maintenance-corridors_d_118
- id: ring-operative_d_019
text: "I'm running maintenance. If there's a Commission access request, file it through Voss."
role: ring-operative
access: [authority]
@@ -290,7 +290,7 @@ lines:
smuggler: "Never encounters this."
detective: "Operative redirects to Voss. Everyone redirects to Voss. That's structural, not coincidence."
- id: maintenance-corridors_d_119
- id: ring-operative_d_020
text: "Nothing unusual back here. Conduit work. Same every cycle."
role: ring-operative
access: [authority, public]
@@ -305,7 +305,7 @@ lines:
# Surface-only: covers routine corridor presence without operational tells.
# ==========================================
- id: maintenance-corridors_d_120
- id: ring-operative_d_021
text: "Power relay's been cycling weird. I'm watching it."
role: ring-operative
access: [public, peer]
@@ -315,7 +315,7 @@ lines:
mood: [content]
tags: [ring-operative, surface-cover, generation-pass]
- id: maintenance-corridors_d_121
- id: ring-operative_d_022
text: "Pael was supposed to handle B-7. He didn't show."
role: ring-operative
access: [public, peer]
@@ -325,7 +325,7 @@ lines:
mood: [content]
tags: [ring-operative, surface-cover, pael, generation-pass]
- id: maintenance-corridors_d_122
- id: ring-operative_d_023
text: "Watch the floor here. Drain panel's been loose three cycles."
role: ring-operative
access: [public, peer]
@@ -335,7 +335,7 @@ lines:
mood: [content]
tags: [ring-operative, surface-cover, generation-pass]
- id: maintenance-corridors_d_123
- id: ring-operative_d_024
text: "Airflow's better down the B-section. C-section's got the recycler issue."
role: ring-operative
access: [public, peer]
@@ -345,7 +345,7 @@ lines:
mood: [content]
tags: [ring-operative, surface-cover, spatial, generation-pass]
- id: maintenance-corridors_d_124
- id: ring-operative_d_025
text: "You want the service bay, it's left at the junction. Don't go right — that's the restricted access."
role: ring-operative
access: [public, peer]
@@ -358,7 +358,7 @@ lines:
smuggler: "He's directing people away from the route. Social traffic management."
detective: "Operative redirects me away from C-4 access junction instinctively."
- id: maintenance-corridors_d_125
- id: ring-operative_d_026
text: "Long shift. These corridors look the same after a while."
role: ring-operative
access: [public, peer]
@@ -368,7 +368,7 @@ lines:
mood: [frustrated]
tags: [ring-operative, surface-cover, generation-pass]
- id: maintenance-corridors_d_126
- id: ring-operative_d_027
text: "Scan's clear. I checked it an hour ago."
role: ring-operative
access: [public, peer]
@@ -1,7 +1,7 @@
# Dialogue: transit-worker at Maintenance Corridors
# NPC: Tev Osel (Tier 3, NOBODY/CIVILIAN)
# Voice: patient, mildly distracted, confused why anyone is asking him anything.
# KEY LINE: maintenance-corridors_d_001 — sounds like surveillance positioning inquiry
# KEY LINE: transit-worker_d_001 — sounds like surveillance positioning inquiry
# Actually: Tev missed Kosse at the corridor junction and is looking for them
# Ticket: #307 | Sprint: 12
@@ -15,7 +15,7 @@ lines:
# Actually: Tev missed his partner at the regular meeting spot.
# ==========================================
- id: maintenance-corridors_d_001
- id: transit-worker_d_001
text: "You see which way Kosse went? Should've been through here twenty minutes ago."
role: transit-worker
access: [public]
@@ -36,7 +36,7 @@ lines:
# ROUTINE LINES
# ==========================================
- id: maintenance-corridors_d_002
- id: transit-worker_d_002
text: "They shifted the freight route again. Always forget which junction."
role: transit-worker
access: [public]
@@ -46,7 +46,7 @@ lines:
mood: [content]
tags: [tev, atmospheric]
- id: maintenance-corridors_d_003
- id: transit-worker_d_003
text: "Just on break. Out of your way in a minute."
role: transit-worker
access: [public]
@@ -56,7 +56,7 @@ lines:
mood: [content]
tags: [tev, incidental]
- id: maintenance-corridors_d_004
- id: transit-worker_d_004
text: "Late freight route runs long. Can't do anything about it."
role: transit-worker
access: [public]
@@ -66,7 +66,7 @@ lines:
mood: [content]
tags: [tev, atmospheric]
- id: maintenance-corridors_d_005
- id: transit-worker_d_005
text: "Waiting for someone. Shouldn't be long."
role: transit-worker
access: [public]
@@ -1,5 +1,5 @@
# Dialogue: bar-owner at The Last Shift
# NPC: Lera Osk (Tier 2, SYSTEM/OPERATOR)
# NPC: Lera Sessik (Tier 2, ANCHOR/OPERATOR)
# Voice: direct, dry. Runs her bar like a territory. Not political — protective.
# Ring-aware: knows it exists in her bar. Doesn't ask. Doesn't interfere. Not complicit.
# Torek arrangement: she gives him a tab specifically — Commission visibility is useful.
@@ -13,7 +13,7 @@ lines:
# GREETINGS — territorial welcome
# ==========================================
- id: the-last-shift_d_100
- id: bar-owner_d_001
text: "Usual?"
role: bar-owner
access: [public, peer, insider]
@@ -23,7 +23,7 @@ lines:
mood: [content]
tags: [lera, greeting, economy-of-words]
- id: the-last-shift_d_101
- id: bar-owner_d_002
text: "Long shift?"
role: bar-owner
access: [public, peer]
@@ -33,7 +33,7 @@ lines:
mood: [content]
tags: [lera, greeting]
- id: the-last-shift_d_102
- id: bar-owner_d_003
text: "Bar's clean, drink's cold. That's the deal."
role: bar-owner
access: [public]
@@ -43,7 +43,7 @@ lines:
mood: [content]
tags: [lera, greeting, house-rules]
- id: the-last-shift_d_103
- id: bar-owner_d_004
text: "Sess'll get you. I've got something in the back."
role: bar-owner
access: [public, peer]
@@ -57,7 +57,7 @@ lines:
# BAR MANAGEMENT — house rules, operations
# ==========================================
- id: the-last-shift_d_104
- id: bar-owner_d_005
text: "Fights go outside. First and last warning."
role: bar-owner
access: [public, peer, insider]
@@ -67,7 +67,7 @@ lines:
mood: [content]
tags: [lera, house-rules, authority]
- id: the-last-shift_d_105
- id: bar-owner_d_006
text: "Tab closes at end of cycle. Not at end of your memory."
role: bar-owner
access: [public, peer]
@@ -77,7 +77,7 @@ lines:
mood: [content]
tags: [lera, tab, house-rules]
- id: the-last-shift_d_106
- id: bar-owner_d_007
text: "Torek's running a tab. That's fine. Don't ask me why."
role: bar-owner
access: [peer]
@@ -91,7 +91,7 @@ lines:
detective: "Bar owner specifically maintains a tab for a Commission officer. Useful relationship or leverage?"
notes: "Lera's Torek arrangement: she keeps him comfortable because knowing where the Commission officer is drinking is better than not knowing."
- id: the-last-shift_d_107
- id: bar-owner_d_008
text: "Lattice stays off at the bar. Sign's on the wall."
role: bar-owner
access: [public, peer, insider]
@@ -102,7 +102,7 @@ lines:
tags: [lera, lattice-free, house-rules]
notes: "Not legally enforceable — Lera knows this. It's about atmosphere, not compliance."
- id: the-last-shift_d_108
- id: bar-owner_d_009
text: "Grain spirit's back. Limited run. Don't make it last two nights."
role: bar-owner
access: [public, peer, insider]
@@ -116,7 +116,7 @@ lines:
# PEOPLE — Lera knows everyone
# ==========================================
- id: the-last-shift_d_109
- id: bar-owner_d_010
text: "Kael's in the back. If you're looking."
role: bar-owner
access: [peer, insider]
@@ -129,7 +129,7 @@ lines:
smuggler: "Good. She keeps track. That's what she does."
detective: "Lera knows where Kael Davan is without being asked. She tracks her regulars."
- id: the-last-shift_d_110
- id: bar-owner_d_011
text: "Naia's been in early the last few nights. Doesn't say much."
role: bar-owner
access: [peer]
@@ -142,7 +142,7 @@ lines:
smuggler: "Lera noticed Naia's off. She notices everything. She's saying it because she wants someone to do something."
detective: "Bar owner volunteers behavioral change in Naia Tamm. Lera observes her regulars carefully."
- id: the-last-shift_d_111
- id: bar-owner_d_012
text: "Ren? Been here two months. Quiet, pays their tab. That's all I can tell you."
role: bar-owner
access: [public, peer]
@@ -155,7 +155,7 @@ lines:
smuggler: "That's Lera's version of a clean assessment. She'd say more if she suspected something."
detective: "Lera's read on Ren: quiet, no trouble. If Lera's not worried, the baseline is probably accurate."
- id: the-last-shift_d_112
- id: bar-owner_d_013
text: "Tev? Good worker. Three years. Minds their own."
role: bar-owner
access: [public, peer]
@@ -172,7 +172,7 @@ lines:
# RING-ADJACENT — Lera's knowledge edge
# ==========================================
- id: the-last-shift_d_113
- id: bar-owner_d_014
text: "Whatever's happening in the back corner isn't my business. Don't make it my business."
role: bar-owner
access: [insider]
@@ -186,7 +186,7 @@ lines:
detective: "Never hears this line."
notes: "Lera's arrangement: she doesn't ask, she doesn't see, and she keeps things stable. Pragmatic, not complicit."
- id: the-last-shift_d_114
- id: bar-owner_d_015
text: "Kael's got my number. He's been a regular long enough that that means something."
role: bar-owner
access: [peer]
@@ -203,7 +203,7 @@ lines:
# DETECTIVE ENCOUNTER — neutral, unwelcoming
# ==========================================
- id: the-last-shift_d_115
- id: bar-owner_d_016
text: "Commission's welcome. Like everyone else. Bar rules apply."
role: bar-owner
access: [authority, public]
@@ -213,7 +213,7 @@ lines:
mood: [content]
tags: [lera, detective-path, neutral-welcome]
- id: the-last-shift_d_116
- id: bar-owner_d_017
text: "If someone in my bar has something to say to Commission, they'll say it. I don't push that."
role: bar-owner
access: [authority]
@@ -223,8 +223,8 @@ lines:
mood: [content]
tags: [lera, detective-path, institutional-independence]
- id: the-last-shift_d_117
text: "I've run this bar twelve years without a Commission complaint. I'd like to keep it that way."
- id: bar-owner_d_018
text: "I've run this bar eighteen years without a Commission complaint. I'd like to keep it that way."
role: bar-owner
access: [authority]
trust: surface
@@ -233,7 +233,7 @@ lines:
mood: [suspicious]
tags: [lera, detective-path, authority-assertion]
- id: the-last-shift_d_118
- id: bar-owner_d_019
text: "I see a lot. I don't always remember all of it."
role: bar-owner
access: [authority]
@@ -250,7 +250,7 @@ lines:
# ELEVATED TRUST — what Lera actually says
# ==========================================
- id: the-last-shift_d_119
- id: bar-owner_d_020
text: "Naia's been asking questions I can't answer. Someone should."
role: bar-owner
access: [peer, insider]
@@ -267,7 +267,7 @@ lines:
# SIGN-OFF / CLOSING TIME
# ==========================================
- id: the-last-shift_d_120
- id: bar-owner_d_021
text: "Last call. Finish up."
role: bar-owner
access: [public, peer, insider]
@@ -277,7 +277,7 @@ lines:
mood: [content]
tags: [lera, closing]
- id: the-last-shift_d_121
- id: bar-owner_d_022
text: "Bar's closed. Come back next cycle."
role: bar-owner
access: [public, peer, insider]
@@ -287,7 +287,7 @@ lines:
mood: [content]
tags: [lera, closing]
- id: the-last-shift_d_122
- id: bar-owner_d_023
text: "Good night. Stay out of trouble."
role: bar-owner
access: [public, peer, insider]
@@ -301,7 +301,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-last-shift_d_123
- id: bar-owner_d_024
text: "You look tired. Sit."
role: bar-owner
access: [peer, insider]
@@ -311,7 +311,7 @@ lines:
mood: [content]
tags: [lera, greeting, generation-pass]
- id: the-last-shift_d_124
- id: bar-owner_d_025
text: "Same as yesterday?"
role: bar-owner
access: [peer, insider]
@@ -321,7 +321,7 @@ lines:
mood: [content]
tags: [lera, greeting, generation-pass]
- id: the-last-shift_d_125
- id: bar-owner_d_026
text: "New face. Bar rules are on the wall."
role: bar-owner
access: [public]
@@ -331,7 +331,7 @@ lines:
mood: [content]
tags: [lera, greeting, newcomer, generation-pass]
- id: the-last-shift_d_126
- id: bar-owner_d_027
text: "No trouble tonight. I mean it."
role: bar-owner
access: [public, peer, insider]
@@ -341,7 +341,7 @@ lines:
mood: [content]
tags: [lera, house-rules, generation-pass]
- id: the-last-shift_d_127
- id: bar-owner_d_028
text: "Sess'll get you settled. I'm in the back."
role: bar-owner
access: [public, peer]
@@ -351,7 +351,7 @@ lines:
mood: [content]
tags: [lera, redirect, generation-pass]
- id: the-last-shift_d_128
- id: bar-owner_d_029
text: "We close at 2300. Don't make me say it twice."
role: bar-owner
access: [public, peer]
@@ -361,7 +361,7 @@ lines:
mood: [content]
tags: [lera, house-rules, generation-pass]
- id: the-last-shift_d_129
- id: bar-owner_d_030
text: "Good crowd tonight. Quiet enough."
role: bar-owner
access: [peer, insider]
@@ -371,7 +371,7 @@ lines:
mood: [content]
tags: [lera, ambient, generation-pass]
- id: the-last-shift_d_130
- id: bar-owner_d_031
text: "Bar's yours. I've got accounts."
role: bar-owner
access: [public, peer, insider]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — familiar, community register
# ==========================================
- id: the-last-shift_d_300
- id: bar-regular_d_001
text: "Long one today?"
role: bar-regular
access: [public, peer]
@@ -22,7 +22,7 @@ lines:
mood: [content]
tags: [bar-regular, greeting]
- id: the-last-shift_d_301
- id: bar-regular_d_002
text: "You're in early. Good shift or bad shift?"
role: bar-regular
access: [public, peer]
@@ -32,7 +32,7 @@ lines:
mood: [content]
tags: [bar-regular, greeting]
- id: the-last-shift_d_302
- id: bar-regular_d_003
text: "Grain spirit's back. I already had two. Don't judge."
role: bar-regular
access: [public, peer]
@@ -46,7 +46,7 @@ lines:
# TERMINAL FLOOR — off-duty opinions
# ==========================================
- id: the-last-shift_d_303
- id: bar-regular_d_004
text: "Push today. Bay six was backed up until 2000. Voss wasn't happy."
role: bar-regular
access: [public, peer]
@@ -56,7 +56,7 @@ lines:
mood: [content]
tags: [bar-regular, terminal, voss]
- id: the-last-shift_d_304
- id: bar-regular_d_005
text: "Maret flagged another one. Nobody knows what Voss does with them."
role: bar-regular
access: [peer]
@@ -69,7 +69,7 @@ lines:
smuggler: "They're talking about Maret's flags as general knowledge. That's a wider problem than I thought."
detective: "Maret's pattern is common knowledge on the floor. Multiple witnesses, informal. That can be confirmed."
- id: the-last-shift_d_305
- id: bar-regular_d_006
text: "Bay four's been held all week. Three cycles. Nobody's said what's in it."
role: bar-regular
access: [peer]
@@ -82,7 +82,7 @@ lines:
smuggler: "They've noticed bay four. It's becoming visible. Not good."
detective: "Bay four hold is common knowledge at worker level. That means it predates any routine explanation."
- id: the-last-shift_d_306
- id: bar-regular_d_007
text: "Transition window's the worst part of the job. Twenty minutes to hand off and nothing ever lines up."
role: bar-regular
access: [public, peer]
@@ -95,7 +95,7 @@ lines:
smuggler: "The window is their biggest frustration. And their biggest frustration is the ring's biggest asset."
detective: "Workers discuss the transition window as a systematic failure. The gap is known."
- id: the-last-shift_d_307
- id: bar-regular_d_008
text: "Commission audit's coming cycle fifteen. Everyone's running around double-checking everything."
role: bar-regular
access: [public, peer]
@@ -108,7 +108,7 @@ lines:
smuggler: "Cycle fifteen. That's the deadline. Get clean before then."
detective: "Workers are responding to the audit announcement. Behavioral change in the workforce. Note who isn't anxious."
- id: the-last-shift_d_308
- id: bar-regular_d_009
text: "Voss has been on the floor more than usual this cycle. Watching everything."
role: bar-regular
access: [peer]
@@ -125,7 +125,7 @@ lines:
# COMMUNITY — the full life of this district
# ==========================================
- id: the-last-shift_d_309
- id: bar-regular_d_010
text: "Drifters lost again. Four-nil this time. I'm done watching until they rebuild."
role: bar-regular
access: [public, peer]
@@ -135,7 +135,7 @@ lines:
mood: [content]
tags: [bar-regular, sports, drifters]
- id: the-last-shift_d_310
- id: bar-regular_d_011
text: "Ventilation's still the same in Sector 3. Seven years of 'under discussion.' I've stopped expecting anything."
role: bar-regular
access: [public, peer]
@@ -145,49 +145,49 @@ lines:
mood: [content]
tags: [bar-regular, infrastructure, grievance]
- id: the-last-shift_d_311
- id: bar-regular_d_012
text: "You see the missing person notice on the board? Vessels Tamm. I don't know a Vessels Tamm."
role: bar-regular
access: [public, peer]
trust: surface
situation: [routine]
topic: [community, routine]
topic: [personal, routine]
mood: [content]
tags: [bar-regular, vessels-tamm, notice]
dual_lens:
smuggler: "Vessels Tamm. Naia's surname is Tamm. Don't chase it — but don't ignore it either."
detective: "Vessels Tamm notice is circulating casually. People haven't connected it to Naia Tamm yet."
- id: the-last-shift_d_312
- id: bar-regular_d_013
text: "Re-em clinic's backed up four cycles for non-urgent. Main station if it's serious, apparently."
role: bar-regular
access: [public, peer]
trust: surface
situation: [routine]
topic: [community, routine]
topic: [personal, routine]
mood: [content]
tags: [bar-regular, clinic, community]
- id: the-last-shift_d_313
- id: bar-regular_d_014
text: "Lattice upgrades are up eighteen percent this cycle. That's more than my raise."
role: bar-regular
access: [public, peer]
trust: surface
situation: [routine]
topic: [institution, community]
topic: [institution, personal]
mood: [content]
tags: [bar-regular, lattice, economy]
dual_lens:
smuggler: "That's why the waiting list keeps growing. They're providing a service."
detective: "Economic grievance expressed casually. Price pressure is general knowledge. Confirms motive context."
- id: the-last-shift_d_314
- id: bar-regular_d_015
text: "Workers' Association is pushing the handoff protocol again. Voss won't budge."
role: bar-regular
access: [public, peer]
trust: surface
situation: [routine]
topic: [institution, community]
topic: [institution, personal]
mood: [suspicious]
tags: [bar-regular, workers-association, voss, transition-window]
dual_lens:
@@ -198,7 +198,7 @@ lines:
# DETECTIVE ENCOUNTER — community wariness
# ==========================================
- id: the-last-shift_d_315
- id: bar-regular_d_016
text: "You new? Haven't seen you before."
role: bar-regular
access: [public]
@@ -208,7 +208,7 @@ lines:
mood: [suspicious]
tags: [bar-regular, detective-path, recognition]
- id: the-last-shift_d_316
- id: bar-regular_d_017
text: "Commission? I just drink here. Whatever you're after, I probably can't help."
role: bar-regular
access: [authority]
@@ -218,7 +218,7 @@ lines:
mood: [suspicious]
tags: [bar-regular, detective-path, deflection]
- id: the-last-shift_d_317
- id: bar-regular_d_018
text: "Ask Lera. She knows what happens in this bar better than anyone."
role: bar-regular
access: [authority, public]
@@ -232,7 +232,7 @@ lines:
# SIGN-OFF
# ==========================================
- id: the-last-shift_d_318
- id: bar-regular_d_019
text: "Another cycle done. Same tomorrow."
role: bar-regular
access: [public, peer]
@@ -242,7 +242,7 @@ lines:
mood: [content]
tags: [bar-regular, closing]
- id: the-last-shift_d_319
- id: bar-regular_d_020
text: "Safe home. Watch the corridors after 2200."
role: bar-regular
access: [public, peer]
@@ -256,7 +256,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-last-shift_d_320
- id: bar-regular_d_021
text: "Pull up a chair. Floor's full otherwise."
role: bar-regular
access: [public, peer]
@@ -266,7 +266,7 @@ lines:
mood: [content]
tags: [bar-regular, greeting, generation-pass]
- id: the-last-shift_d_321
- id: bar-regular_d_022
text: "You're in late. Voss kept you?"
role: bar-regular
access: [peer]
@@ -276,7 +276,7 @@ lines:
mood: [content]
tags: [bar-regular, greeting, voss, generation-pass]
- id: the-last-shift_d_322
- id: bar-regular_d_023
text: "Just got off. Bay six all night. My back knows it."
role: bar-regular
access: [public, peer]
@@ -286,7 +286,7 @@ lines:
mood: [frustrated]
tags: [bar-regular, greeting, generation-pass]
- id: the-last-shift_d_323
- id: bar-regular_d_024
text: "Drifters are playing Mirren next cycle. Home game. Might actually go."
role: bar-regular
access: [public, peer]
@@ -296,17 +296,17 @@ lines:
mood: [content]
tags: [bar-regular, sports, community, generation-pass]
- id: the-last-shift_d_324
- id: bar-regular_d_025
text: "Sector 3 meeting went nowhere. Same as last time. Same as the time before that."
role: bar-regular
access: [public, peer]
trust: surface
situation: [routine]
topic: [institution, community]
topic: [institution, personal]
mood: [content]
tags: [bar-regular, infrastructure, grievance, generation-pass]
- id: the-last-shift_d_325
- id: bar-regular_d_026
text: "New hire's asking questions on the floor again. Good. Someone should."
role: bar-regular
access: [peer]
@@ -316,7 +316,7 @@ lines:
mood: [content]
tags: [bar-regular, new-hire, community, generation-pass]
- id: the-last-shift_d_326
- id: bar-regular_d_027
text: "Span gate was loud today. Three cycles now. That's not normal."
role: bar-regular
access: [public, peer]
@@ -326,7 +326,7 @@ lines:
mood: [suspicious]
tags: [bar-regular, infrastructure, generation-pass]
- id: the-last-shift_d_327
- id: bar-regular_d_028
text: "Kael's not in tonight. Unusual."
role: bar-regular
access: [peer]
@@ -339,7 +339,7 @@ lines:
smuggler: "Kael's absence is visible. Other people notice too."
detective: "Kael Davan's routine absence is noted socially. Behavioral change is general knowledge."
- id: the-last-shift_d_328
- id: bar-regular_d_029
text: "Lera keeps the grain spirit back until she's sure you're a regular. You must've made the grade."
role: bar-regular
access: [peer]
@@ -349,7 +349,7 @@ lines:
mood: [content]
tags: [bar-regular, lera, humor, generation-pass]
- id: the-last-shift_d_329
- id: bar-regular_d_030
text: "Good night. Don't let the Commission ruin your morning."
role: bar-regular
access: [public, peer]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — service register
# ==========================================
- id: the-last-shift_d_200
- id: bartender_d_001
text: "What'll it be?"
role: bartender
access: [public, peer, insider]
@@ -22,7 +22,7 @@ lines:
mood: [content]
tags: [sess, greeting]
- id: the-last-shift_d_201
- id: bartender_d_002
text: "Back early tonight. Long shift?"
role: bartender
access: [public, peer]
@@ -32,7 +32,7 @@ lines:
mood: [content]
tags: [sess, greeting, social]
- id: the-last-shift_d_202
- id: bartender_d_003
text: "Grain spirit's back. Lera's running it at cycle price for two more days."
role: bartender
access: [public, peer, insider]
@@ -46,7 +46,7 @@ lines:
# BAR OBSERVATIONS — social intelligence
# ==========================================
- id: the-last-shift_d_203
- id: bartender_d_004
text: "Kael's been in most nights this week. Quiet though. Not his usual."
role: bartender
access: [peer]
@@ -59,7 +59,7 @@ lines:
smuggler: "Sess noticed Kael's off. She's reading it as personal. She doesn't know what's underneath."
detective: "Bartender flagging behavioral change in Kael Davan. Consistent with other observations."
- id: the-last-shift_d_204
- id: bartender_d_005
text: "Torek tipped well again tonight. Always does. Nicer than you'd expect for Commission."
role: bartender
access: [peer, public]
@@ -72,7 +72,7 @@ lines:
smuggler: "Torek's making friends with the staff. Not good."
detective: "Bartender views Torek favorably. Torek is cultivating the service staff deliberately."
- id: the-last-shift_d_205
- id: bartender_d_006
text: "Naia was in earlier. Didn't stay. Left something on the table — looked like a note. Lera has it."
role: bartender
access: [peer]
@@ -85,7 +85,7 @@ lines:
smuggler: "Naia left something. Lera has it. Go to Lera."
detective: "Naia Tamm left written material at the bar. Lera is holding it. That's a material lead."
- id: the-last-shift_d_206
- id: bartender_d_007
text: "Ren? Yeah, they're always in the morning crowd. Don't say much. Good for the morning."
role: bartender
access: [public, peer]
@@ -95,7 +95,7 @@ lines:
mood: [content]
tags: [sess, ren, drifter]
- id: the-last-shift_d_207
- id: bartender_d_008
text: "Tev watches the door. Every shift. Lera likes it that way."
role: bartender
access: [peer]
@@ -108,7 +108,7 @@ lines:
smuggler: "Tev watches the door for Lera. Not for anyone else. That's the setup."
detective: "Tev's entrance-monitoring is confirmed as Lera's request. Not operational surveillance."
- id: the-last-shift_d_208
- id: bartender_d_009
text: "Sera was in earlier. She usually stays later. She left when Torek came in."
role: bartender
access: [peer]
@@ -125,7 +125,7 @@ lines:
# SOCIAL / COMMUNITY
# ==========================================
- id: the-last-shift_d_209
- id: bartender_d_010
text: "Drifters lost again. Tev took it hard. Drank two too many."
role: bartender
access: [public, peer]
@@ -135,7 +135,7 @@ lines:
mood: [content]
tags: [sess, tev, sports, community]
- id: the-last-shift_d_210
- id: bartender_d_011
text: "Workers' Association meeting's Thursday. Half the regulars will be here Friday in a mood either way."
role: bartender
access: [public, peer]
@@ -145,7 +145,7 @@ lines:
mood: [content]
tags: [sess, workers-association, community]
- id: the-last-shift_d_211
- id: bartender_d_012
text: "Quiet night. Good for everyone."
role: bartender
access: [public, peer, insider]
@@ -159,7 +159,7 @@ lines:
# DETECTIVE ENCOUNTER — helpful, unfocused
# ==========================================
- id: the-last-shift_d_212
- id: bartender_d_013
text: "Commission comes in sometimes. Not usually this early in the cycle."
role: bartender
access: [authority, public]
@@ -169,7 +169,7 @@ lines:
mood: [content]
tags: [sess, detective-path, observation]
- id: the-last-shift_d_213
- id: bartender_d_014
text: "I know most of the regulars. Ask me anything. I probably know something."
role: bartender
access: [authority, public]
@@ -182,7 +182,7 @@ lines:
smuggler: "Never encounters this line."
detective: "Bartender is cooperative. Useful — but she'll answer questions she doesn't know are questions."
- id: the-last-shift_d_214
- id: bartender_d_015
text: "Lera's the one who knows the real history of this place. I've only been here four cycles."
role: bartender
access: [authority, public]
@@ -196,7 +196,7 @@ lines:
# SIGN-OFF
# ==========================================
- id: the-last-shift_d_215
- id: bartender_d_016
text: "Last round's on the board. After that, Lera calls it."
role: bartender
access: [public, peer, insider]
@@ -206,7 +206,7 @@ lines:
mood: [content]
tags: [sess, closing]
- id: the-last-shift_d_216
- id: bartender_d_017
text: "Good night. Safe walk."
role: bartender
access: [public, peer, insider]
@@ -220,7 +220,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-last-shift_d_217
- id: bartender_d_018
text: "The usual? Or trying something different tonight?"
role: bartender
access: [peer, insider]
@@ -230,7 +230,7 @@ lines:
mood: [content]
tags: [sess, greeting, generation-pass]
- id: the-last-shift_d_218
- id: bartender_d_019
text: "Busy one tonight. Give me a second."
role: bartender
access: [public, peer, insider]
@@ -240,7 +240,7 @@ lines:
mood: [content]
tags: [sess, greeting, generation-pass]
- id: the-last-shift_d_219
- id: bartender_d_020
text: "Good shift or bad shift? Your face says one thing, your order'll confirm it."
role: bartender
access: [peer]
@@ -250,7 +250,7 @@ lines:
mood: [content]
tags: [sess, greeting, social, generation-pass]
- id: the-last-shift_d_220
- id: bartender_d_021
text: "Lera says the specials board is final. Don't ask me to substitute."
role: bartender
access: [public, peer]
@@ -260,7 +260,7 @@ lines:
mood: [content]
tags: [sess, lera, house-rules, generation-pass]
- id: the-last-shift_d_221
- id: bartender_d_022
text: "Torek's in his usual spot. Corner table. Second drink already."
role: bartender
access: [peer]
@@ -270,7 +270,7 @@ lines:
mood: [content]
tags: [sess, torek, observation, generation-pass]
- id: the-last-shift_d_222
- id: bartender_d_023
text: "Ren's been here since this morning. Still on the same drink."
role: bartender
access: [peer]
@@ -280,7 +280,7 @@ lines:
mood: [content]
tags: [sess, ren, observation, generation-pass]
- id: the-last-shift_d_223
- id: bartender_d_024
text: "Drifters game is on the Meridian relay tonight. If anyone's got a feed, that'll be the crowd."
role: bartender
access: [public, peer]
@@ -290,7 +290,7 @@ lines:
mood: [content]
tags: [sess, sports, community, generation-pass]
- id: the-last-shift_d_224
- id: bartender_d_025
text: "Slow night for a change. Enjoy it."
role: bartender
access: [public, peer, insider]
@@ -300,7 +300,7 @@ lines:
mood: [content]
tags: [sess, ambient, generation-pass]
- id: the-last-shift_d_225
- id: bartender_d_026
text: "Tab's cleared. Lera'll be pleased."
role: bartender
access: [peer]
@@ -1,7 +1,7 @@
# Dialogue: day-worker at The Last Shift
# NPC: Ren Tosse (Tier 3, NOBODY/CIVILIAN)
# Voice: casual-observant, asks questions without social friction. Just drifting.
# KEY LINE: the-last-shift_d_001 — sounds like intel gathering on night operations
# KEY LINE: day-worker_d_001 — sounds like intel gathering on night operations
# Actually: asking about after-hours freight work availability
# Ticket: #307 | Sprint: 12
@@ -15,7 +15,7 @@ lines:
# Actually: asking if there's paid after-hours freight work available.
# ==========================================
- id: the-last-shift_d_001
- id: day-worker_d_001
text: "Night rotation still short-handed? Heard the after-hours freight pays decent."
role: day-worker
access: [public]
@@ -36,7 +36,7 @@ lines:
# ROUTINE LINES
# ==========================================
- id: the-last-shift_d_002
- id: day-worker_d_002
text: "Bay seven any better this cycle? The super was a problem last time."
role: day-worker
access: [public]
@@ -46,7 +46,7 @@ lines:
mood: [content]
tags: [ren, job-seeking]
- id: the-last-shift_d_003
- id: day-worker_d_003
text: "Sova's decent. Station layout makes sense once you learn it."
role: day-worker
access: [public]
@@ -56,7 +56,7 @@ lines:
mood: [content]
tags: [ren, atmospheric]
- id: the-last-shift_d_004
- id: day-worker_d_004
text: "Just passing through. Maybe not. Depends on the work."
role: day-worker
access: [public]
@@ -66,7 +66,7 @@ lines:
mood: [content]
tags: [ren, drifter]
- id: the-last-shift_d_005
- id: day-worker_d_005
text: "Cheap drink here's better than most stations I've stopped at."
role: day-worker
access: [public]
@@ -76,7 +76,7 @@ lines:
mood: [content]
tags: [ren, atmospheric]
- id: the-last-shift_d_006
- id: day-worker_d_006
text: "Any idea when the manifest deadline runs? Trying to time when the work window opens."
role: day-worker
access: [public]
@@ -12,7 +12,7 @@ lines:
# SOCIAL — off-shift casual
# ========================================
- id: the-last-shift_d_001
- id: kael-davan_d_015
text: "Saved you a seat. Lera's got the spiced rice tonight."
role: dock-worker
access: [insider, peer]
@@ -22,7 +22,7 @@ lines:
topic: [personal, colleague]
tags: [kael, greeting, phase-1]
- id: the-last-shift_d_002
- id: kael-davan_d_016
text: "First round's mine. Don't argue — you covered me last week."
role: dock-worker
access: [insider, peer]
@@ -32,7 +32,7 @@ lines:
topic: [personal]
tags: [kael, social, phase-1]
- id: the-last-shift_d_003
- id: kael-davan_d_017
text: "Voss tried to dock my overtime again. Lera told him off. Wish I'd seen it."
role: dock-worker
access: [insider, peer]
@@ -42,7 +42,7 @@ lines:
topic: [colleague, routine]
tags: [kael, humor, voss, lera]
- id: the-last-shift_d_004
- id: kael-davan_d_018
text: "Torek's holding court at the bar again. How does he afford to drink like that?"
role: dock-worker
access: [insider, peer]
@@ -55,7 +55,7 @@ lines:
fact_id: investigation.torek_spending_pattern
confidence: suspects
- id: the-last-shift_d_005
- id: kael-davan_d_019
text: "Quiet night. I like these ones. No drama, just people winding down."
role: dock-worker
access: [peer]
@@ -65,7 +65,7 @@ lines:
topic: [personal]
tags: [kael, atmospheric]
- id: the-last-shift_d_006
- id: kael-davan_d_020
text: "Lera asked about my overtime schedule. Told her I'm trying to cut back."
role: dock-worker
access: [insider, peer]
@@ -79,7 +79,7 @@ lines:
# NAIA REFERENCES — personal, unprompted
# ========================================
- id: the-last-shift_d_007
- id: kael-davan_d_021
text: "Naia's meeting me here after her shift. Try not to embarrass me."
role: dock-worker
access: [insider, peer]
@@ -89,7 +89,7 @@ lines:
topic: [personal]
tags: [kael, unprompted, naia-reference]
- id: the-last-shift_d_008
- id: kael-davan_d_022
text: "Naia wants to plan a trip. Off-station. Somewhere with actual sky. Sounds expensive."
role: dock-worker
access: [insider, peer]
@@ -99,7 +99,7 @@ lines:
topic: [personal]
tags: [kael, unprompted, naia-reference]
- id: the-last-shift_d_009
- id: kael-davan_d_023
text: "She worries. I tell her it's just dock work. She doesn't buy it anymore."
role: dock-worker
access: [insider]
@@ -113,7 +113,7 @@ lines:
# RING OPS — bar context, trust-gated
# ========================================
- id: the-last-shift_d_010
- id: kael-davan_d_024
text: "Nils wants to talk. Tomorrow, bay side. Said it's about volume."
role: dock-worker
access: [insider]
@@ -123,7 +123,7 @@ lines:
topic: [danger]
tags: [kael, ring-ops, nils]
- id: the-last-shift_d_011
- id: kael-davan_d_025
text: "Keep it light tonight. Torek's listening and he doesn't know when to stop."
role: dock-worker
access: [insider]
@@ -133,7 +133,7 @@ lines:
topic: [danger, colleague]
tags: [kael, ring-ops, torek, caution]
- id: the-last-shift_d_012
- id: kael-davan_d_026
text: "Lera knows more than she lets on. She won't say anything — but don't test it."
role: dock-worker
access: [insider]
@@ -150,7 +150,7 @@ lines:
# POST-CONTRADICTION — at the bar, damaged trust
# ========================================
- id: the-last-shift_d_013
- id: kael-davan_d_027
text: "I saved your seat. ...Force of habit."
role: dock-worker
access: [insider, peer]
@@ -160,7 +160,7 @@ lines:
topic: [personal]
tags: [kael, contaminated-trust, phase-5]
- id: the-last-shift_d_014
- id: kael-davan_d_028
text: "Naia keeps asking why I'm quiet. I told her work stress. Not wrong."
role: dock-worker
access: [insider]
@@ -170,7 +170,7 @@ lines:
topic: [personal]
tags: [kael, contaminated-trust, naia-reference, phase-5]
- id: the-last-shift_d_015
- id: kael-davan_d_029
text: "We're good, right? You and me. We're still good."
role: dock-worker
access: [insider]
@@ -187,7 +187,7 @@ lines:
# ========================================
# first_meeting: InteractionMemory.count == 0. RelationshipState: Unknown → Public access only.
- id: the-last-shift_d_016
- id: kael-davan_d_030
text: "Evening. Friend of someone's, or first time at Lera's?"
role: dock-worker
access: [public]
@@ -199,7 +199,7 @@ lines:
notes: "Layer 2 greeting — first meeting at the bar. Kael is curious about a stranger but not suspicious. Off-shift, looser than at the terminal."
# established — peer/smuggler context: count >= 3, RelationshipState: Known/Friendly → Peer/Insider access.
- id: the-last-shift_d_017
- id: kael-davan_d_031
text: "You made it. I was starting to think something came up."
role: dock-worker
access: [peer, insider]
@@ -211,7 +211,7 @@ lines:
notes: "Layer 2 greeting — established, bar context. Warm, they were expected. Kael was watching for them."
# established — authority/detective context: count >= 3, RelationshipState: PersonOfInterest → Authority access.
- id: the-last-shift_d_018
- id: kael-davan_d_032
text: "Still in the district. Thought you'd be done by now."
role: dock-worker
access: [authority]
@@ -223,7 +223,7 @@ lines:
notes: "Layer 2 greeting — repeat with authority figure, bar context. Politely questioning their continued presence. The bar is supposed to be off-limits from work."
# post-confrontation: confrontation in InteractionMemory.notable_events. NPC mood shifted to suspicious/frustrated.
- id: the-last-shift_d_019
- id: kael-davan_d_033
text: "You came back."
role: dock-worker
access: [peer, insider]
@@ -9,7 +9,7 @@ location: the-last-shift
role: commission-liaison
lines:
- id: the-last-shift_d_204
- id: pc-detective_d_001
text: "Good evening. Off the clock, more or less. Enjoying the local hospitality."
role: commission-liaison
access: [public]
@@ -19,7 +19,7 @@ lines:
topic: [personal]
tags: [pc-detective, greeting, public-tier]
- id: the-last-shift_d_205
- id: pc-detective_d_002
text: "Ms. Venn recommended the ale. She was right. This district has its charms."
role: commission-liaison
access: [public]
@@ -28,7 +28,7 @@ lines:
topic: [routine, colleague]
tags: [pc-detective, social, public-tier, sera]
- id: the-last-shift_d_206
- id: pc-detective_d_003
text: "Just unwinding. The review can wait until morning. Have a good evening."
role: commission-liaison
access: [public]
@@ -9,7 +9,7 @@ location: the-last-shift
role: dock-worker
lines:
- id: the-last-shift_d_201
- id: pc-smuggler_d_001
text: "Evening. Just having a drink with friends. Can I help you?"
role: dock-worker
access: [public]
@@ -19,7 +19,7 @@ lines:
topic: [personal]
tags: [pc-smuggler, greeting, public-tier]
- id: the-last-shift_d_202
- id: pc-smuggler_d_002
text: "Lera's ale is the one good thing about a ten-hour shift. Don't let anyone tell you different."
role: dock-worker
access: [public]
@@ -29,7 +29,7 @@ lines:
topic: [routine]
tags: [pc-smuggler, social, public-tier]
- id: the-last-shift_d_203
- id: pc-smuggler_d_003
text: "Off the clock, thanks. Whatever it is, it can wait until morning."
role: dock-worker
access: [authority]
@@ -38,7 +38,7 @@ lines:
# Phase 3-5: same warmth, but the detective is now watching
# ============================================================
- id: the-last-shift_d_101
- id: sera-venn_d_001
text: "There you are. Grab a seat — Lera's doing that thing with the grain spirit again. Don't ask, just trust me and order the ale."
role: bar-regular
access: [peer]
@@ -48,7 +48,7 @@ lines:
mood: [warm, content]
tags: [greeting, phase-1, establishes-warmth]
- id: the-last-shift_d_102
- id: sera-venn_d_002
text: "Corner booth's free. I saved it — force of habit."
role: bar-regular
access: [peer]
@@ -58,7 +58,7 @@ lines:
mood: [warm]
tags: [greeting, phase-1, spatial-anchor]
- id: the-last-shift_d_103
- id: sera-venn_d_003
text: "You look like you've been reading manifests all day. Sit. I'll get you something that isn't recycled air."
role: bar-regular
access: [peer]
@@ -68,7 +68,7 @@ lines:
mood: [warm, content]
tags: [greeting, phase-1-2]
- id: the-last-shift_d_104
- id: sera-venn_d_004
text: "Evening. Same booth, same drink?"
role: bar-regular
access: [peer]
@@ -78,7 +78,7 @@ lines:
mood: [content]
tags: [greeting, phase-2-5, repeatable]
- id: the-last-shift_d_105
- id: sera-venn_d_005
text: "Good timing. Naia was here earlier — you just missed her."
role: bar-regular
access: [peer]
@@ -88,7 +88,7 @@ lines:
mood: [content]
tags: [greeting, phase-2, naia-reference]
- id: the-last-shift_d_106
- id: sera-venn_d_006
text: "Grab a seat. Quiet night so far — Lera's in a good mood, which means the ale's fresh."
role: bar-regular
access: [peer, public]
@@ -98,7 +98,7 @@ lines:
mood: [content]
tags: [greeting, phase-1-3]
- id: the-last-shift_d_107
- id: sera-venn_d_007
text: "Hey. Long day?"
role: bar-regular
access: [peer]
@@ -108,7 +108,7 @@ lines:
mood: [frustrated]
tags: [greeting, phase-3-5, post-pattern, shorter]
- id: the-last-shift_d_108
- id: sera-venn_d_008
text: "Same time, same place. You're becoming a regular."
role: bar-regular
access: [peer, public]
@@ -123,7 +123,7 @@ lines:
# What Sera says during normal bar evenings
# ============================================================
- id: the-last-shift_d_110
- id: sera-venn_d_009
text: "The Last Shift. Official name. Everyone calls it Lera's, though. Eighteen years behind that counter."
role: bar-regular
access: [peer]
@@ -133,7 +133,7 @@ lines:
mood: [content]
tags: [orientation, district-background]
- id: the-last-shift_d_111
- id: sera-venn_d_010
text: "See the card game in the corner? That's Harek's table. Security officer, off-duty. Harmless — just loud."
role: bar-regular
access: [peer]
@@ -143,7 +143,7 @@ lines:
mood: [content]
tags: [orientation, npc-introduction]
- id: the-last-shift_d_112
- id: sera-venn_d_011
text: "Most of the after-shift crowd comes from the Terminal. Dock workers, scheduler types. They drink hard and leave early."
role: bar-regular
access: [peer]
@@ -153,7 +153,7 @@ lines:
mood: [content]
tags: [orientation, district-background]
- id: the-last-shift_d_113
- id: sera-venn_d_012
text: "Sova's not bad, once you know the rhythms. Shift change is when things move. Between shifts is when you can breathe."
role: bar-regular
access: [peer]
@@ -163,7 +163,7 @@ lines:
mood: [content]
tags: [setting-atmosphere]
- id: the-last-shift_d_114
- id: sera-venn_d_013
text: "I do lattice calibration, mostly. Commission-standard equipment checks. Relay nodes, terminal diagnostics. Exciting stuff."
role: bar-regular
access: [peer]
@@ -173,7 +173,7 @@ lines:
mood: [content]
tags: [self-introduction, institutional-background]
- id: the-last-shift_d_115
- id: sera-venn_d_014
text: "I transferred here fourteen months ago. Border-system relay posting before this. Sova was supposed to be the quiet one."
role: bar-regular
access: [peer]
@@ -183,7 +183,7 @@ lines:
mood: [content]
tags: [backstory, phase-1]
- id: the-last-shift_d_116
- id: sera-venn_d_015
text: "Lera remembers every regular's order. Eighteen years of that. Don't ask how she does it — she'll just pour you one and prove it."
role: bar-regular
access: [peer, public]
@@ -193,7 +193,7 @@ lines:
mood: [warm]
tags: [community-texture]
- id: the-last-shift_d_117
- id: sera-venn_d_016
text: "The shift crowd clears out by 19:00. After that it's just us — regulars, a few stragglers. The good hours."
role: bar-regular
access: [peer]
@@ -203,7 +203,7 @@ lines:
mood: [content]
tags: [setting-atmosphere, routine]
- id: the-last-shift_d_118
- id: sera-venn_d_017
text: "You adapting to station gravity yet? Point-nine-three standard takes a week to stop noticing."
role: bar-regular
access: [peer]
@@ -213,7 +213,7 @@ lines:
mood: [content]
tags: [setting-detail, phase-1]
- id: the-last-shift_d_119
- id: sera-venn_d_018
text: "I like this booth. Corner. You can see the door and the bar. Force of institutional habit, I suppose."
role: bar-regular
access: [peer]
@@ -232,7 +232,7 @@ lines:
# --- Surface trust ---
- id: the-last-shift_d_120
- id: sera-venn_d_019
text: "The Commission runs maintenance checks quarterly. I handle the field side — equipment calibration, relay diagnostics. Standard."
role: bar-regular
access: [peer, authority]
@@ -242,7 +242,7 @@ lines:
mood: [content]
tags: [institutional-background]
- id: the-last-shift_d_121
- id: sera-venn_d_020
text: "Naia teaches the station kids. Primary education, mostly. She's good at it — patient. The kids adore her."
role: bar-regular
access: [peer]
@@ -252,7 +252,7 @@ lines:
mood: [warm]
tags: [naia-reference, phase-2]
- id: the-last-shift_d_122
- id: sera-venn_d_021
text: "The district runs on freight. Terminal handles most of it — container processing, manifest filing. Bread and butter."
role: bar-regular
access: [peer, public]
@@ -262,7 +262,7 @@ lines:
mood: [content]
tags: [district-context]
- id: the-last-shift_d_123
- id: sera-venn_d_022
text: "I don't know the dock workers well. I'm Commission, they're logistics. Different worlds, same station."
role: bar-regular
access: [peer, authority]
@@ -272,7 +272,7 @@ lines:
mood: [content]
tags: [deflection-surface, distance-from-hub]
- id: the-last-shift_d_124
- id: sera-venn_d_023
text: "Most people here are decent. Working shifts, paying bills, coming to Lera's. It's a community."
role: bar-regular
access: [peer, public]
@@ -282,7 +282,7 @@ lines:
mood: [content]
tags: [community-framing]
- id: the-last-shift_d_125
- id: sera-venn_d_024
text: "I can look things up if you need. Commission terminals access compliance records, shift logs, equipment histories. Part of the job."
role: bar-regular
access: [peer, authority]
@@ -294,7 +294,7 @@ lines:
# --- Real trust ---
- id: the-last-shift_d_130
- id: sera-venn_d_025
text: "Naia's been worried lately. About Kael — her partner. He's a dock worker at the Terminal. Late nights, evasive answers. She confided in me."
role: bar-regular
access: [peer]
@@ -307,7 +307,7 @@ lines:
fact_id: "relationship.kael_naia_connection"
confidence: "knows_of"
- id: the-last-shift_d_131
- id: sera-venn_d_026
text: "The Commission kiosk on Level 3 logs every compliance check. If someone ran an unauthorized scan, it would show up there. Hypothetically."
role: bar-regular
access: [peer, authority]
@@ -317,7 +317,7 @@ lines:
mood: [focused]
tags: [institutional-access, phase-2-3, self-incriminating-hint]
- id: the-last-shift_d_132
- id: sera-venn_d_027
text: "I worry about Naia. She doesn't deserve whatever's happening. She just wants Kael to come home at a reasonable hour."
role: bar-regular
access: [peer]
@@ -327,7 +327,7 @@ lines:
mood: [frustrated, anxious]
tags: [motivation-reveal, phase-2-3]
- id: the-last-shift_d_133
- id: sera-venn_d_028
text: "Compliance data goes back eighteen months on the Commission systems. Anyone with field-tech access could cross-reference shift records with manifest filings."
role: bar-regular
access: [peer, authority]
@@ -340,7 +340,7 @@ lines:
fact_id: "investigation.manifest_discrepancy"
confidence: "suspects"
- id: the-last-shift_d_134
- id: sera-venn_d_029
text: "I'm not an investigator. I calibrate equipment and file reports. But I notice things. Patterns in the data, mostly."
role: bar-regular
access: [peer]
@@ -350,7 +350,7 @@ lines:
mood: [focused]
tags: [self-awareness, capability-reveal]
- id: the-last-shift_d_135
- id: sera-venn_d_030
text: "You know me. I wouldn't sit on something if I thought it mattered. But not everything that looks irregular IS irregular."
role: bar-regular
access: [peer]
@@ -360,7 +360,7 @@ lines:
mood: [anxious]
tags: [deflection-real-tier, phase-4, the-lie]
- id: the-last-shift_d_136
- id: sera-venn_d_031
text: "Some of the regulars here have hard lives. Grey economy, tight margins. I'm not judging. I'm just a field tech who likes the ale."
role: bar-regular
access: [peer]
@@ -372,7 +372,7 @@ lines:
# --- Secret trust ---
- id: the-last-shift_d_140
- id: sera-venn_d_032
text: "I ran a compliance check. Unauthorized. Six weeks ago. Naia was so worried about Kael — I thought I could help. I found manifest discrepancies tied to his shifts."
role: bar-regular
access: [peer]
@@ -385,7 +385,7 @@ lines:
fact_id: "investigation.manifest_discrepancy"
confidence: "knows_details"
- id: the-last-shift_d_141
- id: sera-venn_d_033
text: "If I report it, Naia's life falls apart. Kael gets investigated. Maybe arrested. She didn't ask me to find this. I did it on my own."
role: bar-regular
access: [peer]
@@ -395,7 +395,7 @@ lines:
mood: [anxious]
tags: [motivation-confession, phase-5]
- id: the-last-shift_d_142
- id: sera-venn_d_034
text: "The irregularities correlate with Torek Lintar's inspection shifts. That's why I can't — I can't be near him. He doesn't know I know."
role: bar-regular
access: [peer]
@@ -408,7 +408,7 @@ lines:
fact_id: "investigation.shift_mismatch"
confidence: "knows_details"
- id: the-last-shift_d_143
- id: sera-venn_d_035
text: "I don't know what you're looking for. I told you everything I know. If you're asking whether I'm hiding something — I'm a field tech, not an analyst."
role: bar-regular
access: [peer, authority]
@@ -418,7 +418,7 @@ lines:
mood: [suspicious, anxious]
tags: [denial-under-pressure, phase-4-5, the-wall]
- id: the-last-shift_d_144
- id: sera-venn_d_036
text: "Every tip I gave you was real. The district background, the shift patterns, the regulars. I didn't steer you wrong. I just... didn't give you everything."
role: bar-regular
access: [peer]
@@ -434,7 +434,7 @@ lines:
# Phase 1-2: genuinely helpful. Phase 3-5: overcompensation.
# ============================================================
- id: the-last-shift_d_150
- id: sera-venn_d_037
text: "Oh — before I forget. The dock scheduler, Maret? She's sharp. If you need freight records context, she's the one to ask."
role: bar-regular
access: [peer]
@@ -447,7 +447,7 @@ lines:
fact_id: "relationship.trust_network"
confidence: "suspects"
- id: the-last-shift_d_151
- id: sera-venn_d_038
text: "I checked the relay calibration logs for the Terminal this morning. Nothing flagged. But the data throughput was higher than standard — more manifests processed than usual."
role: bar-regular
access: [peer, authority]
@@ -457,7 +457,7 @@ lines:
mood: [focused]
tags: [unprompted, overcompensation, phase-3-4]
- id: the-last-shift_d_152
- id: sera-venn_d_039
text: "I asked around — quietly — about the shift rotation schedule. Voss has been adjusting it more than usual this cycle. Might mean nothing."
role: bar-regular
access: [peer]
@@ -467,7 +467,7 @@ lines:
mood: [focused, frustrated]
tags: [unprompted, overcompensation, phase-3-4]
- id: the-last-shift_d_153
- id: sera-venn_d_040
text: "The maintenance corridor access logs are Commission-maintained. I can pull them if you need. Just say the word."
role: bar-regular
access: [peer, authority]
@@ -477,7 +477,7 @@ lines:
mood: [content]
tags: [unprompted, information-offer, overcompensation, phase-3-5]
- id: the-last-shift_d_154
- id: sera-venn_d_041
text: "Naia mentioned Kael came home late again last night. Third time this week. She's trying not to make it a thing."
role: bar-regular
access: [peer]
@@ -490,7 +490,7 @@ lines:
fact_id: "relationship.kael_naia_connection"
confidence: "knows_details"
- id: the-last-shift_d_155
- id: sera-venn_d_042
text: "You should know — Lera watches out for her regulars. If people have been asking about you, she'd know. She just won't volunteer it."
role: bar-regular
access: [peer]
@@ -505,7 +505,7 @@ lines:
# Phase 3-4: avoidance excuses, topic deflection, controlled evasion
# ============================================================
- id: the-last-shift_d_160
- id: sera-venn_d_043
text: "Actually, I should head out. Early shift tomorrow — calibration rounds start at 07:30."
role: bar-regular
access: [peer, public]
@@ -515,7 +515,7 @@ lines:
mood: [content]
tags: [avoidance-excuse-1, torek-trigger, phase-3]
- id: the-last-shift_d_161
- id: sera-venn_d_044
text: "Sorry — headache coming on. Station air does that sometimes. I'll catch you tomorrow."
role: bar-regular
access: [peer, public]
@@ -525,7 +525,7 @@ lines:
mood: [content]
tags: [avoidance-excuse-2, torek-trigger, phase-3]
- id: the-last-shift_d_162
- id: sera-venn_d_045
text: "I need to check something at the Commission kiosk. Forgot to file a diagnostic report. I'll be quick — or not. You know how the system is."
role: bar-regular
access: [peer]
@@ -535,7 +535,7 @@ lines:
mood: [content]
tags: [avoidance-excuse-3, torek-trigger, phase-3]
- id: the-last-shift_d_163
- id: sera-venn_d_046
text: "Torek? He's just loud. I don't love loud drunks. Nothing personal — I just like a quieter evening."
role: bar-regular
access: [peer]
@@ -545,7 +545,7 @@ lines:
mood: [content]
tags: [torek-deflection, phase-4, the-deflection]
- id: the-last-shift_d_164
- id: sera-venn_d_047
text: "Manifests? I wouldn't know — I'm on the equipment side, not the freight side. Different department entirely."
role: bar-regular
access: [peer, authority]
@@ -555,7 +555,7 @@ lines:
mood: [content]
tags: [topic-deflection, cargo-avoidance, phase-3-5]
- id: the-last-shift_d_165
- id: sera-venn_d_048
text: "Anyway — did you try the ale? Lera got a new supply run in. Much better than last week's batch."
role: bar-regular
access: [peer]
@@ -565,7 +565,7 @@ lines:
mood: [content]
tags: [redirect, warmth-recovery, phase-4]
- id: the-last-shift_d_166
- id: sera-venn_d_049
text: "I'm just a field tech. I maintain equipment. I don't track people or shipments — that's your department."
role: bar-regular
access: [peer, authority]
@@ -575,7 +575,7 @@ lines:
mood: [suspicious]
tags: [deflection-firm, phase-4-5, the-wall]
- id: the-last-shift_d_167
- id: sera-venn_d_050
text: "Let's talk about something else. How's the investigation going — generally? Making progress?"
role: bar-regular
access: [peer]
@@ -591,7 +591,7 @@ lines:
# Same surface warmth, but calibrated. Trust is contaminated.
# ============================================================
- id: the-last-shift_d_170
- id: sera-venn_d_051
text: "Same booth. Drink's on me tonight."
role: bar-regular
access: [peer]
@@ -601,7 +601,7 @@ lines:
mood: [warm]
tags: [post-discovery, surface-warmth, phase-5]
- id: the-last-shift_d_171
- id: sera-venn_d_052
text: "I pulled the relay diagnostic data you asked about. Clean. No anomalies in the terminal equipment this quarter."
role: bar-regular
access: [peer, authority]
@@ -611,7 +611,7 @@ lines:
mood: [content]
tags: [post-discovery, overcompensation, phase-5]
- id: the-last-shift_d_172
- id: sera-venn_d_053
text: "You've been quiet tonight. Everything all right with the case?"
role: bar-regular
access: [peer]
@@ -621,7 +621,7 @@ lines:
mood: [frustrated]
tags: [post-discovery, awareness, phase-5]
- id: the-last-shift_d_173
- id: sera-venn_d_054
text: "I know you're good at what you do. That's why I'm glad you're here. This district needed someone thorough."
role: bar-regular
access: [peer]
@@ -631,7 +631,7 @@ lines:
mood: [warm, anxious]
tags: [post-discovery, genuine-but-contaminated, phase-5]
- id: the-last-shift_d_174
- id: sera-venn_d_055
text: "If there's anything I can help with — anything at all — you just ask. You know that."
role: bar-regular
access: [peer]
@@ -650,7 +650,7 @@ lines:
# first_meeting (detective path): RelationshipState Unknown → Public/Authority access.
# Sera clocks them as Commission immediately. Makes them welcome anyway.
- id: the-last-shift_d_175
- id: sera-venn_d_056
text: "Commission? Don't worry, I don't bite. I'm Sera. Corner booth's better — you can see the whole room."
role: bar-regular
access: [public, authority]
@@ -663,7 +663,7 @@ lines:
# established (detective path): count >= 3, RelationshipState: Known/Friendly → Peer access.
# Warmer, more personal. Sera has been waiting for them.
- id: the-last-shift_d_176
- id: sera-venn_d_057
text: "I was hoping you'd come in. Saved the corner booth. How'd today go?"
role: bar-regular
access: [peer]
@@ -676,7 +676,7 @@ lines:
# first_meeting (smuggler path): smuggler is dock crew, Sera knows the district crowd.
# Peer/insider access from the start — Sera has social network, not institutional distance.
- id: the-last-shift_d_177
- id: sera-venn_d_058
text: "Terminal crew? Grab a seat. Kael usually sits in the back if you're looking for him."
role: bar-regular
access: [peer, insider]
@@ -689,7 +689,7 @@ lines:
# post-confrontation: confrontation logged. Sera's surface warmth is present but qualified.
# She doesn't know where they stand. The welcome is real but uncertain.
- id: the-last-shift_d_178
- id: sera-venn_d_059
text: "You're back. I wasn't sure you would be."
role: bar-regular
access: [peer]
@@ -13,7 +13,7 @@ lines:
# ARRIVALS — checking in
# ==========================================
- id: the-terminal_d_500
- id: courier_d_001
text: "Route's clear. Moving on schedule."
role: courier
access: [public, peer]
@@ -23,7 +23,7 @@ lines:
mood: [content]
tags: [renn, greeting, operational]
- id: the-terminal_d_501
- id: courier_d_002
text: "Manifest for bay three. Needs a scheduler stamp."
role: courier
access: [public, peer]
@@ -33,7 +33,7 @@ lines:
mood: [content]
tags: [renn, greeting, procedural]
- id: the-terminal_d_502
- id: courier_d_003
text: "Inbound from Havara. Three cans, all matched."
role: courier
access: [public, peer]
@@ -47,7 +47,7 @@ lines:
# SURFACE ROUTINE — low-information, on-script
# ==========================================
- id: the-terminal_d_503
- id: courier_d_004
text: "Not staying. Just the drop."
role: courier
access: [public, peer]
@@ -57,7 +57,7 @@ lines:
mood: [content]
tags: [renn, operational, brief]
- id: the-terminal_d_504
- id: courier_d_005
text: "Paperwork's clean. You can check."
role: courier
access: [public, peer, authority]
@@ -67,7 +67,7 @@ lines:
mood: [content]
tags: [renn, procedural, documentation]
- id: the-terminal_d_505
- id: courier_d_006
text: "Pickup's scheduled at 1600. I'll be back."
role: courier
access: [public, peer]
@@ -77,7 +77,7 @@ lines:
mood: [content]
tags: [renn, operational]
- id: the-terminal_d_506
- id: courier_d_007
text: "Corridor's clear from my end. No delays."
role: courier
access: [public, peer]
@@ -94,7 +94,7 @@ lines:
# RING-ADJACENT — insider/real trust only
# ==========================================
- id: the-terminal_d_507
- id: courier_d_008
text: "Documentation's handled. Bay four, per the note."
role: courier
access: [insider]
@@ -107,7 +107,7 @@ lines:
smuggler: "Renn's confirming the paperwork trail. Bay four, as arranged."
detective: "Never hears this line."
- id: the-terminal_d_508
- id: courier_d_009
text: "Transit stamp's current. Nothing flagged at the gate."
role: courier
access: [insider]
@@ -117,7 +117,7 @@ lines:
mood: [focused]
tags: [renn, ring-ops, documentation]
- id: the-terminal_d_509
- id: courier_d_010
text: "Pickup window is the same as last cycle. Twenty minutes."
role: courier
access: [insider]
@@ -130,7 +130,7 @@ lines:
smuggler: "Twenty minutes. Transition window confirmation."
detective: "Never hears this line."
- id: the-terminal_d_510
- id: courier_d_011
text: "Tell Voss the hold note clears at 1400."
role: courier
access: [insider]
@@ -147,7 +147,7 @@ lines:
# DETECTIVE ENCOUNTER — surface cooperation
# ==========================================
- id: the-terminal_d_511
- id: courier_d_012
text: "Licensed freight courier. Manifest's available on request."
role: courier
access: [authority]
@@ -157,7 +157,7 @@ lines:
mood: [content]
tags: [renn, detective-path, procedural]
- id: the-terminal_d_512
- id: courier_d_013
text: "I drop documentation. What's inside the containers isn't my business."
role: courier
access: [authority]
@@ -170,7 +170,7 @@ lines:
smuggler: "That's the answer. Clean and correct."
detective: "Classic courier deflection. Legally accurate. Operationally suspicious."
- id: the-terminal_d_513
- id: courier_d_014
text: "I've been running this route for two years. Nothing unusual."
role: courier
access: [authority, public]
@@ -184,7 +184,7 @@ lines:
# DEPARTURES
# ==========================================
- id: the-terminal_d_514
- id: courier_d_015
text: "That's the drop. I'm out."
role: courier
access: [public, peer]
@@ -194,7 +194,7 @@ lines:
mood: [content]
tags: [renn, closing]
- id: the-terminal_d_515
- id: courier_d_016
text: "Next run's tomorrow. Early."
role: courier
access: [public, peer]
@@ -208,7 +208,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-terminal_d_516
- id: courier_d_017
text: "Two stops today. First one's done."
role: courier
access: [public, peer]
@@ -218,7 +218,7 @@ lines:
mood: [content]
tags: [renn, greeting, generation-pass]
- id: the-terminal_d_517
- id: courier_d_018
text: "Outbound for Havara. Needs a bay assignment."
role: courier
access: [public, peer]
@@ -228,7 +228,7 @@ lines:
mood: [content]
tags: [renn, greeting, procedural, generation-pass]
- id: the-terminal_d_518
- id: courier_d_019
text: "Gate's backed up. Running twenty minutes late."
role: courier
access: [public, peer]
@@ -238,7 +238,7 @@ lines:
mood: [content]
tags: [renn, delay, generation-pass]
- id: the-terminal_d_519
- id: courier_d_020
text: "Paperwork's in order. Same as always."
role: courier
access: [public, peer, authority]
@@ -248,7 +248,7 @@ lines:
mood: [content]
tags: [renn, procedural, generation-pass]
- id: the-terminal_d_520
- id: courier_d_021
text: "Quick turnaround today. Back out by 1300."
role: courier
access: [public, peer]
@@ -258,7 +258,7 @@ lines:
mood: [content]
tags: [renn, operational, generation-pass]
- id: the-terminal_d_521
- id: courier_d_022
text: "Signed off on bay three. That's me done here."
role: courier
access: [public, peer]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — shift arrival
# ==========================================
- id: the-terminal_d_200
- id: dock-worker_d_001
text: "Push today. Bay six's backed up."
role: dock-worker
access: [public, peer]
@@ -22,7 +22,7 @@ lines:
mood: [content]
tags: [dock-worker, greeting]
- id: the-terminal_d_201
- id: dock-worker_d_002
text: "Late night last cycle. Bay eight ran until 2300."
role: dock-worker
access: [public, peer]
@@ -32,7 +32,7 @@ lines:
mood: [frustrated]
tags: [dock-worker, greeting]
- id: the-terminal_d_202
- id: dock-worker_d_003
text: "Voss is on already. Floor's locked down tight."
role: dock-worker
access: [peer]
@@ -49,7 +49,7 @@ lines:
# FLOOR TALK — routine, cargo, observation
# ==========================================
- id: the-terminal_d_203
- id: dock-worker_d_004
text: "Bay four's been on hold three days. Nobody says why."
role: dock-worker
access: [public, peer]
@@ -62,7 +62,7 @@ lines:
smuggler: "He doesn't know what's in it. Just annoyed at the backup."
detective: "Worker notices Bay 4 hold as unusual. Not suspicious — just a worker tracking the floor."
- id: the-terminal_d_204
- id: dock-worker_d_005
text: "Manifest looks clean. Three inbounds, two outbounds. Light day."
role: dock-worker
access: [public, peer]
@@ -72,7 +72,7 @@ lines:
mood: [content]
tags: [dock-worker, manifest]
- id: the-terminal_d_205
- id: dock-worker_d_006
text: "Maret's been at that terminal all morning. Never good."
role: dock-worker
access: [peer]
@@ -85,7 +85,7 @@ lines:
smuggler: "Maret reviewing something. If she flags a discrepancy, Voss'll sit on it. He always does."
detective: "Workers have noticed Maret's behavior. Whatever she found, it's not small."
- id: the-terminal_d_206
- id: dock-worker_d_007
text: "Drin ran bay six again last cycle. Third inspection this week."
role: dock-worker
access: [public, peer]
@@ -98,7 +98,7 @@ lines:
smuggler: "Drin on six while bay four stays held. Scheduled or arranged?"
detective: "Workers notice inspection distribution. Drin on six, bay four untouched."
- id: the-terminal_d_207
- id: dock-worker_d_008
text: "Transition window's twice as long with Commission eyes on the floor."
role: dock-worker
access: [peer]
@@ -111,7 +111,7 @@ lines:
smuggler: "Commission presence during transition. Window's compromised."
detective: "Workers slow down when Commission is visible. Normal — or trained."
- id: the-terminal_d_208
- id: dock-worker_d_009
text: "Good crew this cycle. Linn and Pael are both on."
role: dock-worker
access: [public, peer]
@@ -121,7 +121,7 @@ lines:
mood: [content]
tags: [dock-worker, pael, community]
- id: the-terminal_d_209
- id: dock-worker_d_010
text: "Cargo mark's off by forty, according to Maret. Voss logged it and moved on."
role: dock-worker
access: [peer]
@@ -137,7 +137,7 @@ lines:
fact_id: investigation.oversight_gap_pattern
confidence: suspects
- id: the-terminal_d_210
- id: dock-worker_d_011
text: "C-4 containers haven't been checked in three days. Not our problem, apparently."
role: dock-worker
access: [peer]
@@ -154,7 +154,7 @@ lines:
# COMMUNITY / CASUAL
# ==========================================
- id: the-terminal_d_211
- id: dock-worker_d_012
text: "You heading to the Last Shift after? Lera's got the grain spirit back."
role: dock-worker
access: [public, peer]
@@ -164,7 +164,7 @@ lines:
mood: [content]
tags: [dock-worker, social, the-last-shift]
- id: the-terminal_d_212
- id: dock-worker_d_013
text: "Workers' Association meeting's cycle twenty-two. Handoff protocol's back on the agenda."
role: dock-worker
access: [public, peer]
@@ -174,7 +174,7 @@ lines:
mood: [content]
tags: [dock-worker, workers-association, community]
- id: the-terminal_d_213
- id: dock-worker_d_014
text: "Drifters lost again. Four-three. Pael seemed to take it personally."
role: dock-worker
access: [public, peer]
@@ -188,7 +188,7 @@ lines:
# DETECTIVE ENCOUNTER — deflection, redirect
# ==========================================
- id: the-terminal_d_214
- id: dock-worker_d_015
text: "Manifest questions go to the scheduler. I'm floor-side."
role: dock-worker
access: [authority, public]
@@ -198,7 +198,7 @@ lines:
mood: [suspicious]
tags: [dock-worker, detective-path, redirect]
- id: the-terminal_d_215
- id: dock-worker_d_016
text: "Commission's asked before. Same answers. We run a clean floor."
role: dock-worker
access: [authority]
@@ -208,7 +208,7 @@ lines:
mood: [suspicious]
tags: [dock-worker, detective-path, deflection]
- id: the-terminal_d_216
- id: dock-worker_d_017
text: "Voss handles discrepancies. That's above my level."
role: dock-worker
access: [authority, peer]
@@ -222,7 +222,7 @@ lines:
# PEER CANDOR — elevated trust
# ==========================================
- id: the-terminal_d_217
- id: dock-worker_d_018
text: "Maret's going to push it. She doesn't leave a number sitting."
role: dock-worker
access: [peer]
@@ -235,7 +235,7 @@ lines:
smuggler: "Maret's not going to drop it. That's the problem."
detective: "Workers think Maret will force the issue. She may be the break."
- id: the-terminal_d_218
- id: dock-worker_d_019
text: "Voss runs a tight floor. Maybe too tight, if you take my meaning."
role: dock-worker
access: [peer]
@@ -252,7 +252,7 @@ lines:
# SIGN-OFF
# ==========================================
- id: the-terminal_d_219
- id: dock-worker_d_020
text: "Bay's clear. Good shift."
role: dock-worker
access: [public, peer]
@@ -262,7 +262,7 @@ lines:
mood: [content]
tags: [dock-worker, closing]
- id: the-terminal_d_220
- id: dock-worker_d_021
text: "Night crew's here. We're done."
role: dock-worker
access: [public, peer]
@@ -276,7 +276,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-terminal_d_221
- id: dock-worker_d_022
text: "Early start. Bay two's already loaded."
role: dock-worker
access: [public, peer]
@@ -286,7 +286,7 @@ lines:
mood: [content]
tags: [dock-worker, greeting, generation-pass]
- id: the-terminal_d_222
- id: dock-worker_d_023
text: "Air recyclers are loud this morning. Must be the outer ring issue again."
role: dock-worker
access: [public, peer]
@@ -296,7 +296,7 @@ lines:
mood: [content]
tags: [dock-worker, station, generation-pass]
- id: the-terminal_d_223
- id: dock-worker_d_024
text: "Pael's in early. Something must be broken in B-7 again."
role: dock-worker
access: [peer]
@@ -306,7 +306,7 @@ lines:
mood: [content]
tags: [dock-worker, pael, generation-pass]
- id: the-terminal_d_224
- id: dock-worker_d_025
text: "Running light today. Maybe I'll actually make it to the Last Shift before close."
role: dock-worker
access: [public, peer]
@@ -316,7 +316,7 @@ lines:
mood: [content]
tags: [dock-worker, social, generation-pass]
- id: the-terminal_d_225
- id: dock-worker_d_026
text: "Bay three's got a scan hold. Not my problem. Not my bay."
role: dock-worker
access: [public, peer]
@@ -326,7 +326,7 @@ lines:
mood: [content]
tags: [dock-worker, scan-hold, generation-pass]
- id: the-terminal_d_226
- id: dock-worker_d_027
text: "New hire's back. Asking questions again. Good kid."
role: dock-worker
access: [peer]
@@ -336,7 +336,7 @@ lines:
mood: [content]
tags: [dock-worker, new-hire, generation-pass]
- id: the-terminal_d_227
- id: dock-worker_d_028
text: "Handoff's in ten. Don't disappear on me."
role: dock-worker
access: [peer]
@@ -346,7 +346,7 @@ lines:
mood: [content]
tags: [dock-worker, handoff, generation-pass]
- id: the-terminal_d_228
- id: dock-worker_d_029
text: "Span gate cycled early. All inbounds are going to be late. Plan around it."
role: dock-worker
access: [public, peer]
@@ -356,7 +356,7 @@ lines:
mood: [suspicious]
tags: [dock-worker, span-gate, generation-pass]
- id: the-terminal_d_229
- id: dock-worker_d_030
text: "Linn's not in today. We're running two short on bay six."
role: dock-worker
access: [public, peer]
@@ -366,7 +366,7 @@ lines:
mood: [frustrated]
tags: [dock-worker, staffing, generation-pass]
- id: the-terminal_d_230
- id: dock-worker_d_031
text: "Lunch is in the break room. Don't touch the one with Pael's name on it. He will notice."
role: dock-worker
access: [public, peer]
@@ -376,7 +376,7 @@ lines:
mood: [content]
tags: [dock-worker, pael, humor, generation-pass]
- id: the-terminal_d_231
- id: dock-worker_d_032
text: "Overtime's posted if you want it. Bay eight, 1800 to 2200."
role: dock-worker
access: [public, peer]
@@ -386,7 +386,7 @@ lines:
mood: [content]
tags: [dock-worker, overtime, generation-pass]
- id: the-terminal_d_232
- id: dock-worker_d_033
text: "Off on time today. First time this cycle."
role: dock-worker
access: [public, peer]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — Phase 1: Comfort (0-10 min)
# ========================================
- id: the-terminal_d_001
- id: kael-davan_d_034
text: "There you are. Good — I was starting to wonder."
role: dock-worker
access: [insider, peer]
@@ -22,7 +22,7 @@ lines:
topic: [colleague]
tags: [kael, greeting, phase-1]
- id: the-terminal_d_002
- id: kael-davan_d_035
text: "Morning. Freight's stacked clean today. Should be a smooth one."
role: dock-worker
access: [insider, peer]
@@ -32,7 +32,7 @@ lines:
topic: [routine]
tags: [kael, greeting, phase-1]
- id: the-terminal_d_003
- id: kael-davan_d_036
text: "Voss has the schedule posted already. Keen today. That's suspicious."
role: dock-worker
access: [insider, peer]
@@ -42,7 +42,7 @@ lines:
topic: [colleague, routine]
tags: [kael, greeting, humor]
- id: the-terminal_d_004
- id: kael-davan_d_037
text: "Hey. Grab a coffee before Voss finds something to complain about."
role: dock-worker
access: [insider, peer]
@@ -52,7 +52,7 @@ lines:
topic: [colleague]
tags: [kael, greeting, phase-1]
- id: the-terminal_d_005
- id: kael-davan_d_038
text: "You look like you slept about as well as I did."
role: dock-worker
access: [peer]
@@ -63,7 +63,7 @@ lines:
tags: [kael, greeting, casual]
# Public greeting for detective / outsiders
- id: the-terminal_d_006
- id: kael-davan_d_039
text: "Can I help you? If you're looking for the supervisor, Voss is by the schedule board."
role: dock-worker
access: [public, authority]
@@ -77,7 +77,7 @@ lines:
# SHIFT TALK — routine dialogue
# ========================================
- id: the-terminal_d_007
- id: kael-davan_d_040
text: "Bay three's running behind. If we don't clear it by shift end, Voss is going to lose it."
role: dock-worker
access: [insider, peer]
@@ -87,7 +87,7 @@ lines:
topic: [routine, cargo]
tags: [kael, operational]
- id: the-terminal_d_008
- id: kael-davan_d_041
text: "Manifest says this one's forty kilos over. Logging error, probably."
role: dock-worker
access: [public, peer]
@@ -100,7 +100,7 @@ lines:
fact_id: investigation.manifest_discrepancy
confidence: suspects
- id: the-terminal_d_009
- id: kael-davan_d_042
text: "Night shift left the dock in decent shape for once. Miracles happen."
role: dock-worker
access: [insider, peer]
@@ -110,7 +110,7 @@ lines:
topic: [routine]
tags: [kael, humor]
- id: the-terminal_d_010
- id: kael-davan_d_043
text: "Loading arm three's been grinding all week. Someone should file a maintenance ticket."
role: dock-worker
access: [public, peer]
@@ -120,7 +120,7 @@ lines:
topic: [routine]
tags: [kael, environmental]
- id: the-terminal_d_011
- id: kael-davan_d_044
text: "Maret rerouted the bay four queue. Tight schedule today."
role: dock-worker
access: [insider, peer]
@@ -130,7 +130,7 @@ lines:
topic: [cargo, colleague]
tags: [kael, operational, maret]
- id: the-terminal_d_012
- id: kael-davan_d_045
text: "Shift transition in twenty. After that it's Drin's problem."
role: dock-worker
access: [insider, peer]
@@ -140,7 +140,7 @@ lines:
topic: [routine]
tags: [kael, operational]
- id: the-terminal_d_013
- id: kael-davan_d_046
text: "Naia's got a school event tonight. I'm heading home straight after shift."
role: dock-worker
access: [insider, peer]
@@ -150,7 +150,7 @@ lines:
topic: [personal]
tags: [kael, naia-reference, casual]
- id: the-terminal_d_014
- id: kael-davan_d_047
text: "New hire started on bay seven. Keen. Reminds me of my first week."
role: dock-worker
access: [public, peer]
@@ -164,7 +164,7 @@ lines:
# RING COORDINATION — insider, real trust
# ========================================
- id: the-terminal_d_015
- id: kael-davan_d_048
text: "Container 4471 is flagged. I'll reroute it during shift transition."
role: dock-worker
access: [insider]
@@ -174,7 +174,7 @@ lines:
topic: [cargo]
tags: [kael, ring-ops, operational, phase-2]
- id: the-terminal_d_016
- id: kael-davan_d_049
text: "Voss kept the window clear. Twenty minutes, maybe twenty-five."
role: dock-worker
access: [insider]
@@ -184,7 +184,7 @@ lines:
topic: [cargo, routine]
tags: [kael, ring-ops, operational, phase-2]
- id: the-terminal_d_017
- id: kael-davan_d_050
text: "Renn's on the corridor side. Package moves when I give the signal."
role: dock-worker
access: [insider]
@@ -194,7 +194,7 @@ lines:
topic: [cargo]
tags: [kael, ring-ops, renn, phase-2]
- id: the-terminal_d_018
- id: kael-davan_d_051
text: "Manifest's clean on my end. Maret processed it without questions."
role: dock-worker
access: [insider]
@@ -204,7 +204,7 @@ lines:
topic: [cargo]
tags: [kael, ring-ops, maret, phase-2]
- id: the-terminal_d_019
- id: kael-davan_d_052
text: "Nils wants higher volume next cycle. I told him we're already tight."
role: dock-worker
access: [insider]
@@ -214,7 +214,7 @@ lines:
topic: [danger, trust]
tags: [kael, ring-ops, nils, tension, phase-2]
- id: the-terminal_d_020
- id: kael-davan_d_053
text: "Drin waved through bay four without checking. At least he's reliable."
role: dock-worker
access: [insider]
@@ -227,7 +227,7 @@ lines:
fact_id: investigation.drin_inspection_pattern
confidence: knows_details
- id: the-terminal_d_021
- id: kael-davan_d_054
text: "Keep an eye on Maret. She's been looking at the manifests longer than usual."
role: dock-worker
access: [insider]
@@ -237,7 +237,7 @@ lines:
topic: [colleague, danger]
tags: [kael, ring-ops, maret, caution, phase-2]
- id: the-terminal_d_022
- id: kael-davan_d_055
text: "There's a sealed one in the batch tonight. Nils says components. Don't open it."
role: dock-worker
access: [insider]
@@ -251,7 +251,7 @@ lines:
# TRUST-GATED — deep trust, secret tier
# ========================================
- id: the-terminal_d_023
- id: kael-davan_d_056
text: "Some days I wonder how long we can keep this up."
role: dock-worker
access: [insider]
@@ -261,7 +261,7 @@ lines:
topic: [trust, personal]
tags: [kael, vulnerability, friend-arc]
- id: the-terminal_d_024
- id: kael-davan_d_057
text: "Naia asked me again last night. Where I go. Why I'm late. I hate lying to her."
role: dock-worker
access: [insider]
@@ -271,7 +271,7 @@ lines:
topic: [personal, trust]
tags: [kael, naia-reference, vulnerability]
- id: the-terminal_d_025
- id: kael-davan_d_058
text: "If something happens to me — look out for Naia. Promise me that."
role: dock-worker
access: [insider]
@@ -285,7 +285,7 @@ lines:
# UNPROMPTED — Kael volunteers or warns
# ========================================
- id: the-terminal_d_026
- id: kael-davan_d_059
text: "Heads up — Voss is in a mood. Keep your head down this shift."
role: dock-worker
access: [insider, peer]
@@ -295,7 +295,7 @@ lines:
topic: [colleague, danger]
tags: [kael, unprompted, warning]
- id: the-terminal_d_027
- id: kael-davan_d_060
text: "I'm buying at Lera's after shift. You're coming. No arguments."
role: dock-worker
access: [insider, peer]
@@ -305,7 +305,7 @@ lines:
topic: [personal, colleague]
tags: [kael, unprompted, social]
- id: the-terminal_d_028
- id: kael-davan_d_061
text: "Torek's been running his mouth at the bar. You might want to have a word."
role: dock-worker
access: [insider]
@@ -319,7 +319,7 @@ lines:
# DEFLECTION — Phase 2-4: Post-contradiction
# ========================================
- id: the-terminal_d_029
- id: kael-davan_d_062
text: "It's nothing. Just work stuff."
role: dock-worker
access: [insider, peer]
@@ -329,7 +329,7 @@ lines:
topic: [personal]
tags: [kael, deflection, phase-4]
- id: the-terminal_d_030
- id: kael-davan_d_063
text: "Don't worry about it. I've got it handled."
role: dock-worker
access: [insider, peer]
@@ -339,7 +339,7 @@ lines:
topic: [personal, trust]
tags: [kael, deflection, phase-4]
- id: the-terminal_d_031
- id: kael-davan_d_064
text: "I was checking on a maintenance issue. That's all."
role: dock-worker
access: [insider, peer]
@@ -349,7 +349,7 @@ lines:
topic: [routine]
tags: [kael, deflection, lying, phase-4]
- id: the-terminal_d_032
- id: kael-davan_d_065
text: "You're reading too much into it. Come on — shift's starting."
role: dock-worker
access: [insider, peer]
@@ -359,7 +359,7 @@ lines:
topic: [routine, trust]
tags: [kael, deflection, subject-change, phase-4]
- id: the-terminal_d_033
- id: kael-davan_d_066
text: "Can we not do this here? Voss is watching."
role: dock-worker
access: [insider]
@@ -374,7 +374,7 @@ lines:
# ========================================
# Path A: Gentle confrontation
- id: the-terminal_d_034
- id: kael-davan_d_067
text: "I... yeah. I need to tell you something. But not here."
role: dock-worker
access: [insider]
@@ -385,7 +385,7 @@ lines:
tags: [kael, confession-path, phase-5]
# Path B: Harsh pressure
- id: the-terminal_d_035
- id: kael-davan_d_068
text: "You want to report me? Go ahead. See what Nils does to both of us."
role: dock-worker
access: [insider]
@@ -396,7 +396,7 @@ lines:
tags: [kael, hostile-path, phase-5]
# If smuggler pushes further (gentle)
- id: the-terminal_d_036
- id: kael-davan_d_069
text: "Naia can't keep living like this. Neither can I. I'm looking for a way out."
role: dock-worker
access: [insider]
@@ -407,7 +407,7 @@ lines:
tags: [kael, confession, naia-reference, phase-5]
# If detective confronts (authority path)
- id: the-terminal_d_037
- id: kael-davan_d_070
text: "I don't know what you're talking about. I load containers. That's my job."
role: dock-worker
access: [public, authority]
@@ -416,7 +416,7 @@ lines:
topic: [routine]
tags: [kael, deflection, detective-path]
- id: the-terminal_d_038
- id: kael-davan_d_071
text: "You want to ask questions, talk to Voss. He runs the schedule."
role: dock-worker
access: [public, authority]
@@ -432,7 +432,7 @@ lines:
# ========================================
# first_meeting: InteractionMemory.count == 0. RelationshipState: Unknown → Public access only.
- id: the-terminal_d_050
- id: kael-davan_d_072
text: "This section is dock crew and logistics. If you're Commission or admin, check-in's back that way."
role: dock-worker
access: [public]
@@ -444,7 +444,7 @@ lines:
notes: "Layer 2 greeting — first meeting. Kael is helpful but orienting the stranger away from his work."
# established — peer/smuggler context: count >= 3, RelationshipState: Known/Friendly → Peer/Insider access.
- id: the-terminal_d_051
- id: kael-davan_d_073
text: "You're back. Good — bay three's been quiet. I'll fill you in."
role: dock-worker
access: [peer, insider]
@@ -456,7 +456,7 @@ lines:
notes: "Layer 2 greeting — established. Warm, practical, skips pleasantries. Treats them as crew."
# established — authority/detective context: count >= 3, RelationshipState: PersonOfInterest → Authority access.
- id: the-terminal_d_052
- id: kael-davan_d_074
text: "Back again. I'm on shift — if you have questions, make them quick."
role: dock-worker
access: [authority]
@@ -468,7 +468,7 @@ lines:
notes: "Layer 2 greeting — repeat with authority figure. Professional, clipped, not hostile. Kael can't refuse but won't volunteer."
# post-confrontation: confrontation in InteractionMemory.notable_events. NPC mood shifted to suspicious/frustrated.
- id: the-terminal_d_053
- id: kael-davan_d_075
text: "Didn't expect you today."
role: dock-worker
access: [peer, insider]
@@ -1,7 +1,7 @@
# Dialogue: maintenance-tech at The Terminal
# NPC: Pael Varren (Tier 3, NOBODY/CIVILIAN)
# Voice: tired-competent, minimal, dry. Knows the infrastructure. Knows nothing else.
# KEY LINE: the-terminal_d_040 — sounds like surveillance knowledge, is a maintenance complaint
# KEY LINE: maintenance-tech_d_001 — sounds like surveillance knowledge, is a maintenance complaint
# Ticket: #307 | Sprint: 12
location: the-terminal
@@ -14,7 +14,7 @@ lines:
# Actually: the B-7 seal fails when people use the corridor too much. Pael notices load.
# ==========================================
- id: the-terminal_d_040
- id: maintenance-tech_d_001
text: "B-7's had three seal failures this cycle. Someone's been through there more than they should."
role: maintenance-tech
access: [public]
@@ -35,7 +35,7 @@ lines:
# ROUTINE LINES
# ==========================================
- id: the-terminal_d_041
- id: maintenance-tech_d_002
text: "Air recycler's running hot again. Third deferred ticket this week."
role: maintenance-tech
access: [public]
@@ -45,7 +45,7 @@ lines:
mood: [content]
tags: [pael, atmospheric]
- id: the-terminal_d_042
- id: maintenance-tech_d_003
text: "Loading arm three's been on my list for a month. Management keeps deferring it."
role: maintenance-tech
access: [public]
@@ -55,7 +55,7 @@ lines:
mood: [content]
tags: [pael, atmospheric]
- id: the-terminal_d_043
- id: maintenance-tech_d_004
text: "If the gate tone shifts, stay clear of bay six. She needs recalibration."
role: maintenance-tech
access: [public]
@@ -65,7 +65,7 @@ lines:
mood: [content]
tags: [pael, warning, helpful]
- id: the-terminal_d_044
- id: maintenance-tech_d_005
text: "Not me. I just fix them."
role: maintenance-tech
access: [public]
@@ -75,7 +75,7 @@ lines:
mood: [content]
tags: [pael, response]
- id: the-terminal_d_045
- id: maintenance-tech_d_006
text: "Gate hum's been three cycles off-spec. Nobody cares until it stops completely."
role: maintenance-tech
access: [public]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — early days
# ==========================================
- id: the-terminal_d_400
- id: new-hire_d_001
text: "Still figuring out the bay numbering. Six and eight are in the wrong order."
role: new-hire
access: [public, peer]
@@ -22,7 +22,7 @@ lines:
mood: [content]
tags: [new-hire, greeting, orientation]
- id: the-terminal_d_401
- id: new-hire_d_002
text: "Voss told me to check in with you. Is that the standard thing?"
role: new-hire
access: [public, peer]
@@ -32,7 +32,7 @@ lines:
mood: [content]
tags: [new-hire, greeting, voss]
- id: the-terminal_d_402
- id: new-hire_d_003
text: "First full cycle on the floor. Trying to stay out of the way."
role: new-hire
access: [public, peer]
@@ -46,7 +46,7 @@ lines:
# FRESH OBSERVATIONS — the dangerous innocence
# ==========================================
- id: the-terminal_d_403
- id: new-hire_d_004
text: "Does bay four usually get held that long? The other bays turned over twice already."
role: new-hire
access: [public, peer]
@@ -59,7 +59,7 @@ lines:
smuggler: "New eyes on bay four. He doesn't know what he's looking at. But he noticed."
detective: "New hire asking exactly the right question without knowing it."
- id: the-terminal_d_404
- id: new-hire_d_005
text: "I saw someone go into C-4 last night without a scan log. Is that normal?"
role: new-hire
access: [public, peer]
@@ -76,7 +76,7 @@ lines:
confidence: suspects
notes: "High-value inadvertent witness line. Operationally dangerous for the ring."
- id: the-terminal_d_405
- id: new-hire_d_006
text: "Why does the transition window only get twenty minutes? The stations I trained at ran thirty."
role: new-hire
access: [public, peer]
@@ -89,7 +89,7 @@ lines:
smuggler: "He's asking about the window. Doesn't know why it matters. Answer carefully."
detective: "New hire confirms the window length is unusually short. File that."
- id: the-terminal_d_406
- id: new-hire_d_007
text: "Do containers usually get rerouted mid-hold? One went out through bay four while I thought it was still held."
role: new-hire
access: [public, peer]
@@ -105,7 +105,7 @@ lines:
fact_id: investigation.manifest_discrepancy
confidence: suspects
- id: the-terminal_d_407
- id: new-hire_d_008
text: "Maintenance corridor access is just off C-4, right? I took the wrong turn twice."
role: new-hire
access: [public, peer]
@@ -122,7 +122,7 @@ lines:
# LEARNING / QUESTIONS
# ==========================================
- id: the-terminal_d_408
- id: new-hire_d_009
text: "What's the Commission kiosk actually for? Nobody seems to use it."
role: new-hire
access: [public, peer]
@@ -132,7 +132,7 @@ lines:
mood: [content]
tags: [new-hire, commission, curiosity]
- id: the-terminal_d_409
- id: new-hire_d_010
text: "Maret seems stressed. Is that always the case or is something going on?"
role: new-hire
access: [peer]
@@ -145,7 +145,7 @@ lines:
smuggler: "He noticed Maret. Good that he's asking, not reporting. Manage this."
detective: "New hire reads Maret's stress without context. Confirms it's visible."
- id: the-terminal_d_410
- id: new-hire_d_011
text: "Voss seems really serious. Is he always like that?"
role: new-hire
access: [public, peer]
@@ -155,7 +155,7 @@ lines:
mood: [content]
tags: [new-hire, voss, curiosity]
- id: the-terminal_d_411
- id: new-hire_d_012
text: "How long before Voss trusts you to run a bay on your own?"
role: new-hire
access: [peer]
@@ -169,7 +169,7 @@ lines:
# SIGN-OFF
# ==========================================
- id: the-terminal_d_412
- id: new-hire_d_013
text: "That was a full shift. I'll have the bay numbers down by next cycle."
role: new-hire
access: [public, peer]
@@ -179,7 +179,7 @@ lines:
mood: [content]
tags: [new-hire, closing]
- id: the-terminal_d_413
- id: new-hire_d_014
text: "Good day. Lot to learn. But good."
role: new-hire
access: [public, peer]
@@ -193,7 +193,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-terminal_d_414
- id: new-hire_d_015
text: "Is there a trick to the manifest terminal or does it always lag like this?"
role: new-hire
access: [public, peer]
@@ -203,7 +203,7 @@ lines:
mood: [content]
tags: [new-hire, orientation, generation-pass]
- id: the-terminal_d_415
- id: new-hire_d_016
text: "How long has Maret been here? She seems like she knows everything."
role: new-hire
access: [peer]
@@ -213,7 +213,7 @@ lines:
mood: [content]
tags: [new-hire, maret, curiosity, generation-pass]
- id: the-terminal_d_416
- id: new-hire_d_017
text: "Nobody told me about the span gate frequency. I kept thinking something was wrong."
role: new-hire
access: [public, peer]
@@ -223,7 +223,7 @@ lines:
mood: [content]
tags: [new-hire, orientation, generation-pass]
- id: the-terminal_d_417
- id: new-hire_d_018
text: "The Last Shift — is that actually a bar? I haven't been yet."
role: new-hire
access: [public, peer]
@@ -233,7 +233,7 @@ lines:
mood: [content]
tags: [new-hire, social, generation-pass]
- id: the-terminal_d_418
- id: new-hire_d_019
text: "I thought the scanner on bay three was broken. Turns out you have to hold the badge longer."
role: new-hire
access: [public, peer]
@@ -243,7 +243,7 @@ lines:
mood: [content]
tags: [new-hire, orientation, humor, generation-pass]
- id: the-terminal_d_419
- id: new-hire_d_020
text: "Who's Drin? I keep hearing the name but haven't met them."
role: new-hire
access: [public, peer]
@@ -253,7 +253,7 @@ lines:
mood: [content]
tags: [new-hire, drin, curiosity, generation-pass]
- id: the-terminal_d_420
- id: new-hire_d_021
text: "Three cycles in and I still can't tell when Voss is satisfied. Is there a tell?"
role: new-hire
access: [peer]
@@ -17,7 +17,7 @@ lines:
# Second playthrough: "That's me."
# ========================================
- id: the-terminal_d_044
- id: pc-detective_d_004
text: "Good morning. Commission Liaison Office. I'm conducting a routine compliance review."
role: commission-liaison
access: [public]
@@ -27,7 +27,7 @@ lines:
topic: [routine]
tags: [pc-detective, greeting, public-tier]
- id: the-terminal_d_045
- id: pc-detective_d_005
text: "Standard review. Manifest reconciliation, scheduling compliance. Nothing unusual."
role: commission-liaison
access: [public]
@@ -36,7 +36,7 @@ lines:
topic: [routine]
tags: [pc-detective, purpose-question, public-tier]
- id: the-terminal_d_046
- id: pc-detective_d_006
text: "Only been here a few days. Seems like a well-run operation."
role: commission-liaison
access: [public]
@@ -45,7 +45,7 @@ lines:
topic: [routine]
tags: [pc-detective, district-question, public-tier]
- id: the-terminal_d_047
- id: pc-detective_d_007
text: "Ms. Venn? Colleague. We've worked in the same division."
role: commission-liaison
access: [public]
@@ -54,7 +54,7 @@ lines:
topic: [colleague]
tags: [pc-detective, sera-question, public-tier]
- id: the-terminal_d_048
- id: pc-detective_d_008
text: "I appreciate the interest, but I'm sure you have cargo to route. I won't keep you."
role: commission-liaison
access: [public, authority]
@@ -17,7 +17,7 @@ lines:
# Second playthrough: "That's me."
# ========================================
- id: the-terminal_d_039
- id: pc-smuggler_d_004
text: "Morning. Can I help you with something?"
role: dock-worker
access: [public]
@@ -27,7 +27,7 @@ lines:
topic: [routine]
tags: [pc-smuggler, greeting, public-tier]
- id: the-terminal_d_040
- id: pc-smuggler_d_005
text: "Cargo routing. Containers come in, I process them out. Standard stuff."
role: dock-worker
access: [public, authority]
@@ -36,7 +36,7 @@ lines:
topic: [routine, cargo]
tags: [pc-smuggler, work-question, public-tier]
- id: the-terminal_d_041
- id: pc-smuggler_d_006
text: "It's a freight hub. Busy during shifts, quiet after. Same as anywhere."
role: dock-worker
access: [public]
@@ -45,7 +45,7 @@ lines:
topic: [routine]
tags: [pc-smuggler, hub-question, public-tier]
- id: the-terminal_d_042
- id: pc-smuggler_d_007
text: "Good crew. We do our shifts, go home. Nothing exciting."
role: dock-worker
access: [public, authority]
@@ -54,7 +54,7 @@ lines:
topic: [colleague, routine]
tags: [pc-smuggler, colleagues-question, public-tier]
- id: the-terminal_d_043
- id: pc-smuggler_d_008
text: "Look, I just move containers. You'd want to talk to Voss about scheduling."
role: dock-worker
access: [authority]
@@ -13,7 +13,7 @@ lines:
# GREETINGS — scheduler's opening read
# ==========================================
- id: the-terminal_d_300
- id: scheduler_d_001
text: "Manifest's current. Bay assignments are posted."
role: scheduler
access: [public, peer]
@@ -23,7 +23,7 @@ lines:
mood: [content]
tags: [maret, procedural, greeting]
- id: the-terminal_d_301
- id: scheduler_d_002
text: "Container 4471 is still in C-4. Scheduled for bay six by 1400. Check with Voss if the hold's lifted."
role: scheduler
access: [public, peer]
@@ -39,7 +39,7 @@ lines:
fact_id: investigation.manifest_discrepancy
confidence: suspects
- id: the-terminal_d_302
- id: scheduler_d_003
text: "Three inbounds today. All verified against the manifest. No gaps so far."
role: scheduler
access: [public, peer]
@@ -53,7 +53,7 @@ lines:
# DISCREPANCY PATTERN — the core arc
# ==========================================
- id: the-terminal_d_303
- id: scheduler_d_004
text: "Container 4474 shows a forty-seven kilo discrepancy. I've flagged it twice."
role: scheduler
access: [public, peer]
@@ -69,7 +69,7 @@ lines:
fact_id: investigation.oversight_gap_pattern
confidence: suspects
- id: the-terminal_d_304
- id: scheduler_d_005
text: "It was logged as administrative. I don't know what that means for a weight discrepancy."
role: scheduler
access: [public, peer]
@@ -82,7 +82,7 @@ lines:
smuggler: "She doesn't know what 'administrative' means either. Voss's answer covered nothing."
detective: "Maret knows the answer she got wasn't a real answer. She's still sitting with that."
- id: the-terminal_d_305
- id: scheduler_d_006
text: "The transition window anomalies cluster at shift change. I've run the numbers four times. It's not random."
role: scheduler
access: [peer]
@@ -98,7 +98,7 @@ lines:
fact_id: investigation.manifest_discrepancy
confidence: knows_details
- id: the-terminal_d_306
- id: scheduler_d_007
text: "Three separate manifest entries show weight variations under ten kilos. Within rounding, technically."
role: scheduler
access: [peer]
@@ -111,7 +111,7 @@ lines:
smuggler: "She's tracking the small ones too. But 'within rounding' is what Voss told her. She's not satisfied."
detective: "Sub-threshold variations. Small enough to dismiss individually. Maret's running aggregate analysis."
- id: the-terminal_d_307
- id: scheduler_d_008
text: "I filed a formal notice with Voss. He reviewed it and told me it was resolved. It's not resolved."
role: scheduler
access: [peer]
@@ -131,7 +131,7 @@ lines:
# ROUTINE SCHEDULING — grounded work
# ==========================================
- id: the-terminal_d_308
- id: scheduler_d_009
text: "Bay eight's running on reduced capacity until 1700. Route inbounds through six."
role: scheduler
access: [public, peer]
@@ -141,7 +141,7 @@ lines:
mood: [content]
tags: [maret, operational]
- id: the-terminal_d_309
- id: scheduler_d_010
text: "Drin's running the inspection on bay six this cycle. Bay four's on hold per Voss's directive."
role: scheduler
access: [public, peer]
@@ -154,7 +154,7 @@ lines:
smuggler: "Maret confirmed it: bay four is Voss's directive. Not a system hold."
detective: "The bay four hold comes directly from Voss, not from the system. Discretionary hold."
- id: the-terminal_d_310
- id: scheduler_d_011
text: "Overtime requests go through Meridian. I can't process them on paper anymore."
role: scheduler
access: [public, peer]
@@ -164,7 +164,7 @@ lines:
mood: [content]
tags: [maret, administrative]
- id: the-terminal_d_311
- id: scheduler_d_012
text: "Manifest update at 1300. If your containers aren't posted by then, they don't make the cycle."
role: scheduler
access: [public, peer]
@@ -178,7 +178,7 @@ lines:
# DETECTIVE ENCOUNTER — access and resistance
# ==========================================
- id: the-terminal_d_312
- id: scheduler_d_013
text: "Commission manifest access requires a formal request through Voss. I can tell you what's on the public record."
role: scheduler
access: [authority]
@@ -188,7 +188,7 @@ lines:
mood: [content]
tags: [maret, detective-path, procedural]
- id: the-terminal_d_313
- id: scheduler_d_014
text: "The flagged entries are in the system. They were logged as reviewed and closed. That's what I can confirm."
role: scheduler
access: [authority]
@@ -201,7 +201,7 @@ lines:
smuggler: "Never encounters this line."
detective: "Maret confirms the flags exist in the system. And she's using careful language: 'what I can confirm.' There's more."
- id: the-terminal_d_314
- id: scheduler_d_015
text: "I'm not in a position to speak to why certain reviews were closed the way they were."
role: scheduler
access: [authority]
@@ -218,7 +218,7 @@ lines:
# ELEVATED TRUST — Maret breaks professional distance
# ==========================================
- id: the-terminal_d_315
- id: scheduler_d_016
text: "The pattern I'm seeing doesn't make sense for equipment failure. It's too consistent."
role: scheduler
access: [peer, insider]
@@ -234,7 +234,7 @@ lines:
fact_id: investigation.manifest_discrepancy
confidence: knows_details
- id: the-terminal_d_316
- id: scheduler_d_017
text: "I filed a second formal notice two cycles ago. It's been under review since then. By Voss."
role: scheduler
access: [peer]
@@ -250,7 +250,7 @@ lines:
fact_id: behavioral.voss_awareness
confidence: knows_details
- id: the-terminal_d_317
- id: scheduler_d_018
text: "I don't think I should be the one telling you this."
role: scheduler
access: [insider]
@@ -265,7 +265,7 @@ lines:
# SIGN-OFF
# ==========================================
- id: the-terminal_d_318
- id: scheduler_d_019
text: "Manifest posted for the evening shift. I'm off at 1500."
role: scheduler
access: [public, peer]
@@ -275,7 +275,7 @@ lines:
mood: [content]
tags: [maret, closing]
- id: the-terminal_d_319
- id: scheduler_d_020
text: "Numbers don't lie. People lie about numbers."
role: scheduler
access: [peer]
@@ -290,7 +290,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-terminal_d_320
- id: scheduler_d_021
text: "Bay assignments updated as of 0900. Check before you touch anything."
role: scheduler
access: [public, peer]
@@ -300,7 +300,7 @@ lines:
mood: [content]
tags: [maret, procedural, generation-pass]
- id: the-terminal_d_321
- id: scheduler_d_022
text: "Inbound from the Havara corridor is delayed six hours. Adjust your bay queue."
role: scheduler
access: [public, peer]
@@ -310,7 +310,7 @@ lines:
mood: [content]
tags: [maret, operational, generation-pass]
- id: the-terminal_d_322
- id: scheduler_d_023
text: "Scan log for bay six is still open from last cycle. Close it before end of shift."
role: scheduler
access: [public, peer]
@@ -320,7 +320,7 @@ lines:
mood: [content]
tags: [maret, procedural, generation-pass]
- id: the-terminal_d_323
- id: scheduler_d_024
text: "Overtime requests need scheduler approval now. Voss changed the process."
role: scheduler
access: [public, peer]
@@ -330,7 +330,7 @@ lines:
mood: [content]
tags: [maret, voss, administrative, generation-pass]
- id: the-terminal_d_324
- id: scheduler_d_025
text: "If a container's not logged by 1300, it doesn't make the cycle. That's the rule."
role: scheduler
access: [public, peer]
@@ -340,7 +340,7 @@ lines:
mood: [content]
tags: [maret, procedural, generation-pass]
- id: the-terminal_d_325
- id: scheduler_d_026
text: "End-of-cycle reconciliation is at 1430. Be available."
role: scheduler
access: [public, peer]
@@ -350,7 +350,7 @@ lines:
mood: [content]
tags: [maret, procedural, generation-pass]
- id: the-terminal_d_326
- id: scheduler_d_027
text: "Weight tolerance is plus or minus five kilos. Anything outside that gets a flag."
role: scheduler
access: [public, peer]
@@ -360,7 +360,7 @@ lines:
mood: [content]
tags: [maret, procedural, manifest, generation-pass]
- id: the-terminal_d_327
- id: scheduler_d_028
text: "Commission audit prep starts next cycle. Get your documentation current."
role: scheduler
access: [public, peer]
@@ -12,7 +12,7 @@ lines:
# GREETINGS — shift arrival
# ==========================================
- id: the-terminal_d_100
- id: shift-supervisor_d_001
text: "You're late. Don't be late."
role: shift-supervisor
access: [public, peer, insider]
@@ -22,7 +22,7 @@ lines:
mood: [content]
tags: [voss, greeting, authority]
- id: the-terminal_d_101
- id: shift-supervisor_d_002
text: "Manifest's posted. Bay assignments are final — don't renegotiate them with me."
role: shift-supervisor
access: [public, peer]
@@ -32,7 +32,7 @@ lines:
mood: [content]
tags: [voss, procedural]
- id: the-terminal_d_102
- id: shift-supervisor_d_003
text: "Bay 4's running a review hold. Nobody touches that freight until I say."
role: shift-supervisor
access: [public, peer]
@@ -45,7 +45,7 @@ lines:
smuggler: "Review hold on Bay 4. Voss knows the container's in the queue. Is he covering it or flagging it?"
detective: "Supervisor flagging Bay 4 himself. Either due diligence or containment."
- id: the-terminal_d_103
- id: shift-supervisor_d_004
text: "Commission liaison's in the district this cycle. Keep the floor clean."
role: shift-supervisor
access: [public, peer]
@@ -62,7 +62,7 @@ lines:
# SHIFT AUTHORITY — routine management
# ==========================================
- id: the-terminal_d_104
- id: shift-supervisor_d_005
text: "Drin was supposed to run the bay six inspection. Tell him I'm asking."
role: shift-supervisor
access: [public, peer]
@@ -72,7 +72,7 @@ lines:
mood: [content]
tags: [voss, drin, authority]
- id: the-terminal_d_105
- id: shift-supervisor_d_006
text: "Transition window's twenty minutes. Not twenty-five, not thirty. Twenty."
role: shift-supervisor
access: [public, peer, insider]
@@ -82,7 +82,7 @@ lines:
mood: [content]
tags: [voss, procedural, transition-window]
- id: the-terminal_d_106
- id: shift-supervisor_d_007
text: "Overtime requests go through the Meridian form. Don't bring them to me."
role: shift-supervisor
access: [public, peer]
@@ -92,7 +92,7 @@ lines:
mood: [content]
tags: [voss, procedural]
- id: the-terminal_d_107
- id: shift-supervisor_d_008
text: "Bay eight's going to downtime at 1300. Plan around it."
role: shift-supervisor
access: [public, peer]
@@ -102,7 +102,7 @@ lines:
mood: [content]
tags: [voss, operational]
- id: the-terminal_d_108
- id: shift-supervisor_d_009
text: "Maret flagged a discrepancy. I've reviewed it. It's logged. Move on."
role: shift-supervisor
access: [public, peer]
@@ -118,7 +118,7 @@ lines:
fact_id: investigation.oversight_gap_pattern
confidence: suspects
- id: the-terminal_d_109
- id: shift-supervisor_d_010
text: "Freight's moving. That's what matters. Questions slow things down."
role: shift-supervisor
access: [public, peer]
@@ -132,7 +132,7 @@ lines:
# SCHEDULING COVER — insider, real trust
# ==========================================
- id: the-terminal_d_110
- id: shift-supervisor_d_011
text: "Bay four is clear for transition. I've adjusted the inspection schedule."
role: shift-supervisor
access: [insider]
@@ -145,7 +145,7 @@ lines:
smuggler: "He adjusted the schedule. The window is actually clear. Voss is part of this."
detective: "Never hears this line."
- id: the-terminal_d_111
- id: shift-supervisor_d_012
text: "Drin's on bay six. Bay four's mine tonight."
role: shift-supervisor
access: [insider]
@@ -155,7 +155,7 @@ lines:
mood: [focused]
tags: [voss, ring-ops, scheduling-cover, drin]
- id: the-terminal_d_112
- id: shift-supervisor_d_013
text: "The hold note is administrative. Don't treat it as anything else."
role: shift-supervisor
access: [insider]
@@ -170,7 +170,7 @@ lines:
# DETECTIVE ENCOUNTER — authority tier
# ==========================================
- id: the-terminal_d_113
- id: shift-supervisor_d_014
text: "You'll need to submit a formal access request through Commission channels for any restricted data."
role: shift-supervisor
access: [authority]
@@ -180,7 +180,7 @@ lines:
mood: [content]
tags: [voss, detective-path, institutional-resistance]
- id: the-terminal_d_114
- id: shift-supervisor_d_015
text: "The manifest's available to any Commission officer. Ask the scheduler."
role: shift-supervisor
access: [authority]
@@ -190,7 +190,7 @@ lines:
mood: [content]
tags: [voss, detective-path, redirect]
- id: the-terminal_d_115
- id: shift-supervisor_d_016
text: "My workers perform their duties. If there's an irregularity, it'll show in the log."
role: shift-supervisor
access: [authority]
@@ -200,7 +200,7 @@ lines:
mood: [content]
tags: [voss, detective-path, deflection]
- id: the-terminal_d_116
- id: shift-supervisor_d_017
text: "I've been running this floor for six years. The numbers work out."
role: shift-supervisor
access: [authority, public]
@@ -214,7 +214,7 @@ lines:
# PRESSURE / CONFRONTATION
# ==========================================
- id: the-terminal_d_117
- id: shift-supervisor_d_018
text: "Close the door."
role: shift-supervisor
access: [insider, peer]
@@ -224,7 +224,7 @@ lines:
mood: [frustrated]
tags: [voss, confrontation, private]
- id: the-terminal_d_118
- id: shift-supervisor_d_019
text: "Maret keeps flagging the discrepancies. That needs to stop being her problem."
role: shift-supervisor
access: [insider]
@@ -237,7 +237,7 @@ lines:
fact_id: behavioral.voss_awareness
confidence: knows_of
- id: the-terminal_d_119
- id: shift-supervisor_d_020
text: "If you've got a problem with how I run this floor, that's a conversation for another time."
role: shift-supervisor
access: [public, peer]
@@ -251,7 +251,7 @@ lines:
# SIGN-OFF / END OF SHIFT
# ==========================================
- id: the-terminal_d_120
- id: shift-supervisor_d_021
text: "Sign-off is at 1400. Be done."
role: shift-supervisor
access: [public, peer]
@@ -261,7 +261,7 @@ lines:
mood: [content]
tags: [voss, procedural]
- id: the-terminal_d_121
- id: shift-supervisor_d_022
text: "Night shift takes over at 2200. I don't want to hear about the handoff tomorrow."
role: shift-supervisor
access: [public, peer]
@@ -271,7 +271,7 @@ lines:
mood: [content]
tags: [voss, procedural]
- id: the-terminal_d_122
- id: shift-supervisor_d_023
text: "Good shift. Tomorrow at 0600."
role: shift-supervisor
access: [public, peer, insider]
@@ -285,7 +285,7 @@ lines:
# GENERATION PASS — ambient variants
# ==========================================
- id: the-terminal_d_123
- id: shift-supervisor_d_024
text: "Assignments don't change once they're posted. Read the board."
role: shift-supervisor
access: [public, peer]
@@ -295,7 +295,7 @@ lines:
mood: [content]
tags: [voss, procedural, generation-pass]
- id: the-terminal_d_124
- id: shift-supervisor_d_025
text: "If there's a discrepancy, it goes to the scheduler. Not to me directly."
role: shift-supervisor
access: [public, peer]
@@ -305,7 +305,7 @@ lines:
mood: [content]
tags: [voss, procedural, maret, generation-pass]
- id: the-terminal_d_125
- id: shift-supervisor_d_026
text: "Span gate's cycling at 1100. Clear the bay six access before then."
role: shift-supervisor
access: [public, peer]
@@ -315,7 +315,7 @@ lines:
mood: [content]
tags: [voss, operational, generation-pass]
- id: the-terminal_d_126
- id: shift-supervisor_d_027
text: "I don't repeat myself on safety calls. If you missed it, ask someone who was listening."
role: shift-supervisor
access: [public, peer]
@@ -325,7 +325,7 @@ lines:
mood: [content]
tags: [voss, authority, generation-pass]
- id: the-terminal_d_127
- id: shift-supervisor_d_028
text: "End-of-day log goes to Meridian before you leave. No exceptions."
role: shift-supervisor
access: [public, peer]
@@ -335,7 +335,7 @@ lines:
mood: [content]
tags: [voss, procedural, generation-pass]
- id: the-terminal_d_128
- id: shift-supervisor_d_029
text: "Inbounds from Havara are on the revised schedule. Check the update."
role: shift-supervisor
access: [public, peer]
@@ -345,7 +345,7 @@ lines:
mood: [content]
tags: [voss, operational, generation-pass]
- id: the-terminal_d_129
- id: shift-supervisor_d_030
text: "Clean floor, clean manifest. That's the job."
role: shift-supervisor
access: [public, peer, insider]
@@ -47,8 +47,8 @@ items:
notes: >
Physical proof of cargo routing irregularities. The manifest shows
three containers rerouted during shift transition with weights that
don't reconcile. Cross-ref: Kael dialogue the-terminal_d_008
("Manifest says this one's forty kilos over") and the-terminal_d_018
don't reconcile. Cross-ref: Kael dialogue kael-davan_d_041
("Manifest says this one's forty kilos over") and kael-davan_d_051
("Manifest's clean on my end").
- fact_id: "investigation.cargo_anomaly"
relationship: "corroborates"
@@ -24,7 +24,7 @@ lines:
# --- Arrival ---
- id: commission-kiosk_m_d_001
- id: pc-detective_m_d_001
text: "Commission terminal. Authorized access, full import logs. Let's see what the system knows."
role: player_character
access: [public]
@@ -33,7 +33,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, tutorial, orientation]
- id: commission-kiosk_m_d_002
- id: pc-detective_m_d_002
text: "Standard regulation spec. Commission requisition 4-C. They didn't upgrade this terminal in three years."
role: player_character
access: [public]
@@ -42,7 +42,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, atmospheric]
- id: commission-kiosk_m_d_003
- id: pc-detective_m_d_003
text: "Filtered air. A Commission smell — sterile, precise, faintly antiseptic. Familiar."
role: player_character
access: [public]
@@ -51,7 +51,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, atmospheric, sensory]
- id: commission-kiosk_m_d_004
- id: pc-detective_m_d_004
text: "Back at the kiosk. The terminal logged my last access thirty-two minutes ago."
role: player_character
access: [public]
@@ -60,7 +60,7 @@ lines:
situation: [arrival]
tags: [arrival, orientation]
- id: commission-kiosk_m_d_005
- id: pc-detective_m_d_005
text: "Nobody uses this terminal except Commission staff. Which means whoever was here before me has credentials."
role: player_character
access: [public]
@@ -71,7 +71,7 @@ lines:
# --- Perception / Sound ---
- id: commission-kiosk_m_d_006
- id: pc-detective_m_d_006
text: "The terminal hum is different from the freight equipment. Cleaner. Higher frequency."
role: player_character
access: [public]
@@ -80,7 +80,7 @@ lines:
situation: [routine]
tags: [sensory, environmental]
- id: commission-kiosk_m_d_007
- id: pc-detective_m_d_007
text: "Footsteps at the transit corridor entrance. Not Commission — wrong pace."
role: player_character
access: [public]
@@ -89,7 +89,7 @@ lines:
situation: [routine, observation]
tags: [sensory, caution]
- id: commission-kiosk_m_d_008
- id: pc-detective_m_d_008
text: "Someone's running queries at the main manifest board. Loud keystrokes — frustrated."
role: player_character
access: [public]
@@ -100,7 +100,7 @@ lines:
# --- Tutorial / Evidence ---
- id: commission-kiosk_m_d_009
- id: pc-detective_m_d_009
text: "Import logs go back forty days. Weight discrepancies flagged automatically — unless someone cleared the flag."
role: player_character
access: [public]
@@ -109,7 +109,7 @@ lines:
situation: [routine, investigation]
tags: [tutorial, investigation, orientation]
- id: commission-kiosk_m_d_010
- id: pc-detective_m_d_010
text: "Personnel movement log. Every authorized access to every restricted space, timestamped."
role: player_character
access: [public]
@@ -120,7 +120,7 @@ lines:
# --- Time Idle / Ruminative ---
- id: commission-kiosk_m_d_011
- id: pc-detective_m_d_011
text: "Pattern is forming. Three manifests, same routing anomaly, different filing dates. That's deliberate."
role: player_character
access: [public]
@@ -129,7 +129,7 @@ lines:
situation: [routine, investigation]
tags: [investigation, analytical]
- id: commission-kiosk_m_d_012
- id: pc-detective_m_d_012
text: "The kiosk shows what the system knows. The system doesn't know what it hasn't been told."
role: player_character
access: [public]
@@ -138,7 +138,7 @@ lines:
situation: [routine]
tags: [atmospheric, investigation]
- id: commission-kiosk_m_d_013
- id: pc-detective_m_d_013
text: "Commission protocols say: document everything, infer nothing. The gap between those two is where cases live."
role: player_character
access: [public]
@@ -147,7 +147,7 @@ lines:
situation: [routine]
tags: [atmospheric, analytical]
- id: commission-kiosk_m_d_014
- id: pc-detective_m_d_014
text: "Twelve access events in the manifest terminal in the last six days. Elevated for a district this size."
role: player_character
access: [public]
@@ -156,7 +156,7 @@ lines:
situation: [routine, investigation]
tags: [investigation, analytical]
- id: commission-kiosk_m_d_015
- id: pc-detective_m_d_015
text: "Evidence is what the system logged. Inference is what it means. Right now I have plenty of both."
role: player_character
access: [public]
@@ -167,7 +167,7 @@ lines:
# --- Anomaly ---
- id: commission-kiosk_m_d_016
- id: pc-detective_m_d_016
text: "Access log shows a non-Commission credential used at 0340. That terminal should have rejected it."
role: player_character
access: [public]
@@ -177,7 +177,7 @@ lines:
priority: 7
tags: [investigation, caution, analytical]
- id: commission-kiosk_m_d_017
- id: pc-detective_m_d_017
text: "Container 4471 has three separate manifest entries with different timestamps. One of them is false."
role: player_character
access: [public]
@@ -191,7 +191,7 @@ lines:
priority: 8
tags: [investigation, contraband, analytical]
- id: commission-kiosk_m_d_018
- id: pc-detective_m_d_018
text: "Standard access log would show calibration events. This one shows something cleared three days ago. Manually."
role: player_character
access: [public]
@@ -202,7 +202,7 @@ lines:
# --- Knowledge-Gated: Sera Arc ---
- id: commission-kiosk_m_d_019
- id: pc-detective_m_d_019
text: "Venn, S. — last calibration event logged here: 0615 this morning. Standard. Her schedule is precise."
role: player_character
access: [public]
@@ -216,7 +216,7 @@ lines:
priority: 5
tags: [npc, sera, routine, friend-arc]
- id: commission-kiosk_m_d_020
- id: pc-detective_m_d_020
text: "Venn's credentials are in the access log. Three times today. She's thorough, or she's looking for something."
role: player_character
access: [public]
@@ -230,7 +230,7 @@ lines:
priority: 6
tags: [npc, sera, investigation, friend-arc, dual-lens]
- id: commission-kiosk_m_d_021
- id: pc-detective_m_d_021
text: "She runs calibration checks on the Commission terminal. That's her job. But the access log shows she's also running manifest queries. That's not."
role: player_character
access: [public]
@@ -244,7 +244,7 @@ lines:
priority: 7
tags: [npc, sera, tell, investigation, friend-arc]
- id: commission-kiosk_m_d_022
- id: pc-detective_m_d_022
text: "Venn cleared a flag on container 4471 six days ago. Routine override — authorized. But 4471 is in my manifest discrepancy list."
role: player_character
access: [public]
@@ -260,7 +260,7 @@ lines:
priority: 9
tags: [npc, sera, investigation, contraband, friend-arc, contaminated-trust]
- id: commission-kiosk_m_d_023
- id: pc-detective_m_d_023
text: "She knows. Either she found it and cleared it deliberately, or she was directed to. Both are worse than I want to believe."
role: player_character
access: [public]
@@ -278,7 +278,7 @@ lines:
# --- Post-Conversation ---
- id: commission-kiosk_m_d_024
- id: pc-detective_m_d_024
text: "She said she hadn't touched the manifest terminal. The log says otherwise. Either she forgot or she lied."
role: player_character
access: [public]
@@ -292,7 +292,7 @@ lines:
priority: 9
tags: [npc, sera, post-conversation, tell, friend-arc, contaminated-trust]
- id: commission-kiosk_m_d_025
- id: pc-detective_m_d_025
text: "Davan said he runs a clean dock. The manifest data disagrees on three specific points. Filed."
role: player_character
access: [public]
@@ -1,7 +1,7 @@
character: detective
location: general
lines:
- id: general_m_d_001
- id: pc-detective_m_d_001
text: "Sova Transit District. Population twelve thousand and change. Let's narrow that down."
role: player_character
access: [public]
@@ -10,7 +10,7 @@ lines:
trigger: enter_location
tags: [arrival, analytical]
- id: general_m_d_002
- id: pc-detective_m_d_002
text: "The span gate resonance is constant here. Background radiation of a freight hub."
role: player_character
access: [public]
@@ -19,7 +19,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: general_m_d_003
- id: pc-detective_m_d_003
text: "First week on station. The case file is thin. The manifest discrepancies are not."
role: player_character
access: [public]
@@ -28,7 +28,7 @@ lines:
trigger: time_idle
tags: [investigation, orientation]
- id: general_m_d_004
- id: pc-detective_m_d_004
text: "Everyone knows I'm Commission. The conversations stop when I walk in. Noted."
role: player_character
access: [public]
@@ -37,7 +37,7 @@ lines:
trigger: time_idle
tags: [investigation, social]
- id: general_m_d_005
- id: pc-detective_m_d_005
text: "Station gravity's point-nine-three standard. Enough to notice after a long shift."
role: player_character
access: [public]
@@ -46,7 +46,7 @@ lines:
trigger: time_idle
tags: [environmental, personal]
- id: general_m_d_006
- id: pc-detective_m_d_006
text: "The previous liaison rotated out three months ago. That gap matters."
role: player_character
access: [public]
@@ -55,7 +55,7 @@ lines:
trigger: time_idle
tags: [investigation, analytical]
- id: general_m_d_007
- id: pc-detective_m_d_007
text: "Back in the district. Let's see what changed while I wasn't watching."
role: player_character
access: [public]
@@ -64,7 +64,7 @@ lines:
trigger: return_visit
tags: [arrival, analytical]
- id: general_m_d_008
- id: pc-detective_m_d_008
text: "Recycled air, industrial residue, and that constant gate hum. Sova in three notes."
role: player_character
access: [public]
@@ -73,7 +73,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: general_m_d_009
- id: pc-detective_m_d_009
text: "Communities like this close ranks around outsiders. Patient work."
role: player_character
access: [public]
@@ -82,7 +82,7 @@ lines:
trigger: time_idle
tags: [reflection, social]
- id: general_m_d_010
- id: pc-detective_m_d_010
text: "The manifest data goes back eighteen months. The pattern starts around month six."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: maintenance-corridors
lines:
# --- Arrival + Environmental Flavor ---
- id: maintenance-corridors_m_d_001
- id: pc-detective_m_d_001
text: "Maintenance level. Low lighting, exposed conduit, restricted access nobody enforces."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, environmental]
- id: maintenance-corridors_m_d_002
- id: pc-detective_m_d_002
text: "The air's different down here. Cooler. Damper. Less filtered."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: maintenance-corridors_m_d_003
- id: pc-detective_m_d_003
text: "Service corridor B-section. Not on the public transit map. Interesting routing options."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, investigation]
- id: maintenance-corridors_m_d_004
- id: pc-detective_m_d_004
text: "Water recycling conduits. The station's circulatory system, hidden from view."
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: maintenance-corridors_m_d_005
- id: pc-detective_m_d_005
text: "Pipe network branching three directions. Good for maintenance. Better for privacy."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: enter_location
tags: [environmental, analytical]
- id: maintenance-corridors_m_d_006
- id: pc-detective_m_d_006
text: "Emergency lighting only. Visibility reduced, but so is surveillance coverage."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: enter_location
tags: [environmental, investigation]
- id: maintenance-corridors_m_d_007
- id: pc-detective_m_d_007
text: "The rust smell is stronger near junction B-7. Older section. Less maintenance."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: maintenance-corridors_m_d_008
- id: pc-detective_m_d_008
text: "Back in the corridors. Different route this time — mapping the alternatives."
role: player_character
access: [public]
@@ -77,7 +77,7 @@ lines:
# --- Investigation Observations ---
- id: maintenance-corridors_m_d_009
- id: pc-detective_m_d_009
text: "Scuff marks on the floor. Recent, heavy. Cargo weight, not foot traffic."
role: player_character
access: [public]
@@ -86,7 +86,7 @@ lines:
trigger: observe_anomaly
tags: [investigation, evidence]
- id: maintenance-corridors_m_d_010
- id: pc-detective_m_d_010
text: "The service hatch has been used recently. Oil on the handle, dust disturbed."
role: player_character
access: [public]
@@ -95,7 +95,7 @@ lines:
trigger: observe_anomaly
tags: [investigation, evidence]
- id: maintenance-corridors_m_d_011
- id: pc-detective_m_d_011
text: "Restricted access, but the lock plate shows wear. Regular use by someone with clearance."
role: player_character
access: [public]
@@ -104,7 +104,7 @@ lines:
trigger: observe_anomaly
tags: [investigation, evidence]
- id: maintenance-corridors_m_d_012
- id: pc-detective_m_d_012
text: "Sound carries differently down here. Voices from two junctions away, if you're patient."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
trigger: hear_sound
tags: [environmental, investigation]
- id: maintenance-corridors_m_d_013
- id: pc-detective_m_d_013
text: "Chalk marks on the wall. Low, deliberate. Not maintenance notation — wrong color."
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: observe_anomaly
tags: [investigation, evidence]
- id: maintenance-corridors_m_d_014
- id: pc-detective_m_d_014
text: "This corridor connects bay four to temp storage. Bypasses the main freight floor entirely."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: enter_location
tags: [investigation, operational]
- id: maintenance-corridors_m_d_015
- id: pc-detective_m_d_015
text: "Footsteps ahead. Single set, moving fast. Someone who knows where they're going."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: hear_sound
tags: [investigation, caution]
- id: maintenance-corridors_m_d_016
- id: pc-detective_m_d_016
text: "The temp storage access from this side isn't logged on the manifest system. Blind spot."
role: player_character
access: [public]
@@ -151,7 +151,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: maintenance-corridors_m_d_017
- id: pc-detective_m_d_017
text: "The cargo scuff pattern matches bay four containers. Dock to corridor to storage — the route."
role: player_character
access: [public]
@@ -165,7 +165,7 @@ lines:
priority: 8
tags: [investigation, evidence, breakthrough]
- id: maintenance-corridors_m_d_018
- id: pc-detective_m_d_018
text: "Those chalk marks match the courier's timing. Renn's route markers, if I had to guess."
role: player_character
access: [public]
@@ -179,7 +179,7 @@ lines:
priority: 7
tags: [npc, renn, investigation, evidence]
- id: maintenance-corridors_m_d_019
- id: pc-detective_m_d_019
text: "Camera blind spot at junction C-2. Coincidence, or someone mapped the coverage?"
role: player_character
access: [public]
@@ -192,7 +192,7 @@ lines:
min_confidence: suspects
tags: [investigation, operational]
- id: maintenance-corridors_m_d_020
- id: pc-detective_m_d_020
text: "Standard container, no manifest checkpoint along this route. That's the gap."
role: player_character
access: [public]
@@ -206,7 +206,7 @@ lines:
priority: 9
tags: [investigation, breakthrough]
- id: maintenance-corridors_m_d_021
- id: pc-detective_m_d_021
text: "Hatch lubricant is fresh. Someone's maintaining this access point. Not station maintenance."
role: player_character
access: [public]
@@ -219,7 +219,7 @@ lines:
min_confidence: knows_of
tags: [investigation, evidence]
- id: maintenance-corridors_m_d_022
- id: pc-detective_m_d_022
text: "Davan was seen near B-7 during off-shift. Cross-reference with container movements."
role: player_character
access: [public]
@@ -233,7 +233,7 @@ lines:
priority: 7
tags: [npc, kael, investigation]
- id: maintenance-corridors_m_d_023
- id: pc-detective_m_d_023
text: "Two access routes, one manifest checkpoint. Someone designed this gap. Or discovered it."
role: player_character
access: [public]
@@ -246,7 +246,7 @@ lines:
min_confidence: knows_details
tags: [investigation, analytical]
- id: maintenance-corridors_m_d_024
- id: pc-detective_m_d_024
text: "Rosta's gambling debt gives me leverage. But using it makes me the authority they resent."
role: player_character
access: [public]
@@ -260,7 +260,7 @@ lines:
priority: 6
tags: [npc, drin, investigation, moral]
- id: maintenance-corridors_m_d_025
- id: pc-detective_m_d_025
text: "Evidence is accumulating. Names, routes, timing. But I still can't see the full picture."
role: player_character
access: [public]
@@ -10,7 +10,7 @@ lines:
# Contraction discipline: analytical lines = uncontracted; personal lines (Sera) = contracted.
# All lines fully schema-compliant per D-035 (role, access, trust, situation present).
- id: opening_m_d_001
- id: pc-detective_m_d_001
text: "Sova Transit. Mid-morning shift, dock lighting at half-cycle. Consistent with the Commission pre-arrival briefing."
role: player_character
access: [public]
@@ -23,7 +23,7 @@ lines:
tags: [opening-hook, tutorial-arc, analytical, orientation]
notes: "Opening line. Reality matches the briefing — he arrived prepared. Establishes: detective as institutional outsider sent here. Cool-lit corridors per D-046 expressed through 'half-cycle' lighting observation. No contractions (analytical)."
- id: opening_m_d_002
- id: pc-detective_m_d_002
text: "Cargo lubricant, recycled atmosphere. Same mixture on every freight hub from here to the span gate hum. First observation: consistent with Commission briefing profile."
role: player_character
access: [public]
@@ -36,7 +36,7 @@ lines:
tags: [opening-hook, tutorial-arc, atmospheric, analytical]
notes: "Environmental sensory in analytical register. Colon usage (detective verbal pattern, section 2.2). No contractions. 'span gate' is canonical terminology (D-036); 'hum' is sensory detail. Contrast with smuggler's 'Home sweet home' — same smell, opposite emotional valence. He's working, not home."
- id: opening_m_d_003
- id: pc-detective_m_d_003
text: "Lattice overlay initializing. Four identity flags in immediate range — cross-referencing against Commission case file."
role: player_character
access: [public]
@@ -49,7 +49,7 @@ lines:
tags: [opening-hook, tutorial-arc, lattice, perception]
notes: "Teaches insert channel. The analytical lattice is active on entry — it flags and cross-references automatically. Teaches: detective's insert HUD is denser and diegetically active (D-048). Players learn the lattice is a tool, not decoration."
- id: opening_m_d_004
- id: pc-detective_m_d_004
text: "Commission kiosk confirmed my credentials on entry. Authority access logged. The floor knows someone's here."
role: player_character
access: [public]
@@ -62,7 +62,7 @@ lines:
tags: [opening-hook, tutorial-arc, institutional, authority]
notes: "Commission kiosk visible, institutional standing established. Sardonic close — dry awareness of his effect on the social environment. Confidence register: wry humor (section 2.8). Teaches: Authority access tier, institutional role is visible to NPCs."
- id: opening_m_d_005
- id: pc-detective_m_d_005
text: "Visual range terminates at bay nine. Approximately fifteen meters. Consistent with district atmo settings."
role: player_character
access: [public]
@@ -74,7 +74,7 @@ lines:
tags: [opening-hook, tutorial-arc, fog, perception]
notes: "Fog boundary in analytical register. He clocks the measurement, notes 'approximately' — precision with honest calibration. Contrast with smuggler's 'Can't see the far dock' — same fog, different framing: limitation vs. measurement."
- id: opening_m_d_006
- id: pc-detective_m_d_006
text: "Dock workers distributed across nine visible bays. Lattice flagging each on contact. Names from the case file, faces from the floor."
role: player_character
access: [public]
@@ -87,7 +87,7 @@ lines:
tags: [opening-hook, tutorial-arc, lattice, orientation]
notes: "Spatial overview with lattice overlay tutorial. Explains how the insert channel works: lattice matches observed faces against the pre-loaded case file. Parallel structure ('Names from... faces from...'). Teaches: NPC identification comes through the lattice."
- id: opening_m_d_007
- id: pc-detective_m_d_007
text: "Venn, S. at the far terminal. She said she'd come in early."
role: player_character
access: [public]
@@ -100,7 +100,7 @@ lines:
tags: [opening-hook, tutorial-arc, sera, friend-arc]
notes: "First Sera sighting. Surname-initial filing first (institutional reflex) — then breaks to personal contracted voice ('she'd') in the same beat. Teaches contrast: case-file subjects get 'Davan, K.'; Sera gets a different register. D-033 Unknown/Neutral teal entity."
- id: opening_m_d_008
- id: pc-detective_m_d_008
text: "She's got the manifest logs open already. Field tech who knows how to pull records — she knows what I need before I ask."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
tags: [opening-hook, tutorial-arc, sera, friend-arc]
notes: "Sera warmth. Fully contracted throughout ('She's', 'she knows') — personal mode, no analytical distance. Em-dash. Professional respect established without implying prior district visits. Contrast with how he processes everyone else. Teaches: some NPCs are in a different relational category (THE FRIEND pattern, D-034)."
- id: opening_m_d_009
- id: pc-detective_m_d_009
text: "Central manifest board: real-time container routing, weight declarations, bay assignments. Everything routes through here."
role: player_character
access: [public]
@@ -126,7 +126,7 @@ lines:
tags: [opening-hook, tutorial-arc, manifest, investigation]
notes: "Manifest board labeled and cataloged. Colon usage (section 2.2). No contractions (analytical). Teaches: the manifest board is the information hub — and therefore the investigation's starting point."
- id: opening_m_d_010
- id: pc-detective_m_d_010
text: "Container 4471. Temp storage flag since yesterday. Standard turnaround is sub-eight hours — that's not standard."
role: player_character
access: [public]
@@ -139,7 +139,7 @@ lines:
tags: [opening-hook, tutorial-arc, manifest, investigation, anomaly]
notes: "First manifest anomaly. Specific container ID from case file. 'That's not standard' — only contraction in an analytical line, marking it as personal reaction to the finding. Sets the investigation hook: something is wrong before he asks a single question."
- id: opening_m_d_011
- id: pc-detective_m_d_011
text: "Worth flagging. Let's see what the shift logs say about that container's overnight routing."
role: player_character
access: [public]
@@ -152,7 +152,7 @@ lines:
tags: [opening-hook, tutorial-arc, investigation, next-step]
notes: "'Worth flagging' + 'Let's see what' — both detective verbal tics in sequence (section 2.7). Frames the next investigative step. Teaches: investigation is incremental — observe, flag, investigate deeper."
- id: opening_m_d_012
- id: pc-detective_m_d_012
text: "Lattice stable. Nine identity flags and counting. Let's see what this shift tells me."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
# --- Tell Observations (5 core tells from pc-as-npc-spec.md section 8) ---
- id: general_m_d_011
- id: pc-detective_m_d_001
text: "That dock worker checks the corridor before turning. Habitual. Military? Or just careful."
role: player_character
access: [public]
@@ -25,7 +25,7 @@ lines:
state: known
tags: [npc, pc-smuggler, tell, corridor-check]
- id: general_m_d_012
- id: pc-detective_m_d_002
text: "Movement pattern changed at shift change. More direct. Less idle. Interesting."
role: player_character
access: [public]
@@ -38,7 +38,7 @@ lines:
state: known
tags: [npc, pc-smuggler, tell, shift-transition]
- id: general_m_d_013
- id: pc-detective_m_d_003
text: "Slight acceleration past the checkpoint. Most people do it. But this one does it every time."
role: player_character
access: [public]
@@ -51,7 +51,7 @@ lines:
state: known
tags: [npc, pc-smuggler, tell, security-proximity]
- id: general_m_d_014
- id: pc-detective_m_d_004
text: "Same three people at the same table. Every shift. Consistent social group."
role: player_character
access: [public]
@@ -64,7 +64,7 @@ lines:
state: known
tags: [npc, pc-smuggler, tell, social-clustering]
- id: general_m_d_015
- id: pc-detective_m_d_005
text: "That worker checked container 4471 twice. The same container that's in my manifest discrepancy file."
role: player_character
access: [public]
@@ -83,7 +83,7 @@ lines:
# --- Accumulated Observation (gates on multiple tell sightings) ---
- id: general_m_d_016
- id: pc-detective_m_d_006
text: "Three behavioral flags on one dock worker. Corridor awareness, shift-transition alertness, cargo fixation. That's a pattern."
role: player_character
access: [public]
@@ -98,7 +98,7 @@ lines:
priority: 7
tags: [npc, pc-smuggler, investigation, analytical, pattern-recognition]
- id: general_m_d_017
- id: pc-detective_m_d_007
text: "Cross-referencing the dock worker's shift schedule with manifest anomalies. Correlation: moderate."
role: player_character
access: [public]
@@ -115,7 +115,7 @@ lines:
priority: 8
tags: [npc, pc-smuggler, investigation, evidence]
- id: general_m_d_018
- id: pc-detective_m_d_008
text: "The dock worker and Davan eat together every shift. Same courier joins them. Tight operational cluster."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: the-last-shift
lines:
# --- Arrival + Environmental Flavor ---
- id: the-last-shift_m_d_001
- id: pc-detective_m_d_001
text: "The Last Shift. Only place in this district that doesn't smell like freight lubricant."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: the-last-shift_m_d_002
- id: pc-detective_m_d_002
text: "Grain spirit and low conversation. The social nexus of Sova Transit."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [arrival, analytical]
- id: the-last-shift_m_d_003
- id: pc-detective_m_d_003
text: "Bar's busy tonight. Good — crowds talk. Bad — they notice outsiders."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, investigation]
- id: the-last-shift_m_d_004
- id: pc-detective_m_d_004
text: "Standard district bar. Prefab fixtures, aftermarket lighting, a menu board nobody reads."
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: enter_location
tags: [arrival, environmental]
- id: the-last-shift_m_d_005
- id: pc-detective_m_d_005
text: "The Meridian feed on the wall display. Local news, freight indices, Assembly coverage."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: enter_location
tags: [environmental]
- id: the-last-shift_m_d_006
- id: pc-detective_m_d_006
text: "The overhead vent rattles every thirty seconds. Like clockwork. Nobody else notices."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: the-last-shift_m_d_007
- id: pc-detective_m_d_007
text: "The conversation volume dropped when I walked in. Recovering now. They're adjusting."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: enter_location
tags: [social, investigation]
- id: the-last-shift_m_d_008
- id: pc-detective_m_d_008
text: "Back at the bar. Same booth. Let's see who's here and who isn't."
role: player_character
access: [public]
@@ -75,7 +75,7 @@ lines:
trigger: return_visit
tags: [arrival, analytical]
- id: the-last-shift_m_d_009
- id: pc-detective_m_d_009
text: "Kitchen's running. Fried protein and something spiced. The bar's other currency: hot food."
role: player_character
access: [public]
@@ -84,7 +84,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: the-last-shift_m_d_010
- id: pc-detective_m_d_010
text: "Someone carved initials into this table. Old. A history I don't have."
role: player_character
access: [public]
@@ -95,7 +95,7 @@ lines:
# --- NPC Commentary ---
- id: the-last-shift_m_d_011
- id: pc-detective_m_d_011
text: "The bartender — Sessik, L. Knows everyone. Watches everything. Gives away nothing."
role: player_character
access: [public]
@@ -104,7 +104,7 @@ lines:
trigger: observe_npc
tags: [npc, lera, analytical]
- id: the-last-shift_m_d_012
- id: pc-detective_m_d_012
text: "Good to see Sera. At least someone in this district speaks my language."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
trigger: observe_npc
tags: [npc, sera, warm]
- id: the-last-shift_m_d_013
- id: pc-detective_m_d_013
text: "Lintar, T. — the loud one at the bar. Spends freely. Where's the money from?"
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: observe_npc
tags: [npc, torek, investigation]
- id: the-last-shift_m_d_014
- id: pc-detective_m_d_014
text: "A woman at the corner table — Tamm, N., according to Sera. Davan's partner."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: observe_npc
tags: [npc, naia, analytical]
- id: the-last-shift_m_d_015
- id: pc-detective_m_d_015
text: "The regulars have assigned seating. Unofficial, but enforced. I don't have a spot yet."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: observe_npc
tags: [social, atmospheric]
- id: the-last-shift_m_d_016
- id: pc-detective_m_d_016
text: "Sessik greeted three people by name since I sat down. I got a nod. Progress."
role: player_character
access: [public]
@@ -149,7 +149,7 @@ lines:
trigger: witness_interaction
tags: [npc, lera, social]
- id: the-last-shift_m_d_017
- id: pc-detective_m_d_017
text: "Two dock workers in the corner. Heads close, voices low. Not unusual for after-shift."
role: player_character
access: [public]
@@ -158,7 +158,7 @@ lines:
trigger: witness_interaction
tags: [social, investigation]
- id: the-last-shift_m_d_018
- id: pc-detective_m_d_018
text: "Sera introduced me to Tamm. Brief, polite, guarded. Expected."
role: player_character
access: [public]
@@ -167,7 +167,7 @@ lines:
trigger: post_conversation
tags: [npc, sera, naia, social]
- id: the-last-shift_m_d_019
- id: pc-detective_m_d_019
text: "Sessik poured my drink without asking. She remembered. Small thing, but noted."
role: player_character
access: [public]
@@ -176,7 +176,7 @@ lines:
trigger: observe_npc
tags: [npc, lera, social, warm]
- id: the-last-shift_m_d_020
- id: pc-detective_m_d_020
text: "The old man by the window hasn't spoken to anyone all evening. Watching, though."
role: player_character
access: [public]
@@ -187,7 +187,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: the-last-shift_m_d_021
- id: pc-detective_m_d_021
text: "Sera left when Torek arrived. Second time. Different excuse. Same result."
role: player_character
access: [public]
@@ -201,7 +201,7 @@ lines:
priority: 7
tags: [npc, sera, torek, tell, friend-arc]
- id: the-last-shift_m_d_022
- id: pc-detective_m_d_022
text: "That's the third time. Three excuses, one result. That's data."
role: player_character
access: [public]
@@ -215,7 +215,7 @@ lines:
priority: 9
tags: [npc, sera, torek, tell, friend-arc, contradiction]
- id: the-last-shift_m_d_023
- id: pc-detective_m_d_023
text: "Lintar's spending doesn't match a dock worker's salary. Source unknown."
role: player_character
access: [public]
@@ -228,7 +228,7 @@ lines:
min_confidence: knows_of
tags: [npc, torek, investigation]
- id: the-last-shift_m_d_024
- id: pc-detective_m_d_024
text: "Sessik's eyes tracked me when I sat near the back room. Protective, not hostile."
role: player_character
access: [public]
@@ -241,7 +241,7 @@ lines:
min_confidence: suspects
tags: [npc, lera, investigation]
- id: the-last-shift_m_d_025
- id: pc-detective_m_d_025
text: "Sera's being extra helpful tonight. Names, backgrounds, context. Compensating?"
role: player_character
access: [public]
@@ -255,7 +255,7 @@ lines:
priority: 7
tags: [npc, sera, friend-arc, behavioral]
- id: the-last-shift_m_d_026
- id: pc-detective_m_d_026
text: "Same booth. Same warm smile. Same offer to buy me a drink. Everything except the truth."
role: player_character
access: [public]
@@ -269,7 +269,7 @@ lines:
priority: 8
tags: [npc, sera, contaminated-trust, friend-arc]
- id: the-last-shift_m_d_027
- id: pc-detective_m_d_027
text: "Tamm seems distressed tonight. Not her baseline. Something at home, or something she heard?"
role: player_character
access: [public]
@@ -282,7 +282,7 @@ lines:
state: known
tags: [npc, naia, behavioral]
- id: the-last-shift_m_d_028
- id: pc-detective_m_d_028
text: "Sessik just redirected a conversation away from freight schedules. Protective reflex?"
role: player_character
access: [public]
@@ -295,7 +295,7 @@ lines:
min_confidence: suspects
tags: [npc, lera, investigation]
- id: the-last-shift_m_d_029
- id: pc-detective_m_d_029
text: "Davan and his partner at the bar. She's talking. He's not listening. Noted."
role: player_character
access: [public]
@@ -308,7 +308,7 @@ lines:
state: known
tags: [npc, kael, naia, behavioral]
- id: the-last-shift_m_d_030
- id: pc-detective_m_d_030
text: "The regulars are warming up. Three visits and I'm almost furniture. Almost."
role: player_character
access: [public]
@@ -324,7 +324,7 @@ lines:
# --- Kael Davan — Additional Bar Observations ---
# Ticket: #297 review fix | Filling detective monologue gap
- id: the-last-shift_m_d_043
- id: pc-detective_m_d_031
text: "Davan is different here. Looser. The dock worker persona drops and something warmer replaces it."
role: player_character
access: [public]
@@ -337,7 +337,7 @@ lines:
state: known
tags: [npc, kael, behavioral, dual-context]
- id: the-last-shift_m_d_044
- id: pc-detective_m_d_032
text: "Davan checks the door every time it opens. Even here. Even off-shift. That is not relaxation."
role: player_character
access: [public]
@@ -357,7 +357,7 @@ lines:
# 5-phase FRIEND arc: trust > background data > pattern > question >
# contaminated trust. Contraction shifts mark objectivity loss.
- id: the-last-shift_m_d_031
- id: pc-detective_m_d_033
text: "Sera changed the subject when I mentioned manifests. Smooth. Practised, maybe."
role: player_character
access: [public]
@@ -371,7 +371,7 @@ lines:
priority: 6
tags: [npc, sera, tell, behavioral, friend-arc]
- id: the-last-shift_m_d_032
- id: pc-detective_m_d_034
text: "She volunteers names, routines, context. Gives everything I ask for -- except what I did not."
role: player_character
access: [public]
@@ -385,7 +385,7 @@ lines:
priority: 6
tags: [npc, sera, tell, overcompensation, friend-arc]
- id: the-last-shift_m_d_033
- id: pc-detective_m_d_035
text: "Sera at the Commission kiosk. Third pass this shift. That is not standard recalibration."
role: player_character
access: [public]
@@ -399,7 +399,7 @@ lines:
priority: 7
tags: [npc, sera, tell, peripheral, friend-arc]
- id: the-last-shift_m_d_034
- id: pc-detective_m_d_036
text: "Everything Sera told me checks out. Layout, schedules, regulars. Accurate. Thorough. Selective?"
role: player_character
access: [public]
@@ -413,7 +413,7 @@ lines:
priority: 5
tags: [npc, sera, analytical, phase-2-3, friend-arc]
- id: the-last-shift_m_d_035
- id: pc-detective_m_d_037
text: "She mentioned Naia's worry about Kael early -- before I had context. Priming, or concern?"
role: player_character
access: [public]
@@ -427,7 +427,7 @@ lines:
priority: 7
tags: [npc, sera, naia, retrospective, friend-arc]
- id: the-last-shift_m_d_036
- id: pc-detective_m_d_038
text: "Sera's institutional access goes further than field calibration. She could pull records I cannot."
role: player_character
access: [public]
@@ -440,7 +440,7 @@ lines:
state: friendly
tags: [npc, sera, analytical, capability, friend-arc]
- id: the-last-shift_m_d_037
- id: pc-detective_m_d_039
text: "Assessment: Venn, S. Cooperative. Embedded. Competent. Green -- with reservations now."
role: player_character
access: [public]
@@ -454,7 +454,7 @@ lines:
priority: 7
tags: [npc, sera, phase-transition, analytical, friend-arc]
- id: the-last-shift_m_d_038
- id: pc-detective_m_d_040
text: "Correction: amber. The avoidance pattern is behavioural, not preferential. Data, not taste."
role: player_character
access: [public]
@@ -468,7 +468,7 @@ lines:
priority: 9
tags: [npc, sera, phase-transition, tell, contradiction, friend-arc]
- id: the-last-shift_m_d_039
- id: pc-detective_m_d_041
text: "She's the first person who made this place feel less foreign. I need that to not matter right now."
role: player_character
access: [public]
@@ -482,7 +482,7 @@ lines:
priority: 8
tags: [npc, sera, emotional, phase-4-5, friend-arc]
- id: the-last-shift_m_d_040
- id: pc-detective_m_d_042
text: "Those early tips. The district layout, the introductions. Were those help -- or steering?"
role: player_character
access: [public]
@@ -496,7 +496,7 @@ lines:
priority: 7
tags: [npc, sera, contaminated-trust, retrospective, friend-arc]
- id: the-last-shift_m_d_041
- id: pc-detective_m_d_043
text: "She's not a suspect. She's a friend with access to everything I need. That is the problem."
role: player_character
access: [public]
@@ -510,7 +510,7 @@ lines:
priority: 8
tags: [npc, sera, contaminated-trust, phase-4-5, friend-arc]
- id: the-last-shift_m_d_042
- id: pc-detective_m_d_044
text: "Trust: compromised. The question is not what she told me. It is what she did not."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: the-terminal
lines:
# --- Arrival + Environmental Flavor ---
- id: the-terminal_m_d_001
- id: pc-detective_m_d_001
text: "Logistics hub. Standard prefab, heavy foot traffic. Let's see what the shift change tells me."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, analytical]
- id: the-terminal_m_d_002
- id: pc-detective_m_d_002
text: "Sixteen bays, four loading arms, one manifest board. Everything routes through here."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [arrival, orientation]
- id: the-terminal_m_d_003
- id: pc-detective_m_d_003
text: "Cargo lubricant and recycled air. The smell of honest work. Or a convincing imitation."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: the-terminal_m_d_004
- id: pc-detective_m_d_004
text: "Loading arm three has a grind in its cycle. Maintenance deferred — budget, or negligence?"
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: hear_sound
tags: [environmental, analytical]
- id: the-terminal_m_d_005
- id: pc-detective_m_d_005
text: "Shift change in twenty minutes. The handover period is where oversight gets thin."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: time_idle
tags: [investigation, operational]
- id: the-terminal_m_d_006
- id: pc-detective_m_d_006
text: "The manifest board updates in real-time. Container movement, weight, destination. All logged."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: enter_location
tags: [environmental, orientation]
- id: the-terminal_m_d_007
- id: pc-detective_m_d_007
text: "Heavy foot traffic at shift change. Good — observation is easier in a crowd."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: enter_location
tags: [arrival, investigation]
- id: the-terminal_m_d_008
- id: pc-detective_m_d_008
text: "Containers stacked to regulation height in most bays. Bay four is the exception."
role: player_character
access: [public]
@@ -75,7 +75,7 @@ lines:
trigger: observe_anomaly
tags: [environmental, investigation]
- id: the-terminal_m_d_009
- id: pc-detective_m_d_009
text: "Back at the hub. Different shift, different faces. Same manifest board."
role: player_character
access: [public]
@@ -84,7 +84,7 @@ lines:
trigger: return_visit
tags: [arrival]
- id: the-terminal_m_d_010
- id: pc-detective_m_d_010
text: "The dock lights are running half-cycle. Saves power. Also reduces visibility."
role: player_character
access: [public]
@@ -93,7 +93,7 @@ lines:
trigger: enter_location
tags: [environmental, analytical]
- id: the-terminal_m_d_011
- id: pc-detective_m_d_011
text: "Something in the air beyond the lubricant. Chemical, faint. Not standard freight residue."
role: player_character
access: [public]
@@ -102,7 +102,7 @@ lines:
trigger: enter_location
tags: [environmental, investigation]
- id: the-terminal_m_d_012
- id: pc-detective_m_d_012
text: "Overhead conveyor running at capacity. The manifest says otherwise. Interesting."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
# --- NPC Commentary ---
- id: the-terminal_m_d_013
- id: pc-detective_m_d_013
text: "Shift supervisor Voss. Tense posture, checking the dock more than the schedule."
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: observe_npc
tags: [npc, voss, behavioral]
- id: the-terminal_m_d_014
- id: pc-detective_m_d_014
text: "Korr, M. — freight scheduler. Efficient, focused. Either very good or very careful."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: observe_npc
tags: [npc, maret, analytical]
- id: the-terminal_m_d_015
- id: pc-detective_m_d_015
text: "Rosta, D. — dock inspector. Quick checks, minimal documentation. That's a pattern."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: observe_npc
tags: [npc, drin, investigation]
- id: the-terminal_m_d_016
- id: pc-detective_m_d_016
text: "Dock worker. Davan, K. Unremarkable on paper. But he knows the bay layout cold."
role: player_character
access: [public]
@@ -149,7 +149,7 @@ lines:
trigger: observe_npc
tags: [npc, kael]
- id: the-terminal_m_d_017
- id: pc-detective_m_d_017
text: "Conversations stop when I pass. Standard response to Commission presence. Expected."
role: player_character
access: [public]
@@ -158,7 +158,7 @@ lines:
trigger: witness_interaction
tags: [social, investigation]
- id: the-terminal_m_d_018
- id: pc-detective_m_d_018
text: "Two workers talking by bay six. One sees me, taps the other. Both go quiet."
role: player_character
access: [public]
@@ -167,7 +167,7 @@ lines:
trigger: witness_interaction
tags: [social, investigation]
- id: the-terminal_m_d_019
- id: pc-detective_m_d_019
text: "Voss gave Davan a look across the bay. Brief. Loaded. Filing it."
role: player_character
access: [public]
@@ -176,7 +176,7 @@ lines:
trigger: witness_interaction
tags: [npc, voss, kael, behavioral]
- id: the-terminal_m_d_020
- id: pc-detective_m_d_020
text: "The courier — Renn — moves fast. Efficient routing. Knows these corridors well."
role: player_character
access: [public]
@@ -185,7 +185,7 @@ lines:
trigger: observe_npc
tags: [npc, renn, analytical]
- id: the-terminal_m_d_021
- id: pc-detective_m_d_021
text: "New shift starting. Different bodies, same avoidance radius around me."
role: player_character
access: [public]
@@ -194,7 +194,7 @@ lines:
trigger: observe_npc
tags: [social, atmospheric]
- id: the-terminal_m_d_022
- id: pc-detective_m_d_022
text: "Rosta's inspection of bay four took forty seconds. The others: three minutes each."
role: player_character
access: [public]
@@ -203,7 +203,7 @@ lines:
trigger: observe_npc
tags: [npc, drin, investigation, behavioral]
- id: the-terminal_m_d_023
- id: pc-detective_m_d_023
text: "Voss deflected when I asked about overnight containers. Smooth, but I caught it."
role: player_character
access: [public]
@@ -212,7 +212,7 @@ lines:
trigger: post_conversation
tags: [npc, voss, investigation]
- id: the-terminal_m_d_024
- id: pc-detective_m_d_024
text: "Korr won't make eye contact. Not hostility — something else. Anxiety, maybe."
role: player_character
access: [public]
@@ -223,7 +223,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: the-terminal_m_d_025
- id: pc-detective_m_d_025
text: "Davan's lattice activity spiked. Expecting a message? Or checking for surveillance?"
role: player_character
access: [public]
@@ -236,7 +236,7 @@ lines:
state: known
tags: [npc, kael, behavioral, tell]
- id: the-terminal_m_d_026
- id: pc-detective_m_d_026
text: "Container 4471. Logged for temp storage overnight. Turnaround should be sub-eight hours."
role: player_character
access: [public]
@@ -250,7 +250,7 @@ lines:
priority: 7
tags: [investigation, evidence]
- id: the-terminal_m_d_027
- id: pc-detective_m_d_027
text: "Weight discrepancy on the bay four manifest. Point-three kilos over declared. Noted."
role: player_character
access: [public]
@@ -263,7 +263,7 @@ lines:
min_confidence: suspects
tags: [investigation, evidence]
- id: the-terminal_m_d_028
- id: pc-detective_m_d_028
text: "Rosta's gambling debts are in the case file. Leverage, if I need it. Do I need it?"
role: player_character
access: [public]
@@ -276,7 +276,7 @@ lines:
min_confidence: knows_details
tags: [npc, drin, investigation, moral]
- id: the-terminal_m_d_029
- id: pc-detective_m_d_029
text: "Three names keep appearing near the anomalous manifests: Voss, Davan, Renn. Coincidence?"
role: player_character
access: [public]
@@ -290,7 +290,7 @@ lines:
priority: 7
tags: [investigation, analytical]
- id: the-terminal_m_d_030
- id: pc-detective_m_d_030
text: "The shift transition creates a twenty-minute gap in active oversight. Every cycle."
role: player_character
access: [public]
@@ -304,7 +304,7 @@ lines:
priority: 7
tags: [investigation, operational]
- id: the-terminal_m_d_031
- id: pc-detective_m_d_031
text: "Korr changed the schedule after our conversation. Covering tracks, or just doing her job?"
role: player_character
access: [public]
@@ -318,7 +318,7 @@ lines:
value: schedule_modification
tags: [npc, maret, investigation]
- id: the-terminal_m_d_032
- id: pc-detective_m_d_032
text: "Rosta inspects everything else by the book. Bay four is the exception. That's the tell."
role: player_character
access: [public]
@@ -332,7 +332,7 @@ lines:
priority: 8
tags: [npc, drin, investigation, behavioral]
- id: the-terminal_m_d_033
- id: pc-detective_m_d_033
text: "Davan's at an unusual terminal. Not his assigned bay. Why the move?"
role: player_character
access: [public]
@@ -345,7 +345,7 @@ lines:
state: known
tags: [npc, kael, investigation]
- id: the-terminal_m_d_034
- id: pc-detective_m_d_034
text: "Voss is watching me watch his people. He knows what I'm here for."
role: player_character
access: [public]
@@ -359,7 +359,7 @@ lines:
priority: 6
tags: [npc, voss, investigation]
- id: the-terminal_m_d_035
- id: pc-detective_m_d_035
text: "The routing logs and the physical movement don't match. Someone's hand-editing."
role: player_character
access: [public]
@@ -376,7 +376,7 @@ lines:
# --- Kael Davan FRIEND Arc — Additional Detective Observations ---
# Ticket: #297 review fix | Filling detective monologue gap (7 → 12 Kael lines)
- id: the-terminal_m_d_036
- id: pc-detective_m_d_036
text: "Davan's shift pattern is clean. Too clean. Same bays, same hours, same rotation. Nothing out of place."
role: player_character
access: [public]
@@ -389,7 +389,7 @@ lines:
state: known
tags: [npc, kael, analytical, behavioral]
- id: the-terminal_m_d_037
- id: pc-detective_m_d_037
text: "Davan eats with the same group every shift. Social anchor, or keeping tabs?"
role: player_character
access: [public]
@@ -402,7 +402,7 @@ lines:
state: known
tags: [npc, kael, social, behavioral]
- id: the-terminal_m_d_038
- id: pc-detective_m_d_038
text: "Davan handled that container routing without checking the manifest board. He knows the schedule by heart."
role: player_character
access: [public]
@@ -416,7 +416,7 @@ lines:
priority: 6
tags: [npc, kael, behavioral, tell, competence]
- id: the-terminal_m_d_039
- id: pc-detective_m_d_039
text: "Davan's posture changed when I approached bay four. Subtle. Filing it."
role: player_character
access: [public]
@@ -430,7 +430,7 @@ lines:
priority: 7
tags: [npc, kael, tell, behavioral, friend-arc]
- id: the-terminal_m_d_040
- id: pc-detective_m_d_040
text: "Three manifests with weight discrepancies. All during Davan's shifts. Correlation is not causation. But noted."
role: player_character
access: [public]
@@ -1,7 +1,7 @@
character: smuggler
location: general
lines:
- id: general_m_s_001
- id: pc-smuggler_m_s_001
text: "Another day on Sova. Same air, same hum, same faces."
role: player_character
access: [public]
@@ -10,7 +10,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: general_m_s_002
- id: pc-smuggler_m_s_002
text: "The span gate hum's louder today. Maintenance hasn't touched it in weeks."
role: player_character
access: [public]
@@ -19,7 +19,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: general_m_s_003
- id: pc-smuggler_m_s_003
text: "Twenty minutes between shifts. That's the window. Always has been."
role: player_character
access: [public]
@@ -28,7 +28,7 @@ lines:
trigger: time_idle
tags: [operational, orientation]
- id: general_m_s_004
- id: pc-smuggler_m_s_004
text: "People talk about leaving Sova. Nobody actually does."
role: player_character
access: [public]
@@ -37,7 +37,7 @@ lines:
trigger: time_idle
tags: [reflection]
- id: general_m_s_005
- id: pc-smuggler_m_s_005
text: "Two years of this. Still not used to the gate vibration in my teeth."
role: player_character
access: [public]
@@ -46,7 +46,7 @@ lines:
trigger: hear_sound
tags: [environmental, personal]
- id: general_m_s_006
- id: pc-smuggler_m_s_006
text: "The money's good. The risk's manageable. Keep telling yourself that."
role: player_character
access: [public]
@@ -55,7 +55,7 @@ lines:
trigger: time_idle
tags: [reflection, operational]
- id: general_m_s_007
- id: pc-smuggler_m_s_007
text: "Back again. Same corridors, same shortcuts, same reasons."
role: player_character
access: [public]
@@ -64,7 +64,7 @@ lines:
trigger: return_visit
tags: [arrival]
- id: general_m_s_008
- id: pc-smuggler_m_s_008
text: "Commission patrols are thinner this cycle. Good or bad — hard to say."
role: player_character
access: [public]
@@ -73,7 +73,7 @@ lines:
trigger: observe_anomaly
tags: [operational, caution]
- id: general_m_s_009
- id: pc-smuggler_m_s_009
text: "If anyone asks, I'm clocking overtime. That's always the story."
role: player_character
access: [public]
@@ -82,7 +82,7 @@ lines:
trigger: time_idle
tags: [operational, cover]
- id: general_m_s_010
- id: pc-smuggler_m_s_010
text: "Recycled air tastes different near the outer ring. Sharper. More machine."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: maintenance-corridors
lines:
# --- Arrival + Environmental Flavor ---
- id: maintenance-corridors_m_s_001
- id: pc-smuggler_m_s_001
text: "Service corridor. Dim lights, damp walls, and the constant drip of condensation."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: maintenance-corridors_m_s_002
- id: pc-smuggler_m_s_002
text: "Cooler down here. The ventilation doesn't reach this far."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: maintenance-corridors_m_s_003
- id: pc-smuggler_m_s_003
text: "The access panel's still loose. Good — our route's intact."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, operational]
- id: maintenance-corridors_m_s_004
- id: pc-smuggler_m_s_004
text: "Pipes groaning overhead. Station's old. These corridors are older."
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: hear_sound
tags: [environmental, atmospheric]
- id: maintenance-corridors_m_s_005
- id: pc-smuggler_m_s_005
text: "Water recycling rumbles through the walls down here. Constant."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: maintenance-corridors_m_s_006
- id: pc-smuggler_m_s_006
text: "Back in the maintenance tunnels. Rust and coolant."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: return_visit
tags: [arrival, atmospheric]
- id: maintenance-corridors_m_s_007
- id: pc-smuggler_m_s_007
text: "Emergency lighting only. Just enough to see where you're going."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: enter_location
tags: [environmental]
- id: maintenance-corridors_m_s_008
- id: pc-smuggler_m_s_008
text: "Footsteps echo different down here. You learn to listen."
role: player_character
access: [public]
@@ -77,7 +77,7 @@ lines:
# --- NPC + Operational ---
- id: maintenance-corridors_m_s_009
- id: pc-smuggler_m_s_009
text: "Renn's chalk marks on the wall. Route's been used recently."
role: player_character
access: [public]
@@ -86,7 +86,7 @@ lines:
trigger: observe_anomaly
tags: [operational, renn]
- id: maintenance-corridors_m_s_010
- id: pc-smuggler_m_s_010
text: "Someone left a crate at junction B-4. Not one of ours."
role: player_character
access: [public]
@@ -95,7 +95,7 @@ lines:
trigger: observe_anomaly
tags: [operational, caution]
- id: maintenance-corridors_m_s_011
- id: pc-smuggler_m_s_011
text: "The temp storage hatch is warm. Cargo cycled through recently."
role: player_character
access: [public]
@@ -104,7 +104,7 @@ lines:
trigger: enter_location
tags: [operational]
- id: maintenance-corridors_m_s_012
- id: pc-smuggler_m_s_012
text: "Scratches on the lock plate. Someone's been forcing this access point."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
trigger: observe_anomaly
tags: [environmental, caution]
- id: maintenance-corridors_m_s_013
- id: pc-smuggler_m_s_013
text: "Timer says twelve minutes until the oversight gap closes. Plenty."
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: time_idle
tags: [operational]
- id: maintenance-corridors_m_s_014
- id: pc-smuggler_m_s_014
text: "That's Renn's jacket on the pipe junction. He was here."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: observe_anomaly
tags: [npc, renn, operational]
- id: maintenance-corridors_m_s_015
- id: pc-smuggler_m_s_015
text: "Voices echoing from the junction. Can't make out words yet."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: hear_sound
tags: [caution]
- id: maintenance-corridors_m_s_016
- id: pc-smuggler_m_s_016
text: "The route through B-section is clear. Voss kept his word."
role: player_character
access: [public]
@@ -151,7 +151,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: maintenance-corridors_m_s_017
- id: pc-smuggler_m_s_017
text: "Corridor B-7. This is where I saw Kael. With someone I don't know."
role: player_character
access: [public]
@@ -165,7 +165,7 @@ lines:
priority: 8
tags: [npc, kael, contradiction, friend-arc]
- id: maintenance-corridors_m_s_018
- id: pc-smuggler_m_s_018
text: "Fresh boot prints in the dust. Two sets. Different sizes."
role: player_character
access: [public]
@@ -178,7 +178,7 @@ lines:
min_confidence: suspects
tags: [evidence, kael, friend-arc]
- id: maintenance-corridors_m_s_019
- id: pc-smuggler_m_s_019
text: "New scratch marks on the service hatch. Someone's using our route."
role: player_character
access: [public]
@@ -192,7 +192,7 @@ lines:
priority: 7
tags: [operational, caution]
- id: maintenance-corridors_m_s_020
- id: pc-smuggler_m_s_020
text: "The container in temp was supposed to move last shift. It didn't. Why?"
role: player_character
access: [public]
@@ -205,7 +205,7 @@ lines:
min_confidence: knows_of
tags: [operational, contraband]
- id: maintenance-corridors_m_s_021
- id: pc-smuggler_m_s_021
text: "Camera's been repositioned at junction C-2. Who authorized that?"
role: player_character
access: [public]
@@ -219,7 +219,7 @@ lines:
priority: 7
tags: [investigation, caution]
- id: maintenance-corridors_m_s_022
- id: pc-smuggler_m_s_022
text: "If Kael's meeting people down here without telling us, the whole route's exposed."
role: player_character
access: [public]
@@ -233,7 +233,7 @@ lines:
priority: 8
tags: [npc, kael, operational, friend-arc]
- id: maintenance-corridors_m_s_023
- id: pc-smuggler_m_s_023
text: "Sealed containers don't end up in temp storage by accident. Someone rerouted."
role: player_character
access: [public]
@@ -246,7 +246,7 @@ lines:
min_confidence: knows_details
tags: [operational, contraband]
- id: maintenance-corridors_m_s_024
- id: pc-smuggler_m_s_024
text: "Kael's not answering his lattice. He was supposed to meet me here."
role: player_character
access: [public]
@@ -260,7 +260,7 @@ lines:
priority: 7
tags: [npc, kael, concern, friend-arc]
- id: maintenance-corridors_m_s_025
- id: pc-smuggler_m_s_025
text: "Someone wiped the dust off this hatch. Recently. Carefully."
role: player_character
access: [public]
@@ -8,7 +8,7 @@ lines:
# Arc: station hum → spatial count → fog boundary → thin crowd → Kael sighting ×2 → news ticker → sound ping → time countdown → settle ×2.
# All lines fully schema-compliant per D-035 (role, access, trust, situation present).
- id: opening_m_s_001
- id: pc-smuggler_m_s_001
text: "Station hum. B-flat, steady — freight's moving."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
tags: [opening-hook, tutorial-arc, atmospheric]
notes: "Opening sensation. Station audio registers first — the hum tells her freight is running. Teaches: audio environment carries operational information (D-074)."
- id: opening_m_s_002
- id: pc-smuggler_m_s_002
text: "Sixteen bays, three arms running. Standard morning."
role: player_character
access: [public]
@@ -34,7 +34,7 @@ lines:
tags: [opening-hook, tutorial-arc, orientation]
notes: "Spatial orientation. She clocks the floor in two beats — count of bays, count of running arms. Everything in fragments. Teaches: the terminal layout."
- id: opening_m_s_003
- id: pc-smuggler_m_s_003
text: "Haze past bay eight today. Can't see the far dock from here."
role: player_character
access: [public]
@@ -47,7 +47,7 @@ lines:
tags: [opening-hook, tutorial-arc, fog, perception]
notes: "Fog boundary. She notices the edge of her vision as a fact of the floor, not a mechanic. Teaches: perception range has limits (D-015, D-046)."
- id: opening_m_s_004
- id: pc-smuggler_m_s_004
text: "Morning crew's spread thin. Good. Room to move."
role: player_character
access: [public]
@@ -60,7 +60,7 @@ lines:
tags: [opening-hook, tutorial-arc]
notes: "Light crowd = operational comfort. 'Good.' standalone verbal tic (section 1.7). Teaches: NPC density affects movement options."
- id: opening_m_s_005
- id: pc-smuggler_m_s_005
text: "Kael's at dock three. Right where he should be."
role: player_character
access: [public]
@@ -73,7 +73,7 @@ lines:
tags: [opening-hook, tutorial-arc, kael, friend-arc]
notes: "First Kael sighting. Warm, immediate — she locates him before she finishes reading the floor. Teaches: named NPCs are identifiable via insert overlay (D-033 green entity)."
- id: opening_m_s_006
- id: pc-smuggler_m_s_006
text: "Already has his clipboard. Day's organized before it starts. Two years, same routine."
role: player_character
access: [public]
@@ -86,7 +86,7 @@ lines:
tags: [opening-hook, tutorial-arc, kael, friend-arc]
notes: "Kael warmth through behavior-reading, not labeling. Three beats: observation → interpretation → familiarity. 'Two years, same routine' says she knows his patterns intimately. Establishes: Kael is trusted, competent, reliable — the baseline before the friend arc erodes it."
- id: opening_m_s_007
- id: pc-smuggler_m_s_007
text: "Bulletin board's got a new Commission notice. Lattice registration updates. Not today's problem."
role: player_character
access: [public]
@@ -99,7 +99,7 @@ lines:
tags: [opening-hook, tutorial-arc, news-ticker]
notes: "News ticker glance. She reads and dismisses it. The Commission exists as background noise — relevant only when it stops being ignorable. Teaches: environmental text exists, player can engage or skip. D-037 irony: lattice registration is exactly what the ring circumvents."
- id: opening_m_s_008
- id: pc-smuggler_m_s_008
text: "Span gate echo from dock fourteen. Something inbound, running heavy by the sound of it."
role: player_character
access: [public]
@@ -112,7 +112,7 @@ lines:
tags: [opening-hook, tutorial-arc, sound, perception]
notes: "First sound ping at the edge of range. She interprets pitch instantly — the echo tells her load weight. Teaches: sound carries operational information (D-018, D-074 sound model). Sound channel is meaningful."
- id: opening_m_s_009
- id: pc-smuggler_m_s_009
text: "Shift board says light cargo today. Fifty minutes before Voss does his rounds."
role: player_character
access: [public]
@@ -125,7 +125,7 @@ lines:
tags: [opening-hook, tutorial-arc, operational]
notes: "'Fifty minutes' = time-as-countdown verbal tic (section 1.7). She's already calculating windows. Teaches: time and NPC patrol rhythms are trackable."
- id: opening_m_s_010
- id: pc-smuggler_m_s_010
text: "Same floor, same faces, same hum. Could be worse."
role: player_character
access: [public]
@@ -138,7 +138,7 @@ lines:
tags: [opening-hook, tutorial-arc, atmospheric]
notes: "Settling line. Baseline contentment. Parallel three-beat structure then understatement. 'Could be worse' is her comfort language — not enthusiastic, just settled. Voice anchor for confidence register (section 1.8)."
- id: opening_m_s_011
- id: pc-smuggler_m_s_011
text: "Dock's clean this morning. Good start."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
# --- Tell Observations (5 core tells from pc-as-npc-spec.md section 8) ---
- id: general_m_s_011
- id: pc-smuggler_m_s_001
text: "Commission officer just stood there after talking to Voss. Making notes. Not good."
role: player_character
access: [public]
@@ -25,7 +25,7 @@ lines:
state: known
tags: [npc, pc-detective, tell, note-taking, caution]
- id: general_m_s_012
- id: pc-smuggler_m_s_002
text: "That investigator is working the hub section by section. Not browsing. Searching."
role: player_character
access: [public]
@@ -38,7 +38,7 @@ lines:
state: known
tags: [npc, pc-detective, tell, systematic-movement, caution]
- id: general_m_s_013
- id: pc-smuggler_m_s_003
text: "Commission's parked at the manifest terminal again. Third time today."
role: player_character
access: [public]
@@ -51,7 +51,7 @@ lines:
state: known
tags: [npc, pc-detective, tell, manifest-fixation, caution]
- id: general_m_s_014
- id: pc-smuggler_m_s_004
text: "They're watching. From the corner, where they can see the whole dock floor. Professional."
role: player_character
access: [public]
@@ -64,7 +64,7 @@ lines:
state: known
tags: [npc, pc-detective, tell, observation-positioning, caution]
- id: general_m_s_015
- id: pc-smuggler_m_s_005
text: "The investigator and Sera again. Old friends? Or is Sera reporting to them?"
role: player_character
access: [public]
@@ -82,7 +82,7 @@ lines:
# --- Operational Awareness (smuggler's threat assessment) ---
- id: general_m_s_016
- id: pc-smuggler_m_s_006
text: "New Commission face at the hub. Keep your head down. Don't give them anything."
role: player_character
access: [public]
@@ -96,7 +96,7 @@ lines:
priority: 8
tags: [npc, pc-detective, operational, caution, first-sighting]
- id: general_m_s_017
- id: pc-smuggler_m_s_007
text: "That investigator's been here all morning. Nobody stays that long for a routine check."
role: player_character
access: [public]
@@ -110,7 +110,7 @@ lines:
priority: 6
tags: [npc, pc-detective, suspicion, operational]
- id: general_m_s_018
- id: pc-smuggler_m_s_008
text: "Commission's at the bar. Sitting with Sera. Watching the room like they own it."
role: player_character
access: [public]
@@ -23,7 +23,7 @@ lines:
# --- Arrival ---
- id: smuggling-hold_m_s_001
- id: pc-smuggler_m_s_001
text: "Sub-level. The kind of space nobody finds unless you know where to look."
role: player_character
access: [public]
@@ -32,7 +32,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, atmospheric, operational]
- id: smuggling-hold_m_s_002
- id: pc-smuggler_m_s_002
text: "Coolant smell. No ventilation down here — just recycled air and waiting."
role: player_character
access: [public]
@@ -41,7 +41,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, atmospheric, sensory]
- id: smuggling-hold_m_s_003
- id: pc-smuggler_m_s_003
text: "Access hatch sealed from the inside. Good. Route's still ours."
role: player_character
access: [public]
@@ -50,7 +50,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, operational]
- id: smuggling-hold_m_s_004
- id: pc-smuggler_m_s_004
text: "Three containers in temp. Right where they should be."
role: player_character
access: [public]
@@ -63,7 +63,7 @@ lines:
min_confidence: knows_of
tags: [arrival, operational, contraband]
- id: smuggling-hold_m_s_005
- id: pc-smuggler_m_s_005
text: "Back in the hold. Dust on the floor shows the last two paths in. Both mine."
role: player_character
access: [public]
@@ -72,7 +72,7 @@ lines:
situation: [arrival, routine]
tags: [arrival, operational]
- id: smuggling-hold_m_s_006
- id: pc-smuggler_m_s_006
text: "Nobody's been here. Good. That's how it should feel."
role: player_character
access: [public]
@@ -83,7 +83,7 @@ lines:
# --- Perception / Sound ---
- id: smuggling-hold_m_s_007
- id: pc-smuggler_m_s_007
text: "The water recycler's on the other side of that wall. Loud enough to cover conversation."
role: player_character
access: [public]
@@ -92,7 +92,7 @@ lines:
situation: [routine]
tags: [sensory, environmental, operational]
- id: smuggling-hold_m_s_008
- id: pc-smuggler_m_s_008
text: "Footsteps above. Maintenance crew — they stay up top. Always."
role: player_character
access: [public]
@@ -101,7 +101,7 @@ lines:
situation: [routine]
tags: [sensory, environmental, caution]
- id: smuggling-hold_m_s_009
- id: pc-smuggler_m_s_009
text: "That sound. Pressure shift. Someone opened the main hatch."
role: player_character
access: [public]
@@ -111,7 +111,7 @@ lines:
priority: 7
tags: [sensory, caution]
- id: smuggling-hold_m_s_010
- id: pc-smuggler_m_s_010
text: "Pipe drone changes pitch when cargo shifts weight in the upper tier. That's cargo moving."
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
# --- Time Idle / Ruminative ---
- id: smuggling-hold_m_s_011
- id: pc-smuggler_m_s_011
text: "Fifteen minutes until the oversight window closes. Plenty of time. Probably."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
situation: [shift_transition]
tags: [operational, atmospheric]
- id: smuggling-hold_m_s_012
- id: pc-smuggler_m_s_012
text: "Medical-grade lattice components. People need these. That's still the reason."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
situation: [routine]
tags: [atmospheric, contraband]
- id: smuggling-hold_m_s_013
- id: pc-smuggler_m_s_013
text: "Twelve minutes of reduced oversight. Stop counting and do something useful."
role: player_character
access: [public]
@@ -149,7 +149,7 @@ lines:
situation: [shift_transition]
tags: [operational]
- id: smuggling-hold_m_s_014
- id: pc-smuggler_m_s_014
text: "How many times have I been in this room telling myself it's almost done?"
role: player_character
access: [public]
@@ -158,7 +158,7 @@ lines:
situation: [routine]
tags: [atmospheric, personal]
- id: smuggling-hold_m_s_015
- id: pc-smuggler_m_s_015
text: "Quiet. The right kind. Not the wrong kind."
role: player_character
access: [public]
@@ -169,7 +169,7 @@ lines:
# --- Anomaly / Investigation ---
- id: smuggling-hold_m_s_016
- id: pc-smuggler_m_s_016
text: "Container 4471's been opened. Not by me. Not by anyone I authorized."
role: player_character
access: [public]
@@ -179,7 +179,7 @@ lines:
priority: 8
tags: [operational, caution, contraband]
- id: smuggling-hold_m_s_017
- id: pc-smuggler_m_s_017
text: "Dust disturbed at the secondary hatch. Recent. Someone's been using the back route."
role: player_character
access: [public]
@@ -188,7 +188,7 @@ lines:
situation: [routine]
tags: [caution, operational]
- id: smuggling-hold_m_s_018
- id: pc-smuggler_m_s_018
text: "Scratches on the access panel. New ones, over the old ones. Different tool."
role: player_character
access: [public]
@@ -197,7 +197,7 @@ lines:
situation: [routine]
tags: [caution, environmental]
- id: smuggling-hold_m_s_019
- id: pc-smuggler_m_s_019
text: "The temp unit's running warm. Someone moved cargo through here fast."
role: player_character
access: [public]
@@ -208,7 +208,7 @@ lines:
# --- Knowledge-Gated: Kael Arc ---
- id: smuggling-hold_m_s_020
- id: pc-smuggler_m_s_020
text: "Kael used to wait for me here. The one place where we could actually talk."
role: player_character
access: [public]
@@ -222,7 +222,7 @@ lines:
priority: 6
tags: [npc, kael, atmospheric, friend-arc]
- id: smuggling-hold_m_s_021
- id: pc-smuggler_m_s_021
text: "Kael's not answering. Should be here by now. Should have been here ten minutes ago."
role: player_character
access: [public]
@@ -236,7 +236,7 @@ lines:
priority: 7
tags: [npc, kael, concern, friend-arc]
- id: smuggling-hold_m_s_022
- id: pc-smuggler_m_s_022
text: "If Kael talked to someone in that corridor — someone outside the ring — and then this container got touched... that's not coincidence."
role: player_character
access: [public]
@@ -252,7 +252,7 @@ lines:
priority: 9
tags: [npc, kael, investigation, contraband, friend-arc]
- id: smuggling-hold_m_s_023
- id: pc-smuggler_m_s_023
text: "The manifest Kael helped me falsify is in that container. If he talked, they already know."
role: player_character
access: [public]
@@ -268,7 +268,7 @@ lines:
priority: 9
tags: [npc, kael, operational, contraband, friend-arc, contaminated-trust]
- id: smuggling-hold_m_s_024
- id: pc-smuggler_m_s_024
text: "Renn's mark is here. The route's still active. So Kael hasn't burned everything. Yet."
role: player_character
access: [public]
@@ -284,7 +284,7 @@ lines:
# --- Post-Conversation ---
- id: smuggling-hold_m_s_025
- id: pc-smuggler_m_s_025
text: "Renn didn't ask questions. That's either loyalty or he already knows."
role: player_character
access: [public]
@@ -293,7 +293,7 @@ lines:
situation: [routine]
tags: [npc, renn, operational]
- id: smuggling-hold_m_s_026
- id: pc-smuggler_m_s_026
text: "One more run. That's what I keep telling myself. Has been for eight months."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: the-last-shift
lines:
# --- Arrival + Environmental Flavor ---
- id: the-last-shift_m_s_001
- id: pc-smuggler_m_s_001
text: "Lera's. Grain spirit and noise. Could use both right now."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: the-last-shift_m_s_002
- id: pc-smuggler_m_s_002
text: "Bar's half full. The after-shift crowd's starting to filter in."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [arrival]
- id: the-last-shift_m_s_003
- id: pc-smuggler_m_s_003
text: "Lera's got the Meridian feed running. Same headlines, different day."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, environmental]
- id: the-last-shift_m_s_004
- id: pc-smuggler_m_s_004
text: "Smells like fried protein and cheap grain spirit. Perfect."
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: the-last-shift_m_s_005
- id: pc-smuggler_m_s_005
text: "My booth's free. Small victories."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: enter_location
tags: [arrival, personal]
- id: the-last-shift_m_s_006
- id: pc-smuggler_m_s_006
text: "The overhead light's still buzzing. Lera says it adds character."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: the-last-shift_m_s_007
- id: pc-smuggler_m_s_007
text: "Music's louder tonight. Lera's in a good mood, or drowning something out."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: hear_sound
tags: [environmental, atmospheric]
- id: the-last-shift_m_s_008
- id: pc-smuggler_m_s_008
text: "Back at Lera's. Same booth, same drink, same crowd."
role: player_character
access: [public]
@@ -75,7 +75,7 @@ lines:
trigger: return_visit
tags: [arrival]
- id: the-last-shift_m_s_009
- id: pc-smuggler_m_s_009
text: "The ventilation rattles when the kitchen's on. You learn to talk over it."
role: player_character
access: [public]
@@ -84,7 +84,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: the-last-shift_m_s_010
- id: pc-smuggler_m_s_010
text: "Quiet night at Lera's. Fewer people means fewer ears."
role: player_character
access: [public]
@@ -95,7 +95,7 @@ lines:
# --- NPC Commentary ---
- id: the-last-shift_m_s_011
- id: pc-smuggler_m_s_011
text: "Lera's behind the bar. Good — she keeps things steady."
role: player_character
access: [public]
@@ -104,7 +104,7 @@ lines:
trigger: observe_npc
tags: [npc, lera]
- id: the-last-shift_m_s_012
- id: pc-smuggler_m_s_012
text: "Kael's at the corner table. Looks relaxed. That's something."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
trigger: observe_npc
tags: [npc, kael]
- id: the-last-shift_m_s_013
- id: pc-smuggler_m_s_013
text: "Torek's buying rounds again. That man doesn't know the word 'discreet.'"
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: observe_npc
tags: [npc, torek, caution]
- id: the-last-shift_m_s_014
- id: pc-smuggler_m_s_014
text: "Sera's here. Commission field tech. She keeps to herself — mostly."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: observe_npc
tags: [npc, sera, commission]
- id: the-last-shift_m_s_015
- id: pc-smuggler_m_s_015
text: "Naia's laughing at something Lera said. Nice to see her relaxed."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: observe_npc
tags: [npc, naia]
- id: the-last-shift_m_s_016
- id: pc-smuggler_m_s_016
text: "Old Pael's in his usual spot. Hasn't moved in three hours. Impressive."
role: player_character
access: [public]
@@ -149,7 +149,7 @@ lines:
trigger: observe_npc
tags: [npc, atmospheric]
- id: the-last-shift_m_s_017
- id: pc-smuggler_m_s_017
text: "Lera just cut someone off. Smooth about it, but firm."
role: player_character
access: [public]
@@ -158,7 +158,7 @@ lines:
trigger: witness_interaction
tags: [npc, lera]
- id: the-last-shift_m_s_018
- id: pc-smuggler_m_s_018
text: "Kael and Naia at the bar. Leaning into each other. They look good together."
role: player_character
access: [public]
@@ -167,7 +167,7 @@ lines:
trigger: witness_interaction
tags: [npc, kael, naia]
- id: the-last-shift_m_s_019
- id: pc-smuggler_m_s_019
text: "Voss is drinking alone. Not his usual pace."
role: player_character
access: [public]
@@ -176,7 +176,7 @@ lines:
trigger: observe_npc
tags: [npc, voss]
- id: the-last-shift_m_s_020
- id: pc-smuggler_m_s_020
text: "That new face in the corner — been nursing the same drink for an hour."
role: player_character
access: [public]
@@ -187,7 +187,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: the-last-shift_m_s_021
- id: pc-smuggler_m_s_021
text: "Sera left when Torek walked in. Coincidence? Maybe."
role: player_character
access: [public]
@@ -200,7 +200,7 @@ lines:
min_confidence: suspects
tags: [npc, sera, torek, pattern]
- id: the-last-shift_m_s_022
- id: pc-smuggler_m_s_022
text: "Torek just paid cash credit, not station account. Sloppy."
role: player_character
access: [public]
@@ -213,7 +213,7 @@ lines:
min_confidence: knows_of
tags: [npc, torek, operational]
- id: the-last-shift_m_s_023
- id: pc-smuggler_m_s_023
text: "Lera gave me that look. The one that says 'not here, not now.'"
role: player_character
access: [public]
@@ -226,7 +226,7 @@ lines:
min_confidence: knows_details
tags: [npc, lera, operational]
- id: the-last-shift_m_s_024
- id: pc-smuggler_m_s_024
text: "Naia looks tired. Has Kael been coming home late again?"
role: player_character
access: [public]
@@ -239,7 +239,7 @@ lines:
state: known
tags: [npc, naia, kael, concern]
- id: the-last-shift_m_s_025
- id: pc-smuggler_m_s_025
text: "The detective is here. At the bar. My bar."
role: player_character
access: [public]
@@ -253,7 +253,7 @@ lines:
priority: 8
tags: [investigation, caution]
- id: the-last-shift_m_s_026
- id: pc-smuggler_m_s_026
text: "Sera and the detective sharing a booth. Commission people stick together."
role: player_character
access: [public]
@@ -266,7 +266,7 @@ lines:
min_confidence: knows_of
tags: [npc, sera, investigation]
- id: the-last-shift_m_s_027
- id: pc-smuggler_m_s_027
text: "Kael didn't save my seat tonight. First time in months."
role: player_character
access: [public]
@@ -280,7 +280,7 @@ lines:
priority: 8
tags: [npc, kael, contaminated-trust, friend-arc]
- id: the-last-shift_m_s_028
- id: pc-smuggler_m_s_028
text: "The back room light's on. Nils is here, then."
role: player_character
access: [public]
@@ -293,7 +293,7 @@ lines:
min_confidence: knows_details
tags: [operational, nils]
- id: the-last-shift_m_s_029
- id: pc-smuggler_m_s_029
text: "Kael bought Naia a drink but barely looked at her. That's new."
role: player_character
access: [public]
@@ -306,7 +306,7 @@ lines:
min_confidence: suspects
tags: [npc, kael, naia, tell, friend-arc]
- id: the-last-shift_m_s_030
- id: pc-smuggler_m_s_030
text: "Everyone's a little too quiet tonight. Something happened. I can feel it."
role: player_character
access: [public]
@@ -326,7 +326,7 @@ lines:
# perspective. On replay, these lines gain new meaning when the player
# understands Sera's concealment arc from the detective playthrough.
- id: the-last-shift_m_s_031
- id: pc-smuggler_m_s_031
text: "Sera's at the Commission kiosk. Third time this shift. Nobody recalibrates that much."
role: player_character
access: [public]
@@ -340,7 +340,7 @@ lines:
priority: 6
tags: [npc, sera, commission, pattern, dual-lens]
- id: the-last-shift_m_s_032
- id: pc-smuggler_m_s_032
text: "Naia trusts Sera. More than most people here. That says something."
role: player_character
access: [public]
@@ -349,7 +349,7 @@ lines:
trigger: witness_interaction
tags: [npc, sera, naia, social, dual-lens]
- id: the-last-shift_m_s_033
- id: pc-smuggler_m_s_033
text: "Sera was studying the freight display. For a calibration tech, that's a lot of manifest reading."
role: player_character
access: [public]
@@ -363,7 +363,7 @@ lines:
priority: 6
tags: [npc, sera, tell, cargo, dual-lens]
- id: the-last-shift_m_s_034
- id: pc-smuggler_m_s_034
text: "That's three times Sera's bailed when Torek showed up. Maybe she owes him money."
role: player_character
access: [public]
@@ -377,7 +377,7 @@ lines:
priority: 7
tags: [npc, sera, torek, pattern, dual-lens, replay-payload]
- id: the-last-shift_m_s_035
- id: pc-smuggler_m_s_035
text: "Sera offered the detective compliance records. Helpful -- or professional courtesy?"
role: player_character
access: [public]
@@ -390,7 +390,7 @@ lines:
min_confidence: knows_of
tags: [npc, sera, investigation, caution, dual-lens]
- id: the-last-shift_m_s_036
- id: pc-smuggler_m_s_036
text: "Something about Sera tonight. Same smile, different eyes."
role: player_character
access: [public]
@@ -403,7 +403,7 @@ lines:
state: known
tags: [npc, sera, behavioral, dual-lens]
- id: the-last-shift_m_s_037
- id: pc-smuggler_m_s_037
text: "Commission tech who drinks at a dock bar. Either lonely or embedded. Either way -- careful."
role: player_character
access: [public]
@@ -412,7 +412,7 @@ lines:
trigger: observe_npc
tags: [npc, sera, commission, assessment, dual-lens]
- id: the-last-shift_m_s_038
- id: pc-smuggler_m_s_038
text: "Sera knows the shift rotations better than Voss. That's not calibration. That's attention."
role: player_character
access: [public]
@@ -3,7 +3,7 @@ location: the-terminal
lines:
# --- Arrival + Environmental Flavor ---
- id: the-terminal_m_s_001
- id: pc-smuggler_m_s_001
text: "Morning shift. Recycled air and cargo lubricant. Home sweet home."
role: player_character
access: [public]
@@ -12,7 +12,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: the-terminal_m_s_002
- id: pc-smuggler_m_s_002
text: "The loading arms are cycling. Freight's on schedule — for once."
role: player_character
access: [public]
@@ -21,7 +21,7 @@ lines:
trigger: enter_location
tags: [arrival, operational]
- id: the-terminal_m_s_003
- id: pc-smuggler_m_s_003
text: "Shift board's already full. Voss has everyone running double today."
role: player_character
access: [public]
@@ -30,7 +30,7 @@ lines:
trigger: enter_location
tags: [arrival, operational]
- id: the-terminal_m_s_004
- id: pc-smuggler_m_s_004
text: "Cargo lubricant and ozone. You'd think I'd stop noticing."
role: player_character
access: [public]
@@ -39,7 +39,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: the-terminal_m_s_005
- id: pc-smuggler_m_s_005
text: "Loading arm three is grinding again. Someone should file that."
role: player_character
access: [public]
@@ -48,7 +48,7 @@ lines:
trigger: hear_sound
tags: [environmental]
- id: the-terminal_m_s_006
- id: pc-smuggler_m_s_006
text: "Containers stacked six high in bay four. That's new."
role: player_character
access: [public]
@@ -57,7 +57,7 @@ lines:
trigger: observe_anomaly
tags: [environmental, operational]
- id: the-terminal_m_s_007
- id: pc-smuggler_m_s_007
text: "The freight manifest board's flickering. Always flickering."
role: player_character
access: [public]
@@ -66,7 +66,7 @@ lines:
trigger: enter_location
tags: [environmental]
- id: the-terminal_m_s_008
- id: pc-smuggler_m_s_008
text: "Dock lights are on half-cycle. Budget cuts again."
role: player_character
access: [public]
@@ -75,7 +75,7 @@ lines:
trigger: enter_location
tags: [environmental, atmospheric]
- id: the-terminal_m_s_009
- id: pc-smuggler_m_s_009
text: "Back at the hub. Shift transition in fifty minutes."
role: player_character
access: [public]
@@ -84,7 +84,7 @@ lines:
trigger: return_visit
tags: [arrival, operational]
- id: the-terminal_m_s_010
- id: pc-smuggler_m_s_010
text: "Quiet morning. Containers moving, nobody talking. I like it this way."
role: player_character
access: [public]
@@ -93,7 +93,7 @@ lines:
trigger: enter_location
tags: [arrival, atmospheric]
- id: the-terminal_m_s_011
- id: pc-smuggler_m_s_011
text: "Bay three's been sealed off. Inspection, or something else?"
role: player_character
access: [public]
@@ -102,7 +102,7 @@ lines:
trigger: observe_anomaly
tags: [environmental, caution]
- id: the-terminal_m_s_012
- id: pc-smuggler_m_s_012
text: "The conveyor hums at a different pitch when it's loaded heavy. This one's heavy."
role: player_character
access: [public]
@@ -113,7 +113,7 @@ lines:
# --- NPC Commentary ---
- id: the-terminal_m_s_013
- id: pc-smuggler_m_s_013
text: "Kael's already at the dock. Good. The day's better when he's on shift."
role: player_character
access: [public]
@@ -122,7 +122,7 @@ lines:
trigger: observe_npc
tags: [npc, kael, warm]
- id: the-terminal_m_s_014
- id: pc-smuggler_m_s_014
text: "Voss is pacing. Never a good sign."
role: player_character
access: [public]
@@ -131,7 +131,7 @@ lines:
trigger: observe_npc
tags: [npc, voss, caution]
- id: the-terminal_m_s_015
- id: pc-smuggler_m_s_015
text: "Maret's got her scheduling face on. Tight lips, fast hands on the terminal."
role: player_character
access: [public]
@@ -140,7 +140,7 @@ lines:
trigger: observe_npc
tags: [npc, maret]
- id: the-terminal_m_s_016
- id: pc-smuggler_m_s_016
text: "New face at dock seven. Temp worker, or someone I should know about?"
role: player_character
access: [public]
@@ -149,7 +149,7 @@ lines:
trigger: observe_npc
tags: [npc, caution]
- id: the-terminal_m_s_017
- id: pc-smuggler_m_s_017
text: "Drin's at inspection again. Going through the motions, same as always."
role: player_character
access: [public]
@@ -158,7 +158,7 @@ lines:
trigger: observe_npc
tags: [npc, drin]
- id: the-terminal_m_s_018
- id: pc-smuggler_m_s_018
text: "Renn's running late. Third time this week."
role: player_character
access: [public]
@@ -167,7 +167,7 @@ lines:
trigger: observe_npc
tags: [npc, renn, operational]
- id: the-terminal_m_s_019
- id: pc-smuggler_m_s_019
text: "Voss and Maret talking by the schedule board. Both look tense."
role: player_character
access: [public]
@@ -176,7 +176,7 @@ lines:
trigger: witness_interaction
tags: [npc, voss, maret]
- id: the-terminal_m_s_020
- id: pc-smuggler_m_s_020
text: "Kael nodded at me across the bay. Business as usual."
role: player_character
access: [public]
@@ -185,7 +185,7 @@ lines:
trigger: observe_npc
tags: [npc, kael]
- id: the-terminal_m_s_021
- id: pc-smuggler_m_s_021
text: "Someone I don't recognize talking to Drin. Drin's smiling. Drin never smiles."
role: player_character
access: [public]
@@ -194,7 +194,7 @@ lines:
trigger: witness_interaction
tags: [npc, drin, anomaly]
- id: the-terminal_m_s_022
- id: pc-smuggler_m_s_022
text: "Torek's on the dock. Shouldn't be — his shift ended two hours ago."
role: player_character
access: [public]
@@ -203,7 +203,7 @@ lines:
trigger: observe_anomaly
tags: [npc, torek, caution]
- id: the-terminal_m_s_023
- id: pc-smuggler_m_s_023
text: "Nils isn't here yet. That's unusual."
role: player_character
access: [public]
@@ -212,7 +212,7 @@ lines:
trigger: observe_anomaly
tags: [npc, nils, operational]
- id: the-terminal_m_s_024
- id: pc-smuggler_m_s_024
text: "Kael's eating alone today. He always eats with me."
role: player_character
access: [public]
@@ -223,7 +223,7 @@ lines:
# --- Knowledge-Gated Lines ---
- id: the-terminal_m_s_025
- id: pc-smuggler_m_s_025
text: "Kael keeps checking his lattice. Waiting for a message? Not like him to be jumpy."
role: player_character
access: [public]
@@ -237,7 +237,7 @@ lines:
priority: 7
tags: [npc, kael, tell, friend-arc]
- id: the-terminal_m_s_026
- id: pc-smuggler_m_s_026
text: "Container 4471's been in temp since yesterday. That's not the normal routing."
role: player_character
access: [public]
@@ -250,7 +250,7 @@ lines:
min_confidence: knows_details
tags: [operational, contraband]
- id: the-terminal_m_s_027
- id: pc-smuggler_m_s_027
text: "Voss changed the rotation again. Third time this month. Covering something."
role: player_character
access: [public]
@@ -263,7 +263,7 @@ lines:
min_confidence: knows_of
tags: [npc, voss, operational]
- id: the-terminal_m_s_028
- id: pc-smuggler_m_s_028
text: "That detective's back. Asking questions near bay four. Stay calm."
role: player_character
access: [public]
@@ -277,7 +277,7 @@ lines:
priority: 8
tags: [investigation, caution]
- id: the-terminal_m_s_029
- id: pc-smuggler_m_s_029
text: "Maret hasn't looked at me all shift. She noticed something. I know she did."
role: player_character
access: [public]
@@ -292,7 +292,7 @@ lines:
priority: 6
tags: [npc, maret, paranoia]
- id: the-terminal_m_s_030
- id: pc-smuggler_m_s_030
text: "Shift transition in ten. If the route's clean, we move tonight."
role: player_character
access: [public]
@@ -305,7 +305,7 @@ lines:
min_confidence: knows_details
tags: [operational, contraband]
- id: the-terminal_m_s_031
- id: pc-smuggler_m_s_031
text: "Kael was in corridor B-7 last night. Off-shift. Off-route. That's not nothing."
role: player_character
access: [public]
@@ -319,7 +319,7 @@ lines:
priority: 8
tags: [npc, kael, contradiction, friend-arc]
- id: the-terminal_m_s_032
- id: pc-smuggler_m_s_032
text: "Who was Kael talking to? Not anyone from our rotation. I'd know the face."
role: player_character
access: [public]
@@ -333,7 +333,7 @@ lines:
priority: 9
tags: [npc, kael, contradiction, friend-arc]
- id: the-terminal_m_s_033
- id: pc-smuggler_m_s_033
text: "He looked left before answering. He always looks left when he's lying."
role: player_character
access: [public]
@@ -347,7 +347,7 @@ lines:
priority: 9
tags: [npc, kael, tell, friend-arc]
- id: the-terminal_m_s_034
- id: pc-smuggler_m_s_034
text: "Torek's spending too much at Lera's. Someone's going to notice."
role: player_character
access: [public]
@@ -360,7 +360,7 @@ lines:
min_confidence: knows_of
tags: [npc, torek, operational, caution]
- id: the-terminal_m_s_035
- id: pc-smuggler_m_s_035
text: "Same old Kael. Same jokes, same routine. ...What are you not telling me?"
role: player_character
access: [public]
+13 -6
View File
@@ -139,11 +139,10 @@ Tracked questions awaiting discussion or resolution.
- **Source:** Knowledge Graph & Information Boundaries Workshop (Gestalt Round 1)
### Q-025: Knowledge graph cap and eviction strategy
- **Status:** Open
- **Question:** At what point does an NPC's knowledge graph need entry eviction? What is the eviction policy? Options: MAX_ENTITY_KNOWLEDGE constant (e.g., 100 entities), LRU by `last_observed_tick`, lowest confidence first, hybrid approach. How are evicted entries handled — complete removal, or archival to "forgotten" state that can be refreshed?
- **Context:** Memory budget analysis shows ~14 KB per NPC with 50 entities + 20 facts. At 80 Active NPCs this is ~1.1 MB, well within budget. However, long-running sessions or NPCs with high interaction rates could accumulate entries. Eviction policy affects gameplay: forgetting low-confidence rumors creates natural information decay; forgetting old observations simulates memory limits.
- **Assigned to:** Tyre, Dudley
- **Source:** Knowledge Graph & Information Boundaries Workshop (Dudley Round 1, section 8.3)
- **Status:** Resolved — not needed for v0.1/v0.2. Revisit if gossip propagation (Q-024) causes unbounded growth.
- **Question:** At what point does an NPC's knowledge graph need entry eviction? What is the eviction policy?
- **Resolution (2026-02-23):** The v0.1 Gauntlet has ~16 NPCs. At worst case (every NPC knows every other NPC + 50 facts), total KG memory is ~30 KB. The D-041 workshop estimate of ~1.1 MB for 80 Active NPCs still holds. `ToldBy` source (the only mechanism that could cause unbounded growth via gossip chains) is not yet implemented — NPC-to-NPC knowledge transfer does not exist. The existing decay system (`decay_knowledge` in `knowledge/events.rs`) downgrades confidence and marks entries `Stale` but does not remove them, which is correct behavior (preserves "I used to know X" for narrative). No cap or eviction is needed at current or projected v0.1 scale. If gossip propagation ships (Q-024) and causes growth concerns, the simplest eviction policy is: on each decay pass, if `entities.len() > MAX_ENTITIES`, remove `Stale` entries with lowest `last_updated_tick`. The BTreeMap makes this a clean O(N) scan.
- **Source:** Knowledge Graph & Information Boundaries Workshop (Dudley Round 1, section 8.3). Closed by architecture audit 2026-02-23.
### Q-026: Contradiction detection algorithm
- **Status:** Open
@@ -163,6 +162,14 @@ Tracked questions awaiting discussion or resolution.
- **Assigned to:** Gestalt, Paula, Tyre
- **Source:** Sprint 10 PR review discussion (2026-02-19)
### Q-028: Collision-resistant line IDs for auto-generated NPCs
- **Status:** Open
- **Question:** The D-035 NPC-scoped line ID scheme uses NPC slugs as prefix (`kael-davan_d_001`). Hand-authored NPCs have unique slugs, but auto-generated populations (D-029: hundreds of NPCs) will produce collisions when the generator creates multiple NPCs with the same role slug (e.g., two `dock-worker` NPCs). What collision-resistance mechanism should be used? Options: (1) Short UUID/hash suffix on auto-gen slugs (`dock-worker-a7f3_d_001`), (2) Entity UUID as prefix, (3) Slug registry that guarantees uniqueness at generation time, (4) Composite key (entity ID + sequence) in server, human-readable slug only for authored content.
- **Constraints:** Line IDs must be globally unique across entire save file lifetime (history log readiness). Must stay human-readable for hand-authored content. Server treats IDs as opaque strings — solution lives in content/generation layer. Must be compatible with D-035 NPC-scoped namespace.
- **Ticket:** #544
- **Assigned to:** Gestalt, Tyre
- **Source:** Sprint 16 PR #59 review discussion (2026-02-23)
---
*27 questions (3 resolved, 2 partially resolved, 22 open). Last updated: 2026-02-19*
*28 questions (3 resolved, 2 partially resolved, 23 open). Last updated: 2026-02-23*
+20 -20
View File
@@ -113,7 +113,7 @@ lines:
| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `topic` | list\<enum\> | no | `[]` | Topic tags for Layer 4 weighted selection. Lines without topic tags are eligible for any topic context. Values: `colleague`, `routine`, `cargo`, `money`, `trust`, `danger`, `institution`, `personal`, `investigation`. |
| `mood` | list\<enum\> | no | `[]` | Mood tags for Layer 4 weighted selection. Lines without mood tags are eligible for any mood context. Values: `fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved`. |
| `mood` | list\<enum\> | no | `[]` | Mood tags for Layer 4 weighted selection. Lines without mood tags are eligible for any mood context. Values: `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`. |
| `tags` | list\<string\> | no | `[]` | Freeform escape hatch. Not consumed by the filtering engine — used for author organization, content queries, and the line previewer. No validation on values. |
#### Knowledge grant (optional)
@@ -157,33 +157,33 @@ All lines in pool
location: the-last-shift
role: dock-worker
lines:
- id: the-last-shift_d_001
- id: kael-davan_d_015
text: "Saved you a seat. Lera's got the spiced rice tonight."
role: dock-worker
access: [insider, peer]
trust: surface
situation: [bar_evening, social, arrival]
mood: [fond]
mood: [warm]
topic: [personal, colleague]
tags: [kael, greeting, phase-1]
- id: the-last-shift_d_010
- id: kael-davan_d_024
text: "Nils wants to talk. Tomorrow, bay side. Said it's about volume."
role: dock-worker
access: [insider]
trust: real
situation: [bar_evening, social]
mood: [concerned]
mood: [anxious]
topic: [danger]
tags: [kael, ring-ops, nils]
- id: the-last-shift_d_012
- id: kael-davan_d_026
text: "Lera knows more than she lets on. She won't say anything — but don't test it."
role: dock-worker
access: [insider]
trust: real
situation: [bar_evening, social, alone]
mood: [concerned]
mood: [anxious]
topic: [colleague, danger]
tags: [kael, ring-ops, lera, caution]
knowledge_grant:
@@ -291,13 +291,13 @@ character: detective
location: the-last-shift
lines:
# Basic atmospheric line — no prerequisites, any visit
- id: the-last-shift_m_d_001
- id: pc-detective_m_d_001
text: "The Last Shift. Only place in this district that doesn't smell like freight lubricant."
trigger: enter_location
tags: [arrival, atmospheric]
# Knowledge-gated observation — requires prior suspicion
- id: the-last-shift_m_d_021
- id: pc-detective_m_d_021
text: "Sera left when Torek arrived. Second time. Different excuse. Same result."
trigger: observe_anomaly
prerequisites:
@@ -308,7 +308,7 @@ lines:
tags: [npc, sera, torek, tell, friend-arc]
# Relationship-gated line — requires person_of_interest status
- id: the-last-shift_m_d_026
- id: pc-detective_m_d_026
text: "Same booth. Same warm smile. Same offer to buy me a drink. Everything except the truth."
trigger: observe_npc
prerequisites:
@@ -326,15 +326,15 @@ lines:
### 5.1 Pattern
```
{location-slug}_{type}_{character?}_{sequence}
{npc-slug}_{type}_{character?}_{sequence}
```
| Segment | Format | Values | Example |
|---------|--------|--------|---------|
| `location-slug` | kebab-case | Matches location directory name, or `general` | `the-last-shift`, `general` |
| `npc-slug` | kebab-case | NPC name in kebab-case. Each NPC has an independent sequence. | `kael-davan`, `dock-worker`, `pc-detective` |
| `type` | single char | `d` = dialogue, `m` = monologue, `e` = environmental (future) | `d`, `m` |
| `character` | single char | `s` = smuggler, `d` = detective. **Monologue only.** | `s`, `d` |
| `sequence` | 3-digit zero-padded | `001`–`999` | `001`, `042` |
| `sequence` | 3-digit zero-padded | `001`–`999` per NPC | `001`, `042` |
**General validation regex** (matches both dialogue and monologue IDs):
@@ -342,26 +342,26 @@ lines:
^[a-z0-9-]+_(d|m)(_[a-z])?_\d{3}$
```
- `[a-z0-9-]+` — location slug (kebab-case, at least one character)
- `[a-z0-9-]+` — npc slug (kebab-case, at least one character)
- `(d|m)` — pool type: `d` for dialogue, `m` for monologue
- `(_[a-z])?` — optional character segment (monologue only): `_s` or `_d`
- `\d{3}` — three-digit zero-padded sequence number
Use the pool-specific regexes in [Section 5.2](#52-regex-patterns) for strict per-type validation. This general regex is useful for quick format checks that accept either type.
> **D-035 deviation note:** D-035 specifies `{template}_{d|m|e}_{###}`. This spec refines that to `{location-slug}_{d|m}_{###}` (dialogue) and `{location-slug}_m_{s|d}_{###}` (monologue) for two reasons: (1) location-slug is more precise than template name and matches the directory hierarchy, and (2) monologue IDs include a character segment (`s`/`d`) to ensure uniqueness across the hard character partition (D-032). All existing authored content already uses this refined format. D-035 should be updated to reflect the implemented convention.
> **D-035 Sprint 15 amendment:** IDs are NPC-scoped, not location-scoped. The old `{location-slug}_{d|m}_{###}` format caused collisions when the same NPC appeared at multiple locations (e.g. `the-terminal_d_039` appeared in multiple NPC files). The new format `{npc-slug}_{d|m}_{###}` gives each NPC an independent 999-line ceiling. For multi-location NPCs, sequences are globally continuous across files (e.g. kael-davan uses _001-_014 at maintenance-corridors, _015-_033 at the-last-shift, _034-_075 at the-terminal). Single-location NPCs start at _001. Monologue uses per-file restart with composite key (file_path + line_id).
### 5.2 Regex patterns
| Pool type | Regex | Example |
|-----------|-------|---------|
| Dialogue | `^[a-z][a-z0-9-]*_d_[0-9]{3}$` | `the-last-shift_d_001` |
| Monologue | `^[a-z][a-z0-9-]*_m_[sd]_[0-9]{3}$` | `the-last-shift_m_d_021`, `general_m_s_003` |
| Dialogue | `^[a-z][a-z0-9-]*_d_[0-9]{3}$` | `kael-davan_d_001` |
| Monologue | `^[a-z][a-z0-9-]*_m_[sd]_[0-9]{3}$` | `pc-detective_m_d_021`, `pc-smuggler_m_s_003` |
### 5.3 Uniqueness scope
- IDs must be unique **within a single YAML file**.
- IDs are **not required to be globally unique** — location slug + file path provides global uniqueness. The engine uses `(file_path, line_id)` as the composite key.
- IDs are **not required to be globally unique** — npc-slug + file path provides global uniqueness. The engine uses `(file_path, line_id)` as the composite key.
- Sequence numbers need not be contiguous. Gaps are expected when lines are removed or reordered.
### 5.4 ID stability
@@ -406,9 +406,9 @@ All enum values are defined in `content/global/enums/` and validated by the JSON
### 6.5 Moods (D-028 Layer 4)
`fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved`
`anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`
8 values for v0.1.
8 values for v0.1. (Updated Sprint 14 amendment to D-035.)
### 6.6 Monologue triggers
Binary file not shown.
+5 -5
View File
@@ -234,10 +234,10 @@ All schema files live in `content/_schema/`. They use JSON Schema draft 2020-12.
| Content Type | ID Pattern | Example |
|---|---|---|
| Dialogue | `{location_slug}_{d}_{###}` | `the-terminal_d_001` |
| Monologue (smuggler) | `{location_slug}_m_s_{###}` | `the-terminal_m_s_001` |
| Monologue (detective) | `{location_slug}_m_d_{###}` | `the-terminal_m_d_001` |
| Examine | `{location_slug}_{e}_{###}` | `the-terminal_e_001` |
| Dialogue | `{npc-slug}_d_{###}` | `kael-davan_d_001` |
| Monologue (smuggler) | `{npc-slug}_m_s_{###}` | `pc-smuggler_m_s_001` |
| Monologue (detective) | `{npc-slug}_m_d_{###}` | `pc-detective_m_d_001` |
| Examine | `{npc-slug}_{e}_{###}` | `kael-davan_e_001` |
Line IDs are stable. They are never reused, even if a line is deleted. Numbering gaps are expected and acceptable.
@@ -586,7 +586,7 @@ For quick reference during authoring. Authoritative source: `content/global/enum
**Topics (9):** `colleague`, `routine`, `cargo`, `money`, `trust`, `danger`, `institution`, `personal`, `investigation`
**Moods (8):** `fond`, `comfortable`, `worried`, `suspicious`, `analytical`, `conflicted`, `concerned`, `relieved`
**Moods (8):** `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `relieved`, `focused`
**Access Tiers (5):** `public`, `insider`, `authority`, `peer`, `hostile`
+1 -1
View File
@@ -307,7 +307,7 @@ Social positioning in a specific context.
### Full YAML format (engine canonical)
```yaml
- id: transit_m_s_025
- id: pc-smuggler_m_s_025
character: smuggler
text: "Kael keeps checking his lattice. Waiting for a message? Not like him to be jumpy."
trigger: observe_anomaly
+2 -2
View File
@@ -226,7 +226,7 @@ Add `tags: [mirror_moment]` to both sides of every mirror pair so they can be id
```yaml
# Smuggler side — monologue-smuggler.yaml
- id: transit_m_s_044
- id: pc-smuggler_m_s_044
character: smuggler
trigger: observe_npc
prerequisite:
@@ -239,7 +239,7 @@ Add `tags: [mirror_moment]` to both sides of every mirror pair so they can be id
tags: [mirror_moment, mirror_worried_partner]
# Detective side — monologue-detective.yaml
- id: transit_m_d_044
- id: pc-detective_m_d_044
character: detective
trigger: observe_npc
prerequisite:
+7 -7
View File
@@ -359,7 +359,7 @@ skills:
```yaml
# File: districts/{location}/dialogue/{role}-mirror.yaml
- id: "{location}_d_001"
- id: "{npc-slug}_d_001"
text: "Greeting — warm, open, no guardedness"
role: "{role}"
access: ["public"]
@@ -367,7 +367,7 @@ skills:
situation: ["greeting"]
mood: ["warm"]
- id: "{location}_d_002"
- id: "{npc-slug}_d_002"
text: "Sharing worry — direct, no evasion"
role: "{role}"
access: ["public", "insider"]
@@ -375,7 +375,7 @@ skills:
situation: ["worried_inquiry"]
mood: ["anxious"]
- id: "{location}_d_010"
- id: "{npc-slug}_d_010"
text: "Deep emotional vulnerability — peer tier"
role: "{role}"
access: ["peer"]
@@ -385,8 +385,8 @@ skills:
# Monologue content (separate per character, per D-032)
# File: districts/{location}/monologue-smuggler.yaml
- id: "{location}_m_s_001"
# File: districts/{location}/monologue/smuggler/{location}.yaml
- id: "pc-smuggler_m_s_001"
text: "Smuggler feels guilt observing MIRROR's distress"
character: "smuggler"
trigger: "observe_npc"
@@ -394,8 +394,8 @@ skills:
target.known_attributes.name: "{Name}"
target.known_attributes.emotional_state: "worried"
# File: districts/{location}/monologue-detective.yaml
- id: "{location}_m_d_001"
# File: districts/{location}/monologue/detective/{location}.yaml
- id: "pc-detective_m_d_001"
text: "Detective recognizes MIRROR as honest witness"
character: "detective"
trigger: "post_conversation"
+27 -27
View File
@@ -32,7 +32,7 @@ All smuggler openings share:
These lines fire when the smuggler enters The Terminal at session start. 3-4 lines available; engine selects one based on seed variant.
```yaml
- id: transit_m_s_open_001
- id: pc-smuggler_m_s_001
character: smuggler
trigger: enter_location
location: the-terminal
@@ -40,7 +40,7 @@ These lines fire when the smuggler enters The Terminal at session start. 3-4 lin
mood: [content, routine]
tags: [opening_hook, canonical_anchor]
- id: transit_m_s_open_002
- id: pc-smuggler_m_s_002
character: smuggler
trigger: enter_location
location: the-terminal
@@ -48,7 +48,7 @@ These lines fire when the smuggler enters The Terminal at session start. 3-4 lin
mood: [routine, aware]
tags: [opening_hook]
- id: transit_m_s_open_003
- id: pc-smuggler_m_s_003
character: smuggler
trigger: enter_location
location: the-terminal
@@ -56,7 +56,7 @@ These lines fire when the smuggler enters The Terminal at session start. 3-4 lin
mood: [routine, observant]
tags: [opening_hook]
- id: transit_m_s_open_004
- id: pc-smuggler_m_s_004
character: smuggler
trigger: enter_location
location: the-terminal
@@ -70,7 +70,7 @@ These lines fire when the smuggler enters The Terminal at session start. 3-4 lin
These fire when the smuggler sees Kael on the dock floor. Critical FRIEND establishment lines.
```yaml
- id: transit_m_s_open_010
- id: pc-smuggler_m_s_010
character: smuggler
trigger: observe_npc
target: npc:kael-davan
@@ -82,7 +82,7 @@ These fire when the smuggler sees Kael on the dock floor. Critical FRIEND establ
mood: [content, fond]
tags: [opening_hook, friend_phase_1, canonical_anchor]
- id: transit_m_s_open_011
- id: pc-smuggler_m_s_011
character: smuggler
trigger: observe_npc
target: npc:kael-davan
@@ -104,7 +104,7 @@ These fire when the smuggler sees Kael on the dock floor. Critical FRIEND establ
**Operational context line (observe_anomaly — schedule board):**
```yaml
- id: transit_m_s_open_020
- id: pc-smuggler_m_s_020
character: smuggler
trigger: observe_anomaly
location: the-terminal
@@ -131,7 +131,7 @@ Kael greets the smuggler warmly. Asks about the weekend. Makes a dry comment abo
**Absence lines (observe_npc, failed — Kael's station empty):**
```yaml
- id: transit_m_s_open_030
- id: pc-smuggler_m_s_030
character: smuggler
trigger: observe_anomaly
location: the-terminal
@@ -140,7 +140,7 @@ Kael greets the smuggler warmly. Asks about the weekend. Makes a dry comment abo
tags: [opening_hook, variant_b]
notes: "Phase 1 tell: Kael's routine is so established that absence is noted before alarm."
- id: transit_m_s_open_031
- id: pc-smuggler_m_s_031
character: smuggler
trigger: time_idle
location: the-terminal
@@ -169,7 +169,7 @@ Kael arrives slightly disheveled, slightly out of breath. Brief explanation —
**Environmental read lines:**
```yaml
- id: transit_m_s_open_040
- id: pc-smuggler_m_s_040
character: smuggler
trigger: observe_npc
target: npc:voss
@@ -177,7 +177,7 @@ Kael arrives slightly disheveled, slightly out of breath. Brief explanation —
mood: [aware, cautious]
tags: [opening_hook, variant_c]
- id: transit_m_s_open_041
- id: pc-smuggler_m_s_041
character: smuggler
trigger: observe_npc
target: npc:voss
@@ -204,7 +204,7 @@ Kael arrives slightly disheveled, slightly out of breath. Brief explanation —
**Container flag lines:**
```yaml
- id: transit_m_s_open_050
- id: pc-smuggler_m_s_050
character: smuggler
trigger: observe_anomaly
location: the-terminal
@@ -212,7 +212,7 @@ Kael arrives slightly disheveled, slightly out of breath. Brief explanation —
mood: [operational, concerned]
tags: [opening_hook, variant_d]
- id: transit_m_s_open_051
- id: pc-smuggler_m_s_051
character: smuggler
trigger: observe_anomaly
location: the-terminal
@@ -245,7 +245,7 @@ All detective openings share:
Lines that fire when the detective enters their starting location. Selection based on seed variant.
```yaml
- id: transit_m_d_open_001
- id: pc-detective_m_d_001
character: detective
trigger: enter_location
location: the-terminal
@@ -253,7 +253,7 @@ Lines that fire when the detective enters their starting location. Selection bas
mood: [professional, observant]
tags: [opening_hook, canonical_anchor]
- id: transit_m_d_open_002
- id: pc-detective_m_d_002
character: detective
trigger: enter_location
location: the-terminal
@@ -262,7 +262,7 @@ Lines that fire when the detective enters their starting location. Selection bas
tags: [opening_hook, canonical_anchor]
notes: "Companion line to open_001 — these fire in sequence on Variant A/B/C."
- id: transit_m_d_open_003
- id: pc-detective_m_d_003
character: detective
trigger: enter_location
location: the-last-shift
@@ -270,7 +270,7 @@ Lines that fire when the detective enters their starting location. Selection bas
mood: [professional, curious]
tags: [opening_hook, variant_a_bar]
- id: transit_m_d_open_004
- id: pc-detective_m_d_004
character: detective
trigger: enter_location
location: the-terminal
@@ -284,7 +284,7 @@ Lines that fire when the detective enters their starting location. Selection bas
Critical FRIEND establishment for the detective.
```yaml
- id: transit_m_d_open_010
- id: pc-detective_m_d_010
character: detective
trigger: observe_npc
target: npc:sera-venn
@@ -296,7 +296,7 @@ Critical FRIEND establishment for the detective.
mood: [relieved, warm]
tags: [opening_hook, friend_phase_1, canonical_anchor]
- id: transit_m_d_open_011
- id: pc-detective_m_d_011
character: detective
trigger: observe_npc
target: npc:sera-venn
@@ -322,7 +322,7 @@ Critical FRIEND establishment for the detective.
**Bar arrival line:**
```yaml
- id: transit_m_d_open_020
- id: pc-detective_m_d_020
character: detective
trigger: enter_location
location: the-last-shift
@@ -334,7 +334,7 @@ Critical FRIEND establishment for the detective.
**Lera read (public NPC, establishing social texture):**
```yaml
- id: transit_m_d_open_021
- id: pc-detective_m_d_021
character: detective
trigger: observe_npc
target: npc:lera-sessik
@@ -363,7 +363,7 @@ Sera gives a brief district overview — who the regulars are, what the Terminal
**Terminal read line:**
```yaml
- id: transit_m_d_open_030
- id: pc-detective_m_d_030
character: detective
trigger: enter_location
location: the-terminal
@@ -375,7 +375,7 @@ Sera gives a brief district overview — who the regulars are, what the Terminal
**Voss approach (first adversarial NPC):**
```yaml
- id: transit_m_d_open_031
- id: pc-detective_m_d_031
character: detective
trigger: observe_npc
target: npc:voss
@@ -406,7 +406,7 @@ Standard question options: "Thank you, I'll need those." / "Tell me about your s
**Investigative focus line:**
```yaml
- id: transit_m_d_open_040
- id: pc-detective_m_d_040
character: detective
trigger: enter_location
location: the-terminal
@@ -414,7 +414,7 @@ Standard question options: "Thank you, I'll need those." / "Tell me about your s
mood: [analytical, focused]
tags: [opening_hook, variant_c]
- id: transit_m_d_open_041
- id: pc-detective_m_d_041
character: detective
trigger: observe_anomaly
location: the-terminal
@@ -436,7 +436,7 @@ Standard question options: "Thank you, I'll need those." / "Tell me about your s
**Escort read lines:**
```yaml
- id: transit_m_d_open_050
- id: pc-detective_m_d_050
character: detective
trigger: observe_npc
target: npc:torek-lintar
@@ -444,7 +444,7 @@ Standard question options: "Thank you, I'll need those." / "Tell me about your s
mood: [professional, curious]
tags: [opening_hook, variant_d]
- id: transit_m_d_open_051
- id: pc-detective_m_d_051
character: detective
trigger: observe_npc
target: npc:torek-lintar
+60
View File
@@ -0,0 +1,60 @@
# Sprint 17: Tell — Client Tasks
**Goal:** NPCs volunteer information unprompted and express personality through delivery; the player can read time and orientation at a glance from the insert HUD; POI infrastructure lands on the server.
**Branch:** `client`
**Agents:** Stig (UI), Tyre (arch), Hoshe (QA)
## Carry-over from Sprint 16
None. Sprint 16 client tickets (#540, #543) will close before Sprint 17.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #263 | Time display on insert HUD | — |
| #537 | UX: E-Talk overlay improvement | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/perception.md` — D-019 (camera angle), D-033 (entity color)
- `decisions/architecture.md` — D-020 (Godot client + Rust server via IPC)
- `decisions/scope.md` — D-051 (diegetic insert/minimap)
## Notes
### #263 — Time display on insert HUD
**What exists:** The client has `client/scripts/ui/debug_overlay.gd` for debug display. The insert HUD concept is defined in D-051 but no insert UI code exists yet. `client/scripts/rendering/world_renderer.gd` handles the main rendering pipeline. The server sends `SimulationTime` data in `ObserverSnapshot` (see `server/src/bridge/types.rs`).
**What to deliver:** A diegetic time display on the player's insert/HUD. The player needs to know the current game time for routine-based investigation (NPC schedules, shift changes). Should show time in the station's local format, not abstract tick counts.
**Design dependency:** #314 (visual, this sprint) produces the insert/HUD wireframe. Coordinate with Araminta's output for placement and visual style. Can start with a placeholder layout and refine after the wireframe lands.
**Integration:** Reads `SimulationTime` from the snapshot. No new server work required — time data is already in the protocol.
### #537 — UX: E-Talk overlay improvement
**What exists:** The E-Talk interaction overlay appears when adjacent to an NPC. Playtester feedback says it doesn't communicate enough value — the overlay appears but doesn't convey what talking will accomplish or what access tier is available.
**What to deliver:** Improve the overlay to show: NPC name (if known from KG), relationship state color (from `RelationshipState` in snapshot), and a hint of available dialogue tier (surface/real/secret based on trust level). The goal is to make the player feel informed before committing to a conversation.
**Integration:** All data needed is already in `ObserverSnapshot` — the `VisibleEntity` includes `relationship` state and `known_attributes`. No server changes needed.
## Dependency Chain
```
#263 (Time display) → standalone
#537 (E-Talk UX) → standalone
Both can run in parallel.
```
## PR Workflow
When ready to submit, create a PR with `tea` CLI:
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(client): sprint 17 description" --description "body" --base main --head client
```
+66
View File
@@ -0,0 +1,66 @@
# Sprint 17: Tell — Copy Tasks
**Goal:** NPCs volunteer information unprompted and express personality through delivery; the player can read time and orientation at a glance from the insert HUD; POI infrastructure lands on the server.
**Branch:** `copy`
**Agents:** Mellanie (author), Paula (narrative), Gestalt (systems)
## Carry-over from Sprint 16
None. Sprint 16 copy ticket (#542, line ID migration) will close before Sprint 17.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #330 | Diegetic tutorial monologue lines — per character | — (blockers #299, #300 done) |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/content.md` — D-035 (tag taxonomy, monologue prerequisites), D-028 (dialogue architecture)
- `decisions/perception.md` — D-019 (camera and perception), D-011 (fog of perception)
## Notes
### #330 — Diegetic tutorial monologue lines — per character
**What exists:** Monologue pools at `content/campaigns/main/systems/krenn/stations/sova/districts/transit/monologue/detective/` and `.../smuggler/` contain character-specific internal monologue. Opening monologue files exist (`opening.yaml` for both characters). The monologue system in `server/src/simulation/monologue.rs` fires lines based on triggers and prerequisites.
**What to deliver:** 8-10 monologue lines per character (smuggler + detective) that teach core mechanics diegetically:
- Movement and exploration ("These corridors all look the same. Mental note: check the signage.")
- Fog of perception ("Can't see past that corner. Might be worth checking.")
- Sound model ("Voices down the hall. Can't make out the words from here.")
- NPC interaction ("Could ask around. People talk if you give them reason to.")
- Insert/HUD usage ("Check the overlay. Should show the time and nearby contacts.")
Lines fire on first-time events (first move, first fog encounter, first sound heard, first NPC proximity, first insert open). Use `trigger: first_time_event` with appropriate event tags.
**Voice consistency:** Smuggler lines should be observational, street-smart, practical. Detective lines should be analytical, procedure-oriented, careful. Both stay in-character — tutorials are disguised as natural inner thoughts, not fourth-wall-breaking instructions.
**Prerequisite format:** Per D-035:
```yaml
- id: pc-smuggler_m_s_tut_001
text: "Voices. Down the corridor. Can't tell how many."
trigger: first_sound_heard
priority: tutorial
cooldown: -1
```
Use `_tut_` in IDs to distinguish tutorial lines from narrative monologue.
**Note on PC monologue ID collision (Q-028):** The review of PR #59 flagged that PC monologue IDs collide across location files. Use the `_tut_` discriminator for tutorial lines. Q-028 will resolve the broader scheme.
## Dependency Chain
```
#330 (Tutorial monologue) → standalone, no deps
```
## PR Workflow
When ready to submit, create a PR with `tea` CLI:
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(content): sprint 17 description" --description "body" --base main --head copy
```
+80
View File
@@ -0,0 +1,80 @@
# Sprint 17: Tell — Joint Tasks
**Goal:** NPCs volunteer information unprompted and express personality through delivery; the player can read time and orientation at a glance from the insert HUD; POI infrastructure lands on the server.
## Pre-Sprint: Knowledge Flow & NPC Boundaries Workshop
**Brief:** `docs/workshops/knowledge-flow-npc-boundaries/workshop-brief.md`
**Participants:** Tyre (arch), Gestalt (mechanics), Dudley (impl), Paula (narrative), Qatux (docs)
**Timing:** After Sprint 16 closes, before Sprint 17 starts
### Topics and Sprint Impact
| Topic | Resolves | Feeds tickets |
|-------|----------|---------------|
| 1. Knowledge grant mechanism | #141 remaining gap | #172, #149 |
| 2. NPC-NPC knowledge propagation | Q-024 | #172 |
| 3. Unprompted disclosure design | — | #172, #173 |
| 4. NPC information boundaries | #142 | #172 |
| 5. Contradiction detection pipeline | Q-026 | Future (THE FRIEND arc) |
### Expected Outputs
- **D-record** resolving Q-024 and Q-026
- Implementation scope for #141 (wire `knowledge_grant`) and #142 (NPC boundary retrofit priority)
- Mechanical spec for #172 unprompted disclosure
- Updated ticket descriptions for #172 and #173
**#172 is blocked until workshop completes.** Other server tickets can start in parallel.
## Pre-Sprint Decisions
| Decision | Who | Blocks |
|----------|-----|--------|
| Workshop Topics 1-5 | Tyre, Gestalt, Dudley, Paula | #172, #173 |
| Q-028: Collision-resistant line IDs | Gestalt, Tyre | Copy authoring conventions |
## Cross-Team Integration Points
| Server ticket | Client ticket | Integration |
|---------------|---------------|-------------|
| #148 POI data model | #263 Time display | POI data feeds future minimap; time data already in snapshot |
| #232 Protocol versioning | All client | Client must handle version field in messages |
| #172 Unprompted disclosure | #537 E-Talk UX | Disclosure lines appear in dialogue UI; E-Talk overlay should hint at NPC willingness |
| Server ticket | Visual ticket | Integration |
|---------------|---------------|-------------|
| #148 POI data model | #314 Insert/HUD wireframe | POI categories must match wireframe's display elements |
## Sprint Completion Proof
Sprint 17 is **DONE** when:
1. **Unprompted disclosure fires** — An NPC with high trust and relevant knowledge volunteers information to the player without being asked. The volunteered line appears in the dialogue UI. The player's KnowledgeGraph gains a new fact via `knowledge_grant`.
2. **Trait modifiers reshape delivery** — Two NPCs with different personality traits deliver the same base information differently (different mood, different wording or selection weight).
3. **Time is visible** — The insert HUD shows current game time in a diegetic format. Time advances visibly as ticks pass.
4. **POIs exist on the map** — Server spawns POI entities with the `PointOfInterest` component. POIs can be discovered via walking into a location (physical discovery).
5. **Protocol version field exists** — All messages include a version field. Client logs a warning on version mismatch.
6. **E-Talk overlay is informative** — The interaction overlay shows NPC name (if known), relationship color, and a dialogue tier hint.
7. **Insert wireframe is specified** — Both smuggler and detective variants are wireframed with layout, colors, and interaction states documented.
8. **Tutorial monologue fires** — First-time events (move, fog, sound, NPC proximity) trigger diegetic tutorial lines for both characters.
## Test Plan (D-030 alignment)
Sprint 17 is in the **integration testing** phase (Sprints 3-4 per D-030):
- **#172 + #173:** Integration test — NPC with specific axis values and KG state triggers unprompted disclosure; trait modifier changes the selected line.
- **#148 + #149:** Unit tests for POI component CRUD; integration test for discovery event → KG update.
- **#232:** Unit test for version serialization roundtrip; integration test for version mismatch handling.
- **#263:** Manual verification — time display updates as simulation advances.
- **#537:** Manual verification — overlay shows correct NPC data.
- **#330:** Line preview test — tutorial lines fire on first-time triggers, respect cooldown -1 (fire once).
## Teams
| Team | Branch | Agents | Tickets |
|------|--------|--------|---------|
| server | `server` | Dudley, Tyre, Hoshe | #172, #173, #232, #148, #149 |
| client | `client` | Stig, Tyre, Hoshe | #263, #537 |
| copy | `copy` | Mellanie, Paula, Gestalt | #330 |
| visual | `visual` | Araminta | #314 |
+103
View File
@@ -0,0 +1,103 @@
# Sprint 17: Tell — Server Tasks
**Goal:** NPCs volunteer information unprompted and express personality through delivery; the player can read time and orientation at a glance from the insert HUD; POI infrastructure lands on the server.
**Branch:** `server`
**Agents:** Dudley (simulation), Tyre (arch), Hoshe (QA)
## Carry-over from Sprint 16
None. Sprint 16 server was 100% complete (4/4 done).
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #172 | Layer 4: Unprompted disclosure | — |
| #173 | Trait modifier system | — |
| #232 | Create protocol versioning scheme | — |
| #148 | POI data model | — |
| #149 | POI discovery system | #148 |
Use `db/connectors/ticket show <id>` for full details.
## Pre-Sprint Workshop
The **Knowledge Flow & NPC Boundaries** workshop runs before this sprint starts. Brief at `docs/workshops/knowledge-flow-npc-boundaries/workshop-brief.md`. Topics 1-3 directly inform #172 and #173:
- **Topic 1:** Knowledge grant mechanism — how dialogue grants facts to player KG
- **Topic 2:** NPC-to-NPC knowledge propagation (resolves Q-024)
- **Topic 3:** Unprompted disclosure design — how `tell_state.rs` connects to NPC KG
Wait for workshop decisions before starting #172. #148, #149, #232, and #173 can start in parallel.
## Key Decisions
- `decisions/content.md` — D-028 (dialogue architecture, four relational layers), D-024 (NPC 10 axes), D-035 (tag taxonomy)
- `decisions/architecture.md` — D-041 (knowledge graph data model), D-010 (information boundaries), D-020 (engine architecture)
- `decisions/perception.md` — D-033 (entity color from relationship)
## Open Questions to Resolve Early
- **Q-024: Gossip propagation timing** — Resolved by workshop. Feeds #172 implementation.
- **Q-026: Contradiction detection** — Resolved by workshop. Not directly Sprint 17, but workshop output constrains #172 design.
## Notes
### #172 — Layer 4: Unprompted disclosure
**What exists:** `server/src/npc/tell_state.rs` derives tell state from NPC axis values (Secret, ToleranceThreshold, Contentment, MoodState, Relationships). The `TellState` component tracks willingness to volunteer info. `server/src/simulation/dialogue.rs` has the D-028 four-layer pipeline (Layer 1: access tier, Layer 2: situation, Layer 3: trust-gated gossip). Layer 4 is unimplemented.
**What to deliver:** A system that checks NPC `TellState` + `KnowledgeGraph` to select information the NPC wants to volunteer. This is NOT player-initiated — the NPC decides to speak based on mood, trust, and what they know. Workshop Topic 3 will define the trigger conditions and candidate selection algorithm.
**Key gap from audit:** `tell_state.rs` does NOT query the NPC's own `KnowledgeGraph`. NPCs currently operate on ground truth. Workshop Topic 4 (NPC boundaries) will determine the minimum retrofit scope. At minimum, #172 needs `tell_state.rs` to read `&KnowledgeGraph` to know what the NPC can share.
**Integration:** Uses `knowledge_grant` on dialogue lines to give player new facts. Workshop Topic 1 defines the grant schema.
### #173 — Trait modifier system
**What exists:** `server/src/npc/mod.rs` defines NPC components including personality axes per D-024. `server/src/content/line_pool.rs` has the `IndexedDialogueLine` struct with mood and tag fields. No trait-based line transformation exists.
**What to deliver:** A system where NPC personality traits reshape HOW lines are delivered, not per-trait scripts. Base lines exist in the pool; traits modify selection weight, delivery mood, or text variants. Workshop Topic 3 will clarify whether traits also affect WHAT is disclosed (filtering) or just delivery.
**Gotcha:** This must compose with Layer 3 (trust-gated, shipped Sprint 16) and Layer 4 (#172). Trait modifiers should operate as a scoring pass on the already-filtered candidate set, not as a separate filtering stage.
### #232 — Create protocol versioning scheme
**What exists:** `server/src/bridge/types.rs` defines `ObserverSnapshot` and all message types. `client/scripts/protocol/protocol.gd` deserializes them. No version field exists on any message. MessagePack encoding per D-020.
**What to deliver:** Version field in protocol messages, compatibility checking, migration strategy. This is infrastructure — no gameplay change.
**Standalone:** No dependencies on other sprint tickets or workshop output.
### #148 — POI data model
**What exists:** No POI system exists. The `KnowledgeGraph` has `BTreeMap<FactId, FactKnowledge>` for non-entity facts, but POIs are a new domain combining spatial position + knowledge.
**What to deliver:** `PointOfInterest` component with name, location, category, discovery source, visibility rules. This is an ECS component on map entities (bars, terminals, corridors, etc.). POIs feed into the minimap (#151, future) and insert HUD (#263, client Sprint 17).
**Design question:** Do POIs integrate with the knowledge graph as `FactId` entries (e.g., `poi.the_last_shift`), as entity knowledge via `StableId`, or as a separate system? Workshop Topic 1 touches this — POI discovery as knowledge flow.
### #149 — POI discovery system
**What exists:** Nothing. Depends on #148 POI data model.
**What to deliver:** System for learning POIs via: character background (starting knowledge), NPC tips (dialogue grants), research (terminal interaction), physical discovery (walking into a location). Each discovery source maps to a `KnowledgeSource` variant and confidence level.
**Integration:** This IS knowledge flow — uses the same `KnowledgeEventQueue` pattern. New `KnowledgeEventType` variant for POI discovery. Connects to the grant mechanism (Workshop Topic 1).
## Dependency Chain
```
#148 (POI data model) → #149 (POI discovery)
#172 (Unprompted disclosure) → blocked by workshop output
#173 (Trait modifiers) → standalone, parallel
#232 (Protocol versioning) → standalone, parallel
```
## PR Workflow
When ready to submit, create a PR with `tea` CLI:
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(server): sprint 17 description" --description "body" --base main --head server
```
+61
View File
@@ -0,0 +1,61 @@
# Sprint 17: Tell — Visual Tasks
**Goal:** NPCs volunteer information unprompted and express personality through delivery; the player can read time and orientation at a glance from the insert HUD; POI infrastructure lands on the server.
**Branch:** `visual`
**Agents:** Araminta (art direction)
## Carry-over from Sprint 16
None. Sprint 16 visual ticket (#540, sprite camera angle) will close before Sprint 17.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #314 | Insert/HUD wireframe and spec — dual character variants | — (blocker #303 done) |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/perception.md` — D-019 (camera and perception), D-033 (entity color palette)
- `decisions/scope.md` — D-051 (diegetic insert/minimap)
- `decisions/content.md` — D-024 (NPC 10 axes, relationship display)
## Notes
### #314 — Insert/HUD wireframe and spec — dual character variants
**What exists:** D-051 defines the diegetic insert/minimap concept. D-033 defines the entity color palette (Unknown teal, Known green, PersonOfInterest amber, Hostile red). No wireframe or visual spec exists yet for the insert overlay.
**What to deliver:** Wireframe and visual specification for the lattice overlay HUD with dual-character variants:
**Smuggler variant:**
- Social contacts displayed as dots using D-033 relationship colors
- Casual labels ("The Last Shift", "Kael's usual spot")
- Time display (feeds #263 client implementation)
- Informal, street-level aesthetic — the smuggler's network view
**Detective variant:**
- POIs as amber diamonds with formal labels
- Commission-style grid overlay
- Time display with shift indicators
- Analytical, procedural aesthetic — the detective's investigation tool
**Output format:** Wireframe images (PNG) + written spec document describing layout, color usage, typography, and interaction states. Use `/frame0-wireframe` if Frame0 is available, otherwise produce annotated mockups.
**Integration:** This spec directly feeds #263 (client, time display) and future #151 (minimap rendering). The client team needs placement coordinates and visual hierarchy from this spec.
## Dependency Chain
```
#314 (Insert/HUD wireframe) → standalone, informs #263 (client)
```
## PR Workflow
When ready to submit, create a PR with `tea` CLI:
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(visual): sprint 17 description" --description "body" --base main --head visual
```
@@ -0,0 +1,200 @@
# Knowledge Flow & NPC Information Boundaries Workshop Brief
**Goal:** Design the knowledge grant mechanism, NPC-to-NPC knowledge propagation, unprompted disclosure mechanics, NPC information boundaries, and contradiction detection algorithm -- resolving the functional gap between the mature D-041 data model and the Sprint 17 tickets that depend on it.
**Sprint context:** Between Sprint 16 (closing) and Sprint 17 (planned)
**Priority:** CRITICAL -- Sprint 17 blocker (6 tickets depend on these answers)
**Participants:** Tyre (architecture lead), Gestalt (systems design), Dudley (server implementation), Paula (narrative), Qatux (documenter)
**Source:** Tyre architecture audit 2026-02-23, Sprint 17 planning dependency analysis
## Context
The Knowledge Graph data model (D-041) shipped in Sprint 2 and is mature. The Rust types are solid (`server/src/knowledge/types.rs`), the per-entity `KnowledgeGraph` component works (`server/src/knowledge/graph.rs`), the event queue drains correctly (`server/src/knowledge/events.rs`), and the `filter_by_access` system is implemented with 14 passing tests. Player-facing information boundaries function.
The NPC-facing side is functionally empty. NPCs run on ground truth, gossip does not transfer knowledge, contradiction detection does not fire, and dialogue cannot grant facts. Sprint 17 introduces tickets that build on this unfinished foundation.
### Audit Findings (Tyre, 2026-02-23)
**Q-024 (Gossip timing) -- Still open.** The `ToldBy` source variant (`KnowledgeSource::ToldBy { source_id: StableId, tick: u64 }`, `server/src/knowledge/types.rs` line 97) is defined but never constructed anywhere in the codebase. NPC conversations (`run_npc_conversations` in `server/src/simulation/conversation.rs`, line 278+) are cosmetic -- they emit voice `SoundEvent`s and `ConversationEvent`s for the player's snapshot, but no knowledge flows between the participating NPCs. The conversation system IS the natural "routine intersection" hook the original workshop preferred. It already has proximity detection, cooldown management, and deterministic pairing via `StableId` sorting (D-010 principle 4).
**Q-025 (KG cap/eviction) -- Close.** Memory analysis shows ~30KB total at current NPC count. Not needed for v0.1/v0.2. Formal closure recommended.
**Q-026 (Contradiction detection) -- Downstream consumers fully built and tested, detection algorithm not implemented.** Anomaly marking in `server/src/perception/anomaly.rs` fires on `KnowledgeState::Contradicted`. Snapshot flags in `server/src/perception/observer/mod.rs` propagate it. Sprint double-take monologue in `server/src/simulation/monologue.rs` handles it. All tests manually set `Contradicted` on KG entries. The detection algorithm that would SET `Contradicted` based on comparing sources does not exist. Prerequisite: `ToldBy` sources must exist, which requires Topics 1 and 2 to be resolved first.
**#141 (Player info gating) -- Mostly done.** `filter_by_access` in `server/src/knowledge/graph.rs` is implemented and tested (14 tests). The dialogue pipeline enforces KG gating (layers 1-3 in `server/src/simulation/dialogue.rs`). Gap: the `knowledge_grant` field on `IndexedDialogueLine` (`server/src/content/line_pool.rs` line 297) is stubbed -- `Option<KnowledgeGrant>`, always `None`. The YAML schema defines `KnowledgeGrant { fact_id: String, confidence: String }` (`server/src/content/types.rs` lines 495-498). Dialogue lines cannot currently grant facts to the player's KnowledgeGraph.
**#142 (NPC info boundaries) -- Still open.** No NPC system queries its own `KnowledgeGraph`. `server/src/npc/tell_state.rs` uses axis values directly (Secret, ToleranceThreshold, Contentment, MoodState, Relationships). `server/src/npc/routine.rs` uses `DailyRoutine` directly. `server/src/simulation/path_follow.rs` uses ground truth. `server/src/simulation/conversation.rs` uses proximity + probability, not knowledge.
### Sprint 17 Tickets That Depend on These Answers
| Ticket | Title | Dependency |
|--------|-------|------------|
| **#172** | Layer 4: Unprompted disclosure | Knowledge propagation model (what can NPCs share?), NPC KG awareness (what does the NPC know to share?) |
| **#173** | Trait modifier system | Do traits reshape WHAT NPCs disclose (filtering) or HOW they say it (delivery)? Or both? |
| **#148** | POI data model | How do POIs enter the knowledge graph? New FactId category? Or separate system? |
| **#149** | POI discovery system | This IS knowledge flow -- it needs the grant mechanism |
| **#141** | Knowledge-based information gating | Wire `knowledge_grant`, complete the loop |
| **#142** | NPC information boundaries | NPCs use own KG for decisions |
## Key Code to Reference
Participants should read these files before Round 1:
| File | What to read | Why |
|------|-------------|-----|
| `server/src/knowledge/types.rs` | Full file | Core types: `KnowledgeSource::ToldBy` (line 97), `KnowledgeConfidence` hierarchy, `KnowledgeState::Contradicted`, `EntityKnowledge` struct, `FactKnowledge` struct |
| `server/src/knowledge/graph.rs` | Full file | `KnowledgeGraph` component, `filter_by_access` (14 tests), entity/fact queries, `observe_entity`, `observe_entity_leaving_los` |
| `server/src/knowledge/events.rs` | Full file | `KnowledgeEventQueue`, `KnowledgeEventType` variants (currently: `DirectObservation`, `LeftLOS`, `IncompleteInteraction`), `process_knowledge_events` system |
| `server/src/knowledge/registry.rs` | Full file | `EntityRegistry` -- `StableId <-> Entity` bidirectional mapping |
| `server/src/simulation/conversation.rs` (line 278+) | `run_npc_conversations` | NPC proximity pairing, conversation lifecycle, `SoundEvent`/`ConversationEvent` emission -- the hook for knowledge transfer |
| `server/src/simulation/dialogue.rs` | Full file | D-028 four-layer pipeline: access tier (from KG), situations, trust tier (from KG), topic+mood scoring |
| `server/src/npc/tell_state.rs` | Full file | `TellCategory` derivation from axis values -- currently bypasses KG entirely |
| `server/src/npc/relationships.rs` | Full file | `TrustEvent` queue, relationship state transitions, trust derivation |
| `server/src/content/line_pool.rs` (line 297) | `IndexedDialogueLine` | `knowledge_grant: Option<KnowledgeGrant>` stub |
| `server/src/content/types.rs` (lines 495-498) | `KnowledgeGrant` | YAML-parsed schema: `{ fact_id: String, confidence: String }` |
| `server/src/perception/anomaly.rs` | Full file | `detect_anomalies` system -- marks entities with `AnomalyMarker` based on KG `Contradicted`/`PersonOfInterest` |
| `server/src/simulation/monologue.rs` | Lines 1-60, sprint double-take | Anomaly monologue, recognition lines -- downstream consumer of contradiction |
## Decisions to Reference
| Decision | Topic | Relevance |
|----------|-------|-----------|
| **D-010** | Multiplayer-ready info boundaries | Non-negotiable: every design must respect info boundaries, deterministic iteration (BTreeMap), no player-special-casing |
| **D-011** | Fog of perception | NPCs use the same LOS/perception system as player -- information boundaries are universal |
| **D-024** | NPC 10 axes | Tell system, Secret, Contentment, Tolerance -- the axes that should be KG-aware but currently are not |
| **D-028** | Dialogue architecture (four layers) | Layer 4 (unprompted disclosure) is the immediate Sprint 17 target. Knowledge grants must integrate with this pipeline |
| **D-033** | Entity color from relationship | Color shifts on `RelationshipState` change -- downstream of contradiction detection |
| **D-034** | THE FRIEND arc | The canonical contradiction sequence that drives the emotional centerpiece of v0.1 |
| **D-035** | Tag taxonomy with prerequisites | Monologue prerequisite tags gate on knowledge state -- must align with grant mechanism |
| **D-041** | Knowledge graph data model | The foundation -- data model is stable, this workshop fills the behavioral gaps |
| **D-071** | Eavesdropping / ListeningFocus | Overheard info enters KG at lower confidence -- informs NPC overhearing rules |
| **D-078** | Overheard NPC conversation | Server-authoritative occlusion filter, passive dialogue panel -- player-side of NPC conversations |
---
## Workshop Topics
### Topic 1: Knowledge Flow -- The Grant Mechanism
When an NPC tells the player something (dialogue) or the player discovers something (POI, evidence, overheard conversation), how does the knowledge graph get updated?
**Sub-questions:**
1. **Wire `knowledge_grant` on dialogue lines.** The YAML schema exists (`KnowledgeGrant { fact_id, confidence }`). The `IndexedDialogueLine` field exists but is always `None`. What triggers the grant -- line selection? Line display on client? Separate post-dialogue system? Who constructs the `KnowledgeEvent` and what `KnowledgeEventType` variant does it use?
2. **Entity knowledge vs fact knowledge in grants.** Current `KnowledgeGrant` only has `fact_id` (a string). Should grants also update `EntityKnowledge` entries? Example: Sera tells the detective "Kael handles cargo at Dock 7" -- this should create/update an `EntityKnowledge` entry for Kael with `source: ToldBy { source_id: sera_sid }`, not just a fact. Does `KnowledgeGrant` need an `entity_grant` variant?
3. **POI discovery as knowledge flow.** `PointOfInterest` (#148) needs to enter the KG. Options: (a) new `FactId` category "poi.*" (e.g., `FactId("poi.dock_7_restricted")`), (b) extend `EntityKnowledge` to cover locations, (c) separate POI knowledge type. Which integrates cleanest with existing `filter_by_access` and dialogue prerequisite checks?
4. **Physical evidence discovery** (terminals, documents, cargo manifests). Same grant mechanism as dialogue? Or a separate `KnowledgeEventType::EvidenceDiscovered`?
5. **Content author guardrails.** What knowledge should NPCs be ABLE to grant? A content validation rule that prevents authors from accidentally making NPCs omniscient. Example: NPC can only grant facts that exist in their own KG. Authoring-time validation or runtime enforcement?
**Feasibility note (Tyre):** The event queue architecture (`KnowledgeEventQueue`) already handles this pattern cleanly. Adding a new `KnowledgeEventType::KnowledgeGranted` variant and a system that fires it post-dialogue-selection is a ~2-day task. The harder question is the schema design for multi-type grants (entity + fact).
### Topic 2: NPC-to-NPC Knowledge Propagation (resolves Q-024)
When NPCs talk to each other, what knowledge transfers?
**Sub-questions:**
1. **Confirm queued approach or revise.** Q-024 preferred direction: queued at routine intersections. The conversation system (`run_npc_conversations`, `server/src/simulation/conversation.rs` line 278+) IS a routine intersection -- NPCs start conversations when proximate during their routines. It already has proximity detection, cooldown timers, and deterministic pairing. Does this satisfy "queued at routine intersections" or do we need a separate system?
2. **Trust-gated filtering.** NPC A has trust level T toward NPC B. What knowledge does A share at each trust level? Proposed mapping to D-028 trust tiers: `surface` trust = share publicly-known facts only, `real` trust = share observations and rumors, `secret` trust = share sensitive knowledge. How does this map to the `KnowledgeConfidence` hierarchy?
3. **Confidence downgrade on transfer.** When NPC A tells NPC B something, B's entry should be at a lower confidence than A's. Proposed: `ToldBy` confidence = `min(source_confidence, KnowsOf)`. Direct observation downgrades to KnowsOf when transferred. `Suspects` stays `Suspects`. This prevents gossip chains from producing `KnowsDetails` knowledge.
4. **Rate limiting.** How much knowledge per conversation? All eligible entries? Random subset? Fixed cap (e.g., 1-3 facts per conversation)? Cap prevents knowledge-explosion from a single NPC-NPC meeting.
5. **Observable by player.** If the player overhears an NPC-NPC conversation (D-078, occlusion filter), what do they learn? The player gets per-word-occluded text. Should the grant mechanism fire for the player based on what words survived occlusion? Or is overheard knowledge always at `Suspects` confidence regardless of fidelity?
6. **ToldBy source construction.** The `KnowledgeSource::ToldBy { source_id: StableId, tick: u64 }` variant is defined but never constructed. This topic must produce the system that creates it. The source entity's `StableId` is already available in the conversation system (line 321: `sid.map(|s| s.0.0).unwrap_or(u64::MAX)`).
**Feasibility note (Tyre):** Tier 1 difficulty -- the conversation system already does the hard part (pairing, lifecycle, events). Adding a knowledge transfer phase between conversation start and conversation end is architecturally clean. The trust-gated filtering adds a query against both NPCs' KGs, which is O(log N) per entry. At ~50 entries per NPC and 1-3 transfers per conversation, this is sub-microsecond per conversation tick.
### Topic 3: Unprompted Disclosure Design (#172)
NPCs volunteer information to the player without being asked. D-028 Layer 4.
**Sub-questions:**
1. **Connection to NPC KG.** `tell_state.rs` currently derives tell category from raw axis values (Secret, Tolerance, Contentment, Mood). #172 needs NPCs to volunteer INFORMATION, which requires knowing what they know. How does the tell derivation system connect to the NPC's `KnowledgeGraph`? Does `DerivedTellState` gain a `disclosure_candidates: Vec<FactId>` field?
2. **"Do I know something you don't?"** Does the NPC check whether the player already knows a fact before volunteering it? Option A: NPC only checks own KG (simpler, may repeat known info -- but the NPC does not know what the player knows). Option B: NPC checks own KG vs player KG (requires cross-entity KG query, but prevents redundant disclosure). Option A is more realistic (NPCs do not know what you know). Option B is better UX. Which do we choose, or is there a middle ground?
3. **Trait filtering (#173).** Do personality/culture traits affect WHAT is disclosed (filtering -- a cautious NPC withholds certain facts) or HOW it is delivered (delivery -- same facts, different phrasing)? Or both? If "what": traits become a KG filter. If "how": traits become a line-pool modifier. If both: traits filter candidate facts, then modify the delivery of surviving candidates.
4. **Trigger conditions.** When does unprompted disclosure fire? Proposed: trust threshold met + mood permits + location appropriate + rate limit not exceeded. Which axes contribute? Contentment, trust toward player, mood state, presence of other NPCs (witnesses)?
5. **Rate limiting.** How often can an NPC volunteer info? Per-NPC cooldown? Per-fact cooldown (do not repeat the same fact)? Global rate limit across all NPCs (prevent disclosure spam)? Interaction with D-028 Layer 4 line cooldown (`LINE_COOLDOWN_TICKS` = 600 ticks in `server/src/simulation/dialogue.rs` line 39)?
**Feasibility note (Tyre):** Tier 2 difficulty. The dialogue pipeline exists and handles line selection well. The challenge is the "what to disclose" derivation -- this is new logic that sits between the KG and the line pool, and it needs to be both mechanically sound and narratively satisfying. Paula and Gestalt need to co-design the disclosure candidate selection before Dudley can implement.
### Topic 4: NPC Information Boundaries (#142)
NPCs should use their own `KnowledgeGraph` for decisions, not ground truth.
**Sub-questions:**
1. **Which NPC systems should be retrofitted? Priority order.** Candidates:
- `npc/tell_state.rs` -- tell derivation (currently uses axes directly, not KG)
- `simulation/conversation.rs` -- conversation partner selection (currently uses proximity, not knowledge of who is nearby)
- `npc/routine.rs` -- routine execution (currently uses `DailyRoutine` directly)
- `simulation/path_follow.rs` -- pathfinding (currently uses ground truth walkability)
2. **Minimum viable boundary.** What is the smallest retrofit that makes a gameplay-visible difference? Proposed: just `tell_state.rs` + unprompted disclosure (#172). An NPC that only reveals what it knows (via KG) is a meaningful boundary even if its pathfinding still uses ground truth.
3. **Simulation tier interaction (D-026).** Background-tier NPCs (500-2000) have minimal KGs. Do they get simplified boundaries? Option: Background NPCs have no KG-based boundaries (they run state machines, not full AI). Only Active-tier NPCs (30-80) get KG-driven behavior. This is consistent with D-026's tiered simulation model.
4. **What breaks?** If `tell_state.rs` reads from KG instead of raw axes: nothing breaks -- KG reflects observed state, which for an NPC's own axes is always up-to-date. If `path_follow.rs` reads from KG: NPCs might "forget" where things are after decay, leading to stuck NPCs or nonsensical pathfinding. The fallback behavior matters.
5. **Fallback behavior.** When an NPC's KG has no relevant information for a decision, what happens? Options: (a) fall through to ground truth (safe but breaks immersion), (b) use last-known state from KG (realistic but may cause stuck behavior), (c) trigger "ask around" behavior (NPC seeks information, creates emergent scenes). v0.1 recommendation: option (a) with logging, option (c) as future enhancement.
**Feasibility note (Tyre):** Tier 1 for minimum viable (tell_state + disclosure). Tier 3 for full retrofit (pathfinding + routine). Recommend starting with the MVP and expanding per sprint. The conversation system retrofit is Tier 2 -- it needs to check whether NPC A knows NPC B exists before initiating conversation, which adds a KG query to the pairing loop.
### Topic 5: Contradiction Detection Pipeline (resolves Q-026)
The downstream consumers are built and tested. Design the detection algorithm.
**Prerequisite:** Topics 1 and 2 must produce `ToldBy` sources. Without `ToldBy` entries in KGs, there is nothing to contradict.
**Sub-questions:**
1. **Location contradiction (simplest case).** NPC says "X was at location A at time T" (`ToldBy` source). Player observes X at location B at time T (`DirectObservation` source). System detects: same entity, overlapping time window, different locations. Both entries receive `Contradicted` state.
2. **Attribute contradiction.** NPC says "X is trustworthy" (attribute in `known_attributes`). Player discovers X is a smuggler (different attribute value for same key). How are attribute keys structured to enable comparison? Current `known_attributes: BTreeMap<String, String>` is untyped -- does contradiction detection need typed attribute keys?
3. **Content-authored vs automatic detection.** Which contradictions are hand-authored ("fact A contradicts fact B" in content files) and which are algorithmic (same-subject different-value)? Proposed: location contradictions are automatic (algorithmic, based on position + time). Attribute contradictions are content-authored (explicit contradiction pairs in YAML). Fact contradictions are hybrid (some automatic categories, some authored).
4. **Detection timing.** When does contradiction detection run? Per-tick (expensive but immediate)? Per-game-minute (matches decay frequency)? On KG write (event-driven, only checks new entries against existing)? The event-driven approach is most efficient -- only fire detection when a `KnowledgeEvent` modifies a relevant entry.
5. **Event emission chain.** Detection fires `ContradictionDetected` event containing: observer entity, contradicting entries (A, B), contradiction type. Downstream: monologue system triggers "Wait -- that does not add up" line. Relationship system shifts to `PersonOfInterest`. Anomaly marker set. D-033 color transitions amber. What is the exact event flow?
6. **THE FRIEND arc mechanical sequence.** The canonical example from D-034: Sera tells detective "Kael was at the dock during second shift" -> detective observes Kael in corridor B-7 at that time -> location contradiction detected -> both entries Contradicted -> monologue: "Sera said Kael was at the dock. I just saw him in B-7." -> Sera shifts to PersonOfInterest -> amber color. Walk through this sequence and confirm every system fires correctly with the proposed design.
**Feasibility note (Tyre):** Location contradiction detection is Tier 1 -- BTreeMap lookup by `StableId`, compare positions within a time window. Attribute contradiction is Tier 2 -- needs typed attribute keys or content-authored pairs. The event-driven approach (check on KG write) keeps it off the per-tick hot path. Estimated: ~3 days for location detection + event chain, ~2 additional days for attribute detection.
---
## What This Workshop Is NOT
- **Not redesigning D-041.** The data model is stable and works. Types, BTreeMap mandate, event queue, decay -- all ship-tested. This workshop fills behavioral gaps, not structural ones.
- **Not full gossip implementation.** The propagation design is Sprint 17+ dev work. This workshop produces the specification, not the code.
- **Not resolving Q-017** (triangle pressure thresholds). Still deferred -- requires gameplay data.
- **Not designing the full FRIEND arc narrative.** That is Paula + Mellanie content work. This workshop defines the mechanical sequence that enables the narrative.
- **Not addressing Q-025 in depth.** Recommendation: close formally (not needed at current scale). If the workshop agrees, Qatux records the closure.
## Expected Outputs
1. **Decision: D-0XX -- Knowledge Flow & NPC Information Boundaries.** Comprehensive decision covering: grant mechanism schema, NPC-to-NPC propagation model, NPC boundary scope, contradiction detection algorithm. Resolves Q-024 and Q-026.
2. **Formal closure of Q-025** (not needed at current scale, re-evaluate at 500+ Active NPCs).
3. **Implementation scope for #141** -- remaining work list: wire `knowledge_grant` field through dialogue pipeline, construct `KnowledgeEvent` on line selection, verify `filter_by_access` integration.
4. **Implementation scope for #142** -- which systems to retrofit, priority order, minimum viable boundary, fallback behavior spec.
5. **Updated ticket descriptions for #172 and #173** -- mechanical specifications from the workshop: disclosure candidate selection algorithm, trait filter vs modifier decision, trigger conditions, rate limits.
6. **New tickets as needed** -- knowledge propagation system, contradiction detection system, POI-KG integration, `KnowledgeEventType` variants.
## Workshop Format
Two rounds, following project convention:
- **Round 1:** Each participant independently analyzes all 5 topics from their domain perspective. Reference specific code files and line numbers. Tyre: system architecture, ECS patterns, performance budgets. Gestalt: mechanical interactions, "is this fun?", knowledge as gameplay lever. Dudley: Rust implementation feasibility, integration with existing systems, edge cases. Paula: what NPCs should say/know, THE FRIEND arc sequence, narrative consequences of design choices.
- **Round 2:** Cross-review, debate, synthesis into concrete decisions. Produce D-record(s), close Q-024/Q-025/Q-026, and generate implementation tickets with mechanical specifications.
@@ -188,7 +188,7 @@ XREF ERROR: dialogue location "the-warehouse" not in district
```
XREF ERROR: unknown fact_id "investigation.unknown_fact"
In: campaigns/main/.../dialogue/the-terminal/kael-davan.yaml
Line: the-terminal_d_099
Line: kael-davan_d_099
Canonical fact_ids: 42 defined in content/global/knowledge/
```
@@ -228,7 +228,7 @@ XREF WARNING: npc_count mismatch in transit district
#### Check 8: Dialogue line ID uniqueness within pool
**What:** Each dialogue line has an `id` field (e.g., `the-terminal_d_001`). IDs MUST be unique within each dialogue file.
**What:** Each dialogue line has an `id` field (e.g., `kael-davan_d_001`). IDs MUST be unique within each dialogue file.
**Where:** `content/campaigns/**/dialogue/**/*.yaml` → `lines[].id`
@@ -236,7 +236,7 @@ XREF WARNING: npc_count mismatch in transit district
**Error format:**
```
XREF ERROR: duplicate dialogue line id "the-terminal_d_015"
XREF ERROR: duplicate dialogue line id "kael-davan_d_015"
In: campaigns/main/.../dialogue/the-terminal/kael-davan.yaml
First occurrence: line 167
Duplicate: line 215
+111
View File
@@ -0,0 +1,111 @@
# Settled Reach — Sprite Render Pipeline
Offline Godot 4 renderer for the 3D-to-2D sprite pipeline. Produces entity and structural sprites at three resolutions from a fixed camera angle, with outlines applied at working resolution.
## Camera Specification (D-019)
- **Angle**: -72.5° from horizontal (= 17.5° from vertical, the midpoint of the 15-20° from vertical range)
- **Projection**: Orthographic (`size = 1.4`)
- **Position**: `(0, 3, 1)` — above and slightly in front of the model origin
- **Godot Transform3D**: `Transform3D(1, 0, 0, 0, 0.30071, 0.95372, 0, -0.95372, 0.30071, 0, 3, 1)`
This is "the angle" per D-019 amendment (2026-02-12). All entity, object, and wall sprites for v0.1 are rendered at this angle. The Godot gameplay camera is purely orthographic — this tilt is an art convention expressed through the 3D render.
## Lighting Rig
Three-point studio rig. All lights have `shadow_enabled = false` — sprites are shape templates, no baked shadows or directional lighting. The Godot runtime PointLight2D pipeline provides all scene lighting at runtime (D-043).
| Light | Energy | Direction | Purpose |
|-------|--------|-----------|---------|
| KeyLight | 1.0 | Matches camera angle (-72.5° from horizontal) | Main illumination; ensures front face is lit as the camera sees it |
| FillLight | 0.4 | 45° from right side (-45° pitch, +90° yaw) | Fills shadow opposite the key; no deep-black patches on right face |
| RimLight | 0.3 | 45° from behind (-45° pitch, +180° yaw) | Back-edge accent; defines silhouette boundary for outline processing |
**Rationale**: Even 3-light coverage ensures no black patches on a convex mesh, keeping surface colors flat and readable for the outline processing step.
## Resolution Chain (D-043, D-044)
```
1024×1024 — source PNG, full fidelity for outline processing
↓ bilinear resize
256×256 — working PNG, outline applied at 4-8px, color #333340
↓ bilinear resize
64×64 — runtime PNG, deployed to client/assets/sprites/
```
Outline implementation: alpha-mask dilation + flat color fill. Outline width in `render_export.gd`: `outline_width_px = 4` (at 256px = 1px effective at 64px).
## Entity Footprint Spec (D-044, D-066)
- Entity sprites occupy a **24×32px footprint** within the 64×64 visual tile
- Entities render across a **2×2 sim tile sprite footprint** (D-066)
- Visual tile = 64×64px at runtime; sim tile = 32×32px (0.5m)
- **NPC model scale**: `CapsuleMesh(radius=0.25, height=0.62)` at camera size 1.4 produces ~24×32px apparent size in the 64px output tile
## Usage
### Command Line
```bash
# From the repository root
godot --path renderer/ --headless --quit-after 1200 res://render_scene.tscn -- <model_name>
```
The `/sprite-gen` skill wraps this command and handles output placement.
### Adding a New Model
1. Create `renderer/models/<model_name>.tscn` — root node is a `MeshInstance3D` (or `Node3D` with children)
2. Center the mesh at the origin
3. Use a flat `StandardMaterial3D` (no baked shadows — just albedo color + roughness)
4. Run: `godot --path renderer/ --headless --quit-after 1200 res://render_scene.tscn -- <model_name>`
5. Output: 12 PNGs in `renderer/output/` (4 directions × 3 resolutions)
6. Runtime sprites: copy 64px variants to `client/assets/sprites/`
### Material Guidelines
- **Entity sprites**: neutral flat material (albedo Color(0.5, 0.5, 0.5)). D-033 relationship color tinting applied at runtime by the client's entity renderer. Outline color (#333340 per D-043) is compatible with all D-033 tint colors — the dark blue-grey outline remains visible against teal, green, amber, and red entity tints.
- **Structural sprites**: use zone-appropriate texture (`textures/wall_institutional_era1.png`, etc.)
- No specularity: `metallic = 0.0`, `roughness = 0.9`
- No emission, no normal maps — shape is the signal
## Models
| Model | File | Type | Notes |
|-------|------|------|-------|
| `wall_structural` | `models/wall_structural.tscn` | Structure | 1.0×0.8×0.2 box, institutional era-1 texture |
| `wall_bar_green` | `models/wall_bar_green.tscn` | Structure | 1.0×0.8×0.2 box, green panel texture (model only — no sprites rendered yet) |
| `npc_generic` | `models/npc_generic.tscn` | Entity | Capsule silhouette, neutral grey, 24×32px footprint. Rotationally symmetric — north/south and east/west pairs are near-identical by design (asymmetric silhouettes come from named NPC models with identifying features per D-044). |
## Output Naming Convention
```
<model_name>_<direction>_<resolution>.png
wall_structural_north_64.png
npc_generic_south_256.png
```
Directions: `north`, `east`, `south`, `west` (model rotated 0°, 90°, 180°, 270° around Y axis).
## Runtime Sprite Path
64px sprites are deployed to: `client/assets/sprites/`
Naming convention at the client side matches the pipeline output: `<model_name>_<direction>_64.png`. The 1024 and 256 variants stay in `renderer/output/` as pipeline intermediates (gitignored).
## Design Constraints
- **No baked lighting**: sprites are shape templates. Lighting is applied at runtime by Godot's PointLight2D per D-046.
- **No baked shadows**: shadow direction would conflict with runtime point lights at arbitrary positions.
- **No baked mood**: flat materials, three-point neutral rig. Zone atmosphere comes from runtime CanvasModulate.
- **Outline applied at 256px, not 64px**: ensures outline is crisp after bilinear downscale.
- **Transparent background**: `SubViewport.transparent_bg = true` — sprites are PNG with alpha, composited at runtime.
## Cross-References
- D-019: Camera angle spec and amendment
- D-043: Art direction — "functional warmth," resolution chain, outline color
- D-044: Visual hierarchy, entity footprint spec
- D-049: Z-level rendering stack (sprites land on layers 2-3)
- D-066: Dual-scale grid, 2×2 sim tile entity footprint
+14
View File
@@ -0,0 +1,14 @@
[gd_scene load_steps=3 format=3 uid="uid://npcgeneric001"]
[sub_resource type="CapsuleMesh" id="CapsuleMesh_npc"]
radius = 0.25
height = 0.62
[sub_resource type="StandardMaterial3D" id="StandardMaterial3D_npc"]
albedo_color = Color(0.5, 0.5, 0.5, 1.0)
metallic = 0.0
roughness = 0.9
[node name="NpcGeneric" type="MeshInstance3D"]
mesh = SubResource("CapsuleMesh_npc")
surface_material_override/0 = SubResource("StandardMaterial3D_npc")
+1 -1
View File
@@ -1,4 +1,4 @@
[gd_scene load_steps=3 format=3]
[gd_scene load_steps=3 format=3 uid="uid://wallbargreen001"]
[ext_resource type="Texture2D" path="res://textures/wall_bar_green_panels.png" id="1_wall_texture"]
+1 -1
View File
@@ -10,7 +10,7 @@ const ROTATIONS: PackedFloat64Array = [0.0, 90.0, 180.0, 270.0]
@export var model_scene: PackedScene
@export var output_name: String = "wall_structural"
@export var outline_width_px: int = 4 # at 256 = 1px at 64
@export var outline_color: Color = Color(0.2, 0.2, 0.25, 1.0)
@export var outline_color: Color = Color(0.2, 0.2, 0.25, 1.0) # #333340 per D-043
@export var render_now: bool = false:
set(value):
if value and model_scene:
+15 -3
View File
@@ -12,16 +12,28 @@ render_target_update_mode = 1
own_world_3d = true
[node name="Camera3D" type="Camera3D" parent="SubViewport"]
transform = Transform3D(1, 0, 0, 0, 0.309017, 0.951057, 0, -0.951057, 0.309017, 0, 3, 1)
transform = Transform3D(1, 0, 0, 0, 0.30071, 0.95372, 0, -0.95372, 0.30071, 0, 3, 1)
projection = 1
size = 1.4
near = 0.01
far = 10.0
[node name="DirectionalLight3D" type="DirectionalLight3D" parent="SubViewport"]
transform = Transform3D(1, 0, 0, 0, 0.309017, 0.951057, 0, -0.951057, 0.309017, 0, 3, 1)
[node name="KeyLight" type="DirectionalLight3D" parent="SubViewport"]
transform = Transform3D(1, 0, 0, 0, 0.30071, 0.95372, 0, -0.95372, 0.30071, 0, 0, 0)
light_color = Color(1, 1, 1, 1)
light_energy = 1.0
shadow_enabled = false
[node name="FillLight" type="DirectionalLight3D" parent="SubViewport"]
transform = Transform3D(0, -0.70711, 0.70711, 0, 0.70711, 0.70711, -1, 0, 0, 0, 0, 0)
light_color = Color(1, 1, 1, 1)
light_energy = 0.4
shadow_enabled = false
[node name="RimLight" type="DirectionalLight3D" parent="SubViewport"]
transform = Transform3D(-1, 0, 0, 0, 0.70711, 0.70711, 0, 0.70711, -0.70711, 0, 0, 0)
light_color = Color(1, 1, 1, 1)
light_energy = 0.3
shadow_enabled = false
[node name="ModelRoot" type="Node3D" parent="SubViewport"]
+1 -1
View File
@@ -1092,7 +1092,7 @@ dependencies = [
[[package]]
name = "settled-reach-server"
version = "0.1.14"
version = "0.1.15"
dependencies = [
"bevy_app",
"bevy_ecs",
+3 -8
View File
@@ -178,13 +178,6 @@ impl Plugin for BridgePlugin {
.after(crate::simulation::sound::collect_sound_events)
.after(crate::simulation::conversation::run_npc_conversations)
.after(crate::simulation::dialogue::process_walk_away),
crate::simulation::dialogue::process_talk_interaction
.after(crate::simulation::input::process_player_input),
crate::simulation::dialogue::process_walk_away
.after(crate::simulation::input::process_player_input)
.after(crate::simulation::dialogue::process_talk_interaction),
crate::simulation::dialogue::process_confrontation_response
.after(crate::simulation::input::process_player_input),
crate::simulation::follow::update_follow_state
.after(crate::perception::observer::compute_visibility_geometry)
.after(crate::simulation::movement::validate_movement)
@@ -195,9 +188,11 @@ impl Plugin for BridgePlugin {
.after(crate::simulation::monologue::trigger_event_monologue)
.after(crate::simulation::dialogue::process_talk_interaction)
.after(crate::simulation::dialogue::process_confrontation_response)
.after(crate::simulation::dialogue::process_dialogue_response)
.before(crate::simulation::time::advance_tick),
crate::perception::observation::emit_observation_events
.after(crate::perception::observer::compute_observer_snapshot),
.after(crate::perception::observer::compute_observer_snapshot)
.before(crate::simulation::time::advance_tick),
send_bridge_snapshot
.after(crate::perception::observer::compute_observer_snapshot),
),
+7
View File
@@ -377,6 +377,13 @@ pub enum PlayerAction {
/// Clears dialogue, monologue, and interaction buffers.
/// Rejected with a log warning on non-Gauntlet maps.
TeleportToHub,
/// Player chose a dialogue option (#539, D-028 follow-up).
/// response_id is the line_id that was displayed; target_entity_id is the NPC's wire ID.
/// Server runs the same 4-layer pipeline to select a follow-up line.
DialogueResponse {
target_entity_id: u64,
response_id: String,
},
}
impl PlayerAction {
+12
View File
@@ -27,6 +27,7 @@ impl Plugin for NpcPlugin {
fn build(&self, app: &mut App) {
app.init_resource::<relationships::RelationshipGraph>()
.init_resource::<relationships::TrustEventQueue>()
.init_resource::<crate::knowledge::KnowledgeEventQueue>()
.init_resource::<routine::PreviousDayPhase>()
.init_resource::<tolerance::ToleranceBreachEventQueue>()
.init_resource::<routine::RoutineDeviationEventQueue>()
@@ -42,6 +43,7 @@ impl Plugin for NpcPlugin {
.after(crate::simulation::dialogue::process_talk_interaction)
.after(crate::simulation::dialogue::process_walk_away)
.after(crate::simulation::dialogue::process_confrontation_response)
.after(crate::simulation::dialogue::process_dialogue_response)
.before(crate::simulation::time::advance_tick),
relationships::update_relationship_dynamics
.after(relationships::update_trust)
@@ -59,6 +61,16 @@ impl Plugin for NpcPlugin {
.after(mood::update_mood)
.after(routine::detect_routine_deviation)
.before(crate::perception::observer::compute_observer_snapshot),
crate::simulation::dialogue::process_talk_interaction
.after(crate::simulation::input::process_player_input),
crate::simulation::dialogue::process_walk_away
.after(crate::simulation::input::process_player_input)
.after(crate::simulation::dialogue::process_talk_interaction),
crate::simulation::dialogue::process_confrontation_response
.after(crate::simulation::input::process_player_input),
crate::simulation::dialogue::process_dialogue_response
.after(crate::simulation::input::process_player_input)
.after(crate::simulation::dialogue::process_talk_interaction),
),
);
+740 -62
View File
@@ -79,6 +79,11 @@ impl Default for CurrentMood {
///
/// Prevents the same line from being selected within LINE_COOLDOWN_TICKS.
/// Entries older than the cooldown window are pruned each query.
///
/// Design note: this is player-global, not per-NPC. Line IDs are NPC-scoped
/// per D-035 (`{template}_{d|m|e}_{###}`), so cross-NPC collisions don't occur
/// in practice. If a future sprint introduces shared line IDs across roles,
/// the key should become `(StableId, line_id)` instead.
#[derive(Component, Debug, Default)]
pub struct DialogueCooldownTracker {
used: std::collections::BTreeMap<String, u64>, // line_id → tick_used (D-041)
@@ -116,6 +121,16 @@ pub struct ActiveDialogue {
pub started_tick: u64,
}
/// Marker: player submitted a dialogue response this tick (#539).
///
/// Set by process_player_input when PlayerAction::DialogueResponse is received.
/// Consumed and removed by process_dialogue_response each tick.
#[derive(Component, Debug)]
pub struct DialogueResponseRequest {
pub target: Entity,
pub response_id: String,
}
/// Marker: player walked away during active dialogue this tick (D-064).
///
/// Set by process_player_input when PlayerAction::WalkAway is received.
@@ -311,11 +326,9 @@ pub fn select_dialogue_line<'a>(
return None;
}
// Weighted random selection
// Weighted random selection — score_line always returns >= 1 (base score),
// so total_weight > 0 is guaranteed when scored is non-empty.
let total_weight: u32 = scored.iter().map(|(_, s)| s).sum();
if total_weight == 0 {
return None;
}
let mut roll = rng.random_range(0..total_weight);
for (line, weight) in &scored {
@@ -325,8 +338,70 @@ pub fn select_dialogue_line<'a>(
roll -= weight;
}
// Fallback (shouldn't reach here with valid weights)
Some(scored.last().unwrap().0)
unreachable!("weighted selection with total_weight > 0 must select a line")
}
// ---------------------------------------------------------------------------
// Shared pipeline: Layers 1-4
// ---------------------------------------------------------------------------
/// Run the full D-028 four-layer dialogue pipeline and return a selected line.
///
/// Shared by `process_talk_interaction` and `process_dialogue_response` to
/// avoid duplicating the L1-L4 query + scoring logic. Callers handle the
/// result differently (initial Talk sets ActiveDialogue; follow-up may clear it).
fn run_dialogue_pipeline<'a>(
line_pool: &'a crate::content::line_pool::LinePoolIndex,
location: &str,
role: &str,
relationship: RelationshipState,
confidence: crate::knowledge::types::KnowledgeConfidence,
day_phase: crate::simulation::time::DayPhase,
interaction_mem: Option<&InteractionMemory>,
npc_mood: Option<Mood>,
cooldown: &DialogueCooldownTracker,
tick: u64,
rng: &mut impl Rng,
) -> Option<&'a IndexedDialogueLine> {
// Layer 1: Access tiers from relationship
let access_tiers = available_access_tiers(relationship);
// Layer 2: Derive active situations from game state
let mut situations = derive_situations(day_phase, relationship);
// Layer 2 extension: first_meeting / repeated_visit from InteractionMemory (#325, D-028)
if let Some(mem) = interaction_mem {
if mem.is_first_meeting() {
situations.push(Situation::FirstMeeting);
} else if mem.is_repeated_visit() {
situations.push(Situation::RepeatedVisit);
}
}
// Layer 3: Trust tier from relationship + confidence (D-075)
let trust = relationship_to_trust(relationship, confidence);
// Query Layers 1-3: collect candidates across all available access tiers
let mut candidates: Vec<&IndexedDialogueLine> = Vec::new();
let mut seen_ids: BTreeSet<&str> = BTreeSet::new();
for access in &access_tiers {
let results = line_pool.query_dialogue(location, role, *access, &situations, trust);
for line in results {
if seen_ids.insert(&line.id) {
candidates.push(line);
}
}
}
if candidates.is_empty() {
return None;
}
// Layer 4: Topic + mood weighted selection
let active_topics: Vec<Topic> = Vec::new(); // v0.1: no topic context yet
select_dialogue_line(&candidates, npc_mood, &active_topics, cooldown, tick, rng)
}
// ---------------------------------------------------------------------------
@@ -405,73 +480,26 @@ pub fn process_talk_interaction(
.map(|sid| observer_kg.relationship_with(&sid))
.unwrap_or(RelationshipState::Unknown);
// Layer 1: Access tiers from relationship
let access_tiers = available_access_tiers(relationship);
// Layer 2: Derive active situations from game state
let mut situations = derive_situations(time.day_phase(), relationship);
// Layer 2 extension: first_meeting / repeated_visit from InteractionMemory (#325, D-028)
if let Some(ref mem) = interaction_mem_opt {
if mem.is_first_meeting() {
situations.push(Situation::FirstMeeting);
} else if mem.is_repeated_visit() {
situations.push(Situation::RepeatedVisit);
}
}
// Layer 3: Trust tier from relationship + confidence (D-075)
// Default to Suspects for unknown NPCs — no KG entry means no basis for
// deeper dialogue, which correctly yields Surface trust tier.
let confidence = target_stable
.and_then(|sid| observer_kg.confidence_of(&sid))
.unwrap_or(crate::knowledge::types::KnowledgeConfidence::Suspects);
let trust = relationship_to_trust(relationship, confidence);
// Query Layers 1-3: collect candidates across all available access tiers
let mut candidates: Vec<&IndexedDialogueLine> = Vec::new();
let mut seen_ids: BTreeSet<&str> = BTreeSet::new();
for access in &access_tiers {
let results = line_pool.0.query_dialogue(
&profile.location,
&profile.role,
*access,
&situations,
trust,
);
for line in results {
// Deduplicate across access tiers (BTreeSet for deterministic iteration)
if seen_ids.insert(&line.id) {
candidates.push(line);
}
}
}
if candidates.is_empty() {
tracing::debug!(
"No dialogue lines available for {}/{} (access={:?}, situations={:?}, trust={:?})",
profile.location,
profile.role,
access_tiers,
situations,
trust,
);
commands.entity(player_entity).remove::<TalkRequest>();
return;
}
// Layer 4: Topic + mood weighted selection
let npc_mood = mood_opt.map(|m| m.0);
let active_topics: Vec<Topic> = Vec::new(); // v0.1: no topic context yet
// Prune old cooldown entries
cooldown.prune(time.tick);
let selected = select_dialogue_line(
&candidates,
let selected = run_dialogue_pipeline(
&line_pool.0,
&profile.location,
&profile.role,
relationship,
confidence,
time.day_phase(),
interaction_mem_opt.as_deref(),
npc_mood,
&active_topics,
&cooldown,
time.tick,
&mut rng.rng,
@@ -658,7 +686,7 @@ pub fn process_walk_away(
/// Hardcoded confrontation monologue lines (D-063).
/// Fired as a monologue spike when the player delivers a confrontation.
/// Future: move to content pools with trigger="confrontation_delivered".
/// TODO: move to content pools with trigger="confrontation_delivered" (D-028/D-035).
const CONFRONTATION_LINES: &[(&str, &str)] = &[
(
"confront_01",
@@ -767,6 +795,185 @@ pub fn process_confrontation_response(
.remove::<ConfrontationDelivered>();
}
// ---------------------------------------------------------------------------
// System: process_dialogue_response (#539)
// ---------------------------------------------------------------------------
/// Process DialogueResponse actions through the full D-028 four-layer pipeline (#539).
///
/// Called when the player picks a dialogue option. Re-runs the same pipeline as
/// process_talk_interaction to select a follow-up line. Clears ActiveDialogue if
/// no candidates remain after cooldown filtering (conversation ends naturally).
///
/// The response_id is the line_id that was shown; it's already on cooldown from
/// process_talk_interaction, ensuring the follow-up is a different line.
///
/// System ordering: after process_player_input, after process_talk_interaction,
/// before compute_observer_snapshot.
#[tracing::instrument(level = "debug", skip_all)]
#[allow(clippy::type_complexity, clippy::too_many_arguments)]
pub fn process_dialogue_response(
mut commands: Commands,
time: Res<SimulationTime>,
line_pool: Option<Res<LinePoolIndexResource>>,
registry: Res<EntityRegistry>,
mut rng: ResMut<SimRng>,
mut trust_queue: ResMut<TrustEventQueue>,
mut player_query: Query<
(
Entity,
&KnowledgeGraph,
&DialogueResponseRequest,
&mut DialogueResponseBuffer,
&mut DialogueCooldownTracker,
Option<&ActiveDialogue>,
),
With<PlayerCharacter>,
>,
mut npc_query: Query<(
&DialogueProfile,
Option<&CurrentMood>,
Option<&mut InteractionMemory>,
Option<&NpcName>,
Option<&NpcColorIndex>,
)>,
) {
let Ok((
player_entity,
observer_kg,
response_req,
mut response_buffer,
mut cooldown,
active_dialogue_opt,
)) = player_query.single_mut()
else {
return;
};
let target = response_req.target;
let response_id = response_req.response_id.clone();
// Always remove the marker regardless of outcome — request is consumed this tick.
commands
.entity(player_entity)
.remove::<DialogueResponseRequest>();
let Some(line_pool) = line_pool else { return };
// Look up NPC dialogue profile, mood, interaction history, name, and color
let Ok((profile, mood_opt, mut interaction_mem_opt, npc_name_opt, color_idx_opt)) =
npc_query.get_mut(target)
else {
tracing::debug!(
"DialogueResponse target {:?} has no DialogueProfile — cannot select follow-up",
target
);
return;
};
// Resolve target's StableId for KG lookup
let target_stable = registry.to_stable(target);
let relationship = target_stable
.map(|sid| observer_kg.relationship_with(&sid))
.unwrap_or(RelationshipState::Unknown);
let confidence = target_stable
.and_then(|sid| observer_kg.confidence_of(&sid))
.unwrap_or(crate::knowledge::types::KnowledgeConfidence::Suspects);
let npc_mood = mood_opt.map(|m| m.0);
// Prune old cooldown entries
cooldown.prune(time.tick);
let selected = run_dialogue_pipeline(
&line_pool.0,
&profile.location,
&profile.role,
relationship,
confidence,
time.day_phase(),
interaction_mem_opt.as_deref(),
npc_mood,
&cooldown,
time.tick,
&mut rng.rng,
);
if let Some(line) = selected {
let Some(speaker_stable) = registry.to_stable(target) else {
tracing::warn!(
"DialogueResponse target {:?} not in EntityRegistry — skipping follow-up",
target
);
return;
};
let speaker_display_name = {
let known = observer_kg
.entity_knowledge(&speaker_stable)
.map(|e| e.known_attributes.contains_key("name"))
.unwrap_or(false);
if known {
npc_name_opt
.map(|n| n.0.clone())
.unwrap_or_else(|| "Unknown".to_string())
} else {
display_label_for_role(&profile.role)
}
};
let speaker_color = color_idx_opt.map(|c| c.0).unwrap_or(0u8);
response_buffer.response = Some(DialogueResponseEvent {
line_id: line.id.clone(),
text: line.text.clone(),
speaker_entity_id: speaker_stable.0,
speaker_color_index: speaker_color,
speaker_name: speaker_display_name,
});
cooldown.record(&line.id, time.tick);
// Update ActiveDialogue with current tick — prevents stale started_tick
commands.entity(player_entity).insert(ActiveDialogue {
target,
interaction_type: crate::knowledge::events::InteractionType::Talk,
started_tick: time.tick,
});
// Trust progression: follow-up dialogue warms the NPC
trust_queue.push(TrustEvent::TalkCompleted {
npc: target,
player: player_entity,
});
// Interaction tracking: record follow-up as a talk event
if let Some(ref mut mem) = interaction_mem_opt {
mem.record_talk(time.tick);
}
tracing::debug!(
"Follow-up selected: id={}, response_id={}, location={}, role={}",
line.id,
response_id,
profile.location,
profile.role,
);
} else {
// No follow-up lines — conversation ends naturally (D-062: invisible locks)
tracing::debug!(
"No follow-up lines for response_id={} at {}/{} — ending conversation",
response_id,
profile.location,
profile.role,
);
// Clear active dialogue state
if active_dialogue_opt.is_some() {
commands.entity(player_entity).remove::<ActiveDialogue>();
}
}
}
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
@@ -1787,6 +1994,304 @@ mod tests {
use rand::SeedableRng;
// -- Trust-gated gossip tests (#171, D-075) ----------------------------------
/// Build a pool with a Surface-tier Public line and a Secret-tier Insider line.
/// Used to verify that KnowledgeConfidence gates Secret access correctly.
fn build_trust_tier_pool() -> LinePoolIndex {
let mut index = LinePoolIndex::default();
let pool = IndexedDialoguePool {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
lines: vec![
IndexedDialogueLine {
id: "trust_surface_001".to_string(),
text: "Just another day at the terminal.".to_string(),
role: "dock-worker".to_string(),
access: vec![AccessTier::Public],
trust: TrustTier::Surface,
situation: vec![Situation::Routine],
topic: vec![],
mood: vec![],
tags: vec![],
knowledge_grant: None,
},
IndexedDialogueLine {
id: "trust_secret_001".to_string(),
text: "The manifests don't match. You didn't hear that from me.".to_string(),
role: "dock-worker".to_string(),
access: vec![AccessTier::Insider], // requires Friendly relationship
trust: TrustTier::Secret, // requires Friendly + KnowsDetails+
situation: vec![Situation::Routine],
topic: vec![],
mood: vec![],
tags: vec![],
knowledge_grant: None,
},
],
};
index.dialogue.insert(
("the-terminal".to_string(), "dock-worker".to_string()),
pool,
);
index
}
#[test]
fn trust_gated_knows_details_can_get_secret_tier_line() {
// D-075: Friendly + KnowsDetails → Secret trust tier → secret lines available.
// Spec ref: #171, D-075 "Secret: Friendly + KnowsDetails+"
use crate::knowledge::types::KnowledgeConfidence;
let mut world = setup_dialogue_world();
world.insert_resource(LinePoolIndexResource(build_trust_tier_pool()));
let npc = world
.spawn((
Npc,
TilePosition::new(5, 5, 0),
DialogueProfile {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
))
.id();
let npc_sid = world.resource_mut::<EntityRegistry>().register(npc);
// observe_entity → Direct; observe_entity_leaving_los → KnowsDetails
let mut kg = KnowledgeGraph::new();
kg.observe_entity(npc_sid, TilePosition::new(5, 5, 0), 0);
kg.observe_entity_leaving_los(&npc_sid, 1);
kg.set_relationship(&npc_sid, RelationshipState::Friendly);
assert_eq!(
kg.confidence_of(&npc_sid),
Some(KnowledgeConfidence::KnowsDetails),
"precondition: KG must have KnowsDetails confidence"
);
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
kg,
TalkRequest { target: npc },
DialogueResponseBuffer::default(),
DialogueCooldownTracker::default(),
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
// Run with multiple seeds — Secret-tier line must appear at least once
let mut saw_secret_line = false;
for seed in 0u64..50 {
world.get_mut::<DialogueResponseBuffer>(player).unwrap().response = None;
world.entity_mut(player).insert(TalkRequest { target: npc });
// Reset cooldown so the pool is not exhausted between iterations
world.entity_mut(player).insert(DialogueCooldownTracker::default());
world.insert_resource(SimRng::new(seed));
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_talk_interaction);
schedule.run(&mut world);
world.flush();
if let Some(resp) = &world
.get::<DialogueResponseBuffer>(player)
.unwrap()
.response
{
if resp.line_id == "trust_secret_001" {
saw_secret_line = true;
break;
}
}
}
assert!(
saw_secret_line,
"Friendly + KnowsDetails player should be able to access Secret-tier lines (D-075 #171)"
);
}
#[test]
fn trust_gated_suspects_only_gets_surface_tier() {
// D-075: Friendly + Suspects → Surface trust tier → Secret lines invisible.
// Spec ref: #171, D-075 "Surface: any relationship + any confidence"
use crate::knowledge::types::KnowledgeConfidence;
let mut world = setup_dialogue_world();
world.insert_resource(LinePoolIndexResource(build_trust_tier_pool()));
let npc = world
.spawn((
Npc,
TilePosition::new(5, 5, 0),
DialogueProfile {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
))
.id();
let npc_sid = world.resource_mut::<EntityRegistry>().register(npc);
// Friendly relationship but Suspects confidence → Surface trust only
let mut kg = KnowledgeGraph::new();
kg.observe_entity(npc_sid, TilePosition::new(5, 5, 0), 0);
kg.set_relationship(&npc_sid, RelationshipState::Friendly);
// Patch down to Suspects (observe_entity sets Direct — too high)
kg.entities.get_mut(&npc_sid).unwrap().confidence = KnowledgeConfidence::Suspects;
assert_eq!(
kg.confidence_of(&npc_sid),
Some(KnowledgeConfidence::Suspects),
"precondition: KG must have Suspects confidence"
);
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
kg,
TalkRequest { target: npc },
DialogueResponseBuffer::default(),
DialogueCooldownTracker::default(),
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
let mut saw_secret = false;
for seed in 0u64..50 {
world.get_mut::<DialogueResponseBuffer>(player).unwrap().response = None;
world.entity_mut(player).insert(TalkRequest { target: npc });
world.entity_mut(player).insert(DialogueCooldownTracker::default());
world.insert_resource(SimRng::new(seed));
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_talk_interaction);
schedule.run(&mut world);
world.flush();
if let Some(resp) = &world
.get::<DialogueResponseBuffer>(player)
.unwrap()
.response
{
if resp.line_id == "trust_secret_001" {
saw_secret = true;
break;
}
}
}
assert!(
!saw_secret,
"Friendly + Suspects player must NOT access Secret-tier lines (D-075 #171)"
);
}
// -- Line variety regression test (#338, D-028) ------------------------------
/// Pool with 12 distinct Public/Surface/Routine lines for variety testing.
fn build_variety_pool() -> LinePoolIndex {
let mut index = LinePoolIndex::default();
let lines: Vec<IndexedDialogueLine> = (1u32..=12)
.map(|n| IndexedDialogueLine {
id: format!("variety_{:03}", n),
text: format!("Line number {}.", n),
role: "dock-worker".to_string(),
access: vec![AccessTier::Public],
trust: TrustTier::Surface,
situation: vec![Situation::Routine],
topic: vec![],
mood: vec![],
tags: vec![],
knowledge_grant: None,
})
.collect();
let pool = IndexedDialoguePool {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
lines,
};
index.dialogue.insert(
("the-terminal".to_string(), "dock-worker".to_string()),
pool,
);
index
}
#[test]
fn line_variety_no_repeats_within_cooldown_window() {
// #338, D-028: No line_id should repeat within LINE_COOLDOWN_TICKS.
// Regression: Talk 10 times at tick 0 (well within the 600-tick window).
// Each selected line must be distinct — cooldown tracker enforces this.
let mut world = setup_dialogue_world();
world.insert_resource(LinePoolIndexResource(build_variety_pool()));
let npc = world
.spawn((
Npc,
TilePosition::new(5, 5, 0),
DialogueProfile {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
))
.id();
world.resource_mut::<EntityRegistry>().register(npc);
// Unknown player — Public access only; tick stays at 0 throughout
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
KnowledgeGraph::new(),
TalkRequest { target: npc },
DialogueResponseBuffer::default(),
DialogueCooldownTracker::default(),
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
let mut seen_ids: Vec<String> = Vec::new();
for seed in 0u64..10 {
world.get_mut::<DialogueResponseBuffer>(player).unwrap().response = None;
world.entity_mut(player).insert(TalkRequest { target: npc });
// NOTE: SimulationTime is NOT advanced — all 10 talks happen within tick 0
world.insert_resource(SimRng::new(seed));
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_talk_interaction);
schedule.run(&mut world);
world.flush();
if let Some(resp) = &world
.get::<DialogueResponseBuffer>(player)
.unwrap()
.response
{
let id = resp.line_id.clone();
assert!(
!seen_ids.contains(&id),
"Line '{}' was repeated within the {}-tick cooldown window (iteration {}). \
Cooldown tracker must prevent repeats. (#338)",
id,
LINE_COOLDOWN_TICKS,
seed,
);
seen_ids.push(id);
}
}
assert_eq!(
seen_ids.len(),
10,
"Should have selected 10 distinct lines across 10 consecutive Talks (#338)"
);
}
// === Confrontation Response Tests (#520, D-063) ===
#[test]
@@ -1957,4 +2462,177 @@ mod tests {
"Marker should be removed after processing"
);
}
// === DialogueResponse Tests (#539) ===
#[test]
fn process_dialogue_response_selects_follow_up_line() {
let mut world = setup_dialogue_world();
let index = build_test_line_pool();
world.insert_resource(LinePoolIndexResource(index));
let npc = world
.spawn((
Npc,
TilePosition::new(5, 5, 0),
DialogueProfile {
location: "the-terminal".to_string(),
role: "dock-worker".to_string(),
},
CurrentMood(Mood::Content),
))
.id();
world.resource_mut::<EntityRegistry>().register(npc);
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
KnowledgeGraph::new(),
// Simulate that first line was already selected (on cooldown)
DialogueResponseRequest {
target: npc,
response_id: "test_d_001".to_string(),
},
DialogueResponseBuffer::default(),
DialogueCooldownTracker::default(),
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_dialogue_response);
schedule.run(&mut world);
world.flush();
// Should have consumed the marker
assert!(
world.get::<DialogueResponseRequest>(player).is_none(),
"DialogueResponseRequest should be consumed"
);
}
#[test]
fn process_dialogue_response_clears_active_dialogue_when_no_lines() {
let mut world = setup_dialogue_world();
// Build pool with only one line — it will be on cooldown
let mut index = LinePoolIndex::default();
let pool = IndexedDialoguePool {
location: "test".to_string(),
role: "worker".to_string(),
lines: vec![IndexedDialogueLine {
id: "only_line".to_string(),
text: "Only thing I can say.".to_string(),
role: "worker".to_string(),
access: vec![AccessTier::Public],
trust: TrustTier::Surface,
situation: vec![Situation::Routine],
topic: vec![],
mood: vec![],
tags: vec![],
knowledge_grant: None,
}],
};
index
.dialogue
.insert(("test".to_string(), "worker".to_string()), pool);
world.insert_resource(LinePoolIndexResource(index));
let npc = world
.spawn((
Npc,
TilePosition::new(5, 5, 0),
DialogueProfile {
location: "test".to_string(),
role: "worker".to_string(),
},
))
.id();
world.resource_mut::<EntityRegistry>().register(npc);
// Set the only line on cooldown — so no follow-up can be selected
let mut cooldown = DialogueCooldownTracker::default();
cooldown.record("only_line", 0);
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
KnowledgeGraph::new(),
DialogueResponseRequest {
target: npc,
response_id: "only_line".to_string(),
},
DialogueResponseBuffer::default(),
cooldown,
ActiveDialogue {
target: npc,
interaction_type: crate::knowledge::events::InteractionType::Talk,
started_tick: 0,
},
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_dialogue_response);
schedule.run(&mut world);
world.flush();
// No follow-up lines — ActiveDialogue should be cleared
assert!(
world.get::<ActiveDialogue>(player).is_none(),
"ActiveDialogue should be cleared when no follow-up lines available"
);
assert!(
world.get::<DialogueResponseRequest>(player).is_none(),
"DialogueResponseRequest should be consumed"
);
// Buffer should remain empty
let buffer = world.get::<DialogueResponseBuffer>(player).unwrap();
assert!(
buffer.response.is_none(),
"No response when all lines on cooldown"
);
}
#[test]
fn process_dialogue_response_no_profile_is_noop() {
let mut world = setup_dialogue_world();
// NPC without DialogueProfile
let npc = world.spawn((Npc, TilePosition::new(5, 5, 0))).id();
world.resource_mut::<EntityRegistry>().register(npc);
let player = world
.spawn((
PlayerCharacter,
TilePosition::new(5, 6, 0),
KnowledgeGraph::new(),
DialogueResponseRequest {
target: npc,
response_id: "some_line".to_string(),
},
DialogueResponseBuffer::default(),
DialogueCooldownTracker::default(),
))
.id();
world.resource_mut::<EntityRegistry>().register(player);
let mut schedule = bevy_ecs::schedule::Schedule::default();
schedule.add_systems(process_dialogue_response);
schedule.run(&mut world);
world.flush();
assert!(
world.get::<DialogueResponseRequest>(player).is_none(),
"DialogueResponseRequest consumed even with no profile"
);
let buffer = world.get::<DialogueResponseBuffer>(player).unwrap();
assert!(
buffer.response.is_none(),
"No response for NPC without DialogueProfile"
);
}
}
+74 -1
View File
@@ -280,6 +280,19 @@ pub fn process_player_input(
PlayerAction::TeleportToHub => {
handle_teleport_to_hub(&mut player_query, &mut commands);
}
PlayerAction::DialogueResponse {
target_entity_id,
ref response_id,
} => {
handle_dialogue_response(
&mut commands,
&registry,
&player_query,
&all_positions,
target_entity_id,
response_id,
);
}
PlayerAction::UsePerceptionMode(ref mode) => {
tracing::trace!("UsePerceptionMode({}) — no-op for Sprint 1", mode);
}
@@ -458,6 +471,65 @@ fn handle_talk(
tracing::debug!(target_id, "Talk: TalkRequest marker set on player");
}
/// Handle DialogueResponse action: set DialogueResponseRequest marker (#539).
/// The follow-up dialogue pipeline runs in process_dialogue_response (dialogue.rs).
///
/// Range check: same CLOSE_RANGE as Talk/Confront (D-010 info boundary). The player
/// must still be near the NPC to continue a conversation — walking away mid-dialogue
/// should not allow remote follow-ups.
#[allow(clippy::type_complexity)]
fn handle_dialogue_response(
commands: &mut Commands,
registry: &EntityRegistry,
player_query: &Query<
(
Entity,
&TilePosition,
Option<&mut Stance>,
Option<&mut PlayerMoveCooldown>,
),
With<PlayerCharacter>,
>,
all_positions: &Query<&TilePosition>,
target_entity_id: u64,
response_id: &str,
) {
let Ok((player_entity, player_pos, _, _)) = player_query.single() else {
return;
};
let target_stable = StableId(target_entity_id);
let Some(target_entity) = registry.to_entity(&target_stable) else {
tracing::warn!(target_entity_id, "DialogueResponse: target entity not in registry");
return;
};
// Server-side range check: reject DialogueResponse if target moved out of range
if let Ok(target_pos) = all_positions.get(target_entity) {
let distance = player_pos
.manhattan_distance(target_pos)
.unwrap_or(u32::MAX);
if distance > crate::simulation::interaction::CLOSE_RANGE {
tracing::info!(
target_entity_id,
distance,
"DialogueResponse: target out of range (max {})",
crate::simulation::interaction::CLOSE_RANGE,
);
return;
}
}
commands
.entity(player_entity)
.insert(crate::simulation::dialogue::DialogueResponseRequest {
target: target_entity,
response_id: response_id.to_string(),
});
tracing::debug!(target_entity_id, response_id, "DialogueResponse: marker set on player");
}
/// Handle Confront verb: set ConfrontationDelivered marker on the player entity (#520, D-063).
/// The confrontation response system runs in process_confrontation_response (dialogue.rs).
/// Server-side range check: Confront requires CLOSE_RANGE (same as Talk).
@@ -741,7 +813,8 @@ fn handle_teleport_to_hub(
.remove::<crate::simulation::dialogue::TalkRequest>()
.remove::<crate::simulation::dialogue::ActiveDialogue>()
.remove::<crate::simulation::dialogue::WalkAwayRequest>()
.remove::<crate::simulation::dialogue::ConfrontationDelivered>();
.remove::<crate::simulation::dialogue::ConfrontationDelivered>()
.remove::<crate::simulation::dialogue::DialogueResponseRequest>();
tracing::info!(
x = hub_spawn.x,
+13
View File
@@ -253,6 +253,19 @@ fn generate_msgpack_fixtures() {
&rmp_serde::to_vec_named(&input_batch).unwrap(),
);
// PlayerInput: DialogueResponse (#539)
let input_dialogue_response = PlayerInput {
tick: 300,
action: PlayerAction::DialogueResponse {
target_entity_id: 42,
response_id: "kael-davan_d_001".to_string(),
},
};
write_fixture(
"input_dialogue_response",
&rmp_serde::to_vec_named(&input_dialogue_response).unwrap(),
);
// Diagonal movement fixtures (clockwise: NE, SE, SW, NW)
for (name, action) in [
("input_move_northeast", PlayerAction::MoveNortheast),
+9 -2
View File
@@ -107,6 +107,14 @@ fn all_player_action_variants_roundtrip() {
PlayerAction::ToggleStanceUp,
PlayerAction::ToggleStanceDown,
PlayerAction::WalkAway,
PlayerAction::SetFacing {
facing: "north".to_string(),
},
PlayerAction::TeleportToHub,
PlayerAction::DialogueResponse {
target_entity_id: 42,
response_id: "kael-davan_d_001".to_string(),
},
];
for action in actions {
@@ -169,8 +177,7 @@ fn all_fixtures_deserialize() {
rmp_serde::from_slice::<u64>(&bytes)
.unwrap_or_else(|e| panic!("deserialize boundary raw fixture {}: {}", name, e));
} else {
assert!(
false,
panic!(
"unknown fixture naming convention: {} — add a deserialization branch for this prefix",
name
);