From 6371f3d70a3a28185b133bcaccb1a7fed7e0616b Mon Sep 17 00:00:00 2001 From: Teakowa Date: Sun, 30 Aug 2026 02:57:33 +0800 Subject: [PATCH] fix(wir): remove provider helper action carriers Fixes #129 --- crates/workshop-rs/src/element_count.rs | 21 +------- crates/workshop-rs/src/emitter.rs | 58 --------------------- crates/workshop-rs/src/semantic.rs | 4 +- crates/workshop-rs/src/validate.rs | 4 +- crates/workshop-rs/src/wir/dump.rs | 10 ---- crates/workshop-rs/src/wir/mod.rs | 13 +---- crates/workshop-rs/src/wir/validate.rs | 2 - crates/workshop-rs/tests/element_count.rs | 27 +--------- crates/workshop-rs/tests/emitter.rs | 62 +++++++++-------------- crates/workshop-rs/tests/roundtrip.rs | 10 ++-- crates/workshop-rs/tests/wir_expansion.rs | 9 ++-- docs/element-count.md | 8 ++- docs/implementation-role.md | 10 ++++ docs/provenance.md | 12 +++++ 14 files changed, 67 insertions(+), 183 deletions(-) diff --git a/crates/workshop-rs/src/element_count.rs b/crates/workshop-rs/src/element_count.rs index b77b9e1..afe7e7c 100644 --- a/crates/workshop-rs/src/element_count.rs +++ b/crates/workshop-rs/src/element_count.rs @@ -103,9 +103,8 @@ impl Program { /// Count the canonical Workshop target represented by this WIR program. /// /// The catalog is used to reject unknown action/value identities before a - /// report is produced. Presentation-only `Debug` and `Print` WIR nodes - /// are intentionally rejected because their emitted HUD expansion is not - /// yet represented as canonical WIR actions. + /// report is produced. Native display actions are represented by their + /// canonical catalog-backed action calls. pub fn element_count( &self, catalog: &Catalog, @@ -341,22 +340,6 @@ impl Counter<'_> { children.push(self.action(*nested)?.node); } } - Action::Debug { .. } => { - return Err(ElementCountError::Unsupported { - kind: ElementNodeKind::Action, - name: "debug".to_string(), - span, - reason: "the emitter expands Debug into a HUD action; count the canonical HUD action instead".to_string(), - }); - } - Action::Print { .. } => { - return Err(ElementCountError::Unsupported { - kind: ElementNodeKind::Action, - name: "print".to_string(), - span, - reason: "the emitter expands Print into a HUD action; count the canonical HUD action instead".to_string(), - }); - } Action::Call { name: action_name, args, diff --git a/crates/workshop-rs/src/emitter.rs b/crates/workshop-rs/src/emitter.rs index 02eb72f..2219e73 100644 --- a/crates/workshop-rs/src/emitter.rs +++ b/crates/workshop-rs/src/emitter.rs @@ -873,16 +873,6 @@ impl Emitter<'_> { let end = self.spelling(Kind::Structural, "end")?; self.line(level, &format!("{end};"))?; } - wir::Action::Debug { value, .. } => { - // `debug(value)` displays the value as HUD text. The - // reference formats values with type-aware machinery; Wright - // emits a semantically equivalent but presentation-simpler - // Create HUD Text (documented intentional difference). - self.emit_hud_text(*value, level, true)?; - } - wir::Action::Print { message, .. } => { - self.emit_hud_text(*message, level, false)?; - } wir::Action::AssignMember { target, op, value, .. } => { @@ -1042,54 +1032,6 @@ impl Emitter<'_> { Ok(()) } - /// Emit a `debug`/`print` action as a `Create HUD Text` effect. - /// - /// `debug` renders the value into the HUD body; `print` renders the - /// message directly (a `format` value already carries the text). Every - /// fixed token resolves through the catalog, so the effect is - /// locale-correct by data and fails explicitly on missing target-locale - /// mappings. - fn emit_hud_text(&mut self, value: wir::ValueId, level: usize, is_debug: bool) -> Result<()> { - let mut body = String::new(); - if is_debug { - // Display the value in the HUD body: Custom String("{0}", value). - body.push_str(&self.spelling(Kind::Value, "customString")?); - body.push_str("(\"{0}\", "); - self.value(value, &mut body)?; - body.push(')'); - } else { - self.value(value, &mut body)?; - } - // Create HUD Text(All Players(All Teams), Null, header, body, text, - // location, sort order, header color, subheader color, text color, - // reevaluation, spectators) — the canonical catalog layout (probe P6 - // emission), so the emitted text reparses against the catalog's - // expected enum domains at the canonical positions. - let mut line = String::new(); - line.push_str(&self.spelling(Kind::Action, "createHudText")?); - line.push('('); - line.push_str(&self.spelling(Kind::Value, "allPlayers")?); - line.push('('); - line.push_str(&self.enum_spelling("Team", "ALL")?); - line.push_str("), Null, "); - line.push_str(&body); - line.push_str(", Null, "); - line.push_str(&self.enum_spelling("HudPosition", "LEFT")?); - line.push_str(", -9999, Color("); - line.push_str(&self.enum_spelling("Color", "WHITE")?); - line.push_str("), Color("); - line.push_str(&self.enum_spelling("Color", "WHITE")?); - line.push_str("), Color("); - line.push_str(&self.enum_spelling("Color", "WHITE")?); - line.push_str("), "); - line.push_str(&self.enum_spelling("HudReeval", "VISIBILITY_AND_STRING")?); - line.push_str(", "); - line.push_str(&self.enum_spelling("SpecVisibility", "VISIBLE_ALWAYS")?); - line.push_str(");"); - self.line(level, &line)?; - Ok(()) - } - fn args(&mut self, args: &[wir::ValueId], out: &mut String) -> Result<()> { for (index, arg) in args.iter().enumerate() { if index > 0 { diff --git a/crates/workshop-rs/src/semantic.rs b/crates/workshop-rs/src/semantic.rs index 63b5df7..9000f89 100644 --- a/crates/workshop-rs/src/semantic.rs +++ b/crates/workshop-rs/src/semantic.rs @@ -180,9 +180,7 @@ fn inspect_action( | Action::SetPlayerVariable { .. } | Action::ModifyPlayerVariable { .. } | Action::AssignMember { .. } - | Action::CallSubroutine { .. } - | Action::Debug { .. } - | Action::Print { .. } => {} + | Action::CallSubroutine { .. } => {} } } diff --git a/crates/workshop-rs/src/validate.rs b/crates/workshop-rs/src/validate.rs index 1ccdfc4..b41fe6f 100644 --- a/crates/workshop-rs/src/validate.rs +++ b/crates/workshop-rs/src/validate.rs @@ -130,9 +130,7 @@ fn validate_action( } } wir::Action::SetGlobalVariable { value, .. } - | wir::Action::ModifyGlobalVariable { value, .. } - | wir::Action::Debug { value, .. } - | wir::Action::Print { message: value, .. } => { + | wir::Action::ModifyGlobalVariable { value, .. } => { validate_value(program, catalog, *value, errors); } wir::Action::SetPlayerVariable { player, value, .. } diff --git a/crates/workshop-rs/src/wir/dump.rs b/crates/workshop-rs/src/wir/dump.rs index 310bb66..15fc2c1 100644 --- a/crates/workshop-rs/src/wir/dump.rs +++ b/crates/workshop-rs/src/wir/dump.rs @@ -311,16 +311,6 @@ fn render_action(program: &Program, id: super::ActionId, out: &mut String, level render_action(program, *action, out, level + 1); } } - Action::Debug { value, span } => { - out.push_str(&format!("{}debug ", indent(level))); - render_value(program, *value, out); - out.push_str(&format!("{}\n", span_suffix(*span))); - } - Action::Print { message, span } => { - out.push_str(&format!("{}print ", indent(level))); - render_value(program, *message, out); - out.push_str(&format!("{}\n", span_suffix(*span))); - } Action::Call { name, args, span } => { out.push_str(&format!("{}call {name}(", indent(level))); for (index, arg) in args.iter().enumerate() { diff --git a/crates/workshop-rs/src/wir/mod.rs b/crates/workshop-rs/src/wir/mod.rs index 1f93d12..c7afd56 100644 --- a/crates/workshop-rs/src/wir/mod.rs +++ b/crates/workshop-rs/src/wir/mod.rs @@ -7,9 +7,7 @@ //! //! Name policy: call/value `name` fields keep the canonical catalog ids //! (`countOf`, `wait`, `createBeamEffect`); mapping those to localized -//! Workshop presentation spellings is an emission concern. `debug` and -//! `print` are represented as dedicated [`Action::Debug`]/[`Action::Print`] -//! nodes. +//! Workshop presentation spellings is an emission concern. //! //! Extracted from the Wright-authored `wright-ir` crate (the `wir`, //! `settings`, and `source` modules); see @@ -399,13 +397,6 @@ pub enum Action { body: Vec, span: Option, }, - /// The `debug(value)` HUD debug effect. - Debug { value: ValueId, span: Option }, - /// The `print(message)` HUD message effect. - Print { - message: ValueId, - span: Option, - }, /// Any other action call with side effects. Call { name: String, @@ -428,8 +419,6 @@ impl Action { | Action::While { span, .. } | Action::ForGlobalVariable { span, .. } | Action::ForPlayerVariable { span, .. } - | Action::Debug { span, .. } - | Action::Print { span, .. } | Action::Call { span, .. } => *span, } } diff --git a/crates/workshop-rs/src/wir/validate.rs b/crates/workshop-rs/src/wir/validate.rs index 1f05a5b..0221f5c 100644 --- a/crates/workshop-rs/src/wir/validate.rs +++ b/crates/workshop-rs/src/wir/validate.rs @@ -180,8 +180,6 @@ fn check_action(program: &Program, id: super::ActionId) -> Result<(), IrError> { } Ok(()) } - Action::Debug { value, .. } => check_value(program, *value), - Action::Print { message, .. } => check_value(program, *message), Action::Call { args, .. } => { for arg in args { check_value(program, *arg)?; diff --git a/crates/workshop-rs/tests/element_count.rs b/crates/workshop-rs/tests/element_count.rs index 81edc80..ee41643 100644 --- a/crates/workshop-rs/tests/element_count.rs +++ b/crates/workshop-rs/tests/element_count.rs @@ -1,6 +1,6 @@ use workshop_rs::catalog::{Catalog, Locale}; use workshop_rs::convert::{self, ConvertOptions}; -use workshop_rs::element_count::{ElementCountError, ElementNodeKind}; +use workshop_rs::element_count::ElementNodeKind; use workshop_rs::parser; use workshop_rs::settings::{Settings, SettingsNode}; use workshop_rs::source::SourceFile; @@ -186,31 +186,6 @@ fn representative_corpus_program_produces_a_report() { assert!(report.total > 2); } -#[test] -fn debug_reports_an_explicit_incomplete_surface() { - let mut program = Program::default(); - let value = number(&mut program, 1.0); - let action = program.actions.push(Action::Debug { value, span: None }); - program.rules.push(Rule { - name: "debug".to_string(), - span: None, - name_span: None, - disabled: false, - event: Event::Global, - conditions: vec![], - actions: vec![action], - }); - - assert!(matches!( - program.element_count(&catalog()), - Err(ElementCountError::Unsupported { - kind: ElementNodeKind::Action, - name, - .. - }) if name == "debug" - )); -} - fn attach_value(mut program: Program, value: wir::ValueId) -> Program { let variable = program.global_variables.push(wir::WorkshopVariable { name: "result".to_string(), diff --git a/crates/workshop-rs/tests/emitter.rs b/crates/workshop-rs/tests/emitter.rs index 0bede97..c177789 100644 --- a/crates/workshop-rs/tests/emitter.rs +++ b/crates/workshop-rs/tests/emitter.rs @@ -236,44 +236,32 @@ fn emitted_condition_matches_reference_infix_form() { } #[test] -fn debug_actions_emit_hud_text() { - // Debug/Print emit a semantically equivalent Create HUD Text effect - // (documented intentional difference from the reference's type-aware - // formatting). - let mut program = wir::Program::default(); - let file = program - .files - .push(workshop_rs::source::SourceFile::new("workshop.txt")); - let value = program.values.push(workshop_rs::wir::ValueNode::new( - workshop_rs::wir::Value::Number { - value: 1.0, - text: "1".to_string(), - }, - None, - )); - let debug = program - .actions - .push(wir::Action::Debug { value, span: None }); - program.rules.push(wir::Rule { - name: "x".into(), - span: None, - name_span: None, - disabled: false, - event: wir::Event::Global, - conditions: vec![], - actions: vec![debug], - }); - let _ = file; - let emitted = emitter::emit(&program, &catalog(), &en()).expect("Debug emits"); - assert!( - emitted.contains("Create HUD Text(All Players(All Teams), Null, Custom String(\"{0}\", 1)"), - "debug emits the value as HUD text:\n{emitted}" - ); - // The emitted text reparses to a createHudText action call. +fn native_hud_actions_use_the_generic_catalog_path() { let catalog = catalog(); - let reparsed = - workshop_rs::parser::parse_with_context(&emitted, &catalog, &en(), &catalog).unwrap(); - assert_eq!(reparsed.rules.len(), 1); + let source = r#" + variables { global: 0: value } + rule ("hud") { + event { Ongoing - Global; } + actions { + Create HUD Text(All Players(All Teams), Null, Null, Custom String("{0}", Global.value), Left, 0, Null, Null, Null, Visible To and String, Default Visibility); + } + } + "#; + let program = parser::parse_with_context(source, &catalog, &en(), &catalog) + .expect("native HUD action parses"); + let rule = program.rules.iter().next().expect("HUD rule"); + assert!(matches!( + program.actions.get(rule.actions[0]), + Some(wir::Action::Call { name, args, .. }) + if name == "createHudText" && args.len() == 11 + )); + workshop_rs::validate::validate_canonical_ids(&program, &catalog) + .expect("native HUD action validates"); + + let emitted = emitter::emit(&program, &catalog, &en()).expect("native HUD action emits"); + let reparsed = parser::parse_with_context(&emitted, &catalog, &en(), &catalog) + .expect("emitted HUD action reparses"); + assert!(workshop_rs::roundtrip::equivalent(&program, &reparsed)); } #[test] diff --git a/crates/workshop-rs/tests/roundtrip.rs b/crates/workshop-rs/tests/roundtrip.rs index 4efab80..00b995c 100644 --- a/crates/workshop-rs/tests/roundtrip.rs +++ b/crates/workshop-rs/tests/roundtrip.rs @@ -139,12 +139,14 @@ fn equivalence_detects_semantic_differences() { }, None, )); - a.actions.push(workshop_rs::wir::Action::Debug { - value: value_a, + a.actions.push(workshop_rs::wir::Action::Call { + name: "wait".into(), + args: vec![value_a], span: None, }); - b.actions.push(workshop_rs::wir::Action::Debug { - value: value_b, + b.actions.push(workshop_rs::wir::Action::Call { + name: "wait".into(), + args: vec![value_b], span: None, }); a.rules.push(workshop_rs::wir::Rule { diff --git a/crates/workshop-rs/tests/wir_expansion.rs b/crates/workshop-rs/tests/wir_expansion.rs index e02e927..f31eb7d 100644 --- a/crates/workshop-rs/tests/wir_expansion.rs +++ b/crates/workshop-rs/tests/wir_expansion.rs @@ -317,18 +317,19 @@ fn build_surface_program() -> wir::Program { Some(s(6, 14, 24)), )); - let debug_value = program.values.push(ValueNode::new( + let wait_duration = program.values.push(ValueNode::new( Value::Number { value: 1.0, text: "1".to_string(), }, Some(s(7, 11, 12)), )); - let debug = program.actions.push(Action::Debug { - value: debug_value, + let wait = program.actions.push(Action::Call { + name: "wait".into(), + args: vec![wait_duration], span: Some(s(7, 9, 13)), }); - let if_body = vec![debug]; + let if_body = vec![wait]; let if_action = program.actions.push(Action::If { branches: vec![wir::IfBranch { condition: compare, diff --git a/docs/element-count.md b/docs/element-count.md index ba6b7d5..7358110 100644 --- a/docs/element-count.md +++ b/docs/element-count.md @@ -28,16 +28,14 @@ The calculator is locale-independent: it reads canonical WIR identities and never emitted spellings. It validates WIR and catalog identities before producing a report. Unknown or unsupported constructs return `ElementCountError` instead of yielding a misleading exact total. In -particular, the current `Debug` and `Print` nodes are presentation helpers that -the emitter expands into `Create HUD Text`; callers should count that canonical -action until a reviewed expansion contract is added. +Native display actions such as `Create HUD Text` are counted through their +canonical catalog-backed action calls. The independent behavioral source for the supported rules is the [Workshop.codes element-count calculation reference](https://workshop.codes/wiki/articles/element-count-calculation). Known evidence gap: this initial API does not claim live-client/editor -validation, source-language debug-count compatibility, or an exact count for -presentation-only WIR helpers. Those belong to later +validation or source-language debug-count compatibility. Those belong to later client-backed/consumer integration work after the canonical WIR surface is stable. The current real-project `rework.ow` fixture still stops in the parser on an ambiguous bare `None` enum spelling, so it is not counted as a passing diff --git a/docs/implementation-role.md b/docs/implementation-role.md index 994aef3..6b12cb4 100644 --- a/docs/implementation-role.md +++ b/docs/implementation-role.md @@ -78,6 +78,16 @@ If no lossless lowering exists, `opy-rs` must report its explicit integration boundary rather than extending this Workshop contract with source-language carriers. +## Source-language display helpers + +`Debug` and `Print` are not native Workshop action identities. The independent +[Workshop.codes action inventory](https://workshop.codes/wiki/categories/actions) +lists the native display operation as [`Create HUD Text`](https://workshop.codes/wiki/articles/create-hud-text), +whose documented parameters and persistent HUD behavior are represented by the +catalog-backed `createHudText` action call. Source-language helpers such as +OverPy `debug(...)` and `print(...)` therefore remain provider-owned lowering +concerns; canonical WIR does not carry dedicated variants for them. + This classifies the remaining interoperability probes without using OPY syntax as Workshop evidence: the dictionary-literal probe is an OPY-owned lowering gap unless it folds to one of the native indexed/member forms, while the diff --git a/docs/provenance.md b/docs/provenance.md index 41788d0..5e4e9e5 100644 --- a/docs/provenance.md +++ b/docs/provenance.md @@ -69,6 +69,18 @@ license, reviewed) is embedded in the dataset itself and surfaced by | Action/Value signature cross-check | Representative Workshop.codes article links remain recorded in the catalog provenance; fetched snapshots and acceptance results are CI evidence, not generator or runtime inputs. | | Settings emission table (`src/settings/table.rs` and generated data files) | Hand-written fixture surface plus the reviewed `workshop-data` export at commit `d854bf01fc7bbf3b2169f67408c07a8da8989ad6`; generated entries, names, locale mappings, and source paths are committed together in the declared multi-locale projection, while pinned OverPy 9.7.10 output remains the behavioral check (classes 1/5). | +### Native display action boundary (`workshop-rs#129`) + +The independent [Workshop.codes action inventory](https://workshop.codes/wiki/categories/actions) +contains no native `Debug` or `Print` action, while its [`Create HUD Text`] +article documents the native display action, its eleven parameters, and its +persistent HUD behavior. The catalog therefore represents HUD output through +the canonical `createHudText` action call. OverPy `debug(...)` and `print(...)` +remain source-language helpers whose lowering belongs to their provider, not to +canonical WIR. + +[`Create HUD Text`]: https://workshop.codes/wiki/articles/create-hud-text + ### Locale coverage * `en-US` is the primary locale and is complete. The committed catalog