From b8e860d85e4b2febce43d2dee1f2d3e41a2c156d Mon Sep 17 00:00:00 2001 From: wtclaude Date: Fri, 16 Oct 2026 15:18:51 -0500 Subject: [PATCH] =?UTF-8?q?feat(sidecar):=20protocol=2014=20=E2=80=94=20th?= =?UTF-8?q?e=20raid=20base=20spawn=20and=20spot=20check=20forwards=20(PLAN?= =?UTF-8?q?=5FREDESIGNS=20=C2=A711.4)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit POST /raid/spawn stamps the body raid.spawn and POST /raid/check stamps it raid.check, and each forwards it. The plugin keeps the key and the run's base in its world registry, so world.revert takes it back like a zone; nothing is kept here. The five raid.* events ride the feed like any other. Spec: docs rust-link/PROTOCOL.md §20.12. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY --- sidecar/src/main.rs | 7 +++++++ sidecar/src/web.rs | 45 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/sidecar/src/main.rs b/sidecar/src/main.rs index 668ac3e..cf9b386 100644 --- a/sidecar/src/main.rs +++ b/sidecar/src/main.rs @@ -213,6 +213,13 @@ use tracing_subscriber::EnvFilter; /// recipient. The plugin remembers the key so nothing runs twice; this process keeps nothing and /// knows nothing of what a command does. /// +/// RaidableBases (§20.12, the module's PLAN_REDESIGNS.md §11.4) adds two forwards: `POST +/// /raid/spawn`, one base for a run's step, and `POST /raid/check`, a spot's checks with nothing +/// spawned. The plugin keeps the key and the run's base in its world registry, so `world.revert` +/// takes it back like a zone and `world.expired` says when the plugin ended it first. `raid.started`, +/// `raid.completed`, `raid.ended`, `raid.purchased` and `raid.locked` ride the feed like any other +/// event. +/// /// `docs/rust-link/PROTOCOL.md` is the specification — §8 the read path, §9 identity, §10 the /// mirror, §11 configuration, §12 clans, §13 the raid frame, §14 the leases, §15 the world verbs, /// §16 the rewards, §17 the map, §18 the optional mods, §19 the walk's fixes, §20 the step editor; diff --git a/sidecar/src/web.rs b/sidecar/src/web.rs index 0d95b06..24e5bc6 100644 --- a/sidecar/src/web.rs +++ b/sidecar/src/web.rs @@ -135,6 +135,11 @@ pub async fn serve(addr: &str, state: AppState) -> anyhow::Result<()> { // so nothing runs twice, fills the placeholders and decides who is online; this process // moves the line and knows nothing of what any command does (no allowlist, D384). .route("/commands/run", post(commands_run)) + // Protocol 14 (§20.12): one RaidableBases base for a run, and a spot's checks. The plugin + // keeps the key and the run's base in its world registry, so `world.revert` takes it back + // like a zone; nothing is kept here. + .route("/raid/spawn", post(raid_spawn)) + .route("/raid/check", post(raid_check)) // Protocol 10 (§29): who took part, what they may redeem, and a line in chat. The tally // is counted and held by the plugin, the kit catalogue is Kits' own, and whether a chat // line was already said is the plugin's memory. These move lines, like everything above. @@ -597,6 +602,19 @@ async fn commands_run(State(st): State, Json(body): Json) -> Re forward_object(&st, body, "command.run", "a console command run").await } +/// Spawn one RaidableBases base for a run's step (protocol 14). `raid.queued` (its id, when the +/// raids helper made it the run's, and `repeat` for a key already seen) or `raid.error` with a +/// reason. The plugin's world registry holds the run's base; nothing is remembered here. +async fn raid_spawn(State(st): State, Json(body): Json) -> Response { + forward_object(&st, body, "raid.spawn", "a raid base spawn").await +} + +/// Check a spot for a raid base without spawning one (protocol 14): `raid.checked`, with the +/// refusal's reason and the warnings, or `raid.error` when the spot cannot be checked at all. +async fn raid_check(State(st): State, Json(body): Json) -> Response { + forward_object(&st, body, "raid.check", "a raid base spot check").await +} + /// Start counting who takes part in a run. `tally.ok` or `tally.error` with a reason. async fn tally_open(State(st): State, Json(body): Json) -> Response { forward_object(&st, body, "tally.open", "a tally").await @@ -1397,6 +1415,33 @@ mod tests { assert_eq!(teardown["teardownOf"], "12:6"); } + /// Protocol 14, RaidableBases: a spawn is stamped `raid.spawn` and a check `raid.check` + /// whatever their bodies name, so each route does its one thing; the plugin's own names for + /// difficulties, profiles and bases (D386), the spot and the settings pass through untouched. + #[test] + fn protocol_14_raid_spawn_and_check_are_stamped() { + let body = json!({ "cmd": "world.revert", "reqId": "theirs", "key": "12:7", "runId": "12", + "difficulty": "Wobbly", "profile": "Wobbly Bases", "base": "wobbly1", + "x": 650.0, "z": 2037.0, "heightAdjust": 5.0, + "pvp": false, "npcs": true, "npcCount": 0, "despawnMinutes": 90, "lock": true }); + let spawn = stamp(body.clone(), "raid.spawn", "r-68").expect("an object is stamped"); + + assert_eq!(spawn["cmd"], "raid.spawn"); + assert_eq!(spawn["reqId"], "r-68"); + assert_eq!(spawn["key"], "12:7"); + assert_eq!(spawn["difficulty"], "Wobbly"); + assert_eq!(spawn["base"], "wobbly1"); + assert_eq!(spawn["heightAdjust"], 5.0); + assert_eq!(spawn["npcCount"], 0); + assert_eq!(spawn["pvp"], false); + assert!(stamp(json!("Wobbly"), "raid.spawn", "r-69").is_none()); + + let check = stamp(body, "raid.check", "r-70").expect("an object is stamped"); + assert_eq!(check["cmd"], "raid.check"); + assert_eq!(check["x"], 650.0); + assert_eq!(check["profile"], "Wobbly Bases"); + } + /// Protocol 12: a title set is forwarded whole. The markup inside each `text` is the module's /// and the plugin's business, so it must arrive byte for byte as the website composed it. #[test]