compat: math.huge and SetColorTexture, the two load blockers of the ported pair

Measured through `addon_harness` before anything was written, which is the only reason this
is two small changes rather than a guess at a list. Both `Addon_Ports` addons failed to load
at all; these are their first two stops.

**`math.huge` was nil, deliberately.** `stdlib_backport.rs` restores the 5.1 stdlib members
ClassicAPI backports, scoped to what the corpus calls, and `math.huge` was on the stated
"nothing calls them" list. `Cinematic/Core.lua:1103` is `local lastMovingOff = -math.huge`
at **file scope**, so the nil stopped the addon's main file dead ("attempt to perform
arithmetic on field `?`") and nothing after it ran — one site, but a load-time one, which is
the bar that table sets. The real `ClassicAPI.dll` carries `huge` in its own stdlib name
table beside `fmod`, `modf`, `gmatch`, `match`, `select`, `maxn`, `cosh` and `reverse`, so
this is what the client we already claim to be actually has.

Two tests pinned it absent and are updated rather than deleted: `lua50.rs`'s keeps asserting
this *layer* removes it (the registry stash is the proof), and `stdlib_backport.rs`'s keeps
the narrowness assertion for the four that stay gone. The new assertion is `-math.huge`,
because file-scope arithmetic is the shape that failed.

**`Texture:SetColorTexture`** is the modern spelling of `SetTexture`'s colour arm, which
1.12 already has (`0x770360`'s 8×8 solid surface, and `region/paint.rs` defaults the alpha
to 1 as it does). Retail split the names when `SetTexture` stopped taking a colour, so every
Era-era addon writes the new one. `Cinematic/Options.lua:701` paints its list border with it
and died at session start; OpenMapClassic its map chrome. Four lines of the same composition
chunk `SetShown` and the geometry pair use, and an `ALLOWED_BEYOND` row with both call sites
— `docs/ADDON_COMPAT.md` had it on the same "plausible and untraced" line as
`RegisterUnitEvent`, and this is that line's follow-through.

Movement, same harness both ways:
- Cinematic load: `Core.lua:1103` → `Frames.lua:2611` (~10 files further; wants
  `RegisterUnitEvent` next). Session: `Options.lua:701` → `Options.lua:4130` (3400 lines on,
  wants `SetWordWrap`).
- OpenMapClassic: unchanged at `DialogBorderOpaqueTemplate`, which neither fix touches.

benilla-ui 2076 passed, 0 failed. Full chain green.

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 14:19:56 -07:00
co-authored by Claude Opus 5
parent 2a65266837
commit 79e2494fc6
5 changed files with 49 additions and 15 deletions
@@ -78,6 +78,20 @@ end
end
"#;
/// Texture-only: `SetColorTexture(r, g, b[, a])` is the modern name for `SetTexture`'s colour arm,
/// which 1.12 already has (`0x770360` makes the 8x8 solid surface, and our `region/paint.rs` arm
/// defaults the alpha to 1 exactly as it does). Retail split the two spellings when `SetTexture`
/// stopped taking a colour; every Era-era addon writes the new one, and both of the addons being
/// ported here are blocked on it — `Cinematic/Options.lua:701` paints its list border with it, and
/// OpenMapClassic its map chrome.
const TEXTURE_DELEGATES: &str = r#"
return function(m)
function m.SetColorTexture(self, r, g, b, a)
self:SetTexture(r, g, b, a)
end
end
"#;
/// FontString-only: `SetFormattedText` is `SetText(format(...))`. 1.12's `string.format` is the
/// `format` global here, as `lua50.rs` leaves it.
const FONTSTRING_DELEGATES: &str = r#"
@@ -157,6 +171,7 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
delegate(lua, key, GEOMETRY_DELEGATES)?;
delegate(lua, key, SHOWN_DELEGATES)?;
}
delegate(lua, REG_TEXTURE_METHODS, TEXTURE_DELEGATES)?;
delegate(lua, REG_FONTSTRING_METHODS, FONTSTRING_DELEGATES)?;
// `GetStringHeight()`: the laid-out height of the current text — wrapped to the declared
@@ -28,13 +28,18 @@
//! | `math.fmod` | 2 |
//! | `math.modf` | 1 |
//! | `string.reverse` | 1 |
//! | `math.huge` | 1 |
//!
//! `string.reverse` joined the list when SoftSpoken was installed — it is a ClassicAPI-dependent
//! addon, so it writes 5.1 and its one `string.reverse` site is a load-time call.
//! addon, so it writes 5.1 and its one `string.reverse` site is a load-time call. `math.huge`
//! joined it when the two Addon_Ports addons were measured: `Cinematic/Core.lua:1103` is
//! `local lastMovingOff = -math.huge` at **file scope**, so the nil stopped the addon's main file
//! dead ("attempt to perform arithmetic on field `?`") and nothing after it ran. One site is the
//! bar this table sets, and a load-time one at that.
//!
//! Deliberately **not** restored, because nothing calls them: `strrev`,
//! `table.maxn`, `math.huge`, the hyperbolics, and `coroutine`. Each stays nil, so the surface
//! grows by five names rather than by a whole library — and `table.maxn` in particular would
//! `table.maxn`, the hyperbolics, and `coroutine`. Each stays nil, so the surface
//! grows by six names rather than by a whole library — and `table.maxn` in particular would
//! fight `lua50.rs`'s `n`-based table library, which is 5.0 semantics this client genuinely needs.
//!
//! ## What this costs, stated plainly
@@ -60,6 +65,12 @@ const RESTORED: &[(&str, &str)] = &[
("math", "fmod"),
("math", "modf"),
("string", "reverse"),
// Not a function: the stash is over `Value`, so a number restores the same way. The real
// `ClassicAPI.dll` carries `huge` in its own stdlib name table beside `fmod`, `modf`,
// `gmatch`, `match`, `select`, `maxn`, `cosh` and `reverse` (its string pool, the evidence
// class `benilla-dll-contract-from-strings` names), so this is what the client it claims to
// be actually has.
("math", "huge"),
];
pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
@@ -138,25 +149,24 @@ mod tests {
"abc"
);
assert_eq!(s.eval::<f64>("return math.fmod(7, 3)").unwrap(), 1.0);
// `math.huge` is a number, not a function: the one site that needed it is file-scope
// arithmetic (`Cinematic/Core.lua:1103`, `-math.huge`), so negating it is the test.
assert!(s
.eval::<bool>("return -math.huge < -1e308 and math.huge > 1e308")
.unwrap());
assert_eq!(
s.eval::<(f64, f64)>("return math.modf(3.25)").unwrap(),
(3.0, 0.25)
);
}
/// The narrowness is the point: five names, not a whole library. `table.maxn` especially must
/// The narrowness is the point: six names, not a whole library. `table.maxn` especially must
/// stay gone — it fights `lua50.rs`'s `n`-based table library, which is 5.0 semantics this
/// client relies on.
#[test]
fn nothing_beyond_the_measured_members_comes_back() {
let s = UiScript::new().unwrap();
for expr in [
"strrev",
"table.maxn",
"math.huge",
"math.cosh",
"coroutine",
] {
for expr in ["strrev", "table.maxn", "math.cosh", "coroutine"] {
assert_eq!(
s.eval::<String>(&format!("return type({expr})")).unwrap(),
"nil",
+3 -3
View File
@@ -513,17 +513,17 @@ mod tests {
}
/// The 5.1-only members this *dialect* removes stay removed in the finished VM — except the
/// five `compat/classic_api/stdlib_backport.rs` deliberately hands back, which is a documented
/// six `compat/classic_api/stdlib_backport.rs` deliberately hands back, which is a documented
/// maintainer decision, not drift.
///
/// Split in two on purpose. For the restored five, asserting "nil" would be asserting the
/// Split in two on purpose. For the restored six, asserting "nil" would be asserting the
/// opposite of the decision; instead this checks they are in the registry stash, which is
/// proof *this layer* removed them and only compat put them back. For everything else the
/// original assertion stands unchanged, so a genuine regression still fails here.
#[test]
fn the_5_1_only_members_are_not_offered() {
let s = UiScript::new().unwrap();
for expr in ["table.maxn", "math.huge", "math.cosh", "coroutine"] {
for expr in ["table.maxn", "math.cosh", "coroutine"] {
assert!(
s.eval::<bool>(&format!("return {expr} == nil")).unwrap(),
"{expr} is a Lua 5.1 addition — 1.12 does not have it, and nothing restores it"
@@ -290,6 +290,15 @@ const ALLOWED_BEYOND: &[(&str, &str, &str)] = &[
"ClassicAPI: the laid-out wrapped height, the other half of GetStringWidth's measure — \
AceGUI-3.0's Label and CheckBox call it unguarded",
),
(
"SetColorTexture",
"Texture",
"ClassicAPI: the modern spelling of SetTexture's colour arm, which 1.12 already has \
(0x770360's 8x8 solid surface; region/paint.rs defaults the alpha to 1 as it does). \
Retail split the two names when SetTexture stopped taking a colour, so every Era-era \
addon writes this one — Cinematic/Options.lua:701 paints its list border with it and \
died at session start without it, and OpenMapClassic its map chrome",
),
(
"SetShown",
"Button CheckButton ColorSelect DressUpModel EditBox FontString Frame GameTooltip LootButton MessageFrame Minimap Model MovieFrame PlayerModel ScrollFrame ScrollingMessageFrame SimpleHTML Slider StatusBar TabardModel Texture WorldFrame",