Files
benilla-octo/crates/benilla-ui/src/script/tests/texcoord_font.rs
T
SamandClaude Fable 5.1 86b155d6e7 bindings answer the reference's shapes, and the panel kit goes stock
- four return shapes were wrong — `UnitPowerType` returns ONE value in 5875 (the Era `(type, "RAGE")` pair does not exist, and its miss arm pushes the NUMBER 0, which `UnitFrame.lua:122` indexes `ManaBarColor` with), `UnitName` returns `(name, server)`, `GetTexCoord` returns EIGHT (ours four — a shape that exists nowhere, with zero call sites in FrameXML, so only the binary could have caught it), `GetLootMethod` answers `freeforall` ungrouped — the fifth right-name-wrong-arity finding in two days, so wow-re's `binding-shapes.tsv` (1722 rows over all 82 registrar tables) is vendored and a gate probes every binding's arity against it: 313 probes, eight divergences (seven global, one widget); `GetBuildInfo` pushed a fourth value no 1.12 client does (458 corpus sites take one, the four that destructure a fourth read nil on the real client), `GetAddOnInfo` RAISES on a numeric index, `Model:GetFogColor` is a packed `0xAARRGGBB` dword with no unset state, and the five miss branches are the reference's exact tuples
- the panel-template kit is the reference's own `UIPanelTemplates` pair (our 18 templates were a strict subset — `TabButtonTemplate` and the `ScrollingEdit_*` family arrive, which unblocks the macro window and HelpFrame), and the swap found two structural facts: `FauxScrollFrameTemplate` is a `<ScrollFrame>` while every instance we declared was a `<Frame>` (fifteen elements across twelve files), and an instance `<OnLoad>` REPLACES the template's — four FriendsFrame lists set `updateFunc` in their own OnLoad, so `ScrollFrame_OnLoad` never ran and guild/friends/ignore/who had been silently painting nothing behind a `frame.offset or 0`
- the options widget kit (`OptionFrameBoxTemplate`, `OptionsButtonTemplate`, `OptionsCheckButtonTemplate`, `OptionsSliderTemplate`) comes off the chain too, giving back the reference's own per-widget hover; one template of ours stays because `UIOptionsFrame.xml` is not built here, and the lesson is general — when a file SHRINKS rather than dies, every harness that named it gains the chain entry beside it, not instead of it

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-02 17:06:36 +07:00

245 lines
8.1 KiB
Rust

//! TexCoords + Font objects (decision 0084).
use super::common::script;
use crate::script::*;
/// `SetTexCoord(l, r, t, b)` writes the region's UV sub-rect; it surfaces on the extracted quad,
/// `GetTexCoord` reads it back; the 8-arg affine form carries per-corner UVs (the route-line
/// draw's rotated quads).
#[test]
fn set_tex_coord_changes_extracted_uv() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
s.run(
r#"
f = CreateFrame("Frame", "TcF")
f:SetWidth(100); f:SetHeight(100); f:SetPoint("CENTER")
t = f:CreateTexture("TcTex", "ARTWORK")
t:SetTexture("Interface\\Foo")
t:SetTexCoord(0.1, 0.6, 0.2, 0.8)
"#,
)
.unwrap();
s.resolve();
let tex = s
.extract()
.into_iter()
.find(|q| matches!(&q.content, QuadContent::Texture { path: Some(p), .. } if p.contains("Foo")))
.expect("the Foo texture quad");
assert!(
matches!(&tex.content, QuadContent::Texture { tex_coords: Some(TexCoords::Rect(tc)), .. } if *tc == [0.1, 0.6, 0.2, 0.8]),
"SetTexCoord surfaces on the extracted quad, got {:?}",
tex.content
);
// EIGHT values, per corner, in `SetTexCoord`'s own usage order — `ULx, ULy, LLx, LLy, URx,
// URy, LRx, LRy` (decision 1840). A Rect stored as `[l, r, t, b]` comes back out as its four
// corners, which is the only shape the reference has: there is no 4-value getter.
let got: (f32, f32, f32, f32, f32, f32, f32, f32) = s.eval("return t:GetTexCoord()").unwrap();
assert_eq!(got, (0.1, 0.2, 0.1, 0.8, 0.6, 0.2, 0.6, 0.8));
// The 8-arg affine form (arg order UL, LL, UR, LR) lands as per-corner UVs in screen order
// [TL, TR, BR, BL] — here a 90° rotation of the full texture.
s.run("t:SetTexCoord(0,1, 1,1, 0,0, 1,0)").unwrap();
s.resolve();
let tex = s
.extract()
.into_iter()
.find(|q| matches!(&q.content, QuadContent::Texture { path: Some(p), .. } if p.contains("Foo")))
.unwrap();
assert!(
matches!(
&tex.content,
QuadContent::Texture { tex_coords: Some(TexCoords::Corners(c)), .. }
if *c == [[0.0, 1.0], [0.0, 0.0], [1.0, 0.0], [1.0, 1.0]]
),
"the affine form carries corners in screen winding, got {:?}",
tex.content
);
// A no-arg reset returns to the full texture.
s.run("t:SetTexCoord()").unwrap();
s.resolve();
let tex = s
.extract()
.into_iter()
.find(|q| matches!(&q.content, QuadContent::Texture { path: Some(p), .. } if p.contains("Foo")))
.unwrap();
assert!(matches!(
&tex.content,
QuadContent::Texture {
tex_coords: None,
..
}
));
}
/// `SetFontObject("Name")` copies a registered [`FontObject`]'s face/height/color onto the
/// FontString; `GetFontObject` hands back the OBJECT (whose `GetName` round-trips); a later call
/// re-points it (the resolved paint on the extracted quad follows). An unknown name errors.
#[test]
fn set_font_object_repoints_fontstring() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
s.register_font_object(
"Big",
FontObject {
font: Some("Fonts\\MORPHEUS.TTF".into()),
height: Some(18.0),
color: Some([0.0, 0.0, 0.0, 1.0]),
outline: Outline::None,
justify_h: None,
justify_v: None,
shadow: None,
},
);
s.register_font_object(
"Small",
FontObject {
font: Some("Fonts\\FRIZQT__.TTF".into()),
height: Some(10.0),
color: Some([1.0, 1.0, 1.0, 1.0]),
outline: Outline::None,
justify_h: None,
justify_v: None,
shadow: None,
},
);
s.run(
r#"
f = CreateFrame("Frame", "FoF")
f:SetWidth(120); f:SetHeight(30); f:SetPoint("CENTER")
fs = f:CreateFontString("FoText", "ARTWORK")
fs:SetText("Hi")
fs:SetFontObject("Big")
"#,
)
.unwrap();
assert_eq!(
s.eval::<String>("return fs:GetFontObject():GetName()")
.unwrap(),
"Big"
);
let resolved = |s: &UiScript| -> (Option<String>, Option<f32>, Option<[f32; 4]>) {
s.extract()
.into_iter()
.find_map(|q| match q.content {
QuadContent::Text {
font,
font_height,
color,
..
} => Some((font, font_height, color)),
_ => None,
})
.expect("a text quad")
};
s.resolve();
assert_eq!(
resolved(&s),
(
Some("Fonts\\MORPHEUS.TTF".into()),
Some(18.0),
Some([0.0, 0.0, 0.0, 1.0])
)
);
// Re-point.
s.run("fs:SetFontObject('Small')").unwrap();
s.resolve();
assert_eq!(
resolved(&s),
(
Some("Fonts\\FRIZQT__.TTF".into()),
Some(10.0),
Some([1.0, 1.0, 1.0, 1.0])
)
);
// An unknown font object errors (never a silent no-op).
assert!(s.run("fs:SetFontObject('Nope')").is_err());
}
/// `justifyV`: MIDDLE is the default (the client's own FontString default — what seats the money
/// numbers on their coins' centerline), `SetJustifyV` overrides it, and a font object carrying one
/// applies it on `SetFontObject`.
#[test]
fn justify_v_defaults_middle_and_overrides() {
let mut s = script();
s.set_screen_size(800.0, 600.0);
s.run(
r#"
f = CreateFrame("Frame", "JvF")
f:SetWidth(100); f:SetHeight(30); f:SetPoint("CENTER")
fs = f:CreateFontString("JvText", "ARTWORK")
fs:SetText("Hi")
"#,
)
.unwrap();
let justify_v = |s: &UiScript| {
s.extract()
.into_iter()
.find_map(|q| match q.content {
QuadContent::Text { justify_v, .. } => Some(justify_v),
_ => None,
})
.expect("a text quad")
};
s.resolve();
assert_eq!(justify_v(&s), JustifyV::Middle, "the client default");
s.run("fs:SetJustifyV('BOTTOM')").unwrap();
s.resolve();
assert_eq!(justify_v(&s), JustifyV::Bottom);
// A font object carrying justify_v applies it — but ONLY on the axis this string has not
// already claimed for itself. `SetJustifyV` above severed the V axis, and §5-verified that
// severance is permanent: the reference clears the axis's inheritMask bit (`+0x124`, the
// per-axis justify mask — wow-re `system/ui/scratch/font-object-lua-surface.md`) and never
// restores it, so a later `SetFontObject` cannot take the axis back.
//
// This assertion used to read `Top`. That was our copy-everything model, not the client's; the
// shipped XML path is unaffected either way, because the loader applies `inherits=` BEFORE the
// element's own `justifyV=` (`Loader::apply_fontstring_font`), so the explicit attribute still
// wins.
s.register_font_object(
"TopFont",
FontObject {
font: Some("Fonts\\FRIZQT__.TTF".into()),
height: Some(12.0),
color: None,
outline: Outline::None,
justify_h: None,
justify_v: Some(JustifyV::Top),
shadow: None,
},
);
s.run("fs:SetFontObject('TopFont')").unwrap();
s.resolve();
assert_eq!(justify_v(&s), JustifyV::Bottom, "severance is permanent");
// A string that never claimed the axis DOES take the object's justification.
s.run(
r#"
fresh = f:CreateFontString(nil, "ARTWORK")
fresh:SetText("Fresh")
fresh:SetFontObject('TopFont')
"#,
)
.unwrap();
s.resolve();
let fresh = s
.extract()
.into_iter()
.find_map(|q| match q.content {
QuadContent::Text {
text: Some(ref t),
justify_v,
..
} if t == "Fresh" => Some(justify_v),
_ => None,
})
.expect("a text quad");
assert_eq!(fresh, JustifyV::Top);
}