SoftSpoken (z1xel/SoftSpoken, a 1.12 narration port) failed to load: nine files
across six addons would not compile, and the three that did then raised at
runtime indexing namespaces the failed chunks never built. Every one of the nine
is valid Lua 5.1 and invalid Lua 5.0 — `#`, `%`, `0x3040`, `local NAME = ...`.
`third_party/lua-src` is a fork that deliberately 5.0-ifies the grammar: three
arms deleted from `lparser.c` (`simpleexp`'s `TK_DOTS`, `getunopr`'s `'#'`,
`getbinopr`'s `'%'`) and `read_numeral` replaced with 5.0's, which has no hex.
Every deletion is byte-read out of `WoW.exe`'s own parser and every one is right
about stock 1.12.
No OctoWoW client runs stock 1.12. ClassicAPI.dll is in the realm's bundle and
rewrites every chunk at the `luaL_loadbuffer` chokepoint — file scripts,
`loadstring` and XML `OnLoad` alike — adding exactly five constructs: `#`, `%`,
`...` as an expression, `0x` hex and `[=[ ]=]`. Confirmed three ways: the repo's
own highlights line, PR #28, and the DLL's string pool, which twice says "Our
5.1 syntax backport makes the probe pass". `docs/ADDON_COMPAT.md`'s row for this
said "free — benilla's VM is mlua/Lua 5.1 already". That was the error.
So the target grammar is the union, and the fork now carries it:
* the three parser arms restored, as stock 5.1 writes them;
* `read_numeral` grown an explicit `0x` arm. It could not take 5.1's version
back wholesale: 5.1 reached hex through a trailing `isalnum` slurp, and that
slurp has to stay gone or `1then` breaks again
(`AdvancedTradeSkillWindow2.lua:3878`, which the client compiles). The
prefix arm gives both;
* addon file chunks now called with `(addonName, addonPrivateTable)`
(`UiScript::run_addon_chunk`, four call sites). Taking `...` without passing
the arguments only moves the failure one line later, which is what
ClassicAPI's own notes record;
* `string.reverse` added to the stdlib backport — SoftSpoken calls it.
1.12's own dialect is untouched. `LUA_COMPAT_VARARG` withholds the `arg` table
only from a function that *uses* `...` as a value, so the shipped FrameXML's
`function f(...) … arg.n … end` still gets `arg`: the two dialects coexist one
function at a time, which is what the client-plus-ClassicAPI does.
The cost is the era probe. 170 corpus `loadstring` sites ask
`loadstring("return function(...) return ... end")` and 92 library files in 24
folders branch on the answer; they now take the 5.1 branch, as they do on the
real client. Two branch wrongly there and ClassicAPI ships a compensation for
each — Compost-2.0 rev 17406+'s no-argument `Erase` stub (bundled by BigWigs and
QuickHeal; it silently stops clearing in place) and Cartographer_Notes'
`WorldMapDeathRelease`. Both are now carried in `lua51_probe_fallout.rs`, probed
by behaviour rather than by revision and re-checked on every `ADDON_LOADED`,
because AceLibrary can upgrade a library mid-session.
Measured on grom's own AddOns folder: SoftSpoken, SoftSpoken_Stories,
SoftSpoken_Places, SoftSpoken_Writings, SpokenZonesAudio, SpokenBooksAudio,
SpokenQuestsAudio{Horde,Alliance,Gossip,Shared}, SSS_VOFallback and
AI_VoiceOverData_Turtle all load with zero load errors and zero session errors,
98 `Spoken*` globals, `/softspoken` registered, and PLAYER_ENTERING_WORLD,
QUEST_DETAIL, GOSSIP_SHOW and ITEM_TEXT_BEGIN reaching 108/108, 1/1, 6/6 and 2/2
handlers clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
`AdvancedTradeSkillWindow2.lua:3878` is `if Item.ReplaceWords == 1then`, with no space, and it
compiles on the reference. Its `read_numeral` (`0x6ffee0`) is 5.0's: digits, at most one `.` and its
digits, then — given an `e`/`E` — the marker, an optional sign (the `0x2b`/`0x2d` test at `0x6fff04`,
reached with the marker already saved) and its own digit loop. The only character-class test in the
whole routine is the ctype digit mask at `0x6fff47`; past it the buffer is terminated and handed to
`luaO_str2d` (`0x6f5900`). 5.1 instead ends with
`while (isalnum(ls->current) || ls->current == '_') save_and_next(ls);`, which swallowed `1then`
into the numeral and failed it — so we raised `malformed number` and lost a 4000-line file and every
global it defines, and its own XML then raised four more times calling them. The addon was dead.
The ninth hunk of the `lua-src` fork, which is where a grammar difference belongs: `BENILLA.md` is
explicit that rewriting the chunk source means parsing Lua with a regex. A replacement, not a
deletion — 5.1 had no exponent-digit loop of its own, so deleting the slurp alone would have cost
`1e2`, which is what the new test caught.
The same boundary is the deliberate half: 1.12 has **no hex literals**. The digit loop stops at the
`x`, so the reference reads `0xFF` as `0` and the name `xFF` and cannot parse the statement around
it. A comment- and string-stripping scan of the installed corpus finds hex in code in exactly two
files, `pfQuest/toolbox/pngLua/deflate.lua` and `numberlua.lua`, which no `.toc` row reaches — zero
loaded sites, the same standard the fork's three other deletions were held to. Our own shipped Lua
and his saved variables have none either. Two of our own test chunks did, and are now decimal:
no chunk of benilla's Lua may use a construct the client's parser rejects.
Verified: the whole chain still loads with no failure, the `WOW_DATA` failure set is unchanged, and
`AdvancedTradeSkillWindow2` now publishes `ATSW_OnLoad` and `ATSWTypeColor`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
The notes and comments say what is true of the 1.12.1 client and cite the
byte address; the record numbers, the dates and the private repository's
file paths go.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- a cooldown still running when you log out and back in keeps sweeping instead of vanishing, and another character no longer inherits the previous session's cooldowns (2116)
- the server's Welcome and Patch 1.12 lines print at login in system yellow, and a character whose General, Trade and LocalDefense had gone silent for good gets them back at the next login (2119, 2120)
- the stock Interface Options window loads hidden beside ours, so pfUI's options skin finds the real thing; the Auto-join Guild Recruitment Channel row has a real store, and a /reload no longer trips on its camera dropdowns (2115)
- the combat log words a power gain on the display scale, so MikScrollingBattleText shows +1 Rage where it showed +10; a 1.12 predicate answers 1 or nil rather than a Lua boolean, the test ColorPickerPlus makes (2117, 2118)
- a Lua error quotes a program element with a backquote the way 5.0 does, so AceLibrary's argument checks report their caller instead of raising inside the error path (2122)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- the cooldown pie, the GCD sweep, minimap pings and the map arrow draw again after a logout, login or /reload; they went blank until a restart, which read as OmniCC killing the cooldowns (2113)
- an addon's own font on a button label keeps its face, size and outline instead of being undone every frame, MikScrollingBattleText's reported look (2112)
- an empty label reads as nothing rather than an empty string, so Cartographer's map label keeps its colour; a slider thumb with no authored size takes its art's own size, so Dewdrop's Look'n'Feel slider moves (2109, 2110)
- Bagnon's saved bag list comes back in the order it was written, so the keyring stops jumping to the front after a restart; hovering a Bagnon bag costs a third of what it did (2111, 2114)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- five idle minutes sit you down and flag you AFK, thirty log you out through the ordinary countdown; holding a key is idle, moving the mouse is not, and a swimmer stays afloat (2092)
- a knockdown no longer wedges: the landing froze the clip that held the base-animation lock, leaving a knocked-down player on their back for the session (2098); the perf cost pill starts hidden (2099)
- Lua is exactly 1.12's 5.0 — no vararg expression, no #, no %, the remembered table size — so Cartographer loads without errors and Mik's Scrolling Battle Text shows its text, in its own shipped font now that addon TTFs load (2101, 2102, 2103)
- a widget name is a Lua global for SetPoint and its kin, so Bartender2's bars spread out; a button's NormalText disowns its justify, the raise renumbers hidden frames too, and a missing toc file warns instead of erroring (2105, 2100, 2104, 2107, 2106)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
- dialogs carry the reference's toplevel bit, so the stack-split window opens above Bagnon instead of behind it
- the failure line fills its blank — "Requires Blacksmith Hammer", not "Requires" — and Opening casts stop printing a label the spell itself marks "no text"
- the pet bar's Attack/Follow/Stay tooltips name their keybinding, and typed text reaches the focused frame through the client's own delivery walk
- AtlasLoot parses again: our Lua keeps 5.0's compatibility semicolon
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- the workspace pointed at third_party/lua-src (the Lua 5.1 fork the addon VM builds on) and the export left that folder behind — every build of today's snapshots broke at dependency resolution
- third_party/ now ships, and the sync gained a gate that refuses to publish an export that does not resolve as a cargo workspace
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>