--- title: "Sprint 20 — Server Briefing" description: "Role definition schema, spatial requirements, triangle definition and generation" type: sprint status: archived sprint: 20 team: "server" --- # Sprint 20: Shape — Server Tasks **Goal:** The social site template system gains its foundational schema; triangles become generatable and observable as escalating tensions; the client gains save/load UI and code quality improvements. **Branch:** `server` **Agents:** Dudley (simulation), Tyre (architecture), Hoshe (QA) ## Carry-over from Sprint 19 None. Sprint 19 treated as complete. ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #163 | Role definition schema | — | | #164 | Spatial requirement specification | — | | #165 | Single-ownership model | — | | #106 | Triangle definition schema | — | | #107 | Intra-template triangle generation | — | | #250 | Triangle escalation system | — (#103, #105 done) | Use `db/connectors/ticket show ` for full details. ## Key Decisions - `decisions/content.md` — D-023 (three-tier content model: Tier 1 drama modules, Tier 2 templates, Tier 3 procedural), D-024 (NPC generation model: 10 axes, triangles as atomic social unit — 2 per template minimum), D-025 (social site / functional cluster as atomic template unit: 4-8 NPCs, 15-40 tiles, single-ownership with reference links), D-029 (population entanglement ratio: 30/50/20 — triangles are the 50% mundane layer) - `decisions/scope.md` — D-087 (v0.1 triangle configuration: T1 Kael-Smuggler-Ring, T2 Sera-Detective-Commission, T4 Drin-System-Ring as active forks; T3 and T5 as passive tensions), D-089 (self-contained triangle forks for v0.1, no cross-triangle cascade) - `decisions/architecture.md` — D-010 (deterministic simulation: BTreeMap for all collections, no HashMap), D-026 (simulation tiers: Active-tier NPCs are fully simulated; template instantiation populates Active tier), D-041 (KnowledgeGraph: per-entity component — template instantiation must assign KnowledgeGraph to each spawned NPC) ## Notes ### #163 — Role definition schema The `RoleDefinition` struct already exists in `server/src/npc/generate.rs` as a procedural generation input — it defines `name`, location pool entries, and per-axis ranges. This ticket extends that to become the canonical Tier 2 role schema. What this ticket must deliver: - A `RoleSchema` type (new, distinct from `RoleDefinition`) in a new `server/src/content/template/` module (or `server/src/content/types.rs` extended). Fields: `role_id: RoleId` (newtype over String), `required_traits: Vec`, `skill_focus: Vec`, `relationship_constraints: Vec`, `routine_template: Vec` — these are constraints fed into the NPC generator, not hardcoded values. - A `RelationshipConstraint` type: `{ with_role: RoleId, kind: RelationshipKind, required_trust: TrustRange }`. Constrains who this role must be in relationship with within the same template. - YAML deserialization via `serde`. Schema files will live at `server/data/templates/` (create the directory). - Unit tests: round-trip YAML serialize/deserialize a sample role schema. Validate constraint logic (no self-referential constraints, no duplicate role_id within a template). Integration points: `server/src/npc/generate.rs` (`RoleDefinition` → becomes a builder derived from `RoleSchema`), `server/src/content/types.rs` (existing content type infrastructure), `server/src/knowledge/types.rs` (`StableId`, `RelationshipKind`). Gotcha: `RoleId` must be stable across save/load — it's a string slug, not a bevy `Entity`. Keep it a newtype over `String` so it serializes cleanly with `StableId`. ### #164 — Spatial requirement specification No existing spatial specification type exists. This is greenfield within the template system. What this ticket must deliver: - A `SpaceSpec` type: `{ tile_count_min: u32, tile_count_max: u32, sightline_zones: Vec, privacy_level: PrivacyLevel, traffic_pattern: TrafficPattern }`. - `SightlineZone`: a named sub-area with a coverage radius in sim tiles (0.5m each, per D-066). Example: `{ name: "bar_counter", radius: 4 }` — 4 sim tiles = 2m clear sightline. - `PrivacyLevel` enum: `Public`, `SemiPrivate`, `Private`. Governs NPC behavior (NPCs are less likely to disclose secrets in Public spaces). - `TrafficPattern` enum: `Thoroughfare`, `Destination`, `Restricted`. Governs procedural NPC routine routing through this space. - YAML deserialization. Schema files co-locate with role schemas at `server/data/templates/`. - Unit tests: sample spec round-trip, validation that min <= max tile count. Integration points: `server/src/content/template/` (new module or extended `server/src/content/types.rs`), future chunk generation (`server/src/simulation/` — spatial specs will inform where templates are placed in the map). No simulation code changes needed this sprint — spec types only. Gotcha: Tile counts are in sim tiles (0.5m). A 15-40 visual tile space (per D-025) = 30-80 sim tiles. Document this conversion explicitly in code comments to prevent future confusion. ### #165 — Single-ownership model NPCs are owned by exactly one template, with reference links to others (D-025). No existing ownership component exists. What this ticket must deliver: - A `TemplateOwnership` ECS component: `{ template_id: TemplateId, role_id: RoleId }`. Assigned at template instantiation, never reassigned. - A `TemplateId` newtype over `u64` — deterministic from world seed + template slug hash. - A `TemplateReference` struct: `{ from_template: TemplateId, to_template: TemplateId, via_role: RoleId, relationship_metadata: RelationshipKind }`. Stored in a `TemplateReferenceMap` resource (a `BTreeMap>`). - Logic for lifecycle coordination: when a template is unloaded (NPC tier drops to State-saved or Ungenerated per D-026), `TemplateReference` links are preserved in the serialized state, not destroyed. - Unit tests: spawn two templates with cross-references, verify `TemplateReferenceMap` entries, verify `TemplateOwnership` components. Integration points: `server/src/simulation/tier.rs` (tier transitions must preserve `TemplateOwnership`), `server/src/simulation/save_state.rs` (serialize `TemplateOwnership` and `TemplateReferenceMap` as part of `SaveStateV1` — add fields), `server/src/npc/generate.rs` (generator receives `TemplateId` + `RoleId` at spawn time). Gotcha: `TemplateId` from seed + slug hash must be deterministic across save/load — use `StdHasher` is prohibited (non-deterministic), use a seeded hash (e.g., `std::hash::Hasher` from a fixed algorithm) or simply hash the slug string bytes with a fixed polynomial. Log the `TemplateId` computed value in tests for debugging. ### #106 — Triangle definition schema The D-024 spec says triangles are the atomic unit of social intrigue — 2 per template minimum, 1 cross-template. No `TriangleDef` type exists anywhere in the codebase. What this ticket must deliver: - A `TriangleDef` type: `{ triangle_id: TriangleId, roles: [RoleId; 3], conflict_type: ConflictType, interest_axes: [NpcAxis; 3], relationship_constraints: Vec }`. Three roles, each with a conflicting axis (Want, Secret, Tolerance, etc.). - `ConflictType` enum based on D-087 active fork patterns: `ResourceCompetition`, `LoyaltyConflict`, `SecretExposure`, `AuthorityChallenge`. Passive tensions use `LatentTension` variant. - `TriangleId` newtype over `u64` — deterministic from template seed + role triple. - Validation: all three roles must be distinct within the template; the conflict type must map to at least one axis divergence (no conflict on identical axis values). - YAML deserialization. Triangle definitions are authored as part of a template file or as a standalone `triangles.yaml` per template — Dudley to decide the co-location approach. - Unit tests: sample triangle round-trip, validation for duplicate roles, validation for self-consistent conflict. Integration points: `server/src/content/template/` (lives alongside `RoleSchema` and `SpaceSpec`), `server/src/npc/generate.rs` (the generator will consume `TriangleDef` in #107 to assign axis values that produce the desired conflict), `decisions/content.md` D-087 (v0.1 triangles T1-T5 should be expressible in this schema). Gotcha: D-089 — self-contained triangles for v0.1, no cross-triangle cascade. Do not add cross-triangle state fields to `TriangleDef`. Cross-template triangles are expressed by a `TriangleDef` that references a `RoleId` from a different `TemplateId` — the cross-template link is in the role, not a special triangle type. ### #107 — Intra-template triangle generation The template system can now describe triangles (#106). This ticket generates them from the description. What this ticket must deliver: - A `generate_intra_template_triangles(world: &mut World, template_id: TemplateId, defs: &[TriangleDef], rng: &mut SimRng) -> Vec` function. - `TriangleState` ECS component: `{ triangle_id: TriangleId, role_assignments: BTreeMap, tension: u8, phase: TrianglePhase }`. `tension` starts at a seeded value within a configured range. `TrianglePhase` enum: `Dormant`, `Simmering`, `Active`, `Resolved`. - Constraint satisfaction: for each `TriangleDef`, assign generated NPCs (by `StableId`) to the three roles. Validate that the NPC's axis values satisfy the conflict (e.g., for a `LoyaltyConflict`, the NPC filling the `loyalty_torn` role must have a Relationships axis with entries for both of the other two roles). - Minimum 2 triangles per template — emit an error (not a panic) if the template definition provides fewer than 2 `TriangleDef` entries. - Unit tests: spawn a 4-NPC template, generate 2 triangles, assert `TriangleState` components exist and role assignments are valid, assert constraint satisfaction. Integration points: `server/src/npc/generate.rs` (NPC generation runs first; triangle generation consumes the generated NPCs' axis values), `server/src/content/template/` (#106 types), `server/src/simulation/rng.rs` (`SimRng` for determinism). Gotcha: Constraint satisfaction can fail if the NPC pool doesn't provide a suitable candidate for a role. Implement a fallback: if no NPC satisfies the strict constraint, pick the closest match and log a warning. Do not panic — world generation must be robust to imperfect seeds. ### #250 — Triangle escalation system Blockers #103 (relationship dynamics) and #105 (tolerance threshold triggers) are done. `TriangleState` from #107 is available this sprint. What this ticket must deliver: - An ECS system `tick_triangle_escalation` that runs once per game-minute (every 10 ticks per D-031). For each `TriangleState` in `Simmering` or `Active` phase: increment `tension` by a seeded per-triangle rate (drawn from `SimRng` at world-gen time, stored on `TriangleState`). When `tension` exceeds the lowest `ToleranceThreshold` among the triangle's three NPCs, transition `phase` from `Simmering` to `Active`. - Observable events: when a triangle enters `Active`, emit a `TriangleCrisisEvent` (new event type) containing `triangle_id`, `role_assignments`, and `trigger_npc: StableId`. The monologue system and knowledge system can subscribe to this event — but do not wire those subscribers this sprint. Emit the event; downstream consumption is future work. - `Resolved` transition: when the player resolves an active fork (mechanism TBD — stub a `ResolveTriangle(TriangleId)` command for now), set `phase = Resolved`. D-089: resolution does not cascade. - Unit tests: simulate 60 ticks on a triangle with a known tension rate, assert `Active` transition at the expected tick. Test `Resolved` command sets phase correctly. Integration points: `server/src/simulation/tier.rs` (`tick_triangle_escalation` only runs on Active-tier NPCs per D-026), `server/src/simulation/time.rs` (game-minute scheduler — 10-tick interval), `server/src/npc/tolerance.rs` (`ToleranceThreshold` component), `server/src/npc/relationships.rs` (`RelationshipGraph` — tension rate influenced by relationship stress), `server/src/bridge/types.rs` (add `TriangleCrisisEvent` to `ObserverSnapshot` for future client rendering). Gotcha: Different seeds produce different tolerance thresholds — the same triangle template can escalate in 5 minutes or 30 minutes depending on the seed. This is intentional (D-087). Do not hardcode a tension rate — it must come from `SimRng` at world-gen time and be stored on the component. ## Dependency Chain ``` #163 (Role definition schema) ─┐ #164 (Spatial requirement spec) ├─ parallel, no inter-dependency #165 (Single-ownership model) ─┘ │ └─ feeds #166 (Template-to-instance mapping, Sprint 21) #106 (Triangle definition schema) ──► #107 (Intra-template generation) ──► #250 (Escalation system) │ └─ feeds #108 (Cross-template generation, Sprint 21) ``` #163/#164/#165 and #106/#107/#250 are two parallel tracks. All six tickets can begin in week 1; #107 and #250 gate on #106 completing first. ## PR Workflow When ready to submit, create a PR with the `tea` CLI. All flags are required to avoid TTY prompts: ```bash tea pr create --repo jpmschweitzer/settled-reach --login schweitz \ --title "feat(simulation): social site template schema and triangle system" \ --description "body" --base main --head server ```