raid tab art is back, set bonuses fit, and 12 voices bound the mix

- the mix is bounded the way the reference's is: FMOD gave 1.12 exactly twelve software channels and nothing else limits a priest buffing five people — ported as a 12-voice ceiling (a probe capture read 42 live voices at +13 dBFS, the limiter pumping 13 dB to hold it), plus same-kit copies started in one frame capped at two, since five sample-aligned Fortitudes are one sound +14 dB; `$WOW_SOUND_PROBE` records the mix before and after the limiter with a timeline, F9 stamps the moment
- the raid tab draws its window art again — its four corner textures were written with single backslashes, which Lua drops silently, and a sweep now reads every archive path a shipped Lua chunk names; a drag whose release never arrives (pointer left the window, a second press) ends instead of gluing the frame to the cursor for the rest of the session, and `WOW_PROBE_DRAG` drives any UI drag headlessly in a live client
- the item tooltip fits its set bonuses again: an emptied line kept its last measured height forever (a pooled cell with no way to say "empty"), and the two blank rows above the set block are the reference's own literal (a space and a newline) — a whole row each, not the empty string
- an equipped BoE or BoP piece reads Soulbound: the bind line reads the item's own flags through the client's bound predicate instead of the template's bonding, one predicate shared with the enchant cursor's bind confirm

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-25 18:25:07 +07:00
co-authored by Claude Fable 5
parent 0d7563b581
commit 034c48f2cb
46 changed files with 2300 additions and 123 deletions
+35 -18
View File
@@ -368,12 +368,13 @@ impl UiScript {
// "<Button>ButtonDown"; a release fires it when press+release landed on the same frame AND
// it registered "<Button>ButtonUp" — UNLESS a started drag is being resolved instead.
#[allow(clippy::type_complexity)]
let (click_id, drag_release, world_dropped, slider_jump, double_id): (
let (click_id, drag_release, world_dropped, slider_jump, double_id, abandoned): (
Option<u32>,
Option<cursor::DragRelease>,
bool,
Option<(u32, f32)>,
Option<u32>,
Option<crate::widget::FrameHandle>,
) = {
let mut model = self.model_mut();
let hit_handle = hit_id.and_then(|id| model.id_to_frame.get(&id).copied());
@@ -386,6 +387,10 @@ impl UiScript {
model.mouse_down_on.remove(button);
}
}
// A press REPLACES any in-flight gesture, so a started one has to be ended first
// — `abandon_drag`'s own doc carries why a silent drop is a stuck UI rather than a
// cancelled drag. Its `OnDragStop` fires below, outside this borrow.
let abandoned = cursor::abandon_drag(&mut model);
cursor::arm_drag(&mut model, hit_handle, button, (x, y));
// A left press on a Slider begins the engine drag (0250 §5): a thumb press grabs
// it in place; a track press seats the thumb under the cursor first (the returned
@@ -400,7 +405,7 @@ impl UiScript {
let click = hit_handle
.filter(|&h| button::wants_click(&model, h, &wants))
.and(hit_id);
(click, None, false, jump, None)
(click, None, false, jump, None, abandoned)
} else {
let pressed = model.mouse_down_on.remove(button);
// A left release ends any in-flight thumb drag (decision 0250 §5).
@@ -473,7 +478,7 @@ impl UiScript {
let double_id = double.and_then(|h| model.frame_to_id.get(&h).copied());
// Exclusive, not additive — the double leg is taken *instead of* the single one.
let click = if double_id.is_some() { None } else { click };
(click, release, dropped, None, double_id)
(click, release, dropped, None, double_id, None)
}
};
// A track press's value jump fires OnValueChanged first (outside the borrow), the same
@@ -545,24 +550,16 @@ impl UiScript {
editbox::drag_end(&self.lua);
}
}
// A gesture this press REPLACED gets its `OnDragStop` too, before anything the new press
// does: the handler on the other end has a `StopMovingOrSizing` in it, and until that runs
// the engine's one move slot is still held (see `cursor::abandon_drag`).
if let Some(source) = abandoned {
self.fire_drag_stop(source);
}
// The drag trio's stop/receive pair, fired outside the model borrow above (only for a
// gesture that actually started — see `cursor::DragRelease::started`).
if let Some(release) = drag_release.filter(|r| r.started) {
let source_id = {
let model = self.model_ref();
model
.arena
.frame(release.source)
.is_some()
.then(|| model.frame_to_id.get(&release.source).copied())
.flatten()
};
if let Some(id) = source_id {
if let Err(e) = event::fire_widget_handler(&self.lua, id, "OnDragStop", Vec::new())
{
self.push_error(e);
}
}
self.fire_drag_stop(release.source);
if let Some(id) = hit_id {
if let Err(e) =
event::fire_widget_handler(&self.lua, id, "OnReceiveDrag", Vec::new())
@@ -592,6 +589,26 @@ impl UiScript {
hit_id.is_some() || world_dropped
}
/// Fire `OnDragStop` on a gesture's source — the ONE place that ends a started drag, so the
/// release path and the two abandon paths ([`cursor::abandon_drag`]) cannot drift apart.
/// A source that died between the press and here fires nothing.
pub(super) fn fire_drag_stop(&mut self, source: crate::widget::FrameHandle) {
let id = {
let model = self.model_ref();
model
.arena
.frame(source)
.is_some()
.then(|| model.frame_to_id.get(&source).copied())
.flatten()
};
if let Some(id) = id {
if let Err(e) = event::fire_widget_handler(&self.lua, id, "OnDragStop", Vec::new()) {
self.push_error(e);
}
}
}
/// A mouse-wheel spin at `(x, y)`: fires `OnMouseWheel(self, delta)` on the hit frame — or,
/// when the hit frame carries no OnMouseWheel handler, on its NEAREST ANCESTOR that does (the
/// client's bubbling: a reward-row Button inside a ScrollFrame doesn't eat the wheel, the