A wheel notch stepped from the easing scale, so notches did not accumulate
`ZoomIn`/`ZoomOut` walked the zoom ladder from `GetCanvasScale()` — the live value the smooth zoom is still easing — so the notch after the first re-read a scale that had barely moved, found the same rung still under it, and re-set the target it already had. The ease closes about 63% of the gap a second, which is slow enough that a player never gets a second rung. Driven at grom's own 2560x1440 with his addons loaded, over OpenMapClassic's 27-step ladder (0.0330 .. 2.3734): four wheel-down notches at the map's centre all set target 0.9030 — one rung — and two more at the far right set nothing. That is his "it will only scroll very little out … and will only very little scroll in period". `GetZoomStepScale()` steps from `targetScale` instead, so notches accumulate and either end of a long ladder is reachable. The same measurement rules out the position dependence he saw: the wheel's target over the centre and 19 px from the right edge is `WorldMapButton` both times, because `wheel_target` walks past frames that take no wheel the way `0x7664f0` does. `docs/ADDON_COMPAT.md` also records the two addon-side causes of "I can't see any buttons but I can use them", both fixed in `Addon_Ports/` rather than here: OpenMapClassic's rail tabs are never dressed because `mod.FaceSideTab` ships in a file the release omits, and the OpenMap tab's own icon is absent from this patch chain. And pfQuest's minimap icons now fade with the minimap. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
@@ -753,9 +753,25 @@ function MapCanvasScrollControllerMixin:SetZoomTarget(scale, x, y)
|
||||
end
|
||||
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
|
||||
-- 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
|
||||
-- (1.0000 -> 0.9030) and two more moved it none. Stepping from `targetScale` makes notches
|
||||
-- accumulate, which is both how it feels in Era and the only way either end of a 27-step
|
||||
-- ladder is reachable at all -- OpenMapClassic's runs 0.033 to 2.373, and its bottom rung is
|
||||
-- the whole stitched world.
|
||||
function MapCanvasScrollControllerMixin:GetZoomStepScale()
|
||||
-- Through `GetCanvasScale`, because it clamps every held scale onto the current ladder as a
|
||||
-- side effect, and a canvas addon can have replaced the ladder since the last notch.
|
||||
local live = self:GetCanvasScale()
|
||||
return self.targetScale or live
|
||||
end
|
||||
|
||||
function MapCanvasScrollControllerMixin:ZoomIn()
|
||||
local n = table.getn(self.zoomLevels)
|
||||
local current = self:GetCanvasScale()
|
||||
local current = self:GetZoomStepScale()
|
||||
for i = 1, n do
|
||||
if self.zoomLevels[i].scale > current + 0.0001 then
|
||||
self:SetZoomTarget(self.zoomLevels[i].scale)
|
||||
@@ -765,7 +781,7 @@ function MapCanvasScrollControllerMixin:ZoomIn()
|
||||
end
|
||||
|
||||
function MapCanvasScrollControllerMixin:ZoomOut()
|
||||
local current = self:GetCanvasScale()
|
||||
local current = self:GetZoomStepScale()
|
||||
for i = table.getn(self.zoomLevels), 1, -1 do
|
||||
if self.zoomLevels[i].scale < current - 0.0001 then
|
||||
self:SetZoomTarget(self.zoomLevels[i].scale)
|
||||
|
||||
@@ -480,3 +480,61 @@ fn the_canvas_container_is_the_windows_content_area_in_both_map_modes() {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// A wheel notch steps from where the view is **heading**, so notches accumulate while the
|
||||
/// smooth zoom is still easing toward the last one.
|
||||
///
|
||||
/// Before this, `ZoomIn`/`ZoomOut` read `GetCanvasScale()` — the live, easing value. The ease
|
||||
/// closes about 63% of the gap a second, so the notch after the first found the same rung still
|
||||
/// under it and re-set the target it already had: measured live at grom's window size with his
|
||||
/// addons, four wheel-down notches moved the ladder one step and two more moved it none, which
|
||||
/// is what "scroll only goes a little way out" is. The ladder here is synthetic because the
|
||||
/// rule has to hold for a canvas addon's own ladder, which is the only kind with enough rungs
|
||||
/// for the defect to show — `CreateZoomLevels` is the documented place to replace it.
|
||||
#[test]
|
||||
fn wheel_notches_accumulate_while_the_zoom_is_still_easing() {
|
||||
let Some(s) = harness() else { return };
|
||||
let seat = "local sc = WorldMapFrame.ScrollContainer \
|
||||
sc.zoomLevels = {} \
|
||||
for i = 1, 10 do sc.zoomLevels[i] = { scale = i, layerIndex = 1 } end \
|
||||
sc.currentScale, sc.targetScale, sc.zoomTarget = 5, 5, 5 ";
|
||||
let (a, b, c, live): (f64, f64, f64, f64) = s
|
||||
.eval(&format!(
|
||||
"{seat} \
|
||||
sc:ZoomOut() local a = sc.targetScale \
|
||||
sc:ZoomOut() local b = sc.targetScale \
|
||||
sc:ZoomOut() \
|
||||
return a, b, sc.targetScale, sc.currentScale"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
(a, b, c),
|
||||
(4.0, 3.0, 2.0),
|
||||
"three notches down the ladder must be three rungs, not one repeated"
|
||||
);
|
||||
assert_eq!(
|
||||
live, 5.0,
|
||||
"and none of it touched the easing scale — the OnUpdate owns that"
|
||||
);
|
||||
let (a, b, c): (f64, f64, f64) = s
|
||||
.eval(&format!(
|
||||
"{seat} \
|
||||
sc:ZoomIn() local a = sc.targetScale \
|
||||
sc:ZoomIn() local b = sc.targetScale \
|
||||
sc:ZoomIn() \
|
||||
return a, b, sc.targetScale"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!((a, b, c), (6.0, 7.0, 8.0), "and the same going in");
|
||||
// The step is still clamped to the ladder's own ends, which is what stops a held wheel
|
||||
// walking the target past a rung that exists.
|
||||
let (lo, hi): (f64, f64) = s
|
||||
.eval(&format!(
|
||||
"{seat} \
|
||||
for _ = 1, 20 do sc:ZoomOut() end local lo = sc.targetScale \
|
||||
for _ = 1, 40 do sc:ZoomIn() end \
|
||||
return lo, sc.targetScale"
|
||||
))
|
||||
.unwrap();
|
||||
assert_eq!((lo, hi), (1.0, 10.0), "the ladder's own ends hold");
|
||||
}
|
||||
|
||||
@@ -3262,3 +3262,113 @@ reads the live cursor. With a tick per move the same drag walks `0.5000,0.5000
|
||||
0.7582,0.3073 → 0.5080,0.5161`, and `0.7582,0.3073` is exactly what hand-driving produced. A
|
||||
gesture instrument that does not tick measures nothing: **the frame between the moves is the
|
||||
gesture.**
|
||||
|
||||
## The buttons you could click but not see, and the wheel that moved one rung
|
||||
|
||||
grom, 2026-11-04: *"Some of the buttons are still not working … I still can't see any buttons or
|
||||
settings, but I can find some of them invisible and use them … scroll is not working correctly,
|
||||
like it will only scroll very little out unless I point my mouse on the far right side of the map,
|
||||
and will only very little scroll in period."*
|
||||
|
||||
Three separate causes, two of them in the addon rather than here.
|
||||
|
||||
### The rail tabs were never dressed, because the file that dresses them is not in the release
|
||||
|
||||
`Map/Panel.lua`'s `RailTab()` builds a tab out of four textures — `bg`, `hover`, `selected` and
|
||||
`icon` — and gives none of them an anchor or a texture. The function that does both,
|
||||
`mod.FaceSideTab`, ships in `Map/Dungeons.lua`: a file this release of OpenMap Classic does not
|
||||
include and whose name does not appear in its `.toc`. Panel.lua guards the call
|
||||
(`if mod.FaceSideTab then`), so with `tabRail` on — which is what grom's saved settings carry —
|
||||
the rail is built, its tabs are sized, placed on the map's edge and take clicks and tooltips, and
|
||||
**draw nothing at all**.
|
||||
|
||||
Measured on the live interface, with a probe that prints each region's anchor count beside its
|
||||
rect:
|
||||
|
||||
```
|
||||
btn1 … size=40x42 points=1 RIGHT->WorldMapScrollContainer.BOTTOMRIGHT(+0,+65)
|
||||
r1 Texture layer=BACKGROUND shown=1 NIL-RECT points=0 tex=nil
|
||||
r2 Texture layer=HIGHLIGHT shown=1 NIL-RECT points=0 tex=nil
|
||||
r3 Texture layer=OVERLAY shown=0 NIL-RECT points=0 tex=nil
|
||||
r4 Texture layer=ARTWORK shown=1 NIL-RECT points=0 tex=nil
|
||||
```
|
||||
|
||||
`points=0` is the whole diagnosis: not an anchor that failed, an anchor that was never set. The
|
||||
button's own rect resolves, which is why the hit test finds it — **a frame can be in the hit chain
|
||||
and emit no quad at all**, and that pair is what "invisible but I can use it" means. A region dump
|
||||
that prints only `IsShown()` and the texture path cannot tell the two apart; print
|
||||
`GetNumPoints()` beside the rect.
|
||||
|
||||
The port supplies the missing function as `Map/SideTabs.lua` (one `.toc` line after
|
||||
`Map\Panel.lua`), in the addon's own visual language: `Tile()`'s boxed face, a one-pixel edge that
|
||||
`SetLit` tints, the action button's square highlight and the checked glow. `b.boxed` is set there
|
||||
rather than in `RailTab` so `SetLit` only reaches for a backdrop once there is one.
|
||||
|
||||
### An icon the client does not have reads back nil, and that is the feature test
|
||||
|
||||
The OpenMap tab itself stayed a blank box after that, because it asks for
|
||||
`Interface\Icons\Achievement_Zone_EasternKingdoms_01` — a retail icon, **ABSENT from this whole
|
||||
patch chain** (`chain_report`: 0 copies). The addon's own `Look()` has a fallback path, but it
|
||||
keys on `SetTexture` answering `false`, which is retail's contract; ours answers `1` or **nil**,
|
||||
which is 1.12's own (`0x79bb40`, `(nil) | (number)`, and `reference/1.12-shapes.tsv` agrees).
|
||||
|
||||
What settles it without a list of which icons exist: **a texture whose file did not load reads
|
||||
back `GetTexture() == nil`**, live, because `Model::texture_probe` judges the path at
|
||||
`SetTexture` time. So `if not b.icon:GetTexture() then b.icon:SetTexture(FALLBACK) end` is a tab
|
||||
asking about itself, and it covers any favourite whose icon is as retail as the OpenMap tab's.
|
||||
Note that this only holds where a probe is installed: offline (the harness) `SetTexture` answers
|
||||
nil for everything and the read-back keeps the path, so a fallback driven off *the return value*
|
||||
would replace every icon on the rail. The read-back does not have that failure mode.
|
||||
|
||||
### The wheel: a notch stepped from the easing scale, so notches did not accumulate
|
||||
|
||||
`ZoomIn`/`ZoomOut` walked the ladder from `GetCanvasScale()` — the **live, easing** value.
|
||||
`currentScale` closes about 63% of the gap a second (`SCALE_RATE`), so the notch after the first
|
||||
re-read a scale that had barely moved, found the same rung still under it, and re-set the target
|
||||
it already had. Driven at grom's own window size with his addons loaded:
|
||||
|
||||
```
|
||||
ladder steps=27 lo=0.0330 hi=2.3734 current=1.0000 target=1.0000
|
||||
wheel -1 ×4 at the map's centre → target 0.9030, 0.9030, 0.9030, 0.9030 (ONE rung)
|
||||
wheel -1 ×2 at the far right → target 0.9030, 0.9030 (none)
|
||||
```
|
||||
|
||||
`GetZoomStepScale()` now steps from `targetScale`, so notches accumulate and either end of a
|
||||
27-step ladder is reachable. Covered by
|
||||
`map_canvas_tests::wheel_notches_accumulate_while_the_zoom_is_still_easing`, which reads
|
||||
`(4.0, 4.0, 4.0)` without the fix and `(4.0, 3.0, 2.0)` with it.
|
||||
|
||||
The same measurement **rules out the position dependence**: the wheel's target over the map's
|
||||
centre and 19 px from its right edge is the same frame both times (`WorldMapButton`, level 3,
|
||||
topmost wheel-enabled hit), because `wheel_target` walks past frames that take no wheel the way
|
||||
the reference's `0x7664f0` does. What looked like "it only works on the far right" was the one
|
||||
rung arriving late.
|
||||
|
||||
### pfQuest's minimap icons did not fade with the minimap
|
||||
|
||||
1.12's `SetAlpha` (`0x76a690`) writes the raw value to the frame **and every descendant**, with no
|
||||
draw-time product, so pfUI's fade on the minimap holder does reach pfQuest's pins — they are
|
||||
children of `Minimap`, which `pfQuest/map.lua:232` takes as its draw layer. But pfQuest reasserts
|
||||
its own alpha on every pin on every update (`NodeAnimate` eases each toward `defalpha`), so the
|
||||
fade is overwritten within a frame.
|
||||
|
||||
`pfUI/modules/autofade.lua` now ties them the way it already ties the unit frames — *through* the
|
||||
addon rather than against it. A pin's `SetAlpha` records what pfQuest asked for and applies
|
||||
`asked × factor`; its `GetAlpha` answers what pfQuest asked for, so NodeAnimate's easing runs
|
||||
entirely in pfQuest's own numbers and never sees the factor. The factor is `pfUI.minimap`'s own
|
||||
live alpha, which is what makes the tie to the minimap structural rather than configured:
|
||||
|
||||
```
|
||||
minimap 1.00 → pin wants 1.00 raw 1.00
|
||||
minimap 0.25 → pin wants 1.00 raw 0.25
|
||||
… 40 rounds of pfQuest's own Animate(.., 1, ..) → wants 1.000 raw 0.250 (no fight, no drift)
|
||||
minimap 1.00 → pin wants 1.00 raw 1.00
|
||||
```
|
||||
|
||||
### Still open: the zone art at middle zoom
|
||||
|
||||
Not reproduced. `unexploredMode = "reveal"` is **not** the cause — the same view captured with
|
||||
the fog `default` and `reveal` is pixel-indistinguishable — and switching `lod`, `close` and
|
||||
`zoneart` off one at a time changed nothing in a capture either (those take a rebuild the slash
|
||||
command alone does not force). What a capture cannot reproduce is which zones the player has
|
||||
actually explored, and that is the one input the complaint turns on.
|
||||
|
||||
Reference in New Issue
Block a user