the inspect and talent windows are the reference's own

- the inspect and talent windows are the reference's own: `Blizzard_InspectUI` and `Blizzard_TalentUI` are LoadOnDemand addons whose real `.xml`/`.lua` live inside `patch.MPQ` — the loose `Interface\AddOns` folder holds only a `.pub` signature, which three notes in this tree had read as "packed, no source to migrate from", one wrong inference that kept five windows on hand-written transcriptions; 2300 lines of ours out, no engine work needed, and three things our talent transcription had quietly invented fell out (there was no level-10 gate, every tree scrolled where a shallow one does not, and a nil `TalentMicroButton` threw out of OnShow before the window painted)
- a nil unit token RAISES in 53 of 83 bindings: wow-re censused the registrar table at `0x850438` — `UnitName(nil)` raises and `UnitIsVisible(nil)` is quiet, one line apart in stock files, so there is no rule to derive, the table is the rule; ours answered a default and our own test had pinned it; both token positions of the seven two-token predicates are gated, `GetRaidTargetIndex` too, and `UnitIsAFK`/`UnitIsDND` are confirmed not to exist in 5875 at all (zero occurrences under a positive control) — we invented them, and they stay deliberately ungated
- `EditBox:SetNumber` did not exist (the only chain file calling it is `MoneyInputFrame.lua`, on the chain since the money kit): it is byte-identical to `SetText`, formats through `%.14g` in MSVC's CRT (`1e+020`, `1.#INF`), and `OnTextChanged` never fires from an edit — it fires from the dirty-word drain inside the box's own OnUpdate, coalesced to one per box per frame, where ours fired synchronously and `MoneyInputFrame_SetCopper` raised on it; `PanelTemplates_TabResize`'s `tab` argument is optional as the reference's is (stock macro tabs call it with `0`)
- the skull or star on your CURRENT target never appeared or cleared until you re-targeted — `RAID_TARGET_UPDATE` was never fired, and stock `TargetFrame.lua` hangs its repaint off it; a raid-to-party conversion now empties the mark board as `0x4ba550` does; and the group-loot roll popups were already on the chain inside stock `LootFrame.xml` — our 546-line `GroupLootFrame.xml` had been shadowing all four of them

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-02 17:06:18 +07:00
co-authored by Claude Fable 5.1
parent 276d0896ad
commit eb554c0957
41 changed files with 1400 additions and 3134 deletions
+217 -30
View File
@@ -72,8 +72,11 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// `lua_isstring` gate admits tag 3 and hands the resolver `"5"`). `check_unit_token` inside
// `with_unit` is the second of those; the first is the `?` on the argument type below.
// The gate is the BINDING's, not `with_unit`'s: `check_unit_token` lets a nil through by
// design, because that is right for `UnitExists`, `UnitIsVisible` and most of this family. It
// is wrong for exactly these two, so the shape-A test happens here, before the shared path.
// design, because that is right for `UnitExists`, `UnitIsVisible` and eleven others. **"and
// most of this family" is what this comment used to say, and it was the wrong way round** —
// wow-re has since censused all 83 entries of the table at `0x850438` and 53 of them gate and
// raise, with only 13 unit-token bindings quiet (decision 1834). These two were never the
// exception; they were an early instance of the rule.
for (name, usage, by_player) in [
("UnitIsTapped", r#"Usage: UnitIsTapped("unit")"#, false),
(
@@ -151,7 +154,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
g.set(
"UnitName",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitName("unit")"#,
)?);
let name = with_unit(lua, &token, None, |u| u.name.clone())?;
match name {
Some(n) => Ok(Value::String(lua.create_string(&n)?)),
@@ -162,14 +170,24 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
g.set(
"UnitHealth",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitHealth("unit")"#,
)?);
with_unit(lua, &token, 0i64, |u| i64::from(u.health))
})?,
)?;
g.set(
"UnitHealthMax",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitHealthMax("unit")"#,
)?);
with_unit(lua, &token, 0i64, |u| i64::from(u.max_health))
})?,
)?;
@@ -183,7 +201,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// `b` INFERRED and 0 in normal play — it can never drive the value ≤ 0 anyway).
g.set(
"UnitLevel",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitLevel("unit")"#,
)?);
check_unit_token(&token)?;
let model = lua.app_data_ref::<Model>().expect("model app_data");
let Some(u) = token.as_ref().and_then(|t| model.unit(t)) else {
@@ -223,7 +246,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// (`UnitCanAttack("player", "target")`).
g.set(
"UnitCanAttack",
lua.create_function(|lua, (a, b): (Option<String>, Option<String>)| {
lua.create_function(|lua, (a, b): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let a = Some(crate::script::binding_abi::string_arg(
lua,
a,
r#"Usage: UnitCanAttack("unit", "otherUnit")"#,
)?);
let b = Some(crate::script::binding_abi::string_arg(
lua,
b,
r#"Usage: UnitCanAttack("unit", "otherUnit")"#,
)?);
let token = pick_unit_token(&a, &b);
Ok(if with_unit(lua, &token, false, |u| u.can_attack)? {
Value::Integer(1)
@@ -247,7 +282,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
g.set(
"UnitIsDead",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsDead("unit")"#,
)?);
with_unit(lua, &token, false, |u| u.dead)
})?,
)?;
@@ -256,13 +296,23 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// so IsDead is false for it and the popup flow branches on all three.
g.set(
"UnitIsGhost",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsGhost("unit")"#,
)?);
with_unit(lua, &token, false, |u| u.ghost)
})?,
)?;
g.set(
"UnitIsDeadOrGhost",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsDeadOrGhost("unit")"#,
)?);
with_unit(lua, &token, false, |u| u.dead || u.ghost)
})?,
)?;
@@ -274,7 +324,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// frame paints its name plate blue then, exactly like the reference.
g.set(
"UnitReaction",
lua.create_function(|lua, (token, _other): (Option<String>, Option<String>)| {
lua.create_function(|lua, (token, _other): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitReaction("unit", "otherUnit")"#,
)?);
let _other = Some(crate::script::binding_abi::string_arg(
lua,
_other,
r#"Usage: UnitReaction("unit", "otherUnit")"#,
)?);
let r = with_unit(lua, &token, 0u8, |u| u.reaction)?;
Ok(if r == 0 {
Value::Nil
@@ -294,7 +356,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// reaction (0) → nil for both, the API's "can't tell". `1`/nil returns, era-style.
g.set(
"UnitIsEnemy",
lua.create_function(|lua, (a, b): (Option<String>, Option<String>)| {
lua.create_function(|lua, (a, b): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let a = Some(crate::script::binding_abi::string_arg(
lua,
a,
r#"Usage: UnitIsEnemy("unit", "otherUnit")"#,
)?);
let b = Some(crate::script::binding_abi::string_arg(
lua,
b,
r#"Usage: UnitIsEnemy("unit", "otherUnit")"#,
)?);
let token = pick_unit_token(&a, &b);
let r = with_unit(lua, &token, 0u8, |u| u.reaction)?;
Ok(if (1..=2).contains(&r) {
@@ -306,7 +380,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
)?;
g.set(
"UnitIsFriend",
lua.create_function(|lua, (a, b): (Option<String>, Option<String>)| {
lua.create_function(|lua, (a, b): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let a = Some(crate::script::binding_abi::string_arg(
lua,
a,
r#"Usage: UnitIsFriend("unit", "otherUnit")"#,
)?);
let b = Some(crate::script::binding_abi::string_arg(
lua,
b,
r#"Usage: UnitIsFriend("unit", "otherUnit")"#,
)?);
let token = pick_unit_token(&a, &b);
let r = with_unit(lua, &token, 0u8, |u| u.reaction)?;
Ok(if r >= 5 {
@@ -335,7 +421,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// target plate disagree about the same mob.
g.set(
"UnitIsCivilian",
lua.create_function(|lua, unit: Option<String>| {
lua.create_function(|lua, unit: Value| {
let unit = Some(crate::script::binding_abi::string_arg(
lua,
unit,
r#"Usage: UnitIsCivilian("unit")"#,
)?);
let model = lua.app_data_ref::<Model>().expect("model app_data");
let player_level = model.player_req.level;
Ok(match unit.and_then(|u| model.unit(&u)) {
@@ -374,7 +465,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// is trivially the same unit; otherwise both snapshots must carry a real (nonzero) guid.
g.set(
"UnitIsUnit",
lua.create_function(|lua, (a, b): (Option<String>, Option<String>)| {
lua.create_function(|lua, (a, b): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let a = Some(crate::script::binding_abi::string_arg(
lua,
a,
r#"Usage: UnitIsUnit("unit", "otherUnit")"#,
)?);
let b = Some(crate::script::binding_abi::string_arg(
lua,
b,
r#"Usage: UnitIsUnit("unit", "otherUnit")"#,
)?);
// BOTH arguments go through the resolver, so either being unrecognised raises.
check_unit_token(&a)?;
check_unit_token(&b)?;
@@ -494,7 +597,19 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// whisper a same-faction player), without the faction machinery the engine doesn't carry.
g.set(
"UnitCanCooperate",
lua.create_function(|lua, (a, b): (Option<String>, Option<String>)| {
lua.create_function(|lua, (a, b): (Value, Value)| {
// BOTH positions carry an `lua_isstring` gate — two sites in the body —
// so either argument being nil raises (decision 1836).
let a = Some(crate::script::binding_abi::string_arg(
lua,
a,
r#"Usage: UnitCanCooperate("unit", "otherUnit")"#,
)?);
let b = Some(crate::script::binding_abi::string_arg(
lua,
b,
r#"Usage: UnitCanCooperate("unit", "otherUnit")"#,
)?);
let token = pick_unit_token(&a, &b);
let ok = with_unit(lua, &token, false, |u| {
u.exists && u.is_player && u.reaction >= 5
@@ -507,7 +622,14 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// fed per token).
g.set(
"GetRaidTargetIndex",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
// Gated (`0x4bb4be` → `0x4bb4cd`), and the usage string names the
// argument unquoted — the reference's own spelling. Decision 1836.
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
"Usage: GetRaidTargetIndex(unit)",
)?);
let idx = with_unit(lua, &token, 0u8, |u| u.raid_target)?;
Ok(if idx > 0 {
Value::Integer(i64::from(idx))
@@ -522,7 +644,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// stated v1 shortcut, module doc), these are new and follow the era shape from the start.
g.set(
"UnitIsConnected",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsConnected("unit")"#,
)?);
Ok(if with_unit(lua, &token, false, |u| u.is_connected)? {
Value::Integer(1)
} else {
@@ -554,7 +681,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// two callers) — see the field doc.
g.set(
"UnitIsPVP",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsPVP("unit")"#,
)?);
Ok(if with_unit(lua, &token, false, |u| u.pvp)? {
Value::Integer(1)
} else {
@@ -564,7 +696,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
)?;
g.set(
"UnitIsPVPFreeForAll",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitIsPVPFreeForAll("unit")"#,
)?);
Ok(if with_unit(lua, &token, false, |u| u.is_pvp_ffa)? {
Value::Integer(1)
} else {
@@ -588,7 +725,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// files are on our chain.
g.set(
"UnitFactionGroup",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitFactionGroup("unit")"#,
)?);
let pair = with_unit(lua, &token, (None, None), |u| {
(u.faction_group.clone(), u.faction_group_localized.clone())
})?;
@@ -615,7 +757,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// reports nil, nil — the live API's shape for an absent unit.
g.set(
"UnitRace",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitRace("unit")"#,
)?);
let pair = with_unit(lua, &token, None, |u| {
u.race.clone().zip(u.race_file.clone())
})?;
@@ -630,7 +777,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
)?;
g.set(
"UnitClass",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitClass("unit")"#,
)?);
let pair = with_unit(lua, &token, None, |u| {
u.class.clone().zip(u.class_file.clone())
})?;
@@ -667,7 +819,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// The truthy leg is the NUMBER 1, never a boolean; the false leg is nil, never `false`.
g.set(
"UnitHasRelicSlot",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitHasRelicSlot("unit")"#,
)?);
let has = with_unit(lua, &token, false, |u| u.has_relic_slot)?;
Ok(if has { Value::Integer(1) } else { Value::Nil })
})?,
@@ -677,7 +834,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// unit is absent or the sex hasn't streamed (`0`), the API's "can't tell".
g.set(
"UnitSex",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitSex("unit")"#,
)?);
let sex = with_unit(lua, &token, 0u8, |u| u.sex)?;
Ok(if sex == 0 {
Value::Nil
@@ -763,7 +925,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// alt-power color components; addons that read those handle nil).
g.set(
"UnitPowerType",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitPowerType("unit")"#,
)?);
let ty = with_unit(lua, &token, 0u8, |u| u.power_type)?;
Ok((i64::from(ty), power_token(ty).to_string()))
})?,
@@ -782,13 +949,23 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
// have" is spelled `UnitPowerType(unit)` first, which we already provide and which IS 1.12.
g.set(
"UnitMana",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitMana("unit")"#,
)?);
with_unit(lua, &token, 0i64, |u| i64::from(u.power))
})?,
)?;
g.set(
"UnitManaMax",
lua.create_function(|lua, token: Option<String>| {
lua.create_function(|lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitManaMax("unit")"#,
)?);
with_unit(lua, &token, 0i64, |u| i64::from(u.max_power))
})?,
)?;
@@ -800,7 +977,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
let is_player = |token: &Option<String>| token.as_deref() == Some("player");
g.set(
"UnitXP",
lua.create_function(move |lua, token: Option<String>| {
lua.create_function(move |lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitXP("unit")"#,
)?);
let model = lua.app_data_ref::<Model>().expect("model app_data");
Ok(if is_player(&token) {
i64::from(model.player_xp)
@@ -811,7 +993,12 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
)?;
g.set(
"UnitXPMax",
lua.create_function(move |lua, token: Option<String>| {
lua.create_function(move |lua, token: Value| {
let token = Some(crate::script::binding_abi::string_arg(
lua,
token,
r#"Usage: UnitXPMax("unit")"#,
)?);
let model = lua.app_data_ref::<Model>().expect("model app_data");
Ok(if is_player(&token) {
i64::from(model.player_next_level_xp)
+8 -3
View File
@@ -644,9 +644,14 @@ pub(crate) fn token_recognised(token: &str) -> bool {
/// 1203's shape pointed the other way: a failure the client reports, silently swallowed.
///
/// The split is THREE-way, not two, and the two quiet legs are as carved:
/// * **absent argument** — quiet nil (the per-binding argument gates are NOT uniform; `UnitName`
/// has its own `lua_isstring` gate and `UnitExists` has none at all, and only those two poles
/// are verified, so this does not invent a gate for the other ~102);
/// * **absent argument** — quiet nil *here*, because this helper is only the resolver's half.
/// Whether a nil ever reaches it is the BINDING's question, and it is settled per binding:
/// wow-re's census of all 83 entries at `0x850438` found 53 that gate the token position with
/// `lua_isstring` and raise `Usage:`, against 13 unit-token bindings with no gate at all
/// (decision 1834). The gated ones call `binding_abi::string_arg` before they get here, so a
/// nil never arrives; the quiet 13 pass it straight through. This comment used to say the
/// gates were "NOT uniform … only those two poles are verified" and decline to guess, which
/// was the right call at the time — the table now exists, so it is applied rather than feared;
/// * **`""`** — quiet nil;
/// * a **recognised** token naming nothing (`"party5"` solo, `"playerfoo"`) — quiet nil.
pub(crate) fn check_unit_token(token: &Option<String>) -> mlua::Result<()> {
+80 -4
View File
@@ -984,11 +984,32 @@ fn an_unrecognised_unit_token_raises_and_a_recognised_empty_one_does_not() {
implementation that raises on \"did not resolve\" gets wrong."
);
}
// QUIET NIL, leg 3: the empty string and an absent argument. The per-binding argument gates are
// NOT uniform in the client and only two poles are verified, so this asserts the resolver's
// behaviour and does not invent a gate.
// QUIET NIL, leg 3: the empty string — which passes the `lua_isstring` gate (it IS a string)
// and dies quietly in the resolver's own NULL/empty guard.
assert!(s.run(r#"UnitName("")"#).is_ok());
assert!(s.run("UnitName()").is_ok());
// …but an ABSENT or nil argument is NOT leg 3, and this line used to assert that it was.
// `UnitName 0x517020` gates its token position at `0x517048` and raises
// `Usage: UnitName("unit")` (`0x850ee0`); `luaL_error` does not return. The comment this
// replaces said the per-binding gates were "not uniform … only two poles verified", which was
// true when it was written — wow-re has since censused all 83 entries of the table at
// `0x850438`: 53 gate and raise, and only 13 unit-token bindings are quiet. Decision 1834.
assert!(s.run("UnitName()").is_err(), "absent argument raises");
assert!(s.run("UnitName(nil)").is_err(), "nil argument raises");
// A NUMBER passes the gate — `lua_isstring` admits tag 3 — and is handed to the resolver as
// "5", which then raises the family's OTHER message rather than the `Usage:` one.
assert!(
s.run("UnitName(5)").is_err(),
"a number reaches the resolver"
);
// The thirteen that stay quiet, three of them here: no gate at all, so nil is fine.
for quiet in ["UnitExists", "UnitIsVisible", "UnitClassification"] {
assert!(
s.run(&format!("{quiet}()")).is_ok(),
"{quiet} is one of the 13 with no gate — nil must stay quiet"
);
}
// A two-unit call gates BOTH arguments.
assert!(s.run(r#"UnitIsUnit("player", "bogus")"#).is_err());
@@ -1371,3 +1392,58 @@ fn unit_has_relic_slot_answers_one_or_nil() {
"function"
);
}
/// **The two-token predicates gate BOTH positions** — decision 1836, closing the half 1834 left
/// open on purpose.
///
/// 1834 applied the `lua_isstring` gate only where wow-re's partial list named a single-token
/// binding, because a census that finds "a gate somewhere in this body" cannot say *which*
/// argument carries it. The complete 83-row table (`nil-unit-token-arg-law.md` §10) resolves it:
/// these seven each carry **two** `lua_isstring` sites, so either argument being nil raises.
#[test]
fn a_two_token_predicate_raises_on_either_nil_argument() {
let mut s = UiScript::new().unwrap();
s.set_unit("player", Some(player()));
s.set_unit("target", Some(player()));
for verb in [
"UnitIsUnit",
"UnitIsEnemy",
"UnitIsFriend",
"UnitCanCooperate",
"UnitCanAttack",
"UnitReaction",
] {
assert!(
s.run(&format!(r#"{verb}("player", "target")"#)).is_ok(),
"{verb} with both tokens is fine"
);
assert!(
s.run(&format!(r#"{verb}("player")"#)).is_err(),
"{verb} with the SECOND argument absent must raise"
);
assert!(
s.run(&format!(r#"{verb}(nil, "target")"#)).is_err(),
"{verb} with the FIRST argument nil must raise"
);
assert!(s.run(&format!("{verb}()")).is_err(), "{verb} with neither");
}
// `GetRaidTargetIndex` is the one of six previously-unclassified names that takes a token, and
// it is gated. Its usage string names the argument UNQUOTED — the reference's own spelling.
assert!(s.run(r#"GetRaidTargetIndex("player")"#).is_ok());
assert!(s.run("GetRaidTargetIndex()").is_err());
// …while the other five take no argument at all and have no gate. `GetTimeToWellRested` is a
// three-instruction stub in the reference that always pushes nil; computing a value there
// would be divergence, not a feature.
for none in [
"GetQuestGreenRange",
"GetRestState",
"GetXPExhaustion",
"GetTimeToWellRested",
"GetBillingTimeRested",
] {
assert!(s.run(&format!("{none}()")).is_ok(), "{none} takes no token");
}
}