NTFS load order, a title-sorted AddOns list, and tooltips at the cursor
- addons load in the order NTFS lists a folder, case-insensitive with an underscore before the letters, the order the vanilla corpus was written against and the one that decides which addon provides a shared library (2178) - GetNumAddOns and the index form of every AddOn verb walk a title-sorted list that hides the Blizzard addons the server says to hide, and the character-select AddOns list is in title order (2175) - SetPoint and SetAllPoints raise the reference's own errors for a frame name that does not exist or an omitted relative frame instead of quietly anchoring to the parent, and an explicit nil means the screen root (2176) - a tooltip owned with ANCHOR_CURSOR follows the mouse, which pfUI's tooltip, xpbar and chat modules, pfQuest's browser and TipBuddy ask for; an omitted or unknown anchor is ANCHOR_LEFT (2176) - a $parent name handed to CreateFrame, CreateTexture or CreateFontString expands as it does in XML, so FonzAppraiser's thirty dropdowns and pfQuest's search icon get the names they asked for (2176) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -20,24 +20,35 @@ use super::{
|
||||
pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let m = lua.create_table()?;
|
||||
|
||||
// SetOwner(owner, anchorType [, x, y]) — remember the owner, clear the content (the engine
|
||||
// fires OnTooltipCleared: a fresh owner never inherits the last hover's lines or money), and
|
||||
// point the plate per the anchor law. The six owner anchors are the documented 1.12 API set
|
||||
// (spec-faithful, same posture as StatusBar's contract; the corpus exercises RIGHT/LEFT/
|
||||
// BOTTOMRIGHT); ANCHOR_NONE leaves pointing to the caller (GameTooltip_SetDefaultAnchor),
|
||||
// ANCHOR_PRESERVE keeps the previous placement. The mode is stored on the tooltip and read back
|
||||
// by `GetAnchorType` below.
|
||||
// SetOwner(owner, anchorType [, x, y]) — the binding `0x5310d0` and the core it calls,
|
||||
// `0x52ffe0(owner_region, mode, dx, dy)`: SetAlpha(255) (`0x52fff4`), fade-disarm
|
||||
// (`0x530002`), `+0x314 = owner` (`0x53000c`), the mode (`0x530031`), the offsets, then the
|
||||
// anchor-apply `0x52fe90`. We clear the content too, because the engine fires
|
||||
// OnTooltipCleared: a fresh owner never inherits the last hover's lines or money.
|
||||
//
|
||||
// **The `anchorType`-omitted default here is ANCHOR_RIGHT and the reference's is ANCHOR_NONE.**
|
||||
// The reference's SetOwner core defaults `[+0x318]` to mode 7 = ANCHOR_NONE (`0x530012`, wow-re
|
||||
// `bag-portrait-and-appendtext.md` §5) — no `SetPoint` at all, the plate left wherever it was.
|
||||
// Two stock 1.12 files reach that leg (`QuestTimerFrame.xml:19`, `WorldStateFrame.xml:681`,
|
||||
// both `GameTooltip:SetOwner(this)`), and neither is on `benilla.toc` yet, so nothing today
|
||||
// takes it. It is left alone deliberately rather than quietly flipped: our ANCHOR_NONE arm
|
||||
// below DROPS the plate's anchors where the reference merely skips the SetPoint, so changing
|
||||
// the default would land those two hovers at the layout origin rather than "wherever it was" —
|
||||
// the two questions have to be settled together, and the second one is a visible-placement
|
||||
// change. Recorded here so the next reader sees a stated divergence, not an oversight.
|
||||
// **The omitted / unreadable / unrecognised anchorType is mode 0 = ANCHOR_LEFT, silently.**
|
||||
// `0x53120d` writes 0 into the binding's local mode *before any compare runs*; `0x531214`'s
|
||||
// `lua_isstring` gate jumps the whole `SStrCmpI` chain when arg 3 is absent, nil, boolean or a
|
||||
// table; the chain's last `jne` reaches the same join untouched; and `[0x531221, 0x53133a)`
|
||||
// contains no `luaL_error` (earned zero, positive-controlled against the five this function
|
||||
// *does* have in its owner-validation prologue). `0x5313c0` then hands that local to the core.
|
||||
//
|
||||
// The core's own `mov [+0x318],7` at `0x530012` is **not** that default: it is an
|
||||
// unconditional pre-store that `0x530031` overwrites with the argument, and it survives only
|
||||
// when the owner pointer is NULL — the un-own path `0x530a60` takes on every hide.
|
||||
//
|
||||
// **The anchor-apply clears, and only ANCHOR_PRESERVE escapes it** (`0x52fe90` read
|
||||
// contiguously): a NULL owner returns at `0x52fe9e`; **mode 8 returns at `0x52fead`, before
|
||||
// the clear**; then `0x52feb8` tests the caller's arg1, which `0x530037` always passes as
|
||||
// **1** (`0x53002d push 0x1`), so the mode-7 skip at `0x52feba` is dead on this path and
|
||||
// `0x52fec2 call 0x767ed0` (ClearAllPoints) runs for **every mode 0..7**; and only then does
|
||||
// `0x52fed0 ja` send 6 and 7 home while 0..5 take the SetPoint jump table `0x52ffbc`.
|
||||
//
|
||||
// So the three arms below are: 0..5 clear-and-point, **6 and 7 clear**, 8 nothing. Decision
|
||||
// 2176 replaces 2142's open thread 1, which had it backwards on both counts — it read the
|
||||
// core's pre-store as the Lua default and read "no SetPoint" as "no clear", and a wow-re §5
|
||||
// dispatched for the CURSOR mechanism refuted both at the bytes (`system/ui/scratch/`
|
||||
// `tooltip-cursor-anchor-law.md` §0.2/§0.3/§2).
|
||||
m.set(
|
||||
"SetOwner",
|
||||
lua.create_function(
|
||||
@@ -59,29 +70,36 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
let owner_id = model.frame_id(owner_h);
|
||||
// The anchor mode is resolved ONCE, into the reference's own enum, and both
|
||||
// the placement below and `GetAnchorType`'s answer are taken from that one
|
||||
// value — so the plate and the getter can never tell different stories. An
|
||||
// unrecognised string (`ANCHOR_CURSOR` among them, which this engine cannot
|
||||
// honour — see [`TooltipAnchor::Cursor`]) warns and is recorded as the
|
||||
// ANCHOR_RIGHT it is actually placed by, not as the string it was handed.
|
||||
// value — so the plate and the getter can never tell different stories.
|
||||
//
|
||||
// Absent, unreadable and unrecognised all land on **ANCHOR_LEFT**, which is
|
||||
// the binding's zero-initialised local (`0x53120d`) surviving the
|
||||
// `lua_isstring` gate or the compare chain, with no raise. The warning on the
|
||||
// unrecognised leg is ours and stays: the reference is silent, but nothing
|
||||
// observable to Lua changes, and this exact warning is what made
|
||||
// `ANCHOR_CURSOR` visible as a real ninth mode nine corpus files ask for
|
||||
// rather than a typo (2142's open thread 2).
|
||||
let anchor = match anchor
|
||||
.as_deref()
|
||||
.map(str::to_ascii_uppercase)
|
||||
.as_deref()
|
||||
.unwrap_or("ANCHOR_RIGHT")
|
||||
.unwrap_or("")
|
||||
{
|
||||
"ANCHOR_RIGHT" => TooltipAnchor::Right,
|
||||
"ANCHOR_LEFT" => TooltipAnchor::Left,
|
||||
"ANCHOR_TOPRIGHT" => TooltipAnchor::TopRight,
|
||||
"ANCHOR_TOPLEFT" => TooltipAnchor::TopLeft,
|
||||
"ANCHOR_BOTTOMRIGHT" => TooltipAnchor::BottomRight,
|
||||
"ANCHOR_RIGHT" => TooltipAnchor::Right,
|
||||
"ANCHOR_BOTTOMLEFT" => TooltipAnchor::BottomLeft,
|
||||
"ANCHOR_BOTTOMRIGHT" => TooltipAnchor::BottomRight,
|
||||
"ANCHOR_TOPLEFT" => TooltipAnchor::TopLeft,
|
||||
"ANCHOR_TOPRIGHT" => TooltipAnchor::TopRight,
|
||||
"ANCHOR_CURSOR" => TooltipAnchor::Cursor,
|
||||
"ANCHOR_NONE" => TooltipAnchor::None,
|
||||
"ANCHOR_PRESERVE" => TooltipAnchor::Preserve,
|
||||
"" => TooltipAnchor::Left,
|
||||
other => {
|
||||
model.record_warning(format!(
|
||||
"SetOwner: unknown anchor '{other}' (kept ANCHOR_RIGHT)"
|
||||
"SetOwner: unknown anchor '{other}' (mode 0, ANCHOR_LEFT)"
|
||||
));
|
||||
TooltipAnchor::Right
|
||||
TooltipAnchor::Left
|
||||
}
|
||||
};
|
||||
tip_mut(&mut model, h)?.anchor = anchor;
|
||||
@@ -96,58 +114,56 @@ pub(in crate::script) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
None
|
||||
}
|
||||
};
|
||||
match pts {
|
||||
Some((own, rel)) => {
|
||||
let new =
|
||||
Anchor::new(own, owner_id, rel, x.unwrap_or(0.0), y.unwrap_or(0.0));
|
||||
let input = model.layout_inputs.entry(h).or_default();
|
||||
// The no-op compare keeps the per-frame SetOwner idiom (the bag
|
||||
// hover's OnUpdate re-enter) from dirtying tier 1 by itself; the
|
||||
// content clear above already reports its own writes.
|
||||
let same = input.anchors.len() == 1
|
||||
&& crate::script::object::anchor_bits_eq(&input.anchors[0], &new);
|
||||
if !same {
|
||||
// The plate re-points at the button under the cursor, which moves
|
||||
// an EDGE — structural under 1388, and therefore a whole-graph
|
||||
// derivation on every bag slot and every spellbook button a hover
|
||||
// sweep crossed. It names its node now (decision 1625): the old
|
||||
// and new target lists are both right here, so the cached graph's
|
||||
// edges are re-pointed instead of thrown away.
|
||||
let old_targets: Vec<u32> =
|
||||
input.anchors.iter().map(|a| a.relative_to).collect();
|
||||
input.anchors = vec![new];
|
||||
model.touch_layout_retarget_frame(h, &old_targets, &[owner_id]);
|
||||
// **Modes 6 and 7 CLEAR and stop; mode 8 does neither.** `0x52fe90` reaches
|
||||
// `0x52fec2 call 0x767ed0` for every mode 0..7 — the mode-7 skip beside it is
|
||||
// gated on an arg1 that `SetOwner`'s core always passes as 1 — and mode 8
|
||||
// returned two compares earlier, at `0x52fead`, without touching anything.
|
||||
//
|
||||
// For ANCHOR_NONE that clear is what makes `GameTooltip_SetDefaultAnchor`
|
||||
// (`SetOwner(owner, "ANCHOR_NONE")` → `ClearAllPoints()` → `SetPoint(...)`,
|
||||
// the path of every action-bar hover) safe: a stale owner anchor cannot win
|
||||
// the frame the caller forgets to re-point. For ANCHOR_CURSOR it is the
|
||||
// opening move of a placement the per-frame update finishes
|
||||
// (`script::tooltip::cursor_anchor`).
|
||||
//
|
||||
// Dropping every anchor is a retarget to the EMPTY target set, and it names
|
||||
// its node like any other (decision 1625). It matters here more than
|
||||
// anywhere: left on the conservative touch, this line was a whole-graph
|
||||
// derivation on every action button the cursor crossed and on nothing else —
|
||||
// exactly the shape the director's recorder reported (decision 1630).
|
||||
if matches!(anchor, TooltipAnchor::Cursor | TooltipAnchor::None) {
|
||||
let dropped = match model.layout_inputs.get_mut(&h) {
|
||||
Some(input) if !input.anchors.is_empty() => {
|
||||
Some(input.anchors.drain(..).map(|a| a.relative_to).collect())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(old_targets) = dropped {
|
||||
let old_targets: Vec<u32> = old_targets;
|
||||
model.touch_layout_retarget_frame(h, &old_targets, &[]);
|
||||
}
|
||||
None if anchor == TooltipAnchor::None => {
|
||||
// The caller points it (ClearAllPoints+SetPoint) — drop ours now so a
|
||||
// stale owner anchor never wins the frame the caller forgets to.
|
||||
//
|
||||
// Dropping every anchor is a retarget to the EMPTY target set, and it
|
||||
// names its node like any other (decision 1625). It matters here more
|
||||
// than anywhere: this arm is `GameTooltip_SetDefaultAnchor`'s, which is
|
||||
// the path EVERY action-bar hover takes (stock `ActionButton_SetTooltip`, the
|
||||
// `UberTooltips` default; the stance, pet and bonus bars the same) —
|
||||
// while a bag slot anchors straight to its button and never comes
|
||||
// through here. So a conservative touch on this line was a whole-graph
|
||||
// derivation on every action button the cursor crossed, and on nothing
|
||||
// else, which is exactly the shape the director's recorder reported (decision 1630):
|
||||
// every derive frame owned by a MultiBar/BonusAction button.
|
||||
let dropped = match model.layout_inputs.get_mut(&h) {
|
||||
Some(input) if !input.anchors.is_empty() => {
|
||||
Some(input.anchors.drain(..).map(|a| a.relative_to).collect())
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
if let Some(old_targets) = dropped {
|
||||
let old_targets: Vec<u32> = old_targets;
|
||||
model.touch_layout_retarget_frame(h, &old_targets, &[]);
|
||||
}
|
||||
}
|
||||
if let Some((own, rel)) = pts {
|
||||
let new =
|
||||
Anchor::new(own, owner_id, rel, x.unwrap_or(0.0), y.unwrap_or(0.0));
|
||||
let input = model.layout_inputs.entry(h).or_default();
|
||||
// The no-op compare keeps the per-frame SetOwner idiom (the bag
|
||||
// hover's OnUpdate re-enter) from dirtying tier 1 by itself; the
|
||||
// content clear above already reports its own writes.
|
||||
let same = input.anchors.len() == 1
|
||||
&& crate::script::object::anchor_bits_eq(&input.anchors[0], &new);
|
||||
if !same {
|
||||
// The plate re-points at the button under the cursor, which moves
|
||||
// an EDGE — structural under 1388, and therefore a whole-graph
|
||||
// derivation on every bag slot and every spellbook button a hover
|
||||
// sweep crossed. It names its node now (decision 1625): the old
|
||||
// and new target lists are both right here, so the cached graph's
|
||||
// edges are re-pointed instead of thrown away.
|
||||
let old_targets: Vec<u32> =
|
||||
input.anchors.iter().map(|a| a.relative_to).collect();
|
||||
input.anchors = vec![new];
|
||||
model.touch_layout_retarget_frame(h, &old_targets, &[owner_id]);
|
||||
}
|
||||
// ANCHOR_PRESERVE. `TooltipAnchor::Cursor` shares the arm for
|
||||
// exhaustiveness only — the string match above never produces it (it warns
|
||||
// and records ANCHOR_RIGHT instead), so nothing reaches here that way.
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
fire_cleared(lua, h);
|
||||
|
||||
Reference in New Issue
Block a user