Commit Graph
13 Commits
Author SHA1 Message Date
gromandClaude Opus 5 524e500d43 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
2026-11-05 11:48:43 -07:00
gromandClaude Opus 5 082dd2ed0f The map's arrow, its stratum, and a fade while you walk
Three of grom's 2026-11-05 reports about the built-in map, all measured.

**The player arrow was the one piece of 1.12's map content left outside the canvas.**
*"my player arrow goes to the wrong spot at any zoom level, this needs to be accurate all
of the time."* `WorldMapFrame_OnLoad` builds it with
`CreateWorldMapArrowFrame(WorldMapFrame)` (`WorldMapFrame.lua:19`), so its parent is the
WINDOW, while `MapCanvas.lua` reparents everything it is anchored against into the canvas.
`SetPoint`'s offsets are in the positioned frame's own units, so the offsets the stock
update computes — `playerX * GetWidth()`, times `WorldMapDetailFrame:GetScale()`, which
`SeatOnCanvas` pins at 1 because the zoom lives one level up on `ScrollContainer.Child` —
are short by exactly the canvas zoom. Measured on a 1600x900 harness with the view centred
on the player: exact at zoom 1, 250.5 x 501.0 screen px out at zoom 2.

`WorldMapPlayer` — the invisible mouseover rect for the same spot, from the same two
FrameXML lines — has always been right, because 1.12's XML nests it under `WorldMapButton`,
which IS in the canvas. That makes it the oracle the test uses rather than a recomputed
expectation. Not reparented like the detail frame and the button: the arrow is a size-less
`Model` whose rect is the file's bounding box, so inside the canvas it would grow with the
zoom, and no frame scale fixes the size without undoing the position — both read the same
effective scale. The cost of staying outside is clipping, hence an explicit bounds test,
which is a hide the stock code never needed because a map that cannot pan cannot put the
player off-screen.

That re-seat runs every frame the map is open, so nothing in it may force a layout resolve:
the art's size is read off `WorldMapButton` (explicitly sized) rather than the detail frame
(`SetAllPoints`, so derived), and the bounds test is arithmetic on the controller's own
normalized scroll rather than a rect comparison — `GetLeft` on a dirty tree measures
30-370 us against 0.42 us settled, and a pan writes the scroll every frame.

While in there: `InstantPanAndZoom` set `Child:SetScale` from its raw argument while
`GetCanvasScale` clamps to the zoom ladder, so a scale past the ceiling drew the art at one
scale with every extent, pan clamp and pin placement computed for another. It clamps first
now, as the ease path already did by construction.

**The window fell out of its own stratum every time it opened.** *"the map needs to be on a
higher level as I can see my unitframe over it."* `WorldMapFrame.xml:5` declares
`frameStrata="FULLSCREEN"`; measured FULLSCREEN with no addons and MEDIUM at level 0 with
his, which is the bottom of the bucket pfUI's unit frames live in
(`pfUI/api/unitframes.lua:413`). Five visible frames drew over the map rect.

No addon does it. The new `$WOW_WATCH_STRATA=<FrameName>` instrument — every
`SetFrameStrata` AND every `SetParent` on a named frame, with the Lua caller site — found
one FULLSCREEN from the XML loader and then `WorldMapFrame:SetParent(UIParent)`, from the
realm's own `WorldMapFrame_Minimize` (`WorldMapFrame.lua:183`) by way of
`WorldMapFrame_OnEvent`, on the open path. A reparent forces the moved frame into its new
parent's stratum (`0x76ab5a`, pinned off the binary by `script::tests::setparent`), and
`UIParent` is MEDIUM. The reparent is right and stays; the window's own declared stratum is
re-asserted on `SeatLevels` instead, which already re-seats the frame levels on every show
for a cousin reason, plus hooks on Minimize/Maximize since the minimize runs while the
window is up. After: FULLSCREEN, 0 frames over the map.

**And the fade.** *"It would also be nice if the whole map window went to 80%
transparentcy when the player is moving."* `mapFadeWhileMoving`, percent, 0 off, default 80,
with a slider on the Options window's Interface page. Written as a FACTOR of whatever alpha
the window already has, because that alpha belongs to somebody else — pfUI sets it from its
own config and offers shift-wheel over the map — so the base is re-read whenever the live
alpha is not the one this last wrote, which is another writer's signature; their setting is
what gets faded, and closing the map restores it. `GetSpeed()` is the movement test: ours,
always present, and 0 standing still by construction, off the same move flags the engine
answers `IsSwimming` and `IsMounted` from.

Also `$WOW_CAPTURE_EVAL`/`_SETUP` take a path ending in `.lua` as the chunk. A probe worth
writing is longer than a shell argument survives, and a quoting error in a probe reads
exactly like the thing being measured not being there.

Verified: `the_player_arrow_tracks_the_hit_area_at_every_canvas_zoom` walks zoom 1, 1.5,
2.5 and 4 asserting the arrow and the hit area agree within a screen pixel, and that the
canvas applied the zoom asked for. In a real session with his addons: FULLSCREEN and 0
visible frames over the map (was MEDIUM/0 and 5). Options census 140 -> 141 on both counts.
benilla-app at its recorded baseline, benilla-ui 2128/0, clippy and the two lints clean.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-05 10:38:31 -07:00
gromandClaude Opus 5 841fd1a81f Two map verbs disagreed about where the player was
`UnitState::position` was fed from a streamed object's `Transform` read raw, and a
`Transform` is Bevy's frame (`wow_to_bevy` is `(-y, z, -x)`): the two map axes arrived
swapped and negated and the third was the player's height. Three compat verbs answer
from that field — SuperWoW's `UnitPosition`, UnitXP_SP3's `getUnitPosition`, and
`C_Map.GetPlayerMapPosition` — while 1.12's own `GetPlayerMapPosition` reads the
per-frame world-map feed, which converts. So the client sat in a state no real client
can be in: the 1.12 verb placing the arrow and the modern verb saying the player was on
no map at all.

Against a zone's few-thousand-yard rect the height lands outside 0..1, so the modern
verb answered nil — which is OpenMapClassic's "You aren't on the world map here" and
`Map/StockArrow.lua` declining to place anything, leaving 1.12's wrong placement
standing. Against a continent's sixteen thousand it lands inside, and answers a
plausible wrong fraction. Both of grom's reports, one cause. Distance was the one read
this never broke, the transform being rigid.

Measured in a capture with the player at Northshire: `UnitPosition` now answers
(-8980, -160, 110), `C_Map.GetPlayerMapPosition(1415)` and the 1.12 verb agree to seven
places, Elwynn answers a fraction and Dun Morogh correctly answers nil; and with the
view centred on the player's own world point the arrow lands on the container's centre
to a third of a pixel.

A capture could not have shown any of this — its synthetic player carried no position,
so every reader answered nil there. `demo_unit_feed` now publishes the capture's own
parking spot, at the cost of one ambiguous pair written up at the ceiling.

Also here, from the same play test:

  * A canvas zoom now tells the two 1.12 frames their scale moved, with their own scale,
    which is the only way a 1.12 map addon can hear it. pfQuest counter-scales its pins
    inside that hook; the canvas scales its child, so the hook never fired and the pins
    kept the size they were built at. Measured 0 -> 24 `pfMap.ResizeNodes` over one
    glide from 8x to 30x.

  * `CLASSIC_API_VERSION` says 1.18.0, at grom's ask. The DLL beside the real client
    reads 1.15.0.0 and nothing in his folder gates above pfUI's 11400 nag threshold, so
    the trade is the one that constant has always named and the floor is unchanged.

The addon-side half is in `Addon_Ports/openmap_port`: `unexploredMode` defaults to
`reveal`, with the one-shot migration this addon already uses for the kind. A 1.12 zone
sheet is blank parchment and every scrap of terrain is an overlay the client hands out
only where explored, while a city sheet has no overlays at all — which is the whole of
"it will only paint the actual map art for cities".

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-04 14:17:39 -07:00
gromandClaude Opus 5 d5beabab42 The map's mouse surface shrank with the zoom, and four things fell through the hole
The canvas gave the map no mouse surface of its own, on the reasoning that 1.12's
`WorldMapButton` covers the same rect and sits above it. The button is pinned INSIDE the canvas
child, so it scales with it: measured at the world view with OpenMapClassic driving, at grom's
own 2560x1440, a 23x16 px button inside a 701x468 px window. Every press on the other 99%
reached the next frame that takes the mouse — `WorldMapFrame`, which pfUI makes a
`RegisterForDrag("LeftButton")` handle for the whole window. So dragging the map moved the
window, and a click never reached `ProcessCanvasClickHandlers`, which is the only way a map pin
is ever dropped.

The container takes the mouse and the wheel now and drives the same controller. The frame level
mattered as much: the hit sweep probes one `(strata, level)` plane in link order and a parent is
linked before its children (`0x764aa0`), so a container tied with `WorldMapFrame` still lost
every press to its own parent. It is re-seated to `map:GetFrameLevel() + 1` on every show,
because the window is level 0 while the canvas builds and level 1 once `ShowUIPanel` has raised
it.

With the click arriving, a pin still stored nothing: `Map/Pins.lua:57` trims the note with
`strtrim`, which was deliberately absent. `script/stdlib.rs` still refuses it on the 1.12
surface, for the reason written there; this build claims ClassicAPI, whose client does have it,
so it lands in `compat/classic_api/string_trim.rs` with both spellings. Eight bare call sites in
the corpus, one of them in pfUI's search box on every keystroke.

`SCALE_RATE` 10 -> 24. A 0.7 s half-life was tolerable only while a wheel notch could not
accumulate; now that they add up the glide is what you feel ("scrolls past where I stop
scrolling").

`BenillaMapTextures(uiMapID)` is `GetExploredMapTextures` without the explored filter. Reveal
mode draws unexplored overlays out of a retail FileDataID table this client resolves none of —
captured with the fog default and with reveal, the same view came back pixel-identical, 0 by
`compare -metric AE`. The overlays were never missing, only their names: `Map/RevealFromClient.lua`
in `Addon_Ports/openmap_port` rebuilds the table from this, with paths.

Also in the port, not here: the arrow is placed through OpenMap's own coordinates, because
1.12 measures `GetPlayerMapPosition` against Blizzard's world and continent art and the stitched
map is a different projection.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-04 09:34:00 -07:00
gromandClaude Opus 5 c92f078c39 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
2026-11-04 07:53:50 -07:00
gromandClaude Opus 5 3e8c995e27 map: clamp the canvas onto whatever zoom ladder is in force
CreateZoomLevels is the extension point a canvas addon replaces
wholesale, and OnLoad seeds a scale (1) before any addon has run.
OpenMapClassic's ladder is 27 steps from 0.0125 to 0.899 -- its whole
range below this file's own 1, because its stitched world is reached by
zooming out past a single zone -- so the canvas sat above its own
ceiling, and ZoomIn, which walks the ladder by value, found no step at
all above it.

ClampZoomToLevels is called from GetCanvasScale rather than from
CreateZoomLevels, because CreateZoomLevels is exactly the function the
addon will not have.

Also records where OpenMapClassic stands, measured: 808 textures in the
canvas subtree, 166 placed, spanning ~4600x3270 units of a 1002x668
canvas, with a ladder floor implying a world ~80000 units wide. The Era
map tree under it answers correctly; GetMapArtLayers answering one
constant for every map is the thread to pull next.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-03 15:05:14 -07:00
gromandClaude Opus 5 abb4dc1dd3 ui: a Slider's thumb had no rect, so no scrollbar could be skinned or seen
"Half of the info on the right is out of the frame" was a scroll frame with
699 of content in a 451 window, a scroll range of 228, and a scrollbar
with nothing on it. The thumb's rect was only ever computed inside the
draw pass and never published: the region sweep skips anchor-less regions
and a thumb is anchor-less by construction, because its slider places it.

That is invisible until something anchors to the thumb, and pfUI does --
SkinScrollbar clears the stock knob and hangs its own off the thumb's two
corners, so every pfUI-skinned scrollbar in the interface was a bare
track, which is a scroll frame that silently cannot be scrolled.

The thumb is on the layout roster now, survives the unanchored prune, and
carries its slider's (vertical, fraction) in its node hash -- without the
last one a named touch moved no hash and seeded nothing, which
WOW_LAYOUT_VERIFY caught as an incremental pass disagreeing with a full
derivation. The draw pass keeps computing its own rect: extract takes
&self and cannot resolve, so a SetValue with no resolve behind it still
has to draw where the value now is.

map: the window has two layouts and the canvas only knew the maximized one

Pressing maximize left the art a 1002x668 patch in a full-screen window
and minimize could not put it back, because the container was anchored
once at build to WorldMapPositioningGuide TOP -502,-69 -- 1.12's
maximized anchor against a fixed 1024x768 guide. Read as insets instead
(15/4/33/20 windowed, 10/12/69/31 maximized) the same numbers reproduce
1.12 at 1.12's sizes and fit any other window, which is also the left-edge
bleed: -502 against a guide stretched to pfUI's 1017-wide window starts
the art 6.5 px in, not 10.

The zoom ladder's floor follows: it was the constant 1, true only while
the container was the art's own size, and is the measured fit now. A view
outside a rebuilt ladder is pulled onto its nearest end, since ZoomIn and
ZoomOut walk it by value -- OpenMapClassic's whole ladder sits below ours.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-03 14:27:51 -07:00
gromandClaude Opus 5 41512a04ef map: 1002x668 is a contract, and the canvas counted the windowed scale twice
Every 1.12 map addon places its art in absolute pixels against
WorldMapDetailFrame's 1002x668 — pfUI's reveal does it one SetPoint per
tile — so that size is a contract, not a size. 1.12's windowed mode puts
WORLDMAP_WINDOWED_SCALE (0.7) on the frame and on WorldMapButton; under
the canvas the container already carries the window, so that scale was a
second authority on the same rect and, with the frame SetAllPoints to the
canvas, came back as unit size: 1002 / 0.7 = 1431. Everything absolute
inside it landed at 0.700 of where it belonged, which is grom's
screenshot drawing Coldridge Valley twice, and the mouse surface kept its
XML 1002x668 beside 1431 units of art, so clicks read 70% of the map.

SeatOnCanvas makes the canvas the only authority: both frames to scale 1,
the frame filling the canvas, the button given the art's rect — explicitly
sized, because pfUI reads WorldMapButton:GetSize() to size the whole map
window. Re-run after Minimize/Maximize and on every canvas resize.

Rescaling the children this file knows the names of could never have
fixed it; the children it does not know the names of are the point.

saved-variables.lua now goes through saved_home, so a capture with
$WOW_CAPTURE_SAVED opens the player's windowed map — the only mode the
bug exists in, and the reason neither harness had ever seen it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-03 13:03:17 -07:00
gromandClaude Opus 5 d11b9ac3ae map: the art was scaled by the canvas while living in the detail frame
Grom's report was "textures are not where they should be", and still wrong with OpenMapClassic
switched off — which is what made it ours rather than the addon's. His own session log had both
states 1.4 s apart, same map, same frame: detail 1431x954 with tiles 366x366 (correct, 1431/1002 =
1.428), then the same frame with tiles back at the XML's raw 256x256.

`ScaleDetailArt` was being handed the CANVAS's size by `OnCanvasSizeChanged`, on the assumption
that the detail frame is `SetAllPoints` to the canvas. 1.12's own windowed mode breaks that:
`WorldMapFrame_Minimize` puts `WORLDMAP_WINDOWED_SCALE` (0.7) on `WorldMapDetailFrame` and
`WorldMapButton` and insets the frame to TOPLEFT 15,-33, so the same screen area is a different
number of units in each — his trace reads `detail 1431x954 scale 0.56` inside `WorldMapCanvas
1002x668 scale 0.80`. Scaled by the canvas's number every tile came out 1.43x too small: the art
covered 1024x768 of a 1431x954 frame, the rest bare parchment, and the exploration overlays scaled
to match landed nowhere near their terrain.

The detail frame's own size is the authority, since the tiles are its children. `GetLeft()` first
to settle the layout, because that size is derived and an unforced read answers the size from
before the last change; the caller's w,h stay as the fallback for what cannot be measured.

Reproduced and controlled in the harness with the windowed scale applied as the stock code applies
it: tile 362 where 362 is wanted, against the control's 256 — the number his log recorded. Two
earlier attempts are written up in docs/ADDON_COMPAT.md as dead ends, because both looked right:
caching the canvas size prefers the wrong quantity, and converting it by the effective-scale ratio
gives 366 against a true 362, since Minimize also insets the frame.

`$WOW_CAPTURE_SAVED` now reaches the enable-state file too, so a capture reproduces which addons
the player has turned off — without which "I turned it off and it is still wrong" cannot be
answered offline at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-03 10:54:54 -07:00
gromandClaude Opus 5 5e2f1f3ac8 ui: SetClipsChildren, the verb an addon asked for by name and was refused in silence
Era's map canvas is built on one verb this client did not have. Its `ScrollContainer` is a plain
Frame with `SetClipsChildren(true)`, and everything below it is cropped to that rect.
`assets/ui/MapCanvas.lua` substituted a real ScrollFrame, because this client crops a ScrollFrame's
scroll child and every descendant of it — which covers the canvas and nothing parented to the
container beside it. On Era that distinction does not exist, so an addon has no reason to respect
it.

OpenMapClassic parents its pin layer exactly there and asks for the verb by name, behind an `if`
(`Map/Stitch.lua:1070`), so the absence was not a raise. Measured on grom's own config with the map
open: the container sits at L=1151 R=2153 and its seventeen direct children span L=-765 R=2153 —
about 1900 units off the left edge, cropped by nothing.

Implemented as a frame flag with `SetClipsChildren`/`DoesClipChildren` and the `clipChildren` XML
attribute; `effective_clip` now intersects the rect of every ancestor that sets it alongside the
ScrollFrame rule, so the two nest and compose. An ancestor rather than the frame itself: the verb
clips a frame's children, and stock art overhangs its own frame on purpose. `MapCanvas.lua` sets it
on the container too, so a layer parked there is cropped whether or not its addon thought to ask.

Not claimed as the whole of grom's map report: the screenshot behind it has not been reproduced
offline, because a capture's view is not the view his session was in. This is the part that was
provably drawing uncropped.

Also here, both from the same play-test:

- `C_Spell.CancelSpellByID` answered with `SpellStopCasting` only. pfUI's buff bar cancels with it
  on a right click where 1.12's calls `CancelPlayerBuff`, so with nothing in flight the click did
  nothing and said nothing — grom had never been able to right-click a buff off on this client. It
  now finds the id among the player's own auras and queues it on the same CMSG_CANCEL_AURA path,
  behind the same `cancel_authorized` gate, and falls through to the cast only when there is no
  such aura.

- `$WOW_CAPTURE_SAVED=<Realm>-<Character>` points a capture at a player's own saved variables. A
  capture is hermetic by design, but with addons and no saved variables it renders an interface
  nobody is configured to be looking at — which is why the world map came out correct in a capture
  while being wrong on his screen. It doubles as the identity because a capture never logs in and
  `pfUI_config` is SavedVariablesPerCharacter.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-03 09:46:32 -07:00
gromandClaude Opus 5 7f97777597 ui: the stock map art has to follow the detail frame it is drawn into
`WOW_MAP_TRACE=1` named it on grom's first run, in one line:

    detail 1431x954 @scale 0.70 | 1 256x256 @(10,1122) | 2 256x256 @(266,1122) | ...

1.12's `WorldMapDetailFrame` is always 1002x668, so everything drawn into it is placed in absolute
pixels against that: twelve 256x256 tiles anchor-chained from its TOPLEFT, each exploration overlay
at the offset `WorldMapOverlay.dbc` gives it. The canvas breaks that the moment it sizes the frame
to anything else, and it does — `OnCanvasSizeChanged` falls back to the container's own rect
whenever it has no art layer to read, which is every moment the canvas has no `mapID`, including
the world-view blip the same log shows on every map open.

So: a 1024x768 sheet of art pinned to the top-left of a 1431x954 frame, covering 72% of the width
and 80% of the height, with the rest of the map drawing nothing. That is the grey he reported,
with the art magnified relative to what was left of the frame.

The art is now rescaled to whatever size the frame has. The tiles need only their size set, because
the XML chains them TOPRIGHT-to-TOPLEFT and BOTTOMLEFT-to-TOPLEFT. The overlays carry absolute
offsets, so they are captured from what the stock repaint last wrote — hooked on
`WorldMapFrame_Update`, the only moment those base values are readable — and scaled from that base,
so a second pass cannot compound. Measured: a 1431x954 frame now holds a 1462x1097 grid, the same
1024/1002 and 768/668 proportion the reference frames.

Two smaller faults fell out and are fixed with it: the container resizing was not treated as a
canvas size change at all (the canvas was re-sized only on a map change), and a derived resize
fires no `OnSizeChanged` here, so the rescale takes the size its caller just set rather than
reading the frame back, which would answer the old one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-02 16:35:53 -07:00
gromandClaude Opus 5 70e4278c79 ui: the map's hovered-zone label is chrome, not map content
The canvas reparents 1.12's `WorldMapButton` into the zooming child so that every
1.12 addon's icons pan and scale with the map. `WorldMapFrameAreaFrame` hangs off
that button in the client's own XML — it covers the art, so that is where the
label that names what the cursor is over was hung — and it went along with it.

Its label is a 102 px THICKOUTLINE FontString. Inside the canvas it scales with
the zoom, so at zoom 2.5 every letter is a 261 px glyph, and one zone name's worth
of those is most of the 2048² glyph sheet. Measured in grom's 2026-11-02 session:
one `ui_text: a 261.10907 logical-px request … past the 256 ceiling`, then 20
`glyph sheet full … resetting at the frame boundary`, one per hover, each of them
a frame in which every string on screen is missing — the map "flashing the
interface". Frame cost with the map open went 11 ms → 16-20 ms over the same
window.

So it goes back on the window, anchored to the container's top, which is where the
art's top edge is now; at rest that is the pixel it was already at. Measured
through the harness: the biggest text under `WorldMapFrame` was 102 px at zoom 1
and 1632 px at zoom 16, and is now 102 px at every zoom.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-02 10:00:20 -07:00
gromandClaude Opus 5 75ba592fba ui: the Era map canvas over 1.12's own world map, and the Line region
What 1.12 withholds here is a framework, not a verb. Its world map is a fixed
1002x668 frame showing one zone — twelve tiles, a hit button, POI buttons, two
dropdowns — with no canvas, no pan, no zoom, no pins and no notion of where one
map sits on another. Every map addon written after 4.0 drives the canvas
framework instead, and there is no stock file to migrate from because there is
no stock feature.

The constraint that shaped every decision: `WorldMapFrame` already has eight
1.12 consumers on this install, all anchoring icons to `WorldMapDetailFrame`
and `WorldMapButton`. So nothing is replaced. The canvas is added, and those
two frames are reparented INTO it — at rest the rect they had, at scale 1 with
no pan, so the stock window is pixel-identical, and the payoff is that the 1.12
art and every icon on it now pan and scale WITH the canvas for free.

Clipping comes from a real ScrollFrame: this client clips a scroll child and
every descendant of it from one source, in the draw pass and the hit-test, so
drawing and hitting agree. The base-art layer a canvas addon hides is a proxy
that switches draw layers rather than the frame, because hiding the frame would
take the POI buttons and every addon's icons with it.

Three regressions, each found by measurement, each pinned: the anchor cycle
(container to detail frame to canvas to container — a cycle resolves to
nothing, and the symptom was `GetCenter()` nil inside the stock hover pass); the
base scale (a minScale below 1 opens the map zoomed out of its own window); and
`dataProviders` being a set rather than an array, because callers iterate it
with `pairs` and bind the key — an array handed that loop the number 1.

`CreateLine` is a Texture carrying two endpoints, drawn through the
explicit-corner quad path the cooldown wedge already uses. Nothing stands in
for it: a line between two moving points is a rotated quad whose angle and
length both change. `C_Texture.GetAtlasInfo` answers nil for every name, and
the nil is the answer — but the getter's presence is what makes the common
atlas guard take its fallback branch instead of raising.

The harness grew a second surface, and the finding is worth keeping: the full
screen map hides `UIParent`, so opening it in the ordinary render probe dropped
the corpus's draw column from 37/86 to 18/86 — nineteen unrelated addons. As
its own pass: no addon lost a quad and nineteen gained, pfQuest 374 -> 911,
pfUI 1063 -> 2259.

OpenMapClassic now reads 1/1 on all five columns, stitching 74 of the realm's
own zones. Corpus roster unchanged at 85 ok / 1 fail; the 12 data-half reds
byte-identical to HEAD's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
2026-11-01 22:21:35 -07:00