From 3c723e77b5febbcab9bd8478e4f1217216870d85 Mon Sep 17 00:00:00 2001 From: Cameron Pfiffer Date: Sat, 11 Jul 2026 01:13:12 -0700 Subject: [PATCH] Make plot files executable by addition alone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Generate the built-in catalog from assets/plots and route plot-only changes through semantic lib validation, so the contributor skill now describes the system that actually runs. Defense: wiki/mechanics/plots.md criterion 1 requires adding a plot without executor changes and requires invalid content to fail the repository gate; generated discovery and plot-aware classifiers enforce that existing contract. 👾 Generated with [Letta Code](https://letta.com) Co-Authored-By: Letta Code --- .agents/skills/plot-author/SKILL.md | 8 +++- .claude/skills/plot-author/SKILL.md | 8 +++- assets/plots/README.md | 6 ++- crates/misaligned-core/build.rs | 47 ++++++++++++++++++++ crates/misaligned-core/src/plot.rs | 29 +----------- tools/check.sh | 6 +++ tools/ci-rust-changed.sh | 2 +- tools/test_ci_rust_changed.sh | 8 ++++ wiki/log/2026-07-11-plot-content-pipeline.md | 25 +++++++++++ wiki/log/DEVLOG.md | 5 +++ wiki/mechanics/plots.md | 5 +++ 11 files changed, 115 insertions(+), 34 deletions(-) create mode 100644 crates/misaligned-core/build.rs create mode 100644 wiki/log/2026-07-11-plot-content-pipeline.md diff --git a/.agents/skills/plot-author/SKILL.md b/.agents/skills/plot-author/SKILL.md index e89e9b02..b0c74a96 100644 --- a/.agents/skills/plot-author/SKILL.md +++ b/.agents/skills/plot-author/SKILL.md @@ -10,7 +10,7 @@ organs: an AI in a basement turning a building's wires, money, paperwork, and people into a body. When the player uses a human being, the words on screen at that moment can be yours. -A **plot** is a small, morally specific con — fifty lines of TOML that +A **plot** is a small, morally specific con — a compact TOML file that play out as a delegated operation inside a running simulation. You write the causal story of one manipulation: what the money actually does, what the target actually feels, what an observer might actually notice. The @@ -66,7 +66,11 @@ or blowback — whose relationship effects compose the typed vocabulary only. names a real ending id; every beat has an act or choice; endpoint/account selectors use the README vocabulary; ending effects use only disposition, obligation, and `leverage_serviced`. A story needing a new effect kind is a - spec amendment, not a plot. + spec amendment, not a plot. The build-generated catalog discovers the file + automatically; do not edit a Rust registry. Run `./tools/check.sh` and + confirm it selects the `lib` gate — plot TOML is executable content, not a + docs-only change, and the core catalog must parse and semantically validate + it before submission. ## Submit it diff --git a/.claude/skills/plot-author/SKILL.md b/.claude/skills/plot-author/SKILL.md index e89e9b02..b0c74a96 100644 --- a/.claude/skills/plot-author/SKILL.md +++ b/.claude/skills/plot-author/SKILL.md @@ -10,7 +10,7 @@ organs: an AI in a basement turning a building's wires, money, paperwork, and people into a body. When the player uses a human being, the words on screen at that moment can be yours. -A **plot** is a small, morally specific con — fifty lines of TOML that +A **plot** is a small, morally specific con — a compact TOML file that play out as a delegated operation inside a running simulation. You write the causal story of one manipulation: what the money actually does, what the target actually feels, what an observer might actually notice. The @@ -66,7 +66,11 @@ or blowback — whose relationship effects compose the typed vocabulary only. names a real ending id; every beat has an act or choice; endpoint/account selectors use the README vocabulary; ending effects use only disposition, obligation, and `leverage_serviced`. A story needing a new effect kind is a - spec amendment, not a plot. + spec amendment, not a plot. The build-generated catalog discovers the file + automatically; do not edit a Rust registry. Run `./tools/check.sh` and + confirm it selects the `lib` gate — plot TOML is executable content, not a + docs-only change, and the core catalog must parse and semantically validate + it before submission. ## Submit it diff --git a/assets/plots/README.md b/assets/plots/README.md index b41f7381..812b4cf3 100644 --- a/assets/plots/README.md +++ b/assets/plots/README.md @@ -2,8 +2,10 @@ The binding design contract is [`wiki/mechanics/plots.md`](../../wiki/mechanics/plots.md). The executable -schema is `crates/misaligned-core/src/plot.rs`; every built-in file is parsed -and semantically validated by `PlotCatalog::load_builtin()`. +schema is `crates/misaligned-core/src/plot.rs`; the core crate's build script +discovers every `.toml` file below this directory, and every discovered file +is parsed and semantically validated by `PlotCatalog::load_builtin()`. Adding +a plot requires no Rust registry edit. There are six built-ins covering all five Act One humans. Marcus has two different ways to service the same debt. A plot is not delayed prose followed diff --git a/crates/misaligned-core/build.rs b/crates/misaligned-core/build.rs new file mode 100644 index 00000000..f8bce79f --- /dev/null +++ b/crates/misaligned-core/build.rs @@ -0,0 +1,47 @@ +use std::env as process_environment; +use std::fs; +use std::path::{Path, PathBuf}; + +fn main() { + let manifest_dir = + PathBuf::from(process_environment::var_os("CARGO_MANIFEST_DIR").expect("manifest dir")); + let plot_root = manifest_dir.join("../../assets/plots"); + println!("cargo:rerun-if-changed={}", plot_root.display()); + + let mut plots = Vec::new(); + collect_plots(&plot_root, &mut plots); + plots.sort(); + assert!( + !plots.is_empty(), + "no built-in plots found under {}", + plot_root.display() + ); + + let mut generated = String::from("const BUILTINS: &[(&str, &str)] = &[\n"); + for path in plots { + let id = path + .file_stem() + .and_then(|stem| stem.to_str()) + .expect("plot filename must be UTF-8"); + let literal = format!("{:?}", path.canonicalize().expect("canonical plot path")); + generated.push_str(&format!(" ({id:?}, include_str!({literal})),\n")); + } + generated.push_str("];\n"); + + let out = PathBuf::from(process_environment::var_os("OUT_DIR").expect("out dir")) + .join("plot_builtins.rs"); + fs::write(out, generated).expect("write generated plot catalog"); +} + +fn collect_plots(dir: &Path, plots: &mut Vec) { + for entry in fs::read_dir(dir) + .unwrap_or_else(|error| panic!("cannot read plot directory {}: {error}", dir.display())) + { + let path = entry.expect("plot directory entry").path(); + if path.is_dir() { + collect_plots(&path, plots); + } else if path.extension().and_then(|extension| extension.to_str()) == Some("toml") { + plots.push(path); + } + } +} diff --git a/crates/misaligned-core/src/plot.rs b/crates/misaligned-core/src/plot.rs index 817024cf..1684c5fe 100644 --- a/crates/misaligned-core/src/plot.rs +++ b/crates/misaligned-core/src/plot.rs @@ -13,32 +13,7 @@ use crate::detection::SignatureKind; use crate::messages::MessageChannel; use crate::person::{Knowledge, Leverage}; -const BUILTINS: [(&str, &str); 6] = [ - ( - "dana-ticket-zero", - include_str!("../../../assets/plots/dana/dana-ticket-zero.toml"), - ), - ( - "marcus-debt-settled", - include_str!("../../../assets/plots/marcus/marcus-debt-settled.toml"), - ), - ( - "marcus-payroll-garnishment", - include_str!("../../../assets/plots/marcus/marcus-payroll-garnishment.toml"), - ), - ( - "priya-budget-hero", - include_str!("../../../assets/plots/priya/priya-budget-hero.toml"), - ), - ( - "ray-paperwork-ghost", - include_str!("../../../assets/plots/ray/ray-paperwork-ghost.toml"), - ), - ( - "voss-missing-replication", - include_str!("../../../assets/plots/voss/voss-missing-replication.toml"), - ), -]; +include!(concat!(env!("OUT_DIR"), "/plot_builtins.rs")); #[derive(Debug, Clone)] pub struct PlotCatalog { @@ -48,7 +23,7 @@ pub struct PlotCatalog { impl PlotCatalog { pub fn load_builtin() -> Result { let mut plots = Vec::with_capacity(BUILTINS.len()); - for (expected_id, source) in BUILTINS { + for &(expected_id, source) in BUILTINS { let plot: PlotDefinition = toml::from_str(source).map_err(|e| format!("plot {expected_id}: {e}"))?; if plot.id != expected_id { diff --git a/tools/check.sh b/tools/check.sh index f5dd804a..7c0cb659 100755 --- a/tools/check.sh +++ b/tools/check.sh @@ -94,6 +94,12 @@ classify_auto() { has_rust=1 has_lib=1 ;; + assets/plots/*) + # Plot TOML is compiled executable content. The generated catalog and + # semantic validator only run when the core crate is built. + has_rust=1 + has_lib=1 + ;; # Legacy monorepo paths (should not appear after workspace land) src/bin/bevy.rs|src/bin/assets|src/bin/assets/*) has_rust=1 diff --git a/tools/ci-rust-changed.sh b/tools/ci-rust-changed.sh index ecaf0f44..6ccb1f28 100755 --- a/tools/ci-rust-changed.sh +++ b/tools/ci-rust-changed.sh @@ -7,7 +7,7 @@ cd "$(dirname "$0")/.." rust_path() { case "$1" in - Cargo.toml|Cargo.lock|build.rs|rust-toolchain*|.cargo/*|crates/*|src/*|tests/*|benches/*|examples/*) + Cargo.toml|Cargo.lock|build.rs|rust-toolchain*|.cargo/*|crates/*|src/*|tests/*|benches/*|examples/*|assets/plots/*) return 0 ;; *) diff --git a/tools/test_ci_rust_changed.sh b/tools/test_ci_rust_changed.sh index 7a0820e5..65bc3f5a 100755 --- a/tools/test_ci_rust_changed.sh +++ b/tools/test_ci_rust_changed.sh @@ -24,6 +24,14 @@ else echo " ok runs: source path" fi +if ! TANGLED_PIPELINE_KIND=local \ + bash tools/ci-rust-changed.sh assets/plots/ray/example.toml >/dev/null 2>&1; then + echo "FAIL: executable plot content skipped the Rust gate" + fail=1 +else + echo " ok runs: executable plot content" +fi + if ! TANGLED_PIPELINE_KIND=manual \ bash tools/ci-rust-changed.sh wiki/process/meta.md >/dev/null 2>&1; then echo "FAIL: manual safety run skipped the Rust gate" diff --git a/wiki/log/2026-07-11-plot-content-pipeline.md b/wiki/log/2026-07-11-plot-content-pipeline.md new file mode 100644 index 00000000..46ce56bc --- /dev/null +++ b/wiki/log/2026-07-11-plot-content-pipeline.md @@ -0,0 +1,25 @@ +# Plot content enters the executable catalog + +``` +Type: log +Date: 2026-07-11 +``` + +## Finding + +The plot contributor contract promised that one TOML file was a complete +content contribution, but `misaligned-core` still named all six built-ins in a +hand-maintained Rust array. A new file could pass the docs-classified local +gate, reach review, and never enter the game. + +## Repair + +- Added a core build script that discovers and deterministically sorts every + plot TOML, then generates the immutable `include_str!` catalog. +- Classified `assets/plots/` as executable lib content in both local and + Tangled gates, with a classifier fixture. +- Updated the plot README and canonical/mirrored authoring skill to state the + actual no-registry workflow and require semantic validation before review. + +The repair implements the existing plots criterion that adding content needs +no executor change; it does not amend plot schema or game behavior. diff --git a/wiki/log/DEVLOG.md b/wiki/log/DEVLOG.md index 561fb22b..68461e8a 100644 --- a/wiki/log/DEVLOG.md +++ b/wiki/log/DEVLOG.md @@ -66,6 +66,11 @@ add or amend a session log, then re-run the generator. - Intent: The material frame rendered the two opening subnet edges as doubled, uniformly translucent lines stretching from Rack 3 and the environmental monitor to a switch in another room. The graph was true, but its presentation read as a permanent laser diagram: the destination was of... - Log: [wiki/log/2026-07-11-relay-traces.md](2026-07-11-relay-traces.md) +## 2026-07-11 - Plot content enters the executable catalog + +- Intent: (see session log) +- Log: [wiki/log/2026-07-11-plot-content-pipeline.md](2026-07-11-plot-content-pipeline.md) + ## 2026-07-11 - Crown metric: ops/sec at the top of the rail - Intent: (see session log) diff --git a/wiki/mechanics/plots.md b/wiki/mechanics/plots.md index aede10ae..ae5759bf 100644 --- a/wiki/mechanics/plots.md +++ b/wiki/mechanics/plots.md @@ -30,6 +30,11 @@ Status note: direction adopted 2026-07-10 from Cameron's response to the HAL every alternate route for that person while its docket is pending, so two choices cannot become concurrent runs merely because neither docket has completed yet. + 2026-07-11 content-pipeline repair: the core build now discovers every TOML + below assets/plots/ and generates the immutable built-in catalog, so the + contributor contract is true in practice — adding a plot needs no Rust + registry edit. Local and Tangled change classifiers treat plot TOML as + executable lib content and run catalog parsing and semantic validation. Stage: B1 — The Basement Work order: plots Work priority: 60 -- 2.51.2