docs(sprints): add Sprint 18: Touch briefings

14 tickets across server (9), client (3), copy (2). Examine mechanic,
NPC awareness, social propagation, minimap, dialogue UI, save state
data model, and blocker-clearing work for Sprint 19.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-24 13:42:52 +01:00
co-authored by Claude Opus 4.6
parent d1ca99b0c0
commit 86aab2d14e
4 changed files with 452 additions and 0 deletions
+99
View File
@@ -0,0 +1,99 @@
# Sprint 18: Touch — Client Tasks
**Goal:** The player can examine entities and objects to generate character-filtered observations; NPCs detect and react when watched; social actions propagate through the relationship graph; minimap renders POIs on the client.
**Branch:** `client`
**Agents:** Stig (UI/rendering), Tyre (architecture), Hoshe (QA)
## Carry-over from Sprint 17
None. Sprint 17 closed 19/19.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #151 | Minimap rendering | #148/#149 (both done in Sprint 17) |
| #174 | Dialogue UI — client (D-061 spec) | #434 (done) |
| #264 | Knowledge/journal display | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/architecture.md` — D-020 (client is pure renderer, no game logic in GDScript), D-041 (knowledge graph — client displays KG data from snapshot), D-042 (UI microcopy format — YAML via UIStrings autoload)
- `decisions/perception.md` — D-013 (diegetic insert/POI system — minimap is insert-layer UI), D-061 (dialogue box spec — 20% max height, no portraits)
- `decisions/content.md` — D-028 (dialogue architecture — client renders options, server selects), D-062 (invisible locked options), D-064 (walk-away — WASD during dialogue)
- `decisions/scope.md` — D-027 (vertical slice — dual-character POI and knowledge display)
## Notes
### #151 — Minimap rendering
POI data infrastructure (`PointOfInterest` component, discovery events) landed on the server in Sprint 17 (#148, #149). The `ObserverSnapshot` will carry POI data for discovered points. This ticket wires that data into a rendered minimap overlay.
What this ticket must deliver:
- A `MinimapRenderer` scene or node attached to the insert HUD layer (z-layer 6, diegetic insert per D-049)
- Nearby POIs rendered as colored dots at their relative compass position from player origin
- Distant POIs (beyond minimap radius) rendered as directional arrows at the minimap border
- POI dot color and shape vary by category (the server sends `poi_category` in snapshot — use it)
- Player is always centered; minimap does not scroll or rotate (fixed-north, D-015)
- Minimap must be diegetically framed — it reads as a neural insert overlay, not a traditional game HUD
- If no POIs discovered: minimap is empty but the insert frame still renders (the frame is diegetic, always present)
Existing infrastructure to build on:
- `client/scripts/autoloads/game_state.gd` — holds `current_snapshot` which will include POI array from server
- `client/scripts/autoloads/ui_strings.gd` — minimap label strings (add to `client/data/ui-strings.yaml` per D-042)
- `client/scripts/rendering/world_renderer.gd` — reference for how snapshot data drives rendered output
Gotcha: POI positions are in simulation tile coordinates. The minimap renders relative compass direction and distance, not absolute tile positions. Convert server tile positions to player-relative vectors in GDScript.
### #174 — Dialogue UI — client (implement to D-061 spec)
The dialogue box spec (#434, D-061) was delivered in Sprint 7 and is done. This ticket (#174) is the older "Dialogue UI" story whose description has been updated to reference the D-061 spec. Given that the dialogue box, response selection, and walk-away mechanic are already implemented (#434, #435, #437), this ticket now covers the remaining dialogue UI surface not yet wired.
What this ticket must deliver — audit first, then implement gaps:
- Verify the existing dialogue box correctly uses `game_state.current_dialogue` field (set in Sprint 14+)
- **Examine result display**: the examine verb (#242 server) returns a character-filtered text description. The client needs a display path for this — it should appear as a non-interactive overlay (not a dialogue box, no options), floating above the examined entity or in a dedicated "observation" panel. Design to spec: brief, diegetic, auto-dismisses after 4-6 seconds
- **Dialogue UI hardening**: confirm invisible locked options (D-062) — no grayed-out elements, no lock icons anywhere in the dialogue tree
- **Confrontation styling** (#436, D-063): verify italic first-person voice for confrontation options is rendering correctly; confirm the 1-2 second pre-delivery monologue beat fires before the option triggers
Integration point: `client/scripts/autoloads/game_state.gd` holds `current_dialogue`. The rendering layer reads this each frame. Examine result will come through a new `current_examine_result` field (coordinate field name with server team).
### #264 — Knowledge/journal display
Client UI for reviewing accumulated KG facts. The player character's `KnowledgeGraph` is populated server-side and sent down in snapshot as a structured object. This ticket creates the review panel.
What this ticket must deliver:
- A journal/insert panel — toggle key (TBD, coordinate with server team for any keybind — likely `J` or dedicated insert shortcut)
- Displays accumulated facts grouped by entity: "What I know about Kael Davan", then fact entries with confidence level and source
- Fact entries show: fact text, `KnowledgeConfidence` level (Suspects / KnowsOf / KnowsDetails / Direct), source (`DirectObservation` / `ToldBy` / `Heard`), and `last_observed_tick` timestamp converted to game-time string
- `Contradicted` facts rendered with a visual distinction (strikethrough or amber tint) — these are the moments where THE FRIEND arc surfaces in the UI
- `Stale` facts rendered more dimly than `Active` facts
- The display is read-only — no player interaction with entries beyond scrolling
- Diegetic frame: the panel reads as neural insert memory recall. Use `UIStrings` (D-042) for all labels (`client/data/ui-strings.yaml`)
Integration with `game_state.gd`: the snapshot does not currently carry a full KG dump — coordinate with server team. The server team will need to add a `player_knowledge` field to `ObserverSnapshot` (or a separate periodic message). Define the wire format jointly before implementation.
Key gotcha: the journal panel must close when dialogue opens and vice versa — they cannot be open simultaneously. Both compete for insert-layer attention.
## Dependency Chain
```
#151 (minimap) → POI data in snapshot (#148/#149 done) — start immediately
#174 (dialogue UI hardening + examine result display) → examine field from server #242
→ coordinate wire format week 1, implement week 2
#264 (knowledge/journal display) → player_knowledge field in snapshot (coordinate with server)
→ start design week 1, implement after wire format agreed
```
Parallel tracks: #151 can start immediately. #174 and #264 both need a brief coordination with server team on wire format additions — block 30 minutes in week 1 to agree those field names, then implement in parallel.
## 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
```
+90
View File
@@ -0,0 +1,90 @@
# Sprint 18: Touch — Copy Tasks
**Goal:** The player can examine entities and objects to generate character-filtered observations; NPCs detect and react when watched; social actions propagate through the relationship graph; minimap renders POIs on the client.
**Branch:** `copy`
**Agents:** Mellanie (author), Paula (narrative lead), Gestalt (systems)
## Carry-over from Sprint 17
None. Sprint 17 closed 19/19.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #544 | Design: collision-resistant line IDs for auto-generated NPCs | — |
| #158 | Tier 1 drama module schema | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/content.md` — D-023 (three-tier content model — Tier 1 is drama modules), D-024 (NPC generation — hundreds of auto-generated NPCs per D-029 population model), D-028 (dialogue line pool format), D-035 (line ID scheme — Amendment Sprint 15: NPC-scoped IDs; D-029 auto-generated NPCs need collision-resistant variant)
- `decisions/scope.md` — D-027 (vertical slice scope — Tier 1 modules activate for the smuggler/detective scenario)
## Notes
### #544 — Design: collision-resistant line IDs for auto-generated NPCs
**Context (Q-028):** The current line ID scheme (D-035 Amendment Sprint 15) uses `{npc-slug}_{d|m}_{###}` — e.g., `kael-davan_d_001`. For hand-authored NPCs with unique slugs this works. But D-029 specifies hundreds of procedurally generated NPCs (the 70% mundane majority), each with a generated slug like `dock-worker`. A district with 40 dock workers all using `dock-worker_d_001` produces immediate collision.
**What this ticket must deliver:**
- A design document (output to `docs/design/` or as a decision record) specifying the collision-resistant scheme for auto-generated NPC line IDs
- Evaluation of at least three options:
1. **Short UUID suffix on NPC slug**`dock-worker-a3f2_d_001`. Human-readable, unique per NPC, but IDs are not stable across seed changes
2. **StableId prefix**`npc-00042_d_001`. Machine-readable, stable if `StableId` persists. Less author-friendly
3. **Role slug + instance counter**`dock-worker-03_d_001`. Human-readable, author can write role-scoped lines used by all instances of that role. Requires a clear definition of "role" as the ID namespace
4. **Slug registry with collision resolution** — same slug gets `dock-worker`, `dock-worker-2`, etc. at generation time, recorded in content registry
- The chosen scheme must satisfy:
- Hand-authored NPCs (Kael, Sera, etc.) retain their current human-readable slugs — no migration
- Auto-generated NPCs can be distinguished from one another in line IDs
- The scheme is implementable in the content registry (`server/src/knowledge/registry.rs`) without breaking existing authored content
- Authors can still write role-scoped lines that apply to all dock workers (shared content), distinct from instance-specific authored lines
- **Gestalt owns this ticket** — it resolves Q-028. Output: a decision record (D-NNN) or documented convention added to `docs/design/`. Once the scheme is chosen, update `docs/design/interaction-verbs-v0.1.md` or the content authoring guide if needed.
Timeline: resolve by end of week 1 so server/ci teams can implement the registry change in Sprint 18 or Sprint 19.
### #158 — Tier 1 drama module schema
Tier 1 content (D-023) is authored drama modules drawn from a pool at game start. The smuggler/detective vertical slice (D-027) IS a Tier 1 module. This ticket defines the structure every Tier 1 module must follow so the storyteller can activate and manage them consistently.
**What this ticket must deliver:**
- A YAML schema definition file at `content/schemas/drama_module.schema.yaml` (or extend existing schema files in `content/schemas/`)
- The schema must cover:
- **Entry conditions**: what world-state must be true for this module to be activatable (NPC present, player relationship threshold, location accessible, etc.)
- **NPC requirements**: which NPC slots the module requires (protagonist, antagonist, witness, etc.) and what axes they must satisfy (e.g., "protagonist must have Major secret")
- **Event sequences**: ordered or unordered events the module can fire, with storyteller trigger conditions for each (proximity, tick threshold, player action)
- **Outcomes**: resolution states the module can reach (exposed, escaped, ambiguous, abandoned)
- **Pool format**: how multiple modules coexist in the pool — each module is a YAML file in `content/modules/tier1/`
- A stub Tier 1 module file for the smuggling ring scenario at `content/modules/tier1/smuggling_ring_v0_1.yaml` — this is the vertical slice module
- Coordinate with server team: the storyteller module stub (`server/src/storyteller/`) will eventually activate modules by reading this schema. Schema design choices constrain implementation — agree on the top-level structure with Tyre before finalizing
**Paula owns the dramatic structure design** (entry conditions, event sequences, outcomes). **Gestalt owns the schema implementation** (YAML format, field names, validation rules). **Mellanie reviews** for authoring ergonomics — can a writer actually fill this template?
Output: schema file + stub module file + brief authoring notes in `docs/design/tier1-module-authoring.md` explaining the fields to future writers.
## Dependency Chain
```
#544 (line ID design) → standalone — Gestalt starts week 1
→ output unblocks server/ci registry implementation (Sprint 19)
#158 (drama module schema) → standalone — Paula + Gestalt start week 1
→ output unblocks storyteller activation (future sprint)
```
Both tickets are design-first — produce documents and schema files, not code. Both run in parallel from day 1.
## Open Questions to Resolve Early
- **Q-028: collision-resistant line IDs** — #544 IS the resolution ticket. Gestalt must produce a concrete decision by end of week 1. The decision should be registered as D-NNN via the standard decision record format in `decisions/content.md`.
## 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(copy): description" --description "body" --base main --head copy
```
+88
View File
@@ -0,0 +1,88 @@
# Sprint 18: Touch — Joint Tasks
**Goal:** The player can examine entities and objects to generate character-filtered observations; NPCs detect and react when watched; social actions propagate through the relationship graph; minimap renders POIs on the client.
## Pre-Sprint
No blocking decisions required before implementation starts. All architectural decisions for Sprint 18 scope are confirmed.
| Decision | Status | Impact |
|----------|--------|--------|
| D-011 (NPCs use same LOS system) | Confirmed | #115 (NPC vision) — no new LOS mechanism needed |
| D-026 (simulation tiers) | Confirmed | #95 (background state machines) — `BackgroundSim` marker exists |
| D-041 (knowledge graph data model) | Confirmed | #242 (examine), #264 (journal display) — KG is the output format |
| D-062 (invisible locked options) | Confirmed | #174 (dialogue UI audit) — no grayed-out options anywhere |
| D-035 Amendment Sprint 15 (NPC-scoped line IDs) | Confirmed | #544 (ID design) extends this for auto-generated NPCs |
| Q-028 (collision-resistant line IDs) | **Open** | #544 resolves this — copy team, week 1 |
## Cross-Team Integration Points
### Server → Client wire format additions (week 1 coordination)
Two new snapshot fields must be agreed before client implementation begins:
| New field | Server ticket | Client ticket | Format notes |
|-----------|--------------|---------------|--------------|
| `examine_result` | #242 | #174 | `{entity_id, text, confidence}` or null — character-filtered observation text |
| `player_knowledge` | #256 (stub) / #242 | #264 | Partial KG dump: `{entities: [{id, name, confidence, source, state, last_tick}], facts: [...]}` |
| `poi_list` | #148/#149 (done) | #151 | Already in snapshot from Sprint 17 — verify field name with server |
Action: server team (Tyre/Dudley) and client team (Stig) align on field names and wire format on **day 1**. No code needed — just agreed field names written to a `.tmp/` file or directly into `server/src/bridge/types.rs` as stub structs.
### Server → Copy dependency
| Server ticket | Copy output | Integration |
|---------------|-------------|-------------|
| #91 (skill system) | #544 (ID scheme) | Auto-generated NPC skills will need line IDs once the NPC pool is large. ID scheme must work for skill-bearing generated NPCs. |
| #115 (NPC vision) | #248 (pressure framework) | Awareness events from #244 drive `exposure_pressure` in #248 — copy team's monologue lines with `mood: [anxious]` are the output surface. |
### Copy → Server dependency
| Copy output | Server ticket | Integration |
|-------------|--------------|-------------|
| #544 (ID scheme decision) | Content registry | Server team cannot implement collision-resistant registry until scheme is decided. Sprint 19 work. |
| #158 (drama module schema) | Storyteller activation | Schema design constrains the storyteller module interface. Server team should review #158 output before storyteller implementation begins. |
## Sprint Completion Proof
Sprint 18 is **DONE** when:
1. **Examine fires and is character-filtered** — Player uses Examine on an NPC at close range. The client displays a short observation text. The smuggler and detective receive different text for the same NPC — same entity, different perspective.
2. **NPC notices the player** — An NPC with the player in its LOS for ≥N consecutive ticks changes behavior: route deviation, posture shift, or tell state update. The player can observe this response.
3. **Social ripple is observable** — Player action toward NPC A (e.g., a trust-positive dialogue) causes a measurable trust delta on NPC B (second-order relationship). Verify via server state inspection or the WRONG button (F12) snapshot.
4. **Tell states wire to snapshot** — Active-tier NPCs with Major secrets show `Nervous` or `Guarded` tell in the `ObserverSnapshot`. The client renders this (monologue trigger or entity tint — verify whichever is wired).
5. **Minimap renders POIs** — Discovered POIs appear as dots/arrows on the insert minimap overlay. Player position is centered. Minimap is present when at least one POI has been discovered.
6. **Journal panel opens** — Player opens journal panel. At least one KG fact entry is visible with confidence level, source, and game-time timestamp.
7. **Background NPCs tick** — A Background-tier NPC's schedule, mood, relationship, and job state visibly change over game-time (verify via WRONG button or server log). Tick rate is once per game-minute (10 ticks per D-031).
8. **Skill system exists** — Generated NPCs have a `SkillSet` component. At least one NPC is spawned with `combat_trained` skill, resulting in a `CombatCapability` marker component attached.
9. **Save data model stubs round-trip** — A `SaveStateV1` struct serializes and deserializes without data loss. Test coverage confirms roundtrip fidelity for: entity positions, KG entries, relationship graph, simulation tick.
10. **Line ID scheme is decided** — Q-028 is resolved. A decision record (D-NNN) exists in `decisions/content.md`. The scheme is documented clearly enough for a writer to apply it immediately.
## Test Plan (D-030 alignment)
Sprint 18 is in the **integration testing** phase (ongoing from Sprint 3 per D-030). The gauntlet infrastructure from Sprint 17 is the primary test harness for server-side verification.
| Ticket | Test approach |
|--------|---------------|
| #242 (examine) | Unit test: `process_examine_interaction` with mock KG → confirm character-filtered output differs between smuggler and detective KG states. Integration test: examine verb from player action → KG entry written via event queue. |
| #244 (NPC awareness) | Unit test: NPC with player in LOS for N ticks → `PlayerAwareness` component threshold crossed → routine deviation fired. |
| #248 (pressure framework) | Unit test: high awareness events → `exposure_pressure` rises. Integration: pressure visible in snapshot HUD data. |
| #249 (social propagation) | Unit test: trust delta +5 to NPC A → NPC B (strong relationship to A) receives delta ~+2. Cycle test: A→B→A propagation terminates cleanly. |
| #337 (tell state wiring) | Integration test: `TellCategory::Nervous` for NPC with Major secret + stress past midpoint → confirmed in snapshot `entities[].tell_state`. |
| #91 (skill system) | Unit test: NPC with `combat_trained` skill in `SkillSet``CombatCapability` component present after spawn. Unit test: NPC without `combat_trained` → no `CombatCapability`. |
| #115 (NPC vision) | Unit test: NPC placed adjacent to player (within LOS, no walls) → `NpcVisionState` contains player `StableId`. Wall-blocked: player not visible. |
| #256 (save state) | Unit test: serialize `SaveStateV1` with known state → deserialize → all fields match. Round-trip for `KnowledgeGraph` (already serializable). |
| #95 (background ticks) | Unit test: NPC with `BackgroundSim`, advance 10 ticks → schedule state machine advances. Unit test: mood drift toward neutral after 10 ticks. |
| #151 (minimap) | Manual: POI discovered → minimap dot appears. Distant POI: directional arrow appears at minimap border. |
| #174 (dialogue UI) | Manual: examine result appears as overlay, auto-dismisses. Dialogue options confirmed: no locked/grayed options visible. Confrontation option in italic voice. |
| #264 (journal) | Manual: journal panel opens, KG facts listed with correct metadata. Contradicted facts visually distinct. |
| #544 (ID scheme) | Decision review: scheme handles all four population categories (hand-authored, role-based, generated unique, generated shared). |
| #158 (drama module schema) | Schema review: stub `smuggling_ring_v0_1.yaml` validates against `drama_module.schema.yaml`. |
## Teams
| Team | Branch | Agents | Tickets |
|------|--------|--------|---------|
| server | `server` | Dudley, Tyre, Hoshe | #242, #244, #248, #249, #337, #91, #115, #256, #95 |
| client | `client` | Stig, Tyre, Hoshe | #151, #174, #264 |
| copy | `copy` | Mellanie, Paula, Gestalt | #544, #158 |
+175
View File
@@ -0,0 +1,175 @@
# Sprint 18: Touch — Server Tasks
**Goal:** The player can examine entities and objects to generate character-filtered observations; NPCs detect and react when watched; social actions propagate through the relationship graph; minimap renders POIs on the client.
**Branch:** `server`
**Agents:** Dudley (simulation), Tyre (architecture), Hoshe (QA)
## Carry-over from Sprint 17
None. Sprint 17 closed 19/19.
## New Tickets
| # | Title | Blocked by |
|---|-------|------------|
| #242 | Examine mechanic | #240 (done) |
| #244 | NPC player-awareness behavior | — |
| #248 | Character goal/pressure framework | — |
| #249 | Player-action social propagation | — |
| #337 | Tell state derivation system | #323 (done) |
| #91 | Skill system & combat flag | — |
| #115 | NPC vision system | — |
| #256 | Save state data model | — |
| #95 | Background tier state machines | — |
Use `db/connectors/ticket show <id>` for full details.
## Key Decisions
- `decisions/architecture.md` — D-010 (information boundaries), D-020 (IPC architecture), D-026 (simulation tiers), D-041 (knowledge graph data model)
- `decisions/perception.md` — D-011 (fog of perception — NPCs use same LOS), D-035 (symmetric shadowcasting)
- `decisions/content.md` — D-024 (NPC 10-axis model, skill set axis, combat component), D-028 (dialogue architecture — examine verb is a dialogue layer entry point)
- `decisions/scope.md` — D-027 (vertical slice criteria — character-specific observation)
## Notes
### #242 — Examine mechanic
The interaction dispatcher (`server/src/simulation/interaction.rs`) already computes `VerbKind::ExamineNpc` and `VerbKind::ExamineObject` in the `NearbyInteractionBuffer`. The examine verb appears at close range (≤2 tiles, `CLOSE_RANGE`). `PlayerAction` dispatch and `process_player_input` are the entry points in `server/src/simulation/input.rs`.
What this ticket must deliver:
- A `process_examine_interaction` system that handles `PlayerAction::Examine { entity_id }` (or equivalent)
- Generates a detailed `ObservationEvent` with low uncertainty for the target entity
- Applies character-specific filtering via the observer's `KnowledgeGraph` (same NPC looks different to smuggler vs detective — smuggler reads cargo-handling posture, detective reads procedural tells)
- Result is written to the observer's `KnowledgeGraph` via `KnowledgeEventQueue` as a `DirectObservation` entry with `KnowledgeConfidence::Direct`
- Emits an examine result field in `ObserverSnapshot` so the client can display character-filtered detail text
Integration points: `server/src/simulation/interaction.rs` (verb dispatch), `server/src/knowledge/graph.rs` (`KnowledgeGraph` write), `server/src/perception/observation.rs` (observation event pattern), `server/src/simulation/dialogue.rs` (examine result mirrors dialogue result pattern).
### #244 — NPC player-awareness behavior
NPCs use the same LOS system as the player (D-011). The awareness system detects when an NPC's LOS query includes the player's `TilePosition`, and generates a behavioral response.
What this ticket must deliver:
- A new `PlayerAwareness` component on Active-tier NPCs tracking: whether the player is in this NPC's LOS, for how many consecutive ticks, and accumulated suspicion level
- A `detect_player_awareness` system running after `compute_observer_snapshot` — iterate Active-tier NPCs, run a simplified LOS check or piggyback on existing shadowcast state
- When awareness crosses threshold: routine deviation behavior (NPC changes path or posture), fed into the `DerivedTellState` pipeline (already exists in `server/src/npc/tell_state.rs`)
- Feeds follow-verb suspicion in `server/src/simulation/follow.rs`
Key file: `server/src/simulation/follow.rs` already has proximity + attention logic for follow suspicion — awareness system reuses this infrastructure. New system lives in `server/src/simulation/` or `server/src/npc/`.
### #248 — Character goal/pressure framework
Defines systemic pressures per character that modulate monologue salience and observation priority. Not scripted arcs — emergent from interaction of existing axes (D-024).
What this ticket must deliver:
- A `CharacterPressure` component on the player character entity: `exposure_pressure: i32` (smuggler), `institutional_pressure: i32` (detective), `relationship_pressure: i32` (both)
- Pressure inputs: exposure rises when NPCs notice the player (feeds from #244), relationship pressure from trust changes in `server/src/npc/relationships.rs`, institutional pressure from detective-specific interaction patterns
- Pressure outputs: written into `ObserverSnapshot` HUD widget data; high pressure raises monologue trigger weight for anxiety-tagged lines
- Coordinate with copy team — monologue lines using `mood: [anxious]` or `mood: [frustrated]` tags (D-035) are the output surface
This is a design-and-implement ticket — start by defining the pressure struct, then wire inputs from existing systems. Monologue salience weighting is the primary v0.1 output.
### #249 — Player-action social propagation
Player actions toward one NPC ripple through the relationship graph at three decay orders (D-029 topology principle). The `RelationshipGraph` resource (`server/src/npc/relationships.rs`) and `TrustEventQueue` are the integration points.
What this ticket must deliver:
- A `propagate_social_actions` system triggered when a `TrustEventQueue` event fires from player action
- First-order: immediate full delta to the directly affected NPC
- Second-order: `delta * 0.4` to NPCs with strong relationships to the first-order NPC (trust > 3 in `RelationshipGraph`)
- Third-order: `delta * 0.15` to NPCs one further hop away, delayed by configurable ticks
- Propagation topology varies per seed (D-029 anti-metagaming) — the same action produces different cascades depending on who knows whom
- Write propagated trust changes back to `TrustEventQueue` or directly to `Relationships` components with a `PropagatedTrust` marker
Gotcha: propagation must not loop (A affects B affects A). Visited-entity set per propagation pass prevents cycles.
### #337 — Tell state derivation system
The `derive_tell_state` system already exists and is fully tested in `server/src/npc/tell_state.rs`. This ticket existed in the backlog because the mood state machine (#323) it depends on was not yet done. #323 is now done.
What this ticket must deliver:
- Verify `derive_tell_state` runs correctly in the current schedule (it is already registered in `server/src/npc/mod.rs` after `mood::update_mood`)
- Wire `DerivedTellState` into the observer snapshot output — confirm `ObserverSnapshot.entities[].tell_state` is populated for visible entities
- Integration test: NPC with Major secret + stress past midpoint shows `TellCategory::Nervous` in snapshot
- This ticket is mostly verification + integration wiring, not new code — the system is complete, the sprint task is closing the loop into the snapshot
### #91 — Skill system & combat flag
What this ticket must deliver:
- A `SkillSet` component: `BTreeMap<String, u8>` of named skills with level values (BTreeMap per D-010 determinism requirement)
- When a `SkillSet` contains `"combat_trained"` with value ≥ 1, the ECS system attaches a `CombatCapability` marker component to that NPC at spawn time
- `SkillSet` added to the NPC generation pipeline in `server/src/npc/generate.rs` (already sets other D-024 axes)
- The `CombatCapability` component is a zero-sized marker for now — future sprints add stats
NPC skill sets are generated from content YAML at startup. The content loader in `server/src/content/` reads NPC definitions — add `skills: {}` as a YAML field on NPC templates.
### #115 — NPC vision system
NPCs must use the same LOS shadowcasting system as the player (D-011 — "Applies to ALL entities"). The shadowcast machinery lives in `server/src/perception/shadowcast.rs`.
What this ticket must deliver:
- NPC vision is computed via `compute_los` (or equivalent call) for Active-tier NPCs each tick
- Results stored in an `NpcVisionState` component: set of `StableId` values currently visible to this NPC, plus the player entity if visible
- NPC memory: `NpcMemory` component tracking last-known-position of the player even after leaving LOS ("saw you enter building → knows you're inside" per D-011)
- Inference stub: if player was seen entering a room, NPC `KnowledgeGraph` records `DirectObservation` of player at that room's zone, degrading to `KnowsOf` after configurable ticks
This feeds #244 (awareness) — the `detect_player_awareness` system reads `NpcVisionState` rather than running its own LOS query.
Performance note: only run LOS for NPCs whose `TilePosition` is within `ACTIVE_RADIUS` (already guaranteed by `ActiveSim` marker). Full shadowcast per NPC per tick is feasible at 30-80 active NPCs — Tyre has confirmed the budget.
### #256 — Save state data model
Define the serialization format for full game state. Shares architecture with #96 (state serialization system, still backlog — this ticket is the data model design, not the save/load implementation).
What this ticket must deliver:
- A `SaveStateV1` struct (versioned from day one) covering: entity state, `KnowledgeGraph` per entity (already serializable via `serde` in `server/src/knowledge/graph.rs`), `RelationshipGraph`, game clock position (`SimulationTime`), seed value
- Write format: MessagePack (consistent with IPC protocol per D-020) or RON for human-readable debugging — decide and document
- The struct must roundtrip cleanly: serialize + deserialize produces identical ECS world state
- Stub tests proving the roundtrip; full save/load flow is #257 (future sprint)
The `KnowledgeGraph` is already `Serialize + Deserialize`. The main design work is enumerating which ECS components must be captured and in what order (deterministic serialization per D-010).
### #95 — Background tier state machines
Background-tier NPCs (marked `BackgroundSim` in `server/src/simulation/tier.rs`) currently receive no simulation — tier markers exist but no background tick systems run. This ticket adds the four D-026 state machines for background NPCs.
What this ticket must deliver:
- A `background_tick` system gated by `With<BackgroundSim>` that fires once per game-minute (every 10 ticks per D-031)
- Four mini state machines per background NPC:
1. **Schedule**: advance NPC to next routine activity based on `DayPhase` (reads `DayPhase` from `server/src/simulation/time.rs`, updates `Routine` component)
2. **Mood**: simple mood drift toward neutral; significant events (stress > threshold) can shift from neutral
3. **Relationships**: trust drift toward baseline over time; no events-driven trust changes for background NPCs
4. **Job**: job performance score drift based on contentment (lower contentment → lower performance)
- Background tick does NOT run pathfinding, LOS, or dialogue — those are Active-tier only
- Background NPCs promoted to Active receive their current state machine state (no reset on promotion)
## Dependency Chain
```
#95 (background tier state machines) → standalone, no blockers
#115 (NPC vision system) → #244 (player-awareness behavior)
#248 (character goal/pressure framework) ← feeds from awareness events
#337 (tell state wiring) → standalone, verify + wire into snapshot
#242 (examine mechanic) → standalone (dispatcher already exists)
#249 (social propagation) → standalone (relationships already exist)
#91 (skill system & combat flag) → standalone
#256 (save state data model) → standalone (design + stub)
```
Parallel tracks: #95, #91, #337, #256, #249, and #242 can all start in week 1. #244 starts after #115 is in review.
## 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
```