--- title: "Seed Configuration Schema — Design Document" description: "Schema and generation algorithm for deterministic world instantiation from seed value — pool draws, template assignments, triangles, entanglement, contraband, and starting knowledge" type: architecture status: draft ticket: "#394" decision_refs: [D-010, D-024, D-025, D-027, D-029, D-034, D-035, D-036, D-037, D-041] author: "Tyre" created: 2026-02-13 updated: 2026-02-13 --- # Seed Configuration Schema — Design Document **Ticket:** #394 **Author:** Tyre (Technical Architect) **Status:** Draft **Date:** 2026-02-13 **Decisions referenced:** D-010, D-024, D-025, D-027, D-029, D-034, D-035, D-036, D-037, D-041 --- ## 1. Purpose The seed configuration is the **handoff contract** between authored content (pools, templates, triangles in `content/`) and a running game instance (ECS entities in memory). It answers one question: *given this content and this seed value, what specific world do we instantiate?* At game start, the seeder reads content definitions and a seed value, then produces a `SeedConfig` — a deterministic, serializable record of every randomized selection. This record is saved with the game state and replayed identically on reload. **What the seed config is NOT:** - Not a content authoring format (content authors write pools/templates/triangles — the seed config *consumes* them) - Not a runtime state snapshot (that's the ECS world — the seed config is the *recipe* that built it) - Not a save file (the save file *contains* the seed config alongside mutable game state) ## 2. Design Constraints | Constraint | Source | Impact | |-----------|--------|--------| | Deterministic reproduction | D-010 principle 4, D-030 #7 | Same seed + same content version = identical `SeedConfig`. No HashMap iteration, no platform-dependent RNG. | | BTreeMap for ordered collections | D-041 | All maps in the seed config use BTreeMap, not HashMap. | | 30/50/20 entanglement ratio (variable) | D-029 | ~30% flat, ~50% mundane triangles, ~20% intrigue-entangled. Ratios vary per seed to prevent metagaming. | | Single-candidate pools in v0.1 | Sprint briefing | Architecture supports N candidates; v0.1 pools contain exactly 1 candidate each. | | Template instantiation via role slots | D-025 | Social sites define roles; the seed assigns NPCs to roles. Single ownership with reference links. | | Two playable characters | D-027 | Smuggler + detective. Seed config records which character the player selected. | | Saved with game state | Ticket #394 | Serialized into save files. Must be self-contained (no external content references that could drift). | | Content version pinning | Implicit | Seed config records content version to detect content/save incompatibility. | ## 3. Schema Overview ``` SeedConfig ├── meta │ ├── seed: u64 │ ├── content_version: String │ ├── schema_version: u32 │ └── generated_at: String (ISO 8601) ├── character_selection: CharacterSelection ├── pool_draws: BTreeMap ├── template_assignments: BTreeMap ├── triangle_config: TriangleConfig ├── entanglement: EntanglementConfig ├── contraband: ContrabandSelection └── starting_knowledge: BTreeMap> ``` ## 4. Schema Detail ### 4.1 Meta ```rust struct SeedMeta { /// The seed value. u64 for sufficient randomness space. /// v0.1: displayed nowhere; v0.2+: player can enter a seed for shared runs. seed: u64, /// Content version string from content/content.yaml. /// If the save's content_version doesn't match the loaded content, /// the loader warns or refuses to load (prevents desync). content_version: String, /// Schema version for forward compatibility. Increment on breaking changes. schema_version: u32, /// ISO 8601 timestamp of generation (informational, not used in logic). generated_at: String, } ``` **Rationale:** `seed` is the root of determinism — every randomized decision traces back to this value through a deterministic RNG (see section 6). `content_version` pins the content snapshot to prevent save/content drift. ### 4.2 Character Selection ```rust struct CharacterSelection { /// Which character the player chose. Determines starting knowledge, /// access tiers, monologue pools, perception modes. player_character: CharacterId, /// All available characters for this campaign (for reference/validation). available_characters: Vec, } /// CharacterId is a string enum matching content definitions. /// v0.1: "smuggler" | "detective" type CharacterId = String; struct CharacterDefinition { id: CharacterId, display_name: String, /// Starting social site (determines spawn location) home_template: TemplateId, /// Starting access tiers for NPC interactions default_access: Vec, } ``` **Note:** Character selection is the one non-deterministic input — the player chooses. Everything else flows from `seed` + `player_character`. ### 4.3 Pool Draws Pools are the core randomization mechanism. Each pool defines N candidates for a role; the seeder draws one. ```rust /// Pool identifier matching content pool definitions. /// Format: "{scope}:{pool_name}" — e.g., "transit:friend_smuggler" type PoolId = String; struct PoolDraw { /// Which pool this draw came from pool_id: PoolId, /// The selected candidate's NPC canonical ID selected: NpcId, /// All candidates that were available (for debugging/replay verification) candidates: Vec, /// Index into candidates that was selected (for replay verification) selected_index: usize, } ``` **v0.1 pools (single-candidate each):** | Pool ID | Selected | Purpose | |---------|----------|---------| | `transit:friend_smuggler` | `npc:kael-davan` | Smuggler's FRIEND (D-034) | | `transit:friend_detective` | `npc:sera-venn` | Detective's FRIEND (D-034) | | `transit:bar_regulars` | (set of NPCs) | Bar regular population | | `transit:compromised_inspector` | `npc:torek-lintar` | The compromised Commission inspector | | `transit:primary_contraband` | `contraband:lattice-components` | What's being smuggled | **v0.2+ expansion:** Pools grow to N candidates. `friend_smuggler` might offer 3 dock workers who could each be the FRIEND, with different contradiction arcs. The seeder draws one. Same schema, more candidates. **Pool categories:** ```rust enum PoolCategory { /// Selects one NPC for a named narrative role NpcRole, /// Selects a set of NPCs for a group (bar regulars, shift workers) NpcGroup, /// Selects a contraband type Contraband, /// Selects an entanglement pattern (which NPCs are intrigue-connected) EntanglementPattern, } ``` ### 4.4 Template Assignments Templates (D-025 social sites) define role slots; the seeder fills them with NPCs. ```rust /// Template identifier matching content template definitions. /// Format: location slug — e.g., "logistics-hub", "bar", "smuggling-ring" type TemplateId = String; struct TemplateAssignment { template_id: TemplateId, /// Which location(s) this template is instantiated in locations: Vec, /// Role slot → NPC assignments role_assignments: BTreeMap, } /// Role slot identifier from template definition. /// Format: "{template}:{role}" — e.g., "logistics-hub:shift-supervisor" type RoleSlotId = String; struct RoleAssignment { /// The NPC assigned to this role slot npc_id: NpcId, /// Whether this NPC is the primary owner of this template (D-025 single ownership) is_owner: bool, /// If not owner, this is a reference link with relationship metadata reference_metadata: Option, } struct ReferenceLink { /// The template that owns this NPC owning_template: TemplateId, /// Why this NPC appears in this template (relationship context) relationship: String, /// How many time phases this NPC spends at this template's location presence_phases: Vec, } ``` **Example — v0.1 Sova Transit District:** ```yaml # Logistics Hub template template: logistics-hub locations: [the-terminal] roles: shift-supervisor: npc: npc:voss is_owner: true dock-worker-1: npc: npc:kael-davan is_owner: true dock-worker-2: npc: npc:drin is_owner: true new-hire: npc: npc:renn is_owner: true scheduler: npc: npc:maret-korr is_owner: true courier: npc: npc:harek is_owner: true # Bar template template: bar locations: [the-last-shift] roles: bar-owner: npc: npc:lera-sessik is_owner: true bartender: npc: npc:pell is_owner: true bar-regular-1: npc: npc:sera-venn is_owner: false reference: owning_template: null # Sera owns herself (Commission field tech, not bar staff) relationship: "social anchor — evening regular" presence_phases: [evening] bar-regular-2: npc: npc:resha is_owner: true # Smuggling ring template template: smuggling-ring locations: [maintenance-corridors] roles: ring-operative: npc: npc:devra is_owner: true contact: npc: npc:kael-davan is_owner: false reference: owning_template: logistics-hub relationship: "ring member — dual role" presence_phases: [night] ``` ### 4.5 Triangle Configuration ```rust struct TriangleConfig { /// All triangles instantiated in this seed triangles: BTreeMap, /// Which triangles are initially active (storyteller can activate others later) initially_active: Vec, } /// Triangle identifier matching content triangle definitions. type TriangleId = String; struct TriangleInstance { triangle_id: TriangleId, /// The 3 NPCs assigned to this triangle's member slots. /// Maps triangle role → NPC ID. members: BTreeMap, /// Initial fork state. In v0.1: all triangles start at their default state. /// In v0.2+: seed can randomize starting fork positions for variety. initial_fork_state: Option, /// Whether this triangle is "active" (storyteller managing) or "passive" (running on its own) activation_mode: TriangleActivationMode, } enum TriangleActivationMode { /// Storyteller actively manages fork progression based on player proximity Active, /// Triangle runs on background simulation, forks resolve without storyteller intervention Passive, } ``` **v0.1 triangle instances:** | Triangle | Members | Mode | Notes | |----------|---------|------|-------| | `hub-power` | Voss (authority), Kael (subordinate), Maret (caught-between) | Active | Workplace hierarchy tension | | `worried-knowledge` | Sera (holder), Torek (subject), Naia (protected) | Active | Sera's unreported evidence | | `bar-tensions` | Lera (owner), Resha (regular), Pell (bartender) | Passive | Mundane social friction | | `worried-partner` | Kael (partner), Naia (worried), Devra (cause) | Active | Ring pressure on relationship | | `informant-question` | Torek (inspector), Drin (worker), Olin (bystander) | Passive | Mundane workplace gossip | ### 4.6 Entanglement Configuration ```rust struct EntanglementConfig { /// The target ratio for this seed (varies around 30/50/20 per D-029) target_ratio: EntanglementRatio, /// The actual ratio achieved after assignment (may differ slightly due to rounding) actual_ratio: EntanglementRatio, /// Per-NPC entanglement tier assignment npc_tiers: BTreeMap, /// Module attachment ratio: known vs stranger NPCs for intrigue connections module_attachment_ratio: ModuleAttachmentRatio, } struct EntanglementRatio { /// Percentage of NPCs that are truly flat (routine + greeting only) flat_pct: u8, /// Percentage of NPCs in mundane triangles (no conspiracy connection) mundane_pct: u8, /// Percentage of NPCs entangled with intrigue content entangled_pct: u8, } struct ModuleAttachmentRatio { /// Percentage of intrigue-connected NPCs that are known to the player character known_pct: u8, /// Percentage that are strangers stranger_pct: u8, } enum EntanglementTier { /// Routine + greeting, no triangle membership, social wallpaper Flat, /// Member of mundane triangle(s), no conspiracy connection Mundane, /// Connected to intrigue content (ring member, compromised, witness, etc.) Entangled, } ``` **v0.1 entanglement breakdown (17 NPCs):** | Tier | Count | Pct | NPCs | |------|-------|-----|------| | Flat | 5 | 29% | Renn, Harek, Sess, Sabel, Tav | | Mundane | 9 | 53% | Voss, Lera, Pell, Resha, Maret, Drin, Olin, Naia, Torek* | | Entangled | 3 | 18% | Kael (ring), Devra (ring), Sera (witness) | *Torek straddles mundane/entangled — he's compromised (entangled) but his triangle surface reads as mundane institutional friction. The seed config records him as entangled; the player discovers this through gameplay.* **Revised breakdown with Torek entangled:** | Tier | Count | Pct | NPCs | |------|-------|-----|------| | Flat | 5 | 29% | Renn, Harek, Sess, Sabel, Tav | | Mundane | 8 | 47% | Voss, Lera, Pell, Resha, Maret, Drin, Olin, Naia | | Entangled | 4 | 24% | Kael, Devra, Sera, Torek | This lands at 29/47/24 — within D-029's variable range around 30/50/20. ### 4.7 Contraband Selection ```rust struct ContrabandSelection { /// Primary contraband type for this seed primary: ContrabandType, /// Secondary contraband types available (for variety in future seeds) secondary: Vec, } struct ContrabandType { /// Identifier matching content/global/knowledge/contraband.yaml id: String, /// Display name for content systems (dialogue lines reference this) display_name: String, /// What the ring calls it internally (used in insider-access dialogue) ring_codename: String, /// Moral valence — affects monologue tone when player discovers it moral_ambiguity: MoralAmbiguity, } enum MoralAmbiguity { /// Clearly wrong (weapons, poisons) Clear, /// Morally complex (medical supplies, access tech) Ambiguous, /// Arguably justified (survival supplies, freedom tech) Sympathetic, } ``` **v0.1:** Single contraband type — unlicensed lattice components (D-037). `moral_ambiguity: Ambiguous`. The ring is smuggling *access*, not weapons. ### 4.8 Starting Knowledge ```rust /// Per-character starting knowledge state. /// Loaded into KnowledgeGraph components at entity creation time (D-041). struct StartingKnowledge { /// Facts this character knows at game start facts: Vec, /// Entity knowledge at game start (NPCs the character already knows about) entities: Vec, } struct StartingFact { /// Fact ID from content/global/knowledge/*.yaml fact_id: String, /// Starting confidence level confidence: ConfidenceLevel, /// Source of this knowledge source: KnowledgeSource, } struct StartingEntityKnowledge { /// NPC stable ID entity_id: NpcId, /// What attributes the character knows about this NPC at start known_attributes: BTreeMap, /// Starting confidence confidence: ConfidenceLevel, /// Source source: KnowledgeSource, } /// Maps to D-041's 4-level hierarchy enum ConfidenceLevel { Suspects, KnowsOf, KnowsDetails, Direct, } enum KnowledgeSource { /// Character background — they knew this before game start Background, /// Institutional knowledge — comes with the job Institutional, } ``` **Smuggler starting knowledge:** - Knows colleagues at logistics hub (KnowsOf: Voss, Drin, Renn, Maret, Harek) - Knows FRIEND deeply (KnowsDetails: Kael) - Knows bar regulars casually (Suspects: Lera, Pell) - Knows ring exists, knows Devra (KnowsDetails: Devra, ring operations) - Does NOT know Sera, Torek, or Commission personnel (detective's world) - Knows contraband type (KnowsDetails: lattice components) **Detective starting knowledge:** - Knows Commission chain of command (KnowsOf: Torek) - Knows FRIEND (KnowsDetails: Sera) - Knows bar casually (Suspects: Lera — goes there off-duty) - Knows assignment briefing (Suspects: smuggling activity on Sova) - Does NOT know ring members, specific smugglers, or insider logistics operations - Does NOT know contraband type (investigation target) ## 5. File Format and Location ### 5.1 Authored content (input to seeder) Lives in `content/` under the campaign hierarchy. Relevant files: ``` content/ ├── global/ │ └── knowledge/ │ └── contraband.yaml # Contraband type definitions ├── campaigns/main/systems/van-maanens-star/stations/sova/districts/transit/ │ ├── npcs/*.yaml # NPC profiles (candidates for pool draws) │ ├── triangles/*.yaml # Triangle definitions (instantiated by seed) │ ├── locations/*.yaml # Location definitions │ ├── routines/schedules.yaml # NPC daily schedules │ └── pools.yaml # Pool definitions (NEW — ticket #389) ``` ### 5.2 Pool definition format (content/...pools.yaml) ```yaml # Pool definitions for Sova Transit District # Each pool defines N candidates for a named role. # The seeder draws from these pools using the seed value. pools: - pool_id: "transit:friend_smuggler" category: npc_role description: "Smuggler's FRIEND — closest colleague, emotional anchor" constraints: - must_be_in_template: "logistics-hub" - must_have_pattern: "FRIEND" - bonded_character: "smuggler" candidates: - npc_id: "npc:kael-davan" weight: 1 # v0.1: only candidate # v0.2+: additional candidates with different contradiction arcs - pool_id: "transit:friend_detective" category: npc_role description: "Detective's FRIEND — social anchor, information holder" constraints: - bonded_character: "detective" candidates: - npc_id: "npc:sera-venn" weight: 1 - pool_id: "transit:compromised_inspector" category: npc_role description: "The Commission inspector compromised by the ring" constraints: - must_have_access: "authority" candidates: - npc_id: "npc:torek-lintar" weight: 1 - pool_id: "transit:primary_contraband" category: contraband description: "What the ring is smuggling" candidates: - id: "contraband:lattice-components" weight: 1 ``` ### 5.3 Generated seed config (runtime output) **Format:** RON (Rusty Object Notation) for Rust-native deserialization. Mirrors the Rust structs from section 4. **Location:** Embedded in save files. Not a standalone file during gameplay — the seeder generates it in memory, the ECS consumes it, and the save system serializes it alongside game state. **Debug dump location:** `runtime/debug/seed-config-{seed}.ron` — written only in debug builds or when `--dump-seed` flag is passed. Useful for content authors testing pool behavior. ### 5.4 Schema file A JSON Schema for validating pool definition files goes into `content/_schema/pools.schema.json`. The seed config itself is validated at the Rust type level (serde deserialization), not via JSON Schema. ## 6. Generation Algorithm ### 6.1 Seeder pipeline ``` Input: seed: u64, player_character: CharacterId, content: LoadedContent Output: SeedConfig 1. Initialize deterministic RNG from seed └── Use `rand_chacha::ChaCha20Rng::seed_from_u64(seed)` └── ChaCha20 is platform-independent, deterministic, cryptographically strong 2. Draw pool selections (order: alphabetical by pool_id for determinism) ├── For each pool in sorted order: │ ├── Compute weighted random selection from candidates │ ├── Record PoolDraw { selected, candidates, selected_index } │ └── Advance RNG state (consumed regardless of pool size) └── Validate: no NPC selected for conflicting roles 3. Assign templates ├── For each template in sorted order: │ ├── Fill mandatory role slots from pool draws │ ├── Fill remaining slots from available NPCs (weighted by fit) │ ├── Record ownership (first template assigned = owner) │ └── Create reference links for cross-template NPCs └── Validate: every NPC has exactly one owning template 4. Configure triangles ├── Map pool-drawn NPCs into triangle member slots ├── Determine activation mode per triangle │ └── Active if any member is entangled; passive otherwise └── Set initial fork states (v0.1: all default) 5. Compute entanglement ├── Generate target ratio (vary around 30/50/20 using seed RNG) │ └── flat_pct: 25-35 (uniform draw) │ └── entangled_pct: 15-25 (uniform draw) │ └── mundane_pct: 100 - flat - entangled ├── Classify NPCs: pool draws determine entangled set, triangle │ membership determines mundane, remainder is flat └── Record actual ratio achieved 6. Select contraband └── Draw from contraband pool (v0.1: single candidate) 7. Generate starting knowledge ├── For each character: │ ├── Background knowledge from character definition │ ├── Institutional knowledge from character role │ ├── Social knowledge from template assignments │ │ └── Character knows NPCs in their home template (KnowsOf) │ │ └── Character knows FRIEND deeply (KnowsDetails) │ └── Investigation knowledge (detective only: assignment briefing) └── Validate: no character knows things they shouldn't 8. Assemble SeedConfig and return ``` ### 6.2 Determinism guarantees The seeder MUST produce identical output given identical inputs. This requires: 1. **Platform-independent RNG:** ChaCha20 (not platform `thread_rng`). Same byte stream on Linux, macOS, Windows. 2. **Sorted iteration:** All collections iterated in sorted order (BTreeMap handles this; Vec collections must be pre-sorted or iteration order must be specified). 3. **No floating-point in selection logic:** Weights are integers. Selection uses integer arithmetic only. 4. **Content version pinning:** The `content_version` field detects if content changed between save and load. 5. **RNG consumption order:** The RNG advances in a fixed order regardless of pool sizes or skip conditions. This prevents "butterfly effect" where adding a candidate to one pool shifts all subsequent draws. ### 6.3 RNG consumption protocol To prevent butterfly effects when pools change size between content versions: ``` For each pool (sorted alphabetically): 1. Consume exactly `max_candidates` RNG values (configurable per pool, default 8) 2. Use the first consumed value to select from actual candidates 3. Remaining consumed values are discarded This means adding a candidate to pool A doesn't shift the RNG sequence for pool B. ``` **v0.1 simplification:** With single-candidate pools, all draws are deterministic regardless. The protocol matters for v0.2+ when pools have real variation. ## 7. Integration Points ### 7.1 Who writes seed configs | Component | Responsibility | |-----------|---------------| | **Seeder system** (Rust, `server/src/simulation/seeder.rs`) | Generates `SeedConfig` from seed + content | | **Save system** (Rust) | Serializes `SeedConfig` into save files | | **Content loader** (Rust, ticket #408) | Reads pool definitions from content YAML | | **Debug CLI** (Rust, `--dump-seed` flag) | Writes debug RON dump | ### 7.2 Who reads seed configs | Component | What it reads | Why | |-----------|--------------|-----| | **Entity spawner** | Pool draws, template assignments | Creates ECS entities with correct components | | **Knowledge initializer** | Starting knowledge | Populates KnowledgeGraph components (D-041) | | **Storyteller** | Triangle config, entanglement | Knows which triangles to manage, which NPCs are intrigue-relevant | | **Save/load** | Full SeedConfig | Restores game state from save | | **Replay system** | SeedMeta | Verifies deterministic reproduction | | **Line previewer** (ticket #407) | Full SeedConfig | Simulates line selection for a given seed | ### 7.3 Content author workflow 1. Author writes NPC profiles, templates, triangles, pools in `content/` 2. Author runs `make validate-content` to check schemas 3. Author runs line previewer (`tooling/line-previewer`) with a test seed to verify line selection 4. Author can inspect seed config via `--dump-seed` to verify NPC assignments match expectations ## 8. v0.1 vs v0.2+ Scope | Aspect | v0.1 | v0.2+ | |--------|------|-------| | Pool candidates | 1 per pool (deterministic) | N per pool (randomized) | | Entanglement ratio | Fixed at ~29/47/24 | Variable per seed (25-35 / 40-55 / 15-25) | | Contraband types | 1 (lattice components) | 3+ with different moral valences | | Triangle initial states | All default | Seed-randomized starting positions | | Module attachment | Fixed known/stranger split | Variable per seed (D-029: 60-70/30-40) | | Starting knowledge | Hardcoded per character | Generated from character definition + pool draws | | Cross-district pools | N/A (one district) | NPCs can be drawn across district boundaries | | Template variants | Fixed templates | Template variants (same social site, different layouts) | ## 9. Validation Rules The seeder validates invariants after generation: ```rust fn validate(config: &SeedConfig, content: &LoadedContent) -> Result<(), SeedError> { // 1. Every NPC has exactly one owning template assert_single_ownership(&config.template_assignments)?; // 2. FRIEND NPCs are assigned to the correct character assert_friend_bonds(&config.pool_draws)?; // 3. Triangle members match NPC assignments assert_triangle_consistency(&config.triangle_config, &config.template_assignments)?; // 4. Entanglement ratio is within acceptable range assert_entanglement_bounds(&config.entanglement)?; // 5. No circular ownership in reference links assert_no_circular_refs(&config.template_assignments)?; // 6. Starting knowledge respects information boundaries // (smuggler doesn't know detective-only facts, etc.) assert_knowledge_boundaries(&config.starting_knowledge, content)?; // 7. All NPC IDs reference valid content profiles assert_npc_ids_valid(&config, content)?; // 8. Content version matches assert_content_version(&config.meta, content)?; Ok(()) } ``` ## 10. Serialization Format The `SeedConfig` serializes to RON for save files and debug dumps: ```ron SeedConfig( meta: SeedMeta( seed: 42, content_version: "0.1.0", schema_version: 1, generated_at: "2026-02-13T10:00:00Z", ), character_selection: CharacterSelection( player_character: "smuggler", available_characters: [ CharacterDefinition( id: "smuggler", display_name: "Dock Worker", home_template: "logistics-hub", default_access: [Public, Insider], ), CharacterDefinition( id: "detective", display_name: "Commission Investigator", home_template: "logistics-hub", default_access: [Public, Authority], ), ], ), pool_draws: { "transit:compromised_inspector": PoolDraw( pool_id: "transit:compromised_inspector", selected: "npc:torek-lintar", candidates: ["npc:torek-lintar"], selected_index: 0, ), "transit:friend_detective": PoolDraw( pool_id: "transit:friend_detective", selected: "npc:sera-venn", candidates: ["npc:sera-venn"], selected_index: 0, ), "transit:friend_smuggler": PoolDraw( pool_id: "transit:friend_smuggler", selected: "npc:kael-davan", candidates: ["npc:kael-davan"], selected_index: 0, ), "transit:primary_contraband": PoolDraw( pool_id: "transit:primary_contraband", selected: "contraband:lattice-components", candidates: ["contraband:lattice-components"], selected_index: 0, ), }, // ... (template_assignments, triangle_config, etc.) ) ``` ## 11. Crate Dependencies ```toml # In server/Cargo.toml or wherever the seeder lives [dependencies] serde = { version = "1", features = ["derive"] } ron = "0.8" # RON serialization rand = "0.8" # RNG traits rand_chacha = "0.3" # Platform-independent deterministic RNG ``` No new dependencies beyond what the server already uses. `rand` and `serde` are existing deps. `ron` and `rand_chacha` are standard Rust ecosystem crates with no transitive bloat. ## 12. Open Questions | # | Question | Impact | Suggested Resolution | |---|----------|--------|---------------------| | 1 | Should the seed config include routine schedule overrides, or should routines be purely content-driven? | If the seeder can modify routines (e.g., FRIEND's deviation schedule depends on which FRIEND was drawn), routines become partially generated. If not, content must pre-author all variants. | Content-driven for v0.1 (one candidate = one routine). v0.2+: seeder generates routine deviations based on drawn contradiction arcs. | | 2 | Should `max_candidates` (RNG consumption budget per pool) be configurable per pool or global? | Per-pool allows fine-grained control but complicates the protocol. Global is simpler but wastes RNG state for small pools. | Global default of 8, with per-pool override in pool definition YAML. 8 handles up to 8 candidates without waste, which covers v0.2 comfortably. | | 3 | How does the seed config interact with the storyteller's module activation system (D-023 Tier 1)? | The storyteller needs to know which Tier 1 modules are *available* (not yet activated) vs *activated* vs *completed*. Does the seed config pre-select available modules, or does the storyteller draw from its own pool at runtime? | Seed config pre-selects *available* modules from a module pool. Storyteller activates them based on player proximity. This keeps all randomization in the seeder for determinism. | | 4 | Should the debug dump include a human-readable narrative summary (e.g., "Kael Davan is the smuggler's FRIEND, smuggling lattice components...")? | Useful for content authors, trivial to generate, but adds code surface. | Yes. Add a `summary: String` field to `SeedMeta` generated at dump time. Not serialized into save files. | ## 13. Implementation Sequence This is a design document. Implementation is Sprint 5+. Suggested build order: 1. **Rust types** — Define `SeedConfig` and all sub-structs with serde derives. ~1 day. 2. **Pool definition schema** — `pools.schema.json` in `content/_schema/`. ~0.5 day. 3. **Pool loader** — Extend content loader to parse pools.yaml. ~1 day. 4. **Seeder system** — `server/src/simulation/seeder.rs`. Core generation logic. ~2 days. 5. **Validation** — Invariant checks from section 9. ~1 day. 6. **Save integration** — Serialize/deserialize SeedConfig in save system. ~0.5 day. 7. **Debug dump** — `--dump-seed` CLI flag. ~0.5 day. 8. **Starting knowledge generation** — Section 4.8 logic. ~1 day. **Total estimate:** ~7-8 developer-days. Parallelizable with content authoring work. --- *Design document for ticket #394. Implementation deferred to Sprint 5+.* *Cross-references: D-010, D-024, D-025, D-027, D-029, D-034, D-035, D-036, D-037, D-041.*