ui: texture masks, the later-era texture dials, and the pools a canvas map needs

The mask pair is the one that matters and nothing can stand in for it. 1.12 has
no texture masking at all — its single masked thing is the minimap disc, cut
engine-side — and no idiom reaches the same end, because the point is that a
rectangular texture draws as a non-rectangular shape. A seamless world map is
that operation and nothing else: a zone's art is a rectangle of parchment and
the map wants it cut to the zone's coastline.

It was affordable because the render pass already had the slot. `UiQuad::mask`
is a screen-anchored alpha mask built for the minimap's fixed circle with tiles
panning under it, which is exactly `AddMaskTexture`'s semantics — the mask is
another region with its own anchors, and the subject keeps the fragments its
alpha covers at that place. So this is plumbing, not a new material.

One mask per quad, said rather than hidden: widening it is a second texture
binding and a second uniform on every UI quad in the client. The list is kept
whole, because the order decides the winner — first added, which for a stitched
map is the land mask. Two traps are pinned by tests: a mask region is skipped at
extract rather than marked hidden, since its rect is what it covers; and a mask
whose art is missing drops the mask, not the quad, because zero coverage would
blank the art it was meant to shape.

`Texture:SetRotation` was nearly free — the renderer has carried the angle since
the minimap arrow, so this is the Lua setter for it. `SetDesaturation` is the
fractional form of 1.12's own flag, rounded onto the one flag this renderer has,
with the getter answering the caller's own number. The tile pair is applied: the
host computes the repeat count once the art has loaded.

`SetPropagateKeyboardInput` is stored, not applied: 1.12 already always
propagates, so there is no swallow mode for false to select. `EditBox:HasFocus`
publishes a fact this client already tracks and never exposed.

Corpus: 85 ok / 1 fail, roster and digest unchanged. The 12 data-half reds are
byte-identical to HEAD's.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
2026-11-01 21:15:55 -07:00
co-authored by Claude Opus 5
parent 87f93fea46
commit b49856c5cb
14 changed files with 983 additions and 16 deletions
@@ -405,6 +405,112 @@ const ALLOWED_BEYOND: &[(&str, &str, &str)] = &[
the two spellings cannot drift. Ace3 calls it unguarded on every button it builds \
(AceConfigDialog-3.0.lua:586)",
),
(
"AddMaskTexture",
"Texture",
"The map canvas: 1.12 has no texture masking at all, and nothing can stand in for it \
because the point is that a RECTANGULAR texture draws as a non-rectangular shape. A \
seamless world map is that operation and nothing else — a zone's parchment cut to the \
zone's coastline — so without it every zone draws as a paper rectangle over its \
neighbours' oceans and labels. Called unguarded where it is used: Map/Close.lua:162",
),
(
"RemoveMaskTexture",
"Texture",
"The other half of AddMaskTexture, so a caller that swaps a mask can take the old one \
off; without it a re-masked tile keeps the first cut forever",
),
(
"CreateMaskTexture",
"Button CheckButton ColorSelect DressUpModel EditBox Frame GameTooltip LootButton MessageFrame Minimap Model MovieFrame PlayerModel ScrollFrame ScrollingMessageFrame SimpleHTML Slider StatusBar TabardModel WorldFrame",
"The region AddMaskTexture takes: a Texture that shapes others and never draws itself. \
Built over the stock CreateTexture on the same table, so a mask is an ordinary Texture \
in every other respect. Map/Close.lua:159 builds one before any texture can ask for it",
),
(
"SetRotation",
"Texture",
"1.12 registers SetRotation on PlayerModel alone (0x84f1fc), so a Texture there cannot \
turn. The renderer has carried the angle since the minimap arrow, which the host draws; \
this is the Lua setter for it. The map's own player arrow and its compass rail need it",
),
(
"GetRotation",
"Texture",
"The read half of SetRotation, so the pair round-trips; 0 by default",
),
(
"SetDesaturation",
"Texture",
"The later-era FRACTIONAL form of 1.12's own SetDesaturated. This renderer greys \
all-or-nothing (one material flag, not a blend weight), so a fraction is taken as on \
past the midpoint — an approximation, and the honest one available, since the only use \
of it in the corpus is dimming art the caller has already decided to dim \
(Map/Close.lua desaturates minimap tiles by a fixed amount so the painted map reads \
over them)",
),
(
"GetDesaturation",
"Texture",
"The read half, answering the caller's own fraction rather than the rounded flag",
),
(
"SetHorizTile",
"Texture",
"Repeat the art across the region at its own texel size. 1.12 reaches the same end with \
SetTexCoord(0, n, 0, 1) — UVs past the texture, which is the idiom this renderer keys \
its wrap mode on — but has no switch meaning 'however many times it takes', because \
that count is rect/texels and 1.12's Lua is never told the texel size. Applied: the \
host computes the count once the art has loaded, and an explicit SetTexCoord wins",
),
(
"SetVertTile",
"Texture",
"As SetHorizTile, for the other axis",
),
(
"GetHorizTile",
"Texture",
"The read half of SetHorizTile; false by default",
),
(
"GetVertTile",
"Texture",
"The read half of SetVertTile; false by default",
),
(
"HasFocus",
"EditBox",
"1.12's EditBox table has SetFocus and ClearFocus and no getter at all, so an addon of \
that era keeps its own flag. The fact is already tracked here (Model::focused_editbox, \
the client's own DAT_00cf4dc8) and was simply never published. Called unguarded inside \
an OnEditFocusLost callback (Map/Nav.lua:374), so a nil method raised every time a \
search box lost focus",
),
(
"SetCursorPosition",
"EditBox",
"1.12 has no cursor setter at all — the caret moves only by typing, clicking or \
HighlightText. Lands through HighlightText's own clamp and UTF-8 snap with the selection \
collapsed, so there is one definition of where a caret may sit. Map/News.lua:282 calls \
it to scroll a long read-only link box back to its start",
),
(
"SetPropagateKeyboardInput",
"Button CheckButton ColorSelect DressUpModel EditBox Frame GameTooltip LootButton MessageFrame Minimap Model MovieFrame PlayerModel ScrollFrame ScrollingMessageFrame SimpleHTML Slider StatusBar TabardModel WorldFrame",
"4.x's switch for whether a frame with an OnKeyDown handler lets the key reach the next \
frame and the binding system. STORED, NOT APPLIED, and the reason is that 1.12's answer \
is always-propagate: its key routing hands the key to the binding system regardless of \
what a handler did, so there is no swallow mode for false to select. Refusing it costs a \
caller its whole key path — Map/Sketch.lua:579 arms a catcher frame and flips the flag \
inside its own handler, so a nil method raises once per keypress",
),
(
"GetPropagateKeyboardInput",
"Button CheckButton ColorSelect DressUpModel EditBox Frame GameTooltip LootButton MessageFrame Minimap Model MovieFrame PlayerModel ScrollFrame ScrollingMessageFrame SimpleHTML Slider StatusBar TabardModel WorldFrame",
"The read half, so the pair round-trips; true by default, which is what this client does \
unconditionally",
),
(
"CanWordWrap",
"FontString",