diff --git a/crates/benilla-app/assets/ui/ChatFrame.xml b/crates/benilla-app/assets/ui/ChatFrame.xml
index 8a499c9b..1b2d500a 100644
--- a/crates/benilla-app/assets/ui/ChatFrame.xml
+++ b/crates/benilla-app/assets/ui/ChatFrame.xml
@@ -241,6 +241,11 @@
oldX = 0, oldY = 0,
reveal = 0, -- 0..1 master fade the tab/texture alphas scale by
revealTarget = 0,
+ -- What the apply block below last WROTE, so an unchanged frame writes nothing
+ -- (decision 1396). -1 is unreachable for both, so the first pass always applies.
+ appliedReveal = -1,
+ appliedSelected = -1,
+ tabsSettled = false, -- both tabs' host-measured label widths have landed
flashTab = 0, -- tab id whose whisper flash runs (0 = none), 0288 P4 wires it
flashLeft = 0, -- remaining flash seconds (ref: 60s, blink 0.25/0.25)
}
@@ -331,14 +336,19 @@
-- PanelTemplates_TabResize(5, tab), re-expressed: the ref sizes every docked tab to its
-- label (FCF_DockUpdate) — middle slice = text width + 5, tab = middle + the two 16px
-- caps; the flash/highlight bands follow the Left/Right pieces by anchor. The label
- -- width is host-measured (GetStringWidth), so early frames may see 0 — the OnUpdate
- -- below re-applies until it settles, then the change-guard makes this a no-op.
+ -- width is host-measured (GetStringWidth), so early frames may see 0. It RETURNS whether
+ -- the width is known yet — which is what lets FCF_OnUpdate's apply gate keep polling
+ -- until the measurement lands and then stop for good (decision 1396). Without that
+ -- answer the gate would have to choose between polling forever and shipping tabs stuck
+ -- at their authored width on whichever session measured late.
function BenillaFCF_TabResize(tab)
local w = getglobal(tab:GetName() .. "Text"):GetStringWidth()
- if w == 0 or tab.benillaTabTextW == w then return end
+ if w == 0 then return false end
+ if tab.benillaTabTextW == w then return true end
tab.benillaTabTextW = w
getglobal(tab:GetName() .. "Middle"):SetWidth(w + 5)
tab:SetWidth(w + 5 + 32)
+ return true
end
-- The hover rect: the dock's rect expanded by the ref's MouseIsOver pads
@@ -389,16 +399,43 @@
-- Apply: tabs at 1.0 (selected) / 0.5 (unselected) × reveal; the selected window's
-- background+borders at 0.25 × reveal (ref: fade oldAlpha↔DEFAULT_CHATFRAME_ALPHA).
- for i = 1, 2 do
- local tab = getglobal("ChatFrame" .. i .. "Tab")
- if tab then
- local a = (i == BenillaFCF.selected) and 1.0 or 0.5
- tab:SetAlpha(a * s.reveal)
- BenillaFCF_TabResize(tab)
+ --
+ -- GATED on everything the writes below depend on — the reveal, the selected tab, and
+ -- whether the label measurements have landed (decision 1396). **The reference needs
+ -- no gate because it does not do this here at all**: ref FCF_OnUpdate hands the fade
+ -- to UIFrameFadeIn/Out, and UIFrameFade_OnUpdate walks only the frames still fading,
+ -- so a settled dock costs nothing. This transcription inlined the ramp and lost that
+ -- property with it, and ~24 getglobals, two table builds and ~20 SetAlpha calls then
+ -- ran every frame of every session to write the values that were already there —
+ -- 36 us/frame, the largest single handler in the client (1395 measured it). The
+ -- guard restores the reference's own behaviour rather than inventing one.
+ if s.reveal ~= s.appliedReveal or s.selected ~= s.appliedSelected then
+ for i = 1, 2 do
+ local tab = getglobal("ChatFrame" .. i .. "Tab")
+ if tab then
+ tab:SetAlpha(((i == s.selected) and 1.0 or 0.5) * s.reveal)
+ end
+ for _, t in ipairs(BenillaFCF_Textures(i)) do
+ t:SetAlpha(0.25 * s.reveal)
+ end
end
- for _, t in ipairs(BenillaFCF_Textures(i)) do
- t:SetAlpha(0.25 * s.reveal)
+ s.appliedReveal = s.reveal
+ s.appliedSelected = s.selected
+ end
+
+ -- The tab widths are a SEPARATE trigger and get their own latch, deliberately: they
+ -- depend on a host measurement rather than on the fade, and folding them into the
+ -- gate above would tie the alphas to a width that can legitimately never arrive (an
+ -- empty label measures 0 forever) — the apply block would then run every frame again
+ -- for a reason having nothing to do with what it writes. Polls until both land, then
+ -- stops for good.
+ if not s.tabsSettled then
+ local settled = true
+ for i = 1, 2 do
+ local tab = getglobal("ChatFrame" .. i .. "Tab")
+ if tab and not BenillaFCF_TabResize(tab) then settled = false end
end
+ s.tabsSettled = settled
end
-- The whisper flash blink (0.25 on / 0.25 off, 60s — ref UIFrameFlash args).
diff --git a/crates/benilla-app/assets/ui/OptionsFrame.xml b/crates/benilla-app/assets/ui/OptionsFrame.xml
index 6bb59125..2b467237 100644
--- a/crates/benilla-app/assets/ui/OptionsFrame.xml
+++ b/crates/benilla-app/assets/ui/OptionsFrame.xml
@@ -615,7 +615,7 @@
"RowEnableAll", "RowMaster", "RowSound", "RowMusic", "RowAmbience",
"RowEnableMusic", "RowEnableAmbience",
},
- Graphics = { "RowUiScale", "RowWorldDetail" },
+ Graphics = { "RowUiScale", "RowWorldDetail", "RowVerticalSync" },
Nameplates = { "RowPlayerNames", "RowNpcNames", "RowOwnName" },
-- The stock globals that were already defined AND consumed when 1134 opened the store
-- (1136; Buff Durations 1139), in the reference's own on-screen order — its Basic
@@ -2075,6 +2075,22 @@
})
+
+
+
+
+
+
+ -- Vertical Sync: 1.12's own Video Options checkbox
+ -- 5 (gxVSync), over the window's present mode.
+ -- The reference row carries gxRestart = 1; ours
+ -- does not and must not be marked deferred --
+ -- wgpu swaps the presentation interval live, so
+ -- the box takes effect as you click it.
+ OptionsRow_OnLoad(self, "gxVSync", "Vertical Sync",
+ "OPTION_TOOLTIP_VERTICAL_SYNC")
+
+