Standardized YAML frontmatter on all 115 sprint briefing files across sprints 1-26 with title, description, type, status, sprint number, and team fields. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
13 KiB
title, description, type, status, sprint, team
| title | description | type | status | sprint | team |
|---|---|---|---|---|---|
| Sprint 20 — Server Briefing | Role definition schema, spatial requirements, triangle definition and generation | sprint | archived | 20 | 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 <id> 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
RoleSchematype (new, distinct fromRoleDefinition) in a newserver/src/content/template/module (orserver/src/content/types.rsextended). Fields:role_id: RoleId(newtype over String),required_traits: Vec<PersonalityTrait>,skill_focus: Vec<Skill>,relationship_constraints: Vec<RelationshipConstraint>,routine_template: Vec<RoutineEntry>— these are constraints fed into the NPC generator, not hardcoded values. - A
RelationshipConstrainttype:{ 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 atserver/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
SpaceSpectype:{ tile_count_min: u32, tile_count_max: u32, sightline_zones: Vec<SightlineZone>, 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.PrivacyLevelenum:Public,SemiPrivate,Private. Governs NPC behavior (NPCs are less likely to disclose secrets in Public spaces).TrafficPatternenum: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
TemplateOwnershipECS component:{ template_id: TemplateId, role_id: RoleId }. Assigned at template instantiation, never reassigned. - A
TemplateIdnewtype overu64— deterministic from world seed + template slug hash. - A
TemplateReferencestruct:{ from_template: TemplateId, to_template: TemplateId, via_role: RoleId, relationship_metadata: RelationshipKind }. Stored in aTemplateReferenceMapresource (aBTreeMap<TemplateId, Vec<TemplateReference>>). - Logic for lifecycle coordination: when a template is unloaded (NPC tier drops to State-saved or Ungenerated per D-026),
TemplateReferencelinks are preserved in the serialized state, not destroyed. - Unit tests: spawn two templates with cross-references, verify
TemplateReferenceMapentries, verifyTemplateOwnershipcomponents.
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
TriangleDeftype:{ triangle_id: TriangleId, roles: [RoleId; 3], conflict_type: ConflictType, interest_axes: [NpcAxis; 3], relationship_constraints: Vec<RelationshipConstraint> }. Three roles, each with a conflicting axis (Want, Secret, Tolerance, etc.). ConflictTypeenum based on D-087 active fork patterns:ResourceCompetition,LoyaltyConflict,SecretExposure,AuthorityChallenge. Passive tensions useLatentTensionvariant.TriangleIdnewtype overu64— 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.yamlper 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<TriangleState>function. TriangleStateECS component:{ triangle_id: TriangleId, role_assignments: BTreeMap<RoleId, StableId>, tension: u8, phase: TrianglePhase }.tensionstarts at a seeded value within a configured range.TrianglePhaseenum:Dormant,Simmering,Active,Resolved.- Constraint satisfaction: for each
TriangleDef, assign generated NPCs (byStableId) to the three roles. Validate that the NPC's axis values satisfy the conflict (e.g., for aLoyaltyConflict, the NPC filling theloyalty_tornrole 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
TriangleDefentries. - Unit tests: spawn a 4-NPC template, generate 2 triangles, assert
TriangleStatecomponents 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_escalationthat runs once per game-minute (every 10 ticks per D-031). For eachTriangleStateinSimmeringorActivephase: incrementtensionby a seeded per-triangle rate (drawn fromSimRngat world-gen time, stored onTriangleState). Whentensionexceeds the lowestToleranceThresholdamong the triangle's three NPCs, transitionphasefromSimmeringtoActive. - Observable events: when a triangle enters
Active, emit aTriangleCrisisEvent(new event type) containingtriangle_id,role_assignments, andtrigger_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. Resolvedtransition: when the player resolves an active fork (mechanism TBD — stub aResolveTriangle(TriangleId)command for now), setphase = Resolved. D-089: resolution does not cascade.- Unit tests: simulate 60 ticks on a triangle with a known tension rate, assert
Activetransition at the expected tick. TestResolvedcommand 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:
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