Files
benilla-octo/crates/benilla-app/assets/ui/PetPaperDollFrame.xml
T
SamandClaude Fable 5 d1140829d7 the interface code is ours: the transcription debt is paid
- forty files that carried the reference's literal Lua are rewritten as our own expression of the behaviour — worst identical run 61 lines down to 7 — and the README now says exactly that
- the overlap gate measures the code itself across all four reference corpora, and ratchets: the number may only go down
- a lost session ends at the login screen instead of silently re-authenticating and stealing the account back
- a building's skybox is a real model: Caverns of Time's white ceiling becomes its painted nebula and additive stars

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 19:21:03 +07:00

768 lines
51 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<Ui>
<!--
benilla's PET paper doll (decision 1057) — the character window's **tab 2**, dressed from the
real 1.12 `PetPaperDollFrame.xml`/`.lua` (line cites below are into that extraction:
`ref-PetPaperDollFrame.xml`/`.lua`, plus `ref-PaperDollFrame.lua` for the shared setters and
`ref-CharacterFrame.lua` for the tab dispatch).
**Its own top-level file, loaded straight after `CharacterFrame.xml`** — the SkillFrame.xml
precedent: this engine reads no XML `parent=` attribute, so a page ships as a sibling pinned
to `CharacterFrame`'s TOPLEFT by name, and `BENILLA_CHARACTERFRAME_SUBFRAMES` (which
this page joins as entry 2) is what hides it when the whole window closes.
**Almost none of the page's Lua lives here, and that is the point.** The reference's own
`PaperDollFrame_Set{Armor,Damage,AttackPower,AttackBothHands,Defense}` take `(unit, prefix)`
and `PetPaperDollFrame_Update` (ref l.62-94) calls every one of them with `("pet", "Pet")` —
the pet sheet is the SAME code as the character sheet pointed at another unit. Our
transcription in `CharacterFrame.xml` already carries those parameters (and already carries
the ref's own `if unit == "pet" then return end` early-outs in the two RANGED setters, ref
l.470-473/l.541-544 — the pet page has no ranged rows, so the ref no-ops rather than
nil-indexing). So this file adds only what is genuinely pet-only: the stats/resistances pair
that reads the pet's own frames, the XP bar, loyalty, training points and the diet icon.
`prefix` here is `"Pet"`, so `prefix.."ArmorFrame"` resolves `PetArmorFrame`,
exactly as `"Character"` resolves the sibling page's.
**The tab is conditional, and `PetTab_Update` is the whole mechanism** (ref
`PetPaperDollFrame.lua:189-198`): no pet UI ⇒ tab 2 hides and tab 3 re-anchors onto tab 2's
LEFT, closing the gap; a pet ⇒ tab 2 shows and tab 3 goes back to the -16 chain. This used
to be the ref's arithmetic on a DIFFERENT page (tab 3 was Skills here, Reputation there);
`ReputationFrame.xml` took slot 3, so it is now the reference's function moving the
reference's tab, with nothing left to reconcile.
Named deferrals / deviations (each re-cited at its own site below):
- The model pane is a **booth-texture stand-in**, not a live `<PlayerModel>` widget — the
0208 §5 decision the character page already ships under. The ref's
`PetModelFrame:SetUnit("pet")` (ref l.67) therefore has no analogue: the app's
`petdoll` booth resolves the pet itself (decision 1057), so the pane samples a bake
that is already pointed at the right unit. `DISPLAY_SIZE_CHANGED`/`RefreshUnit` drop
with the widget, exactly as they do on the character page.
- The XP bar ships with **no numerals**: `TextStatusBar_Initialize`/
`SetTextStatusBarTextPrefix`/`ShowTextStatusBarText` have no engine surface (the same
gap `ActionBar.xml`'s own XP bar records — "No TextStatusBar text layer exists here at
all"). The bar itself, its dwarf-notch art, its range and its colour are all faithful;
only the "1234/5678" overlay is missing, and it lands for both bars at once when that
machinery does.
- `PET_UI_UPDATE` / `PET_UI_CLOSE` are **registered but nothing fires them yet** (nothing
in the app sources either event). Registered anyway rather than dropped: the ref's own
handler arms are re-expressed whole, and `UNIT_PET` + `PET_BAR_UPDATE` — both of which we
DO fire — already cover the same "the pet went away while its page was open" case
through the identical branch.
- **CLOSED (decision 1062):** the level line's family word and the diet tooltip both
landed. `UnitCreatureFamily("pet")` and `GetPetFoodTypes()` are real bindings now, fed
from `CreatureFamily.dbc` (the word + its pet-food mask) and `ItemPetFood.dbc` (the
eight diet names) through the pet's cached creature template; `BuildListString` is the
ref's own `UIParent.lua:1051` joiner, re-expressed in `UIParent.xml`. Both call sites
below dropped their existence guards. The ref's OWN `if ( UnitCreatureFamily("pet") )`
guard stays — nil is still reachable (a template with no family; the creature query not
yet answered) and a blank level line is the ref's answer to it.
- `PetPaperDollFrame_SetResistances` reads the school ids from the shared
`BENILLA_MAGICRES_IDS` table where the ref reads each frame's own `id=` attr
(`frame:GetID()`, ref l.112). Same five ids in the same order (6/2/3/4/5); the table is
what the sibling page already uses, and one convention across the two pages beats
matching the ref's two.
- The ref's `PetPaperDollCloseButton` (ref l.276-292) inherits `UIPanelButtonTemplate`,
which this codebase has never needed to define; its art/size/font are transcribed inline
from the shape every other panel button here uses (QuestFrame.xml's own precedent).
-->
<Script><![CDATA[
-- ── GlobalStrings this page uses (ref-GlobalStrings.lua, cited per line — the
-- `X = X or "..."` idiom is this codebase's convention: the real localized file, loaded off
-- the patch chain first, always wins; these fill in for the test harnesses, which load no
-- chain. Redeclared here rather than leaned on from CharacterFrame.xml so a subset load of
-- this file alone still resolves.) ────────────────────────────────────────────────────────
ATTACK_COLON = ATTACK_COLON or "Attack:" -- :70
DAMAGE_COLON = DAMAGE_COLON or "Damage:" -- :888
ATTACK_POWER_COLON = ATTACK_POWER_COLON or "Power:" -- :71
DEFENSE_COLON = DEFENSE_COLON or "Defense:" -- :917
ARMOR_COLON = ARMOR_COLON or "Armor:" -- :63
ATTACK_TOOLTIP = ATTACK_TOOLTIP or "Attack Rating" -- :75
DEFENSE_TOOLTIP = DEFENSE_TOOLTIP or "Defense Rating" -- :918
TRAINING_POINTS = TRAINING_POINTS or "Training Points:" -- :4305
UNIT_LEVEL_TEMPLATE = UNIT_LEVEL_TEMPLATE or "Level %d" -- :4436
PET_DIET_TEMPLATE = PET_DIET_TEMPLATE or "Diet: %s" -- :3037
NUM_PET_STATS = 5 -- (ref l.2)
BENILLA_NUM_PET_RESISTANCE_TYPES = 5 -- NUM_PET_RESISTANCE_TYPES (ref l.1)
-- ── The page ────────────────────────────────────────────────────────────────────────────
-- ref PetPaperDollFrame_OnLoad (l.4-29). The five labels live ONLY in the row template's
-- $parentLabel and Lua fills them — never a re-declared region in an instance (the sibling
-- page's OnLoad carries the why: this engine's template expansion APPENDS children, so a
-- re-declared $parentLabel makes a second anchorless FontString instead of merging).
-- `SetTextStatusBarTextPrefix(…, XP)` is dropped with the bar's numerals (header).
function PetPaperDollFrame_OnLoad()
local frame = this
getglobal("PetAttackFrameLabel"):SetText(ATTACK_COLON)
getglobal("PetDamageFrameLabel"):SetText(DAMAGE_COLON)
getglobal("PetAttackPowerFrameLabel"):SetText(ATTACK_POWER_COLON)
getglobal("PetDefenseFrameLabel"):SetText(DEFENSE_COLON)
getglobal("PetArmorFrameLabel"):SetText(ARMOR_COLON)
frame:RegisterEvent("PET_UI_UPDATE")
frame:RegisterEvent("PET_BAR_UPDATE")
frame:RegisterEvent("PET_UI_CLOSE")
frame:RegisterEvent("UNIT_PET")
frame:RegisterEvent("UNIT_PET_EXPERIENCE")
frame:RegisterEvent("UNIT_MODEL_CHANGED")
frame:RegisterEvent("UNIT_LEVEL")
frame:RegisterEvent("UNIT_RESISTANCES")
frame:RegisterEvent("UNIT_STATS")
frame:RegisterEvent("UNIT_DAMAGE")
frame:RegisterEvent("UNIT_RANGEDDAMAGE")
frame:RegisterEvent("UNIT_ATTACK_SPEED")
frame:RegisterEvent("UNIT_ATTACK_POWER")
frame:RegisterEvent("UNIT_RANGED_ATTACK_POWER")
frame:RegisterEvent("UNIT_DEFENSE")
frame:RegisterEvent("UNIT_ATTACK")
frame:RegisterEvent("UNIT_PET_TRAINING_POINTS")
PetTab_Update()
end
-- ref PetPaperDollFrame_OnEvent (l.31-48), all four arms. NOTE the shape: unlike the
-- character page, this one does NOT gate on visibility — the first arm has to run while the
-- page is HIDDEN, because that is what keeps the tab itself in step with whether a pet
-- exists. Only the last arm (a plain "the pet's numbers moved") is unit-token gated.
function PetPaperDollFrame_OnEvent()
local frame = this
if event == "PET_UI_UPDATE" or event == "PET_BAR_UPDATE"
or (event == "UNIT_PET" and arg1 == "player") then
-- The pet left while its own page was open: close the window (ToggleCharacter on
-- the visible page is the ref's own way of saying HideUIPanel), then re-fit the tab.
if frame:IsVisible() and not HasPetUI() then
ToggleCharacter("PetPaperDollFrame")
end
PetTab_Update()
PetPaperDollFrame_Update()
elseif event == "PET_UI_CLOSE" then
if frame:IsVisible() then
ToggleCharacter("PetPaperDollFrame")
end
PetTab_Update()
elseif event == "UNIT_PET_EXPERIENCE" then
PetExpBar_Update()
elseif arg1 == "pet" then
PetPaperDollFrame_Update()
end
end
-- ref PetPaperDollFrame_OnShow/_OnHide (l.50-60): the window's ONE name line is shared —
-- the pet page borrows it and puts the player's back on the way out.
function PetPaperDollFrame_OnShow()
getglobal("CharacterNameText"):Hide()
local petName = getglobal("PetNameText")
petName:Show()
petName:SetText(UnitName("pet"))
PetPaperDollFrame_Update()
end
function PetPaperDollFrame_OnHide()
getglobal("CharacterNameText"):Show()
getglobal("PetNameText"):Hide()
end
-- ref PetPaperDollFrame_Update (l.62-94). The `hasPetUI` early-out is load-bearing, not
-- defensive: every setter below reads the "pet" token, and with no pet those all answer the
-- absent shape — repainting from it would blank a page the ref leaves showing its last state
-- until the tab itself goes.
function PetPaperDollFrame_Update()
local hasPetUI, canGainXP = HasPetUI()
if not hasPetUI then return end
-- ref l.67 `PetModelFrame:SetUnit("pet")` — no analogue: the app's `petdoll` booth
-- resolves the pet itself (header).
--
-- The ref's own `if ( UnitCreatureFamily("pet") ) then` guard, kept (decision 1062):
-- nil is genuinely reachable — no family on the template, or the creature query that
-- carries the family id has not answered yet — and the reference's answer is a BLANK
-- level line rather than a bare "Level 58". Hoisted to a local because the ref calls
-- the binding twice for one answer.
local family = UnitCreatureFamily("pet")
if family then
getglobal("PetLevelText"):SetText(
format(UNIT_LEVEL_TEMPLATE, UnitLevel("pet")) .. " " .. family)
end
getglobal("PetLoyaltyText"):SetText(GetPetLoyalty())
PetExpBar_Update()
-- The two panes this page has to draw itself, for two DIFFERENT reasons (both below):
-- the resistance column genuinely differs from the character page's, while the stat
-- block is the same logic that only duplicates because PetStatFrame/CharacterStatFrame
-- share no prefix for a `prefix..` renderer to take.
PetPaperDollFrame_SetResistances()
PetPaperDollFrame_SetStats()
-- ...and then the shared rows, which are not this page's code at all: every one of
-- these is CharacterFrame.xml's own renderer, handed ("pet", "Pet") where the character
-- page hands it ("player", "Character"), so `prefix.."ArmorFrame"` resolves PetArmorFrame
-- here and CharacterArmorFrame there. That substitution IS the page (header comment).
-- Called top-to-bottom in the order the rows are anchored on screen — each renderer only
-- writes its own row's FontStrings from live API state, so nothing here reads anything
-- another one wrote and the order is free.
PaperDollFrame_SetAttackBothHands("pet", "Pet")
PaperDollFrame_SetAttackPower("pet", "Pet")
PaperDollFrame_SetDamage("pet", "Pet")
PaperDollFrame_SetDefense("pet", "Pet")
PaperDollFrame_SetArmor("pet", "Pet")
-- The pet sheet has no ranged rows at all, and these two say so themselves — both open
-- with the reference's own `if unit == "pet" then return end` (CharacterFrame.xml). They
-- are called anyway so that both pages drive the identical set of renderers and neither
-- can quietly fall behind the other.
PaperDollFrame_SetRangedDamage("pet", "Pet")
PaperDollFrame_SetRangedAttackPower("pet", "Pet")
-- The hunter-only block (ref l.83-93): the diet icon and the training-point line are
-- the two things a warlock's minion never has. `canGainXP` is HasPetUI's second return.
if canGainXP then
getglobal("PetPaperDollPetInfo"):Show()
local totalPoints, spent = GetPetTrainingPoints()
local text = getglobal("PetTrainingPointText")
text:SetText(totalPoints - spent)
text:Show()
getglobal("PetTrainingPointLabel"):Show()
else
getglobal("PetPaperDollPetInfo"):Hide()
getglobal("PetTrainingPointText"):Hide()
getglobal("PetTrainingPointLabel"):Hide()
end
end
-- ref PetPaperDollFrame_SetResistances (l.96-137). Deliberately NOT the character page's
-- version: the pet's has no resistance-quality subtext (no `RESISTANCE_TOOLTIP_SUBTEXT`
-- line at all) and colours the NUMBER off its own sign rather than off pos-vs-neg
-- magnitude — two real differences in the reference, kept rather than unified.
function PetPaperDollFrame_SetResistances()
for i = 1, BENILLA_NUM_PET_RESISTANCE_TYPES do
local resistId = BENILLA_MAGICRES_IDS[i]
local text = getglobal("PetMagicResText" .. i)
local frame = getglobal("PetMagicResFrame" .. i)
local base, resistance, positive, negative = UnitResistance("pet", resistId)
frame.tooltip = getglobal("RESISTANCE" .. resistId .. "_NAME")
-- Resistances can be negative: red below zero, green above, plain white at zero.
if resistance < 0 then
text:SetText(RED_FONT_COLOR_CODE .. resistance .. FONT_COLOR_CODE_CLOSE)
elseif resistance == 0 then
text:SetText(resistance)
else
text:SetText(GREEN_FONT_COLOR_CODE .. resistance .. FONT_COLOR_CODE_CLOSE)
end
if positive ~= 0 or negative ~= 0 then
frame.tooltip = frame.tooltip .. " ( " .. HIGHLIGHT_FONT_COLOR_CODE .. base
if positive > 0 then
frame.tooltip = frame.tooltip .. GREEN_FONT_COLOR_CODE .. " +" .. positive
end
if negative < 0 then
frame.tooltip = frame.tooltip .. " " .. RED_FONT_COLOR_CODE .. negative
end
frame.tooltip = frame.tooltip .. FONT_COLOR_CODE_CLOSE .. " )"
end
end
end
-- ref PetPaperDollFrame_SetStats (l.139-181) — byte-for-byte the character page's
-- _SetStats with the pet's own frame names and the "pet" token (the ref carries the same
-- duplication; unifying them would mean inventing a prefix the reference's own
-- `PetStatFrame`/`CharacterStatFrame` naming doesn't share).
function PetPaperDollFrame_SetStats()
for i = 1, NUM_PET_STATS do
local label = getglobal("PetStatFrame" .. i .. "Label")
local text = getglobal("PetStatFrame" .. i .. "StatText")
local frame = getglobal("PetStatFrame" .. i)
local statName = getglobal("SPELL_STAT" .. (i - 1) .. "_NAME")
label:SetText(statName .. ":")
local stat, effectiveStat, posBuff, negBuff = UnitStat("pet", i)
local tooltipText = HIGHLIGHT_FONT_COLOR_CODE .. statName .. " "
if posBuff == 0 and negBuff == 0 then
text:SetText(effectiveStat)
frame.tooltip = tooltipText .. effectiveStat .. FONT_COLOR_CODE_CLOSE
else
tooltipText = tooltipText .. effectiveStat
if posBuff > 0 or negBuff < 0 then
tooltipText = tooltipText .. " (" .. (stat - posBuff - negBuff) .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 then
tooltipText = tooltipText .. FONT_COLOR_CODE_CLOSE .. GREEN_FONT_COLOR_CODE .. "+" .. posBuff .. FONT_COLOR_CODE_CLOSE
end
if negBuff < 0 then
tooltipText = tooltipText .. RED_FONT_COLOR_CODE .. " " .. negBuff .. FONT_COLOR_CODE_CLOSE
end
if posBuff > 0 or negBuff < 0 then
tooltipText = tooltipText .. HIGHLIGHT_FONT_COLOR_CODE .. ")" .. FONT_COLOR_CODE_CLOSE
end
frame.tooltip = tooltipText
if negBuff < 0 then
text:SetText(RED_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE)
else
text:SetText(GREEN_FONT_COLOR_CODE .. effectiveStat .. FONT_COLOR_CODE_CLOSE)
end
end
end
end
-- ref PetExpBar_Update (l.183-187). `min(0, currXP)` is the ref's own; XP can't be negative,
-- so it is just 0 — quoted, not simplified.
function PetExpBar_Update()
local currXP, nextXP = GetPetExperience()
local bar = getglobal("PetPaperDollFrameExpBar")
bar:SetMinMaxValues(math.min(0, currXP), nextXP)
bar:SetValue(currXP)
end
-- ref PetTab_Update (l.189-198) — the conditional tab, whole, and byte-for-byte the
-- reference's since the Reputation page took tab 3 (this file's header comment). The
-- re-anchor is what keeps the row gapless: with tab 2 hidden, tab 3 sits at tab 2's own LEFT
-- (i.e. exactly where tab 2 would have started) instead of -16 past its RIGHT. Only tab 3
-- is touched — tab 4 chains off tab 3, so the whole tail follows it.
function PetTab_Update()
local tab2 = getglobal("CharacterFrameTab2")
local tab3 = getglobal("CharacterFrameTab3")
-- Nil-guarded like CharacterFrame_ShowSubFrame's own loop: a test subset may load this
-- file without the window's tab row.
if not tab2 or not tab3 then return end
if not HasPetUI() then
tab2:Hide()
tab3:SetPoint("LEFT", "CharacterFrameTab2", "LEFT", 0, 0)
else
tab2:Show()
tab3:SetPoint("LEFT", "CharacterFrameTab2", "RIGHT", -16, 0)
end
end
]]></Script>
<Frame name="PetPaperDollFrame" enableMouse="true" parent="CharacterFrame" hidden="true" id="2">
<!-- The real 384×512 page (ref l.4, `setAllPoints="true"`); pinned to the window's TOPLEFT
by name, the SkillFrame.xml precedent (no cross-file `parent=` in this engine). -->
<Size><AbsDimension x="384" y="512"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="CharacterFrame" relativePoint="TOPLEFT"/></Anchors>
<HitRectInsets><AbsInset left="0" right="30" top="0" bottom="75"/></HitRectInsets>
<Layers>
<!-- The quadrant background (ref l.9-57): the "General" top halves every
PaperDollInfoFrame page shares (they carry the portrait-ring cutout), plus this
page's own bottom pair. -->
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-General-TopRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="2" y="-1"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PetPaperDollFrame\UI-PetPaperDollFrame-BotLeft">
<Size><AbsDimension x="256" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="2" y="-257"/></Offset></Anchor></Anchors>
</Texture>
<Texture file="Interface\PetPaperDollFrame\UI-PetPaperDollFrame-BotRight">
<Size><AbsDimension x="128" y="256"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="258" y="-257"/></Offset></Anchor></Anchors>
</Texture>
<!-- The pet's name (ref l.58-69), sitting exactly where the window's own player
name line does — the two swap on this page's Show/Hide. -->
<FontString name="PetNameText" inherits="GameFontHighlight">
<Size><AbsDimension x="109" y="16"/></Size>
<Anchors><Anchor point="CENTER"><Offset><AbsDimension x="6" y="232"/></Offset></Anchor></Anchors>
</FontString>
<!-- "Level %d <Family>" (ref l.70-78) and the loyalty level under it (l.79-87). -->
<FontString name="PetLevelText" inherits="GameFontNormalSmall">
<Anchors>
<Anchor point="TOP" relativeTo="PetNameText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-6"/></Offset></Anchor>
</Anchors>
</FontString>
<FontString name="PetLoyaltyText" inherits="GameFontNormalSmall">
<Anchors>
<Anchor point="TOP" relativeTo="PetLevelText" relativePoint="BOTTOM"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor>
</Anchors>
</FontString>
<!-- Unspent training points (ref l.88-105) — shown for hunter pets only. -->
<FontString name="PetTrainingPointText" inherits="GameFontHighlightSmall">
<Anchors><Anchor point="BOTTOMRIGHT"><Offset><AbsDimension x="-135" y="86"/></Offset></Anchor></Anchors>
</FontString>
<FontString name="PetTrainingPointLabel" inherits="GameFontNormalSmall" text="TRAINING_POINTS">
<Anchors>
<Anchor point="RIGHT" relativeTo="PetTrainingPointText" relativePoint="LEFT"><Offset><AbsDimension x="-5" y="0"/></Offset></Anchor>
</Anchors>
</FontString>
</Layer>
</Layers>
<Frames>
<!-- The pet XP bar (ref l.109-176): 319×11 at BOTTOMLEFT (23,105), the two 160/159-wide
dwarf notch strips over it, the shared purple status-bar fill. The numerals are the
header's named deferral; everything else here is the ref's. -->
<!-- No `enableMouse`: the ref's bar takes the mouse only to show its numerals on hover
(l.166-172), and those are the deferral above — a mouse-enabled bar with nothing to
show would just eat clicks. It comes back with the numerals. -->
<StatusBar name="PetPaperDollFrameExpBar">
<Size><AbsDimension x="319" y="11"/></Size>
<Anchors><Anchor point="BOTTOMLEFT"><Offset><AbsDimension x="23" y="105"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="OVERLAY">
<Texture name="PetPaperDollXPBar1" file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="160" y="13"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0.203125" right="0.8046875" top="0.2890625" bottom="0.33984375"/>
</Texture>
<Texture file="Interface\MainMenuBar\UI-MainMenuBar-Dwarf">
<Size><AbsDimension x="159" y="13"/></Size>
<Anchors><Anchor point="LEFT" relativeTo="PetPaperDollXPBar1" relativePoint="RIGHT"/></Anchors>
<TexCoords left="0.203125" right="0.8046875" top="0.2890625" bottom="0.33984375"/>
</Texture>
</Layer>
</Layers>
<BarTexture file="Interface\TargetingFrame\UI-StatusBar"/>
<BarColor r="0.58" g="0.0" b="0.55"/>
<Scripts>
<!-- The ref's OnEvent is the TextStatusBar CVar hook plus PetExpBar_Update; only
the latter has anything to do here, and the page's own UNIT_PET_EXPERIENCE
arm already drives it, so no per-bar registration ships. -->
<OnLoad>PetExpBar_Update()</OnLoad>
</Scripts>
</StatusBar>
<!-- The model pane (ref PlayerModel PetModelFrame, l.177-244): 318×224 at TOPLEFT
(25,-78) — wider than the character page's 233 because there is no equipment column
beside it. A plain Frame sampling the `petdoll` booth bake (header). -->
<Frame name="PetModelFrame">
<Size><AbsDimension x="318" y="224"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="25" y="-78"/></Offset></Anchor></Anchors>
<Layers>
<!-- BACKGROUND, not ARTWORK — see decision 1070. The booth quad stands in for a
<PlayerModel> WIDGET, whose 3D content the reference renders outside the
layer batches entirely and with a transparent background; ours is one
OPAQUE texture inside them. The draw layer is BUCKET-WIDE and outranks the
frame (0884), so at ARTWORK this quad painted over every same-level sibling
region below OVERLAY — which is exactly how the diet icon two frames down
(BACKGROUND, the reference's own layer for it) became a mouse-enabled hole
with a working tooltip and nothing drawn (director, 2026-08-06). Sitting at
BACKGROUND, the pane's single region ties with such siblings and the link
stamp decides, restoring the reference's declaration order. A pane can only
ever stop covering things this way, never start. -->
<Layer level="BACKGROUND">
<Texture name="$parentTexture">
<Size><AbsDimension x="318" y="224"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- Rotate buttons (ref l.201-242): 35×35. NOTE the ref anchors the left one to
the PAGE, not to the pane (l.206) — (23,-76), two pixels up and left of the
pane's own corner — and the right one chains off its TOPRIGHT. -->
<Button name="PetModelFrameRotateLeftButton">
<Size><AbsDimension x="35" y="35"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="PetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="23" y="-76"/></Offset></Anchor></Anchors>
<NormalTexture file="Interface\Buttons\UI-RotationLeft-Button-Up"/>
<PushedTexture file="Interface\Buttons\UI-RotationLeft-Button-Down"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<OnClick>BenillaPaperDollModel_RotateLeft(getglobal("PetModelFrame"))</OnClick>
</Scripts>
</Button>
<Button name="PetModelFrameRotateRightButton">
<Size><AbsDimension x="35" y="35"/></Size>
<Anchors><Anchor point="TOPLEFT" relativeTo="PetModelFrameRotateLeftButton" relativePoint="TOPRIGHT"/></Anchors>
<NormalTexture file="Interface\Buttons\UI-RotationRight-Button-Up"/>
<PushedTexture file="Interface\Buttons\UI-RotationRight-Button-Down"/>
<HighlightTexture file="Interface\Buttons\ButtonHilight-Round" alphaMode="ADD"/>
<Scripts>
<OnLoad>self:RegisterForClicks("LeftButtonDown", "LeftButtonUp")</OnLoad>
<OnClick>BenillaPaperDollModel_RotateRight(getglobal("PetModelFrame"))</OnClick>
</Scripts>
</Button>
</Frames>
<Scripts>
<!-- The four Model_* helpers are the sibling page's, shared: they drive
`frame.setFacing`, which each pane sets to its own booth's yaw binding
(decision 1057 — that generalization is what let this page reuse them
instead of growing a third copy of the held-spin loop). No OnMouseUp/
OnReceiveDrag here: the ref's pet pane is not a drop target (a pet wears
nothing), so it stays mouse-inert. -->
<OnLoad>
self.setFacing = BenillaPetPaperDollModel_SetFacing
BenillaPaperDollModel_OnLoad(self)
BenillaSetBoothTexture(getglobal(self:GetName() .. "Texture"), "petdoll")
</OnLoad>
<OnUpdate>BenillaPaperDollModel_OnUpdate(self, arg1)</OnUpdate>
</Scripts>
</Frame>
<!-- The diet icon (ref PetPaperDollPetInfo, l.245-275): 24×23 under the rotate buttons.
Its texcoords are STATIC in the reference — this icon is the pet's DIET affordance
(its tooltip lists what it eats), not a happiness readout; the happiness face that
does move lives on the pet unit frame (decision 1005/0990). Shown for hunter pets
only. -->
<Frame name="PetPaperDollPetInfo">
<Size><AbsDimension x="24" y="23"/></Size>
<Anchors>
<Anchor point="TOPLEFT" relativeTo="PetModelFrameRotateLeftButton" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="7" y="0"/></Offset></Anchor>
</Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PetPaperDollFrame\UI-PetHappiness">
<Size><AbsDimension x="24" y="23"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="0.1875" top="0" bottom="0.359375"/>
</Texture>
</Layer>
</Layers>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
-- ref l.267-270, unguarded now that both halves exist (decision 1062):
-- `GetPetFoodTypes` is a real binding and `BuildListString` is the ref's
-- own UIParent.lua joiner, re-expressed in UIParent.xml.
--
-- `BuildListString()` returns NIL on an empty list and `format`'s "%s"
-- would error on it, so the pairing matters. Two things keep it safe, and
-- both are the reference's: this frame is shown for HUNTER pets only (the
-- `canGainXP` block above), and in the shipped `CreatureFamily.dbc` every
-- family with a ZERO food mask is a warlock minion or "Remote Control" —
-- every hunter-tameable family has a real diet. `GetPetFoodTypes` is
-- itself hunter-gated in the binary too (`0x6116e0`), so all three agree.
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(format(PET_DIET_TEMPLATE, BuildListString(GetPetFoodTypes())))
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- The page's own Close button (ref PetPaperDollCloseButton, l.276-292) — the panel
button art/size/font inline, since no UIPanelButtonTemplate ships (header). -->
<Button name="PetPaperDollCloseButton" text="CLOSE">
<Size><AbsDimension x="80" y="22"/></Size>
<Anchors><Anchor point="CENTER" relativePoint="TOPLEFT"><Offset><AbsDimension x="305" y="-422"/></Offset></Anchor></Anchors>
<NormalFont inherits="GameFontNormal"/>
<HighlightFont inherits="GameFontHighlight"/>
<NormalTexture file="Interface\Buttons\UI-Panel-Button-Up"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></NormalTexture>
<PushedTexture file="Interface\Buttons\UI-Panel-Button-Down"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></PushedTexture>
<HighlightTexture file="Interface\Buttons\UI-Panel-Button-Highlight" alphaMode="ADD"><TexCoords left="0" right="0.625" top="0" bottom="0.6875"/></HighlightTexture>
<Scripts>
<!-- ref l.289: `HideUIPanel(this:GetParent():GetParent())` — the window, two up
from the button through the page. Named directly here: this engine has no
cross-file parenting, so the page is not really the window's child (header).
CLOSE is the ref's own literal `text="CLOSE"` attribute — the GlobalStrings
key, resolved by the loader's text lookup like every other one here. -->
<OnClick>HideUIPanel(getglobal("CharacterFrame"))</OnClick>
</Scripts>
</Button>
<!-- The attributes block (ref PetAttributesFrame, l.293-516): 230×78 at TOPLEFT
(67,-300), two 114×78 stat-background plates side by side. The pet's plate is the
SIMPLE two-column one — the character page's is three stacked 3-piece columns
because it also carries a ranged block, which a pet has none of. -->
<Frame name="PetAttributesFrame">
<Size><AbsDimension x="230" y="78"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="67" y="-300"/></Offset></Anchor></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="114" y="78"/></Size>
<Anchors><Anchor point="TOPLEFT"/></Anchors>
<TexCoords left="0" right="0.89" top="0" bottom="0.61"/>
</Texture>
<Texture file="Interface\PaperDollInfoFrame\UI-Character-StatBackground">
<Size><AbsDimension x="114" y="78"/></Size>
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="115" y="0"/></Offset></Anchor></Anchors>
<TexCoords left="0" right="0.89" top="0" bottom="0.61"/>
</Texture>
</Layer>
</Layers>
<Frames>
<!-- The 5 primaries (ref l.335-419): TOPLEFT (6,-5), chained BOTTOMLEFT with a
1px gap. Labels are the template's $parentLabel, filled by _SetStats. -->
<Frame name="PetStatFrame1" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT"><Offset><AbsDimension x="6" y="-5"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("pet", "STRENGTH")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetStatFrame2" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame1" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("pet", "AGILITY")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetStatFrame3" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame2" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("pet", "STAMINA")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetStatFrame4" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame3" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("pet", "INTELLECT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetStatFrame5" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetStatFrame4" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>PaperDollStatTooltip("pet", "SPIRIT")</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<!-- The right-hand column (ref l.420-514): attack rating, attack power, damage,
defense, armor — the second plate's own chain, rooted at (122,-5) of the
attributes frame. -->
<Frame name="PetAttackFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttributesFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="122" y="-5"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<!-- ref l.429-433: the pet's attack row shows the BARE tooltip title, with
no ATTACK_TOOLTIP_SUBTEXT line — the character page's own row adds it.
Kept as the ref has it. -->
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(ATTACK_TOOLTIP, 1.0, 1.0, 1.0)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetAttackPowerFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttackFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetDamageFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetAttackPowerFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<!-- ref l.468-470: the pet's damage row reuses the CHARACTER damage
hover verbatim — it reads `self.damage`/`self.dps`, which
_SetDamage filled with the pet's numbers. -->
<OnEnter>CharacterDamageFrame_OnEnter()</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetDefenseFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetDamageFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(DEFENSE_TOOLTIP, 1.0, 1.0, 1.0)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
<Frame name="PetArmorFrame" inherits="BenillaStatRowTemplate">
<Anchors><Anchor point="TOPLEFT" relativeTo="PetDefenseFrame" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-1"/></Offset></Anchor></Anchors>
<Scripts>
<OnLoad>self:EnableMouse(true)</OnLoad>
<OnEnter>
GameTooltip:SetOwner(self, "ANCHOR_RIGHT")
GameTooltip:SetText(self.tooltip)
GameTooltip:AddLine(self.tooltipSubtext, NORMAL_FONT_COLOR.r, NORMAL_FONT_COLOR.g, NORMAL_FONT_COLOR.b, 1)
GameTooltip:Show()
</OnEnter>
<OnLeave>GameTooltip:Hide()</OnLeave>
</Scripts>
</Frame>
</Frames>
</Frame>
<!-- The resistance column (ref PetResistanceFrame, l.517-635): 32×160 whose RIGHT edge
sits 347px in from the page's left — 50px further right than the character page's
297, because the pet page's wider model pane pushes it out. The five icons carry
their own texcoords into the shared ResistanceIcons sheet, in the ref's own school
order (arcane/fire/nature/frost/shadow = BENILLA_MAGICRES_IDS). -->
<Frame name="PetResistanceFrame">
<Size><AbsDimension x="32" y="160"/></Size>
<Anchors>
<Anchor point="TOPRIGHT" relativeTo="PetPaperDollFrame" relativePoint="TOPLEFT"><Offset><AbsDimension x="347" y="-77"/></Offset></Anchor>
</Anchors>
<Frames>
<Frame name="PetMagicResFrame1" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="PetResistanceFrame" relativePoint="TOP"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.2265625" bottom="0.33984375"/>
</Texture>
<FontString name="PetMagicResText1" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="PetMagicResFrame2" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame1" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0" bottom="0.11328125"/>
</Texture>
<FontString name="PetMagicResText2" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="PetMagicResFrame3" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame2" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.11328125" bottom="0.2265625"/>
</Texture>
<FontString name="PetMagicResText3" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="PetMagicResFrame4" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame3" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.33984375" bottom="0.453125"/>
</Texture>
<FontString name="PetMagicResText4" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
<Frame name="PetMagicResFrame5" inherits="BenillaMagicResistanceFrameTemplate">
<Anchors><Anchor point="TOP" relativeTo="PetMagicResFrame4" relativePoint="BOTTOM"/></Anchors>
<Layers>
<Layer level="BACKGROUND">
<Texture file="Interface\PaperDollInfoFrame\UI-Character-ResistanceIcons">
<TexCoords left="0" right="1.0" top="0.453125" bottom="0.56640625"/>
</Texture>
<FontString name="PetMagicResText5" inherits="GameFontHighlightSmall" text="X">
<Anchors><Anchor point="BOTTOM"><Offset><AbsDimension x="0" y="3"/></Offset></Anchor></Anchors>
</FontString>
</Layer>
</Layers>
</Frame>
</Frames>
</Frame>
</Frames>
<Scripts>
<OnLoad>PetPaperDollFrame_OnLoad()</OnLoad>
<OnEvent>PetPaperDollFrame_OnEvent()</OnEvent>
<OnShow>PetPaperDollFrame_OnShow()</OnShow>
<OnHide>PetPaperDollFrame_OnHide()</OnHide>
</Scripts>
</Frame>
</Ui>