--- title: "Sprint 27 — Server Briefing" description: "ZoneTypeTemplate structs, Clippy cleanup, cargo audit CI, content/ move to server/, v0.1 YAML audit" type: sprint status: active sprint: 27 team: "server" --- # Sprint 27: Scale — Server Tasks **Goal:** Extend the zone-type template library from 2 to 31 zone types, wire the server to load them, and eliminate Clippy debt — clearing the path to the NPC legibility sprint. **Branch:** `server` **Agents:** Dudley (simulation), Tyre (arch), Hoshe (QA) ## New Tickets | # | Title | Blocked by | |---|-------|------------| | #663 | ZoneTypeTemplate and LocationSpec structs for D-142 | — | | #635 | Clippy cleanup and CI enforcement | — | | #637 | Add cargo audit to CI/review pipeline | — | | #669 | Move content/ directory under server/ | — | | #672 | Audit and flag v0.1 YAML content | — | Use `tooling/db/ticket show ` for full details. ## Key Decisions - `decisions/content.md` — D-142 (zone-type template architecture — the schema this sprint implements), D-139 (composable behavior primitives — `assemble_behaviors` already in `server/src/npc/blueprint.rs`) - `decisions/scope.md` — D-114 (generator-first proof-of-life), D-122 (all NPCs generated) ## Notes ### #663 — ZoneTypeTemplate and LocationSpec structs The existing `ZoneSpec` struct (`server/src/npc/blueprint.rs`, line 38) was authored for the Sprint 25 spike. The zone-type RON files the copy team has now shipped (`content/global/zone-types/rural_agricultural.ron`, `industrial_freight.ron`) use the D-142 format, which does not match the current struct. Four changes required: **1. Rename and extend `ZoneSpec` to `ZoneTypeTemplate`:** - Add `id: String` — the canonical zone-type identifier (e.g. `"rural_agricultural"`) - Add `subtype: Option` — subtype discriminator (currently `None` in all files) - Rename `social_sites: Vec` to `social_site_types: Vec` - The new `SocialSiteTypeSpec` uses `eligible_roles: Vec` (role IDs), not `min_npcs`/`max_npcs` — those are location-spec metadata - `economic_level` and `population_density` become defaults, overridable by location specs **2. Update `RoleSpec`:** - Remove `weight: u8` — weights are declared on location specs, not zone-type templates. Roles in a zone-type template are authoritative behavior pools; location specs declare which roles appear and at what frequency. - `behavior_primitives` is the primary field; `typical_behaviors` is preserved as `#[serde(default)]` for backward compatibility until migration is verified. **3. Add `LocationSpec` struct:** - `zone_type: String` — references a `ZoneTypeTemplate.id` - `culture: String` — references a culture profile - `role_weights: Vec` — `{ role_id: String, weight: u8 }` entries - `economic_level: Option` — overrides zone-type default if set - `population_density: Option` — overrides zone-type default if set - `abandoned: bool` — D-142 abandoned flag - `poi_overlay: Vec` — D-142 poi_overlay list - `social_sites: Vec` — site instances with `site_type`, `label`, `min_npcs`, `max_npcs` **4. Update loader in `generator_spike.rs`:** - `--from-files` currently loads `content/global/-zone-spec.ron`. Update to load from `content/global/zone-types/.ron`. - Resolve `(zone_type, role_id)` → `BehaviorPrimitive` pool by loading the `ZoneTypeTemplate` file then indexing by role ID. - The generator spike binary passes zone-type ID as `--zone-type rural_agricultural` (update the `--zone` arg). Keep `--from-files` mode. **Validate:** After struct changes, run `tooling/validate-ron content/global/zone-types/rural_agricultural.ron zone_type` — it must pass without modification to the RON files. The RON files are the ground truth; the Rust structs must match them, not the other way around. **Test:** Add a unit test in `blueprint.rs` that deserializes `rural_agricultural.ron` from disk and asserts the roles, primitive count, and social_site_types are non-empty. This test will fail until both the struct and the RON files align. **Schema registration in validate-ron:** After struct changes, update `server/src/bin/validate_ron.rs` to accept `zone_type` as a schema name that deserializes to `ZoneTypeTemplate`. --- ### #635 — Clippy cleanup and CI enforcement Two-phase: 1. **One-time cleanup:** Run `cargo clippy --fix` for auto-fixable warnings. Remaining complex warnings (too_many_arguments, complex type signatures) require manual refactors — fix by refactoring, not by suppressing with `#[allow()]`. Goal: zero warnings under `--deny warnings`. 2. **CI gate:** Add `cargo clippy -- --deny warnings` to `make pre-pr` (see `Makefile`). After this sprint, Clippy warnings in PRs are a blocking failure. Run `cargo clippy -- --deny warnings 2>&1 | wc -l` before starting to get a baseline count. The Gemini review found 66 warnings — verify the current count first (Clean House sprint may have reduced it). Fix in batches and commit frequently; a single large Clippy PR is hard to review. --- ### #637 — Add cargo audit to CI/review pipeline Trigger on `Cargo.toml`/`Cargo.lock` changes (not every commit). Approach: 1. Add `cargo audit` target to `Makefile` under `make audit`. 2. Wire it as a check in the CI configuration (`.config/`) that runs when `Cargo.lock` changes. 3. Also add to `make pre-pr` so it runs locally before PRs. The current open advisory is `RUSTSEC-2025-0141` (bincode v1.3.3 unmaintained). `cargo audit` will flag it — this is expected until #636 (bincode migration, deferred) is completed. Add an advisory ignore for this specific ID so CI stays green while the known advisory is tracked, but new advisories will fail. --- ### #669 — Move content/ directory under server/ `content/` is consumed exclusively by the server (generator, validate-ron, tests). Move it to `server/content/`. **Steps:** 1. `git mv content/ server/content/` — preserves git history. 2. Update all path references in server Rust code: search for `"content/"` string literals in `server/src/`. Key files: `server/src/bin/generator_spike.rs` (default `--content-root`), `server/src/bin/validate_ron.rs`, any test fixtures. 3. Update Makefile targets that reference `content/`. 4. Update `tooling/validate-ron` script if it has hardcoded paths. 5. Run `make test` after the move — fixture paths in integration tests may need updating. **Do not** touch `client/data/` — that is client-only and stays put. After the move, `tooling/db/sqlite-query 'SELECT * FROM tickets WHERE description LIKE "%content/global%"'` to identify any ticket descriptions with stale paths (update them for future agents). --- ### #672 — Audit and flag v0.1 YAML content Survey task, not deletion. The server and copy branches contain ~67 YAML files from v0.1 that reference deleted named NPCs (Kael, Sera, Torek, etc.) and the detective arc. **Deliverable:** A single file `docs/architecture/v01-yaml-content-audit.md` listing: - Each YAML file path and its status: `deprecated` (pure detective/named NPC content, no generator value), `convertible` (has useful data worth porting to RON generator format), or `already-superseded` (content was already replaced in main). - For `convertible` files: what specifically is worth extracting (name pools? voice patterns? dialogue seeds?). - Recommended disposition: archive vs delete vs convert. **Do not delete anything.** Flag only. Deletion requires explicit team review in a follow-up ticket. --- ## Dependency Chain ``` #669 (move content/) → must complete before #663 (loader path updates assume new location) #663 (ZoneTypeTemplate structs) → unblocks copy team (#662) full pipeline run #635 (Clippy cleanup) → #637 (cargo audit — add both to pre-pr in same pass) #672 (v0.1 YAML audit) — standalone, parallel track ``` Note: #669 should ideally complete first so #663's loader uses the new `server/content/` paths from the start. If they run in parallel, the loader path can target `server/content/` directly and #669 will confirm the files are there. ## 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): description" --description "body" --base main --head server ```