feat(simulation): NpcBlueprint struct design, RON schema, and validator CLI (#611)

Define ZoneSpec, CultureProfile, and NpcBlueprint structs with serde/RON
deserialization. Ship example RON files as schema contract for the copy
team (#609, #610). Add validate-ron CLI for copy team to lint their files
without compiling the server.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 21:39:27 +01:00
co-authored by Claude Opus 4.6
parent 4cbaf0cb57
commit fb3ebf4313
8 changed files with 638 additions and 1 deletions
+84
View File
@@ -0,0 +1,84 @@
//! RON content validator CLI (#611).
//!
//! Deserializes a RON file into the actual Rust structs and prints errors.
//! This is the copy team's lint tool — run it to check RON files without
//! needing to compile the full server.
//!
//! # Usage
//!
//! ```sh
//! # Via wrapper script (recommended):
//! tooling/validate-ron content/global/zone-identity-spec.example.ron zone
//! tooling/validate-ron content/global/culture-krenn.example.ron culture
//!
//! # Direct:
//! cargo run --bin validate_ron -- <file.ron> <zone|culture>
//! ```
use std::process;
use clap::Parser;
use settled_reach_server::npc::blueprint::{CultureProfile, ZoneSpec};
#[derive(Parser)]
#[command(
name = "validate_ron",
about = "Validate RON content files against Rust struct schemas"
)]
struct Args {
/// Path to the RON file to validate.
file: String,
/// Schema type: "zone" (ZoneSpec) or "culture" (CultureProfile).
schema: String,
}
fn main() {
let args = Args::parse();
let content = match std::fs::read_to_string(&args.file) {
Ok(c) => c,
Err(e) => {
eprintln!("Error reading {}: {}", args.file, e);
process::exit(1);
}
};
match args.schema.as_str() {
"zone" => match ron::from_str::<ZoneSpec>(&content) {
Ok(spec) => {
println!("Valid ZoneSpec: {} ({})", spec.label, spec.zone_type);
println!(" {} roles, {} social sites", spec.roles.len(), spec.social_sites.len());
}
Err(e) => {
eprintln!("Invalid ZoneSpec in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
"culture" => match ron::from_str::<CultureProfile>(&content) {
Ok(profile) => {
println!("Valid CultureProfile: {} ({})", profile.name, profile.id);
println!(
" {} given names, {} family names",
profile.naming.given_names.len(),
profile.naming.family_names.len()
);
println!(
" {} filler words, {} favored traits",
profile.speech.filler_words.len(),
profile.values.favored_traits.len()
);
}
Err(e) => {
eprintln!("Invalid CultureProfile in {}:", args.file);
eprintln!(" {}", e);
process::exit(1);
}
},
other => {
eprintln!("Unknown schema type: '{}'. Use 'zone' or 'culture'.", other);
process::exit(1);
}
}
}