Anisotropic Filtering takes its seat on the Graphics page

The CVar has been registered and read since the filter policy landed, and
last commit put it on the Graphics Preset ladder — but a player had no way
to reach it. The reference's own `OptionsFrameSliders` declares the row at
index 7; ours left that index a hole, so the only route to anisotropy was
`/console anisotropic 16`, and a player whose preset reads Custom (the
seeder never overwrites one) never met the setting at all.

Seated where the reference's own slider order puts it: after Brightness,
before Weather Intensity. Three things it does differently, each stated
beside it:

- The groove runs 1 to 16, the CVar's own `ANISO_RANGE`, not the stock
  table's 1 to 4. That ceiling was the reference *slider's*, not the
  CVar's — its callback parses 1-16 (`0x689110`) — and both the High and
  the Ultra rung ask for 16. So the row carries literal bounds and stays
  out of `BENILLA_GRAPHICS_SLIDER_ROWS`, exactly as `RowRenderScale` does
  for a CVar the stock table never had, which leaves `OptionsFrameSliders`
  verbatim for any addon that reads it.
- `1` reads out as "Off", not "1x": the reference writes filter mode 5
  only at 2 and above, so 1 is the feature off and not its weakest stop.
- The description is ours. 1.12's own OPTION_TOOLTIP_ANISOTROPIC describes
  a four-stop slider, and this one is sixteen.

Deferred to Apply, as Vertical Sync and Render Scale are: a sampler is
baked into each `Image` at load, so the write cannot reach a texture that
is already resident. The tooltip says so in the row rather than leaving a
player to wonder why the ground did not change — the same thing the
reference's UI means by "enabled upon restart".

Verified: `benilla-app --lib` at the exact 269-failure data-gated baseline,
set-identical by `comm`, with the new row's own test and the two census
counts (tooltip keys, raised plates) moved 116 -> 117 as the row asks.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-10-26 14:39:25 -06:00
co-authored by Claude Opus 5
parent b6f3bb001c
commit 1c786b7ecb
2 changed files with 143 additions and 6 deletions
+70 -4
View File
@@ -420,6 +420,19 @@
.. "Below 100% trades sharpness for frame rate; above 100% is supersampling, which "
.. "smooths edges at the cost of frame rate. Applies when you click Apply."
-- 1.12's own OPTION_TOOLTIP_ANISOTROPIC describes a four-stop slider that takes
-- effect on restart. Ours is neither: the groove runs the CVar's own 1-16 (the
-- reference's callback parses that range, `0x689110`; only its *slider* stopped at 4),
-- and it commits in place on Apply. So the text is ours.
BENILLA_TOOLTIP_ANISOTROPIC =
"Sharpens ground and wall texture seen at a grazing angle - the road running away "
.. "from you, a floor stretching into a room.\n\n"
.. "Off is what the original client shipped, which smears that texture to mush a few "
.. "yards out. 16x costs about a millisecond of graphics time and nothing at all on "
.. "the processor, so it is close to free on any modern card.\n\n"
.. "Each texture is sampled the way it was when it loaded, so this takes effect the "
.. "next time you start the game."
-- 1.12 has no such checkbox.
BENILLA_TOOLTIP_BACKGROUND_SOUND =
"Keeps the game's sound playing while you are working in another window.\n\n"
@@ -1051,7 +1064,8 @@
},
Graphics = {
"RowUiScale", "RowFarclip", "RowWorldDetail", "RowBrightness",
"RowWeatherDensity",
-- GFX (anisotropic): index 7 of OptionsFrameSliders, in that table's own order.
"RowAnisotropic", "RowWeatherDensity",
"RowDisplayMode", "RowResolution", "RowVerticalSync",
"RowRenderScale",
},
@@ -1257,7 +1271,15 @@
{ text = WEATHER_DETAIL, func = "weatherDensity", minValue = 0, maxValue = 3, valueStep = 1, tooltipText = OPTION_TOOLTIP_WEATHER_DETAIL },
}
-- Holes at 4, 5, 7 and 8, so the OnShow loop uses the pairs form: ipairs would stop at 3.
-- The rows whose bounds ARE the stock table's, re-applied on show so an addon's write to
-- OptionsFrameSliders lands. Holes at 4, 5, 7 and 8, so the OnShow loop uses the pairs
-- form: ipairs would stop at 3.
--
-- 7 (ANISOTROPIC) is a seated row but deliberately not listed: the stock entry caps at 4,
-- which was the reference *slider's* limit and not the CVar's (its callback parses 1-16,
-- `0x689110`), and High and Ultra both ask for 16. So that row carries its own literal
-- bounds, as `RowRenderScale` does for a CVar the table never had — and the table above
-- stays verbatim for any addon that reads it.
BENILLA_GRAPHICS_SLIDER_ROWS = {
"RowUiScale", "RowFarclip", "RowWorldDetail",
[6] = "RowBrightness", [9] = "RowWeatherDensity",
@@ -1430,6 +1452,14 @@
if row.fmt == "yd" then
return string.format(BENILLA_ADVGFX.yards, math.floor(value + 0.5))
end
-- The anisotropy groove. 1 is not "1x", it is the feature OFF (`tex_filter.rs`:
-- the reference writes filter mode 5 only at 2 and above), and a player reading "1x"
-- next to "2x" would take it for the weakest setting rather than for none.
if row.fmt == "aniso" then
local n = math.floor(value + 0.5)
if n <= 1 then return "Off" end
return string.format("%dx", n)
end
return string.format("%d%%", math.floor(value * 100 + 0.5))
end
@@ -2792,12 +2822,48 @@
OptionsFrameSliders[6].valueStep, "travel")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowAnisotropic" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowBrightness" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowBrightness" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
-- Index 7 of OptionsFrameSliders, seated where the
-- reference's own slider order puts it: after
-- Brightness, before Weather Intensity. Before this
-- the row was a hole, so the only way to anisotropy
-- was `/console anisotropic 16` — and a player on a
-- Custom graphics preset never met it at all, since
-- the preset ladder is what seeds the value.
OptionsRow_OnLoad(self, "anisotropic", "Anisotropic Filtering",
"BENILLA_TOOLTIP_ANISOTROPIC")
-- 1 to 16, the CVar's own range (`ANISO_RANGE`),
-- not the stock table's 1-4 — see
-- BENILLA_GRAPHICS_SLIDER_ROWS. Step 1 rather than
-- a power-of-two ladder because the value is a
-- MAXIMUM the driver is free to round down: an
-- off-ladder stop is a weaker setting, never a
-- wrong one, which is the test `RowShadowResolution`
-- fails and so takes a dropdown instead.
OptionsSliderRow_Setup(self, 1, 16, 1, "aniso")
-- DEFERRED, as the two rows below it are, because
-- the write does not reach a texture that is
-- already resident: a sampler is baked into each
-- `Image` at load (`tex_filter.rs`), which is why
-- the reference's own UI says "enabled upon
-- restart". Apply is therefore the honest commit
-- point, and a drag does not write a CVar on every
-- step of the groove that only the next launch
-- reads.
self.deferred = 1
</OnLoad></Scripts>
</Frame>
<!-- On the CVar, as 1.12's lands: there is no
SetweatherDensity global. -->
<Frame name="$parentRowWeatherDensity" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowBrightness" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowBrightness" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPLEFT" relativeTo="$parentRowAnisotropic" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowAnisotropic" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "weatherDensity", "Weather Intensity",
@@ -1704,6 +1704,71 @@ fn the_render_scale_row_shows_a_percentage_and_defers_to_apply() {
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// GFX (anisotropic): the row that was a hole. Three things it has to get right — the groove
/// reaches the CVar's own 16 and not the stock table's 4, `1` reads as "Off" and not as "1x", and
/// the write waits for Apply (committing re-samples every resident texture, which a drag would
/// pay on every step).
#[test]
fn the_anisotropic_row_reaches_sixteen_reads_off_at_one_and_defers_to_apply() {
benilla_formats::wow_data_or_skip!();
let mut s = audio_harness();
s.set_cvar_host("anisotropic", "1");
let mut s = harness_on(s);
s.run("ShowUIPanel(BenillaOptionsFrame)").unwrap();
s.run("BenillaOptionsFrameCategoryListRowGraphics:Click()")
.unwrap();
let row = "BenillaOptionsFrameContainerBodyGraphicsRowAnisotropic";
assert_eq!(
s.eval::<String>(&format!("return {row}Label:GetText()"))
.unwrap(),
"Anisotropic Filtering"
);
assert_eq!(
s.eval::<String>(&format!("return {row}ControlValue:GetText()"))
.unwrap(),
"Off",
"1 is the feature off, not its weakest stop"
);
// The CVar's own `ANISO_RANGE`, not `OptionsFrameSliders[7]`'s 1-4, which was the reference
// slider's ceiling and is below both the High and the Ultra rung.
assert!(s
.eval::<bool>(&format!(
"local lo, hi = {row}ControlSlider:GetMinMaxValues() \
return lo == 1 and hi == 16"
))
.unwrap());
assert!(s
.eval::<bool>("return BENILLA_TOOLTIP_ANISOTROPIC ~= nil")
.unwrap());
// The stock table stays verbatim for any addon that reads it.
assert!(s
.eval::<bool>("return OptionsFrameSliders[7].maxValue == 4")
.unwrap());
let _ = s.take_cvar_changes();
s.run(&format!("{row}ControlSlider:SetValue(16)")).unwrap();
assert_eq!(
s.eval::<String>(&format!("return {row}ControlValue:GetText()"))
.unwrap(),
"16x"
);
assert!(
s.take_cvar_changes().is_empty(),
"a deferred row must not write the CVar on the move"
);
assert!(s
.eval::<bool>("return BenillaOptionsFrameApplyButton:IsVisible()")
.unwrap());
s.run("BenillaOptionsFrameApplyButton:Click()").unwrap();
assert_eq!(
s.take_cvar_changes(),
vec![("anisotropic".to_string(), "16".to_string())]
);
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
}
/// Pending edits are panel-wide, as the era's modified table: a category switch keeps them, and
/// hiding the window discards them.
#[test]
@@ -3125,6 +3190,9 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
),
("BENILLA_TOOLTIP_LAVA_GLOW", "AdvancedGraphicsRowLavaGlow"),
("BENILLA_TOOLTIP_RENDER_SCALE", "GraphicsRowRenderScale"),
// GFX (anisotropic): ours, because 1.12's own text describes a four-stop slider
// that needs a restart and this row is neither.
("BENILLA_TOOLTIP_ANISOTROPIC", "GraphicsRowAnisotropic"),
("BENILLA_TOOLTIP_DISPLAY_MODE", "GraphicsRowDisplayMode"),
// 1.12 shipped the resolution dropdown with no tooltip at all, so there is no
// `OPTION_TOOLTIP_*` for it to borrow; benilla's own text explains the restart.
@@ -3260,7 +3328,9 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
// lava, fog, daylight, Graphics Preset, Render Distance, ...) add 20. 78 -> 98.
// MONKEY (post / dither / fog / wet / wind / volumetric light): their rows add 17, and the
// Graphics page's resolution dropdown one more. 98 -> 116.
assert_eq!(checked, 116, "every tipped row carries a live key");
// GFX (anisotropic): the Graphics page's own anisotropy slider, seated where the reference's
// slider table already declared it. 116 -> 117.
assert_eq!(checked, 117, "every tipped row carries a live key");
assert_eq!(
untipped,
vec![
@@ -3364,7 +3434,8 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
// per-row; the total is here so a row losing its key is a failure rather than a smaller pass.
// MONKEY (graphics programme): the Advanced Graphics page's described rows add 19. 78 -> 97.
// MONKEY (post / dither / fog / wet / wind / volumetric light / resolution): 97 -> 116.
assert_eq!(raised, 116, "every row but Auto Loot raises a description");
// GFX (anisotropic): the Graphics page's anisotropy slider. 116 -> 117.
assert_eq!(raised, 117, "every row but Auto Loot raises a description");
}
/// 1.12's AdvancedOptionsCombatText box as saved-global rows: a click writes the global, never a