//! **The nameplate widgets** — `CGNamePlateFrame` as the frame-system object it actually is //! (decision 2148; wow-re `system/ui/scratch/nameplate-lua-surface.md`, §5 eight-worker round + //! orchestrator byte arbitration 2026-09-09, benilla dispatch). //! //! benilla drew V-plates for a year as quads pushed straight into the UI pass, which made them //! invisible to Lua: a live run's `WorldFrame:GetChildren()` returned the two named FrameXML //! frames and nothing else, and every 1.12 nameplate addon — all of which find plates by walking //! exactly that list — found nothing to work with. The reference does the opposite, and this //! module is that difference closed. //! //! ## What the reference builds, byte for byte //! //! The plate is a `CSimpleButton` subclass (ctor `0x7cb250`, `…\Ui\NamePlateFrame.cpp`, vtable //! `0x81de50`, size `0x518`) whose **base constructor parents it to the `CGWorldFrame` singleton** //! `ds:0xb4b2bc` (`0x7786a3` → `0x769318` → `SetParent 0x76ab10`, tail-appending at `0x76abdb`) — //! so it is a real member of `[worldframe+0x300]`, returned by `GetChildren` and counted by //! `GetNumChildren`, inheriting strata **0 (WORLD)** and taking level **1**. //! //! - **Identity:** `GetObjectType()` = `"Button"`, `IsObjectType` accepts Button/Frame/Region, and //! `GetName()` is **nil** — `[this+0x98]` is zeroed at `0x76c50b` and only XML `name=` ever //! writes it. Anonymity is load-bearing: `_Nameplates` and pfUI both reject a *named* //! `WorldFrame` child. //! - **Six regions, in creation order**: Border `0x7cb2c2`, Glow `0x7cb361`, Name `0x7cb3d7`, //! Level `0x7cb479`, Skull `0x7cb512`, RaidIcon `0x7cb5b4`. //! - **One child**, the health bar (`GetObjectType()` = `"StatusBar"`), whose own single region is //! the BarFill texture `GetStatusBarTexture()` returns. **No cast bar exists in 1.12** — a second //! child would be blanked by pfUI, which destructures `healthbar, castbar`. //! - **Real anchors.** The ctor writes them through `0x767c70` — the *same* primitive and the same //! `[layoutObj + 4 + 4·point]` storage the Lua `SetPoint` binding ends in — so `GetPoint`, //! `GetLeft` and `GetWidth` answer truthfully. Name = `("BOTTOM", plate, "CENTER", 0, 0)` //! (`0x7cb456`), Level = `("CENTER", plate, "BOTTOMRIGHT", …)` (`0x7cb7f5`); `_Nameplates` //! *validates* both pairs and rejects a plate that fails them. //! - **Levels, and this is the surprise:** the bar is put **below** the plate, not above it — //! `0x7cb32e mov edx,[plate+0xc4]` (=1) · `0x7cb33c dec edx` · `0x7cb33e call 0x76a4f0(bar,0,0)`, //! an absolute level that overrides what `SetParent` had just given it. Plate level 1, **bar //! level 0**. Since the draw walk's layer loop is *outer* and its frame walk *inner* (`0x765920`: //! layer counter `0x76593c`, frame walk `0x7659d0`–`0x7659f8`, `cmp eax,5` at `0x765a63`), the //! whole bar frame drains before any plate layer — which is why the border's bevels cap the //! fill's ends. The director pinned that look off a reference crop while `nameplate-vkey.md` §7 //! recorded the opposite order; the §5 round settled it in the director's favour and corrected //! the note. //! - **Layers** (`GetDrawLayer 0x79a6c0`, table `0x811a80`): Border ARTWORK, Glow **HIGHLIGHT**, //! Name/Level/Skull OVERLAY (the ctor re-layers Name and Level off ARTWORK at `0x7cb438`/ //! `0x7cb4ea`), RaidIcon ARTWORK, BarFill ARTWORK on the bar. **No sub-level exists in 5875**; //! within a layer it is creation order. //! - **Scripts:** the engine installs none, and every C++ override tails into the base //! unconditionally, so an addon's `SetScript` on OnShow/OnHide/OnUpdate/OnEnter/OnLeave/ //! OnMouseDown/OnMouseUp/OnClick fires. `RegisterForClicks(0x500)` = Left/Right on mouse-**up**. //! - **Pooling:** destroy (`0x608a10`) never unparents — it hides and pushes onto a free list, and //! reuse re-runs neither the ctor nor `SetParent`. So **the child-list index is stable for the //! session**, and a retired plate is still returned *and counted* (the list walk has no //! visibility filter). Every corpus addon's `registry[plate]` identity map rests on that. //! //! ## The consumer contract this has to keep //! //! Four independent 1.12 addons (`_Nameplates`, `CustomNameplates`, pfUI's vanilla branch, //! ShaguTweaks' vanilla branch — all `## Interface: 11200`) destructure the two tuples //! *positionally* and identify a plate by `regions[1]:GetTexture()` being exactly //! `Interface\Tooltips\Nameplate-Border`. The byte-derived order and the corpus's agree element for //! element — an independent empirical control on a byte derivation. So the composition is not ours //! to choose; it is an ABI, and the tests transcribe each addon's own walk. //! //! **An addon takes the plate over**, which is the half a painter never had to respect: pfUI blanks //! all six regions and paints its own, sets `SetAlpha(.95)` on non-targets itself, and ShaguTweaks //! reparents the raid icon. So [`Plate::create`] writes the static properties (paths, fonts, //! layers) exactly **once**, [`Plate::lay_out`] rewrites the window-derived anchors only when the //! window moves, and [`Plate::drive`] writes only what actually changed this frame. The one //! deliberate exception is the plate's own anchor, which the reference itself re-issues every dirty //! frame from the seat `0x509ec0` (`ClearAllPoints` + `SetPoint`), wiping an addon anchor *on the //! plate* there too — which is why every corpus addon anchors its overlay **to** the plate and //! re-anchors the **bar**, never the plate. use crate::layout::{Anchor, Point}; use crate::order::DrawLayer; use crate::widget::{FrameHandle, FrameKind, KindState, RegionHandle, RegionKind}; use super::model::Model; use super::UiScript; use super::{FontShadow, JustifyV, TexCoords}; /// Region 1's art — and the identification test every corpus addon runs, which is why it is a /// constant here rather than a caller's argument. pub const BORDER_TEXTURE: &str = "Interface\\Tooltips\\Nameplate-Border"; /// Region 2's. `glow:IsShown()` is how pfUI reads *mouseover* (`nameplates.lua:601`). pub const GLOW_TEXTURE: &str = "Interface\\Tooltips\\Nameplate-Glow"; /// Region 5's — the boss / out-of-range skull (`levelicon` to pfUI, `Boss` to CustomNameplates). pub const SKULL_TEXTURE: &str = "Interface\\TargetingFrame\\UI-TargetingFrame-Skull"; /// Region 6's 4-column atlas. pub const RAID_ICON_TEXTURE: &str = "Interface\\TargetingFrame\\UI-RaidTargetingIcons"; /// The health bar's fill — what `healthbar:GetStatusBarTexture()` hands back. pub const BAR_FILL_TEXTURE: &str = "Interface\\TargetingFrame\\UI-TargetingFrame-BarFill"; /// `NAMEPLATE_FONT` — Friz Quadrata, the plate's own face. pub const PLATE_FONT: &str = "Fonts\\FRIZQT__.TTF"; /// The plate's frame level, and the bar's — `plate.level − 1`, absolute and clamped ≥ 0 /// (`0x7cb32e`/`0x7cb33c`/`0x7cb33e` → `0x76a4f0`). The pair is what puts the border over the fill. const PLATE_LEVEL: u16 = 1; const BAR_LEVEL: u16 = 0; /// Everything about a plate's geometry that follows from the *window* rather than from the unit, /// in FrameXML units (the driver has already divided out the seam scale). /// /// Applied to every pooled plate at once, and only when it changes — these are exactly the writes /// that must not happen per frame, because an addon that re-anchors the bar would otherwise have /// its anchor stamped over at 60 Hz. #[derive(Clone, Copy, Debug, PartialEq)] pub struct PlateGeometry { /// The plate frame's own size. pub width: f32, pub height: f32, /// The health bar, from the plate's BOTTOMLEFT. pub bar_off_x: f32, pub bar_off_y: f32, pub bar_width: f32, pub bar_height: f32, /// The level text / skull seat, from the plate's BOTTOMRIGHT (x is subtracted, y added). pub level_off_x: f32, pub level_off_y: f32, /// The skull's square side. pub skull_size: f32, /// The raid icon's square side; it hangs off the plate's LEFT edge, vertically centred. pub raid_size: f32, /// Font heights for the name and the level. pub name_height: f32, pub level_height: f32, } /// One plate's per-frame state, as the app hands it over: everything that can move while a plate is /// alive. Anything that cannot is written once, at creation. #[derive(Clone, Debug, PartialEq)] pub struct PlateState { /// The unit this plate belongs to — its GUID, and **the plate's identity**. /// /// The reference binds a plate to its unit for as long as the plate lives (`[unit+0xe60]` /// holds the pointer; the free list hands out a plate at create and takes it back at destroy), /// and every corpus addon depends on that: pfUI caches the name, level, colour and its own /// overlay ON the frame (`registry[plate]`, `nameplate.original.*`) and only refreshes what /// its `OnDataChanged` says moved. Handing widget *i* to whichever unit happens to sort i-th /// this frame would make every plate's cached state wrong on the frame two units swap places — /// which is why the pool is keyed by this rather than indexed by the driver's sort order. pub key: u64, /// Where the plate's **TOP-CENTRE** lands, in FrameXML units from the screen's bottom-left — /// the reference's own seat (`nameplate-vkey.md` §8.5: the plate hangs below the point). pub top_centre: (f32, f32), /// `UNIT_FIELD_HEALTH` and `UNIT_FIELD_MAXHEALTH`, **raw**: `healthbar:GetValue()` is /// `[bar+0x320]` (`0x78f5d0`), not a fraction, and `GetMinMaxValues()` answers `(0, max)`. /// pfUI and CustomNameplates both divide, so a fraction here would read as 1 hp out of 1. pub health: f32, pub max_health: f32, /// The bar's reaction colour — the four confirmed dwords. Addons reverse-engineer the unit's /// faction from `GetStatusBarColor`, so these have to be exact. pub bar_colour: [f32; 3], /// The unit's name. pub name: String, /// The level text, or `None` to show the skull in the level's seat instead. pub level: Option, /// The level text's con colour. pub level_colour: [f32; 3], /// The raid-target mark, 0-based (`col = idx & 3`, `row = idx >> 2`), or `None`. pub raid_icon: Option, /// The plate's own alpha: **exactly 1.0 for the target**, dimmed otherwise. `GetAlpha() == 1` /// is how pfUI and CustomNameplates detect the target plate — a contract, not a look. pub alpha: f32, /// Mouseover ∪ target: the bar's brighten. pub lit: bool, /// The plate rect is hovered right now — the name goes yellow (`0x7cb850` OnEnter, name-only) /// and the glow region is shown, which is the mouseover signal addons read. pub hovered: bool, } /// One pooled plate's widgets, in the reference's creation order. #[derive(Debug)] struct Plate { frame: FrameHandle, border: RegionHandle, glow: RegionHandle, name: RegionHandle, level: RegionHandle, skull: RegionHandle, raid: RegionHandle, bar: FrameHandle, fill: RegionHandle, /// Last frame's state, so [`Plate::drive`] writes only what moved. `None` = retired. last: Option, } /// The plate pool — grown on demand, **never shrunk**, in creation order. /// /// The reference's pool is unbounded and its free list FIFO; what matters to an addon is only that /// an index, once handed out, keeps meaning the same Lua object, and that a retired plate stays in /// the child list (hidden). pfUI and ShaguTweaks scan `initialized + 1 .. GetNumChildren()` and /// never reset `initialized`, so a client that reordered or compacted this list would make them /// miss every plate created afterwards — permanently, and silently. #[derive(Debug, Default)] pub(crate) struct NamePlates { plates: Vec, /// Which pool slot each live unit holds, by [`PlateState::key`] — the reference's /// `[unit+0xe60]`, from this side. A slot leaves this map only when its unit's plate is /// retired, and the slot itself never moves. assigned: std::collections::HashMap, /// The geometry every live plate is currently laid out under. geometry: Option, } /// What a sync produced that has to be fired **outside** the model borrow: the frames whose /// effective visibility flipped (their `OnShow`/`OnHide`), and the bars whose value moved (their /// `OnValueChanged`, which pfUI hooks — `nameplates.lua:393` — and reads `this:GetParent()` in). #[derive(Default)] struct SyncEffects { visibility: Vec, values: Vec<(FrameHandle, f32)>, } impl UiScript { /// Drive the plate pool from the app's per-frame verdict: `states[i]` is the i-th **shown** /// plate, and every pooled plate past `states.len()` is retired (hidden, never destroyed). /// /// `geometry` is the window-derived half; passing an unchanged value costs nothing. pub fn sync_nameplates(&mut self, geometry: PlateGeometry, states: &[PlateState]) { let lua = self.lua(); let effects = { let mut model = lua.app_data_mut::().expect("model app_data"); // No WorldFrame, no plates: a VM with no interface loaded (a bare test app, the addon // harness) has nowhere to parent them, and inventing a root would put plates somewhere // an addon's walk could never find them anyway. let Some(world) = model.arena.lookup("WorldFrame") else { return; }; sync(&mut model, world, geometry, states) }; super::event::fire_visibility_changes(lua, effects.visibility); for (bar, value) in effects.values { super::statusbar::fire_engine_value_changed(lua, bar, value); } } /// How many plate widgets the pool holds — the plate driver's census, and the tests'. pub fn nameplate_pool_len(&self) -> usize { let lua = self.lua(); let model = lua.app_data_ref::().expect("model app_data"); model.nameplates.plates.len() } } /// The whole per-frame drive: grow, re-lay-out if the window moved, write what changed, retire the /// tail. fn sync( model: &mut Model, world: FrameHandle, geometry: PlateGeometry, states: &[PlateState], ) -> SyncEffects { let mut effects = SyncEffects::default(); let relayout = model.nameplates.geometry != Some(geometry); model.nameplates.geometry = Some(geometry); // Retire first, so a unit that left this frame frees its slot for one that arrived — the // reference's destroy-then-create through the same free list, in the same tick. let gone: Vec = model .nameplates .assigned .keys() .copied() .filter(|k| !states.iter().any(|s| s.key == *k)) .collect(); for key in gone { if let Some(i) = model.nameplates.assigned.remove(&key) { Plate::retire(model, i, &mut effects); } } for state in states { let slot = match model.nameplates.assigned.get(&state.key) { Some(&i) => i, None => { // The lowest free slot, else a new one at the tail. Lowest-free rather than the // reference's FIFO free list: both keep every existing index meaning what it did, // which is the only property an addon can observe. let free = (0..model.nameplates.plates.len()) .find(|i| model.nameplates.plates[*i].last.is_none()); let i = free.unwrap_or_else(|| { let plate = Plate::create(model, world, &mut effects); model.nameplates.plates.push(plate); model.nameplates.plates.len() - 1 }); model.nameplates.assigned.insert(state.key, i); i } }; if relayout { Plate::lay_out(model, slot, geometry); } Plate::drive(model, slot, world, state, &mut effects); } // A geometry change reaches the retired plates too: they are still in the child list, an addon // can still read them, and they must not come back next frame laid out for the old window. if relayout { for i in 0..model.nameplates.plates.len() { if model.nameplates.plates[i].last.is_none() { Plate::lay_out(model, i, geometry); } } } effects } impl Plate { /// Build one plate: the Button under the WorldFrame, its six regions in the reference's /// creation order, and the health-bar child with its fill. /// /// Everything here is written **once**. An addon that blanks a region (pfUI's `DisableObject` /// does `SetTexture("")`) must find it still blank on the next frame. fn create(model: &mut Model, world: FrameHandle, effects: &mut SyncEffects) -> Plate { // Anonymous, and that is a requirement rather than an omission (module doc). let frame = model.arena.create(FrameKind::Button, None, Some(world)); let strata = model .arena .frame(world) .map(|f| f.strata) .unwrap_or_default(); model.arena.set_frame_strata(frame, strata); model.arena.set_frame_level(frame, PLATE_LEVEL, true); // **The mouse bit stays OFF, and that is a deliberate gap, not an omission** (2148 §8). // The reference's plate is mouse-enabled — hovering it publishes the mouseover // (`0x7cb850` → `0x492890`) and a click selects through the button's own click slot. On // this engine, a mouse-enabled frame under the cursor is what makes `PointerOverUi` true, // and `target::hover::update_hover` returns early on exactly that: setting the bit without // an engine-side hover/click seam takes the hover and gives nothing back — no model // brighten, no lit bar, and no selection, because the click stops at the UI too. The seam // comes first; then the bit, in the same change. // `a_plate_does_not_yet_claim_the_ui_pointer` is the tripwire. // // It has to be turned OFF explicitly: a `Button` is born mouse-enabled here, faithfully — // `CSimpleButton`'s ctor writes `[+0xcc] = 0x4` (`0x7786a3`), which is why the plate needs // no `EnableMouse` of its own in the reference. model.arena.set_mouse_enabled(frame, false); let border = texture(model, frame, DrawLayer::Artwork, BORDER_TEXTURE); let glow = texture(model, frame, DrawLayer::Highlight, GLOW_TEXTURE); let name = font_string(model, frame, JustifyV::Bottom); let level = font_string(model, frame, JustifyV::Middle); let skull = texture(model, frame, DrawLayer::Overlay, SKULL_TEXTURE); let raid = texture(model, frame, DrawLayer::Artwork, RAID_ICON_TEXTURE); // ↑ THE ORDER. Six regions, and this sequence is the ABI (module doc). // The one child, below the plate's own level so the border draws over the fill. let bar = model.arena.create(FrameKind::StatusBar, None, Some(frame)); model.arena.set_frame_strata(bar, strata); model.arena.set_frame_level(bar, BAR_LEVEL, true); let fill = model .arena .create_region(bar, RegionKind::Texture, DrawLayer::Artwork, 0) .expect("live bar"); model.region_data.entry(fill).or_default().texture = Some(BAR_FILL_TEXTURE.to_string()); if let Some(KindState::StatusBar(sb)) = model.arena.frame_mut(bar).map(|f| &mut f.kind_state) { sb.bar = Some(fill); sb.min = 0.0; sb.max = 1.0; sb.value = 0.0; } // The skull and the raid icon are the two regions that only sometimes show; the glow is // shown on hover alone. The rest are always up while the plate is. for rh in [glow, skull, raid] { model.region_data.entry(rh).or_default().hidden = true; } // Born retired: a fresh plate belongs to no unit yet, and `IsShown()` is what an addon // reads to tell a live plate from a pooled one. effects .visibility .extend(model.arena.set_shown(frame, false)); Plate { frame, border, glow, name, level, skull, raid, bar, fill, last: None, } } /// (Re)lay the static anchors — everything that follows from the window's size rather than the /// unit. Called on the first drive and on a geometry change, never per frame. fn lay_out(model: &mut Model, i: usize, g: PlateGeometry) { let p = &model.nameplates.plates[i]; let (frame, bar) = (p.frame, p.bar); let (border, glow, name, level, skull, raid) = (p.border, p.glow, p.name, p.level, p.skull, p.raid); let plate_id = model.frame_id(frame); // The plate's own size; its position is per-frame and lives in `drive`. let input = model.layout_inputs.entry(frame).or_default(); input.width = g.width; input.height = g.height; // The border fills the plate, and so does the glow — a rim over the whole frame. for rh in [border, glow] { model.region_data.entry(rh).or_default().anchors = vec![ Anchor::new(Point::TopLeft, plate_id, Point::TopLeft, 0.0, 0.0), Anchor::new(Point::BottomRight, plate_id, Point::BottomRight, 0.0, 0.0), ]; } // The health bar: BOTTOMLEFT + (off_x, off_y), explicit size. let bar_input = model.layout_inputs.entry(bar).or_default(); bar_input.anchors = vec![Anchor::new( Point::BottomLeft, plate_id, Point::BottomLeft, g.bar_off_x, g.bar_off_y, )]; bar_input.width = g.bar_width; bar_input.height = g.bar_height; // The name — ("BOTTOM", plate, "CENTER", 0, 0), the pair `_Nameplates` validates. let name_data = model.region_data.entry(name).or_default(); name_data.anchors = vec![Anchor::new( Point::Bottom, plate_id, Point::Center, 0.0, 0.0, )]; name_data.font_height = Some(g.name_height); // The level — ("CENTER", plate, "BOTTOMRIGHT", −x, +y), the other validated pair. let level_data = model.region_data.entry(level).or_default(); level_data.anchors = vec![Anchor::new( Point::Center, plate_id, Point::BottomRight, -g.level_off_x, g.level_off_y, )]; level_data.font_height = Some(g.level_height); level_data.font_explicit.height = true; model.touch_measure(level); // The skull replaces the level number, in the level's own seat. let skull_data = model.region_data.entry(skull).or_default(); skull_data.anchors = vec![Anchor::new( Point::Center, plate_id, Point::BottomRight, -g.level_off_x, g.level_off_y, )]; skull_data.size = Some((g.skull_size, g.skull_size)); // The raid icon hangs off the LEFT edge, vertically centred. let raid_data = model.region_data.entry(raid).or_default(); raid_data.anchors = vec![Anchor::new(Point::Right, plate_id, Point::Left, 0.0, 0.0)]; raid_data.size = Some((g.raid_size, g.raid_size)); // Anchor TARGETS moved (from nothing to the plate) on the first lay-out, so this is the // conservative touch — the only one in this module, and it runs once per geometry change. model.touch_layout(); } /// The per-frame write: position, health, texts, colours, shown flags, alpha — **and nothing /// else**, each only when it moved. fn drive( model: &mut Model, i: usize, world: FrameHandle, state: &PlateState, effects: &mut SyncEffects, ) { let p = &model.nameplates.plates[i]; let (frame, bar) = (p.frame, p.bar); let (glow, name, level, skull, raid, fill) = (p.glow, p.name, p.level, p.skull, p.raid, p.fill); let last = p.last.clone(); let world_id = model.frame_id(world); // The seat, re-issued whenever it moves, exactly like the reference's own (`0x509ec0`: // ClearAllPoints then SetPoint) — which is why an addon anchor on the plate does not // survive there either. A moved OFFSET keeps the graph's edges, so the precise touch // applies; the first seat adds an edge and takes the retarget. if last .as_ref() .is_none_or(|l| l.top_centre != state.top_centre) { let fresh = model .layout_inputs .get(&frame) .is_none_or(|i| i.anchors.is_empty()); let input = model.layout_inputs.entry(frame).or_default(); input.anchors = vec![Anchor::new( Point::Top, world_id, Point::BottomLeft, state.top_centre.0, state.top_centre.1, )]; if fresh { model.touch_layout_retarget_frame(frame, &[], &[world_id]); } else { model.touch_layout_frame(frame); } } if last.is_none() { effects .visibility .extend(model.arena.set_shown(frame, true)); } if last.as_ref().is_none_or(|l| l.alpha != state.alpha) { model.arena.set_alpha(frame, state.alpha); } // The bar reports RAW health (module doc). Its fill carries the reaction colour, lifted by // a uniform brighten while the unit is the mouseover or the target — 0184's form of the // highlight, in place of the additive `Nameplate-Glow` rim. if last .as_ref() .is_none_or(|l| l.health != state.health || l.max_health != state.max_health) { if let Some(KindState::StatusBar(sb)) = model.arena.frame_mut(bar).map(|f| &mut f.kind_state) { sb.min = 0.0; sb.max = state.max_health; sb.value = state.health.clamp(0.0, state.max_health); } effects.values.push((bar, state.health)); } if last .as_ref() .is_none_or(|l| l.bar_colour != state.bar_colour || l.lit != state.lit) { let boost = if state.lit { LIT_BOOST } else { 1.0 }; let c = state.bar_colour; model.region_data.entry(fill).or_default().vertex_color = Some([c[0] * boost, c[1] * boost, c[2] * boost, 1.0]); } // The name: white, yellow while the plate itself is hovered. if last.as_ref().is_none_or(|l| l.name != state.name) { model.region_data.entry(name).or_default().text = Some(state.name.clone()); model.touch_measure(name); model.touch_layout_region(name); } if last.as_ref().is_none_or(|l| l.hovered != state.hovered) { let c = if state.hovered { [1.0, 1.0, 0.0, 1.0] } else { [1.0, 1.0, 1.0, 1.0] }; model.region_data.entry(name).or_default().vertex_color = Some(c); // `glow:IsShown()` IS the mouseover signal (pfUI `nameplates.lua:601`). The region is // shown for real; that it paints nothing is 0184's deviation, not a missing region. model.region_data.entry(glow).or_default().hidden = !state.hovered; } // Level text and skull are mutually exclusive, in one seat. if last .as_ref() .is_none_or(|l| l.level != state.level || l.level_colour != state.level_colour) { match state.level { Some(n) => { let c = state.level_colour; let data = model.region_data.entry(level).or_default(); data.text = Some(n.to_string()); data.vertex_color = Some([c[0], c[1], c[2], 1.0]); data.hidden = false; model.region_data.entry(skull).or_default().hidden = true; model.touch_measure(level); model.touch_layout_region(level); } None => { model.region_data.entry(level).or_default().hidden = true; model.region_data.entry(skull).or_default().hidden = false; } } } // The raid mark: the 4-column atlas cell, shown only while the unit carries one. if last.as_ref().is_none_or(|l| l.raid_icon != state.raid_icon) { match state.raid_icon { Some(idx) => { let (u0, v0) = (f32::from(idx & 3) * 0.25, f32::from(idx >> 2) * 0.25); let data = model.region_data.entry(raid).or_default(); data.tex_coords = Some(TexCoords::Rect([u0, u0 + 0.25, v0, v0 + 0.25])); data.hidden = false; } None => model.region_data.entry(raid).or_default().hidden = true, } } model.nameplates.plates[i].last = Some(state.clone()); } /// Retire a plate: **hide it, never destroy it** (`0x608a10` — unlink from the active list, /// push onto the free list, `Hide`). It keeps its slot in `WorldFrame`'s child list, keeps its /// index, and comes back as the same Lua object, which is the contract every addon's /// `registry[plate]` identity map rests on. fn retire(model: &mut Model, i: usize, effects: &mut SyncEffects) { if model.nameplates.plates[i].last.is_none() { return; } let frame = model.nameplates.plates[i].frame; effects .visibility .extend(model.arena.set_shown(frame, false)); model.nameplates.plates[i].last = None; } } /// One of the plate's texture regions, with its path — created in the ABI's order by the caller. fn texture(model: &mut Model, frame: FrameHandle, layer: DrawLayer, path: &str) -> RegionHandle { let rh = model .arena .create_region(frame, RegionKind::Texture, layer, 0) .expect("live plate"); model.region_data.entry(rh).or_default().texture = Some(path.to_string()); rh } /// One of the plate's two FontStrings. Both are OVERLAY — the ctor re-layers them off ARTWORK /// (`0x7cb438`, `0x7cb4ea`) — and both take `NAMEPLATE_FONT` with the plate's black 1 px drop /// shadow; the heights arrive with the geometry. /// /// `justify_v` is the one benilla-side compensation here, and it is the old painter's, kept: the /// plate's name is seated by its **ink**, not by the line box. Our shaper's box is ascent-heavy /// for Friz, so a box-seated `BOTTOM` anchor drops the name ~8 px — half of it over the plate — /// which is the director's old "the level is not aligned" in its other half. `Bottom` puts the /// text where the reference's own metrics put it. fn font_string(model: &mut Model, frame: FrameHandle, justify_v: JustifyV) -> RegionHandle { let rh = model .arena .create_region(frame, RegionKind::FontString, DrawLayer::Overlay, 0) .expect("live plate"); let data = model.region_data.entry(rh).or_default(); data.font_path = Some(PLATE_FONT.to_string()); data.font_explicit.face = true; // The plate text's shadow (`nameplate-vkey.md` §7: black, ±0.001 gx, director-tuned to a // constant 1 px — the recorded value rounds to 2 px at a large window and reads chunky). data.font_shadow = Some(FontShadow { offset: [1.0, -1.0], color: [0.0, 0.0, 0.0, 1.0], }); data.justify.set_v(justify_v); rh } /// The LIT (mouseover ∪ target) bar brighten — a uniform multiplicative lift of the fill tint, /// gradient untouched (0184: the recorded additive `Nameplate-Glow` rim read as hard edge lines on /// our linear-blending pipeline, and the director pinned the brighten instead). /// /// Quad colours are client-space sRGB (`srgb_quad_color` linearizes them), so this multiplies /// ENCODED texels ~1:1 — the gamma-space modulate the client's FFP would do. **255/215 is the /// largest clean value**: the fill texture's encoded peak (215) lands exactly on white, no row /// clips, and every row scales by the same visible factor. (A first cut of 1.45 assumed a /// linear-space multiply and flattened six rows against white — the very gradient change the /// director banned.) const LIT_BOOST: f32 = 255.0 / 215.0;