feat(rust): RunicNPC is required in the Rust bundle (runicnpc stage 9, D310)
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 2m13s
All checks were successful
PR Checks / rust-gates (pull_request) Successful in 2m13s
- install/update refuse a Rust bundle that carries no RunicNPC, before anything is written, and say how to get one that does. - doctor fails when RunicNPC is missing or was never installed; a file edited by hand stays a warning, since it still loads. - compose-bundles.sh composes no Rust bundle without a RunicNPC release that answers the bridge's runicnpc_api (the published one stays), and fails a bridge that declares none; every Rust bundle now has `npc`. - A RunicNPC API-mismatch message lost its line continuation; restored. The bundle type still parses a bundle without `npc`, so doctor and an old pin can name it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
This commit is contained in:
@@ -362,8 +362,9 @@ pub struct RustBundle {
|
||||
pub sidecar: RustSidecar,
|
||||
pub payload: PluginPayload,
|
||||
/// RunicNPC, Runic Gateway's NPC plugin (docs/runicnpc/PLAN.md D224): a third file placed in
|
||||
/// the plugins directory beside the bridge. Optional until RunicNPC's stage 9 makes it
|
||||
/// required, so a bundle without it — every one before stage 4 — still parses and installs.
|
||||
/// the plugins directory beside the bridge. Required since RunicNPC's stage 9 (D310): a bundle
|
||||
/// without it — every one before stage 4 — still PARSES, so `doctor` and an old pin can name
|
||||
/// it, but `install` and `update` refuse it.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub npc: Option<NpcComponent>,
|
||||
}
|
||||
|
||||
@@ -178,9 +178,10 @@ fn instance_rows(
|
||||
}
|
||||
}
|
||||
|
||||
// ── RunicNPC: ours when the bundle carried it (D224) ──────────────────────
|
||||
// A warning, like a helper's: until RunicNPC's stage 9 the bridge runs without it, and what is
|
||||
// lost is the site's NPC profiles and placements and events' profile NPCs.
|
||||
// ── RunicNPC: ours, and required (D224, D310) ─────────────────────────────
|
||||
// A failure when it is missing or was never placed: since RunicNPC's stage 9 the bridge
|
||||
// refuses every NPC an event places without it. A file edited by hand still loads, so that
|
||||
// stays a warning, as the bridge's own helpers are.
|
||||
if let Some(file) = &instance.npc {
|
||||
let tag = record.npc.as_ref().map(|n| n.tag.as_str()).unwrap_or("?");
|
||||
match std::fs::read(&file.path) {
|
||||
@@ -192,11 +193,17 @@ fn instance_rows(
|
||||
.note("edited or replaced by hand; `update --game rust` puts the released one back"),
|
||||
),
|
||||
Err(_) => rows.push(
|
||||
Row::warn(&label("RunicNPC"), format!("{} is missing", file.path))
|
||||
.note("without it the site's NPC profiles and placements, and events' profile NPCs, are off")
|
||||
Row::fail(&label("RunicNPC"), format!("{} is missing", file.path))
|
||||
.note("RunicNPC is required: without it every NPC an event places is refused, Rust's own scientists included")
|
||||
.note("`update --game rust` puts it back"),
|
||||
),
|
||||
}
|
||||
} else {
|
||||
rows.push(
|
||||
Row::fail(&label("RunicNPC"), "not installed")
|
||||
.note("RunicNPC is required: without it every NPC an event places is refused, Rust's own scientists included")
|
||||
.note("`update --game rust` installs it from the current bundle"),
|
||||
);
|
||||
}
|
||||
|
||||
// ── The plugin's config: the website's ───────────────────────────────────
|
||||
|
||||
@@ -183,7 +183,9 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
|
||||
);
|
||||
}
|
||||
|
||||
// RunicNPC (D224), fetched and checked beside the plugin, before anything is planned.
|
||||
// RunicNPC (D224), fetched and checked beside the plugin, before anything is planned. It is
|
||||
// required since RunicNPC's stage 9 (D310): the bridge refuses every NPC an event places
|
||||
// without it, so a bundle that cannot place it is refused before anything is written.
|
||||
let npc_released = match &bundle.npc {
|
||||
Some(component) => {
|
||||
let path = scratch.path().join(&component.asset.name);
|
||||
@@ -191,7 +193,8 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
|
||||
let released = npc::read_tarball(&path)?;
|
||||
if released.manifest.api != component.api {
|
||||
bail!(
|
||||
"RunicNPC in bundle {} answers API {}, but the bundle says {} — refusing a component the bundle does not describe.",
|
||||
"RunicNPC in bundle {} answers API {}, but the bundle says {} — refusing a \
|
||||
component the bundle does not describe.",
|
||||
bundle.bundle,
|
||||
released.manifest.api,
|
||||
component.api
|
||||
@@ -205,7 +208,12 @@ pub fn deploy(cli: &Cli, mode: Mode) -> Result<()> {
|
||||
}
|
||||
Some(released)
|
||||
}
|
||||
None => None,
|
||||
None => bail!(
|
||||
"bundle {} carries no RunicNPC, and Runic Gateway's Rust events need it: without it \
|
||||
the bridge refuses every NPC an event places. Install the current bundle (leave \
|
||||
--bundle off), or pin one published with RunicNPC v1.0.0 or later.",
|
||||
bundle.bundle
|
||||
),
|
||||
};
|
||||
|
||||
// ── Plan every instance ──────────────────────────────────────────────────
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
//! it. RunicNPC makes that directory itself on first load, because one made from outside the game
|
||||
//! is not writable by it (runicnpc PLAN.md §1.5).
|
||||
//!
|
||||
//! It is optional until RunicNPC's stage 9: a bundle without it installs as before, and a host
|
||||
//! without it runs the bridge with Rust's own scientists only (D243).
|
||||
//! It is required since RunicNPC's stage 9 (D310): the bridge refuses every NPC an event places
|
||||
//! without it, Rust's own scientists included, so `install` and `update` refuse a bundle that does
|
||||
//! not carry it and `doctor` fails a host that lacks it.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::io::Read;
|
||||
|
||||
Reference in New Issue
Block a user