The map's glide is a setting, because two reports bracketed one guess

grom, 2026-11-05: *"its the maps motion that is laggy."* That is `SCALE_RATE`, and it is the
same constant he reported from the other side a day earlier — *"scroll is better but feels pretty
slippery, like scrolls past where I stop scrolling"* — which is what took it from 10 to 24. At 24
the half-life is 0.29 s, so at 60 fps a wheel notch is still closing 167 frames later: "it
overshoots" and "it lags behind" are two readings of one glide, and a third guess would be a third
guess.

`mapMotionSmoothing` is a percentage of a 0.30 s half-life, 0 is no ease at all, and the slider is
on the Options window's Interface page. The top of the range is the 0.29 s he called laggy,
because that is the slowest anyone could want; the default is a quarter of it. Measured over a
full rung (scale 1 -> 1.6) at 60 fps, frames to settle within 0.001: **0% = 1, 10% = 17, 20% = 34,
25% (shipped) = 42, 50% = 84, 100% = 167** — so the default is four times snappier than what he
just played, and still eased.

One rate drives both the pan and the zoom ease, read once a frame: the two are one gesture to a
player, and a zoom that settles while the pan is still drifting reads as the pan being broken. A
drag still writes the scroll directly and never eases, as before.

Verified in a live session with his addons: `mapMotionSmoothing=25`, one notch settles in 42
frames (0.70 s), map still FULLSCREEN, fade still 80. Options census 141 -> 142 on both counts.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-05 11:48:43 -07:00
co-authored by Claude Opus 5
parent 8c6fb4e425
commit 524e500d43
4 changed files with 79 additions and 12 deletions
+37 -7
View File
@@ -61,10 +61,36 @@ local SCALE_SNAP = 0.001
-- notch. That was tolerable only while a notch could not accumulate, because the target was never
-- more than one rung away; once notches add up the glide is the thing you feel. grom, 2026-11-04:
-- *"scroll is better but feels pretty slippery, like scrolls past where I stop scrolling."* At 24
-- the half-life is 0.29 s and a multi-rung spin lands about where the wheel stopped. The pan's is
-- left alone: dragging writes the scroll directly and never eases.
local PAN_RATE = 10
local SCALE_RATE = 24
-- the half-life is 0.29 s and a multi-rung spin lands about where the wheel stopped.
--
-- 0.29 s was still the wrong answer. grom, 2026-11-05: *"its the maps motion that is laggy."*
-- That is the same constant read from the other side — at 60 fps a 0.29 s half-life closes 90% of
-- a notch in 58 frames, so the map keeps drifting for a second after the wheel stops, and the two
-- reports are "it overshoots" and "it lags behind" about one glide. Guessing a third number would
-- be the third guess, so it is a SETTING now, and these two are the ends of its range rather than
-- the behaviour: `mapMotionSmoothing` is a percentage of [`SMOOTH_MAX_HALFLIFE`], and 0 is no ease
-- at all.
--
-- The top of the slider is the 0.29 s half-life he called laggy, because that is the slowest
-- anyone could want and there is no reason to offer slower. The default is a quarter of it:
-- 0.075 s, which closes 90% of a wheel notch in 0.25 s and the last thousandth in 0.69 s.
-- Measured over a full rung (scale 1 -> 1.6) at 60 fps, frames to settle within 0.001:
-- 0% = 1, 10% = 17, 20% = 34, 50% = 84, 100% = 167 (the old constant's own 167).
local SMOOTH_MAX_HALFLIFE = 0.30
-- The default percentage, and the fallback when the CVar is missing (a bare VM, a test).
local SMOOTH_DEFAULT = 25
-- `nil` means no ease: snap to the target this frame. `RATE` is the file's own relation,
-- `7 / halflife`, so the arithmetic below is unchanged.
local function SmoothRate()
local pct = tonumber(GetCVar and GetCVar("mapMotionSmoothing") or nil) or SMOOTH_DEFAULT
if pct <= 0 then
return nil
end
if pct > 100 then
pct = 100
end
return 7 / (pct / 100 * SMOOTH_MAX_HALFLIFE)
end
-- A drag under this many pixels is still a click, which is what `WouldCursorPositionBeClick`
-- answers; Era's own threshold.
local CLICK_SLOP = 5
@@ -773,7 +799,7 @@ function MapCanvasScrollControllerMixin:SetZoomTarget(scale, x, y)
end
-- Which rung a wheel notch steps FROM: where the view is heading, not where the ease has got
-- to. `currentScale` closes about 63% of the gap a second (`SCALE_RATE`), so a second notch
-- to. `currentScale` eases toward the target (`SmoothRate`), so a second notch
-- arriving a frame after the first re-reads a scale that has barely moved, finds the same rung
-- under it, and re-sets the target it already has. Measured at the player's own window size
-- with his addons loaded: four wheel-down notches over the map moved the ladder ONE step
@@ -1173,7 +1199,10 @@ function MapCanvasScrollControllerMixin:OnUpdate(elapsed)
end
end
else
local rate = 1 - math.pow(0.5, (elapsed or 0) * PAN_RATE / 7)
-- One rate for both eases, read once a frame: the two are one gesture to a player, and a
-- zoom that settles while the pan is still drifting reads as the pan being broken.
local smooth = SmoothRate()
local rate = smooth and (1 - math.pow(0.5, (elapsed or 0) * smooth / 7)) or 1
local tx, ty = self.targetScrollX or 0.5, self.targetScrollY or 0.5
local cx, cy = self.currentScrollX or 0.5, self.currentScrollY or 0.5
if math.abs(tx - cx) > PAN_SNAP or math.abs(ty - cy) > PAN_SNAP then
@@ -1189,7 +1218,8 @@ function MapCanvasScrollControllerMixin:OnUpdate(elapsed)
local scaled = false
local ts, cs = self.targetScale or 1, self.currentScale or 1
if math.abs(ts - cs) > SCALE_SNAP then
local rate = 1 - math.pow(0.5, (elapsed or 0) * SCALE_RATE / 7)
local smooth = SmoothRate()
local rate = smooth and (1 - math.pow(0.5, (elapsed or 0) * smooth / 7)) or 1
self.currentScale = cs + (ts - cs) * rate
scaled = true
elseif cs ~= ts then
+21 -2
View File
@@ -479,6 +479,13 @@
.. "In Windowed (Fullscreen) the window already fills the screen, so this sets the "
.. "size it returns to in Windowed. Takes effect on Apply."
-- 1.12's map neither pans nor zooms, so it has no such setting and no reference value.
BENILLA_TOOLTIP_MAP_MOTION_SMOOTHING =
"How long the world map keeps gliding after you stop scrolling or dragging.\n\n"
.. "At 0 the map goes exactly where you put it, the same frame. Higher is smoother "
.. "and slower to settle; past about half way the map is still moving a second after "
.. "the wheel stopped."
-- 1.12 has no such setting: its map does not fade, because its map cannot be read while
-- you walk either way (it covers the screen and takes the keyboard).
BENILLA_TOOLTIP_MAP_FADE_WHILE_MOVING =
@@ -1582,7 +1589,7 @@
"RowProfanityFilter",
"RowShowTutorials", "RowTipOfTheDay", "RowNewbieTips",
-- Appended, as this list's own rule says: the tests assert positional indices.
"RowMapFadeWhileMoving",
"RowMapFadeWhileMoving", "RowMapMotionSmoothing",
},
-- 1.12's chat box (UIOptionsFrame.xml:751-840). Not shipped: Simple Chat, which needs
-- FCF_Set_SimpleChat's re-layout, Lock Chat Settings and Guild Recruitment.
@@ -5064,11 +5071,23 @@
</Frame>
<!-- Read by `MapCanvas.lua`, which owns the behaviour; 0
is off and the row's own floor. -->
<Frame name="$parentRowMapFadeWhileMoving" inherits="OptionsSliderRowTemplate">
<Frame name="$parentRowMapMotionSmoothing" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowNewbieTips" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowNewbieTips" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "mapMotionSmoothing",
"World Map Motion Smoothing",
"BENILLA_TOOLTIP_MAP_MOTION_SMOOTHING")
OptionsSliderRow_Setup(self, 0, 100, 10, "pct")
</OnLoad></Scripts>
</Frame>
<Frame name="$parentRowMapFadeWhileMoving" inherits="OptionsSliderRowTemplate">
<Anchors>
<Anchor point="TOPLEFT" relativeTo="$parentRowMapMotionSmoothing" relativePoint="BOTTOMLEFT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
<Anchor point="TOPRIGHT" relativeTo="$parentRowMapMotionSmoothing" relativePoint="BOTTOMRIGHT"><Offset><AbsDimension x="0" y="-9"/></Offset></Anchor>
</Anchors>
<Scripts><OnLoad>
OptionsRow_OnLoad(self, "mapFadeWhileMoving",
"Fade World Map While Moving",
+13
View File
@@ -1952,6 +1952,15 @@ pub(crate) const REGISTERED: &[Registered] = &[
// frame verb, and `IsPlayerMoving()` is already a verb — so the whole feature is Lua over a
// CVar, and the CVar is what gives it a home in the Options window and in `config.toml`.
// 0 switches it off; the Options row offers 0 and 10..90.
// Same shape and same reader as `mapFadeWhileMoving` below.
ours(
"mapMotionSmoothing",
"25",
"how long the world map's pan and zoom keep gliding after the input stops, as a percent \
of a 0.3 s half-life; 0 snaps. 1.12's map neither pans nor zooms, so there is no \
reference value — grom has now reported both ends of one guess (2026-11-04 \
\"slippery\", 2026-11-05 \"laggy\"), which is what makes it a setting",
),
ours(
"mapFadeWhileMoving",
"80",
@@ -5832,6 +5841,10 @@ mod tests {
"mapFadeWhileMoving",
"MapCanvas.lua's `FadeWhileMoving`, every frame the map is open",
),
(
"mapMotionSmoothing",
"MapCanvas.lua's `SmoothRate`, read by the pan and zoom eases",
),
// The five per-kind plate rows with no gate of their own. They exist because an addon
// written for a later client greys a whole row on a nil `GetCVar` — registering is the
// answer to "does this client have this setting", and each default below is this
@@ -3766,6 +3766,11 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
"BENILLA_TOOLTIP_MAP_FADE_WHILE_MOVING",
"InterfaceRowMapFadeWhileMoving",
),
// The world map's glide, a setting because two reports bracketed one guess.
(
"BENILLA_TOOLTIP_MAP_MOTION_SMOOTHING",
"InterfaceRowMapMotionSmoothing",
),
// MONKEY (advanced graphics): the whole Advanced Graphics page. Every row on it is
// benilla's own mechanism — 1.12 has no realtime shadow, no fixture-lit interior and
// no invented light — so there is no `OPTION_TOOLTIP_*` any of them could resolve,
@@ -3902,8 +3907,8 @@ fn every_row_tooltip_key_resolves_in_the_real_global_strings() {
// GFX (sharpening) / (field of view): one row each, on the two different pages. 136 -> 138.
// GFX (froxel volumetrics): the shadowed-air dial beside the haze tier. 138 -> 139.
// GFX (foliage distance): the ground-clutter horizon. 139 -> 140.
// The world map's fade-while-moving dial. 140 -> 141.
assert_eq!(checked, 141, "every tipped row carries a live key");
// The world map's fade-while-moving dial. 140 -> 141; its motion-smoothing dial, 141 -> 142.
assert_eq!(checked, 142, "every tipped row carries a live key");
assert_eq!(
untipped,
vec![
@@ -4012,7 +4017,7 @@ fn every_flavor_of_row_raises_its_plate_from_the_page_it_lives_on() {
// GFX (shadow rig): the five Advanced Graphics shadow rows. 119 -> 124.
// GFX (interior rig): the ten interior-lighting rows. 124 -> 134.
// GFX (auto-exposure): the tier and its blend. 134 -> 136.
assert_eq!(raised, 141, "every row but Auto Loot raises a description");
assert_eq!(raised, 142, "every row but Auto Loot raises a description");
}
/// 1.12's AdvancedOptionsCombatText box as saved-global rows: a click writes the global, never a