--- title: "Sprint 22 — Server Briefing" description: "Signed z-level fix, NPC pool generation, triangle instantiation, contamination activation" type: sprint status: archived sprint: 22 team: "server" --- # Sprint 22: Wire — Server Tasks **Goal:** Fix the fog regression, wire the authored Sova content into a production population and triangle set, and activate the contamination pacing layer that gives the vertical slice its 30-minute shape. **Branch:** `server` **Agents:** Dudley (simulation dev), Tyre (arch), Hoshe (QA) ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #565 | Fix signed z-level addressing: base_z u8 → i8 | — | | #567 | Add modifications: Vec stub on chunk and MobileChunk | — | | #176 | NPC pool generation | — | | #188 | Triangle instantiation from authored content | #176 | | #254 | Contamination activation mechanic | #188, Q-017 | | #509 | Fuzzy tests for dynamic/procedural maps | — | | #512 | Zone Gate room implementation | — | Use `tooling/db/ticket show ` for full details on any ticket. ## Key Decisions - `decisions/architecture.md` — D-010 (deterministic simulation, BTreeMap over HashMap), D-026 (simulation tiers), D-041 (knowledge graph data model) - `decisions/content.md` — D-023 (three-tier content model), D-024 (10-axis NPC model), D-025 (social site as atomic template unit), D-028 (dialogue tagged pools), D-029 (population entanglement ratio 30/50/20), D-034 (THE FRIEND pattern), D-036 (Sova Transit District setting) - `decisions/scope.md` — D-027 (vertical slice success criteria), D-087 (3 active forks, 2 passive tensions), D-089 (self-contained triangle forks, no cross-triangle cascade) ## Open Questions to Resolve Early - **Q-017: Triangle pressure threshold** — What events increase pressure? What numeric threshold triggers `confrontation` situation? Assigned to Gestalt and Paula. **Resolve before #254 starts** — the contamination mechanic needs a pressure model. If Q-017 is unresolved by the time #188 lands, implement #254 with a timer-based fallback (tick count, e.g., 1800 ticks = 30 game-minutes) and leave the pressure threshold as a named constant for Q-017 to fill in. ## Notes **#565 — Fix signed z-level addressing: base_z u8 → i8** - Decision D-110 (pending formal filing): change `base_z` from `u8` to `i8` in `MultiBlockReservation`, `FloorZone.z_level`, and all structs that reference a z-level *position* (not a count). `z_levels` counts remain `u8`. - Mechanically a cascading type change — no logic changes, only type propagation through ~20 structs. - Grep starting points: `server/src/simulation/spatial.rs`, `server/src/simulation/zone.rs`, and any struct with a field named `z_level` or `base_z`. - Add a doc comment on `DistrictSkeleton` (if present) clarifying the distinction: `base_z: i8` (lowest floor, can be negative for sub-basement) vs `z_levels: u8` (count, always positive). - Acceptance: `cargo test -p server` green, `clippy` clean. **#567 — Add modifications: Vec stub on chunk and MobileChunk** - Decision D-111/D-112 (pending formal filing): DLC scope stub. Entry point for future player construction system. - Add `modifications: Vec` (initially an empty `Vec`) to both static chunk entities and `MobileChunk` entities. - Define `Modification` enum/struct in a new `server/src/simulation/modification.rs` with at minimum: `position: TilePosition`, `modification_type: ModificationType` (enum stub), `placed_at_tick: u64`. - Wire into `SaveStateV1` (`server/src/simulation/save_state.rs`) so player-modified spaces round-trip through save/load. The field can be `Vec` with `#[serde(default)]` — empty for now, but the save slot exists. - No construction logic ships. This is a data model stub only. - Acceptance: a round-trip save/load test asserts the `modifications` field serializes and deserializes without data loss (even when empty). **#176 — NPC pool generation** - This is the key production startup ticket. The Gauntlet uses hand-crafted `setup_gauntlet()`. The production path needs an equivalent that loads from `content/` and spawns the Sova NPC pool. - The authored content already exists: 23 NPC YAML files at `content/campaigns/main/systems/van-maanens-star/stations/sova/districts/transit/npcs/`. The content loader (`server/src/content/loader.rs`) already loads `DistrictContent`. The spawn module (`server/src/content/spawn.rs`) already has `spawn_district_npcs()` and `resolve_cross_references()`. - The gap: there is no production startup path that calls these. The server boots into Gauntlet (`setup_gauntlet`) or does nothing (without the `gauntlet` feature). Add a production startup system that: (1) loads `content/campaigns/main/campaign.yaml`, (2) resolves the Sova district, (3) calls `spawn_district_npcs()` + `resolve_cross_references()`, (4) applies the 30/50/20 entanglement ratio from D-029 to mark NPCs as flat/mundane/intrigue-entangled via a new `EntanglementTag` component. - Entanglement ratio: seed the assignment via `SimRng` (D-010 determinism). The 17 authored Sova NPCs are pre-assigned by content (YAML field `triangle_membership`). The entanglement ratio applies to any procedurally generated padding NPCs. - Acceptance: integration test (non-gauntlet boot) asserts 17+ NPCs spawn with correct `StableId` assignments and at least one `EntanglementTag::Intrigue` entity. **#188 — Triangle instantiation from authored content** - Depends on #176. The 5 triangle YAML files exist at `content/campaigns/main/systems/van-maanens-star/stations/sova/districts/transit/triangles/` (hub-power, bar-tensions, informant-question, worried-knowledge, worried-partner). - The instantiation engine (`server/src/content/instantiation.rs`) already handles `FullTemplateDef` → `TriangleState`. This ticket wires the *authored Sova triangles* (loaded from YAML) into that engine, rather than template-generated triangles. - Per D-087: 3 active forks (T1, T2, T4 = hub-power, bar-tensions, informant-question) and 2 passive tensions (T3, T5 = worried-knowledge, worried-partner). The `TriangleState` component must reflect this classification. - Per D-089: self-contained forks. No cross-triangle cascade. Each `TriangleState` escalates independently. - Cross-template triangle (D-024): bar-tensions links NPCs across the logistics-hub and bar templates. Ensure `TemplateReferenceMap` entries are populated for cross-template role assignments. - Acceptance: after production startup, `query()` returns 5 triangle entities: 3 tagged `Active` (forks), 2 tagged `Passive` (tensions). All 5 have valid role assignments to existing NPC entities. **#254 — Contamination activation mechanic** - Blocked by #188. Blocked by Q-017 resolution (or timer fallback — see Open Questions above). - The `StorytellerPlugin` in `server/src/storyteller/mod.rs` is currently a stub. This ticket adds the v0.1 contamination system. - Minimum viable: a Bevy system that fires after a configurable tick threshold (`CONTAMINATION_DELAY_TICKS`, default 1800 = 30 game-minutes at 10 tps). On firing: (1) set a `ContaminationActive` resource to true, (2) push pressure on all Active triangles (increment `TriangleState::pressure` by a configured delta), (3) emit a `ContaminationEvent` that the monologue system can hook for "the air feels different" NPC behavioural shifts. - This gives the session its shape: 30 minutes of daily life, then escalation begins. The contamination is not an event the player sees directly — it's the upstream cause of NPC deviations, tell escalation, and eventually confrontation-threshold crossings. - If Q-017 resolves before this ticket ships: wire the actual pressure threshold from that resolution. If not: the `CONFRONTATION_THRESHOLD` constant is the hook point, set to a reasonable default (e.g., 75 on a 0-100 scale). - Acceptance: integration test runs 1801 ticks, asserts `ContaminationActive` resource is set and all Active `TriangleState` entities have `pressure > 0`. **#509 — Fuzzy tests for dynamic/procedural maps** - Part of QA epic #455. Randomized testing of procedurally generated maps against structural invariants. - Invariants to assert: (1) all walkable tiles are reachable from the player start position (connectivity), (2) no entity spawns outside the map bounds, (3) every door entity has at least one adjacent walkable tile on each side, (4) tile count within expected range (±20% of configured density). - Use `SimRng` with varying seeds (e.g., 50 seeds, 1ms timeout each). `proptest` or a manual seed loop both acceptable. - Acceptance: `cargo test -p server -- fuzzy_map` passes with at least 50 seed variations, each asserting all 4 invariants. **#512 — Zone Gate room implementation** - Part of QA epic #455. Gauntlet room for testing zone transitions. - Add to `server/src/test_world/rooms/` following the existing room pattern (see `shift_change.rs` for reference). - The room needs: two distinct zones (e.g., `ZoneId::Terminal` and `ZoneId::Corridor`), a walkable path crossing the zone boundary, a door entity on the boundary, and a reset plate. - Next available StableId range starts at 75 (see `test_world/mod.rs` for the allocation table). Update the table comment. - Acceptance: golden file test for this room passes; zone-crossing event fires when player entity crosses the boundary tile. ## Dependency Chain ``` #565 (z-level type fix) — standalone #567 (modifications stub) — standalone #509 (fuzzy map tests) — standalone #512 (zone gate room) — standalone #176 (NPC pool generation) → #188 (triangle instantiation from authored content) → #254 (contamination activation) [also blocked by Q-017 / timer fallback] ``` Parallel tracks: #565, #567, #509, #512 can all start immediately. #176 is the chain head — start it first after those are underway. ## PR Workflow When ready to submit, create a PR with `tea` CLI: ```bash tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): Sprint 22 — NPC pool, triangles, contamination" --description "body" --base main --head server ```