The keyring grows in rows, the rank is art, and two fixtures I left behind

- **Keyring**: `GetKeyRingSize` has no level term at all — it scans for the highest filled slot
  and rounds up to a multiple of four (`ContainerFrame.lua:817-844`), expanding only when the key
  count has caught up with that slot. So one key is one row of four, four keys in four slots grow
  to eight rather than refusing a fifth, and full is all 32. The realm also **fixed** 1.12's silent
  refusal: it renamed the message to `NO_EMPTY_KEYRING_SLOTS_ERROR` and defined it
  ("Your keyring is full."), where stock printed a name that did not exist.
- **Bag tooltip**: the item-button `OnUpdate` accumulates toward `TOOLTIP_UPDATE_TIME` instead of
  counting down from it (`:694-704`), and reads the threshold before adding — so a landing template
  repaints an open plate two frames after it crosses a fifth of a second, not on the next frame.
- **Raid grid**: the rank is a `$parentRankIcon` Texture now (`Blizzard_RaidUI.xml:49`), set to
  `RAID_LEADER_ICON`/`RAID_ASSISTANT_ICON` (`:100`, `:155-161`). The `$parentRank` FontString that
  held "(L)"/"(A)" is still declared and never written.
- **Ready check**: the realm moved it into its own group UI (`Turtle_GroupUI.lua:259`) and
  `RaidFrameReadyCheckButton`'s OnClick calls `GroupFrame_InitiateReadyCheck` by name, so
  `load_social_ui` seats that file; without it the button clicks into a nil.

And two fixtures that were mine to fix and I missed: `quest_tests`' detail and reward panels still
carried `item_id: 0`, which `3f2982445` taught the counters to take-while out of existence. Same
correction as the `benilla-ui` fixture in that commit, with the reason written where it is read.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-31 14:15:58 -06:00
co-authored by Claude Opus 5
parent 34097b6164
commit 9dde1a80e5
4 changed files with 90 additions and 30 deletions
+66 -24
View File
@@ -502,6 +502,24 @@ fn hovered_bag_tooltip_fills_itself_when_the_stats_land() {
..Default::default()
},
);
// Not the next frame: the realm's `ContainerFrameItemButton_OnUpdate` **accumulates** and
// repaints only once `this.updateTooltip` passes `TOOLTIP_UPDATE_TIME`
// (`ContainerFrame.lua:694-704`), where stock 1.12 counted down from it and so repainted on
// the first update after a hover. A fifth of a second, which is what a player sees.
s.tick(0.016);
assert_eq!(
s.eval::<i64>("return GameTooltip:NumLines()").unwrap(),
1,
"one frame in, the plate is still the fallback line"
);
// The threshold is read **before** the accumulate, so the repaint lands on the frame after
// the one that crosses it.
s.tick(0.2);
assert_eq!(
s.eval::<i64>("return GameTooltip:NumLines()").unwrap(),
1,
"the frame that crosses the threshold still reads the old total"
);
s.tick(0.016);
assert!(
s.eval::<i64>("return GameTooltip:NumLines()").unwrap() > 1,
@@ -1639,7 +1657,8 @@ fn the_keyring_button_opens_a_keyring_window() {
keyring_surface(&s);
s.set_money(0);
s.set_has_key(true);
// Level 44: 8 slots (the 40..49 rung). The level decides; the feed carries the same 8.
// The level decides nothing here (see `seat_player_at_level`): `GetKeyRingSize` scans for the
// highest filled slot and rounds up to a multiple of four (`ContainerFrame.lua:817-844`).
seat_player_at_level(&mut s, 44);
s.set_container(-2, Some(keyring(8, true)));
s.fire_event("BAG_UPDATE", vec![benilla_ui::script::ScriptValue::Int(-2)]);
@@ -1670,7 +1689,11 @@ fn the_keyring_button_opens_a_keyring_window() {
if getglobal('{w}Item' .. i):IsShown() then n = n + 1 end end return n"
))
.unwrap();
assert_eq!(shown, 8, "only the level-unlocked keyring slots are drawn");
assert_eq!(
shown, 4,
"one key in slot 1 rounds up to one row of four — not the fed `num_slots`, which stock \
FrameXML never asks the keyring for"
);
assert_eq!(
s.eval::<i64>("return BenillaGetContainerItemID(KEYRING_CONTAINER, 1)")
.unwrap(),
@@ -1731,24 +1754,46 @@ fn a_key_dropped_on_the_button_files_itself() {
"filed into the FIRST FREE keyring slot (1 is taken), not the backpack"
);
// Full: each slot needs a texture, since `PutKeyInKeyRing` reads a slot as free from
// `GetContainerItemInfo`'s first return, the icon (`ContainerFrame.lua:744-746`).
let mut full = keyring(4, true);
for slot in 2..=4 {
full.slots.insert(
slot,
ContainerSlot {
item_id: 7146,
count: 1,
texture: Some("Interface\\Icons\\INV_Misc_Key_07".into()),
..Default::default()
},
);
}
s.set_container(-2, Some(full));
// A key in every slot of the row is not full: `GetKeyRingSize` only keeps the current size
// when `numItems < maxSlotNumberFilled`, so four keys in slots 1-4 round up to eight
// (`ContainerFrame.lua:835-839`) and `PutKeyInKeyRing` finds slot 5 free. Each slot needs a
// texture, since it reads a slot as free from `GetContainerItemInfo`'s first return, the icon.
let keyed = |slots: std::ops::RangeInclusive<u32>| {
let mut ring = keyring(4, true);
for slot in slots {
ring.slots.insert(
slot,
ContainerSlot {
item_id: 7146,
count: 1,
texture: Some("Interface\\Icons\\INV_Misc_Key_07".into()),
..Default::default()
},
);
}
ring
};
s.set_container(-2, Some(keyed(2..=4)));
s.run("PickupContainerItem(0, 3)").unwrap();
s.run("KeyRingButton:Click()").unwrap();
assert_eq!(
s.take_container_moves(),
vec![ContainerMove {
src_bag: 0,
src_slot: 3,
dst_bag: -2,
dst_slot: 5,
count: None,
}],
"a filled row grows by four rather than refusing"
);
// Truly full is all 32 — `GetKeyRingSize` clamps there (`:841`) — and the refusal now says so:
// stock `PutKeyInKeyRing` printed `NO_EMPTY_KEYRING_SLOTS`, a name 1.12 never defines, and the
// realm renamed it to `NO_EMPTY_KEYRING_SLOTS_ERROR` (`:797`) and defined it in
// `GlobalStrings.lua` as "Your keyring is full." So the 1.12 silent-refusal bug is fixed.
s.set_container(-2, Some(keyed(2..=32)));
s.run("PickupContainerItem(0, 3)").unwrap();
// Stock `PutKeyInKeyRing` reports through `NO_EMPTY_KEYRING_SLOTS`, which 1.12 never defines
// (`ContainerFrame.lua:753`), so nothing prints: a 1.12 bug, kept.
s.run("KeyRingButton:Click()").unwrap();
assert!(
s.take_container_moves().is_empty(),
@@ -1756,15 +1801,12 @@ fn a_key_dropped_on_the_button_files_itself() {
);
s.resolve();
assert!(
!s.extract().iter().any(|q| matches!(
s.extract().iter().any(|q| matches!(
&q.content,
QuadContent::Text { text: Some(t), .. } if t.contains("keyring is full")
)),
"the reference's own dangling string name prints nothing — un-quirking it is a decision, \
not a default"
"the refusal reaches UIErrorsFrame now that the string exists"
);
// Nor does it raise: `AddMessage` reads its text through `lua_isstring` (`0x6f3510`), whose
// failure edge jumps to the function's epilogue (`0x79562c`).
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
@@ -178,8 +178,11 @@ fn detail_panel_reward_grid_follows_the_refs_two_per_row_layout() {
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
// A real item id per row, because `GetNumQuestChoices`/`GetNumQuestRewards` take-while on it:
// the reference's counters (`0x5016c0`, `0x5016a0`) `je` out of their loop on the first zero,
// so a row with no id hides itself and every row behind it. Only non-zero-ness matters.
let choice = |name: &str, quality: u32| benilla_ui::script::QuestItemView {
item_id: 0,
item_id: 4_984,
name: Some(name.into()),
texture: None,
count: 1,
@@ -274,8 +277,9 @@ fn reward_panel_choice_click_selects_and_completes_with_zero_based_index() {
load_xml(&s, "Interface\\FrameXML\\QuestFrame.xml");
load_xml(&s, "Interface\\FrameXML\\QuestLogFrame.xml");
// Non-zero, as the reference's take-while counters need (see the detail-grid test above).
let choice = |name: &str| benilla_ui::script::QuestItemView {
item_id: 0,
item_id: 4_984,
name: Some(name.into()),
texture: None,
count: 1,
+13 -4
View File
@@ -240,14 +240,23 @@ fn the_grid_seats_each_row_in_its_own_subgroups_next_free_slot() {
);
assert_eq!(text_of(&s, "RaidGroupButton1Name"), "Me");
// The rank is art now, not a letter: the realm added a `$parentRankIcon` Texture
// (`Blizzard_RaidUI.xml:49`) and the row update sets it to `RAID_LEADER_ICON` or
// `RAID_ASSISTANT_ICON` (`Blizzard_RaidUI.lua:100`, `:155-161`). The `$parentRank` FontString
// 1.12 wrote "(L)"/"(A)" into is still declared and now never written.
let rank_icon = |s: &UiScript, button: &str| {
s.eval::<String>(&format!("return {button}RankIcon:GetTexture() or ''"))
.unwrap()
};
assert_eq!(
text_of(&s, "RaidGroupButton1Rank"),
"(L)",
rank_icon(&s, "RaidGroupButton1"),
"Interface\\GroupFrame\\UI-Group-LeaderIcon",
"the leader token"
);
assert_eq!(text_of(&s, "RaidGroupButton1Rank"), "");
assert_eq!(
text_of(&s, "RaidGroupButton2Rank"),
"(A)",
rank_icon(&s, "RaidGroupButton2"),
"Interface\\GroupFrame\\UI-Group-AssistantIcon",
"the assistant token"
);
assert_eq!(
@@ -743,6 +743,11 @@ pub(super) fn load_social_ui(s: &mut UiScript) {
for f in SOCIAL_UI {
load_ui_strict(s, f);
}
// The `.lua` alone, for `GroupFrame_InitiateReadyCheck`: the realm moved the ready check out
// of `Blizzard_RaidUI` and into its own group UI (`Turtle_GroupUI.lua:259`, toc row 106-ish),
// and `RaidFrameReadyCheckButton`'s `<OnClick>` calls it by name
// (`Blizzard_RaidUI.xml:538-540`). Without it the button clicks into a nil.
load_ui(s, r"Interface\FrameXML\Turtle_GroupUI\Turtle_GroupUI.lua");
seat_chain_addon(s, "Blizzard_RaidUI");
s.run("RaidFrame_LoadUI()").unwrap();
}