a hundred stale claims retire, and text= was never a divergence
- a comment that justifies code by an engine difference is a claim with an expiry date, and the tree had ~96 expired ones across `assets/ui`, `ui_script/` and `benilla-ui` — a hygiene pass, no behaviour change: dangling `Benilla*` names with zero definitions (`BenillaMoney_*` at thirteen sites after 1937/1962 converged the coin cluster onto the stock `SmallMoneyFrameTemplate`; `BenillaGossipRow_Resize`, `BenillaErrorsFrame_AddMessage`, the digit feed 1285 deleted, `BenillaMerchantItem_OnClick` and three more), "the shipped `assets/ui/<X>.xml`" for thirty-odd files the directory no longer holds — it holds nine — each site now naming the chain file that actually loads, `UiPanels.xml` in the present tense after 1988 took the panel manager and the dialog engine stock, and expired self-deprecation: SimpleHTML "a widget kind we do not have" (1337 built it), "benilla has no console" (1948 bound `ConsoleExec`), `Model`'s "seven verbs not published yet" and "`ClearFog` is a guess" (all 23 are published, the carve sits 500 lines below, `UNBUILT` has been `[&str; 0]` since 2027), `frame_flag_gate`'s doc describing four groups of accepted mouse divergences over a `KNOWN` list that is empty (the empty array and its enforcement stay), five comments saying key delivery is not gated on `keyboard_enabled` where `script::keyboard`'s walk filters on exactly that field (1319), `SetRaidTarget` called a verb this house lacks beside an assert that its intent IS queued (1820), two questlog tests deferring to a screenshot for `GetPoint`/`IsVisible` the regions already answer, `benilla.toc`'s "trade still needs four engine verbs" (all four bound, 1966) and its ping `Model`s "this engine draws no pixels for" (2013 renders `MinimapPing.mdx` itself), and `rig_anim`'s `set_paused` naming a booth caller it does not have; and the code the prose was propping up goes with it: `GameMenuFrame.xml`'s six `<OnLoad>` label setters, because `text=` on a `<Button>` is a global-string lookup at the loader's attribute step (0995, built 61 seconds before the file was written) — the buttons carry `text="LOGOUT"`/`"EXIT_GAME"`/`"MACROS"`/`"RETURN_TO_GAME"`/`"MAIN_MENU"` as the reference's own do; `ScriptLogFrame`'s five authored close-button lines, now `inherits="UIPanelCloseButton"` off the chain's `UIPanelTemplates.xml` seated 757 lines above it (1846); and the editbox's 22-line "gap" doc claiming only the self-acquire `autoFocus` path is built and its `false` default unverified — path 1 is 1686's `visibility_focus`, and the default is `true`, byte-read off `flags = 1` at `0x779a29`/`0x779a2e`, so the test named `autofocus_does_not_focus_on_show…` is renamed for what it actually pins (2044) - the `text=` divergence never existed: `resolve_text`'s doc had claimed since 0068 that falling back to the raw attribute on a GlobalStrings miss was "one deliberate divergence" from a reference that draws the empty string, and read at the byte it is the reference's own behaviour — `FrameScript_GetText 0x703bf0` does return a pre-seeded empty string (`0x882748`) when the global is not a string, but that return never reaches a label, because all three `text=` readers image-wide test it and substitute the raw attribute: `Button::LoadXML` at `0x778c07` (`0x778c31 mov eax,esi`), `CSimpleFontString::LoadXML` at `0x771006` byte-identically, and `0x7292a6`, which pushes `edi` on the empty arm; so the loader changes nothing, the two test comments that had called `PetLevelText`'s "Level level race class" placeholder and `CLOSE` our divergence now name the reference's arm (a real 1.12 client draws them too), and what IS ours is the extra warning on a key-shaped miss (0983 → 0991), a strict addition; beside it, `GetEffectiveScale`'s root factor is 1 not because "benilla has no `uiScale` CVar" — 1804 registered one, default 0.9 — but because the dial is applied at the raster seam, `768/uiScale` UI units of virtual-screen height rather than a scale on `UIParent`, so every coordinate the VM hands Lua is already in those units and the reference's `GetCursorPosition()/GetEffectiveScale()` transcriptions convert correctly with a root of 1 (2044) Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -45,12 +45,6 @@
|
||||
on — hovering an ordinary bag slot — runs the real client's code, unedited.
|
||||
|
||||
── Named, not hidden: what still does not work through the sourced files ────────────────────
|
||||
· `MerchantItemButton_OnClick`. The reference's `ContainerFrameItemButton_OnClick` closes its
|
||||
shift-right-click-at-a-vendor arm with a `SplitStack` closure that calls that global, and
|
||||
benilla's merchant window publishes `BenillaMerchantItem_OnClick` instead. Confirming a stack
|
||||
split with a vendor open therefore raises. That is the same "a Benilla name shadows the
|
||||
reference's" class this whole seam exists to close, one window over, and it is left loud
|
||||
rather than guarded (1203): the fix is to publish the merchant global, not to hide the call.
|
||||
· `REPAIR_COST` is read by the reference's repair-mode arm. It is a `GlobalStrings.lua` key and
|
||||
this client sources that file too, so it resolves at runtime — and the arm is unreachable
|
||||
today regardless, since `GameTooltip:SetBagItem`'s repairCost is an INTERIM 0.
|
||||
|
||||
@@ -30,9 +30,9 @@
|
||||
Options", SOUNDOPTIONS_MENU "Sound Options", UIOPTIONS_MENU "Interface Options",
|
||||
MACROS "Macros", LOGOUT "Logout", EXIT_GAME "Exit Game",
|
||||
RETURN_TO_GAME "Return to Game", MAIN_MENU "Main Menu". Declared as the real globals
|
||||
(they are addon-visible API, decision 0068) and read back into each button's label in its
|
||||
OnLoad — the ref's `text="LOGOUT"` attribute IS a global lookup, which this engine's
|
||||
literal `text=` is not.
|
||||
(they are addon-visible API, decision 0068) and carried on each button's own
|
||||
`text=` attribute — which this engine resolves as a global-string lookup exactly as
|
||||
the reference does (decision 0995, `loader/widgets.rs`'s `resolve_text`).
|
||||
· **The sounds** — the ref's own OnClick lines: igMainMenuOption (the five option buttons),
|
||||
igMainMenuLogout, igMainMenuQuit, igMainMenuContinue. All four kits verified present in
|
||||
SoundEntries.dbc (patch-2.MPQ) alongside the igMainMenuOpen/Close pair already in use.
|
||||
@@ -94,10 +94,9 @@
|
||||
scripts, so it never actually moves).
|
||||
`frameStrata="DIALOG"` is what puts it over the world UI, and that is kept.
|
||||
**The "no frame-level raise" half of that reason is gone**: the raise law is built
|
||||
(`script::object::toplevel`, wow-re `ui/scratch/toplevel-raise.md`) and the loader honours
|
||||
`toplevel="true"`. Re-adding it here is a one-word change and would make the Esc menu come
|
||||
to the front over any other DIALOG-strata frame it overlaps; left off pending the
|
||||
director's call on the look, not on the mechanism.
|
||||
(`script::object::toplevel`, wow-re `ui/scratch/toplevel-raise.md`), the loader honours
|
||||
`toplevel="true"`, and the frame below carries it — so the Esc menu does come to the
|
||||
front over any other DIALOG-strata frame it overlaps.
|
||||
-->
|
||||
<Script><![CDATA[
|
||||
-- The reference GlobalStrings (patch-2.MPQ Interface\FrameXML\GlobalStrings.lua), quoted
|
||||
@@ -164,7 +163,7 @@
|
||||
<!-- Era swaps the header text to 13px (MainMenuFrameMixin:OnLoad →
|
||||
SetHeaderFont(GameFontNormalMed1)); the seat is the 1.12 one (within half a
|
||||
unit of era's — header SCOPE note). -->
|
||||
<FontString name="GameMenuFrameTitle" inherits="GameFontNormalMed1">
|
||||
<FontString name="GameMenuFrameTitle" inherits="GameFontNormalMed1" text="MAIN_MENU">
|
||||
<Anchors>
|
||||
<Anchor point="TOP" relativeTo="GameMenuFrameHeader"><Offset><AbsDimension x="0" y="-14"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
@@ -184,12 +183,11 @@
|
||||
the center-replacement rule for the swap (ShowUIPanel l.697-702: native-center
|
||||
frames replace each other); hiding the menu explicitly says the same thing
|
||||
without relying on the eviction path. -->
|
||||
<Button name="GameMenuButtonOptions" inherits="GameMenuButtonTemplate">
|
||||
<Button name="GameMenuButtonOptions" inherits="GameMenuButtonTemplate" text="Options">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT"><Offset><AbsDimension x="28" y="-32"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText("Options")</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(GameMenuFrame)
|
||||
@@ -210,14 +208,13 @@
|
||||
</Anchors>
|
||||
<Scripts><OnLoad>GameMenuButton_Pending(self, GAMEMENU_SUPPORT)</OnLoad></Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonMacros" inherits="GameMenuButtonTemplate">
|
||||
<Button name="GameMenuButtonMacros" inherits="GameMenuButtonTemplate" text="MACROS">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonSupport" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<!-- LIVE since decision 0983 (MacroFrame.xml). The ref's own body
|
||||
(GameMenuFrame.xml l.118-122): hide the menu, then ShowMacroFrame(). -->
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText(MACROS)</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuOption")
|
||||
HideUIPanel(getglobal("GameMenuFrame"))
|
||||
@@ -230,12 +227,11 @@
|
||||
button while either logout dialog is already up (you cannot re-arm a running
|
||||
countdown); it runs on the menu's own show, which is when a re-opened menu has to
|
||||
re-read that state. -->
|
||||
<Button name="GameMenuButtonLogout" inherits="GameMenuButtonTemplate">
|
||||
<Button name="GameMenuButtonLogout" inherits="GameMenuButtonTemplate" text="LOGOUT">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonMacros" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText(LOGOUT)</OnLoad>
|
||||
<OnShow>
|
||||
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
|
||||
self:Enable()
|
||||
@@ -250,12 +246,11 @@
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonQuit" inherits="GameMenuButtonTemplate">
|
||||
<Button name="GameMenuButtonQuit" inherits="GameMenuButtonTemplate" text="EXIT_GAME">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonLogout" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="0"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText(EXIT_GAME)</OnLoad>
|
||||
<OnShow>
|
||||
if not StaticPopup_Visible("CAMP") and not StaticPopup_Visible("QUIT") then
|
||||
self:Enable()
|
||||
@@ -270,12 +265,11 @@
|
||||
</OnClick>
|
||||
</Scripts>
|
||||
</Button>
|
||||
<Button name="GameMenuButtonContinue" inherits="GameMenuButtonTemplate">
|
||||
<Button name="GameMenuButtonContinue" inherits="GameMenuButtonTemplate" text="RETURN_TO_GAME">
|
||||
<Anchors>
|
||||
<Anchor point="TOPLEFT" relativeTo="GameMenuButtonQuit" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-20"/></Offset></Anchor>
|
||||
</Anchors>
|
||||
<Scripts>
|
||||
<OnLoad>self:SetText(RETURN_TO_GAME)</OnLoad>
|
||||
<OnClick>
|
||||
PlaySound("igMainMenuContinue")
|
||||
HideUIPanel(GameMenuFrame)
|
||||
@@ -286,11 +280,10 @@
|
||||
<!-- ref GameMenuFrame OnShow/OnHide, verbatim: the micro row's Main Menu button pushes in
|
||||
while the menu is up, and the bag bar greys out under it (you cannot open bags from
|
||||
behind the menu — ShowUIPanel's center gate would refuse the window anyway, so the
|
||||
greying is the visible half of a rule the panel manager already enforces). Guarded, the
|
||||
the old UiPanels.xml `CloseMenus and CloseMenus()` posture: a headless test loads this file
|
||||
without the action bar behind it. -->
|
||||
greying is the visible half of a rule the panel manager already enforces). Guarded, in
|
||||
the `CloseMenus and CloseMenus()` posture our retired panel file used: a headless test
|
||||
loads this file without the action bar behind it. -->
|
||||
<Scripts>
|
||||
<OnLoad>GameMenuFrameTitle:SetText(MAIN_MENU)</OnLoad>
|
||||
<OnShow>
|
||||
-- The shared era-window scale first (ERA_WINDOW_SCALE, OptionsFrame.xml — loaded
|
||||
-- before this file): the era menu has no scale of its own (GameMenuFrame.xml +
|
||||
|
||||
@@ -108,7 +108,8 @@
|
||||
permanently, AddOns the unselected gold — the era swaps these in code
|
||||
(MinimalTabMixin), and with nothing behind AddOns the selection can never change, so
|
||||
the two states are authored rather than driven. The mixin port comes with the addon
|
||||
list. Width is era's text+40 law via an OnUpdate measure poll; the era's
|
||||
list. Width is era's text+40 law, fitted once from each tab's own `<OnShow>` (2028 —
|
||||
the engine's measurer answers in the call that asks); the era's
|
||||
measure-at-the-medium-font quirk is deliberately dropped (~6 units of padding).
|
||||
- **Search WORKS (0984)** — the era SettingsPanel mechanism re-expressed (the Script
|
||||
block's search section): label-tag substring match scored by matched length,
|
||||
@@ -1186,10 +1187,12 @@
|
||||
-- numbering our page does not use, so each row names the child it drives instead.
|
||||
--
|
||||
-- NOT bound, and named rather than faked: pfUI's module goes on to hook `SetWorldDetail`
|
||||
-- and drive `ConsoleExec("frillDensity ...")`, `lodDist`, `nearClip`, `maxLOD`. benilla has
|
||||
-- no console and none of those CVars. Neither verb is called at load, so the module loads
|
||||
-- and its slider write lands; its EXTENDED arm cannot work here and a no-op ConsoleExec
|
||||
-- would be exactly the silent pretence 1203 exists to forbid.
|
||||
-- (unbound) and drive `ConsoleExec("frillDensity ...")`, `lodDist`, `nearClip`, `maxLOD`.
|
||||
-- `ConsoleExec` itself IS bound (1948 — it writes a registered CVar or pushes a console
|
||||
-- command the app dispatches), but none of those four CVars is registered, so those
|
||||
-- writes reach nothing. Neither verb is called at load, so the module loads and its
|
||||
-- slider write lands; its EXTENDED arm cannot work here, and registering a CVar nothing
|
||||
-- reads would be exactly the silent pretence 1203 exists to forbid.
|
||||
OptionsFrameSliders = {
|
||||
{ text = "UI Scale", func = "uiscale",
|
||||
minValue = 0.64, maxValue = 1.0, valueStep = 0.01, benillaRow = "RowUiScale" },
|
||||
@@ -1546,8 +1549,9 @@
|
||||
-- Grey or wake one row, whichever control it wears (the Audio rule above predates this and
|
||||
-- reaches its one checkbox by name). A dropdown row greys the way 1.12's own
|
||||
-- OptionsFrame_DisableDropDown does — the capsule's toggle BUTTON disabled so the list
|
||||
-- cannot open, and its text in GRAY_FONT_COLOR — since our kit's
|
||||
-- UIDropDownMenu_DisableButton addresses a LIST row (level, id), not the capsule.
|
||||
-- cannot open, and its text in GRAY_FONT_COLOR — since the chain's own
|
||||
-- UIDropDownMenu_DisableButton addresses a LIST row (level, id), not the capsule, and
|
||||
-- 1.12 has no `UIDropDownMenu_DisableDropDown` at all.
|
||||
function OptionsRow_SetEnabled(row, on)
|
||||
local name = row:GetName()
|
||||
local box = getglobal(name .. "Check")
|
||||
|
||||
@@ -216,9 +216,9 @@
|
||||
end
|
||||
]]></Script>
|
||||
|
||||
<!-- One list row: the `#N xCount` index column, then the message head. Same shape as
|
||||
BenillaCraftSkillButtonTemplate (fixed columns — this engine has no FontString-width-based
|
||||
dynamic anchor). -->
|
||||
<!-- One list row: the `#N xCount` index column, then the message head. Fixed columns, 1.12's
|
||||
own list idiom — not a width limitation: a FontString with a zero-authored axis takes its
|
||||
measured box into the resolve (`benilla-ui` `script/layout.rs`, decision 1664). -->
|
||||
<Button name="BenillaScriptLogRowTemplate" virtual="true" hidden="true">
|
||||
<Size><AbsDimension x="293" y="16"/></Size>
|
||||
<Layers>
|
||||
@@ -275,15 +275,12 @@
|
||||
</Layer>
|
||||
</Layers>
|
||||
<Frames>
|
||||
<!-- The top-right X. This engine ships no UIPanelCloseButton; the minimize-button art
|
||||
stands in, MacroFrame/TrainerFrame's precedent. -->
|
||||
<Button name="BenillaScriptLogCloseButton">
|
||||
<Size><AbsDimension x="32" y="32"/></Size>
|
||||
<!-- The top-right X: the chain's own `UIPanelCloseButton` (seated at benilla.toc's
|
||||
`Interface\FrameXML\UIPanelTemplates.xml`, 757 lines above this file). Its
|
||||
32x32, its three minimize-button state textures and its
|
||||
`HideUIPanel(this:GetParent())` are byte-for-byte what stood here authored. -->
|
||||
<Button name="BenillaScriptLogCloseButton" inherits="UIPanelCloseButton">
|
||||
<Anchors><Anchor point="TOPRIGHT"><Offset><AbsDimension x="-29" y="-8"/></Offset></Anchor></Anchors>
|
||||
<NormalTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Up"/>
|
||||
<PushedTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Down"/>
|
||||
<HighlightTexture file="Interface\Buttons\UI-Panel-MinimizeButton-Highlight" alphaMode="ADD"/>
|
||||
<Scripts><OnClick>HideUIPanel(getglobal("BenillaScriptLogFrame"))</OnClick></Scripts>
|
||||
</Button>
|
||||
|
||||
<!-- Declared BEFORE the rows so row text paints over the glow (the house z-tiebreak). -->
|
||||
|
||||
@@ -174,9 +174,10 @@ Interface\FrameXML\MoneyFrame.xml
|
||||
# It is groundwork rather than a window, and the windows it is groundwork FOR are named so the
|
||||
# next session does not have to re-derive it: stock `TradeFrame.xml` and `MailFrame.xml` both build
|
||||
# their amount entry from this template and call `MoneyInputFrame_SetMode`/`_GetCopper`/
|
||||
# `_SetCopper`/`_ClearFocus`/`_SetTextColor`/`_SetOnvalueChangedFunc` — six of the names below —
|
||||
# and neither can be sourced until they exist. (Trade still needs four ENGINE verbs beyond this:
|
||||
# `AddTradeMoney`, `GetCursorMoney`, `GetTradePlayerItemLink`, `GetTradeTargetItemLink`.)
|
||||
# `_SetCopper`/`_ClearFocus`/`_SetTextColor`/`_SetOnvalueChangedFunc` — six of the names below.
|
||||
# Both windows are migrated now: `MailFrame.xml` at l.897 and `TradeFrame.xml` at l.906, the
|
||||
# latter once 1966 bound the four engine verbs it also needed (`AddTradeMoney`,
|
||||
# `GetCursorMoney`, `GetTradePlayerItemLink`, `GetTradeTargetItemLink`).
|
||||
#
|
||||
# **TWO entries, and the `.lua` was missing.** This `.xml` declares only the template and does not
|
||||
# source its `.lua`; the reference's toc lists both (l.11-12). Listing the `.xml` alone brought the
|
||||
@@ -489,8 +490,8 @@ Interface\FrameXML\MainMenuBarMicroButtons.xml
|
||||
Interface\FrameXML\BattlefieldFrame.xml
|
||||
# Minimap.xml + Minimap.lua: the cluster around the engine-rendered <Minimap> widget hole
|
||||
# (`crate::minimap` fills it), the zone text, the zoom buttons, the tracking / mail / meeting
|
||||
# stone / battlefield icons, and `MiniMapPing` — a <Model> this engine draws no pixels for; the
|
||||
# app's ping sprite (`minimap/ping.rs`) follows that frame's own show/hide and alpha, so the
|
||||
# stone / battlefield icons, and `MiniMapPing` — a <Model> that renders its own MinimapPing.mdx
|
||||
# since 2013; `minimap/ping.rs` keeps the pin, the click seat and the wire half, so the
|
||||
# 5 s lifetime is this file's, as the reference's is. After GameTooltip (the zone-text hover).
|
||||
# 0203's transcription is gone.
|
||||
Interface\FrameXML\Minimap.xml
|
||||
@@ -579,8 +580,8 @@ Interface\FrameXML\ColorPickerFrame.xml
|
||||
# <Include>s its own blip templates (WorldMapFrameTemplates.xml) before the frames that inherit
|
||||
# them. Its player arrow is the Create/Position/Show/UpdateWorldMapArrowFrames family
|
||||
# (`script/worldmap_arrow.rs`), an anonymous Model pane the app draws as the minimap's arrow
|
||||
# sprite; its WorldMapPing is a MinimapPing.mdx Model pane this engine holds and draws nothing
|
||||
# into (named gap, 1980).
|
||||
# sprite; its WorldMapPing is the same MinimapPing.mdx Model pane, which draws its own file on
|
||||
# the map sheet since 2013 (closing the gap 1980 named).
|
||||
Interface\FrameXML\WorldMapFrame.xml
|
||||
# ══ THE CAST BAR — the reference's own, off the player's chain (1751) ══
|
||||
# `CastingBarFrame` + its Border/Flash/Spark/Text children, driven by the SPELLCAST_* events
|
||||
@@ -964,7 +965,7 @@ Interface\FrameXML\BankFrame.xml
|
||||
# a `CSimpleFrame` and nothing else, so it is `FrameKind::Frame` and the route lines stay what the
|
||||
# reference makes them: plain `<Texture>` children rotated by an 8-argument `SetTexCoord`.
|
||||
#
|
||||
# Pulls only `TaxiFrame.lua`, and still needs only Fonts/UiPanels/GameTooltip (SetTooltipMoney),
|
||||
# Pulls only `TaxiFrame.lua`, and still needs only Fonts/UIParent/GameTooltip (SetTooltipMoney),
|
||||
# all loaded above — so it keeps its seat in the "NPC session windows grouped" cluster.
|
||||
Interface\FrameXML\TaxiFrame.xml
|
||||
# The stable-master window (decision 1676): the hunter's three-slot pet stable over the stable
|
||||
@@ -1186,8 +1187,8 @@ Interface\FrameXML\TutorialFrame.xml
|
||||
# options family, the same chain `MainMenuBarMicroButtons` is deferred behind. All three columns
|
||||
# read clean because the file LOADS clean; what it needs is a sibling window, not a verb.
|
||||
#
|
||||
# Its CAMP/QUIT dialogs and their event driver HAVE moved out, to UiPanels.xml — the reference's
|
||||
# own homes (StaticPopup.lua l.564/586 for the dialogs, UIParent.lua l.304-315 for the three
|
||||
# events). The ESC menu never owned them; it was just where ours were first needed. That is one
|
||||
# less thing to move when the options family lands.
|
||||
# Its CAMP/QUIT dialogs and their event driver HAVE moved out — the dialogs to `StaticPopup.xml`,
|
||||
# the driver to `UIParent.xml`, the reference's own homes (StaticPopup.lua l.564/586 for the
|
||||
# dialogs, UIParent.lua l.304-315 for the three events), both stock since 1988. The ESC menu never
|
||||
# owned them; it was just where ours were first needed. That is one less thing to move when the options family lands.
|
||||
GameMenuFrame.xml
|
||||
|
||||
@@ -1121,7 +1121,7 @@ fn load_dependencies(
|
||||
///
|
||||
/// **A string literal in the fourth argument, OR a local bound to one in the same file.** The
|
||||
/// literal-only form was the honest under-report this doc used to name — and then a decision was
|
||||
/// made on the number it produced. `assets/ui/ItemButtonTemplate.xml` declined to build
|
||||
/// made on the number it produced. `Interface\FrameXML\ItemButtonTemplate.xml` declined to build
|
||||
/// `ItemButtonTemplate` citing "the harness's template demand ranks it at zero on both axes"; the
|
||||
/// zero was real, and pfUI wanted `ContainerFrameItemButtonTemplate` (which inherits it) the whole
|
||||
/// time, through
|
||||
@@ -3575,10 +3575,10 @@ mod dependency_tests {
|
||||
/// **A template named through a local resolves; the shapes that still cannot are asserted too.**
|
||||
///
|
||||
/// The literal-only scan was an honest under-report — stated at `missing_templates` — and a
|
||||
/// decision was then made on the number it produced: `assets/ui/ItemButtonTemplate.xml`
|
||||
/// declined to build `ItemButtonTemplate` citing a demand of zero "on both axes". The zero was
|
||||
/// real and the demand was not: pfUI binds `local tpl = "ContainerFrameItemButtonTemplate"`
|
||||
/// and passes the variable, so the one addon that wanted it was invisible to the ranking.
|
||||
/// decision was then made on the number it produced:
|
||||
/// `Interface\FrameXML\ItemButtonTemplate.xml` declined to build `ItemButtonTemplate` citing a
|
||||
/// demand of zero "on both axes". The zero was real and the demand was not: pfUI binds `local
|
||||
/// tpl = "ContainerFrameItemButtonTemplate"` and passes the variable, so the one addon that wanted it was invisible to the ranking.
|
||||
///
|
||||
/// Both halves are asserted, because a scanner that quietly widened would be the worse fix:
|
||||
/// the shape it now sees, AND the shapes it still does not, so the next decision quoting this
|
||||
|
||||
@@ -252,8 +252,8 @@ pub(super) fn seed_ui_fixture(
|
||||
gossip.quests = vec![(783, 0, 5, "Eagan Peltskinner".into())];
|
||||
// One short option AND four that WRAP — the live shape both gossip bugs came in as
|
||||
// (the director's screenshots), and a menu deliberately TALLER than the parchment so
|
||||
// the capture covers the whole chain: the per-row auto-height
|
||||
// (`BenillaGossipRow_Resize`), the scroll frame that contains the overflow, and the
|
||||
// the capture covers the whole chain: the stock `GossipResize`'s per-row auto-height,
|
||||
// the scroll frame that contains the overflow, and the
|
||||
// scrollbar that appears with it. A fixture of one-line labels showed none of this —
|
||||
// every row fit the template's 16 px and nothing ever overflowed.
|
||||
let judgement = [
|
||||
|
||||
@@ -35,9 +35,9 @@
|
||||
//! rather than being skipped (`0x48efe0`).
|
||||
//! - **ESC is not an engine binding.** `StopCinematic` has zero native callers in the reference:
|
||||
//! the only skip path is `CinematicFrame.xml`'s own `OnKeyDown`, which is why benilla's copy of
|
||||
//! that frame (`assets/ui/CinematicFrame.xml`) carries the same handler and why the Lua binding
|
||||
//! queues [`SessionRequest::StopCinematic`](benilla_ui::script::SessionRequest) rather than
|
||||
//! reaching in here.
|
||||
//! that frame (stock `Interface\FrameXML\CinematicFrame.xml`) carries the same handler and why
|
||||
//! the Lua binding queues [`SessionRequest::StopCinematic`](benilla_ui::script::SessionRequest)
|
||||
//! rather than reaching in here.
|
||||
//! - **The ack ends the run, once.** `CMSG_COMPLETE_CINEMATIC` goes out on a natural end and on an
|
||||
//! ESC skip alike (`0x48f080`). Decision 0196 is why it can never be dropped: unacked, vmangos
|
||||
//! re-anchors object visibility to its own copy of the flying camera and everything around the
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
//! `MiniMapPing` `<Model>` it shows on `MINIMAP_PING`, re-seats every frame from
|
||||
//! `GetPingPosition()`, holds 5 s (`MINIMAPPING_TIMER`), "fades" 0.5 s through a `SetAlpha(255·t)`
|
||||
//! that clamps to full until the last ~2 ms, and hides. Since 1751's swap that file runs here
|
||||
//! verbatim (1974), and since decision 2008 the `<Model>` it shows **renders its own file**
|
||||
//! verbatim (1974), and since decision 2013 the `<Model>` it shows **renders its own file**
|
||||
//! (`Interface\MiniMap\Ping\MinimapPing.mdx`, through `crate::ui_models`): the spinner on its
|
||||
//! global-sequence clock, the static centre, the ring on the looping Stand — the model's own
|
||||
//! bones, weight tracks and additive quads, on the pane's private clock that runs only while the
|
||||
|
||||
@@ -186,8 +186,9 @@ pub(super) fn chat_wrong_faction(chat_log: &mut ChatLog) {
|
||||
/// (wow-re `system/ui/ui.md` l.2459). Nothing on this path touches the chat composer.
|
||||
///
|
||||
/// **This used to push into the chat feed**, as a stand-in from before benilla had an errors
|
||||
/// frame. It has had a real one for a long time (`assets/ui/ErrorsFrame.xml`, the ref
|
||||
/// `UIErrorsFrame` as a genuine `MessageFrame`), and the stand-in outlived its reason: vmangos
|
||||
/// frame. It has had a real one for a long time — the ref `UIErrorsFrame`, a genuine
|
||||
/// `MessageFrame` (ours until 1751's fourteenth window, the chain's own
|
||||
/// `Interface\FrameXML\UIErrorsFrame.xml` since) — and the stand-in outlived its reason: vmangos
|
||||
/// `Player::SetGameMaster` answers `.gm on|off` with **both** `SendSysMessage` and
|
||||
/// `SendNotification` (`Objects/Player.cpp:2676-2677`/`2701-2702`), so a client that sinks the
|
||||
/// notification into chat prints "GM mode is ON" **twice** where the reference prints it once.
|
||||
@@ -382,8 +383,8 @@ mod tests {
|
||||
/// handler `0x401800` is "read the cstring, `mov edx,1`, `call 0x4945b0`" — event `0xe0`
|
||||
/// `UI_ERROR_MESSAGE`, the UIErrorsFrame toast — plus a console log, and it never reaches the
|
||||
/// chat composer. Benilla used to park the notice in the chat feed for want of an errors
|
||||
/// frame; it has had one since `assets/ui/ErrorsFrame.xml`, and the stand-in was what printed
|
||||
/// "GM mode is ON" twice.
|
||||
/// frame; it has had one since our own `ErrorsFrame.xml` (the chain's `UIErrorsFrame.xml`
|
||||
/// since 1751), and the stand-in was what printed "GM mode is ON" twice.
|
||||
///
|
||||
/// The pair is the test: BOTH halves of one toggle, one chat line, one toast.
|
||||
#[test]
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! The net bridge queues [`CastBarEdge`]s (self-casts only — the producers filter on the self
|
||||
//! guid; the channel pair is self-only *on the wire*), and the drain fires the reference
|
||||
//! client's FrameScript events into the script VM — `SPELLCAST_START` and family, the exact
|
||||
//! contract `assets/ui/CastingBar.xml` (the extracted 1.12 `CastingBarFrame`) registers for.
|
||||
//! contract stock `Interface\FrameXML\CastingBarFrame.xml` registers for.
|
||||
//! The spell name rides the event (resolved here from the `Spell.dbc` catalog — the script VM
|
||||
//! has no spell-catalog binding, deliberately: one lookup face, decision 0107).
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//!
|
||||
//! The net bridge queues [`MirrorTimerEdge`]s and the drain fires the reference client's
|
||||
//! FrameScript events into the script VM — `MIRROR_TIMER_START` / `_PAUSE` / `_STOP`, the exact
|
||||
//! contract `assets/ui/MirrorTimer.xml` (the transcribed 1.12 `MirrorTimer1/2/3`) registers for.
|
||||
//! contract stock `Interface\FrameXML\MirrorTimer.xml` (`MirrorTimer1/2/3`) registers for.
|
||||
//! The bars themselves are the reference's: the frame stores the value and integrates
|
||||
//! `value + scale * elapsed` every OnUpdate, so a packet every few seconds is enough to paint a
|
||||
//! smooth countdown.
|
||||
|
||||
@@ -6,7 +6,7 @@ fn action_ids(s: &mut UiScript) -> Vec<u32> {
|
||||
s.take_action_uses().into_iter().map(|u| u.action).collect()
|
||||
}
|
||||
|
||||
/// Load the real `assets/ui/ActionBar.xml` (the shipped default bar) into a bare engine and
|
||||
/// Load the stock `Interface\FrameXML\ActionBarFrame.xml` into a bare engine and
|
||||
/// drive it with a synthetic action snapshot — the slice-1 chain minus Bevy: template
|
||||
/// expansion over 12 instances, the vanilla bonus-page formula, icon paint on events, empty
|
||||
/// slots drawing no icon, and a physical click queuing the right UseAction id.
|
||||
@@ -798,19 +798,20 @@ fn macro_name_line_follows_get_action_text_through_the_xml() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// The shipped **bag BAR** — `assets/ui/BagFrame.xml` — materialized frame for frame, and then
|
||||
/// driven end to end: the bar's own backpack toggle opens the backpack window, the fed stack
|
||||
/// paints in its slot's well, the slot's clicks queue the right intents, and the toggle shuts it
|
||||
/// again. It lives in this file because the bar seats on `MainMenuBarArtFrame` (ActionBar.xml) —
|
||||
/// the toggle's anchor arithmetic below is the reason.
|
||||
/// The **bag BAR** — stock `Interface\FrameXML\MainMenuBarBagButtons.xml` — materialized frame
|
||||
/// for frame, and then driven end to end: the bar's own backpack toggle opens the backpack
|
||||
/// window, the fed stack paints in its slot's well, the slot's clicks queue the right intents,
|
||||
/// and the toggle shuts it again. It lives in this file because the bar seats on
|
||||
/// `MainMenuBarArtFrame` (`ActionBarFrame.xml`) — the toggle's anchor arithmetic below is the
|
||||
/// reason.
|
||||
///
|
||||
/// **What decision 1751 changed.** This asserted `report.frames == 259` over a breakdown that
|
||||
/// counted five bag WINDOWS and a keyring window (37 + 4×42 + 42, plus the bar's own handful).
|
||||
/// Those windows are gone from this file: the live ones are the reference's `ContainerFrame1..12`,
|
||||
/// executed off the player's own patch chain, and `BagFrame.xml` is the BAR and nothing else —
|
||||
/// this client's stand-in for the reference's `MainMenuBarBagButtons.xml`. So the count is
|
||||
/// recounted from what the file declares today, and the drive reaches the reference's window
|
||||
/// through the bar's own button rather than showing one of ours by name.
|
||||
/// Those windows are gone: the live ones are the reference's `ContainerFrame1..12`, executed off
|
||||
/// the player's own patch chain, and the bar is the reference's own
|
||||
/// `MainMenuBarBagButtons.xml` (1783). So the count is recounted from what that file declares,
|
||||
/// and the drive reaches the reference's window through the bar's own button rather than showing
|
||||
/// one of ours by name.
|
||||
#[test]
|
||||
fn shipped_bag_frame_drives_end_to_end() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -829,8 +830,7 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
// * StackSplit.xml and MerchantFrame.xml after the bags — the reference's
|
||||
// `ContainerFrameItemButton_OnClick` reads `StackSplitFrame` on both arms and
|
||||
// `MerchantFrame:IsShown()` on the right one, so a slot click raises without them. That
|
||||
// dependency is the reference's, not ours; MerchantFrame.xml is additionally the home of
|
||||
// the `BenillaMoney_*` coin rig this chain's windows call.
|
||||
// dependency is the reference's, not ours.
|
||||
let mut bar_frames = 0;
|
||||
for file in BAG_UI {
|
||||
let frames = load_ui(&s, file);
|
||||
@@ -912,9 +912,9 @@ fn shipped_bag_frame_drives_end_to_end() {
|
||||
// Click the toggle → the backpack window opens and slot 1 paints the jerky. The toggle seats
|
||||
// on the bar's art frame BOTTOMRIGHT +(-6,2), 37×37: art frame BOTTOMRIGHT is the bar's
|
||||
// (full-width, bottom-anchored) corner (1024,0) ⇒ toggle x[981,1018] y[2,39], center
|
||||
// (999.5,20.5). That arithmetic is THIS file's — the button is `BagFrame.xml`'s and its seat
|
||||
// is `ActionBar.xml`'s — so the click stays at literal coordinates rather than going through
|
||||
// `centre_of`: hitting them is part of what is being tested.
|
||||
// (999.5,20.5). That arithmetic is THIS file's — the button is `MainMenuBarBagButtons.xml`'s
|
||||
// and its seat is `ActionBarFrame.xml`'s — so the click stays at literal coordinates rather
|
||||
// than going through `centre_of`: hitting them is part of what is being tested.
|
||||
s.mouse_button(999.0, 20.0, "LeftButton", true);
|
||||
s.mouse_button(999.0, 20.0, "LeftButton", false);
|
||||
s.resolve();
|
||||
|
||||
@@ -937,7 +937,8 @@ fn a_second_bag_window_feeds_and_paints_via_the_bag_bar() {
|
||||
);
|
||||
|
||||
assert!(!open(&s, 1), "hidden by default");
|
||||
// CharacterBag0Slot == bag id 1 (BenillaBagBarSlot_OnLoad(self, 1) in BagFrame.xml).
|
||||
// CharacterBag0Slot == bag id 1: the stock bar's handlers carry no `bagId` field and
|
||||
// recompute `this:GetID() - CharacterBag0Slot:GetID() + 1` (see `:2119` below).
|
||||
s.run("CharacterBag0Slot:Click()").unwrap();
|
||||
let _ = s.take_sounds();
|
||||
assert!(open(&s, 1), "the bag-bar click opened bag 1's window");
|
||||
|
||||
@@ -438,7 +438,7 @@ fn without_a_player_at_addon_load_bagnon_draws_an_empty_window() {
|
||||
}
|
||||
|
||||
/// The **other** fault, pinned on its own so a later regression names itself: the reference's
|
||||
/// `SetItemButton*` family (`assets/ui/ItemButtonTemplate.xml`).
|
||||
/// `SetItemButton*` family (`Interface\FrameXML\ItemButtonTemplate.xml`).
|
||||
///
|
||||
/// Bagnon ends every slot update with `SetItemButtonDesaturated` / `SetItemButtonTexture` /
|
||||
/// `SetItemButtonCount`. While those were nil the raise landed inside `BagnonFrame_AddBag`'s
|
||||
@@ -1133,16 +1133,16 @@ fn a_texture_gradient_tints_the_art_it_sits_on() {
|
||||
/// **The director's report: "when I first open the bags with bagnon the gold numbers are all
|
||||
/// cramped up; if I close and open again it looks good."**
|
||||
///
|
||||
/// Bagnon's money display is `SmallMoneyFrameTemplate` — OUR `MoneyFrame.xml`. Its `ShowCoin` used
|
||||
/// to size each coin with `label:GetStringWidth()`, which is served from the measure round-trip and
|
||||
/// therefore reads **0 in the tick that set the text**: every coin came out at exactly one icon
|
||||
/// width and the digits overlapped. The second open looked right because the first open's measure
|
||||
/// had landed in the cache by then — the reopen was reading the previous open's numbers.
|
||||
/// Bagnon's money display is `SmallMoneyFrameTemplate`, off the chain's own `MoneyFrame.xml`. Its
|
||||
/// `ShowCoin` used to size each coin with `label:GetStringWidth()`, which is served from the
|
||||
/// measure round-trip and therefore reads **0 in the tick that set the text**: every coin came out
|
||||
/// at exactly one icon width and the digits overlapped. The second open looked right because the
|
||||
/// first open's measure had landed in the cache by then — the reopen was reading the previous open's numbers.
|
||||
///
|
||||
/// The fix sums `BENILLA_DIGIT_W`, the app's per-digit advance feed
|
||||
/// ([`benilla_ui::script::UiScript::set_digit_advances`]) — data pushed ahead, so the answer exists
|
||||
/// *in* the tick. That feed's own doc names this frame as what it was built for; only the merchant
|
||||
/// price had ever used it.
|
||||
/// The fix is the engine's font measurer answering inside the Lua call that asked
|
||||
/// (`benilla_ui`'s `script::measure`): `GetStringWidth` returns a real number in the tick that set
|
||||
/// the text. A digits-only stand-in — a per-digit advance feed pushed ahead from the app — shipped
|
||||
/// first and was retired by the general answer (decision 1285).
|
||||
///
|
||||
/// The assertion is the FIRST open, which is the half that was broken.
|
||||
#[test]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The shipped `assets/ui/BasicControls.xml` — the reference's second file, driven the way an
|
||||
//! addon drives it.
|
||||
//! The stock `Interface\FrameXML\BasicControls.xml` — the reference's second file, driven the way
|
||||
//! an addon drives it.
|
||||
//!
|
||||
//! Nothing benilla ships calls `message`, `TEXT` or `_ERRORMESSAGE`; their only consumers are
|
||||
//! third-party addons (26 call `TEXT`, ~10 genuinely call `message`, two replace `_ERRORMESSAGE`).
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! The player buff bar (`assets/ui/BuffFrame.xml`, decisions 0255/0257) against its reference
|
||||
//! behaviour. The XML/Lua is the unit under test; the app-side feed (`crate::ui_aura`) is stubbed by
|
||||
//! pushing an [`AuraState`] list straight through [`UiScript::set_auras`] and firing
|
||||
//! The player buff bar (stock `Interface\FrameXML\BuffFrame.xml`, decisions 0255/0257) against its
|
||||
//! reference behaviour. The XML/Lua is the unit under test; the app-side feed (`crate::ui_aura`) is
|
||||
//! stubbed by pushing an [`AuraState`] list straight through [`UiScript::set_auras`] and firing
|
||||
//! `PLAYER_AURAS_CHANGED`, so these exercise the *button* handlers — the row/filter wiring, the
|
||||
//! dispel-tinted border, the stack count, the countdown, the warning flash, and the right-click
|
||||
//! cancel — the way the reference's own `BuffButton_*` do.
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
//! Shipped end-to-end tests for the chat window + input (decision 0084, the chat arc): the real
|
||||
//! `assets/ui/ChatFrame.xml` loaded through the engine loader, driven exactly as the app drives it —
|
||||
//! `add_chat_message` → `resolve`/`extract` (lines render in the pinned colors), the wheel → the Lua
|
||||
//! `OnMouseWheel` handler → `ScrollUp` (which freezes the fade), and the input EditBox's ENTER →
|
||||
//! `OnEnterPressed` → `SubmitChatInput` → `take_chat_input` (the app then parses it — see
|
||||
//! `crate::ui_chat`'s `parse_line` tests for the `/say`//`/yell`//`/w` mapping).
|
||||
//! `Interface\FrameXML\ChatFrame.xml` loaded through the engine loader, driven exactly as the app
|
||||
//! drives it — `add_chat_message` → `resolve`/`extract` (lines render in the pinned colors), the
|
||||
//! wheel → the Lua `OnMouseWheel` handler → `ScrollUp` (which freezes the fade), and the input
|
||||
//! EditBox's ENTER → `OnEnterPressed` → `SubmitChatInput` → `take_chat_input` (the app then parses
|
||||
//! it — see `crate::ui_chat`'s `parse_line` tests for the `/say`//`/yell`//`/w` mapping).
|
||||
|
||||
use benilla_ui::script::{ExtractedQuad, QuadContent, UiScript};
|
||||
|
||||
@@ -378,11 +378,11 @@ fn chat_click_dismisses_a_stuck_spell_but_not_an_item() {
|
||||
|
||||
// ── ChatTypeInfo: the addon-facing color table ────────────────────────────────────────────────
|
||||
|
||||
/// `ChatTypeInfo` carries the shipped default chat colors twice: once in `assets/ui/ChatFrame.xml`
|
||||
/// for addons to read, and once in [`crate::ui_chat::default_color`] for our own feed to render.
|
||||
/// Both are the same wow-re byte table (`chat-color-table.md`, the static registry at
|
||||
/// `.rdata 0x804710`) — so this is the gate that makes the duplication safe: every kind we model
|
||||
/// must agree to the byte, and the table's shape (`sticky`/`id`) must be the reference's.
|
||||
/// `ChatTypeInfo` carries the shipped default chat colors twice: once in
|
||||
/// `Interface\FrameXML\ChatFrame.xml` for addons to read, and once in
|
||||
/// [`crate::ui_chat::default_color`] for our own feed to render. Both are the same wow-re byte
|
||||
/// table (`chat-color-table.md`, the static registry at `.rdata 0x804710`) — so this is the gate
|
||||
/// that makes the duplication safe: every kind we model must agree to the byte, and the table's shape (`sticky`/`id`) must be the reference's.
|
||||
#[test]
|
||||
fn chat_type_info_matches_the_host_color_table() {
|
||||
use crate::ui_chat::{default_color, ChatEventKind as K};
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The shipped `assets/ui/ColorPickerFrame.xml` + the dropdown's colour-swatch row, driven the way
|
||||
//! the corpus drives them.
|
||||
//! The stock `Interface\FrameXML\ColorPickerFrame.xml` + the dropdown's colour-swatch row, driven
|
||||
//! the way the corpus drives them.
|
||||
//!
|
||||
//! Nothing benilla ships opens this window. Its consumers are third-party addons — 86 of the 218
|
||||
//! reach `ColorPickerFrame`, and 64 of those files are copies of `Dewdrop-2.0.lua`, THE Ace2 menu
|
||||
|
||||
@@ -85,8 +85,9 @@ fn bag_setup() -> UiScript {
|
||||
for file in BAG_UI {
|
||||
load_xml(&s, file);
|
||||
}
|
||||
// `ContainerFrame_Update` reads `MerchantFrame:IsShown()` on any slot the tooltip owns, and
|
||||
// `BankFrame` (BAG_UI's last entry) fills its purse through `BenillaMoney_Set`.
|
||||
// `ContainerFrame_Update` reads `MerchantFrame:IsShown()` on any slot the tooltip owns.
|
||||
// `BankFrame`'s purse is an ordinary `SmallMoneyFrameTemplate` off the chain's own
|
||||
// `MoneyFrame.xml` (1937/1962), so it is not a merchant dependency.
|
||||
load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + the placeholder icon
|
||||
load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The shipped errors/info frame (`assets/ui/ErrorsFrame.xml` — the ref UIErrorsFrame, a real
|
||||
//! `<MessageFrame>`) driven engine-only: the yellow `UI_INFO_MESSAGE` toast (the quest
|
||||
//! The errors/info frame — stock `Interface\FrameXML\UIErrorsFrame.xml`, a real
|
||||
//! `<MessageFrame>` — driven engine-only: the yellow `UI_INFO_MESSAGE` toast (the quest
|
||||
//! objective-progress popup's surface), the red `UI_ERROR_MESSAGE` line, insertMode-TOP stacking,
|
||||
//! and the hold+fade expiry.
|
||||
//!
|
||||
@@ -124,7 +124,7 @@ fn an_error_toast_draws_over_an_open_panel_window() {
|
||||
load_xml(&s, "Interface\\FrameXML\\Cooldown.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + the placeholder icon
|
||||
load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // BenillaMoney_* — QuestLogDetail's reward money row
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // QuestLogDetail's reward money row (1928)
|
||||
// pane's UIPanelScrollFrameTemplate). A MISSING template is a loader *warning*, so an
|
||||
// under-loaded list passes and then dies on the first FauxScrollFrame_Update.
|
||||
load_xml(&s, "Interface\\FrameXML\\BasicControls.xml");
|
||||
|
||||
@@ -79,7 +79,9 @@ fn escape_closes_bag_and_panel_releases_loot_and_clears_cursor() {
|
||||
load_xml(&s, "Interface\\FrameXML\\LootFrame.xml");
|
||||
load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + the placeholder icon
|
||||
load_xml(&s, "Interface\\FrameXML\\CharacterFrameTemplates.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // BenillaMoney_Set, BankFrame's purse helper
|
||||
// The reference's own `ContainerFrameItemButton_OnClick` reads `MerchantFrame:IsShown()`
|
||||
// on the bag-slot click below; the coin rig this line used to name is gone (1937/1962).
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml");
|
||||
s.set_money(0);
|
||||
s.set_container(0, Some(one_item_backpack()));
|
||||
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
//! answers the same whichever way it came.
|
||||
//!
|
||||
//! **Divergences are an explicit list with a reason each, never a pattern.** [`KNOWN`] carries
|
||||
//! them, in both directions; a new one cannot hide inside a tolerance. The list is where the
|
||||
//! seven frames the reference makes interactive through *handlers we do not carry* are recorded —
|
||||
//! those want the handler (and its tooltip), never a bare `enableMouse="true"` that would swallow
|
||||
//! the click and give nothing back.
|
||||
//! them, in both directions; a new one cannot hide inside a tolerance. **It is empty today** —
|
||||
//! 1751's window migrations, 1795, 1970 and 1980 each retired a group, and the in-body notes are
|
||||
//! the record of what each one was. The array stays because an empty list is the thing that
|
||||
//! notices a divergence arriving.
|
||||
//!
|
||||
//! ## Four flags, and only four
|
||||
//!
|
||||
@@ -68,8 +68,8 @@
|
||||
//!
|
||||
//! The reference side is **XML only**: a `SetID` or `EnableMouse` the reference makes from Lua at
|
||||
//! `OnLoad` is invisible here and reads as absent. That blind spot only ever under-reports — it
|
||||
//! can hide a divergence, never invent one — so nothing it misses turns into a false failure; the
|
||||
//! handful of frames it does hide are in `KNOWN` with that reason.
|
||||
//! can hide a divergence, never invent one — so nothing it misses turns into a false failure. It
|
||||
//! would be a `KNOWN` entry's reason if any frame still needed one.
|
||||
//!
|
||||
//! The whole module skips cleanly with no install — `_extracted_framexml/` is a gitignored
|
||||
//! Blizzard asset, like every other client-data test here.
|
||||
@@ -157,16 +157,18 @@ impl std::fmt::Display for Flag {
|
||||
}
|
||||
}
|
||||
|
||||
/// The accepted differences, in four groups: **seven frames the reference makes mouse-interactive
|
||||
/// through HANDLERS we do not carry**, the merchant rows whose mouse we take where it does not,
|
||||
/// the faux scroll panes we build from a different widget kind, and the ids the reference sets
|
||||
/// from Lua. Every entry is a judgement someone made; none of them is a tolerance.
|
||||
/// The accepted differences — **none, today**. The four groups this list once carried (the frames
|
||||
/// the reference made mouse-interactive through handlers we did not carry, the merchant rows, the
|
||||
/// faux scroll panes, the Lua-set ids) each retired with the window that owned them; the in-body
|
||||
/// notes below name the record for each. An empty array is not an oversight: it is what fails the
|
||||
/// gate the moment a new divergence appears, and every entry it ever holds is a judgement someone
|
||||
/// made, never a tolerance.
|
||||
const KNOWN: &[Known] = &[
|
||||
// ── The reference has it, we do not: all seven are handler gaps, not flag gaps ─────────────
|
||||
// ── The reference had it and we did not: seven handler gaps, not flag gaps (all retired) ───
|
||||
//
|
||||
// In each of these the reference's mouse comes from an `<OnEnter>`/`<OnLeave>` pair whose body
|
||||
// we have not built, and the interaction the player would notice is the TOOLTIP those handlers
|
||||
// show — not the click-blocking the flag gives. Declaring `enableMouse="true"` here would make
|
||||
// In each of these the reference's mouse came from an `<OnEnter>`/`<OnLeave>` pair whose body
|
||||
// we had not built, and the interaction the player would notice was the TOOLTIP those handlers
|
||||
// show — not the click-blocking the flag gives. Declaring `enableMouse="true"` would have made
|
||||
// the frame swallow the click and hand back nothing, which is worse than the gap.
|
||||
// `PetPaperDollFrameExpBar` RETIRED here (decision 1751's character-sheet window). It read
|
||||
// "the reference bar inherits TextStatusBar, whose OnEnter/OnLeave show the value text; ours is
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The group-loot roll popups (decision 0591, `assets/ui/GroupLootFrame.xml`): the four stacked
|
||||
//! `GroupLootFrame`s that answer `START_LOOT_ROLL`/`CANCEL_LOOT_ROLL` off a pushed
|
||||
//! The group-loot roll popups (decision 0591, stock `Interface\FrameXML\GroupLootFrame.xml`): the
|
||||
//! four stacked `GroupLootFrame`s that answer `START_LOOT_ROLL`/`CANCEL_LOOT_ROLL` off a pushed
|
||||
//! [`LootRollsState`] snapshot (the `loot_roll.rs` seam's own harness idiom, mirrored here the way
|
||||
//! `loot_tests.rs` mirrors it for `set_loot`/`LootState`).
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The shipped **inspect window** driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/InspectFrame.xml` loaded behind `Fonts.xml`/`UiPanels.xml`/`GameTooltip.xml` and fed a
|
||||
//! synthetic target snapshot + a foreign equipment view (decision 0631). `character_tests.rs`'s
|
||||
//! harness, turned onto the other paper doll.
|
||||
//! The reference's own `Blizzard_InspectUI` addon loaded behind
|
||||
//! `Fonts.xml`/`UIParent.xml`/`GameTooltip.xml` and fed a synthetic target snapshot + a foreign
|
||||
//! equipment view (decision 0631). `character_tests.rs`'s harness, turned onto the other paper doll.
|
||||
//!
|
||||
//! What these are here to falsify, in order of how quietly it could have shipped broken:
|
||||
//!
|
||||
|
||||
@@ -595,11 +595,11 @@ fn the_loot_window_draws_over_the_party_frames() {
|
||||
/// previews the row's item in the dressing room (decision 1060), SHIFT posts its link into an open
|
||||
/// chat edit box (decision 1059) — and **neither loots**.
|
||||
///
|
||||
/// That last clause is the whole point of the test, and it is ours to get right rather than the
|
||||
/// reference's: in the real client the loot itself is the C `LootButton` widget's click behaviour
|
||||
/// (l.94's `button:SetSlot(slot)`), so its Lua arms fall through harmlessly; ours owns the
|
||||
/// `LootSlot` call, so the arms had to grow a `return` the reference does not have. The unmodified
|
||||
/// click still loots — the regression that would otherwise ship silently.
|
||||
/// That last clause is the whole point of the test, and it is the WIDGET's to get right: the loot
|
||||
/// itself is the `LootButton` kind's click behaviour (`benilla-ui` `script/button.rs`, decision
|
||||
/// 1799 — `l.94`'s `button:SetSlot(slot)` is what arms it), gated on no shift/ctrl/alt, which is
|
||||
/// why the stock `LootFrameItem_OnClick` never calls a take itself and needs no `return`. The
|
||||
/// unmodified click still loots — the regression that would otherwise ship silently.
|
||||
#[test]
|
||||
fn ctrl_and_shift_on_a_loot_row_preview_and_post_without_looting() {
|
||||
const WOOL_LINK: &str = "|cffffffff|Hitem:2589:0:0:0|h[Wool Cloth]|h|r";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The shipped `assets/ui/MacroFrame.xml` — the macro editor + its name/icon popup (decision
|
||||
//! The reference's own `Blizzard_MacroUI` addon — the macro editor + its name/icon popup (decision
|
||||
//! 0983), driven against the engine's own macro table.
|
||||
//!
|
||||
//! What these guard, end to end through the real file: the window loads clean in its real
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! The shipped **merchant window** driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/MerchantFrame.xml` (+ `GameTooltip.xml` for the hover chain) loaded behind
|
||||
//! `UiPanels.xml` and fed synthetic stock — the phase-4 vendor arc's machine checks (decision
|
||||
//! The **merchant window** driven end-to-end, engine-only (no Bevy): the stock
|
||||
//! `Interface\FrameXML\MerchantFrame.xml` (+ `GameTooltip.xml` for the hover chain) loaded behind
|
||||
//! `UIParent.xml` and fed synthetic stock — the phase-4 vendor arc's machine checks (decision
|
||||
//! 0081/0084). Split from `panel_tests` (which keeps gossip + the slot manager itself) along the
|
||||
//! folder's one-file-per-window convention.
|
||||
|
||||
@@ -40,7 +40,7 @@ fn frame_rect(quads: &[ExtractedQuad], w: f32, h: f32) -> benilla_ui::layout::Re
|
||||
.unwrap_or_else(|| panic!("no bare-frame quad sized {w}x{h}"))
|
||||
}
|
||||
|
||||
/// Load the real `assets/ui/MerchantFrame.xml` (the shipped vendor window) behind `UiPanels.xml`
|
||||
/// Load the stock `Interface\FrameXML\MerchantFrame.xml` behind `UIParent.xml`
|
||||
/// into a bare engine and drive it with a synthetic 2-item stock + a purse — the whole phase-4
|
||||
/// chain minus Bevy (decision 0081), now over the UIPanel slot manager (decision 0084): the
|
||||
/// hidden→shown lifecycle on MERCHANT_SHOW goes through ShowUIPanel (landing at the left slot),
|
||||
@@ -1092,7 +1092,7 @@ fn trade_recipient_money_renders_the_digit_not_ellipsis() {
|
||||
load_xml(&s, f);
|
||||
}
|
||||
load_xml(&s, "ScrollTemplates.xml"); // our scroll kit + the placeholder icon
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // the BenillaMoney_* helpers
|
||||
load_xml(&s, "Interface\\FrameXML\\MerchantFrame.xml"); // MerchantFrame:IsShown(), read on the slot click
|
||||
// TradeFrame's money entry is the chain's own since 1882 — its OnLoad calls MoneyInputFrame_*.
|
||||
load_xml(&s, "Interface\\FrameXML\\MoneyInputFrame.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\MoneyInputFrame.xml");
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
//! The shipped `assets/ui/MoneyFrame.xml` — the reference's `MoneyFrameTemplate` /
|
||||
//! The stock `Interface\FrameXML\MoneyFrame.xml` — the reference's `MoneyFrameTemplate` /
|
||||
//! `SmallMoneyFrameTemplate` kit and the `MoneyTypeInfo` table behind it (decision 1190: a name the
|
||||
//! shipped 1.12 UI defines is a name we publish too — under our own implementation of it, 1260).
|
||||
//!
|
||||
//! Nothing benilla ships consumes this file yet — our own eight coin displays run on
|
||||
//! MerchantFrame.xml's `BenillaMoney_*` slot kit, and MoneyFrame.xml's header is where the two are
|
||||
//! reconciled — so these tests ARE its only driver. That makes them the contract: they exercise it
|
||||
//! the way an addon does (declare a frame on the template, point it at a type, change the type,
|
||||
//! read the coins back), because that is who calls it.
|
||||
//! Eight shipped windows consume this file (1937 converged the coin cluster onto it; 1962 put the
|
||||
//! file itself on the chain). These tests exercise it the way an addon does — declare a frame on
|
||||
//! the template, point it at a type, change the type, read the coins back — because that is who
|
||||
//! calls it.
|
||||
//!
|
||||
//! What they guard: the type switch actually rewiring where the number comes from and whether the
|
||||
//! coins take the mouse; the collapse rule per type (`collapse`, `showSmallerCoins`, `fixedWidth`);
|
||||
@@ -203,8 +202,9 @@ fn update_paints_a_static_frame_by_name_and_resizes_it() {
|
||||
//
|
||||
// **The digits used to contribute 0 here and this test asserted the bare 13s that came of it.**
|
||||
// That was the director's cramped gold on a first open: `ShowCoin` sized each coin from a text
|
||||
// measure that lands a frame later, so it read 0 every first time. It sums the engine's
|
||||
// `BenillaNumberWidth` feed now, which answers in the same tick.
|
||||
// measure that lands a frame later, so it read 0 every first time. The engine's font
|
||||
// measurer answers inside the Lua call that asked now (`script/measure.rs`), so the width
|
||||
// is real on the first pass.
|
||||
assert_eq!(
|
||||
s.eval::<f64>("return TestPurseGoldButton:GetWidth()")
|
||||
.unwrap(),
|
||||
@@ -323,8 +323,8 @@ fn set_money_frame_color_recolours_the_digits_and_not_the_icons() {
|
||||
}
|
||||
|
||||
/// `MoneyTypeInfo` is read directly by addons, so its shape is part of the contract — all seven
|
||||
/// reference types, each with an `UpdateFunc`, and the two we cannot source yet answering 0 rather
|
||||
/// than erroring (the header's `GetSendMailMoney`/`GetSendMailCOD` gap).
|
||||
/// reference types, each with an `UpdateFunc`, and the two send-mail rows reading the amounts
|
||||
/// their engine getters hold (`GetSendMailMoney`/`GetSendMailCOD`, built by 1962).
|
||||
#[test]
|
||||
fn the_money_type_table_carries_all_seven_reference_types() {
|
||||
let s = harness(12_345);
|
||||
@@ -345,7 +345,7 @@ fn the_money_type_table_carries_all_seven_reference_types() {
|
||||
"MoneyTypeInfo[\"{t}\"] with an UpdateFunc"
|
||||
);
|
||||
}
|
||||
// The two whose engine getters we have not built answer 0 instead of nil-calling.
|
||||
// The two send-mail rows: with no send-mail amount set, their getters answer 0.
|
||||
for t in ["SEND_MAIL", "SEND_MAIL_COD"] {
|
||||
s.run(&format!(
|
||||
"this = TestPurse MoneyFrame_SetType(\"{t}\") this = nil"
|
||||
@@ -359,7 +359,7 @@ fn the_money_type_table_carries_all_seven_reference_types() {
|
||||
assert_eq!(
|
||||
s.eval::<i64>("return TestPurse.staticMoney").unwrap(),
|
||||
0,
|
||||
"{t} reads 0 while its engine getter is missing"
|
||||
"{t} reads its engine getter, which is 0 with no send-mail amount set"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -724,14 +724,15 @@ fn load_definers(s: &UiScript, files: &[&str]) {
|
||||
|
||||
/// The Interface page's harness (decision 1136), the same posture as `combat_harness` above: the
|
||||
/// **real** definers ahead of the window, in the manifest's own order, so each row captures the
|
||||
/// same file-scope value it captures in the client. `SHOW_NEWBIE_TIPS` rides in on `GameTooltip.xml`
|
||||
/// which `harness_on` already loads; the two quest globals need their own windows —
|
||||
/// `MerchantFrame.xml` for the coin helpers both quest files reuse and `ScrollTemplates.xml` for
|
||||
/// the kit `QuestFrame.xml` inherits from (the same chain `quest_tests`/`questlog_tests` load).
|
||||
/// `SHOW_BUFF_DURATIONS` arrives with the bar it re-anchors (1139), behind the two files
|
||||
/// `buff_tests` loads ahead of it — `Cooldown.xml` (every button's child) and `ActionBar.xml`
|
||||
/// (`BENILLA_FALLBACK_ICON`), themselves behind `UIParent.xml`. `TextStatusBar.xml` rides in
|
||||
/// ahead of them for the Status Bar Text row's consumer — the XP bar's numerals (1140).
|
||||
/// same file-scope value it captures in the client. `SHOW_NEWBIE_TIPS` is set by our own
|
||||
/// `assets/ui/OptionsFrame.xml` (it lived in our `GameTooltip.xml` until the tooltip went stock,
|
||||
/// 1968; the reference keeps it in `UIOptionsFrame.lua`), which `harness_on` already loads; the two
|
||||
/// quest globals need their own windows — `MerchantFrame.xml` for the coin helpers both quest files
|
||||
/// reuse and `ScrollTemplates.xml` for the kit `QuestFrame.xml` inherits from (the same chain
|
||||
/// `quest_tests`/`questlog_tests` load). `SHOW_BUFF_DURATIONS` arrives with the bar it re-anchors
|
||||
/// (1139), behind the two files `buff_tests` loads ahead of it — `Cooldown.xml` (every button's
|
||||
/// child) and `ActionBar.xml` (`BENILLA_FALLBACK_ICON`), themselves behind `UIParent.xml`.
|
||||
/// `TextStatusBar.xml` rides in ahead of them for the Status Bar Text row's consumer — the XP bar's numerals (1140).
|
||||
fn interface_harness() -> UiScript {
|
||||
let mut s = audio_harness();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
@@ -1847,8 +1848,9 @@ fn defaults_resets_the_controls_page_to_registered_defaults() {
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
}
|
||||
|
||||
/// Drive the window a few frames so the frame-late fits (the scroll body, the tab widths)
|
||||
/// converge: rects resolve after Lua runs, so OptionsScroll_Fit answers one frame behind.
|
||||
/// Drive the window a few frames so the scroll body's frame-late fit converges: rects resolve
|
||||
/// after Lua runs, so `OptionsScroll_Fit` answers one frame behind. (The tab widths are not in
|
||||
/// that set — 2028 put them on a one-shot `<OnShow>OptionsTab_FitWidth`.)
|
||||
fn settle(s: &mut UiScript) {
|
||||
for _ in 0..4 {
|
||||
s.resolve();
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The shipped `assets/ui/UIPanelTemplates.xml` + `assets/ui/OptionsFrameTemplates.xml` — the
|
||||
//! reference's SHARED widget kit, driven the way an addon drives it.
|
||||
//! The stock `Interface\FrameXML\UIPanelTemplates.xml` + our `assets/ui/OptionsFrameTemplates.xml`
|
||||
//! — the reference's SHARED widget kit, driven the way an addon drives it.
|
||||
//!
|
||||
//! **These are not tests of a window.** Nothing benilla ships instantiates a single template in
|
||||
//! either file: their only consumer is a third-party addon writing
|
||||
@@ -238,7 +238,7 @@ fn a_panel_button_from_the_template_labels_and_paints() {
|
||||
|
||||
/// `UIPanelCloseButton` — 8 call sites across 4 corpus addons, and the one template here whose
|
||||
/// whole point is its script: `HideUIPanel(this:GetParent())`, resolved at click time against
|
||||
/// `UiPanels.xml`.
|
||||
/// the chain's `UIParent.xml`.
|
||||
#[test]
|
||||
fn the_templated_close_button_hides_the_frame_it_sits_on() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
@@ -438,7 +438,7 @@ fn the_options_check_button_resolves_its_whole_inheritance_chain() {
|
||||
/// real 1.12.1 client has, read from `reference/1.12-globals.tsv`.
|
||||
///
|
||||
/// Scoped to these two files on purpose: the rest of `assets/ui` is full of deliberately
|
||||
/// benilla-shaped template names (`BenillaScrollBarTemplate`, `BenillaMacroPanelButtonTemplate`,
|
||||
/// benilla-shaped template names (`BenillaScrollBarTemplate`, `BenillaScriptLogRowTemplate`,
|
||||
/// `OptionsRedButtonTemplate`), and a whole-tree sweep would be asserting something else. These
|
||||
/// two files make the opposite claim — *these are the reference's own names, which is why an addon
|
||||
/// can find them* — so that claim is the one worth gating. A template renamed to something
|
||||
|
||||
@@ -4,9 +4,9 @@ use benilla_ui::script::{
|
||||
};
|
||||
|
||||
/// Load one manifest entry into `s`, panicking on any loader error and returning the frame count
|
||||
/// it materialized — the panel tests all load `UiPanels.xml` (decision 0084 §2's slot manager)
|
||||
/// before the panel frame(s) under test, exactly as `benilla.toc`'s own order does, so
|
||||
/// `ShowUIPanel`/`HideUIPanel` already exist when a frame's OnLoad/OnEvent references them.
|
||||
/// it materialized — the panel tests all load `Interface\FrameXML\UIParent.xml` (decision 0084 §2's
|
||||
/// slot manager) before the panel frame(s) under test, exactly as `benilla.toc`'s own order does,
|
||||
/// so `ShowUIPanel`/`HideUIPanel` already exist when a frame's OnLoad/OnEvent references them.
|
||||
///
|
||||
/// This was a private disk-only reader until 1751's second window: `BankFrame.xml` is the
|
||||
/// reference's own file off the player's chain now, and a reader that only knows `assets/ui`
|
||||
@@ -30,7 +30,7 @@ fn frame_rect(quads: &[ExtractedQuad], w: f32, h: f32) -> benilla_ui::layout::Re
|
||||
.unwrap_or_else(|| panic!("no bare-frame quad sized {w}x{h}"))
|
||||
}
|
||||
|
||||
/// Load the real `assets/ui/GossipFrame.xml` (the shipped gossip window) behind `UiPanels.xml`
|
||||
/// Load the stock `Interface\FrameXML\GossipFrame.xml` behind `UIParent.xml`
|
||||
/// into a bare engine and drive it with a synthetic gossip menu — the whole phase-3 chain minus
|
||||
/// Bevy (decision 0081), now over the UIPanel slot manager (decision 0084): the hidden→shown
|
||||
/// lifecycle on GOSSIP_SHOW goes through ShowUIPanel (landing the window at the left slot,
|
||||
@@ -738,7 +738,7 @@ fn displacing_an_npc_window_ends_the_displaced_session() {
|
||||
}
|
||||
|
||||
/// Pin §4's pushable path: a higher-`pushable` occupant (loot's future pushable=7 row, already
|
||||
/// registered in `UiPanels.xml`) gets pushed to the center slot rather than replaced when a
|
||||
/// registered in `UIParent.xml`) gets pushed to the center slot rather than replaced when a
|
||||
/// pushable=0 frame (merchant) wants the left spot (UIParent.lua l.734-741) — the synthetic
|
||||
/// registrant the pin calls for, since no loot window ships yet. A bare `CreateFrame` with a
|
||||
/// distinctive 50×50 size stands in for it: it has no visual layers, but `extract` still emits
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The shipped `assets/ui/ActionBar.xml`'s performance ("ping") meter — the tinted bar in the
|
||||
//! main bar's last empty recess — over the real files, never a stub.
|
||||
//! The stock `Interface\FrameXML\ActionBarFrame.xml`'s performance ("ping") meter — the tinted bar
|
||||
//! in the main bar's last empty recess — over the real files, never a stub.
|
||||
//!
|
||||
//! What these guard, in order: the ref geometry that puts the bar IN the recess (the whole point of
|
||||
//! the slice); the LOW-strata draw order that makes it show *through* the bar art instead of over
|
||||
|
||||
@@ -398,8 +398,10 @@ fn the_level_line_names_the_family_and_is_untouched_without_one() {
|
||||
// level line at all", because our deleted `assets/ui/PetPaperDollFrame.xml` declared
|
||||
// `PetLevelText` with no `text=` and it started empty. The reference declares it
|
||||
// `text="Level level race class"` (stock `PetPaperDollFrame.xml:70`) — a design-time
|
||||
// placeholder, and not a GlobalStrings key, so the loader's `text=` lookup falls through to the
|
||||
// literal and that literal is what a player sees in this state, un-replaced. Both spellings say
|
||||
// placeholder, and not a GlobalStrings key. **A real 1.12 client draws that literal too**:
|
||||
// `CSimpleFontString::LoadXML` substitutes the raw attribute when `FrameScript_GetText` comes
|
||||
// back empty (`771029`-`771032 mov eax,esi`), exactly as `Button::LoadXML 0x778c31` does — so
|
||||
// this is fidelity, not the loader divergence this comment used to call it. Both spellings say
|
||||
// the same thing about the guard; this one says it about the file that ships.
|
||||
let drawn = with_family(None);
|
||||
assert!(
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The shipped **questgiver window** open/close sound, driven engine-only (no Bevy): the real
|
||||
//! `assets/ui/QuestFrame.xml` loaded behind `UiPanels.xml` and shown/hidden through the wire events.
|
||||
//! The window-sound convention's machine check for the quest arc (decision 0090), the sibling of the
|
||||
//! merchant/gossip/bag/loot sound tests.
|
||||
//! `Interface\FrameXML\QuestFrame.xml` loaded behind `UIParent.xml` and shown/hidden through the
|
||||
//! wire events. The window-sound convention's machine check for the quest arc (decision 0090), the
|
||||
//! sibling of the merchant/gossip/bag/loot sound tests.
|
||||
|
||||
use benilla_ui::script::{QuestPanel, QuestState, ScriptValue, SoundRequest, UiScript};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The shipped **quest log window** driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/QuestLogFrame.xml` loaded behind `UiPanels.xml`/`MerchantFrame.xml` (the money helpers
|
||||
//! its reward rows reuse) and fed a synthetic 8-entry log + a resolved detail — mirroring
|
||||
//! `quest_tests.rs`/`bag_tests.rs`'s engine-only harness for the quest-log slice (decision 0088 arc).
|
||||
//! `Interface\FrameXML\QuestLogFrame.xml` loaded behind `UIParent.xml`/`MerchantFrame.xml` (the
|
||||
//! money helpers its reward rows reuse) and fed a synthetic 8-entry log + a resolved detail —
|
||||
//! mirroring `quest_tests.rs`/`bag_tests.rs`'s engine-only harness for the quest-log slice (decision 0088 arc).
|
||||
|
||||
use benilla_ui::script::{
|
||||
ExtractedQuad, PartyMemberInfo, PartyState, QuadContent, QuestItemView, QuestLogDetail,
|
||||
@@ -843,15 +843,19 @@ fn empty_quest_log_hides_rows_and_disables_abandon() {
|
||||
s.run("ToggleQuestLog()").unwrap();
|
||||
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
||||
|
||||
// A bare FontString region has no Show/Hide/IsVisible in this engine (only Frames/Buttons do —
|
||||
// `region.rs`'s method table) — the empty-state message is a toggled SetText, read back here.
|
||||
// The empty log says the reference's QUESTLOG_NO_QUESTS_TEXT (GlobalStrings.lua:3225) through
|
||||
// stock QuestLogNoQuestsText; "Your quest log is empty." was ours (1944).
|
||||
// stock QuestLogNoQuestsText; "Your quest log is empty." was ours (1944). Regions carry the
|
||||
// real `Show`/`Hide`/`IsShown`/`IsVisible` (0138), so the label's visibility is assertable
|
||||
// too — and nothing ever hides this one: `QuestLogFrame.xml:427` is its only mention in the
|
||||
// whole chain, so it stands behind the rows whether or not the log is empty.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogNoQuestsText:GetText()")
|
||||
.unwrap(),
|
||||
"No Active Quests"
|
||||
);
|
||||
assert!(s
|
||||
.eval::<bool>("return QuestLogNoQuestsText:IsVisible()")
|
||||
.unwrap());
|
||||
assert!(!s.eval::<bool>("return QuestLogTitle1:IsVisible()").unwrap());
|
||||
assert!(!s
|
||||
.eval::<bool>("return QuestLogFrameAbandonButton:IsEnabled() ~= 0")
|
||||
@@ -968,14 +972,28 @@ fn reward_rows_follow_the_refs_two_per_row_layout() {
|
||||
|
||||
// With choices present, the receive text reads "...also..." (QuestFrame.lua:461-462) — it
|
||||
// anchors under choice row 1 (the left/odd column: index=2 is even -> anchorIndex-1 -> 1, this
|
||||
// function's own logic). GetPoint() isn't wired for FontString regions in this engine (only
|
||||
// Frame/Button — `region.rs` has no GetPoint), so the anchor CHAIN itself is verified via the
|
||||
// mandatory capture-loop screenshot (this task's report), not introspected here.
|
||||
// function's own logic). Regions carry `GetPoint` (1244), so the anchor CHAIN is introspected
|
||||
// here rather than deferred to a screenshot: TOPLEFT of QuestLogItem1's BOTTOMLEFT, +3/-5.
|
||||
assert_eq!(
|
||||
s.eval::<String>("return QuestLogItemReceiveText:GetText()")
|
||||
.unwrap(),
|
||||
"You will also receive:"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<String>(
|
||||
"local p, rel, rp = QuestLogItemReceiveText:GetPoint() \
|
||||
return p .. '|' .. rel:GetName() .. '|' .. rp"
|
||||
)
|
||||
.unwrap(),
|
||||
"TOPLEFT|QuestLogItem1|BOTTOMLEFT"
|
||||
);
|
||||
assert_eq!(
|
||||
s.eval::<(f64, f64)>(
|
||||
"local _, _, _, x, y = QuestLogItemReceiveText:GetPoint() return x, y"
|
||||
)
|
||||
.unwrap(),
|
||||
(3.0, -5.0)
|
||||
);
|
||||
|
||||
// One fixed reward shows, chained under the receive text (its own SetPoint target).
|
||||
assert!(s.eval::<bool>("return QuestLogItem3:IsShown()").unwrap());
|
||||
|
||||
@@ -648,9 +648,10 @@ mod tests {
|
||||
/// reasoning that widget methods are not in `_G`. True, and it meant the report could not see
|
||||
/// a widget method we had *not built*. `MerchantFrame.xml` read `0 engine` and
|
||||
/// [`chain_readiness_report`] read CLEAN while the stock row's `<OnEnter>` called
|
||||
/// `ShoppingTooltip1:SetMerchantCompareItem(...)`, which this engine does not have — so the
|
||||
/// file loaded, every check passed, and hovering a vendor row would have raised in play. Both
|
||||
/// instruments were right about what they measure. Neither measured the window.
|
||||
/// `ShoppingTooltip1:SetMerchantCompareItem(...)`, which this engine did not have then (1802
|
||||
/// built it) — so the file loaded, every check passed, and hovering a vendor row would have
|
||||
/// raised in play. Both instruments were right about what they measure. Neither measured the
|
||||
/// window.
|
||||
///
|
||||
/// **A remaining `<?>` in the `fx=` column is usually a LoadOnDemand addon**, not something to
|
||||
/// build. `ClassTrainerFrame_Show`, `CraftFrame_Show`, `MacroFrame_SaveMacro`,
|
||||
@@ -1881,8 +1882,9 @@ mod tests {
|
||||
/// read by the next session as if it were live.
|
||||
///
|
||||
/// That is not a tidiness problem. Our copies DIVERGE from the reference deliberately, and the
|
||||
/// divergence is what dies: `UiPanels.xml`'s `PanelTemplates_TabResize` carried a benilla-only
|
||||
/// `return tabWidth` that the tab settle reads, and the reference's returns nothing.
|
||||
/// divergence is what dies: our retired `UiPanels.xml` carried a `PanelTemplates_TabResize`
|
||||
/// with a benilla-only `return tabWidth` that our tab settle read, and the reference's
|
||||
/// returns nothing (both are gone — 1988, 1993).
|
||||
///
|
||||
/// The reverse direction — ours seated BELOW the chain's, so we silently override the
|
||||
/// reference — is a real category too, and a wider audit than this gate.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! The shipped **Reputation tab** (`assets/ui/ReputationFrame.xml`) and the **reputation watch
|
||||
//! bar** (`assets/ui/ActionBar.xml`) driven end-to-end, engine-only (no Bevy) — the per-window test
|
||||
//! module the skills/spellbook/bank files already establish.
|
||||
//! The stock **Reputation tab** (`Interface\FrameXML\ReputationFrame.xml`) and the **reputation
|
||||
//! watch bar** (`Interface\FrameXML\ActionBarFrame.xml`) driven end-to-end, engine-only (no Bevy) —
|
||||
//! the per-window test module the skills/spellbook/bank files already establish.
|
||||
//!
|
||||
//! What it pins is the PAINT law, which is the half `benilla-ui`'s own
|
||||
//! `script::reputation::tests` structurally cannot reach: that module drives the twelve globals and
|
||||
|
||||
@@ -376,10 +376,11 @@ fn the_pages_close_button_sits_where_the_reference_seats_it_and_closes_the_windo
|
||||
// **"Close", not "CLOSE".** An XML `text=` attribute is a GlobalStrings LOOKUP, not a literal
|
||||
// (`loader::Loader::resolve_text`, wow-re `rf28-typed-widget-loadxml.md` l.36 —
|
||||
// `FrameScript_GetText 0x703bf0`), and `CLOSE = "Close"` (`GlobalStrings.lua:760`). This test
|
||||
// read "CLOSE" while the page's list carried no `GlobalStrings.lua`: it was asserting our
|
||||
// loader's miss-fallback (the LITERAL, a deliberate divergence for benilla-authored files),
|
||||
// not the label the reference client draws. [`super::test_ui::CHARACTER_UI`] loads the
|
||||
// player's own strings first, as the app does, so this is the real label now.
|
||||
// read "CLOSE" while the page's list carried no `GlobalStrings.lua`: it was asserting the
|
||||
// miss-fallback (the raw attribute), which is the REFERENCE's own arm too (`0x778c31` /
|
||||
// `0x771032`), not the label a client with the strings loaded draws.
|
||||
// [`super::test_ui::CHARACTER_UI`] loads the player's own strings first, as the app does, so
|
||||
// this is the real label now.
|
||||
let label = page_quads(&s)
|
||||
.iter()
|
||||
.find_map(|q| match &q.content {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The shipped **spellbook window** driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/SpellBookFrame.xml` loaded behind `Fonts.xml`/`UiPanels.xml`/`GameTooltip.xml`
|
||||
//! (plus `ActionBar.xml` for the cross-window place test) and fed a small synthetic book —
|
||||
//! mirroring `character_tests.rs`'s/`action_bar_tests.rs`'s harness (decision 0216 §8, slice 5).
|
||||
//! `Interface\FrameXML\SpellBookFrame.xml` loaded behind
|
||||
//! `Fonts.xml`/`UIParent.xml`/`GameTooltip.xml` (plus `ActionBar.xml` for the cross-window place
|
||||
//! test) and fed a small synthetic book — mirroring `character_tests.rs`'s/`action_bar_tests.rs`'s harness (decision 0216 §8, slice 5).
|
||||
|
||||
use benilla_ui::script::{SpellBookState, SpellSlotView, SpellTabView, UiScript};
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The shared StaticPopup engine (decision 0308 §3 — the ref's registry + Show + OnUpdate
|
||||
//! machinery, transcribed in UiPanels.xml): the countdown/StartDelay/cancels/ESC laws the death
|
||||
//! arc's dialogs ride. Entries here are inline test dialogs — the real entries (DELETE_ITEM,
|
||||
//! ABANDON_QUEST, the death family) are covered by their features' own tests.
|
||||
//! machinery, stock `Interface\FrameXML\StaticPopup.xml` since 1988): the
|
||||
//! countdown/StartDelay/cancels/ESC laws the death arc's dialogs ride. Entries here are inline test
|
||||
//! dialogs — the real entries (DELETE_ITEM, ABANDON_QUEST, the death family) are covered by their features' own tests.
|
||||
|
||||
use benilla_ui::script::UiScript;
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! The shipped taxi-map window driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/TaxiFrame.xml` fed a synthetic two-node snapshot (the [`crate::ui_taxi`] feed's exact
|
||||
//! output shape). Covers what only a runtime load exercises: the Lua parses and every referenced
|
||||
//! global resolves (including the static node-button pool and the runtime-created route-line
|
||||
//! textures — TaxiFrame.xml's own header note on both), `TAXIMAP_OPENED` shows the window with the
|
||||
//! flight master's name and paints the node buttons at their pushed positions, a click on a node
|
||||
//! drains through `TakeTaxiNode`, and `TAXIMAP_CLOSED` hides it.
|
||||
//! `Interface\FrameXML\TaxiFrame.xml` fed a synthetic two-node snapshot (the [`crate::ui_taxi`]
|
||||
//! feed's exact output shape). Covers what only a runtime load exercises: the Lua parses and every
|
||||
//! referenced global resolves (including the static node-button pool and the runtime-created
|
||||
//! route-line textures — TaxiFrame.xml's own header note on both), `TAXIMAP_OPENED` shows the
|
||||
//! window with the flight master's name and paints the node buttons at their pushed positions, a
|
||||
//! click on a node drains through `TakeTaxiNode`, and `TAXIMAP_CLOSED` hides it.
|
||||
|
||||
use benilla_ui::script::{ScriptValue, TaxiNodeType, TaxiUiNode, TaxiUiState, UiScript};
|
||||
|
||||
@@ -23,12 +23,12 @@ fn taxi_script() -> UiScript {
|
||||
load_xml(&s, r"Interface\FrameXML\LocaleProperties.lua");
|
||||
load_xml(&s, r"Interface\FrameXML\StaticPopup.xml");
|
||||
load_xml(&s, "Interface\\FrameXML\\GameTooltip.xml"); // TaxiNodeOnButtonEnter's tooltip + SetTooltipMoney
|
||||
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // BenillaErrorsFrame_AddMessage — DrawOneHopLines' refusal
|
||||
load_xml(&s, "Interface\\FrameXML\\UIErrorsFrame.xml"); // `UIErrorsFrame:AddMessage` — DrawOneHopLines' refusal
|
||||
// Three the reference's own TaxiFrame leans on that our transcription did not:
|
||||
// · GlobalStrings — `ERR_TAXINOPATHS` is a GlobalString, and `AddMessage(nil)` draws an
|
||||
// empty line rather than raising, so its absence is silent.
|
||||
// · UIPanelTemplates (.lua then .xml) — `TaxiCloseButton` inherits `UIPanelCloseButton`,
|
||||
// which lives there and NOT in our UiPanels.xml. Without it the close button loads as a
|
||||
// which the chain declares there. Without it the close button loads as a
|
||||
// bare Button with no handler and a click does nothing at all.
|
||||
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.lua");
|
||||
load_xml(&s, "Interface\\FrameXML\\UIPanelTemplates.xml");
|
||||
@@ -128,10 +128,9 @@ fn shipped_taxi_frame_drives_end_to_end() {
|
||||
|
||||
/// The "no single-hop destination" refusal (ref `DrawOneHopLines`, fired from `OnShow`): a map
|
||||
/// with only the `Current` node (no `Reachable` neighbor at all) hits `numSingleHops == 0` and
|
||||
/// calls `BenillaErrorsFrame_AddMessage` (TaxiFrame.xml's deviation 3 from the reference's
|
||||
/// `UIErrorsFrame:AddMessage`) then hides the window — verified end-to-end since this is the one
|
||||
/// call this engine has no precedent for outside this window (`ErrorsFrame.xml`'s own Lua seam,
|
||||
/// not yet exercised from another shipped window's script).
|
||||
/// calls `UIErrorsFrame:AddMessage(ERR_TAXINOPATHS, …)` (stock `TaxiFrame.lua:161-162`) then
|
||||
/// hides the window — driven end-to-end because the message frame's Lua seam is what the
|
||||
/// refusal is visible through.
|
||||
#[test]
|
||||
fn no_single_hop_destination_posts_the_error_and_closes() {
|
||||
let mut s = taxi_script();
|
||||
|
||||
@@ -665,7 +665,7 @@ pub(super) const BAG_UI: &[&str] = &[
|
||||
// Our adapters over the reference's container files — the keyring tooltip wrapper and the
|
||||
// three bag verbs 0561 shadows (`OpenBackpack`/`CloseBackpack`/`CloseAllBags`). It has to be
|
||||
// AFTER `ContainerFrame.xml` and after the bar, which is why it is here and not up with
|
||||
// UiPanels.xml.
|
||||
// `UIParent.xml`.
|
||||
"ContainerFrameAdapters.xml",
|
||||
// `updateContainerFrameAnchors` measures every open bag against `BankFrame:GetRight()`
|
||||
// (ContainerFrame.lua l.505) on EVERY open and close, so the bank window is not optional
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! `GameTooltipTemplate` as an ADDON sees it — the shipped `assets/ui/GameTooltip.xml` driven the
|
||||
//! way the corpus drives it.
|
||||
//! `GameTooltipTemplate` as an ADDON sees it — the stock `Interface\FrameXML\GameTooltip.xml`
|
||||
//! driven the way the corpus drives it.
|
||||
//!
|
||||
//! **This is not a test of our tooltip window** (that is `tooltip_anchor_tests` /
|
||||
//! `tooltip_compare_tests`, and they are the regression suite this refactor had to leave
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
//! The shipped **trainer window** driven end-to-end, engine-only (no Bevy): the real
|
||||
//! `assets/ui/TrainerFrame.xml` — a client-sorted, collapsible **skill-line tree** with a **dropdown**
|
||||
//! state filter and a draggable **scroll bar** (decisions 0247/0251) — loaded behind its deps
|
||||
//! (`UiPanels.xml` + `UIDropDownMenu.xml` + `ScrollTemplates.xml` + `MerchantFrame.xml` for the
|
||||
//! the stock money frames) and fed a synthetic service list. Covers what only a runtime load
|
||||
//! exercises: the Lua parses and every referenced global resolves, the tree renders interleaved
|
||||
//! header/service rows, a header click folds its group, the dropdown filter hides a state, the wheel
|
||||
//! scrolls the list, the NPC name rides `arg1` into the title, the byte-exact GlobalStrings render, the
|
||||
//! Train button gates on available-and-affordable, and the buy queues the row's spell id.
|
||||
//! The reference's own `Blizzard_TrainerUI` addon — a client-sorted, collapsible **skill-line
|
||||
//! tree** with a **dropdown** state filter and a draggable **scroll bar** (decisions 0247/0251) —
|
||||
//! loaded behind its deps (`UiPanels.xml` + `UIDropDownMenu.xml` + `ScrollTemplates.xml` +
|
||||
//! `MerchantFrame.xml` for the the stock money frames) and fed a synthetic service list. Covers
|
||||
//! what only a runtime load exercises: the Lua parses and every referenced global resolves, the
|
||||
//! tree renders interleaved header/service rows, a header click folds its group, the dropdown
|
||||
//! filter hides a state, the wheel scrolls the list, the NPC name rides `arg1` into the title, the
|
||||
//! byte-exact GlobalStrings render, the Train button gates on available-and-affordable, and the buy queues the row's spell id.
|
||||
|
||||
use benilla_ui::script::{
|
||||
ExtractedQuad, QuadContent, ScriptValue, SoundRequest, TrainerAbilityReq, TrainerService,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! `assets/ui/UIParent.xml`'s addon-facing helpers, driven from Lua the way an addon drives them.
|
||||
//! Stock `Interface\FrameXML\UIParent.xml`'s addon-facing helpers, driven from Lua the way an addon drives them.
|
||||
//!
|
||||
//! The panel/ESC machinery in that file is covered by `panel_tests` and `escape_tests`; this is for
|
||||
//! the loose functions the reference's `UIParent.lua` also defines, which benilla itself may never
|
||||
|
||||
@@ -603,13 +603,11 @@ fn raid_mark_clicks_through_the_nested_level() {
|
||||
.unwrap();
|
||||
s.mouse_button(sx as f32, sy as f32, "LeftButton", true);
|
||||
s.mouse_button(sx as f32, sy as f32, "LeftButton", false);
|
||||
// RED while the `SetRaidTarget` engine binding is absent, and deliberately left that way.
|
||||
// `UnitPopup.xml`'s row calls `SetRaidTargetIcon(menu.unit, mark)`; the definition of that
|
||||
// used to be ours, and since the migration it is the reference's own — `TargetFrame.lua`
|
||||
// l.486-492 — whose whole body is `SetRaidTarget(unit, 0 or index)`. `SetRaidTarget` is an
|
||||
// engine verb this house does not have yet, so the click raises
|
||||
// "attempt to call global 'SetRaidTarget' (a nil value)" and no intent is queued. 1203: it
|
||||
// gets built, never stubbed.
|
||||
// l.486-492 — whose whole body is `SetRaidTarget(unit, 0 or index)`. That is the ENGINE
|
||||
// verb, built by 1820 (`benilla-ui` `script/party.rs`), never the wrapper's name on our
|
||||
// body: this asserts the intent it queues, and `:638` asserts nothing raised.
|
||||
assert_eq!(
|
||||
s.take_party_requests(),
|
||||
vec![benilla_ui::script::PartyRequest::SetRaidTarget {
|
||||
@@ -1211,8 +1209,8 @@ fn the_party_art_paints_over_the_bars() {
|
||||
let mut s = UiScript::new().unwrap();
|
||||
s.set_screen_size(1024.0, 768.0);
|
||||
// The loot test's prefix (`loot_tests.rs`): PartyFrame's inline <Script> reads
|
||||
// StaticPopupDialogs, which UiPanels.xml defines, and its per-member dropdown OnLoad walks the
|
||||
// whole popup kit.
|
||||
// StaticPopupDialogs, which the chain's `StaticPopup.xml` defines, and its per-member dropdown
|
||||
// OnLoad walks the whole popup kit.
|
||||
// GlobalStrings first, for the same reason `load_unit_frames` names it — the stock unit-frame
|
||||
// files resolve it at LOAD (`CombatFeedback.lua` l.7-17, `UnitFrame.lua` l.1-6).
|
||||
load_xml(&s, "Interface\\FrameXML\\GlobalStrings.lua");
|
||||
@@ -1865,10 +1863,9 @@ fn no_two_numeral_strings_overlap_on_any_frame() {
|
||||
/// function UnitFrame_OnEnter() originalUnitFrame_OnEnter() … end
|
||||
/// ```
|
||||
///
|
||||
/// Against a missing global that captured nil and raised on the first hover. Note these are
|
||||
/// ADAPTERS, not renames: the reference's contract takes no arguments and reads `this`, ours takes
|
||||
/// the frame explicitly, so a rename would have reached our body with a nil frame — reachable but
|
||||
/// broken, which is worse than inert.
|
||||
/// Against a missing global that captured nil and raised on the first hover. Our own adapter
|
||||
/// (`BenillaUnitFrame_OnEnter(frame)`, which took the frame explicitly) went with the file 1751's
|
||||
/// twenty-second window deleted: what runs here is the reference's own `this`-shaped body.
|
||||
///
|
||||
/// The same class as the Bagnon bag bug, and equally invisible to the corpus survey: it loads
|
||||
/// addons and fires events, but never hovers anything.
|
||||
|
||||
@@ -17,7 +17,7 @@ fn load_popup_frames(s: &UiScript) {
|
||||
for file in [
|
||||
"Interface\\FrameXML\\Fonts.xml",
|
||||
r"Interface\FrameXML\UIParent.xml",
|
||||
// `SmallMoneyFrame_OnLoad`, which UiPanels' own StaticPopup money rows call at load.
|
||||
// `SmallMoneyFrame_OnLoad`, which the chain's StaticPopup money rows call at load.
|
||||
r"Interface\FrameXML\MoneyFrame.lua",
|
||||
r"Interface\FrameXML\MoneyFrame.xml",
|
||||
// `StaticPopupDialogs` and the `PanelTemplates_*` family, both of which FriendsFrame.xml
|
||||
@@ -75,8 +75,9 @@ fn bake_strings(s: &UiScript) {
|
||||
-- The newbie tooltip the stock unit frame raises on a HOVER, which every test in this file
|
||||
-- takes on its way to a right-click. `UnitFrame_OnEnter` (ref `UnitFrame.lua:58-65`) runs
|
||||
-- the detailed-tip branch whenever `SHOW_NEWBIE_TIPS == "1"` — 1.12's own default
|
||||
-- (`UIOptionsFrame.lua:100`), which our `GameTooltip.xml:46` sets at load — and for a
|
||||
-- player-controlled target that is not us it calls
|
||||
-- (`UIOptionsFrame.lua:100`, a file benilla does not build; our `assets/ui/OptionsFrame.xml`
|
||||
-- is the definer, and this harness loads no options file, so the branch is not reached
|
||||
-- here). For a player-controlled target that is not us that branch calls
|
||||
-- `GameTooltip_AddNewbieTip(PLAYER_OPTIONS_LABEL, 1, 1, 1, NEWBIE_TOOLTIP_PLAYEROPTIONS)`.
|
||||
-- Both are nil in a bare harness, and `GameTooltip:SetText(nil)` raises. Verbatim from the
|
||||
-- real `Interface\FrameXML\GlobalStrings.lua` off the 1.12.1 chain (l.3081 and l.2755).
|
||||
@@ -476,7 +477,7 @@ fn solo_target_inspect_click_reaches_inspect_unit() {
|
||||
|
||||
// ── The PET menu (decision 1066; report B219) ───────────────────────────────────────────────────
|
||||
|
||||
/// The pet menu's own prefix. What joins the popup prefix is `UiPanels.xml` — the StaticPopup
|
||||
/// The pet menu's own prefix. What joins the popup prefix is `StaticPopup.xml` — the StaticPopup
|
||||
/// engine, because two of the four rows go behind a dialog, and since 1953 where the pet arc's
|
||||
/// three dialogs are registered (they rode our pet-bar file until it became the reference's).
|
||||
/// `Cooldown.xml` and
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The always-up world-state readout (`assets/ui/WorldStateFrame.xml`) against the shipped XML —
|
||||
//! The always-up world-state readout (stock `Interface\FrameXML\WorldStateFrame.xml`) —
|
||||
//! report B190's second half, driven the way the client drives it: rows pushed, then
|
||||
//! `UPDATE_WORLD_STATES` fired.
|
||||
//!
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
//! `CMSG_ACTIVATETAXIEXPRESS` with the full node chain; a click on the `Current` node is a
|
||||
//! client-side no-op.
|
||||
//!
|
||||
//! `TaxiFrame.xml` (`crates/benilla-app/assets/ui/TaxiFrame.xml`) is the window; its header comment
|
||||
//! Stock `Interface\FrameXML\TaxiFrame.xml` is the window; the toc's header comment
|
||||
//! carries the three engine-forced deviations from the literal reference Lua (a static node-button
|
||||
//! pool, the title reading an event arg, the error line's call target).
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The world-map data feed (decision 0203 phase 2) — the app half behind
|
||||
//! `assets/ui/WorldMapFrame.xml` and benilla-ui's `script/worldmap.rs` bindings.
|
||||
//! stock `Interface\FrameXML\WorldMapFrame.xml` and benilla-ui's `script/worldmap.rs` bindings.
|
||||
//!
|
||||
//! Two systems, the quest-log seam shape:
|
||||
//! - [`load_world_map_ui`] (once, when the chain + VM + Map.dbc catalog all exist): builds the
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! The always-up world-state readout (`WorldStateFrame`) — the app half behind
|
||||
//! `assets/ui/WorldStateFrame.xml` and benilla-ui's `script/worldstate.rs` bindings.
|
||||
//! stock `Interface\FrameXML\WorldStateFrame.xml` and benilla-ui's `script/worldstate.rs` bindings.
|
||||
//!
|
||||
//! This is report **B190**'s second half: the alliance↔horde progress UI. `WorldStateUI.dbc` says
|
||||
//! which world states are *displayed*, where, and with what label; [`crate::world_state`] holds
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
//! Drives the REAL `assets/ui/AuctionFrame.xml` through the engine (decision 1511) — the auction
|
||||
//! Drives the reference's own `Blizzard_AuctionUI` addon through the engine (decision 1511; 1971
|
||||
//! put it on the player's chain) — the auction
|
||||
//! twin of `mail_frame.rs`: it loads the same file chain the app does (cut to the auction window's
|
||||
//! dependency prefix), pushes a synthetic `AuctionState`, opens the window with the app's own
|
||||
//! `AUCTION_HOUSE_SHOW`/`AUCTION_ITEM_LIST_UPDATE` events, and asserts the transcribed Lua actually
|
||||
|
||||
@@ -23,7 +23,7 @@ use benilla_ui::script::UiScript;
|
||||
///
|
||||
/// **Two of the templates under test are the REFERENCE's since 1860** — `FauxScrollFrameTemplate`
|
||||
/// and `TabButtonTemplate` were ours until the dead-copy sweep, and both now come off the player's
|
||||
/// chain from `Interface\FrameXML\UIPanelTemplates.xml`, seated below `UiPanels.xml` exactly as
|
||||
/// chain from `Interface\FrameXML\UIPanelTemplates.xml`, seated below `UIParent.xml` exactly as
|
||||
/// the manifest seats it. So this list carries the chain pair and the loader below has to be able
|
||||
/// to READ a chain entry, which a disk-only provider under `assets/ui` cannot.
|
||||
const FILES: &[&str] = &[
|
||||
@@ -61,11 +61,11 @@ fn load_ui(script: &UiScript) {
|
||||
|
||||
/// The line an addon writes, and the globals it reads on the next one.
|
||||
///
|
||||
/// `TabButtonTemplate` (UiPanels.xml) is a `<Button>` carrying a `<Size>`, six `<Layers>` slices,
|
||||
/// a `<ButtonText name="$parentText">`, a `<HighlightTexture name="$parentHighlightTexture">`, the
|
||||
/// three state fonts and an `<OnUpdate>` — i.e. every decoration pass at once. Instantiating it as
|
||||
/// `BenillaTemplateProbeTab` must publish `BenillaTemplateProbeTab*`, and must publish nothing
|
||||
/// named after the template.
|
||||
/// `TabButtonTemplate` (`UIPanelTemplates.xml`) is a `<Button>` carrying a `<Size>`, six `<Layers>`
|
||||
/// slices, a `<ButtonText name="$parentText">`, a `<HighlightTexture
|
||||
/// name="$parentHighlightTexture">`, the three state fonts and an `<OnUpdate>` — i.e. every
|
||||
/// decoration pass at once. Instantiating it as `BenillaTemplateProbeTab` must publish
|
||||
/// `BenillaTemplateProbeTab*`, and must publish nothing named after the template.
|
||||
#[test]
|
||||
fn a_real_template_reaches_an_addon_through_create_frame() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Drives the REAL `assets/ui/MailFrame.xml` through the engine (decision 0544 P1/P2) — the mail
|
||||
//! twin of `tradeskill_frame.rs`: it loads the same file chain the app does (cut to the mail
|
||||
//! window's dependency prefix), pushes a synthetic inbox, opens the window with the app's own
|
||||
//! Drives the stock `Interface\FrameXML\MailFrame.xml` through the engine (decision 0544 P1/P2) —
|
||||
//! the mail twin of `tradeskill_frame.rs`: it loads the same file chain the app does (cut to the
|
||||
//! mail window's dependency prefix), pushes a synthetic inbox, opens the window with the app's own
|
||||
//! `MAIL_SHOW`/`MAIL_INBOX_UPDATE` events, and asserts the transcribed Lua actually paints — the
|
||||
//! named regions exist, the rows populate from a fed `MailState`, the paging math is right, and the
|
||||
//! unread/read row state tracks the wire `wasRead` flag.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Drives the REAL `assets/ui/TalentFrame.xml` through the engine — the first test that
|
||||
//! Drives the reference's own `Blizzard_TalentUI` addon through the engine — the first test that
|
||||
//! executes the transcribed talent Lua at all (before this, the window's machinery only ever
|
||||
//! ran inside a live client session; both of the director's day-one reports — no prereq branch
|
||||
//! lines, no tooltip on first hover — slipped through that gap).
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! Drives the REAL `assets/ui/TradeFrame.xml` through the engine (decision 0592 P1) — the trade twin
|
||||
//! of `mail_frame.rs`: it loads the same file chain the app does (cut to the trade window's dependency
|
||||
//! prefix), pushes a synthetic two-sided offer, opens the window with the app's own `TRADE_SHOW`
|
||||
//! event, and asserts the transcribed Lua actually paints — the named regions exist, both columns
|
||||
//! populate from a fed `TradeState`, the money coin trios render, and the accept glow tracks the
|
||||
//! `TRADE_ACCEPT_UPDATE(my, his)` args. This is the machine gate for the XML (a Lua error / missing
|
||||
//! global / wrong region name fails here); the director's eye judges only the *look*.
|
||||
//! Drives the stock `Interface\FrameXML\TradeFrame.xml` through the engine (decision 0592 P1) — the
|
||||
//! trade twin of `mail_frame.rs`: it loads the same file chain the app does (cut to the trade
|
||||
//! window's dependency prefix), pushes a synthetic two-sided offer, opens the window with the app's
|
||||
//! own `TRADE_SHOW` event, and asserts the transcribed Lua actually paints — the named regions
|
||||
//! exist, both columns populate from a fed `TradeState`, the money coin trios render, and the
|
||||
//! accept glow tracks the `TRADE_ACCEPT_UPDATE(my, his)` args. This is the machine gate for the XML
|
||||
//! (a Lua error / missing global / wrong region name fails here); the director's eye judges only the *look*.
|
||||
|
||||
mod common;
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! Drives the REAL `assets/ui/TradeSkillFrame.xml` through the engine — the crafting-book twin
|
||||
//! of `talent_frame.rs`, and the first test that executes the transcribed tradeskill Lua at all
|
||||
//! (the polish pass's own discovery: no suite loaded this file, so a runtime bug in the
|
||||
//! Drives the reference's own `Blizzard_TradeSkillUI` addon through the engine — the crafting-book
|
||||
//! twin of `talent_frame.rs`, and the first test that executes the transcribed tradeskill Lua at
|
||||
//! all (the polish pass's own discovery: no suite loaded this file, so a runtime bug in the
|
||||
//! Show/Update/dropdown code would only ever surface in a live session).
|
||||
//!
|
||||
//! The harness loads the same file chain the app does (`ui_script/mod.rs`'s list, cut to the
|
||||
|
||||
@@ -162,8 +162,8 @@ impl Loader<'_> {
|
||||
self.call(wrapper, "SetToplevel", true, dbg);
|
||||
}
|
||||
// `enableKeyboard="true"` — the XML half of the flag, which enables BOTH key kinds
|
||||
// (`scripts-auto-enable.md` §1-2). The flag is now real and round-trips; key delivery is
|
||||
// still not gated on it, which the method's own doc states rather than this warning.
|
||||
// (`scripts-auto-enable.md` §1-2). The flag is real, and `script::keyboard`'s delivery
|
||||
// walk is what reads it (1319).
|
||||
if el.attr_bool("enableKeyboard") {
|
||||
self.call(wrapper, "EnableKeyboard", true, dbg);
|
||||
}
|
||||
|
||||
@@ -428,18 +428,28 @@ impl Loader<'_> {
|
||||
///
|
||||
/// wow-re `system/ui/scratch/rf28-typed-widget-loadxml.md`: `<Button text=>` (l.36) and
|
||||
/// `<FontString text=>` (l.115) BOTH resolve through `FrameScript_GetText 0x703bf0`, which
|
||||
/// `scratch/inventory-change-failure-display.md` l.119 carves VERIFIED — it resolves the value as
|
||||
/// a Lua global and, **when that global is not a string, returns a pre-seeded EMPTY string**
|
||||
/// (`0x882748`), never the key name. That is why the reference's `text="LOGOUT"` renders "Logout",
|
||||
/// and why `GlobalStrings.lua` runs before any XML (`ui_script::load_global_strings`).
|
||||
/// `scratch/framescript.md` carves VERIFIED — it resolves the value as a Lua global and,
|
||||
/// **when that global is not a string, returns a pre-seeded EMPTY string** (`0x882748`), never
|
||||
/// the key name. That is why the reference's `text="LOGOUT"` renders "Logout", and why
|
||||
/// `GlobalStrings.lua` runs before any XML (`ui_script::load_global_strings`).
|
||||
///
|
||||
/// **One deliberate divergence: a miss falls back to the LITERAL** rather than the reference's
|
||||
/// empty string. benilla authors its own FrameXML (0068) and writes plain English in it —
|
||||
/// `text="Send Mail"`, `text="No results found."` — which the reference's rule would blank. The
|
||||
/// fallback is a strict superset for transcriptions (every real key resolves identically) and it
|
||||
/// fails LOUDER than the reference: a **key-shaped** value that misses keeps its key on screen
|
||||
/// *and* warns here — exactly the signal that was missing when the macro window shipped with
|
||||
/// "CREATE_MACROS" across its title bar (0983 → 0991).
|
||||
/// **The MISS falls back to the raw attribute, and that is the reference's own behaviour — not
|
||||
/// a benilla divergence, which is what this comment used to claim.** `0x703bf0`'s empty return
|
||||
/// never reaches a label: all three `text=` readers image-wide test it and substitute the raw
|
||||
/// attribute string. `Button::LoadXML` at `0x778c07` — recorded in wow-re
|
||||
/// `scratch/template-onload-replacement-law.md` §5, "a fallback to the raw attribute when the
|
||||
/// lookup comes back empty (`0x778c31 mov eax,esi`)" — and byte-identically
|
||||
/// `CSimpleFontString::LoadXML` at `0x771006` (`771012 mov esi,eax` … `771029 test eax,eax` /
|
||||
/// `77102b je 0x771032` / `77102d cmp BYTE [eax],0` / `771030 jne` / `771032 mov eax,esi`), and
|
||||
/// the third reader at `0x7292a6`, which expresses the same law through a copy
|
||||
/// (`7292db mov al,[ebp-0x424]` / `7292e1 test al,al`, empty arm pushes `edi` = the raw).
|
||||
///
|
||||
/// So the reference's `PetPaperDollFrame.xml:70` `text="Level level race class"` really does
|
||||
/// draw that placeholder until Lua overwrites it, and benilla's plain-English `text="Send
|
||||
/// Mail"` renders for the same reason the reference's would. What IS ours is the extra signal:
|
||||
/// a **key-shaped** value that misses warns here as well as keeping its key on screen — the
|
||||
/// signal that was missing when the macro window shipped with "CREATE_MACROS" across its title
|
||||
/// bar (0983 → 0991).
|
||||
pub(super) fn resolve_text(&mut self, raw: &str, dbg: &str) -> String {
|
||||
if let Ok(s) = self.lua().globals().get::<String>(raw) {
|
||||
return s;
|
||||
|
||||
@@ -46,9 +46,9 @@ pub const EQUIPMENT_BAG: i64 = -100;
|
||||
|
||||
/// What the cursor carries — the client's payload-mode global [0xb4d900] as a typed enum
|
||||
/// (wow-re cursor-dragdrop-payload.md §1: 1 = live item, 3 = spell, **4 = pet action**, **8 =
|
||||
/// macro**, **10 = stabled pet**; our Action arm is the client's bar-slot pickup; the
|
||||
/// money/preview arms stay unbuilt). One transition seam for every surface, so sounds,
|
||||
/// CURSOR_UPDATE, and lock display can't drift apart per window (decision 0216).
|
||||
/// macro**, **10 = stabled pet**; our Action arm is the client's bar-slot pickup; the money arm
|
||||
/// is mode 2 ([`CursorMoney`], 1962/1965) and the preview arm stays unbuilt). One transition seam
|
||||
/// for every surface, so sounds, CURSOR_UPDATE, and lock display can't drift apart per window (decision 0216).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum CursorPayload {
|
||||
Item(CursorItem),
|
||||
|
||||
@@ -15,9 +15,10 @@
|
||||
//! **`autoFocus` DOES focus on show** (corrected 2026-08-29, wow-re `editbox-selection-focus-law.md`
|
||||
//! §6): the OnShow override tail-jumps `SetFocus` when nothing else holds focus, and the OnHide
|
||||
//! mirror tail-jumps `ClearFocus`. The old "verified by absence" negative came from a `call`-only
|
||||
//! census that could not see a tail-`jmp`. **Not implemented here yet** — see [`EditBoxState::
|
||||
//! auto_focus`](crate::widget::EditBoxState::auto_focus) for why it waits on the attribute default.
|
||||
//! The self-acquire-on-first-key half stands and is what this module implements.
|
||||
//! census that could not see a tail-`jmp`. Both overrides are [`visibility_focus`] here, and the
|
||||
//! construction default is `true` off the bytes (see
|
||||
//! [`EditBoxState::auto_focus`](crate::widget::EditBoxState::auto_focus)). The
|
||||
//! self-acquire-on-first-key half stands beside it.
|
||||
//! - **Routing (§2):** a focused box processes and CONSUMES every key/char (`return 1` past the
|
||||
//! guard); an unfocused non-autoFocus box ignores input. The override fires ONLY the specialized
|
||||
//! scripts (Enter/Escape/Space/Tab/TextChanged/TextSet/focus), never generic `OnKeyDown`/`OnChar`.
|
||||
|
||||
@@ -20,12 +20,17 @@ fn text_quad(s: &UiScript) -> Option<String> {
|
||||
|
||||
// ── §1/§2 focus acquisition + routing ───────────────────────────────────────────────────────
|
||||
|
||||
/// **The self-acquire path**, path 2 of the two: a box that has had no show TRANSITION (created
|
||||
/// already visible, so `visibility_focus` never runs for it) still takes the keyboard on the first
|
||||
/// key or char event, and processes that same event. Path 1, focus-on-show, is
|
||||
/// [`an_autofocus_box_takes_the_keyboard_when_it_is_shown`] below — this test's name used to assert
|
||||
/// its absence, which stopped being true at decision 1686.
|
||||
#[test]
|
||||
fn autofocus_does_not_focus_on_show_but_self_acquires_first_event() {
|
||||
fn an_autofocus_box_self_acquires_on_the_first_event() {
|
||||
let mut s = script();
|
||||
s.run(r#"E = CreateFrame("EditBox", "E"); E:SetAutoFocus(true)"#)
|
||||
.unwrap();
|
||||
// autoFocus does NOT focus on show — nothing owns the keyboard yet.
|
||||
// No show transition has run for this box, so nothing owns the keyboard yet.
|
||||
assert!(!s.has_keyboard_focus());
|
||||
assert!(!s.eval::<bool>("return E:HasFocus()").unwrap());
|
||||
|
||||
|
||||
@@ -22,8 +22,10 @@
|
||||
//! continuation** alone. The C dispatcher `0x4c2790(slot, flag)` takes the row only on `flag == 0`,
|
||||
//! which no Lua binding reaches — the row click is the C `CLootButton`'s own behaviour — while
|
||||
//! `LootSlot 0x4c2e70` passes `flag = 1`, whose arm refuses every slot but the one a bind confirm
|
||||
//! is pending for. benilla has no `CLootButton`, so the click arm is `BenillaTakeLootSlot(slot)`
|
||||
//! and `LootSlot` keeps the reference's meaning exactly.
|
||||
//! is pending for. benilla builds that widget — [`crate::widget::FrameKind::LootButton`], decision
|
||||
//! 1799 — so the row click's take runs where the reference runs it, in `script::button`'s click
|
||||
//! path under the same no-modifier gate; `BenillaTakeLootSlot` is the Lua-reachable entry to the
|
||||
//! same queue, and `LootSlot` keeps the reference's meaning exactly.
|
||||
//!
|
||||
//! The **coin pile is a synthesized client-side row** (first in the list when the loot carries gold):
|
||||
//! `LootSlotIsCoin` is true for it, its `item` text is the formatted money amount, and `LootSlot(1)`
|
||||
|
||||
@@ -192,9 +192,9 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// Set/GetShadowOffset` are real entries on this class's table, not a courtesy. wow-re's
|
||||
// registrar carve is explicit about the membership — *"Exposed on: FontString, Font object,
|
||||
// EditBox, MessageFrame, ScrollingMessageFrame, SimpleHTML. NOT on Button"* — and names this
|
||||
// class's own shims calling the shared implementations (`GetShadowColor 0x794810`). We shipped the block on two
|
||||
// of the six and this is the third and fourth; SimpleHTML is a widget kind we do not have at
|
||||
// all.
|
||||
// class's own shims calling the shared implementations (`GetShadowColor 0x794810`). All six
|
||||
// carry the block now — `SimpleHTML`'s is its own copy rather than `font_block::install`'s
|
||||
// (`script/simplehtml/mod.rs`), because that class computes its own inter-block step.
|
||||
//
|
||||
// Demand is observed, not counted: `BigWigs/Plugins/Messages.lua:212` is
|
||||
// `self.msgframe:SetFontObject(GameFontNormalLarge)` on a frame it has just given
|
||||
|
||||
@@ -252,9 +252,9 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
|
||||
// Set/GetShadowOffset` are real entries on this class's table, not a courtesy. wow-re's
|
||||
// registrar carve is explicit about the membership — *"Exposed on: FontString, Font object,
|
||||
// EditBox, MessageFrame, ScrollingMessageFrame, SimpleHTML. NOT on Button"* — and names this
|
||||
// class's own shims calling the shared implementations (`GetShadowColor 0x792240`). We shipped the block on two
|
||||
// of the six and this is the third and fourth; SimpleHTML is a widget kind we do not have at
|
||||
// all.
|
||||
// class's own shims calling the shared implementations (`GetShadowColor 0x792240`). All six
|
||||
// carry the block now — `SimpleHTML`'s is its own copy rather than `font_block::install`'s
|
||||
// (`script/simplehtml/mod.rs`), because that class computes its own inter-block step.
|
||||
//
|
||||
// Demand is observed, not counted: `BigWigs/Plugins/Messages.lua:212` is
|
||||
// `self.msgframe:SetFontObject(GameFontNormalLarge)` on a frame it has just given
|
||||
|
||||
@@ -75,12 +75,11 @@
|
||||
//!
|
||||
//! ## What is deliberately NOT here
|
||||
//!
|
||||
//! Five of `Model`'s own 23 — `SetFogNear 0x76f1e0`, `GetFogNear 0x76f2d0`, `SetFogFar 0x76f390`,
|
||||
//! `GetFogFar 0x76f480`, `ClearFog 0x76f540`. No corpus caller, and their bodies are uncarved:
|
||||
//! `ClearFog`'s exact effect on the colour/near/far triple is a guess until someone reads it, and
|
||||
//! a guessed clear reads as knowledge. Named, not stubbed.
|
||||
//! Nothing of `Model`'s own 23 — the fog near/far/clear set was the last hold-out and 2027 carved
|
||||
//! it (`ClearFog 0x76f540` is `76f5c5 and [edi+0x3a4],-2`, bit 0 and nothing else, so the guess
|
||||
//! that kept it out is gone; see the install below).
|
||||
//!
|
||||
//! Also absent, and correctly so: `SetCreature` and `SetCustomRace`. Neither string exists in
|
||||
//! Absent, and correctly so: `SetCreature` and `SetCustomRace`. Neither string exists in
|
||||
//! 5875 in any form (substring scan of the whole mapped image returns 0, against a positive
|
||||
//! control of 27 hits for `Creature`) — they are later-expansion names, and publishing one would
|
||||
//! be decision 1189's error.
|
||||
@@ -704,8 +703,9 @@ fn playermodel_install(lua: &Lua) -> mlua::Result<()> {
|
||||
// it inverted), plays it unless that id is already armed on bone slot 0, and then
|
||||
// UNCONDITIONALLY sets `[+0x3e8] = 1` and `[+0x3ec] = now_ms + 100` — a 100 ms turn hold that
|
||||
// the per-paint `0x505c50` expires. Every one of those is invisible to Lua (no getter reads
|
||||
// them) and lands on a model renderer we have not built, so storing them here would be state
|
||||
// nobody writes and nobody reads. The addresses are the pin for the day the renderer exists.
|
||||
// them). The app's `<Model>` renderer (`benilla-app` `ui_models`, 2013/2019/2027) draws its
|
||||
// pane off the file's own clock and does not model a turn hold, so storing the pair here
|
||||
// would be state nobody writes and nobody reads. The addresses are the pin if it ever does.
|
||||
m.set(
|
||||
"SetRotation",
|
||||
lua.create_function(|lua, (this, rad): (Table, Value)| {
|
||||
|
||||
@@ -299,42 +299,19 @@ pub(super) fn install(lua: &Lua, m: &Table) -> mlua::Result<()> {
|
||||
/// `system/ui/ui.md` l.544-556 summarises it), so what is missing here is never a mystery — it is a
|
||||
/// deliberate not-yet. What the corpus actually asks for, and why each answer is what it is:
|
||||
///
|
||||
/// * **`OnKeyDown` / `OnKeyUp` / `OnChar`** (14 + 1 + 4 corpus sites over 13 addons) — **raising,
|
||||
/// and now unblocked**: the delivery law was the second §5 this work dispatched, and it landed
|
||||
/// (wow-re `scratch/frame-key-script-delivery.md`). benilla still has none of the machinery it
|
||||
/// describes — `EnableKeyboard`/`IsKeyboardEnabled` now exist and the flag round-trips, but there
|
||||
/// is still no keyboard index and no strata walk, and keys are routed straight to the focused
|
||||
/// EditBox — so the names stay out until that exists, which is the whole rule above. What has to
|
||||
/// get built, so the next pass does not have to re-derive it:
|
||||
///
|
||||
/// The frame must be in the hit-test root's **kind-0 / kind-1 bucket**
|
||||
/// (`scratch/scripts-auto-enable.md` §1-2: `0x76af00(kind, …)`, `OnChar` = kind 0,
|
||||
/// `OnKeyDown`/`OnKeyUp` = kind 1; XML `enableKeyboard` enables both, a `<Scripts>` block
|
||||
/// auto-enables per handler, and Lua `SetScript` auto-enables **nothing** — so a Lua-only frame
|
||||
/// is not in the bucket at all and its bound handler can never fire). The dispatcher then walks
|
||||
/// **strata 8 → 0, level high → low, ties oldest-registration-first**, calling each frame's base
|
||||
/// input virtual (`0x765f10` key-down → vtable `+0x60`; `0x765df0` char → `+0x5c`) and
|
||||
/// **stopping at the first nonzero return**. `arg1` is a **key-name string** with no modifier
|
||||
/// prefix, decoded from the *same* table the keybinding chord names use — verified byte-identical
|
||||
/// over 273 codes, which is the opposite of the mouse case's two-table shape. `OnChar` gets the
|
||||
/// literal character, UTF-8.
|
||||
///
|
||||
/// Two findings that will bite whoever implements it. The consumption gate is **existence, not
|
||||
/// handling** — a 1.12 handler cannot signal "handled" (the fire's return is discarded at all
|
||||
/// three sites), so merely having a key script bound suppresses the key's binding; and
|
||||
/// asymmetrically, a frame with **only** an `OnKeyUp` script consumes every key-down and runs
|
||||
/// nothing. And `CGWorldFrame` sits at **strata 0 / level 0** — last in the walk — which is
|
||||
/// precisely why any keyboard-enabled frame pre-empts the entire binding system.
|
||||
///
|
||||
/// (The old gloss here called `0x76bba0` a "frame-script pre-gate" walking the index, after
|
||||
/// `scratch/keybinding-dispatch-law.md` §1. That is refuted: `0x76bba0` is `CSimpleFrame::OnKeyUp`,
|
||||
/// one frame's base virtual. The walk is one level up, in the dispatcher.)
|
||||
/// * **`OnKeyDown` / `OnKeyUp` / `OnChar`** (14 + 1 + 4 corpus sites over 13 addons) — **accepted**
|
||||
/// since decision 1319 built the delivery walk ([`crate::script::keyboard`], whose module doc is
|
||||
/// the mechanism: the kind buckets, the strata 8→0 order, the key-name table, and the
|
||||
/// existence-not-handling consumption gate). They were this list's standing exception; the rule
|
||||
/// above let them in the moment something fired them.
|
||||
/// * **`OnCursorChanged`** (4 sites over 3 addons — all of them the Era `ScrollingEdit_OnCursorChanged`
|
||||
/// auto-scroll idiom) — **raising.** It is the EditBox's own slot (RF-28 `+0x428`), fired by the
|
||||
/// caret flush `0x77da80` with **four float caret-POSITION args**, and caret geometry is the one
|
||||
/// thing this engine deliberately does not have: text is measured host-side. Accepting it would
|
||||
/// hand every caller four zeros, which for its single idiom means a scroll box that silently
|
||||
/// never follows the caret.
|
||||
/// auto-scroll idiom) — **raising**, by this list's own rule: nothing fires it. It is the
|
||||
/// EditBox's own slot (RF-28 `+0x428`), fired by the reference's caret flush `0x77da80` with
|
||||
/// **four float caret-POSITION args**. The geometry itself is no longer the obstacle — the paint
|
||||
/// seam computes `caret_row`/`caret_x` off the host-answered advance table
|
||||
/// ([`crate::script::editbox::seam`]) — but that is a per-extract recompute with no event edge,
|
||||
/// so there is no counterpart to the flush to fire from. Accepting the name before building one
|
||||
/// would mean a scroll box that silently never follows the caret.
|
||||
/// * **`OnAttributeChanged`** (1 site, `Roid-Macros`) — **raising, permanently.** It is 2.0's secure
|
||||
/// frame/attribute system; there is no such slot in any 1.12 resolver. That addon is asking for a
|
||||
/// later client and should hear so.
|
||||
|
||||
@@ -47,7 +47,8 @@ pub(super) fn install(lua: &Lua, m: &Table) -> mlua::Result<()> {
|
||||
)?;
|
||||
// GetPoint([n]) → point, relativeTo, relativePoint, xOfs, yOfs — the n-th (1-based, default
|
||||
// first) anchor. relativeTo is nil when the target is the screen root (the client returns
|
||||
// UIParent there; benilla has no UIParent wrapper yet — stated, a consensus-list call).
|
||||
// UIParent there; ours is the distinct `script::SCREEN` sentinel, which has no wrapper of its
|
||||
// own — the arena's `UIParent` frame is a different handle — stated, a consensus-list call).
|
||||
m.set(
|
||||
"GetPoint",
|
||||
lua.create_function(|lua, (this, n): (Table, Option<i64>)| {
|
||||
@@ -233,9 +234,13 @@ pub(super) fn install(lua: &Lua, m: &Table) -> mlua::Result<()> {
|
||||
)?;
|
||||
|
||||
// GetEffectiveScale() — the frame's real effective scale (parentScale · ownScale, the arena's
|
||||
// propagated product). benilla has no uiScale CVar, so the root factor is 1; a SetScale'd
|
||||
// subtree (the windowed world map) reports its true factor, and the reference's
|
||||
// `GetCursorPosition()/GetEffectiveScale()` transcriptions convert screen→local correctly.
|
||||
// propagated product). The ROOT factor is 1, and that is not because benilla lacks a `uiScale`
|
||||
// CVar (it has one, `cvars.rs`, default 0.9): the dial is applied at the RASTER seam — it sets
|
||||
// how many UI units tall the virtual screen is, `768/uiScale` (`ui_script::seam_scale`, 0584) —
|
||||
// rather than as a scale on UIParent. So every coordinate the VM hands Lua is already in those
|
||||
// units, `GetCursorPosition()` included, and the reference's
|
||||
// `GetCursorPosition()/GetEffectiveScale()` transcriptions convert screen→local correctly with
|
||||
// a root of 1; a SetScale'd subtree (the windowed world map) still reports its true factor.
|
||||
m.set(
|
||||
"GetEffectiveScale",
|
||||
lua.create_function(|lua, this: Table| {
|
||||
|
||||
@@ -887,11 +887,14 @@ pub(super) fn virtual_span(model: &Model, rh: RegionHandle) -> (f32, f32) {
|
||||
// **The floor applies to a KNOWN extent, and a pending measure is not one.** On the
|
||||
// reference every extent is known — the getter measures inline — so `0.0` never comes
|
||||
// back and the question never arises. Ours can be *waiting*, which is not a size but
|
||||
// the absence of an answer, and several of our own convergence drivers read exactly
|
||||
// that: `BenillaGossipRow_Resize`, the tab fit and the quest panel all guard
|
||||
// `if h <= 0 then return end` and re-run from `OnUpdate` until the round-trip lands.
|
||||
// Flooring a pending measure to one unit hands them a number, so they stop waiting and
|
||||
// seat every row at 3px (`shipped_gossip_frame_drives_end_to_end` catches it).
|
||||
// the absence of an answer. **Where that state still lives is a VM with no measurer
|
||||
// installed** (`script::measure`'s is optional): the app seats one at the load edge
|
||||
// (2028), so no in-app caller observes a pending measure any more, and the three
|
||||
// convergence drivers this used to name went with their windows —
|
||||
// `BenillaGossipRow_Resize` (gossip, 1751), our tab fit (1993/2028) and our quest
|
||||
// panel (1944). Flooring a pending measure to one unit hands a measurer-less caller a
|
||||
// number where it should read "not yet"; the surviving `OnUpdate` fits
|
||||
// (`OptionsScroll_Fit`, `BenillaScroll_ResizeChild`) guard on RECTS, not on this.
|
||||
//
|
||||
// A genuinely EMPTY string is a different thing: its extent is known and it is zero, so
|
||||
// it floors — which is the case the reference's floor exists for. The layout sweep
|
||||
|
||||
@@ -242,8 +242,8 @@ fn the_string_setters_gate_their_argument_and_a_number_is_a_string() {
|
||||
/// - **no superset** — a name the reference does not have on a table must not resolve there
|
||||
/// (1189: a name we have and the reference lacks routes an addon down a path the real client
|
||||
/// never takes);
|
||||
/// - **the seven we deliberately do not build must stay absent**, so "unbuilt" cannot quietly
|
||||
/// become "stubbed" without this test being edited to say so.
|
||||
/// - **the `UNBUILT` set must stay absent** — it is empty since 2027, so "unbuilt" cannot
|
||||
/// quietly become "stubbed" without this test being edited to say so.
|
||||
#[test]
|
||||
fn the_two_model_tables_are_the_references_own() {
|
||||
/// `Model` — `CSimpleModel`, table `0x878948`, 23 entries, in table order.
|
||||
|
||||
@@ -1452,8 +1452,8 @@ fn a_frames_type_chain_matches_the_roster() {
|
||||
("Minimap", "Minimap", &["Minimap", "Frame", "Region"]),
|
||||
// `PlayerModel` derives from `Model`, which is why `SetUnit` on a portrait pane finds
|
||||
// `SetCamera` too (wow-re `ui/scratch/widget-type-identity.md` §6) — and `DressUpModel`
|
||||
// derives from it in turn: the roster's maximum depth, 5 (1969; the unbuilt `TabardModel`
|
||||
// would be the other).
|
||||
// derives from it in turn: the roster's maximum depth, 5 (1969; `TabardModel` is the
|
||||
// other depth-5 chain — built by 1977).
|
||||
(
|
||||
"PlayerModel",
|
||||
"PlayerModel",
|
||||
|
||||
@@ -1043,11 +1043,10 @@ fn the_size_getters_take_the_author_first_then_the_natural_width_and_wrapped_hei
|
||||
|
||||
/// The floor, and the state the reference does not have. `0x772930`/`0x772a60` end in a one-unit
|
||||
/// clamp, so a genuinely empty string reads back **1**, never `0.0`. But a measure that has not
|
||||
/// LANDED is not an extent at all — it is our async round-trip, which the reference has no
|
||||
/// equivalent of — and our own convergence drivers (`BenillaGossipRow_Resize` and the tab fit,
|
||||
/// which guard `if h <= 0 then return end` and re-run from `OnUpdate`) read that zero as
|
||||
/// "not yet". Flooring it would tell them to stop waiting. So: floor a known extent, not the
|
||||
/// absence of one.
|
||||
/// LANDED is not an extent at all — it is the host round-trip a VM with **no measurer installed**
|
||||
/// still takes, which the reference has no equivalent of. A caller that guards
|
||||
/// `if h <= 0 then return end` reads that zero as "not yet"; flooring it would tell them to stop
|
||||
/// waiting. So: floor a known extent, not the absence of one.
|
||||
#[test]
|
||||
fn an_empty_string_reads_back_one_unit_and_a_pending_measure_reads_back_zero() {
|
||||
let mut s = script();
|
||||
|
||||
@@ -93,9 +93,9 @@ fn the_flag_round_trips_and_defaults_off() {
|
||||
///
|
||||
/// `enableKeyboard` used to be warned here on the same principle — implementing one clause is not
|
||||
/// a licence to go quiet about the other. It now lands on a real `EnableKeyboard`, so the warning
|
||||
/// is gone and this asserts the flag instead. What is still NOT built is key *delivery*, which the
|
||||
/// method's own doc states; the flag and the delivery are separable in the reference too
|
||||
/// (`frame-key-script-delivery.md` §3.2).
|
||||
/// is gone and this asserts the flag instead. Key *delivery* reads the same flag
|
||||
/// (`script::keyboard`'s walk, 1319); the flag and a handler stay separable in the reference too
|
||||
/// (`frame-key-script-delivery.md` §3.2), which is why this asserts the flag alone.
|
||||
#[test]
|
||||
fn the_xml_toplevel_and_enable_keyboard_attributes_both_reach_their_methods() {
|
||||
let s = script();
|
||||
|
||||
@@ -60,28 +60,24 @@ pub struct EditBoxState {
|
||||
/// Selection end (`E+0x360`). A non-empty selection is `sel_start != sel_end`; every insert
|
||||
/// replaces it first (RF-0082 §3).
|
||||
pub sel_end: usize,
|
||||
/// `autoFocus` (`flags@E+0x318` bit0). Two acquisition paths, and benilla implements only the
|
||||
/// second:
|
||||
/// `autoFocus` (`flags@E+0x318` bit0). Two acquisition paths, and both are built:
|
||||
///
|
||||
/// 1. **On SHOW** — the box self-focuses when it becomes visible, gated on nothing else holding
|
||||
/// focus. The EditBox's own OnShow vtable override (`0x81c910` slot +0x30, `0x77a750`) fires
|
||||
/// the Lua handler and then tail-jumps `SetFocus`:
|
||||
/// `if ([0xcf4dc8] == 0 && (flags & 1)) jmp 0x77e3d0` @`0x77a76d`. The mirror slot +0x34
|
||||
/// (`0x77a780`) tail-jumps `ClearFocus`, so **hiding an edit box releases the keyboard**.
|
||||
/// Ours is [`crate::script::editbox`]'s `visibility_focus`.
|
||||
/// 2. **On the first key/char event** while nothing is focused — the self-acquire guard; the box
|
||||
/// grabs focus and processes that same event.
|
||||
///
|
||||
/// **This corrects what stood here.** Both this comment and wow-re's own `ui.md`/RF-0082 said
|
||||
/// autoFocus does NOT focus on show, "verified by absence" — a census written over `call` alone,
|
||||
/// which cannot see a tail-`jmp`. A `(call|jmp)` census finds the eleventh site (wow-re
|
||||
/// `editbox-selection-focus-law.md` §6, 2026-08-29, dispatched from benilla's login work). Path 1
|
||||
/// is a real behavioural gap here, not just a stale sentence.
|
||||
///
|
||||
/// **`false` at construction is OUR value, not a verified one.** The client's construction-time
|
||||
/// bit0 is unrecorded, and every `autoFocus` in the shipped 1.12.1 chain is `="false"` — ten
|
||||
/// opt-outs, no opt-ins — which is the authoring signature of a default that is ON. Out to wow-re;
|
||||
/// until it answers, this default and the missing path 1 are deliberately left alone together,
|
||||
/// because turning path 1 on under a wrong default is what would actually break something.
|
||||
/// **Both corrections landed together, decision 1686 (2026-08-29).** wow-re's own
|
||||
/// `ui.md`/RF-0082 had published "autoFocus does NOT focus on show, verified by absence" off a
|
||||
/// census written over `call` alone, which cannot see a tail-`jmp`; a `(call|jmp)` census finds
|
||||
/// the eleventh site (`editbox-selection-focus-law.md` §6). The construction default came with
|
||||
/// it and is byte-read, not chosen — see [`Default`]'s `flags = 1` note below. Every
|
||||
/// `autoFocus` in the shipped 1.12.1 chain is `="false"`, ten opt-outs and no opt-ins, which is
|
||||
/// the authoring signature of exactly that default.
|
||||
pub auto_focus: bool,
|
||||
/// `multiLine` (bit1): Enter inserts a newline (rather than firing `OnEnterPressed`) and `\n` is
|
||||
/// accepted into the buffer.
|
||||
|
||||
@@ -90,7 +90,7 @@ pub enum FrameKind {
|
||||
/// ([`KindState::Model`], shared with [`FrameKind::Model`]); the pixels are the app renderer's.
|
||||
/// `CGCharacterModelBase` adds `0x1c` bytes of members over `CSimpleModel` — the turn-animation
|
||||
/// flag/expiry at `+0x3e8`/`+0x3ec` that `SetRotation` arms — and those are **not modeled**:
|
||||
/// no getter reads them, and they drive a shuffle animation on a renderer we have not built.
|
||||
/// no getter reads them, and the app's `<Model>` renderer draws no shuffle animation.
|
||||
/// `script::modelframe`'s `SetRotation` carries the addresses.
|
||||
PlayerModel,
|
||||
/// `DressUpModel` — `CGDressUpModelFrame` (`Ui\DressUpModelFrame.cpp`, factory `0x495c00`,
|
||||
@@ -357,10 +357,10 @@ pub const TOOLTIP_WRAP_WIDTH: f32 = 260.0;
|
||||
/// table owns it*, and `SetUnit`'s single pooled string `0x84f22c` is referenced by two entries in
|
||||
/// two different tables. Ownership now comes from a dword-reference count over the name's VA.
|
||||
///
|
||||
/// **Seven of `Model`'s 23 are not published yet** — `AdvanceTime 0x76eca0`,
|
||||
/// `ReplaceIconTexture 0x76ed70`, `SetFogNear 0x76f1e0`, `GetFogNear 0x76f2d0`,
|
||||
/// `SetFogFar 0x76f390`, `GetFogFar 0x76f480`, `ClearFog 0x76f540` — named here rather than
|
||||
/// stubbed (decision 1134 §4). They have no corpus caller and their bodies are uncarved.
|
||||
/// **All 23 of `Model`'s are published** (decision 2027 carved the last seven — `AdvanceTime`,
|
||||
/// `ReplaceIconTexture` and the fog near/far/clear set). `script::tests::modelframe`'s `UNBUILT`
|
||||
/// array is the live count and is empty; the wall around it is what notices a name arriving or
|
||||
/// leaving (1134 §4's naming rule, with nothing left to name).
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct ModelState {
|
||||
/// The M2/MDX path last given to `SetModel`, or `None` after `ClearModel` / before any set.
|
||||
|
||||
@@ -263,15 +263,14 @@ pub struct Frame {
|
||||
/// `enableKeyboard`) — the kind-0/kind-1 bucket membership `0x76af00` writes as
|
||||
/// `[frame+0xcc] |= 1<<kind`.
|
||||
///
|
||||
/// Default **false**, the client's own default. **The flag round-trips; key DELIVERY is not
|
||||
/// gated on it yet** — the same shape [`Self::mouse_wheel_enabled`] shipped in (1198), and for
|
||||
/// the same reason: the machinery it gates (the strata 8→0 walk, the kind buckets, the
|
||||
/// `OnKeyDown`/`OnKeyUp`/`OnChar` script kinds) does not exist here, so gating on the flag
|
||||
/// would change nothing while pretending otherwise. wow-re's
|
||||
/// `scratch/frame-key-script-delivery.md` §3.2 is explicit that the two are separable:
|
||||
/// `EnableKeyboard(true)` on a script-less frame "puts it in the walk where it is called and
|
||||
/// declines — transparent to everything downstream", so **being enabled is not being a
|
||||
/// handler**, and storing the flag alone is the faithful half rather than a stub of the whole.
|
||||
/// Default **false**, the client's own default. **This is the field key delivery gates on**:
|
||||
/// [`crate::script::keyboard`]'s walk builds its candidate set from
|
||||
/// `effective_visible && keyboard_enabled`, then orders it strata 8→0 / level high→low /
|
||||
/// oldest-registration-first (built 2026-08-14, decision 1319). wow-re's
|
||||
/// `scratch/frame-key-script-delivery.md` §3.2 is explicit that the flag and a handler are
|
||||
/// separable: `EnableKeyboard(true)` on a script-less frame "puts it in the walk where it is
|
||||
/// called and declines — transparent to everything downstream", so **being enabled is not
|
||||
/// being a handler**.
|
||||
pub keyboard_enabled: bool,
|
||||
/// Clamp-to-screen (`SetClampedToScreen` / XML `clampedToScreen` — the client's geometry
|
||||
/// flags **bit4**, applied inside rect assembly `0x767a20`, wow-re `layout.md`): the layout
|
||||
|
||||
@@ -452,8 +452,8 @@ impl WidgetArena {
|
||||
}
|
||||
}
|
||||
|
||||
/// `EnableKeyboard` (`0x776f90`) — kind-0/kind-1 bucket membership. Stored and answered; the
|
||||
/// key path is not gated on it yet (see `crate::widget::WidgetState::keyboard_enabled`).
|
||||
/// `EnableKeyboard` (`0x776f90`) — kind-0/kind-1 bucket membership. Stored and answered, and
|
||||
/// it is what [`crate::script::keyboard`]'s delivery walk filters on (1319).
|
||||
pub fn set_keyboard_enabled(&mut self, h: FrameHandle, enabled: bool) {
|
||||
if let Some(f) = self.frame_mut(h) {
|
||||
f.keyboard_enabled = enabled;
|
||||
|
||||
@@ -98,7 +98,7 @@ impl Default for MatAnimTable {
|
||||
impl MatAnimTable {
|
||||
/// Allocate a slot (1-based; 0 is the identity row). `None` when the table is full — the
|
||||
/// caller then simply doesn't register, and the batch stays frozen at its built seed: a
|
||||
/// degraded look only a >511-material session could see, never a wrong pixel.
|
||||
/// degraded look only a >2047-material session could see, never a wrong pixel.
|
||||
pub fn alloc(&mut self) -> Option<u16> {
|
||||
if let Some(slot) = self.free.pop() {
|
||||
return Some(slot);
|
||||
|
||||
@@ -86,9 +86,9 @@ impl GlobalSeqDrive {
|
||||
})
|
||||
}
|
||||
|
||||
/// Pause/resume the joint writes (the doodad draw gate, and the booth park — a sleeping
|
||||
/// booth camera renders nothing, so its scene's channels hold). While paused the joints
|
||||
/// hold their last pose; resume lands on the anchored cursor with nothing to catch up.
|
||||
/// Pause/resume the joint writes. One caller: the doodad draw gate
|
||||
/// (`doodad_anim`'s `set_paused(!drawn)`). While paused the joints hold their last pose;
|
||||
/// resume lands on the anchored cursor with nothing to catch up.
|
||||
pub fn set_paused(&mut self, paused: bool) {
|
||||
self.paused = paused;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user