Files
benilla-octo/crates/benilla-ui/src/script/tests/region_map.rs
T
SamandClaude Fable 5 ad2a1fb2d1 Quiver loads, teleports stop losing the city, the camera eases right
- Quiver works end to end (the reported nil-field error): 1.12's widget method tables are flat, so the 19 Region methods are ONE function per name — a frame's `GetHeight` IS the texture's — where we had two implementations behind two disjoint registries; `Button:SetFontString` and the Set/GetMin/MaxResize quad land with it, and the 219-addon survey moves loads-without-error 150 → 151 with every other addon byte-identical
- a teleport stops dropping you into a bare city: the reveal gate now waits on geometry that can actually draw (14 regions had sat undrawn for 630 ms with the cover already down), a relocating snap ≥100 yd raises the cover, and the pipeline warm pass runs once per process — a far same-map teleport's cover drops 3.8 s → 0.8 s
- the empty-world flash on a fully-resident teleport dies with it: the camera's world pose publishes before anything in Update reads it, so the visibility verdict stops being computed up to 644 yd from the eye — the lag reads 0 yd on every snap frame of every arm
- the camera follow becomes the engine's own curve — an edge-armed cosine smoothstep clamped to 0.1–2 s behind a 7-state input classifier, the enum 0/1/2 with Smart staying the default — and overlapping chat bubbles stack as whole cards, one frame level per bubble, nearest to the camera on top

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-21 18:10:46 +07:00

174 lines
7.0 KiB
Rust

//! **The Region map is ONE function per name, reached by frames and regions alike** — the
//! [`crate::script::region_map`] law, and the regression guard for bug B267.
//!
//! Its header carries the byte evidence (`Frame ∩ Region = ∅`, parsed out of the two `.data`
//! method tables). What is pinned here is the two things a reader of the code could not otherwise
//! check: that the sharing is real *by identity*, and that it stops exactly at the 19.
//!
//! The reported symptom this file exists for: Quiver's `Api/Index.wow.lua` opens
//! `_Height = WorldFrame.GetHeight`, applies it to a Texture and a FontString, and benilla raised
//! `stale or invalid frame handle` — inside the addon's `VARIABLES_LOADED` handler, four lines
//! before it published `Quiver.CastPetAction`. The addon's own field was nil at macro time because
//! the handler never finished, which is what got filed.
use crate::script::UiScript;
/// A VM with one frame, one texture and one fontstring, each reachable by a global name.
fn vm() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
s.run(
r#"
Host = CreateFrame("Frame", "Host")
Host:SetWidth(200) Host:SetHeight(120)
Host:SetPoint("CENTER", nil, "CENTER", 0, 0)
Tex = Host:CreateTexture("HostTex", "ARTWORK")
Tex:SetWidth(64) Tex:SetHeight(32)
Tex:SetPoint("TOPLEFT", Host, "TOPLEFT", 0, 0)
Str = Host:CreateFontString("HostStr", "OVERLAY")
Str:SetWidth(50) Str:SetHeight(10)
Str:SetPoint("BOTTOMRIGHT", Host, "BOTTOMRIGHT", 0, 0)
"#,
)
.unwrap();
s
}
/// Every one of the 19 is the **same Lua value** on a Frame, a Texture and a FontString.
///
/// Not a stand-in for the behaviour tests below: identity is what makes the pulled-off-method
/// idiom work *for a method the test author never thought of*. In the client it holds because
/// Frame's table does not carry any of the 19 and its lookup tail-calls Region's; here it holds
/// because one function is written into every table that chain reaches.
#[test]
fn the_region_map_is_one_function_on_every_widget() {
let s = vm();
for m in crate::script::REGION_MAP_METHODS {
let same: bool = s
.eval(&format!(
"return Host.{m} == Tex.{m} and Tex.{m} == Str.{m} and Host.{m} ~= nil"
))
.unwrap();
assert!(
same,
"{m} must be ONE function on frame, texture and string"
);
}
}
/// **The bug, reduced to its four lines.** A method pulled off a frame and applied to a region.
#[test]
fn a_method_pulled_off_a_frame_works_on_a_texture_and_a_fontstring() {
let s = vm();
// Quiver's own shape: `Api._Height = WorldFrame.GetHeight`, applied to `r.Icon` (a Texture)
// and `r.Label` (a FontString), from a table built at file scope.
let (th, sh): (f32, f32) = s
.eval(
r#"
local _Height = Host.GetHeight
return _Height(Tex), _Height(Str)
"#,
)
.unwrap();
assert_eq!((th, sh), (32.0, 10.0));
let (tw, sw): (f32, f32) = s
.eval(
r#"
local _Width = Host.GetWidth
return _Width(Tex), _Width(Str)
"#,
)
.unwrap();
assert_eq!((tw, sw), (64.0, 50.0));
// …and the setter half, which is the same split and would have been the next wall.
s.run("local _SetW = Host.SetWidth _SetW(Tex, 99)").unwrap();
assert_eq!(s.eval::<f32>("return Tex:GetWidth()").unwrap(), 99.0);
}
/// The other direction — a method pulled off a **region** and applied to a **frame**. Same one
/// function, so this cannot be made to work by special-casing the reported direction.
#[test]
fn a_method_pulled_off_a_texture_works_on_a_frame() {
let s = vm();
let h: f32 = s.eval("local g = Tex.GetHeight return g(Host)").unwrap();
assert_eq!(h, 120.0);
let name: String = s.eval("local n = Str.GetName return n(Host)").unwrap();
assert_eq!(name, "Host");
let kind: String = s
.eval("local t = Tex.GetObjectType return t(Host)")
.unwrap();
assert_eq!(kind, "Frame");
}
/// Each receiver keeps its OWN behaviour behind the shared name — the per-kind arms are not
/// collapsed into one. `GetObjectType` is the cleanest witness (three different answers), and
/// `GetParent` is the one whose two arms genuinely differ (a region's owner is never nil).
#[test]
fn one_name_still_dispatches_per_kind() {
let s = vm();
let (f, t, g): (String, String, String) = s
.eval("return Host:GetObjectType(), Tex:GetObjectType(), Str:GetObjectType()")
.unwrap();
assert_eq!(
(f.as_str(), t.as_str(), g.as_str()),
("Frame", "Texture", "FontString")
);
// The region arm answers its OWNER; the frame arm answers nil for a parentless top frame.
let owner: String = s.eval("return Tex:GetParent():GetName()").unwrap();
assert_eq!(owner, "Host");
assert!(s.eval::<bool>("return Host:GetParent() == nil").unwrap());
}
/// `GetNumPoints` — on the Region map, so **every** widget answers it. The frame arm did not
/// exist until the map was collapsed to one implementation; the region arm shipped alone.
#[test]
fn get_num_points_answers_on_a_frame_too() {
let s = vm();
assert_eq!(s.eval::<i64>("return Host:GetNumPoints()").unwrap(), 1);
assert_eq!(s.eval::<i64>("return Tex:GetNumPoints()").unwrap(), 1);
s.run("Host:SetPoint(\"TOPLEFT\", nil, \"TOPLEFT\", 0, 0)")
.unwrap();
assert_eq!(s.eval::<i64>("return Host:GetNumPoints()").unwrap(), 2);
s.run("Host:ClearAllPoints()").unwrap();
assert_eq!(s.eval::<i64>("return Host:GetNumPoints()").unwrap(), 0);
}
/// **The carve's other edge, and the control that keeps it honest.** These six look like they
/// belong to the Region map and do not: Frame, Texture and FontString each register their *own*
/// at different addresses (`texture-fontstring-method-split.md` §3 — Texture `SetAlpha 0x79b580`
/// vs FontString `0x79cb70`, and Frame's own `0x79b...` twin). So `WorldFrame.Show(someTexture)`
/// fails on the real client, and hoisting them here to make more addon code work would be the
/// superset decision 1189 had to take back out.
#[test]
fn the_six_look_alikes_are_not_shared() {
let s = vm();
for m in [
"Show",
"Hide",
"IsShown",
"IsVisible",
"SetAlpha",
"GetAlpha",
] {
let shared: bool = s.eval(&format!("return Host.{m} == Tex.{m}")).unwrap();
assert!(
!shared,
"{m} is registered per class in 1.12 and must NOT be hoisted onto the Region map"
);
}
}
/// A receiver that is not a widget at all raises, and says *why* — the bridge resolves the
/// receiver before either arm runs, so this error must not be flattened into a generic one.
#[test]
fn a_non_widget_receiver_raises_and_names_the_reason() {
let s = vm();
let err = s
.eval::<f32>("return Host.GetHeight({})")
.unwrap_err()
.to_string();
assert!(err.contains("T[0] identity"), "unexpected error: {err}");
}