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>
157 lines
13 KiB
Markdown
157 lines
13 KiB
Markdown
---
|
|
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 <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 `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<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 `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<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.
|
|
- `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<TemplateId, Vec<TemplateReference>>`).
|
|
- 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<RelationshipConstraint> }`. 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<TriangleState>` function.
|
|
- `TriangleState` ECS component: `{ triangle_id: TriangleId, role_assignments: BTreeMap<RoleId, StableId>, 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
|
|
```
|