--- title: "Sprint 25 — Server Briefing" description: "NpcBlueprint struct design, template assembly generator" type: sprint status: archived sprint: 25 team: "server" --- # Sprint 25: Emerge — Server Tasks **Goal:** Prove the generator can extrapolate from minimal input — a rural Van Maanen's Star area AND an industrial Van Maanen's Star zone from zone type and culture profile alone, no per-location spec. Two zone types, one culture, side-by-side comparison. **Branch:** `server` **Agents:** Dudley (simulation dev), Tyre (architect) ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #611 | NpcBlueprint struct design | — (starts immediately) | | #612 | Template assembly generator | #611, #609 (copy), #610 (copy) | Use `tooling/db/ticket show ` for full details. ## Key Decisions - `decisions/scope.md` — D-114 (generator-first proof-of-life), D-119 (generator spike critical path) - `decisions/content.md` — D-122 (all NPCs generated), D-128 (culture implicit in location), D-129 (NPC personality: traits + behavior first), D-121 (voice culture-driven), D-123 (AI content templating via culture vectors) - `decisions/architecture.md` — D-012 (chunk-based map, borderless generation future) ## What Exists - **`server/src/simulation/generator.rs`** (576 lines) — `DistrictSkeleton` data model and related enums. Struct definitions only — no production generation logic yet. - **`server/src/npc/generate.rs`** — `RoleDefinition`-driven 10-axis NPC generator using `SimRng` (ChaCha20, deterministic). This pipeline survives; `NpcBlueprint` wraps above it and feeds into it. - **`server/src/simulation/rng.rs`** — `SimRng`. Use this for all randomness in the generator binary. - **`server/src/npc/`** — full NPC component set including `mood.rs`, `relationships.rs`, `routine.rs`, `trait_modifiers.rs`. ## Notes **Phased approach** This sprint discovers the right spec — it does not implement a known one. Three phases: - **Phase 0 (#611):** Define the Rust structs (`ZoneSpec`, `CultureProfile`, `NpcBlueprint`) and write example RON files. Build the RON validator CLI. Share with copy team immediately — this unblocks #609 and #610. - **Phase 1 (#612, early):** Build the generator binary with hardcoded test data. Do not wait for copy to finish their RON files. Hardcode two zone profiles (rural, industrial stub) and a Van Maanen's Star culture stub in Rust. Get the generation pipeline and stdout output working end-to-end. - **Phase 2 (#612, late):** Swap hardcoded stubs for real RON loading from disk. Wire in copy's actual #609 and #610 files. Run the two-zone proof. This phasing means the copy team's blocking relationship is on the final integration, not the generator build. Server can move through Phase 0 and Phase 1 in parallel with copy writing #609/#610. **#611 — NpcBlueprint struct design** - Starts immediately. No blockers. - Define three structs in `server/src/npc/blueprint.rs` (new file): - `ZoneSpec` — deserializes from zone-identity-spec.ron - `CultureProfile` — deserializes from culture-van-maanens-star.ron - `NpcBlueprint` — generator output for a single NPC - All three derive `Serialize`, `Deserialize` (serde + `ron`). **Use RON format, not YAML/JSON.** RON is Rust-native, struct-aware, supports enums and comments. The Rust structs ARE the schema — no separate schema file to maintain. - `NpcBlueprint` fields: name (String), role (occupation), traits (Vec of trait enum), observable_behaviors (Vec), cultural_markers (speech register, filler words from culture profile), relationships (Vec of (npc_id, relationship_type, valence)). - Use a spike-specific `SpikeOutput` struct for the binary's top-level output — do NOT couple to `DistrictSkeleton` for the proof. Keep the spike isolated. - Key deliverable: write `content/global/zone-identity-spec.example.ron` and `content/global/culture-van-maanens-star.example.ron` showing the schema copy must fill. Share these with Miri before copy starts writing real content. - Add a note in the file header pointing to the tickets (#609, #610) that fill these schemas with real content. - **Build a RON validator CLI** (`tooling/validate-content `) that deserializes into the actual Rust structs and prints errors. This is the copy team's lint tool — they run it to check their files without needing to compile the server. ~20 lines of Rust, ship it as part of #611. **#612 — Template assembly generator (absorbs #613)** - Blocked by #611. Build Phase 1 before #609/#610 arrive; integrate in Phase 2. - Binary: `cargo run --bin generator-spike -- --zone --seed ` (new binary in `server/src/bin/`). - Phase 1: hardcoded `ZoneSpec` and `CultureProfile` stubs in Rust. Focus on the generation logic and output formatting. - Phase 2: load zone spec and culture RON from disk at runtime. Zone taxonomy is the file — adding a new zone type requires zero Rust changes. - Determinism: `SimRng` seeded from the `--seed` flag. Same inputs = same output. - NPC generation: use the existing `npc/generate.rs` pipeline. `NpcBlueprint` maps to `RoleDefinition` via a conversion method. The blueprint's cultural markers bias trait selection. - Stdout output per invocation: zone type header, NPC list (name, role, traits, one observable behavior), relationship pairs ("A knows B as colleague (positive)"). - Sprint proof runs twice: `--zone rural --seed 42` and `--zone industrial --seed 42`. The comparison is the test. ## Dependency Chain ``` #611 (structs + example RON + validator) ──> copy #609 (zone spec) ──┐ ──> copy #610 (culture) ──┴──> #612 (generator, Phase 2) #611 ──────────────────────────────────────────────────────────────────> #612 (generator, Phase 1 — no RON needed) ``` #611 first. Phase 1 of #612 runs in parallel with copy writing #609/#610. Phase 2 of #612 waits for both. ## Feasibility Warnings From Troblum's pre-sprint review. Read before starting. 1. **`generate_npc()` requires a live bevy World.** The existing function in `npc/generate.rs` takes `TilePosition`, `StableId`, and a real `bevy_ecs::World`. The spike binary has none of these. Do not attempt to instantiate a full World for text output — stub or strip routine generation in Phase 1. Wire only the axes that produce inspectable output (traits, relationships, cultural markers). Full ECS wiring is deferred. 2. **Cultural text assembly is the real work of #612.** The existing generator produces enum variants and placeholder strings (`format!("{} has a {:?} secret", ...)`). There is no cultural text surface in the codebase today. Getting `CultureProfile` fields to appear in NPC output is a new code path — budget time for it, it is not a one-liner. 3. **`DayPhase` name collision.** `server/src/simulation/generator.rs` defines `DayPhase = String` as a stub type alias, shadowing the real `DayPhase` enum in `server/src/simulation/time.rs`. Use the real enum explicitly or alias the stub out of scope before the spike binary sees both. Do not let the collision silently compile to the wrong type. 4. **Schema negotiation takes rounds.** The first RON draft from copy will not deserialize cleanly — the validator will catch this early. Build in slack between Phase 1 and Phase 2 — expect at least one round of struct adjustments after seeing real content. ## PR Workflow When ready to submit, create a PR with `tea` CLI. All flags are required to avoid TTY prompts (see CLAUDE.md "Gitea access" section): ```bash tea pr create --repo jpmschweitzer/settled-reach --login schweitz --title "feat(simulation): generator spike — NpcBlueprint, template assembly, NPC pipeline" --description "body" --base main --head server ```