docs(sprints): add Sprint 14 "Live" briefings — 22 tickets across 4 teams

NPC mood, trust, routine execution, NPC-to-NPC conversations with D-078
occlusion, monologue display, content line pools, and visual specs.
Teams: server (7), client (3), copy (6), visual (6).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-19 19:52:50 +01:00
co-authored by Claude Opus 4.6
parent f6eb92cb25
commit 16bc87c590
5 changed files with 449 additions and 0 deletions
+70
View File
@@ -0,0 +1,70 @@
# Sprint 14: Live — Client Tasks
**Goal:** Bring NPCs to life — mood, trust, routine execution, and NPC-to-NPC conversation drive the first emergent social observables; the simulation produces a world that breathes independently of the player.
**Branch:** `client`
**Agents:** Stig (UI/rendering dev), Tyre (architect), Hoshe (QA)
## Carry-over from Sprint 13
None. All Sprint 13 client tickets done.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #122 | Monologue display — client | — |
| #535 | Passive dialogue panel — overheard NPC conversation display | #247 (server) |
| #511 | F3 debug overlay (deferred — WRONG button covers same data) | #495 done |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/architecture.md` — D-020 (ObserverSnapshot — `current_monologue` field), D-042 (UI microcopy format — YAML autoload for HUD labels)
- `decisions/perception.md` — D-016 (internal monologue — text display, atmosphere function), D-018 (three-range sound model — NPC-to-NPC conversations are Voice events), D-055 (sprint suppresses monologue at 40% rate — client renders what server sends; no client-side suppression needed)
- `decisions/content.md` — D-032 (separate monologue pools per character — client only needs to render; no character branching in display layer), D-035 (tag taxonomy — monologue has `character`, `trigger`, `prerequisite` tags; client ignores, server filters)
- D-078 (overheard NPC conversation — passive dialogue panel, server-authoritative occlusion, pre-occluded text rendering, speaker attribution)
## Notes
- **#122 — Monologue display — client:** The server-side monologue trigger system is complete — `MonologueEvent` arrives in `ObserverSnapshot.current_monologue` (field added Sprint 5, `server/src/bridge/types.rs`). The visual spec for monologue display was authored in Sprint 13 (#315, `docs/design/monologue-display-spec.md`). Sprint 14 work: implement the client-side display using that spec. Key requirements from the spec:
- **Position:** Bottom-left of viewport, above the interaction verb list, below the minimap. Not in the dialogue box area (max 20% height, D-061).
- **Typography:** Italic, smaller than dialogue (spec has exact px/pt — read `docs/design/monologue-display-spec.md` §Typography).
- **Stacking:** Queue — if a new monologue arrives while one is displaying, queue it (max depth from spec). Do not overwrite mid-display. Sprint 13 note: "monologue not lost on overwrite" is a P0 test (#477, done).
- **Fade:** Auto-fade after display duration. Duration is embedded in `MonologueEvent.display_duration` (float, seconds). Standard Godot `Tween`.
- **Character differentiation:** Smuggler and detective lines use different text colours — hex values in spec. Client reads the active character from `game_state.gd` (autoload at `client/scripts/autoloads/game_state.gd`) or derives it from the snapshot. The server already partitions monologue pools per character (D-032) so the client receives only the correct character's lines — no client-side branching needed.
- Existing `world_renderer.gd` (`client/scripts/rendering/world_renderer.gd`) is the root scene; monologue display is a new `CanvasLayer` child node or a `Control` node in the HUD layer (z-layer 6 per D-049 rendering stack). Do not put it in the dialogue box scene.
- **Integration with audio:** Monologue display does NOT trigger audio (monologue chime fires at cognitive delay onset from the server's recognition pipeline, not from text display). No client-side audio calls needed here.
- Delivery: `client/scripts/rendering/monologue_display.gd` (or similar), integrated into the HUD scene, reading `ObserverSnapshot.current_monologue`. Tests via Hoshe's gdUnit4 harness: queue management, fade-out timing, character colour, no-overwrite on active display.
- **#535 — Passive dialogue panel — overheard NPC conversation display:** Consumes `ConversationEvent` from `ObserverSnapshot` (added by server #247). Render overheard NPC-to-NPC conversations in the existing dialogue box in passive (read-only) mode. Per D-078 amendment — occlusion is server-authoritative; this ticket is a pure renderer of pre-occluded text. Key requirements:
- **Speaker attribution:** Display header as "Speaker → Target" (e.g. "Kael → Mira") using `speaker_name` and `target_name` from the event. Use the same attribution label slot as player conversation NPC-name display — no new layout region needed.
- **Pre-occluded text renderer:** Render `occluded_line` as-is. The server has already performed per-word Bernoulli drops and replaced dropped words with `...`. The client performs no stochastic logic — no `clarity_score`, no `randf()`, no per-word processing. Display the received string directly in the passive panel.
- **Read-only mode:** The dialogue box must suppress response options and player input when displaying a passive conversation. No response list, no input capture. Player can still move freely.
- **Dismissal:** On `conversation_end` event (or if player moves far enough that no `ConversationEvent` arrives for the NPC pair), dismiss via the standard 300ms fade (same `Tween` as walk-away fade, #437 done).
- **Monologue isolation:** Monologue display (#122, z-layer 6) is unaffected. Do not route conversation text through the monologue display. The server triggers `witness_interaction` which may produce a monologue — that arrives separately via `current_monologue` and is rendered by #122 normally.
- **Blocked by #247** — `ConversationEvent` struct must exist on `ObserverSnapshot` before this can be wired. Start with a stub event type and placeholder renderer if #247 is not yet landed.
- Delivery: passive mode in `client/scripts/ui/dialogue_box.gd` (or a new `passive_dialogue.gd` sibling), verbatim `occluded_line` render, speaker attribution header, `conversation_end` dismissal. Tests via Hoshe's gdUnit4 harness: attribution renders correctly, `occluded_line` with `...` tokens renders verbatim, panel does not show response options in passive mode, dismisses on `conversation_end`.
- **#511 — F3 debug overlay (deferred):** This ticket is low priority. The WRONG button (F12, #507 done) already captures snapshot state, text render, and description on demand without per-frame cost. The F3 overlay would duplicate that information as a persistent overlay. Stig recommended deferral in Sprint 12. Only implement this sprint if server tickets finish early and QA explicitly needs real-time overlay data that WRONG button cannot provide. Default: skip.
## Dependency Chain
```
#122 (monologue display) — standalone, no server dependency this sprint
#535 (passive dialogue panel) — blocked by #247 (server, NPC-to-NPC conversation system)
#511 (F3 overlay) — standalone, low priority, implement only if capacity allows
```
#122 has no blockers — `current_monologue` in the snapshot has been live since Sprint 5. The spec (#315) is done. This is a pure client rendering task.
#535 is blocked on #247 landing the `ConversationEvent` struct with `occluded_line: String` on `ObserverSnapshot`. Stig can stub the event type locally and build the renderer against it in parallel; wire-up follows when #247 merges. No stochastic logic needed on the client — render `occluded_line` verbatim.
## PR Workflow
When ready to submit, create a PR with `tea` CLI. All flags are required to avoid TTY prompts (see CLAUDE.md "Gitea access" section):
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(client): description" --description "body" --base main --head client
```
+89
View File
@@ -0,0 +1,89 @@
# Sprint 14: Live — Copy Tasks
**Goal:** Bring NPCs to life — mood, trust, routine execution, and NPC-to-NPC conversation drive the first emergent social observables; the simulation produces a world that breathes independently of the player.
**Branch:** `copy`
**Agents:** Mellanie (author), Paula (narrative designer), Gestalt (systems designer)
## Carry-over from Sprint 13
None. All Sprint 13 copy tickets done (#368 knowledge vocabulary).
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #253 | Monologue content architecture | — |
| #120 | Monologue line pool system | — |
| #121 | Character voice variation | — |
| #168 | Tagged line pool structure | #240 done |
| #328 | Access tier shift design document | #261 done |
| #536 | NPC-to-NPC overheard dialogue line pool | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/content.md` — D-028 (dialogue architecture — four relational layers; #168 implements Layer 1-2 tag structure), D-032 (separate monologue pools per character — hard partition, no shared lines), D-034 (THE FRIEND — Kael Davan / Sera Venn, all monologue content must be coherent with their arcs), D-035 (converged tag taxonomy — 6 structural + 3 selection + 3 monologue-specific tags), D-036 (Sova Transit District setting — Krenn naming conventions, "functional warmth" tone), D-037 (contraband — unlicensed lattice components, moral ambiguity), D-062 (invisible locked dialogue options — new trust tier content appears silently), D-063 (confrontation mechanics — internal voice, italic first-person options), D-064 (walk-away consequences — KG records incompleteness)
- `decisions/perception.md` — D-016 (internal monologue — four functions: perception bridge, atmosphere, diegetic tutorial, unreliable narrator), D-018 (three-range sound model — NPC-to-NPC conversations are Voice events the player overhears)
- D-078 (overheard NPC conversation — lines must survive partial occlusion; front-load key information, short declarative sentences, avoid pronoun-first openers)
## Notes
- **#253 — Monologue content architecture:** Define the authoring contract for all future monologue content. This is a **design document**, not a content file. Output: `docs/design/monologue-content-architecture.md`. Required sections:
- Identity-driven interpretation frame: what each character "cares about, fears, is responsible for" — the interpretive lens that makes the same observation produce different monologue per character. Smuggler: exposure, protection of ring, Kael's loyalty. Detective: institutional duty, personal cost, Sera Venn's reliability.
- Category definitions: perception (translating non-visual senses), atmosphere (environment, mood), tutorial (diegetic hints), observation (NPC-specific). Add a fifth if needed.
- Trigger → category mapping: which triggers (from D-035: `enter_location`, `observe_npc`, `hear_sound`, `observe_anomaly`, `post_conversation`, `discover_evidence`, `witness_interaction`, `time_idle`, `return_visit`) map to which categories. Not 1:1 — one trigger can source multiple categories.
- Volume target: how many lines per trigger type per character for v0.1. Per D-032, all volumes are per-character.
- `prerequisite` map design: what game state conditions gate a line. Examples: `kael_interaction_count >= 1`, `knows_kael_has_secret = true`, `location = maintenance_corridor`. These must be expressible in the KG (D-041 FactIds).
- Mellanie authors, Paula reviews for narrative coherence with THE FRIEND arc (D-034), Gestalt validates prerequisite map is implementable from KG state.
- **#120 — Monologue line pool system:** Produce the first monologue line pool YAML files. Output: `content/` directory (follow existing content structure — see `server/src/content/mod.rs` and `server/src/content/loader.rs` for load path conventions). File structure per D-032: `monologue-smuggler.yaml`, `monologue-detective.yaml`, with subdirectory per location (`terminal/`, `bar/`, `corridor/`). Required tags per line per D-035 (monologue-specific additions): `character`, `trigger`, `prerequisite` (null if unconditional). All 6 structural tags required: `id`, `text`, `role`, `access`, `trust`, `situation`. For monologue, `role` = `player_character`, `access` = `[public]`, `trust` = `surface` (monologue is the player character's internal voice — no access/trust gating applies; tags must still be present for schema compliance).
- Minimum deliverable for Sprint 14: `enter_location` (2-3 lines per location per character) and `time_idle` (2-3 lines per location per character). Eight locations minimum (hub, bar, corridor × 2 characters = 6; add 2 more for the maintenance corridors or gauntlet-equivalent spaces).
- Line quality bar: these are the first lines the game speaks. Every line must pass Mellanie's voice test. Per D-034 THE FRIEND guidance: "no generation expansion" for THE FRIEND content specifically — apply same standard to monologue that will surface during THE FRIEND arc (Kael/Sera scenes).
- **#121 — Character voice variation:** Write the trait modifier system for monologue voice. This is a **companion authoring guide** to #253, not a separate system. Output: `docs/design/monologue-voice-guide.md`. Required:
- Smuggler vs detective base voice registers (tone, vocabulary, sentence structure).
- How `PersonalityTraits` (D-024 — `Cautious`, `Bold`, `Honest`, `Deceptive`, `Compassionate`, `Ruthless`, `Curious`, `Incurious`, `Social`, `Reclusive`) modify monologue delivery. Not per-trait scripts — a transformation guide: "a Cautious character says X instead of Y" with 2-3 example rewrites.
- Background-dependent phrasing: Guardian background vs Senator background vs Worker background produce different idioms, references, assumptions. At minimum: two examples per background per character.
- Mood influence (feeds into #323 server-side): how the 8+1 moods (D-035 amendment: `neutral`, `anxious`, `frustrated`, `content`, `suspicious`, `warm`, `hostile`, `focused`) colour monologue delivery. Not separate line pools — the same line can be delivered more tersely when the character is Anxious, more expansively when Content.
- Paula writes the narrative framing, Mellanie writes the example lines, Gestalt validates mood → delivery mappings are mechanically coherent.
- **#168 — Tagged line pool structure:** Implement the converged D-035 YAML schema as a formal JSON Schema or YAML schema document and update the line previewer CLI. Output: `content/_schema/dialogue-line.schema.json` (or `.yaml`) with all 6 structural + 3 selection + 2 authoring-only tags. Monologue additions (`character`, `trigger`, `prerequisite`) as a separate schema extension or `$ref`. The schema is what the content cross-reference validation (CI ticket #464, done) runs against. Update the line previewer CLI (Sprint 5 `#326`, already done) to validate against the new schema on load. Gestalt leads schema design. Mellanie validates against authoring workflow. The 14 v0.1 situations and 9 v0.1 topics from D-035 must be enumerated in the schema as allowed values.
- Note: `server/src/content/line_pool.rs` already implements the Rust-side `IndexedDialogueLine` type — schema must stay in sync. Cross-reference `server/src/content/line_pool.rs` to confirm enum variants match D-035 enumeration.
- **#328 — Access tier shift design document:** A design document authored by Paula and Mellanie (no code). Output: `docs/design/access-tier-shifts.md`. Required per ticket:
- Per social site, 2-3 most likely tier transitions per character with specific triggers.
- Smuggler at The Terminal (hub): `insider → hostile` when cover blown; `insider → peer` after routine trust-building.
- Smuggler at The Last Shift (bar): `public → insider` after Kael introduction; `insider → hostile` if contraband conversation overheard.
- Detective at The Terminal: `authority → peer` after collaboration event; `authority → hostile` if confrontation fails.
- Detective at The Last Shift: `public → insider` via Sera Venn; `authority → peer` after buying rounds.
- For each transition: what observable event triggers it, which KG facts are required (`FactId` references from #368 knowledge vocabulary), and whether it is reversible.
- This document feeds #169 (Layer 1 access tier filtering, deferred to S15) as its design input.
- **#536 — NPC-to-NPC overheard dialogue line pool:** Write the line pool for NPC-to-NPC conversations that the player can overhear, per D-078. Lines must work at both full clarity (heard normally) and under heavy occlusion (many words dropped). The stochastic word-drop renderer operates per-word — lines must be authored so that partial information is still meaningful and interesting, not just broken.
- **Occlusion-resilient authoring rules:** (1) Front-load key information — the most important word should be in the first third of the sentence. (2) Short declarative sentences — one idea per line turn. (3) No pronoun-first openers (`"She told me..."``"Kael told me..."`) — the first word is statistically the most likely to drop; pronouns without antecedent are unresolvable. (4) Each exchange turn must be self-contained — a player who hears only one side of the exchange should still get a complete thought.
- **Volume and tagging:** Minimum 40 exchange pairs (speaker A line + speaker B response = 1 pair). Cover three registers: social (idle chat, personal news), work (shift logistics, job gripes), gossip (third-party knowledge payload — something the player could use). Tag each pair with: `relationship_type` (colleague, friend, hostile, romantic), `topic`, and `knowledge_payload` (a brief description of what the player can infer from hearing this exchange clearly — or partially).
- **Format:** Use the D-035 YAML schema (see #168 for schema file). These are NPC-sourced lines, not player-character lines — `role` = `npc`, `access` and `trust` tags apply normally. Monologue-specific tags (`character`, `trigger`, `prerequisite`) are not used for this pool; replace with `relationship_type` and `knowledge_payload` as custom extensions, or confirm schema handling with Gestalt before writing.
- **Placement:** Output to `content/npc-conversations/overheard.yaml` (new directory, establish convention).
- Mellanie writes lines, Paula validates narrative coherence (Kael/Sera-adjacent exchanges must honour D-034 THE FRIEND arc), Gestalt validates knowledge_payload tags are achievable from KG state.
- Delivery: `content/npc-conversations/overheard.yaml`, minimum 40 exchange pairs, all tagged, all passing the occlusion-resilience checklist. No dependency on server #247 — content is authored ahead of the system that will select and emit it.
## Dependency Chain
```
#253 (monologue architecture) → #120 (line pool files) → #121 (voice guide)
#168 (tagged line pool schema) → parallel with #253 track
#328 (access tier shifts) → parallel, standalone design doc
#536 (NPC-to-NPC overheard line pool) → parallel, standalone, feeds server #247
```
#253 must be started first — it defines the authoring contract that #120 writes to and #121 extends. #120 and #121 can overlap once #253 is drafted (Paula/Mellanie can start voice guide while Mellanie writes lines). #168 and #328 are fully independent and can proceed in parallel with the monologue track. #536 is also fully independent — no blockers, can proceed in parallel with all other copy work.
## PR Workflow
When ready to submit, create a PR with `tea` CLI. All flags are required to avoid TTY prompts (see CLAUDE.md "Gitea access" section):
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "docs(content): description" --description "body" --base main --head copy
```
+107
View File
@@ -0,0 +1,107 @@
# Sprint 14: Live — Joint / Integration Notes
**Goal:** Bring NPCs to life — mood, trust, routine execution, and NPC-to-NPC conversation drive the first emergent social observables; the simulation produces a world that breathes independently of the player.
**Sprint 14 ID:** 14
**Status:** planning (activate with `db/connectors/sprint start`)
---
## Pre-Sprint Decisions
The following decisions are implemented or directly exercised this sprint and must be cross-referenced by the relevant team:
| Decision | Domain | Implementing ticket(s) |
|----------|--------|----------------------|
| D-024 (10-axis NPC model + combat component) | content | #323 (mood), #324 (trust), #325 (interaction tracking), #101 (routine) |
| D-026 (simulation tiers — ActiveSim scope for behavior) | architecture | #323, #101, #247 |
| D-028 (dialogue — four relational layers) | content | #325 (Layer 2 data), #324 (Layer 3 data), #168 (schema) |
| D-031 (game clock / day phases — routine transitions) | architecture | #101 |
| D-032 (separate monologue pools per character) | content | #120, #121, #122 |
| D-034 (THE FRIEND — Kael Davan / Sera Venn) | content | #318 (visual spec), #253 (monologue architecture) |
| D-035 (converged tag taxonomy) | content | #168 (schema implementation) |
| D-041 (knowledge graph — trust co-gate) | architecture | #324, #325 |
| D-075 (dialogue filtering — trust + confidence co-gate) | content | #324 (trust progression feeds `relationship_to_trust()`) |
---
## Cross-Team Dependencies
| Dependency | From | To | Risk |
|------------|------|----|------|
| #323 (Mood component) | server | copy #253 (monologue architecture must describe mood influence) | Low — copy doc can reference Mood enum; server implements |
| #325 (InteractionMemory) | server | copy #328 (access tier shifts doc references interaction count as trigger) | Low — spec can be written with intent; server implements |
| #324 (trust progression) | server | copy #121 (voice guide references mood/trust for line selection) | Low — parallel, doc references trust tier conceptually |
| #304 (entity color spec) | visual | client (future sprint — entity_renderer.gd implementation) | Low — spec must be complete before client implements |
| #316 (text display hierarchy) | visual | client #122 (monologue display references hierarchy for positioning) | Low-Medium — Stig should read #316 spec before completing #122 layout |
| #251 (tell visual/behavioral spec) | visual | server (future sprint — tell simulation behavior) | Low — server implements behavior from Araminta's spec next sprint |
**Coordination protocol:**
- Visual team (#304 first): Start entity color spec before #318 (THE FRIEND visual cross-references it). Signal to joint channel when #304 is drafted.
- Client team (#122): Read the Sprint 13 monologue display spec (`docs/design/monologue-display-spec.md`) before implementing. Also read #316 once Araminta drafts it — consult if positioning conflicts arise. Default to #315 spec if #316 is not ready.
- Server team (#323 first): Mood component is the most downstream dependency for copy and visual. Draft the `Mood` enum in `server/src/npc/mood.rs` early — copy team needs the enum values for #253 authoring contract and #121 voice guide.
- Copy team (#253 first within copy track): Monologue architecture doc gates #120 and partially gates #121. Start it day one.
---
## Sprint Completion Proof
When Sprint 14 is done, the following is concretely observable:
1. **NPCs have moods:** Query the ECS world during a gauntlet run — Active-tier NPCs have a `Mood` component with a non-Neutral value after sufficient simulation ticks. The `Mood` transitions when `ToleranceThreshold.current_stress` exceeds threshold, or when day phase changes.
2. **Trust accumulates:** Talk to the same NPC three times in sequence. After each interaction, `RelationshipGraph.get_relationship(player_id, npc_id).trust` increments. Walk away mid-conversation (D-064 mechanic) — trust decrements. Trust values are deterministic across replays with the same input sequence.
3. **Routine execution closes the loop:** NPCs in the Active tier move to their routine destination on day-phase transition. After `Morning → Afternoon`, NPCs with an Afternoon `location` in their `DailyRoutine` have an active `PathRequest` issued and eventually reach the target tile. The `ActivityState` component reflects the current routine activity string.
4. **NPC-to-NPC conversations generate sound:** Two NPCs in the same zone within 3 tiles emit a `SoundEvent` of kind `Voice` on the `ObserverSnapshot.sound_events` field. The client's `sound_indicator_renderer.gd` renders a voice-coloured (#e8c547) pulse at the fog edge in the direction of the conversation. The `AudioManager` plays the murmur asset on the WorldSFX bus (already wired Sprint 13).
5. **Monologue displays in client:** Launch the game. Walk into a new room. Within 2 ticks, a monologue line appears in the bottom-left HUD area in italic text. Wait idle for ~100 ticks — a `time_idle` line appears. Lines fade after their display duration. No line is lost — if a new line arrives while one is showing, it queues. Character-specific colour is applied.
6. **Monologue content pools exist:** `content/` directory contains `monologue-smuggler.yaml` and `monologue-detective.yaml` files with at least 8 lines each covering `enter_location` and `time_idle` triggers for at least two locations. All lines validate against the D-035 schema in `content/_schema/dialogue-line.schema.json`.
7. **Tagged line pool schema is authoritative:** Running `make pre-pr` (CI cross-reference validation, #464) validates all content YAML files against the schema. No validation errors.
8. **Visual specs are actionable:** `docs/design/entity-color-system.md`, `docs/design/text-display-hierarchy.md`, `docs/design/sound-indicator-visual.md`, `docs/design/the-friend-visual-treatment.md`, `docs/design/environmental-text-standards.md`, and `docs/design/tell-visual-expression.md` all exist with sufficient detail for Stig to implement from the spec alone — hex values, pixel dimensions, z-layer assignments, animation durations.
9. **Invariant test suite passes:** `cargo test` in `server/` includes `invariants::*` tests that execute against all existing gauntlet rooms without failure. 36 invariants across 4 categories all assert green.
10. **Access tier shift design exists:** `docs/design/access-tier-shifts.md` documents 2-3 tier transitions per character per social site with specific KG fact triggers and reversibility notes. Ready as design input for #169 (Layer 1, S15).
---
## Test Plan Alignment (D-030)
Sprint 14 is in the integration phase (D-030 Phase 2+). Test focus:
- **Server:** New systems (#323 mood, #324 trust, #325 interaction tracking, #101 routine, #103 relationship dynamics, #247 NPC conversations) each require unit tests in their respective modules. Hoshe's test harness pattern: ECS `World` setup, inject components, advance system, assert state change. Determinism required: all random values through `SimRng` (D-010 principle 4). #508 invariant tests run as integration-style tests over gauntlet rooms — these are the first map-agnostic quality gate.
- **Client:** Hoshe's gdUnit4 harness. #122 (monologue display) tests: queue management (queue depth at max, oldest-first eviction?), fade timing, character color correctness, no-overwrite behavior. Verify `current_monologue: None` produces no display (no ghost text from previous tick).
- **Copy:** Content validation is CI-driven (#464 cross-reference check in `make pre-pr`). Line pool YAML files (#120) must pass schema validation. No manual test required for docs (#253, #121, #328, #168).
- **CI:** `make ci` must pass on all branches. `make pre-pr` runs content cross-reference validation including schema check. No new HashMap in simulation crate (clippy ban active).
---
## Decision Coverage Gaps (remaining after Sprint 14)
The following confirmed decisions still have no implementing tickets. Flag to Team Leader if any block upcoming work:
- D-031 (time system) — exercised by #101 routine execution and gauntlet shift change room. No standalone ticket needed.
- D-033 (entity color = relationship to player) — #304 this sprint is the spec. Client implementation is a Sprint 15 ticket (not yet created).
- D-036 (Sova Transit District setting) — no world map authoring ticket yet. Relevant to #155 (hand-crafted location authoring, backlog).
- D-040 (wiki taxonomy) — documentation structure, not code.
- D-043D-046 (art direction decisions) — referenced by #304, #316, #317, #334 this sprint. No standalone implementing tickets.
- D-062 (invisible locked dialogue) — design constraint, implemented by server-side filtering (already in `dialogue.rs`). No new ticket.
- D-070 (confrontation as cognitive vulnerability) — implemented Sprint 13 (audio dip). No further ticket.
- D-074 (audio aesthetic identity) — implemented Sprint 13 (murmur asset). No further ticket.
---
## Notes for Sprint Start
To activate the sprint once planning is approved:
```bash
db/connectors/sprint start
```
This sets Sprint 14 to `active`. Teams then run `db/connectors/sprint start-work --team <team>` for their full context dump.
+82
View File
@@ -0,0 +1,82 @@
# Sprint 14: Live — Server Tasks
**Goal:** Bring NPCs to life — mood, trust, routine execution, and NPC-to-NPC conversation drive the first emergent social observables; the simulation produces a world that breathes independently of the player.
**Branch:** `server`
**Agents:** Dudley (simulation dev), Tyre (architect), Hoshe (QA)
## Carry-over from Sprint 13
None. All Sprint 13 server tickets done.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #323 | NPC mood state machine | — |
| #324 | Trust progression system | — |
| #325 | Interaction tracking component | — |
| #101 | Routine execution system | #87 done, #238 done |
| #103 | Relationship dynamics | — |
| #247 | NPC-to-NPC conversation system | — |
| #508 | Map-agnostic invariant tests (36 invariants) | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/architecture.md` — D-024 (10-axis NPC model), D-026 (simulation tiers — ActiveSim scope), D-031 (game clock / day phases — routine transitions), D-041 (knowledge graph — trust feeds Layer 3 filtering), D-054 (tile-based movement), D-075 (dialogue filtering — trust co-gate with KnowledgeConfidence)
- `decisions/content.md` — D-023 (three-tier content model), D-028 (dialogue four-layer model — Layers 2-4 fed by trust/interaction tracking), D-029 (population entanglement ratio — 30/50/20), D-034 (THE FRIEND — trust arc requirements), D-062 (invisible locked options — new trust tier = new options appear silently)
- `decisions/perception.md` — D-016 (internal monologue — mood drives monologue tone), D-018 (three-range sound model — NPC-to-NPC conversations emit Voice events)
## Notes
- **#323 — NPC mood state machine:** Add a `Mood` component to `server/src/npc/mod.rs`. Eight moods for v0.1: `Neutral`, `Anxious`, `Frustrated`, `Content`, `Suspicious`, `Warm`, `Hostile`, `Focused` (9th: added Sprint 8, D-035 amendment). Mood changes based on: triangle pressure (high ToleranceThreshold stress → Anxious/Hostile), time of day (Evening + long shift → Frustrated), recent interactions (positive player interaction → Warm). Write a `MoodSystem` in a new `server/src/npc/mood.rs` module. Mood is read by the monologue trigger system (`server/src/simulation/monologue.rs`) and will gate Layer 4 dialogue selection (`server/src/simulation/dialogue.rs`). Blocks #337 (tell state derivation, deferred to S15). Add to `NpcPlugin` in `server/src/npc/mod.rs`. Keep all values `i16`/enum — no floats, per D-010 determinism.
- Delivery: `Mood` component, `update_mood` system, unit tests in `server/src/npc/mood.rs`.
- **#324 — Trust progression system:** Trust level per player-NPC pair drives D-028 Layer 3 (trust-gated gossip). The `RelationshipGraph` at `server/src/npc/relationships.rs` already holds `trust: i8` on every `RelationshipEdge`. Sprint 14 work: (1) a system that advances trust based on interaction quality — `TalkVerb` completion → small positive increment, confrontation walk-away → negative decrement, repeat visit (from #325 interaction count) → small positive; (2) expose the current trust value on `ObserverSnapshot` or leave it in the KnowledgeGraph (D-041). Trust maps to D-028 TrustTier via `relationship_to_trust()` in `server/src/simulation/dialogue.rs` — D-075 adds KnowledgeConfidence as co-gate to that function. No new ECS component needed — `RelationshipGraph` resource is the store. Write a `update_trust` system in `server/src/npc/relationships.rs`. Blocks #171 (Layer 3 trust-gated gossip, deferred to S15).
- Integration point: `server/src/simulation/dialogue.rs` `process_talk_interaction` must call `relationship_to_trust()` with `KnowledgeConfidence` (D-075 implementation if not already wired).
- Delivery: `update_trust` system, tests verifying trust increments/decrements on interaction events.
- **#325 — Interaction tracking component:** `InteractionMemory` component per NPC-pair — fields: `interaction_count: u32`, `last_interaction_tick: u64`, `notable_events: Vec<InteractionEvent>`. The `interaction_count` drives D-028 Layer 2 situation activation: `first_meeting` when count == 0, `repeated_visit` when count >= 3. Add to `server/src/npc/mod.rs` or a new `server/src/npc/interaction.rs`. Populated by `process_talk_interaction` in `server/src/simulation/dialogue.rs` each time a Talk verb completes. `notable_events` stores walk-aways (D-064) and confrontations (D-063) — these are already recorded in the KG but `InteractionMemory` provides fast per-pair access without a full KG query.
- Integration point: `server/src/simulation/dialogue.rs` — increment count and stamp tick on each completed Talk. `dialogue.rs` already reads KG and RelationshipGraph; add InteractionMemory to the same query.
- Delivery: `InteractionMemory` component, incremented by dialogue system, used by situation resolver for Layer 2.
- **#101 — Routine execution system:** The routine data model exists (`DailyRoutine`, `RoutineEntry` in `server/src/npc/mod.rs`). The phase-transition trigger exists (`check_phase_transition` in `server/src/npc/routine.rs`) — it already issues `PathRequest` on phase change. Sprint 14 work: verify the full execution loop closes — `PathRequest` → pathfinder → `path_follow` → entity reaches destination and enters the routine activity state. Add an `activity: String``ActivityState` ECS component (or reuse `DailyRoutine.entries[].activity`) so the simulation knows what an NPC is currently doing. This feeds the `DuringActivity` tell trigger (`TellTrigger::DuringActivity` in `server/src/npc/mod.rs`) and the Layer 2 `situation` tag matching. Existing pathfinding: `server/src/simulation/pathfinding.rs`, `server/src/simulation/path_follow.rs`. Gauntlet room `shift_change_*` (Sprint 13 #505) already validates phase-boundary transition — extend it or write new integration tests for full routine loop.
- Delivery: `ActivityState` component (or equivalent) attached/updated as NPCs execute routines; tests verifying NPC reaches routine destination and holds activity state.
- **#103 — Relationship dynamics:** Relationship values decay and reinforce over time. Existing `RelationshipEdge` in `server/src/npc/relationships.rs` has `trust: i8` and `history: Vec<RelationshipEvent>`. Sprint 14 work: (1) passive decay — trust drifts toward 0 at ~1 point per game-day if no recent interaction (controlled by `last_interaction_tick`); (2) interaction reinforcement — talking, helping (future), witnessing positive events strengthens; (3) `RelationshipEvent` is appended on notable interactions. This system runs in the Background tier (D-026) — lightweight, 1 update per game-minute. Add `update_relationship_dynamics` system to `server/src/npc/relationships.rs`, registered in `NpcPlugin`. Blocks #249 (player-action social propagation, deferred to S15).
- Note: Do not confuse with #324 (player-NPC trust). #103 covers NPC-NPC relationship dynamics — the relationship graph that generates the social texture (#249 feeds into). The player-NPC trust arc (THE FRIEND, #324) is a separate ticket.
- **#247 — NPC-to-NPC conversation system:** NPCs in the Active tier who are in proximity (≤3 tiles) and share a social site occasionally enter NPC-to-NPC conversations. Implementation: (1) a `ConversationSystem` in `server/src/simulation/` that detects eligible NPC pairs (same zone, ActiveSim, not already in player conversation, not sprinting away), initiates a `NpcConversation` state component for the pair, and emits a `SoundEvent` of kind `Voice` at the conversation tile — picked up by `server/src/simulation/sound.rs` and included in `ObserverSnapshot.sound_events`; (2) conversation duration in ticks (configurable constant, ~30-120 ticks = 3-12 game-minutes); (3) conversation terminates when one NPC leaves the zone or duration expires. The client's `sound_indicator_renderer.gd` already handles `Voice` sound events. This is the first source of overheard conversations — the eavesdrop mechanic (D-426 `ListeningFocus`) becomes meaningful when NPCs are actually talking.
- **D-078 addition (amended — occlusion is SERVER-AUTHORITATIVE):** Each `ConversationEvent` on `ObserverSnapshot` must carry: `occluded_line: String` (the NPC dialogue line with dropped words replaced by `...`), `speaker_id: EntityId`, `target_id: EntityId`, `speaker_name: String`, `target_name: String`. The server performs per-word occlusion before emission — the client receives pre-occluded text and renders it verbatim.
- **Per-word occlusion algorithm:** Iterate the dialogue line word by word. For each word, perform an independent Bernoulli trial using a seedable RNG (seeded per tick for deterministic replay, D-010). Per-word drop probability is derived from three inputs at the moment of emission: (a) tile distance from player to the speaking NPC — linear decay from 0.0 drop probability at 0 tiles to 1.0 at the `Voice` sound range boundary; (b) ambient noise level at the player position (already tracked in `ObserverSnapshot.ambient_noise`) — adds up to 0.3 to drop probability; (c) whether the player entity has `ListeningFocus` stance active — subtracts 0.2 from drop probability (clamped to [0.0, 1.0]). Words that fail the trial are replaced with `...` in `occluded_line`.
- When a conversation ends (NPC departs zone or duration expires), emit a `conversation_end` event with the same pair IDs so the client can dismiss the panel. Trigger `witness_interaction` on the player observation pipeline whenever the player receives any conversation event (regardless of occlusion level).
- No dialogue content needed in this ticket — content is sourced from the NPC-to-NPC line pool (#536, copy team, Sprint 14).
- Delivery: `NpcConversation` state component, `run_npc_conversations` system, `Voice` `SoundEvent` emission, `ConversationEvent` struct with `occluded_line` on `ObserverSnapshot`, `conversation_end` event, per-word occlusion function with seedable RNG, tests: `npc_conversation_emits_voice_event_when_in_range`, `npc_conversation_terminates_when_apart`, `occlusion_drops_words_with_distance`, `occlusion_suppressed_by_listening_focus`, `occlusion_deterministic_with_same_seed`. Blocks #535 (client).
- **#508 — Map-agnostic invariant tests (36 invariants):** 36 invariants across 4 categories from Gestalt's workshop output. Implement as a test suite in `server/src/test_world/` that runs against any valid map (gauntlet rooms). Categories: structural (8 — tile counts, wall connectivity, spawn point validity), perception (5 — LOS symmetry at range, sound range boundaries), population (8 — NPC count limits, tier assignment correctness), simulation (8 — no entity teleports, determinism, pathfinder termination, interaction buffer clear on sprint). Add a `run_invariants(world: &World)` function callable from gauntlet room tests — each room calls it after setup to assert structural invariants hold. Dynamic map system not required — invariants work on static gauntlet maps.
- Note: #509 (fuzzy tests for procedural maps) remains deferred — requires dynamic map generation.
- Delivery: `server/src/test_world/invariants.rs` module with 36 test assertions; each gauntlet room test calls `run_invariants`.
## Dependency Chain
```
#323 (mood) ─────────────────────────────────────→ #337 (tell derivation, S15)
#324 (trust) ────────────────────────────────────→ #171 (Layer 3, S15)
#325 (interaction tracking) → #101 (routine) is parallel
→ #103 (relationship dynamics) is parallel
→ #247 (NPC conversations) is parallel
#247 (NPC conversations) ────────────────────────→ #535 (client passive panel, blocked)
#508 (invariants) — standalone, parallel with all above
```
All six simulation tickets (#323, #324, #325, #101, #103, #247) are independent of each other and can proceed in parallel. #508 is also fully standalone. #535 (client) is blocked on #247 landing the `ConversationEvent` struct on `ObserverSnapshot` — specifically `occluded_line: String` (pre-occluded by server), `speaker_name`, `target_name`, and `conversation_end`.
## PR Workflow
When ready to submit, create a PR with `tea` CLI. All flags are required to avoid TTY prompts (see CLAUDE.md "Gitea access" section):
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): description" --description "body" --base main --head server
```
+101
View File
@@ -0,0 +1,101 @@
# Sprint 14: Live — Visual Tasks
**Goal:** Bring NPCs to life — mood, trust, routine execution, and NPC-to-NPC conversation drive the first emergent social observables; the simulation produces a world that breathes independently of the player.
**Branch:** `visual`
**Agents:** Araminta (art direction)
## Carry-over from Sprint 13
None. All Sprint 13 visual tickets done (#315 monologue display spec).
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #304 | Entity Color System Spec — relationship-to-player mapping | #303 done |
| #316 | Text display hierarchy spec — 4 content pipelines | #303 done |
| #317 | Sound indicator visual design — fog-edge pulse | #303 done |
| #318 | THE FRIEND visual treatment spec | #303 done, #297 done, #298 done |
| #334 | Environmental text visual standards | #303 done |
| #251 | Tell visual/behavioral expression | — |
All six tickets are unblocked. All are spec/design documents — Araminta authors; no code deliverables from the visual branch this sprint.
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/content.md` — D-033 (entity color = relationship to player — approved color hex values: unknown=teal #4a9ebb, known/friendly=green #6bc9a6, person-of-interest=amber #e8c547, hostile=red #d45d5d, static objects=grey #8090a8), D-034 (THE FRIEND — phase 1 identical to other friendly NPCs, earned visual detail only), D-043 (art direction — functional warmth), D-044 (visual hierarchy — entity > object > structure), D-045 (environmental neutrality — zero shift with narrative state), D-046 (lighting system — three-reference model), D-047 (two-tier animation system — Tier 1 readable, Tier 2 ambiguous), D-048 (neural insert overlay), D-049 (z-level rendering stack — 8 layers)
- `decisions/perception.md` — D-016 (internal monologue — text rendering), D-017 (perception modes), D-018 (three-range sound model — color codes for sound indicator: neutral #c8d0e0, voices #e8c547, danger #d45d5d), D-035 (tag taxonomy — monologue display spec references character field), D-062 (invisible locked dialogue — no lock icons, no hints)
- `decisions/scope.md` — D-027 (vertical slice success criteria — #3: player names an NPC they felt conflicted about = THE FRIEND)
## Notes
- **#304 — Entity Color System Spec:** Araminta formalizes the approved D-033 color model into a full spec document. Output: `docs/design/entity-color-system.md`. Required sections:
- Relationship state → color mapping table (hex values confirmed in D-033 are the canonical source).
- How relationship state is determined at render time: client receives `relationship: RelationshipState` on each `VisibleEntity` in the snapshot (`server/src/bridge/types.rs` `VisibleEntity`). Client maps state to color via a lookup table in `entity_renderer.gd` (`client/scripts/rendering/entity_renderer.gd`).
- Transition behavior: when an entity's relationship state changes (e.g., PersonOfInterest after confrontation), how the color shift renders — instant or tween, duration.
- Static objects: always `#8090a8` regardless of any relationship state. Define what counts as a static object (fixtures, furniture, terminals) vs entity (NPCs, player, carried items).
- Edge cases: player character color (not subject to relationship coloring — different treatment), entities in fog (D-033 color does NOT show through fog — unrecognized fog blobs are grey until cognitive delay resolves), entities at periphery (reduced saturation per D-046 lighting reference model).
- Color blindness note: D-033 colors were selected for functional warmth — include Araminta's assessment of whether the teal/green/amber/red set is distinguishable under deuteranopia and protan deficiency. Flag if adjustment needed.
- This spec is the direct input to the client-side implementation (future sprint — no client ticket this sprint). Must be self-contained enough for Stig to implement from the doc alone.
- **#316 — Text display hierarchy spec:** How the four content pipelines render distinctly. Output: `docs/design/text-display-hierarchy.md`. Four pipelines:
1. **Dialogue** (NPC speech + player response options): bottom panel, max 20% height, max ~100 chars per line (D-061). Speaker name styled. Response options styled differently from speech.
2. **Internal monologue** (player character): left-side or bottom-left, italic, smaller font, character colour from #315 spec (done Sprint 13).
3. **Observation / overheard** (what the player perceives about NPCs or overhears): distinct treatment from monologue — diegetic information output, not character voice. Consider: greyed label, different font weight, positioned differently.
4. **Environmental text** (signage, terminals, news tickers in-world): diegetic — appears in the world layer, not the HUD layer. Two-language treatment per D-036 (Concordat Standard + Krenn vernacular). Character limits per format (signage shorter, terminal longer, news ticker scrolling).
- Each pipeline needs: position on screen, font size/weight, colour scheme, max width/height, fade/truncation behaviour. D-049 z-layer assignments for each.
- Reference: `docs/design/monologue-display-spec.md` (#315, done) for monologue pipeline specifics — this spec extends and cross-references it.
- **#317 — Sound indicator visual design:** Fog-edge pulse indicators for the D-018 three-range sound model. Output: `docs/design/sound-indicator-visual.md`. Design requirements (from ticket and D-018):
- **Purpose:** Complementary to audio, not replacement. Indicates sound presence for players with audio off or in loud environments.
- **Colors:** neutral #c8d0e0 (footsteps, ambient), voices #e8c547 (NPC conversation), danger #d45d5d (alarms, alerts).
- **Position:** Fog edge — where the visible area meets unexplored/deep fog. Not a minimap overlay, not a screen-edge indicator. Appears at the boundary tiles of the player's visible cone.
- **Shape and animation:** Pulse — how many pixels, what frequency, what easing, what opacity range. Must not be distracting during exploration; must be noticeable when specifically a threat.
- **Direction encoding:** Indicates direction the sound comes from — how? Arc segment on fog edge in the direction of source? Size variation? Specify clearly for Stig's implementation.
- **Range differentiation:** Close sounds (≤3 tiles) produce larger/brighter pulses; medium (≤8 tiles) moderate; long (≤20 tiles) subtle. Define the three visual levels.
- **When it does not appear:** When audio is playing and the sound is within the visible area (no need to indicate what you can already see/hear). Rules for suppression.
- The `sound_indicator_renderer.gd` file exists at `client/scripts/rendering/sound_indicator_renderer.gd` — Araminta should note what it currently does vs what the spec calls for.
- **#318 — THE FRIEND visual treatment spec:** How Kael Davan (smuggler's FRIEND) and Sera Venn (detective's FRIEND) look different from other NPCs through earned visual detail only — no special marking. Output: `docs/design/the-friend-visual-treatment.md`. Per D-034 design principle: Phase 1 identical to other friendly NPCs (green rectangle per D-033). Differentiation accrues through story, not through marking. Required sections:
- **Phase 1 (before player builds relationship):** Identical to any other Known/Friendly NPC. Green rectangle, no distinguishing visual.
- **Phase 2 (after 3+ interactions, trust building):** What subtle visual shift occurs — if any — that is diegetically justified, not metatextual marking. Examples: does the character carry an item that renders (a manifest, a specific color? per D-033 object color rules)? Does their routine placement become more predictable visually? Define what "earned visual detail" means concretely.
- **Phase 3 (contradiction discovered — PersonOfInterest):** Transition to amber #e8c547 per D-033. Monologue firing rate spikes. Spec: when does the color shift? Immediately on confrontation? On player delivery of contradiction knowledge? Tween duration.
- **Animation tier:** THE FRIEND is in Tier 2 animation (ambiguous, privately motivated behaviors per D-047) once the player has seen their contradiction. Before contradiction: Tier 1. Spec the transition point.
- Cross-reference `docs/design/entity-color-system.md` (#304, this sprint) once it is drafted.
- **#334 — Environmental text visual standards:** How signage, terminals, and news tickers render in-world. Output: `docs/design/environmental-text-standards.md`. Per ticket:
- **Signage:** Short (1-3 words typical), both Concordat Standard and Krenn vernacular. Character limits. Font size relative to tile size (D-066: 1m visual tiles, 2x retina factor). Position: floating above the tile, or rendered on the tile surface.
- **Terminals:** Longer text, readable on interaction (Observe verb). Two-state: ambient (icon/identifier visible from range) and active (text readable when player is adjacent).
- **News tickers:** Scrolling text, ambient, not blocking gameplay. Where on screen — in-world (floating above terminal), not HUD overlay.
- **Bilingual treatment:** Concordat Standard is the colonial lingua franca (neutral, bureaucratic). Krenn vernacular is the local dialect (warm, compact, consonant-heavy per D-036 naming conventions). Which text in which language per context? Formal signage = Concordat Standard. Informal social text (bar menu, worker notices) = Krenn vernacular. Mixed where both audiences are intended.
- **Rendering layer:** Per D-049 z-layer stack — environmental text is object-layer (layer 2-3), not HUD layer (layer 6+). Must not occlude entities.
- **#251 — Tell visual/behavioral expression:** How NPC behavioral tells manifest in the top-down renderer. Output: `docs/design/tell-visual-expression.md`. Per D-024 tell system (5 categories: nervous, angry, friendly, guarded, routine deviation) and server's `TellSystem` component (`server/src/npc/mod.rs`). In v0.1, tell expression is via **monologue text** (server emits monologue on observe_npc trigger when tell is active), not visual animation — this is confirmed. However, Araminta's task is to define:
- What observable behaviors in the top-down renderer accompany tells — movement hesitation (NPC pauses before entering a room), route changes (NPC takes alternate path), grouping behavior (NPC lingers near another), interaction changes (NPC avoids certain tiles).
- These are **behavioral patterns in the simulation** described visually — the spec tells Dudley what the tell should look like at the tile level, which he implements as movement/pathfinding modifiers. The spec is the design input to server-side tell behavior.
- Per D-047 animation tier: Tier 2 animation is "ambiguous, privately motivated behaviors." Tells are the canonical Tier 2 examples — movement hesitation is a Tier 2 animation. Define which of the 5 tell categories maps to which Tier 2 behaviors.
- Araminta's scope here: the visual description. Dudley implements the simulation behavior. Mellanie writes the monologue that accompanies the tell.
## Dependency Chain
```
#304 (entity color spec) ─────────────────→ #318 (THE FRIEND visual, cross-ref)
#316 (text display hierarchy) ───────────→ standalone
#317 (sound indicator visual) ───────────→ standalone
#318 (THE FRIEND visual) ────────────────→ #251 (tell visual, cross-ref)
#334 (environmental text) ───────────────→ standalone
#251 (tell visual) ──────────────────────→ standalone
```
Recommended order: start #304 (entity color) first — #318 cross-references it. #251 and #318 can proceed in parallel once #304 is drafted. The other three (#316, #317, #334) are fully independent and can proceed in any order.
## PR Workflow
When ready to submit, create a PR with `tea` CLI. All flags are required to avoid TTY prompts (see CLAUDE.md "Gitea access" section):
```bash
tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "docs(visual): description" --description "body" --base main --head visual
```