the numerals kit goes stock, and a chat link is its own mouse target

- the status-bar numerals kit (`TextStatusBar.xml` + `.lua`) is the reference's own, and the attempt found a class of silent failure: `TextStatusBar_Initialize()` takes no argument in 1.12 and acts on `this`; ours took an optional bar, our unit frames passed one, and the reference's version ignores it — so the numerals would simply never appear, no error, nothing missing; seventeen of our signatures differ from the reference's that way (Lua drops a surplus argument in silence), written down before their files migrate, and the three "ours narrower" ones — `StaticPopup_Hide(which, data)`, `StaticPopup_FindVisible`, `WorldMapButton_OnClick(button, frame)` — closed now, because that direction bites when a STOCK file arrives and calls our version
- a chat link needs no mouse on its frame: wow-re read the client's ctors as mouse-enabling Button, CheckButton, Slider, ColorSelect and EditBox and NOT the scroll frames; the objection ("something makes a 1.12 chat link clickable") resolved as the engine synthesising one mouse-enabled `CSimpleHyperlinkButton` per link span (`0x7a3240`) — so a link span is a hit-test candidate on its own, the chat window is inert everywhere else, and the wheel is its own predicate rather than a borrowed mouse bit; seven `*ScrollFrame` known divergences retire
- the relic fit rule: `IsValidForSlot`'s slot-17 leg is an EQUALITY — `(InventoryType == RELIC) == hasRelicSlot` — so a relic lights the ranged slot for a druid and a bow does not, on the bag bar and the paper doll alike; closing it half-right would have lit the highlight for a druid dragging a bow
- a chain `.xml` whose `.lua` never loaded: `MoneyInputFrame.xml` had been on the chain without its `.lua`, so the template loaded and all ten `MoneyInputFrame_*` verbs read nil, silently — a gate now fails any chain `.xml` whose `.lua` sibling is neither sourced nor listed; the gap report's LOAD column carries the probe's collision caveat, its fx column stops crying wolf (it follows `<Script file=>`, stops reading `.Field(` as a global, subtracts a file's own locals), and a window is its xml PLUS the lua it sources

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-02 17:01:18 +07:00
co-authored by Claude Fable 5.1
parent 112e82d276
commit feca23de3f
30 changed files with 981 additions and 404 deletions
+53 -3
View File
@@ -50,7 +50,7 @@ use crate::order;
use crate::widget::FrameHandle;
use super::clip::{effective_clip, scroll_clip_sources};
use super::{button, cursor, editbox, event, UiScript};
use super::{button, cursor, editbox, event, Model, UiScript};
/// The double-click interval — **300 ms, VERIFIED off the bytes**, a hardcoded instruction
/// immediate: `0x77937b 81 f9 2c 01 00 00 cmp ecx, 0x12c`, comparing `now − [this+0x334]` inside
@@ -127,6 +127,15 @@ pub(super) fn install(lua: &mlua::Lua) -> mlua::Result<()> {
Ok(())
}
/// Whether `(x, y)` falls inside one of this frame's hyperlink spans — the engine half of the
/// reference's per-span `CSimpleHyperlinkButton` children.
fn link_span_hit(model: &Model, fh: FrameHandle, x: f32, y: f32) -> bool {
model
.link_spans
.get(&fh)
.is_some_and(|spans| spans.iter().any(|(r, _, _)| point_in_rect(*r, x, y)))
}
impl UiScript {
/// Hit-test the cursor at `(x, y)` and return the **handle** of the captured frame (the
/// topmost-drawn mouse-enabled, effective-visible frame whose rect contains the point **and**
@@ -146,7 +155,22 @@ impl UiScript {
let sorted = order::traversal(&model.arena);
let scroll_sources = scroll_clip_sources(&model);
order::hit_test(&sorted, |fh| {
model.arena.is_mouse_enabled(fh)
// Mouse-enabled, **or inside one of this frame's hyperlink spans**.
//
// That second half is how the reference makes a chat link clickable on a frame that
// takes no mouse at all. `CSimpleMessageScrollFrame`'s ctor leaves `[+0xcc] = 0`, so
// `ChatFrame1` is never in the hit-test index — instead the engine synthesises one
// mouse-enabled `CSimpleHyperlinkButton` child PER SPAN (`0x7a3240`, born through the
// `CSimpleButton` ctor) and routes the three hyperlink scripts back to the parent
// (wow-re `mouse-enable-law.md` + the follow-up round `68987021`).
//
// We hold the spans as rects on the parent rather than as child frames, so the same
// law expresses as a disjunct here. What it buys is everything the reference gets from
// the split: the chat window is a hit candidate only over a LINK, so `GetMouseFocus()`
// answers what is behind it elsewhere, its `movable="true"` +
// `<TitleRegion setAllPoints="true"/>` stay inert so it drags by its tab and not its
// body, and a hover over plain text reassigns to whatever is underneath.
(model.arena.is_mouse_enabled(fh) || link_span_hit(&model, fh, x, y))
&& model.resolved.get(&fh).is_some_and(|r| {
point_in_rect(inset_rect(*r, model.arena.hit_rect_insets(fh)), x, y)
})
@@ -155,6 +179,26 @@ impl UiScript {
})
}
/// [`Self::hit_test`] over the frames that take the WHEEL — either bit, since a mouse-enabled
/// frame with an `<OnMouseWheel>` is a candidate for both. Same rect and clip rules; only the
/// enable predicate differs.
fn hit_test_wheel(&self, x: f32, y: f32) -> Option<u32> {
let model = self.model_ref();
let sorted = order::traversal(&model.arena);
let scroll_sources = scroll_clip_sources(&model);
let fh = order::hit_test(&sorted, |fh| {
(model.arena.is_mouse_wheel_enabled(fh)
|| model.arena.is_mouse_enabled(fh)
|| link_span_hit(&model, fh, x, y))
&& model.resolved.get(&fh).is_some_and(|r| {
point_in_rect(inset_rect(*r, model.arena.hit_rect_insets(fh)), x, y)
})
&& effective_clip(&model, &scroll_sources, fh)
.is_none_or(|c| point_in_rect(c, x, y))
})?;
model.frame_to_id.get(&fh).copied()
}
/// Hit-test the cursor at `(x, y)` and return the **id** of the captured frame, or `None` —
/// [`Self::hit_test_frame`] with the id lookup every caller in the input path wants. The id is
/// the same one used across this host (e.g. the layout [`crate::layout::Handle`]); the app can
@@ -749,7 +793,13 @@ impl UiScript {
/// [`UiScript::errors`].
pub fn mouse_wheel(&mut self, x: f32, y: f32, delta: f32) {
let target: Option<u32> = {
let hit = self.hit_test(x, y);
// **Wheel-enabled OR mouse-enabled**, not mouse-enabled alone. The reference's wheel
// dispatcher `0x7664f0` walks its OWN index and calls the hit probe directly, ignoring
// hover, capture and the mouse bit — `EnableMouse` is not a prerequisite
// (wow-re `mouse-enable-law.md` §6). Ours went through the mouse hit test, which
// coupled the two: correcting the ctor list to the reference's (ScrollFrame is inert
// on the mouse bit) silently stopped every scroll pane from taking the wheel.
let hit = self.hit_test_wheel(x, y);
let model = self.model_ref();
hit.and_then(|id| {
let mut h = *model.id_to_frame.get(&id)?;