nameplates are frames, guild names go overhead, and the camera pivots

- a nameplate is a real frame under WorldFrame, six regions and a health bar in the reference's order, so pfUI, ShaguTweaks and _Nameplates find and restyle plates instead of seeing none; the plate stays out of the mouse's way in this cut (2148)
- a player's guild shows under their overhead name in angle brackets, the line an NPC's subname already used, gated by the UnitNamePlayerGuild option at its stock on (2149)
- smart camera pivot is on, as 1.12 ships it: a mostly vertical drag with the camera pressed against something tilts the view instead of swinging the arm, easing home on release; in water the pivot rises for a swimmer (2149)
- Follow Terrain and head bob are built and ship off, as in 1.12: Follow Terrain leans the camera up or down the slope you are walking onto, and head bob sways the first-person eye in a figure of eight while you move (2149)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-11 20:55:15 +07:00
co-authored by Claude Fable 5.1
parent 7d100d8a36
commit 38e45badb9
20 changed files with 4166 additions and 548 deletions
@@ -28,6 +28,7 @@ mod model_clock;
mod modelframe;
mod movable;
mod name_targets;
mod nameplate;
mod object_model;
mod reference_surface;
mod region_map;
@@ -0,0 +1,489 @@
//! The nameplate ABI, as the four vanilla addons actually read it (decision 2148).
//!
//! These are not "does the pool work" tests. Each is a transcription of a real corpus idiom — the
//! same walk, the same destructuring, the same identification test — so a change that would break
//! a shipped addon fails **here** rather than on the director's screen.
//!
//! Sources, all `## Interface: 11200`: `pfUI/modules/nameplates.lua` (+ `compat/vanilla.lua`),
//! `ShaguTweaks/libs/libnameplate.lua`, `CustomNameplates/CustomNameplates.lua`,
//! `_Nameplates/_Nameplates.lua`. The byte side is wow-re
//! `system/ui/scratch/nameplate-lua-surface.md` (§5, 2026-09-09).
use crate::script::{PlateGeometry, PlateState, UiScript};
/// A VM with a `WorldFrame` to hang plates off — the only thing [`UiScript::sync_nameplates`]
/// requires of a session.
fn vm() -> UiScript {
let mut s = UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
s.run(r#"WorldFrame = CreateFrame("WorldFrame", "WorldFrame") WorldFrame:SetAllPoints()"#)
.unwrap();
s.resolve();
s
}
/// The plate size at the capture window (`vplates` scenario: one gx unit = 1280 px, so the
/// 0.1 × 0.025 plate is 128 × 32 px, and at seam scale 1.0 that is 128 × 32 units).
fn geometry() -> PlateGeometry {
PlateGeometry {
width: 128.0,
height: 32.0,
bar_off_x: 4.0,
bar_off_y: 4.0,
bar_width: 103.0,
bar_height: 9.0,
level_off_x: 11.8,
level_off_y: 9.1,
skull_size: 12.8,
raid_size: 25.6,
name_height: 12.8,
level_height: 11.0,
}
}
fn plate(name: &str, health: f32, max: f32) -> PlateState {
PlateState {
// The unit's GUID stands in as a hash of its name: what matters to these tests is that the
// same unit keeps the same key across frames and two units never share one.
key: name.bytes().map(u64::from).sum(),
top_centre: (500.0, 400.0),
health,
max_health: max,
bar_colour: [1.0, 0.0, 0.0],
name: name.to_string(),
level: Some(12),
level_colour: [1.0, 1.0, 0.0],
raid_icon: None,
alpha: 1.0,
lit: false,
hovered: false,
}
}
/// Drive N plates and settle the layout, the way one app frame does.
fn drive(s: &mut UiScript, states: &[PlateState]) {
s.sync_nameplates(geometry(), states);
s.resolve();
}
/// pfUI + ShaguTweaks share one lineage: `GetNumChildren` grew, so walk the tail.
///
/// `initialized` is never reset in either addon, so a client whose child list SHRANK or REORDERED
/// would make them miss every plate created after the old high-water mark — permanently, and with
/// no error. This is the test that a retired plate keeps its slot.
#[test]
fn the_worldframe_child_list_only_grows_and_keeps_its_order() {
let mut s = vm();
assert_eq!(
s.eval::<i64>("return WorldFrame:GetNumChildren()").unwrap(),
0
);
drive(
&mut s,
&[plate("Wolf", 30.0, 40.0), plate("Bear", 10.0, 90.0)],
);
assert_eq!(
s.eval::<i64>("return WorldFrame:GetNumChildren()").unwrap(),
2
);
// One unit leaves: the plate is HIDDEN, and the child list does not shrink.
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert_eq!(
s.eval::<i64>("return WorldFrame:GetNumChildren()").unwrap(),
2
);
assert!(s
.eval::<bool>(
"local a, b = WorldFrame:GetChildren() return a:IsShown() == 1 and b:IsShown() == nil"
)
.unwrap());
// A third unit arrives: it takes the pooled plate back — the count goes to 2, not 3.
drive(
&mut s,
&[plate("Wolf", 30.0, 40.0), plate("Boar", 5.0, 50.0)],
);
assert_eq!(
s.eval::<i64>("return WorldFrame:GetNumChildren()").unwrap(),
2
);
assert_eq!(s.nameplate_pool_len(), 2);
}
/// pfUI's `IsNamePlate` (`nameplates.lua:70`) and ShaguTweaks' (`libnameplate.lua:12`), verbatim in
/// shape: the object type is `"Button"`, and region #1 is a `Texture` whose `GetTexture()` is
/// exactly `Interface\Tooltips\Nameplate-Border`.
///
/// `CustomNameplates` runs the texture half against **every** WorldFrame child
/// (`CustomNameplates.lua:93`), so `GetRegions()` must also be safe to call on a frame with none.
#[test]
fn a_plate_identifies_by_button_plus_the_border_texture() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert!(s
.eval::<bool>(
r#"
local plate = WorldFrame:GetChildren()
if plate:GetObjectType() ~= "Button" then return false end
local region = plate:GetRegions()
if not region or not region.GetTexture then return false end
if region:GetObjectType() ~= "Texture" then return false end
return region:GetTexture() == "Interface\\Tooltips\\Nameplate-Border"
"#
)
.unwrap());
}
/// The two tuples, positionally — the ABI decision 2148 §3 records.
///
/// `CustomNameplates.lua:212` binds `Border, Glow, Name, Level, Boss, RaidTargetIcon`; pfUI and
/// ShaguTweaks bind the same six by the `NAMEPLATE_OBJECTORDER` table. A **seventh** region is a
/// bug (pfUI blanks any extra with `SetTexture("")`), and a **second** child is a bug too (pfUI
/// destructures `healthbar, castbar` and would blank ours as a TBC cast bar).
#[test]
fn the_region_and_child_tuples_are_six_and_one() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert_eq!(
s.eval::<i64>("local p = WorldFrame:GetChildren() return p:GetNumRegions()")
.unwrap(),
6
);
assert_eq!(
s.eval::<i64>("local p = WorldFrame:GetChildren() return p:GetNumChildren()")
.unwrap(),
1
);
assert!(s
.eval::<bool>(
r#"
local p = WorldFrame:GetChildren()
local border, glow, name, level, levelicon, raidicon = p:GetRegions()
return border:GetTexture() == "Interface\\Tooltips\\Nameplate-Border"
and glow:GetTexture() == "Interface\\Tooltips\\Nameplate-Glow"
and name:GetObjectType() == "FontString"
and level:GetObjectType() == "FontString"
and levelicon:GetTexture() == "Interface\\TargetingFrame\\UI-TargetingFrame-Skull"
and raidicon:GetTexture() == "Interface\\TargetingFrame\\UI-RaidTargetingIcons"
"#
)
.unwrap());
// ShaguTweaks `libnameplate.lua:39`: `plate.healthbar = plate:GetChildren()` — one child, and
// it is the StatusBar. Its own single region is the fill `GetStatusBarTexture()` hands back.
assert!(s
.eval::<bool>(
r#"
local p = WorldFrame:GetChildren()
local healthbar = p:GetChildren()
if healthbar:GetObjectType() ~= "StatusBar" then return false end
if healthbar:GetNumRegions() ~= 1 then return false end
local fill = healthbar:GetStatusBarTexture()
return fill:GetTexture() == "Interface\\TargetingFrame\\UI-TargetingFrame-BarFill"
"#
)
.unwrap());
}
/// `_Nameplates.Nameplate.Initialize` (`_Nameplates.lua:163`) is the strictest consumer: it
/// content-addresses the regions and **validates the two FontString anchors**, rejecting any plate
/// whose name is not `BOTTOM`→`CENTER` and whose level is not `CENTER`→`BOTTOMRIGHT`. Those are the
/// reference's own (ctor `0x7cb456`, bind `0x7cb7f5`).
#[test]
fn the_two_fontstring_anchors_are_what_nameplates_validates() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert!(s
.eval::<bool>(
r#"
local plate = WorldFrame:GetChildren()
local Name, Level
for _, region in ipairs({ plate:GetRegions() }) do
if region:GetObjectType() == "FontString" then
local point, _, relativePoint = region:GetPoint()
if point == "BOTTOM" and relativePoint == "CENTER" then Name = region end
if point == "CENTER" and relativePoint == "BOTTOMRIGHT" then Level = region end
end
end
return Name ~= nil and Level ~= nil
"#
)
.unwrap());
}
/// The bar reports **raw** health, not a fraction — `GetValue 0x78f5d0` → `[bar+0x320]`, and
/// `GetMinMaxValues` → `(0, maxHealth)`. pfUI (`:595`) and CustomNameplates (`:471`) both divide,
/// so a fraction here would read as full health on every plate.
///
/// This one corrected wow-re's own note: `nameplate-vkey.md` §4 had recorded `0x783380`, which is
/// the internal fill *fraction*, as the Lua getter.
#[test]
fn the_healthbar_reports_raw_health_and_its_max() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert!(s
.eval::<bool>(
r#"
local healthbar = WorldFrame:GetChildren():GetChildren()
local min, max = healthbar:GetMinMaxValues()
return healthbar:GetValue() == 30 and min == 0 and max == 40
"#
)
.unwrap());
}
/// The plate is anonymous. `_Nameplates.lua:164` rejects any WorldFrame child with a name, and
/// pfUI's bubble filter does the same — a named plate is invisible to both.
#[test]
fn a_plate_is_anonymous() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert!(s
.eval::<bool>("return WorldFrame:GetChildren():GetName() == nil")
.unwrap());
}
/// The pooling contract: a retired plate comes back as the **same Lua object**. Every addon keys
/// its per-plate state off exactly that identity — pfUI's `registry[plate]` (`:342`),
/// `_Nameplates.Frames[Nameplate]` (`:205`) — and re-runs `OnCreate` for an object it has not seen.
#[test]
fn a_retired_plate_returns_as_the_same_lua_object() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
s.run("FirstPlate = WorldFrame:GetChildren()").unwrap();
drive(&mut s, &[]);
drive(&mut s, &[plate("Bear", 10.0, 90.0)]);
assert!(s
.eval::<bool>("return WorldFrame:GetChildren() == FirstPlate")
.unwrap());
}
/// **A plate belongs to its UNIT, not to this frame's sort order.**
///
/// The driver hands plates over in distance-sorted order — the seat priority the reference solves
/// overlap in — and that order changes whenever two units cross. If the pool were indexed by it,
/// widget #1 would swap units mid-session, and every addon that caches state on the frame would be
/// reading the other unit's: pfUI's `nameplate.original.name`/`level`, its `registry[plate]`
/// bookkeeping, `_Nameplates`' per-plate button. So the pool is keyed by the unit's GUID, and this
/// is the test that says so.
#[test]
fn a_plate_belongs_to_its_unit_across_a_sort_order_swap() {
let mut s = vm();
drive(
&mut s,
&[plate("Wolf", 30.0, 40.0), plate("Bear", 10.0, 90.0)],
);
s.run(
r#"
WolfPlate = nil
for _, p in ipairs({ WorldFrame:GetChildren() }) do
local _, _, name = p:GetRegions()
if name:GetText() == "Wolf" then WolfPlate = p end
end
"#,
)
.unwrap();
assert!(s.eval::<bool>("return WolfPlate ~= nil").unwrap());
// The two units cross: the driver now sorts Bear first. Nothing about either plate's identity
// may move with it.
drive(
&mut s,
&[plate("Bear", 10.0, 90.0), plate("Wolf", 30.0, 40.0)],
);
assert!(s
.eval::<bool>(
r#"
local _, _, name = WolfPlate:GetRegions()
return name:GetText() == "Wolf"
"#
)
.unwrap());
}
/// `plate:GetAlpha() == 1` is how pfUI (`:879`) and CustomNameplates (`:287`) detect the TARGET
/// plate: with a target up, every other plate is dimmed. So alpha is a contract, not a look.
#[test]
fn the_target_plate_is_the_opaque_one() {
let mut s = vm();
let mut target = plate("Wolf", 30.0, 40.0);
let mut other = plate("Bear", 10.0, 90.0);
target.alpha = 1.0;
other.alpha = 0.5;
drive(&mut s, &[target, other]);
assert!(s
.eval::<bool>(
"local a, b = WorldFrame:GetChildren() return a:GetAlpha() == 1 and b:GetAlpha() < 1"
)
.unwrap());
}
/// pfUI hooks the bar's `OnValueChanged` (`:393`) and reads `this:GetParent()` inside the handler
/// to find the plate. The driver moves that value from Rust, so the handler has to fire from there
/// too — the reference's own bar is driven by a GUID-watch callback and fires it the same way.
#[test]
fn an_engine_health_write_fires_onvaluechanged_with_the_plate_as_parent() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
s.run(
r#"
Fired = 0
ParentIsPlate = false
local plate = WorldFrame:GetChildren()
local healthbar = plate:GetChildren()
healthbar:SetScript("OnValueChanged", function()
Fired = Fired + 1
ParentIsPlate = (this:GetParent() == plate)
end)
"#,
)
.unwrap();
drive(&mut s, &[plate("Wolf", 12.0, 40.0)]);
assert_eq!(s.eval::<i64>("return Fired").unwrap(), 1);
assert!(s.eval::<bool>("return ParentIsPlate").unwrap());
// Unmoved health fires nothing: the driver writes only what changed.
drive(&mut s, &[plate("Wolf", 12.0, 40.0)]);
assert_eq!(s.eval::<i64>("return Fired").unwrap(), 1);
}
/// **An addon takes the plate over.** pfUI blanks all six regions (`DisableObject` →
/// `SetTexture("")`) and paints its own art; ShaguTweaks reparents the raid icon. A driver that
/// re-stamped every property every frame would undo that at 60 Hz — so static properties are
/// written once, at creation, and never again.
#[test]
fn the_driver_does_not_overwrite_an_addons_takeover() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
s.run(
r#"
local plate = WorldFrame:GetChildren()
for _, region in ipairs({ plate:GetRegions() }) do
if region.SetTexture then region:SetTexture("") end
end
"#,
)
.unwrap();
// Several frames of ordinary driving — health moving, the unit renamed, the plate re-seated.
for hp in [29.0, 28.0, 27.0] {
let mut state = plate("Wolf", hp, 40.0);
state.top_centre = (500.0 + hp, 400.0);
drive(&mut s, &[state]);
}
assert!(s
.eval::<bool>(
r#"
local border = WorldFrame:GetChildren():GetRegions()
return border:GetTexture() == "" or border:GetTexture() == nil
"#
)
.unwrap());
}
/// pfUI reads `glow:IsShown()` as the MOUSEOVER signal (`:601`, `:880`). benilla does not paint the
/// additive rim (0184 — it read as hard edge lines on our pipeline, and the director pinned a bar
/// brighten instead), but the region is structurally present and really shown, because the
/// deviation is about pixels and this is about data.
#[test]
fn the_glow_region_is_the_mouseover_signal() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
assert!(s
.eval::<bool>(
"local _, glow = WorldFrame:GetChildren():GetRegions() return glow:IsShown() == nil"
)
.unwrap());
let mut hovered = plate("Wolf", 30.0, 40.0);
hovered.hovered = true;
drive(&mut s, &[hovered]);
assert!(s
.eval::<bool>(
"local _, glow = WorldFrame:GetChildren():GetRegions() return glow:IsShown() == 1"
)
.unwrap());
}
/// The level number and the skull share one seat and are mutually exclusive — `level = None` is
/// how the driver says "world boss, or ten levels up". pfUI gates on `levelicon:IsShown()` and
/// CustomNameplates on `Boss:IsVisible()`, so both flags have to move.
#[test]
fn the_skull_replaces_the_level_number() {
let mut s = vm();
let mut skulled = plate("Wolf", 30.0, 40.0);
skulled.level = None;
drive(&mut s, &[skulled]);
assert!(s
.eval::<bool>(
r#"
local _, _, _, level, levelicon = WorldFrame:GetChildren():GetRegions()
return level:IsShown() == nil and levelicon:IsShown() == 1
and levelicon:IsVisible() == 1
"#
)
.unwrap());
}
/// An addon's own frame parented to `WorldFrame` shares the list with the plates —
/// `_NameplatesFrame` and `pfUICombatScreen` both are — which is why the identification test in
/// §2 is load-bearing and why the count-grew heuristic re-scans. It must not be mistaken for one.
#[test]
fn an_addon_frame_under_the_worldframe_is_not_a_plate() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
s.run(r#"CreateFrame("Frame", "pfUICombatScreen", WorldFrame)"#)
.unwrap();
assert_eq!(
s.eval::<i64>("return WorldFrame:GetNumChildren()").unwrap(),
2
);
assert_eq!(
s.eval::<i64>(
r#"
local found = 0
for _, frame in ipairs({ WorldFrame:GetChildren() }) do
local region = frame:GetRegions()
if frame:GetObjectType() == "Button" and region and region.GetTexture
and region:GetTexture() == "Interface\\Tooltips\\Nameplate-Border" then
found = found + 1
end
end
return found
"#
)
.unwrap(),
1
);
}
/// **The plate does NOT claim the UI pointer — not yet, and deliberately.**
///
/// The reference's plate is a mouse-enabled `Button`: hovering it publishes the mouseover
/// (`0x7cb850` OnEnter → `0x492890`) and a click selects the unit through the button's own click
/// slot. benilla cannot simply set that bit, because on this engine the UI pointer pass owning a
/// frame is what makes `PointerOverUi` true, and `target::hover::update_hover` returns early on
/// exactly that — so a mouse-enabled plate with no engine-side seam takes the hover and gives
/// nothing back: no model brighten, no lit bar, and no selection on click, because the click never
/// reaches the world path either.
///
/// So the bit stays off until the seam exists (2148 §8), and this test is the tripwire: turning it
/// on without wiring the hover and click through the engine fails here, in the ABI suite, rather
/// than under the director's cursor.
#[test]
fn a_plate_does_not_yet_claim_the_ui_pointer() {
let mut s = vm();
drive(&mut s, &[plate("Wolf", 30.0, 40.0)]);
// The plate's TOP-CENTRE is (500, 400) and it hangs 32 units below, so this is its middle.
// `mouse_move` speaks the same y-up FrameXML space the anchors do.
//
// The WorldFrame itself IS a legitimate hit here — it is mouse-enabled by construction, and
// the app filters exactly that one id out (`input::feed_ui_input`, decision 1983) so camera
// look, world clicks and hover targeting stay live over it. So the assertion mirrors the
// app's own filter: whatever the pointer lands on must be the WorldFrame, never a plate.
let focus = s.mouse_move(500.0, 384.0);
assert!(
focus.is_none_or(|id| s.is_world_frame(id)),
"a plate claimed the UI pointer: the hover and click seams (2148 §8) have to land in the \
same change that enables the mouse, or plate hover and click-select both die"
);
}