diff --git a/docs/sprints/sprint-18/client.md b/docs/sprints/sprint-18/client.md new file mode 100644 index 000000000..fbf151712 --- /dev/null +++ b/docs/sprints/sprint-18/client.md @@ -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 ` 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 +``` diff --git a/docs/sprints/sprint-18/copy.md b/docs/sprints/sprint-18/copy.md new file mode 100644 index 000000000..f573bb5af --- /dev/null +++ b/docs/sprints/sprint-18/copy.md @@ -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 ` 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 +``` diff --git a/docs/sprints/sprint-18/joint.md b/docs/sprints/sprint-18/joint.md new file mode 100644 index 000000000..177c6db7f --- /dev/null +++ b/docs/sprints/sprint-18/joint.md @@ -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 | diff --git a/docs/sprints/sprint-18/server.md b/docs/sprints/sprint-18/server.md new file mode 100644 index 000000000..68eacd449 --- /dev/null +++ b/docs/sprints/sprint-18/server.md @@ -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 ` 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` 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` 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 +```