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:
@@ -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)
|
||||
|
||||
@@ -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<()> {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user