diff --git a/crates/workshop-rs/src/catalog/data/catalog.json b/crates/workshop-rs/src/catalog/data/catalog.json index 1dd4e5de..fae82ec5 100644 --- a/crates/workshop-rs/src/catalog/data/catalog.json +++ b/crates/workshop-rs/src/catalog/data/catalog.json @@ -7995,7 +7995,7 @@ ] } ], - "digest": "b436f6107f2c77cc39abc9dba74780a9a85facebd7aaaec4f87437a89b9d4655", + "digest": "2d98f50b523e38bfbff59a952c0e4c3c80e8c7f972bb9b30a81fdabc0c46de19", "enums": [ { "domain": "Impulse", @@ -23793,7 +23793,7 @@ "array" ], "paramTypes": [ - "Array|EntityId" + "Any" ], "params": [ "Array" diff --git a/crates/workshop-rs/tests/contextual_semantics.rs b/crates/workshop-rs/tests/contextual_semantics.rs index 8548b272..1baf4d28 100644 --- a/crates/workshop-rs/tests/contextual_semantics.rs +++ b/crates/workshop-rs/tests/contextual_semantics.rs @@ -150,6 +150,36 @@ fn boolean_parameters_accept_any_value_but_keep_structural_checks() { .expect_err("the accepted value itself is still validated structurally"); } +#[test] +fn first_of_wrapper_accepts_any_value_and_round_trips() { + let values = [ + "Vector(1, 2, 3)", + "Hero(Ana)", + "Team Of(Event Player)", + "Map(Hanamura)", + "Color(Red)", + "Button(Jump)", + "Game Mode(Skirmish)", + ]; + for value in values { + let source = format!( + "variables\n{{\n global:\n 0: probe\n}}\nrule (\"wrapper\")\n{{\n event {{ Ongoing - Global; }}\n actions {{ Wait Until(First Of({value}), 2); }}\n}}" + ); + let reparsed = roundtrip::round_trip_with_context( + &source, + &catalog(), + &Locale::new("en-US"), + &catalog(), + ); + assert!(reparsed.equivalent, "First Of({value}) must round-trip"); + validate_program(&program(&format!("Wait Until(First Of({value}), 2);"))); + } + + let arity = program("Wait Until(First Of(Vector(1, 2, 3), 1), 2);"); + validate::validate_canonical_ids_wir(&arity, &catalog()) + .expect_err("First Of arity is still checked"); +} + #[test] fn null_and_empty_string_contexts_normalize_at_their_positions() { let teleport = program("Teleport(Event Player, 0);"); diff --git a/crates/workshop-rs/tests/identity.rs b/crates/workshop-rs/tests/identity.rs index 3a0d1533..f799c903 100644 --- a/crates/workshop-rs/tests/identity.rs +++ b/crates/workshop-rs/tests/identity.rs @@ -12,7 +12,7 @@ use workshop_rs::catalog::{Catalog, Locale}; /// (`workshop-catalog-gen build`) recomputes it and the pin is updated /// deliberately together with the data. const PINNED_CATALOG_DIGEST: &str = - "b436f6107f2c77cc39abc9dba74780a9a85facebd7aaaec4f87437a89b9d4655"; + "2d98f50b523e38bfbff59a952c0e4c3c80e8c7f972bb9b30a81fdabc0c46de19"; #[test] fn committed_catalog_digest_is_pinned() { diff --git a/docs/language-support/values.md b/docs/language-support/values.md index 7d36bc3a..48350232 100644 --- a/docs/language-support/values.md +++ b/docs/language-support/values.md @@ -80,7 +80,7 @@ This document inventories canonical Workshop values and expressions in `workshop | `Farthest Player From` | ✅ Supported | Returns: `Player`; Parameters: (Position: Vector, Team: Team). | | `False` | ✅ Supported | Returns: `BoolLiteral`. | | `Filtered Array` | ✅ Supported | Returns: `Array`; Parameters: (Array: Array, Condition: Boolean). | -| `First Of` | ✅ Supported | Returns: `Object|Array`; Parameters: (Array: Array|EntityId). | +| `First Of` | ✅ Supported | Returns: `Object|Array`; Parameters: (Array: Any). | | `Flag Position` | ✅ Supported | Returns: `Vector`; Parameters: (Team: Team). | | `Forward` | ✅ Supported | Returns: `Array`. | | `Game Mode` | ✅ Supported | Returns: `Gamemode`; Parameters: (Gamemode: Gamemode). | diff --git a/docs/source-attribution/catalog.md b/docs/source-attribution/catalog.md index b5c99d15..32f88d49 100644 --- a/docs/source-attribution/catalog.md +++ b/docs/source-attribution/catalog.md @@ -20,6 +20,7 @@ is embedded in the dataset and surfaced by `workshop-rs-cli version --json`. | OSTW-exercised params/spellings and enum domains (CreateEffect, Workshop Setting, Hero/Map/Button/Icon/Operation/Rounding/InworldTextRev, …) | Pinned OSTW v3.4.0 reference probe emissions (P4/P5/P6/P6b) and the protect-ban entry-point reachable closure. | | Parameter metadata (`paramDomains`, `paramDefaults`, `paramCoercions`) | Pinned-reference probe data, never copied from upstream game data. Contextual literal substitutions are recorded per parameter position and cross-checked against [OverPy's replacement metadata](https://github.com/Zezombye/overpy/blob/2002431649cbdd7ddc3aa70bd184b598ec2820e5/src/types.d.ts), [Workshop emission path](https://github.com/Zezombye/overpy/blob/2002431649cbdd7ddc3aa70bd184b598ec2820e5/src/compiler/astToWorkshop.ts), and [Wait Until handling](https://github.com/Zezombye/overpy/blob/2002431649cbdd7ddc3aa70bd184b598ec2820e5/src/compiler/functions/waitUntil.ts); they do not establish global type coercions. | | Boolean parameter acceptance (typed rule in `values/validate.rs`, not catalog data) | [ADR-0014](../adr/0014-validation-evidence-for-slot-acceptance.md): OverPy wraps values in `First Of` keyed on the `Boolean` parameter type ([`astToWorkshop.ts`](https://github.com/Zezombye/overpy/blob/1e2688954302a402d076944b46db07efb14d7b61/src/compiler/astToWorkshop.ts)), and the fix for Zezombye/overpy#182 states every value can be put in a Boolean field. The values the client rejects there are not modelled. | +| `First Of` argument (`paramTypes[0]`: `Any`) | [ADR-0014](../adr/0014-validation-evidence-for-slot-acceptance.md): pinned OverPy 9.7.10 wraps a value the client rejects in a Boolean parameter as `First Of()` ([`astToWorkshop.ts`](https://github.com/Zezombye/overpy/blob/1e2688954302a402d076944b46db07efb14d7b61/src/compiler/astToWorkshop.ts)), so the wrapper's argument is any value (Vector, Hero, Team, Map, Color, Button, Game Mode, …). The rule is keyed on the wrapped form, not on an array; the `Array|EntityId` declaration was a presumption only. | | Localized parameter labels (`paramAliases`) | Separately reviewed owner mappings, stored per declared parameter position and resolved only through the catalog API. The initial `Wait` `zh-CN` labels are `时间` and `等待行为`, as identified by the documented Chinese Workshop action reference at https://overwatch.huijiwiki.com/wiki/%E5%9C%B0%E5%9B%BE%E5%B7%A5%E5%9D%8A/%E5%8A%A8%E4%BD%9C (accessed 2026-09-11); the canonical parameter order and descriptions are cross-checked against the pinned `workshop-data` action definition. They are not generated by the general zh-CN corpus pipeline. | | Action/Value parameter and return signatures | Workshop.codes structured article properties (Returns, Parameters, Type, and Default), cross-checked against the pinned OverPy metadata and static OSTW data; entries without convergent sources remain explicitly unresolved. Representative article links are recorded with the catalog entries. | | `Create Dummy Bot` hero parameter union (`Hero|Array`) | Workshop behavior accepts a single Hero or Hero array and selects randomly when multiple heroes are provided; pinned OverPy 9.7.10 texture-tag setup emits `getAllHeroes()` into `Create Dummy Bot`. |