From 4cbaf0cb575bd720c9501c30c63092de70949eb6 Mon Sep 17 00:00:00 2001 From: Jeroen Schweitzer Date: Fri, 6 Mar 2026 21:18:12 +0100 Subject: [PATCH] chore(meta): switch Sprint 25 content format from YAML to RON MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit RON is Rust-native and struct-aware — the Rust structs ARE the schema. Includes RON validator CLI for the copy team to lint their files. Co-Authored-By: Claude Opus 4.6 --- docs/sprints/sprint-25/copy.md | 22 ++++++++++++---------- docs/sprints/sprint-25/server.md | 25 +++++++++++++------------ 2 files changed, 25 insertions(+), 22 deletions(-) diff --git a/docs/sprints/sprint-25/copy.md b/docs/sprints/sprint-25/copy.md index a4f3db790..3eb38fdbe 100644 --- a/docs/sprints/sprint-25/copy.md +++ b/docs/sprints/sprint-25/copy.md @@ -24,36 +24,38 @@ Use `tooling/db/ticket show ` for full details. **How this sprint works for copy** -Server starts first. Tyre (#611) defines `ZoneSpec`, `CultureProfile`, and `NpcBlueprint` as Rust structs and writes example YAML showing the expected format. That YAML is the schema contract. Copy fills real content into that schema — not the other way around. +Server starts first. Tyre (#611) defines `ZoneSpec`, `CultureProfile`, and `NpcBlueprint` as Rust structs and writes example RON files showing the expected format. The Rust structs ARE the schema — no separate schema file to maintain. Copy fills real content into that format. -Wait for #611 to deliver its example YAML before writing the real files. Coordinate with Tyre at sprint start to agree on file locations (`content/global/zone-identity-spec.yaml` and `content/global/culture-krenn.yaml` are the expected paths, but Tyre's struct design is authoritative). +Wait for #611 to deliver its example RON before writing the real files. Tyre also ships a **RON validator CLI** (`tooling/validate-content `) that deserializes into the actual Rust structs and prints errors. Use it to lint your files before submitting. + +Coordinate with Tyre at sprint start to agree on file locations (`content/global/zone-identity-spec.ron` and `content/global/culture-krenn.ron` are the expected paths, but Tyre's struct design is authoritative). **#609 — Zone identity spec** -- Output: YAML file the generator deserializes at runtime. Schema defined by Tyre's `ZoneSpec` struct from #611. +- Output: RON file the generator deserializes at runtime. Schema defined by Tyre's `ZoneSpec` struct from #611. - Minimum two zone types with real content: **rural** and **industrial**. These are the two the sprint proof runs. Remaining types can be stubs with plausible values. - The taxonomy must make the generator produce visibly different output per zone type — if rural and industrial look the same, it has failed. - Content scope: what varies between zone types (density, pace, social site mix, NPC role distribution). Not prose worldbuilding — structured parameters that the Rust generator can read. -- Do not invent the schema. Read #611's example YAML first. +- Do not invent the schema. Read #611's example RON first. **#610 — Krenn culture profile** -- Output: YAML file the generator deserializes at runtime. Schema defined by Tyre's `CultureProfile` struct from #611. +- Output: RON file the generator deserializes at runtime. Schema defined by Tyre's `CultureProfile` struct from #611. - Must provide enough cultural signal that generated NPCs feel Krenn, not generic-space-village. - Sprint scope: **name lists** (not phoneme generation rules — Tyre's generator picks from lists), speech markers, economic values, social norms. - Phoneme-based name generation is explicitly out of scope for this sprint. A curated list of Krenn-sounding names is sufficient. - Existing Krenn atmosphere and naming examples in `decisions/content.md` D-036 (amended post-workshop) are a starting point. Go deeper on concrete values (specific speech markers, actual name examples) not broader on atmospheric description. -- Do not invent the schema. Read #611's example YAML first. +- Do not invent the schema. Read #611's example RON first. ## Dependency Chain ``` -server #611 (schema contract) ──> #609 (zone spec YAML) ──┐ - ├──> server #612 (generator) - ──> #610 (culture YAML) ───────┘ +server #611 (schema + validator) ──> #609 (zone spec RON) ──┐ + ├──> server #612 (generator) + ──> #610 (culture RON) ──────┘ ``` -Server defines the shape. Copy fills it. Both #609 and #610 can be written in parallel once #611 delivers its example YAML. +Server defines the shape. Copy fills it. Both #609 and #610 can be written in parallel once #611 delivers its example RON. ## PR Workflow diff --git a/docs/sprints/sprint-25/server.md b/docs/sprints/sprint-25/server.md index d307988c4..3c03edb24 100644 --- a/docs/sprints/sprint-25/server.md +++ b/docs/sprints/sprint-25/server.md @@ -33,9 +33,9 @@ Use `tooling/db/ticket show ` for full details. 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 YAML. 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 YAML. Hardcode two zone profiles (rural, industrial stub) and a Krenn culture stub in Rust. Get the generation pipeline and stdout output working end-to-end. -- **Phase 2 (#612, late):** Swap hardcoded stubs for real YAML loading from disk. Wire in copy's actual #609 and #610 files. Run the two-zone proof. +- **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 Krenn 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. @@ -43,21 +43,22 @@ This phasing means the copy team's blocking relationship is on the final integra - Starts immediately. No blockers. - Define three structs in `server/src/npc/blueprint.rs` (new file): - - `ZoneSpec` — deserializes from zone-identity-spec.yaml - - `CultureProfile` — deserializes from culture-krenn.yaml + - `ZoneSpec` — deserializes from zone-identity-spec.ron + - `CultureProfile` — deserializes from culture-krenn.ron - `NpcBlueprint` — generator output for a single NPC -- All three derive `Serialize`, `Deserialize` (serde + serde_yaml). +- 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.yaml` and `content/global/culture-krenn.example.yaml` showing the schema copy must fill. Share these with Miri before copy starts writing real content. +- Key deliverable: write `content/global/zone-identity-spec.example.ron` and `content/global/culture-krenn.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 YAML from disk at runtime. Zone taxonomy is the file — adding a new zone type requires zero Rust changes. +- 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)"). @@ -66,9 +67,9 @@ This phasing means the copy team's blocking relationship is on the final integra ## Dependency Chain ``` -#611 (structs + example YAML) ──> copy #609 (zone spec) ──┐ - ──> copy #610 (culture) ──┴──> #612 (generator, Phase 2) -#611 ──────────────────────────────────────────────────────────> #612 (generator, Phase 1 — no YAML needed) +#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. @@ -83,7 +84,7 @@ From Troblum's pre-sprint review. Read before starting. 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 YAML draft from copy will not deserialize cleanly. Build in slack between Phase 1 and Phase 2 — expect at least one round of struct adjustments after seeing real content. +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