From 09f9851353a3a5bfd3a6b1fa31b73c091b2a731c Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sat, 26 Sep 2026 15:20:10 +0800 Subject: [PATCH 1/2] fix(manifest): correct startHoT signature Fixes #396 --- crates/opy-rs/src/manifest/data/manifest.json | 24 ++----------------- crates/opy-rs/src/manifest/probes/probes.json | 7 ++++++ .../manifest/probes/start-hot-signature.opy | 7 ++++++ 3 files changed, 16 insertions(+), 22 deletions(-) create mode 100644 crates/opy-rs/src/manifest/probes/start-hot-signature.opy diff --git a/crates/opy-rs/src/manifest/data/manifest.json b/crates/opy-rs/src/manifest/data/manifest.json index 0d7cf5e0..229cb069 100644 --- a/crates/opy-rs/src/manifest/data/manifest.json +++ b/crates/opy-rs/src/manifest/data/manifest.json @@ -983,7 +983,7 @@ { "catalogId": "startHealingOverTime", "evidence": [ - "builtin-surface" + "start-hot-signature" ], "id": "startHoT", "kind": "memberAction", @@ -995,7 +995,7 @@ "name": "duration" }, { - "name": "healing" + "name": "healingPerSecond" } ], "receiver": "Player" @@ -2993,26 +2993,6 @@ "params": [], "receiver": "Player" }, - { - "catalogId": "startHealingOverTime", - "evidence": [ - "builtin-surface" - ], - "id": "startHoT", - "kind": "memberAction", - "params": [ - { - "name": "healer" - }, - { - "name": "rate" - }, - { - "name": "duration" - } - ], - "receiver": "Player" - }, { "catalogId": "isInSpawnRoom", "evidence": [ diff --git a/crates/opy-rs/src/manifest/probes/probes.json b/crates/opy-rs/src/manifest/probes/probes.json index 0093db15..421337bc 100644 --- a/crates/opy-rs/src/manifest/probes/probes.json +++ b/crates/opy-rs/src/manifest/probes/probes.json @@ -357,6 +357,13 @@ "outputSha256": "4be366b4352ebe4ca0b479287385dfb188b36f830a6f8f51f4a0cf0f6af54d7d", "sha256": "5b69c61321ca05c3a9fbc2c12d225dbce749b7a05d82ad1b2f11852f4d5ac208", "source": "missing-values.opy" + }, + { + "expect": "success", + "id": "start-hot-signature", + "outputSha256": "a9b80d7f57b61fdb490c15b63c635f7e5e6dd44658e9603ca630cc27b850a785", + "sha256": "399aa9fea314fec321d49b3cea47af7a5d8dfb84cd52b432325bf1130f38bdbe", + "source": "start-hot-signature.opy" } ] } diff --git a/crates/opy-rs/src/manifest/probes/start-hot-signature.opy b/crates/opy-rs/src/manifest/probes/start-hot-signature.opy new file mode 100644 index 00000000..42ae7a0b --- /dev/null +++ b/crates/opy-rs/src/manifest/probes/start-hot-signature.opy @@ -0,0 +1,7 @@ +rule "startHoT positional signature": + @Event eachPlayer + eventPlayer.startHoT(null, 2, 3) + +rule "startHoT keyword signature": + @Event eachPlayer + eventPlayer.startHoT(healer=null, duration=2, healingPerSecond=3) From 59bd5ebb16787bdfe8995bb74fac4cac762ccb64 Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sat, 26 Sep 2026 15:43:40 +0800 Subject: [PATCH 2/2] test(manifest): exercise startHoT keyword binding --- crates/opy-rs/src/compiler/tests/builtins.rs | 24 ++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/crates/opy-rs/src/compiler/tests/builtins.rs b/crates/opy-rs/src/compiler/tests/builtins.rs index ce45d64a..02bf9070 100644 --- a/crates/opy-rs/src/compiler/tests/builtins.rs +++ b/crates/opy-rs/src/compiler/tests/builtins.rs @@ -305,6 +305,30 @@ rule "builtin surface": assert_eq!(artifact.emitted.matches("Create HUD Text").count(), 6); } +#[test] +fn start_hot_keyword_arguments_bind_like_positional_arguments() { + let source = include_str!("../../manifest/probes/start-hot-signature.opy"); + let hir = crate::compile(source, "start-hot-signature.opy", Path::new(".")) + .expect("pinned startHoT positional and keyword forms must resolve natively"); + let artifact = Compiler::new() + .expect("released Workshop contract must load") + .compile_hir(&hir) + .expect("pinned startHoT positional and keyword forms must lower natively"); + + assert_eq!(artifact.wir.rules.len(), 2); + assert_eq!(artifact.wir.rules[0].actions.len(), 1); + assert_eq!(artifact.wir.rules[1].actions.len(), 1); + let mut positional = artifact.wir.clone(); + positional.rules.truncate(1); + let mut keyword = artifact.wir; + keyword.rules.remove(0); + keyword.rules[0].name = positional.rules[0].name.clone(); + assert!( + workshop_rs::roundtrip::equivalent(&positional, &keyword), + "keyword arguments must bind to the same Workshop arguments as positional calls" + ); +} + #[test] fn stop_chasing_variable_dispatches_by_variable_kind() { let source = "globalvar g\nplayervar p\nrule \"r\":\n @Event eachPlayer\n stopChasingVariable(g)\n stopChasingVariable(eventPlayer.p)\n";