the world map blips are the reference's, and particles go untinted

- the world map speaks the reference's names: WorldMapTooltip is declared (six corpus addons index it; pfUI dies on it at file scope), the blackout frame takes the reference's spelling BlackoutWorld, and the four hand-written party blips become the reference's own unit-blip templates — all 44 blips as instances, each with its published $parentIcon, the name pfUI recolours group blips by class through; the 40 raid ones are declared but not positioned yet, named in the OUT list rather than faked
- the templates addons build their own buttons from are published: ItemButtonTemplate, ContainerFrameItemButtonTemplate and the bag-slot chain — most of the handler scripts were live the whole time (this client executes the reference's own container and paper-doll code off the player's patch chain), so what was missing was mostly declarations, not behaviour — the bank arm stays out and is named as the omission it is; OptionsFrameSliders becomes real, with the three Graphics rows reading it and re-reading on show, so an addon's write to a slider's bounds actually moves the slider; and the template census that had ranked these at "zero demand" learns to see a local bound to a literal — its table went from 3 rows to 7
- the bindings answer in 1.12's own shapes: Button:IsEnabled() answers the NUMBER 1 or 0 (0 is truthy in Lua, so the reference's own == 0 tests got false forever, and three of our files had hand-worked around the divergence rather than fixed it); the constructors' string arguments are four shapes, not one rule — pfUI passes a Button where a template name goes and the reference constructs the FontString and ignores it; SetInventoryItem returns THREE values, the reference's own paper doll destructuring repairCost; UnitIsVisible is object presence and nothing else (57 bytes, one branch, no distance — an out-of-range party member is exists-yes, visible-nil); the tapped pair reads the two dynamic-flags bits, and its repaint wire fires UNIT_FACTION on the tap bit — the half a passing predicate test never sees; UnitIsPartyLeader is two ORed legs covering disjoint failures (a held stranger leading their own party, and an out-of-range member with no object to read a flag from); and the four nameplate verbs take no argument and return zero values
- the ghost's wisps compose onto its body instead of over it — the four emitters drew at full strength over a body at alpha 0.5, and now inherit the instance alpha down the attachment chain the way the reference recomposes it per frame; the residual we had parked ("the wisps should inherit the tint too") is REFUTED at the bytes: a particle batch commits hardcoded white RGB, and even that never reaches the fragment — particles are never tinted, only the alpha crosses, applied to the alpha byte alone — so the shipped shader split was already right and no code changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-31 20:15:16 +07:00
co-authored by Claude Fable 5
parent 961d7e46a0
commit a0d2fc383b
68 changed files with 2186 additions and 272 deletions
@@ -32,6 +32,123 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
})?,
)?;
// `UnitIsVisible(unit)` — `0x516030`, and it is **object presence, nothing else**:
// `ClntObjMgrObjectPtr(resolve(token), TYPEMASK_UNIT) != NULL`. 57 bytes, one branch, no field
// read and no comparison beyond `test eax,eax`.
//
// **Not a synonym for `UnitExists`, and neither implies the other.** An out-of-range party
// member has a roster entry and no object: `UnitExists` = 1 through its GUID fallback,
// `UnitIsVisible` = nil. That pair is the branch pfUI takes seven times — most visibly
// `if not UnitIsVisible(unitstr) or not UnitIsConnected(unitstr)`, which chooses between a 3D
// portrait and a flat one.
//
// **The return is the NUMBER 1, never a boolean** — `0x6f3810` writes tag 3 with the operands
// of `1.0` on the true leg, `lua_pushnil` on the false one. `UnitExists` above answers a Rust
// `bool` and so hands Lua `true`/`false`; that is its own pre-existing question, and matching
// it here would have been the wrong kind of consistency.
g.set(
"UnitIsVisible",
lua.create_function(|lua, token: Option<String>| {
Ok(if with_unit(lua, &token, false, |u| u.has_object)? {
Value::Integer(1)
} else {
Value::Nil
})
})?,
)?;
// `UnitIsTapped(unit)` / `UnitIsTappedByPlayer(unit)` — `0x519c90` / `0x519d00`, a masked-byte
// pair (108 bytes each; only the mask and the `Usage:` string differ). Each is
// `object present && (UNIT_DYNAMIC_FLAGS & mask)` and nothing else: no ownership, no GUID
// compare, no party/raid or health conjunct anywhere in either body.
//
// **Shape A, unlike `UnitIsVisible` directly above** — these two carry an `lua_isstring` gate
// and a `Usage:` `luaL_error`, where their neighbour has none. Two adjacent verbs in one
// family with opposite argument shapes is exactly why 1717's taxonomy is settled per binding;
// inheriting the sibling's shape here would have been wrong in the quiet direction.
//
// The reference raises TWO different messages — `Usage:` for a non-string/non-number, and the
// resolver's `"Unknown unit name: %s"` for a bad token (and for any NUMBER, since the
// `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.
for (name, usage, by_player) in [
("UnitIsTapped", r#"Usage: UnitIsTapped("unit")"#, false),
(
"UnitIsTappedByPlayer",
r#"Usage: UnitIsTappedByPlayer("unit")"#,
true,
),
] {
g.set(
name,
lua.create_function(move |lua, token: Value| {
// Shape A: a string OR a number passes (`lua_isstring` admits tag 3, and the
// client hands the resolver `"5"` — which then raises `Unknown unit name: 5`,
// the family's SECOND message). Everything else — nil, absent, boolean, table,
// function — is the `Usage:` raise.
let token = Some(crate::script::binding_abi::string_arg(lua, token, usage)?);
let hit = with_unit(lua, &token, false, |u| {
if by_player {
u.tapped_by_player
} else {
u.tapped
}
})?;
// One value, and it is the NUMBER 1 or nil — never a boolean, the same shape the
// rest of this family answers in.
Ok(if hit { Value::Integer(1) } else { Value::Nil })
})?,
)?;
}
// `UnitIsPartyLeader(unit)` — `0x516210`. **Two legs, ORed, covering disjoint failures:**
//
// o = ObjPtr(resolve(token), TYPEMASK_PLAYER) -- 0x10, not this family's 8
// (o != NULL && (o.PLAYER_FLAGS & 0x1)) || resolve(token) == g_groupLeaderGuid
//
// The descriptor leg answers for any held player — including a stranger who leads their OWN
// party, which a comparison against our group's leader can never express. The GUID leg answers
// for a group member whose object the client does not hold (an out-of-range `party3`, any
// `raidN`), where there is no descriptor to read. Neither is sufficient alone, which is why
// this is not derivable from `IsPartyLeader()` + `GetPartyLeaderIndex()` however it is
// arranged (wow-re `ui/scratch/party-leader-and-nameplate-verbs.md`, G1 REFUTED).
//
// **No zero guard, and that is deliberate.** `IsPartyLeader 0x4e9130` short-circuits on a
// `0:0` cached leader; this one does not. An unresolvable-but-non-raising token resolves to
// `0:0`, which equals the zeroed leader while ungrouped — so `UnitIsPartyLeader(nil)` answers
// **1 solo**. It reads like a bug and it is the behaviour; answering nil there would be the
// divergence.
//
// Shape C with a shape-A tail: no `lua_isstring` gate and no `Usage:` of its own, but a bad
// token — and any Lua NUMBER, which the resolver stringifies first — raises
// `"Unknown unit name: %s"` from `check_unit_token`. One value on both legs, the number 1 or
// nil, never a boolean.
g.set(
"UnitIsPartyLeader",
lua.create_function(|lua, token: Option<String>| {
check_unit_token(&token)?;
let model = lua.app_data_ref::<Model>().expect("model app_data");
let by_flag = token
.as_ref()
.and_then(|t| model.unit(t))
.is_some_and(|u| u.group_leader);
// The GUID leg. A token with no snapshot resolves to GUID 0 — the reference's `0:0` —
// and 0 == the zeroed leader is exactly the solo case above.
let guid = token
.as_ref()
.and_then(|t| model.unit(t))
.map_or(0, |u| u.guid);
Ok(if by_flag || guid == model.party.leader_guid {
Value::Integer(1)
} else {
Value::Nil
})
})?,
)?;
g.set(
"UnitName",
lua.create_function(|lua, token: Option<String>| {
+59
View File
@@ -30,7 +30,66 @@ use super::Model;
#[derive(Clone, Debug, Default, PartialEq)]
pub struct UnitState {
/// Whether the unit exists (`UnitExists`). A token with no snapshot is treated as `false` too.
///
/// **Stays TRUE for an out-of-range party member** — that is the whole point of the roster
/// fallback, and it is what separates this field from [`Self::has_object`].
///
/// *Named gap, second order:* 1.12's `UnitExists 0x515fb0` is a **conjunction** — the object
/// lookup AND a virtual `[edx+0x58]` that resolves to `IsSelectable 0x60be60`
/// (`!(UNIT_FIELD_FLAGS & 0x02000000) || UNIT_FIELD_CREATEDBY == you`) — falling back to the
/// GUID-only roster test on a miss. We model the fallback and not the conjunct, so a
/// not-selectable unit created by someone else reads `exists` here and `nil` there. No corpus
/// caller reaches it; recorded rather than folded in (wow-re
/// `ui/scratch/unitisvisible-object-presence.md`).
pub exists: bool,
/// **`UnitIsVisible` — the client holds a live object for this token, and nothing else.**
///
/// The reference is 57 bytes with one branch (`0x516030`):
/// `ClntObjMgrObjectPtr(resolve(token), TYPEMASK_UNIT) != NULL`. Its body reads **zero** object
/// fields, makes **zero** comparisons beyond `test eax,eax`, and contains **zero** float
/// opcodes — so there is no distance term, no radius, no alpha/fade/interpolation flag and no
/// "known to the client" bit in it. Addons read it as a range gate because **the range test is
/// the server's**: `SMSG_DESTROY_OBJECT` and the OUT_OF_RANGE demotion unlink the object from
/// the manager index this query searches, so the client answers with the presence of the
/// object the server chose to send.
///
/// Distinct from [`Self::exists`], and neither implies the other. The one that matters here:
/// an out-of-range party member is `exists == true`, `has_object == false` — which is exactly
/// pfUI's `if not UnitIsVisible(unitstr) or not UnitIsConnected(unitstr)` portrait branch.
///
/// The app already computed this and did not publish it: `ui_party::feed`'s
/// `member_unit_state` takes `store: Option<&ObjectStore>` and its own comment calls that
/// argument "the reference's `0x468460`". Every snapshot built from a live store sets it
/// (`ui_unit::snapshot`); the roster-record leg and a token with no snapshot leave it `false`.
///
/// **One presence field serves three verbs** — `UnitIsVisible`, and the object-presence
/// conjunct of both tapped predicates below.
pub has_object: bool,
/// `UnitIsTapped` — `UNIT_DYNAMIC_FLAGS` (UpdateField **143**) bit `0x4`: this unit is
/// someone's kill credit. `0x519c90` is `test BYTE PTR [eax+0x224],0x4` behind the same
/// object-presence check [`Self::has_object`] carries, and **nothing else** — no ownership, no
/// GUID compare, no party/raid or health conjunct anywhere in the body (wow-re
/// `ui/scratch/tapped-bits-and-unit-faction.md`).
///
/// Set only on the live-descriptor leg, so an out-of-range unit reads `false` — which is the
/// object-presence conjunct, for free, by the same route `has_object` gets it.
pub tapped: bool,
/// **`UnitIsPartyLeader`'s FIRST leg** — the server's `PLAYER_FLAGS & 0x1` on this unit's own
/// descriptor, restricted to players (`TYPEMASK_PLAYER`, `0x10` — **not** this family's usual
/// `8`, which is the one difference the predicate makes to the object lookup).
///
/// Not derived client-side, and not the same question as "leads MY group": it is true for a
/// stranger who leads their own party, which no comparison against our group's leader GUID can
/// express. The second leg is that comparison, in `script::party` — the two cover disjoint
/// failures and the reference ORs them (wow-re
/// `ui/scratch/party-leader-and-nameplate-verbs.md`).
pub group_leader: bool,
/// `UnitIsTappedByPlayer` — the same field's bit `0x8` (`0x519d00`, a masked-byte clone of its
/// sibling: 108 bytes each, differing only in the mask and the `Usage:` string).
///
/// Read as a **pair**: `tapped && !tapped_by_player` is "someone else's kill", which is the
/// grey-bar condition unit-frame addons draw (pfUI `api/unitframes.lua:2012`).
pub tapped_by_player: bool,
/// The unit's name (`UnitName` → the string, or nil). `None` while the app's name-query cache
/// hasn't answered (or the server doesn't know the unit).
pub name: Option<String>,
+285 -1
View File
@@ -1,6 +1,6 @@
//! The `Unit*` binding tests (the parent module is the unit under test).
use crate::script::{UiScript, UnitState};
use crate::script::{PartyState, UiScript, UnitState};
fn player() -> UnitState {
UnitState {
@@ -958,3 +958,287 @@ fn a_multibyte_unit_token_raises_rather_than_panicking() {
"Benilla"
);
}
/// **`UnitIsVisible` is object presence, and it is NOT `UnitExists`** — the pair an out-of-range
/// party member separates, and the branch pfUI takes seven times.
///
/// `0x516030` is 57 bytes with one branch:
/// `ClntObjMgrObjectPtr(resolve(token), TYPEMASK_UNIT) != NULL`. No field read, no comparison
/// beyond `test eax,eax`, no float opcode — so no distance, no radius, no visibility flag. The
/// range test is the *server's*: the out-of-range demotion unlinks the object from the very
/// manager index this query searches (wow-re `ui/scratch/unitisvisible-object-presence.md`).
///
/// The state that backs it is [`UnitState::has_object`], which `ui_party::feed`'s
/// `member_unit_state` has always branched on — its `store: Option<&ObjectStore>` argument, whose
/// own comment names `0x468460`. This test is what stops the two fields being conflated back
/// together, because in every *in-range* case they agree and only the out-of-range one tells them
/// apart.
#[test]
fn unit_is_visible_is_object_presence_not_existence() {
let mut s = UiScript::new().unwrap();
// In range: the object is held, both answer yes.
s.set_unit(
"party1",
Some(UnitState {
exists: true,
has_object: true,
..Default::default()
}),
);
assert_eq!(
s.eval::<i64>(r#"return UnitIsVisible("party1")"#).unwrap(),
1,
"a held object is visible — and the return is the NUMBER 1, never a boolean"
);
// **Out of range: the roster still has them, the object manager does not.** This is the row
// that matters; `exists` MUST stay true here, because that is what UnitExists's own GUID
// fallback answers, and folding the two fields into one would silently break it.
s.set_unit(
"party1",
Some(UnitState {
exists: true,
has_object: false,
..Default::default()
}),
);
assert_eq!(
s.eval::<Option<i64>>(r#"return UnitIsVisible("party1")"#)
.unwrap(),
None,
"no object -> nil, even though the token still exists"
);
assert!(
s.eval::<bool>(r#"return UnitExists("party1") and true or false"#)
.unwrap(),
"...and UnitExists must NOT have moved with it — the roster fallback is the difference"
);
// pfUI's actual line (`unitframes.lua:1873`), which chooses a flat portrait over a 3D one.
assert!(
s.eval::<bool>(
r#"return (not UnitIsVisible("party1") or not UnitIsConnected("party1")) and true or false"#
)
.unwrap(),
"the portrait branch fires for an out-of-range member"
);
// A token with no snapshot at all: nil, via the same "no snapshot" path every predicate uses.
assert_eq!(
s.eval::<Option<i64>>(r#"return UnitIsVisible("party4")"#)
.unwrap(),
None
);
}
/// **The tapped pair, and the two ways it goes wrong.**
///
/// `UnitIsTapped 0x519c90` / `UnitIsTappedByPlayer 0x519d00` are a masked-byte pair — 108 bytes
/// each, differing only in the `UNIT_DYNAMIC_FLAGS` mask (`0x4`/`0x8`, UpdateField 143) and the
/// `Usage:` string. Each is `object present && (flags & mask)` and nothing else: no ownership, no
/// GUID compare, no party/raid or health conjunct (wow-re
/// `ui/scratch/tapped-bits-and-unit-faction.md`).
///
/// Asserted here because both are easy to get wrong in a way that still looks right:
///
/// - **Shape A, not shape C.** `UnitIsVisible` beside them has *no* `Usage:` raise; these two do.
/// Inheriting the neighbour's shape would fail quietly, in the permissive direction.
/// - **The pair is read as a conjunction.** `tapped && not tappedByPlayer` — someone *else's* kill
/// — is the condition addons actually draw, and either bit alone says nothing useful.
#[test]
fn the_tapped_pair_is_two_masks_of_one_field_and_raises_on_a_bad_argument() {
let mut s = UiScript::new().unwrap();
let set = |s: &mut UiScript, tapped, by_player| {
s.set_unit(
"target",
Some(UnitState {
exists: true,
has_object: true,
tapped,
tapped_by_player: by_player,
..Default::default()
}),
);
};
// Untapped: both nil.
set(&mut s, false, false);
assert_eq!(
s.eval::<Option<i64>>(r#"return UnitIsTapped("target")"#)
.unwrap(),
None
);
// Tapped by someone ELSE — the grey-bar case, pfUI `api/unitframes.lua:2012`.
set(&mut s, true, false);
assert_eq!(
s.eval::<i64>(r#"return UnitIsTapped("target")"#).unwrap(),
1,
"the return is the NUMBER 1, never a boolean"
);
assert_eq!(
s.eval::<Option<i64>>(r#"return UnitIsTappedByPlayer("target")"#)
.unwrap(),
None
);
assert!(
s.eval::<bool>(
r#"return (UnitIsTapped("target") and not UnitIsTappedByPlayer("target")) and true or false"#
)
.unwrap(),
"someone else's kill — the conjunction addons actually draw"
);
// Tapped by ME: both set, so the grey-bar branch must NOT fire.
set(&mut s, true, true);
assert!(
!s.eval::<bool>(
r#"return (UnitIsTapped("target") and not UnitIsTappedByPlayer("target")) and true or false"#
)
.unwrap(),
"my own tap is not greyed"
);
// Object presence is a conjunct of BOTH: an out-of-range unit is neither, whatever the bits
// say — and the bits cannot even be read, because there is no descriptor to read them from.
s.set_unit(
"target",
Some(UnitState {
exists: true,
has_object: false,
tapped: true,
tapped_by_player: false,
..Default::default()
}),
);
// (The feed can never build that combination — `ui_unit::snapshot` sets all three together —
// but the binding is asserted against it anyway, because a future feed that forgot would
// otherwise report a tapped unit the object manager does not hold.)
assert_eq!(
s.eval::<i64>(r#"return UnitIsTapped("target")"#).unwrap(),
1,
"the binding reads the field it is given; the conjunct is enforced at the FEED, and this \
assertion records which of the two owns it"
);
// SHAPE A. `UnitIsVisible` next door has no `Usage:` raise; these do — and **nil is in the
// list**, which is the half that does not come free: the shared `check_unit_token` lets a nil
// through by design (right for `UnitExists` and `UnitIsVisible`, wrong for these two), so the
// gate has to live in the binding. This assertion is what found that; the original spelling
// used `print`, which is not defined in our VM and so was quietly testing nil all along.
for bad in ["{}", "true", "nil", "", "function() end"] {
assert!(
s.run(&format!("UnitIsTapped({bad})")).is_err(),
"UnitIsTapped({bad}) must raise — shape A, not the neighbour's shape C"
);
assert!(
s.run(&format!("UnitIsTappedByPlayer({bad})")).is_err(),
"UnitIsTappedByPlayer({bad}) must raise too — the pair shares its gate"
);
}
}
/// **`UnitIsPartyLeader` is two legs ORed, and the solo case answers 1.**
///
/// `0x516210` is
///
/// ```text
/// o = ObjPtr(resolve(t), TYPEMASK_PLAYER)
/// (o != NULL && (o.PLAYER_FLAGS & 0x1)) || resolve(t) == g_groupLeaderGuid
/// ```
///
/// and it is **not** derivable from `IsPartyLeader()` + `GetPartyLeaderIndex()` however arranged —
/// the two legs cover disjoint failures (wow-re
/// `ui/scratch/party-leader-and-nameplate-verbs.md`, G1 REFUTED). Each leg is asserted with the
/// other one dead, because either alone looks sufficient until the case it cannot reach.
#[test]
fn unit_is_party_leader_ors_two_legs_and_answers_one_when_solo() {
let mut s = UiScript::new().unwrap();
const ME: u64 = 0x1111;
const THEM: u64 = 0x2222;
// ── LEG 1 alone: the descriptor flag, with the leader GUID matching nobody.
// This is the leg that answers for a STRANGER who leads their own party — something no
// comparison against our group's leader could ever express.
s.set_party(PartyState {
leader_guid: 0x9999,
..Default::default()
});
s.set_unit(
"target",
Some(UnitState {
exists: true,
has_object: true,
guid: THEM,
group_leader: true,
..Default::default()
}),
);
assert_eq!(
s.eval::<i64>(r#"return UnitIsPartyLeader("target")"#)
.unwrap(),
1,
"the server's PLAYER_FLAGS bit answers on its own"
);
// ── LEG 2 alone: no descriptor flag, but the resolved GUID IS our leader. This is the
// out-of-range member — the client holds no object, so leg 1 has nothing to read.
s.set_party(PartyState {
leader_guid: THEM,
..Default::default()
});
s.set_unit(
"party1",
Some(UnitState {
exists: true,
has_object: false,
guid: THEM,
group_leader: false,
..Default::default()
}),
);
assert_eq!(
s.eval::<i64>(r#"return UnitIsPartyLeader("party1")"#)
.unwrap(),
1,
"the GUID compare answers where there is no descriptor to read a flag from"
);
// ── Neither leg: a grouped member who does not lead.
s.set_unit(
"party2",
Some(UnitState {
exists: true,
has_object: true,
guid: ME,
group_leader: false,
..Default::default()
}),
);
assert_eq!(
s.eval::<Option<i64>>(r#"return UnitIsPartyLeader("party2")"#)
.unwrap(),
None
);
// ── **NO ZERO GUARD.** Ungrouped, the cached leader is 0 and an unresolvable-but-non-raising
// token resolves to 0 — so they match and the answer is 1. `IsPartyLeader 0x4e9130`
// short-circuits on a `0:0` leader and `0x516210` does not; that asymmetry is the finding,
// and answering nil here would be the divergence, however much it reads like a bug.
s.set_party(PartyState::default());
assert_eq!(
s.eval::<i64>("return UnitIsPartyLeader(nil)").unwrap(),
1,
"solo + unresolvable token: 0 == 0, and the reference answers 1"
);
assert_eq!(
s.eval::<i64>(r#"return UnitIsPartyLeader("target")"#)
.unwrap(),
1,
"...and so does a token with no snapshot, by the same route"
);
// A bad token still raises through the shared resolver — shape C with a shape-A tail.
assert!(s.run(r#"UnitIsPartyLeader("notatoken")"#).is_err());
}