| T1 |
Spec as Markdown chapters in the repository (user) |
Versioned with the compiler. |
| T2 |
Compiler in Rust, one binary for compiler, package manager and tooling (user) |
|
| T3 |
Support all WoW flavors. Split per-flavor compilation by default (user) |
Per-flavor type checking and dead-code removal. |
| T4 |
Manifest is strict JSON (wowlua.json) with a $schema key (user) |
Tool-friendly. Editors get completion through the JSON schema. |
| T5 |
Dependency sources: git, local path, archive URL, and a wowlua registry (later phase) (user) |
|
| T6 |
Publish targets: CurseForge, Wago, WoWInterface, GitHub Releases (user) |
The major WoW addon sites. |
| T7 |
Minification levels are user-defined in the project, selectable per profile and per publish target (user) |
Different platforms allow different amounts of minification. |
| T8 |
Default with no configuration: release is bundled + strip (comments, whitespace and safe syntactic simplifications), with no renaming. Dev is unminified. (user, amended in review R1) |
Fast loading, readable code. |
| T9 |
Dev builds: one file per module with line numbers preserved. Release: bundled. (user) |
In-game errors in dev builds point at the right source line with no tooling. |
| T10 |
Libraries and the stdlib are embedded as a private, tree-shaken copy per addon (user) |
No cross-addon version conflicts. |
| T11 |
Additional tooling: test runner, language server, formatter, trace decoder (user) |
|
| T12 |
WoW API types: generated from Blizzard's API documentation, enriched with additional information from Ketho's vscode-wow-api (MIT) (user) |
Most accurate, per-flavor, typed event payloads. |
| T13 |
Warning W6010 when a publish target uses a renaming minification level (user) |
Blizzard's AddOn policy forbids hidden or obfuscated code. |
| T14 |
One zip for all flavors, tagged with every flavor's game version per site. Per-target split-flavors available. (user) |
Packager convention, supported by CurseForge and Wago. |
| T15 |
Ecosystem compatibility: import existing TOC and .pkgmeta, optionally keep .pkgmeta, support BigWigs packager keywords in passthrough files (user) |
Easy migration of existing addons. |
| T16 |
Resolver: PubGrub, one version per package per addon (proposal) |
Everything is embedded into one AddOn. |
| T17 |
wowlua publish requires a clean tree and runs tests by default (proposal) |
Safe releases. Both can be overridden with flags. |
| T18 |
Build output is validated (TOC references, Lua 5.1 reparse, Lua limits, XML) before writing (proposal) |
Guarantees a working AddOn. |
| T19 |
TOC layout auto: a single TOC with comma-separated interfaces and inline [AllowLoadGameType …] directives when every targeted client supports it, otherwise per-flavor TOC files (user) |
Blizzard's direction, fewer files. The fallback keeps frozen clients working. |
| T20 |
Flavor IDs are Blizzard game types (standard, camelot, mists, cata, wrath, tbc, vanilla), with friendly aliases in the manifest (user) |
They name rulesets rather than clients, and match TOC directives. |
| T21 |
WoW API types are pre-generated in CI and published as wow-api-<flavor> packages (user) |
Convenience. Blizzard's documentation has no explicit license, and the project owner accepted publishing derived type information. |
| T22 |
wowlua:locale: JSON files per locale, typed named placeholders, and only the base locale plus the client's locale are loaded (via [AllowLoadTextLocale] where every client supports it, otherwise a runtime guard) (user, wording clarified in R1) |
Tool-friendly translations, compile-time checked, less memory. |
| T23 |
Every minification level keeps at least one line break per statement, so source maps stay line-based and wowlua trace always works (user, R1) |
WoW error messages have no column numbers. |
| T24 |
Bundles isolate modules: each runs protected, errors go to geterrorhandler(), and later modules still load (user, R1) |
Same behavior as separate files. |
| T25 |
Publish builds go to dist/targets/<target>/. Zips get a target suffix when targets differ (user, R1) |
No collisions between targets. |
| T26 |
Module graph and load order are computed per flavor. Single-TOC order is a topological sort over all flavors (user, R1) |
@if on imports works. |
| T27 |
Top-level config defines defaults that profiles override (deep merge for objects, replacement for arrays) (user, R1) |
|
| T28 |
Output layout: user code under src/, generated code under wowlua/, libraries under libs/ (user, R1) |
No collisions with user folders. |
| T29 |
wowlua:meta in libraries: addon, build, flavor from the consuming addon; features, config are the library's own. Libraries support all flavors unless declared otherwise (user, R1) |
|
| T30 |
Resolution over the union of features, flavors and dev-dependencies, with one version per package across the workspace where possible. Lock entries record their users (user, R1) |
--features never invalidates the lockfile. |
| T31 |
The lockfile pins the flavor-table version and resolved interface numbers (user, R1) |
Deterministic TOCs across machines. |
| T32 |
build.version: tagged release builds use X.Y.Z (the tag must match), untagged builds X.Y.Z-alpha+<hash>, and the dev profile X.Y.Z-dev+<hash> (user, R1) |
|
| T33 |
Publish targets have free names plus a type field. Per-target nolib zips are supported (user, R1) |
Several targets per site. |
| T34 |
XML references to .wowlua are rewritten. Modules referenced from XML stay separate files in bundled builds (user, R1) |
|
| T35 |
asset(path) in wowlua:meta folds to the full game path and checks that the file exists (user, R1) |
|
| T36 |
License header comments are never stripped. Every build gets a generated LICENSES.txt (user, R1) |
Library licenses stay intact. |
| T37 |
No telemetry. English messages only. explain works offline (user, R1) |
|
| T38 |
dev/install refuse to overwrite AddOn folders not built by wowlua unless --force is given. wowlua uninstall exists (user, R1) |
|
| T39 |
Dependencies never run code at fetch or build time. Path traversal and escaping symlinks are rejected. Tokens are stored in the OS keychain where available (user, R1) |
|
| T40 |
The wowlua runtime and standard library embedded in addons are MIT-licensed (user, R1) |
Permissive. Addon authors keep any license for their own code. |
| T41 |
Distribution through GitHub Releases binaries (plus install script and a setup-wowlua GitHub Action), Homebrew, winget and Scoop (user, R1) |
|
| T42 |
wowlua init moves Lua and XML files into src/ and rewrites absolute in-game paths to them. Assets stay in place (user, M0) |
Standard layout for adopted addons, without breaking references. |
| T43 |
build.flavor-files restricts source files to flavors (glob → flavors) (user, M0) |
Plain Lua files cannot use @if. init writes it from flavor-specific TOCs. |
| T44 |
In single TOCs, a flavor that is the only targeted one of its family is written as the family name (mainline/classic) in load directives (user, M0) |
Recognized by every client with inline directives. |
| T45 |
wowlua install defaults to the dev profile (user, M0) |
Same as wowlua dev, without watching. |
| T46 |
Manifest schema URL: the GitHub raw URL of schema/wowlua-<version>.json in the repository (user, M0) |
Works without a website. |
| T47 |
M0 accepts .wowlua files that are pure Lua 5.1. M0 includes a release pipeline to GitHub Releases. M0 creates only the crates it needs (user, M0) |
|
| T48 |
Releases are built by a hand-written GitHub Actions workflow (six targets, checksums, install scripts) instead of cargo-dist. setup-wowlua is a composite action inside the wowlua repository (proposal, M0) |
Transparent and easy to extend with Homebrew, winget and Scoop. No separate wowlua GitHub organization is needed. |
| T49 |
A file matched by several build.flavor-files globs belongs to the union of their flavors (proposal, M0) |
Predictable, order-independent. |
| T50 |
addon.flavor-overrides expresses per-flavor metadata (title, notes, author, category, group, icon, toc-extra). null removes a field for a flavor (user, ElvUI test) |
Real addons (ElvUI) vary titles, icons and compartment entries per flavor. One TOC can do this with inline conditions on ## lines. |
| T51 |
The package manager supports SVN sources, locked to a revision, using the system svn client (user, ElvUI test) |
Ace3, LibStub and other core libraries are published on CurseForge's SVN repositories. .pkgmeta projects convert without manual work. |
| T52 |
Bindings.xml / Bindings_<Suffix>.xml in the package root are passed through to the AddOn root (user, ElvUI test) |
The client loads them automatically; dropping them loses keybindings. |
| T53 |
wowlua init determines, per flavor, the one TOC that flavor's client reads (game suffix, then family suffix, then the plain name) and takes files, dependencies and metadata only from it (proposal, ElvUI test) |
Matches the client. Combining TOCs would load files a client never loads. |
| T54 |
addon.toc-data holds per-flavor files of verbatim ## Key: Value lines. init writes them when a TOC has more than 200 X- directives (proposal, corpus test) |
QuestieDB stores its database in about 100,000 TOC directives per flavor. In the manifest they made a 163 MB file; as data files the manifest is 16 KB. |
| T55 |
build.variable-files keeps TOC lines with [Game], [Family] and [TextLocale]. They are emitted verbatim when every targeted client supports file variables and expanded otherwise. Every expansion must exist (E5002) (proposal, corpus test) |
BigWigs and DBM use them. Expansion keeps older clients working. |
| T56 |
Flavor table: Cataclysm Classic 4.4.2 and the closed Wrath Classic 3.4.5 client support every TOC feature, including file variables, and recognize their own and all older game types (proposal, corpus test) |
BigWigs and DBM ship single TOCs with these features for both clients. The earlier data was too conservative and forced per-flavor TOCs. |
| T57 |
init takes the union of SavedVariables across the TOCs of all flavors and the value used by the most flavors for Version, load flags and publish IDs (proposal, corpus test) |
Structural fields cannot vary per flavor (T50). The union loses no saved data; a single stub TOC, such as Questie's Interface: 00000 file, is outvoted by the real ones. |
| T58 |
Files an addon loads from another AddOn's folder (..\Other\file.lua): the owner's init moves them into its sources and ships them as build.shared-files; the loader lists them as build.external-files with their output path (user, corpus test 2) |
TellMeWhen_Options and MythicDungeonTools_UI work this way. MDT's referenced XML includes libraries MDT itself loads, so the files must stay together in one place, owned by one package. |
| T59 |
addon.name may be any valid folder name (no \ / : * ? " < > |, no control characters, no leading or trailing space or dot) (user, corpus test 2) |
Real addons use ! and + prefixes to load first (!RealUI_Preloads, !BugGrabber). |
| T60 |
When an adopted addon already has a src folder, init moves sources to wowlua-src/ (user, corpus test 2) |
AllTheThings has its own src/. Moving into it nested the files and pulled unloaded leftovers into the build. The output layout stays src/…. |
| T61 |
XML references that differ in case from the file resolve like in the client, with warning W4310 (user, corpus test 2) |
SpartanUI and Total RP 3 do this and work in game. |
| T62 |
Inline directives may stand anywhere in a line, may be written [Name: values], and also work in XML file attributes. LoadOnDemand: true counts as set (verified in game, corpus test 2) |
Checked with a probe addon on the WoW: Forever beta client 1.60.1: the colon form and XML conditions both filter, true makes an addon load on demand. Total RP 3, ls_UI and ShadowedUF use these forms. |
| T63 |
Dependency directives keep the manifest order; consecutive entries for the same flavors share a line (proposal, corpus test 2) |
Grouping all unrestricted entries first reordered BetterBags' and RealUI's dependencies. The order may influence which dependency the client loads first, so the build keeps the author's order. |
| T64 |
Milestone M1 compiles .wowlua files and erases type-only syntax (annotations, casts, generic parameters, type, interface, declare, import type, export type). Other wowlua syntax reports the milestone that implements it (user, M1) |
Type annotations are useful long before the checker exists, and erasing them needs no lowering. |
| T65 |
wowlua fmt follows StyLua's style: reflow at line-width, calls always parenthesized, tables { a = 1 } flat or one field per line with a trailing comma, a single table or function argument (or a function as the last argument) hugs the parentheses, at most one blank line kept, ; only before a statement starting with ( (user, M1) |
Familiar to WoW authors, many of whom already use StyLua. |
| T66 |
The conformance corpus is a pinned list (xtask/corpus.txt: URL and SHA-256 per release) run by a scheduled CI job; regular CI runs the same checks on files written for the project (tests/corpus-sample/). The Lua 5.1 test suite is downloaded, not vendored (user, M1) |
Hundreds of megabytes do not belong in every CI run; the Lua 5.1 tests carry no explicit license. |
| T67 |
Fuzzing is done with deterministic randomized tests (random bytes, token soup, mutated samples) on stable Rust instead of cargo-fuzz (user, M1) |
No nightly toolchain; failures reproduce from the seed. |
| T68 |
Source maps of files shared by all flavors go to .maps/<Addon>/<output path>.map; only per-flavor files get a <flavor>/ level (user, M1) |
One map per output file. |
| T69 |
XML file attributes, TOC lines and the type grammar accept the forms the spec's examples use: pack expansions (A...), (EventArgs<E>...) and variadic results -> ...any (proposal, M1) |
The grammar appendix lacked them while chapter 2 and the standard library use them. |
| T70 |
Load order: dependencies found by usage analysis that contradict build.load-order give warning W1035 and the listed order wins; contradicting imports stay E1035. Guarded uses (if X, X and …, not X, X ~= nil, type(X), and the uses those tests guard) do not order files. A function passed to a package function counts as called only if that function calls (or forwards) the parameter. The deferred-API markers are a curated list in the flavor table; X.Name(…) matches the method markers like X:Name(…) (user, M2 corpus test) |
Without types the analysis over-approximates; init copies the order of a working TOC, so a migrated addon must keep it. On the corpus (108 addons) every migrated addon keeps its order. |
| T71 |
Only conditions that read a wowlua:meta value or a const are static (simplified and eliminated); literal-only conditions such as if true then are emitted as written (user, M2) |
Plain Lua must round-trip to identical bytecode (§13.5.3). |
| T72 |
E1014 only for cycles of load-time edges. A run-time import edge constrains the order unless it lies in a cycle of the full graph; then the binding becomes lazy (user, M2) |
Mixed cycles are common (a module calls back into its importer later) and work with lazy bindings. |
| T73 |
Load-order details: W1033 is not reported for files listed in build.load-order; files outside a flavor take no part in that flavor's analysis; usage edges between an XML file and the files it loads are ignored (they interleave in document order) (proposal, M2) |
The listed order already decides; flavor-specific files never load together. |
| T74 |
Smaller M2 rules: destructuring without a value is error E0101; decorator names may be keywords (@if); when flavors need incompatible orders the build falls back to per-flavor TOCs (W4304, E1036 if single is forced); in shared flavor mode addon.interface is chosen at run time from WOW_PROJECT_ID; build.minify and build.bundled report "none"/false until those features exist (proposal, M2) |
Gaps found while implementing M2. |
| T75 |
Class, enum and decorator checks that need only declarations run in M3, with classes collected package-wide across imports (E3001–E3005, E3007, E3010, E3011, E3040–E3043, E3002, E3020, E3021, E3032, E3033); type-based checks (E3006, E2003 on instances, E2052, E3044, W3021) wait for the checker (M4) (user, M3) |
Most class mistakes are structural and cheap to report early. |
| T76 |
Every top-level class and enum is hoisted to a local at the module top, exported or not; use while loading before the declaration has run is E1037 (user, M3) |
Code above a declaration must not silently read a global of the same name. |
| T77 |
Native widget types come from a compiler-provided declaration file (@widget declare class Frame, Button, … for every CreateFrame type, wowlua-stdlib), implicitly in scope until the generated wow-api packages (M4) declare them (user, M3) |
Widget classes need to know their native base before types exist. |
| T78 |
The debugging core decorators (@profile, @noprofile, @trace, @reflect, @checked, @unchecked) report milestone M5 until then; @inline and @nocheck are accepted as no-ops until M7 and M4 (user, M3) |
Same rule as other features of later milestones (T64). |
| T79 |
M3 ships the standard-library infrastructure without public modules: wowlua:<name> resolves to a compiler-provided module compiled into the addon as wowlua/std/<name>.lua when imported (transitively; module ID wowlua:<name>). A module's declaration file binds exports to @intrinsic ids; wrapping framework decorators apply like user decorators, registering ones are called as decorator(final, ctx) after every transformation (__wl.register). WOWLUA_STDLIB_DIR points the compiler at a directory of modules for developing the standard library; internal.wrap and internal.register are the infrastructure's own test intrinsics (user + proposal, M3) |
Later milestones (M5, M8) only add modules and table entries. |
| T80 |
The UI sample addon lives in examples/widget-demo; CI builds it and runs it in the embedded VM against a mock frame API (user, M3) |
The exit criterion (works in game) stays checkable without a client. |
| T81 |
Smaller M3 rules: implementing an abstract method needs override; @debugOnly methods become no-op stubs in release builds (calls through obj:m() cannot be found without types); exported enums always get their table until tree-shaking arrives (M7); in a class extending a native widget directly, super:m() calls the native method (getmetatable(self).__index.m); constant parts of interpolated strings (literals, consts, wowlua:meta scalars, enum members) are folded into the text; a user decorator's context name is the member name for class members; statements sharing an output line are separated by a space (proposal, M3) |
Gaps found while implementing M3. |
| T82 |
@script(name, { hook? }) is a core decorator for instance methods of widget classes: the method becomes the frame's handler of that script (SetScript, or HookScript with hook = true), installed after the most-derived constructor (for XML frames: before the class's OnLoad); registrations are inherited (user, M3) |
Script handlers are part of the widget-class construction the compiler generates, so the decorator cannot be a library function; core decorators also work before the standard library ships. |
| T83 |
The wow-api-<flavor> declarations are embedded in the compiler (crate wowlua-api, generated files committed) and regenerated with cargo xtask types fetch and cargo xtask types gen: Blizzard's API documentation from wow-ui-source at the newest live client tag of each flavor (test clients excluded), merged with Ketho's vscode-wow-api annotations at a pinned commit. Blizzard's documentation wins where both declare a name. The types packages, flavors.<id>.types and wowlua types sync arrive with the package manager; until then a compiler release updates the API (user, M4) |
Checking works offline and without a registry, and one compiler release has one consistent API. |
| T84 |
The globals of Blizzard's UI code are declared, loosely typed: top-level functions, tables, mixins and values of the FrameXML Lua files, named frames, regions and fonts of its XML files, and the client's global strings (BlizzardInterfaceResources). Names every flavor has go into the shared file (user, M4) |
Addons use them constantly; reporting them as undeclared would bury real problems. |
| T85 |
Per-flavor availability comes from the client's GlobalAPI names (BlizzardInterfaceResources, names only): a function is declared for a flavor only when its client has it. Other flavors' documentation fills gaps in older documentation, and API functions no source documents are declared untyped ((...any) -> ...any) (user, M4) |
Accurate flavor guards (E2030) without losing functions older documentation lacks, such as vanilla's GetTime. |
| T86 |
The compiler declares LibStub as LibStubLibrary & ((major: string, silent: boolean?) -> any) (user, M4) |
Nearly every addon embeds it. |
| T87 |
Basic-mode leniency, found by checking the corpus: globals assigned by any file of the package can be read (the assignment stays W1002); files calling setfenv get no global diagnostics; declared native classes and widgets are open ([string]: any), since addons store fields on frames; fields of open tables not yet assigned read as any; forward-declared locals read as any inside closures; a string literal matches an all-uppercase literal type in any casing ("topleft" for "TOPLEFT"); .. is checked per union member; a function without any annotation is unannotated (W2055 instead of E2006) |
Valid, common Lua must check without errors (M4 exit criterion). |
| T88 |
Shared flavor mode types the code against the first selected flavor's API and takes availability from each flavor's own declarations. Guards are flavor ==/~=, WOW_PROJECT_ID ==/~= (with WOW_PROJECT_* constants or numbers) and existence checks (truthiness, ~= nil), combined through and, or and not, kept after an if whose other branches end, and joined where paths meet. A namespace that exists narrows to the flavors that have it, so members some of those lack still need a guard |
Existing multi-flavor code checks unchanged, and a guard on a namespace does not hide missing members. |
| T89 |
Feature detection in every build mode: an undeclared global or a member a type lacks reads as nil without a diagnostic in an existence test (an if/while/until condition, an operand of and, or or not, == nil/~= nil), and as any in the code the test guards (user, M4) |
Single-flavor builds of multi-flavor code (if C_Reputation.GetFactionDataByID then …) must not report the guard itself. |
| T90 |
W1003 reports only the parameters and loop variables after the last used one of the same list; names starting with _, self, exported and decorated declarations are exempt (user, M4) |
Callbacks need positional parameters they do not use (function(self, event, ...)). |
| T91 |
Lints run in check, build and lint on .wowlua files, and on .lua files with check.lua; wowlua lint shows only lints and errors that stop the analysis; --!nolint turns them off per file (user, M4) |
Lints are useful by default, and plain Lua follows the type checker's opt-in. |
| T92 |
Fixes remove a statement whose names are all unused when its values have no side effects, and otherwise rename to _name (not when the file uses _name); unused import names are removed, and an import left without names becomes import _ from "…" unless it has no load-order effect (import type, wowlua:), in which case it is removed (user, M4) |
Fixes never change behavior or the load order. |
| T93 |
W1004 reports a local, loop variable, local function or parameter (also of a nested function) that shadows a visible local of an enclosing scope; redeclaring in the same block is not reported (user, M4) |
local x = f(x) twice in a block is a common Lua idiom. |
| T94 |
Smaller M4 rules: in strict mode, calls of native methods on self (self:SetSize(…)) do not end the definite-assignment check (E2052); wowlua types convert <path> writes one <name>.d.wowlua for a file or a directory of annotated files and refuses output that does not parse; wowlua fix --manifest reports the manifest as current while only one schema exists; unreachable code (W1005) is reported once per region by the type checker; a callback (no E2050) is a function expression passed as a call argument or with an expected type; a table literal for a union of table types is checked against the first member it fits, and one whose elements all have the expected element type gets it; local x: T without a value is nil until assigned; inferred type arguments are checked against their constraints (E2014); type parameters of every declaration exist before any body is resolved, so a generic class can be used before its declaration or from another module |
|
| T95 |
Reading an array element gives T in every mode, also in strict mode; maps and indexers give V? (user, M4) |
for i = 1, #xs do … xs[i] … end must not need a check, as in TypeScript and Luau. |
| T96 |
Dev builds have every debugging tool built in without code changes: the dev profile defaults to instrument = "all", and the new profile settings monitor and capture-errors default to true in dev only (user, M5) |
Profiling and monitoring should not need decorators or imports while developing. |
| T97 |
In profiles with instrumentation or the monitor, plain .lua files are compiled (line numbers preserved) instead of copied byte for byte; embedded libraries under build.libs-dir stay untouched (user, M5) |
Plain Lua code of the addon shows up in the profiler and monitor too; release output is unchanged. |
| T98 |
The newest runtime among the loaded dev builds serves /wl and the dev window; the others register through a versioned interface (user, M5) |
Addons built by different compiler versions share one console that understands all of them. |
| T99 |
The monitor rewrites the addon's own SetScript, HookScript and C_Timer calls at compile time instead of hooking frame methods globally (M5) |
Global hooks would taint Blizzard frames and other addons. |
| T100 |
Error capture chains into seterrorhandler, records errors from the addon's folder and always calls the previous handler (user, M5) |
Works for all code, including XML handlers and libraries, and keeps BugSack working. |
| T101 |
A dev window (tabs: overview, profiler, monitor, log, errors, watch, inspector) is part of the dev console; watches come from debug.watch, typed expressions (window or /wl watch), and right-clicking values; tables open in a live tree inspector (user, M5) |
A normal debugging workflow inside the game. |
| T102 |
Superseded by T171. Dev builds ship the dev window's fonts (Marcellus SC, Figtree; SIL OFL) in wowlua/fonts/, with their licences in LICENSES.txt; release builds never do (user, M5) |
The window matches the SuuUI design; the client has neither font. |
| T103 |
The dev window's state (position, size, tab, addon) is saved per addon in the SavedVariables WowluaDevDB_<Addon>, added to dev builds' TOC and written to every loaded dev build (user, M5) |
The window comes back where it was after a reload, whichever runtime draws it. |
| T104 |
Until the wowlua registry exists, registry dependencies ("name": "^1.0") are error E4102; dependencies come from git, svn, path and archive sources. The wow-api declarations stay embedded in the compiler (T83) (user, M6) |
No registry server yet; builds stay offline-capable. |
| T105 |
SVN sources use the system svn client. A pin is the last revision that changed the URL, and SVN externals are fetched only with "submodules": true, like git submodules (user, M6) |
Stable pins; one opt-in policy for nested repositories. |
| T106 |
Embedded lua-library entry files load before the package's own files, nested dependencies first, otherwise in declaration order. build.load-order may name a library entry by its output path (libs/LibStub/LibStub.lua) to place it among the package's files (user, M6) |
Libraries are ready before code uses them; migrated addons keep their exact order. |
| T107 |
A reference in the package's XML into <libs-dir>/<install-path>/ names a file of that embedded library and is rewritten to its absolute in-game path; that file is then not listed in the TOC again (M6) |
Existing embeds.xml files keep working after init moves them into src/. |
| T108 |
Archive sources accept file:// URLs for local archives; every download over the network uses HTTPS (M6) |
Local release zips and tests need no server. |
| T109 |
wowlua cache clean removes cache entries unused for 30 days (every fetch and build stamps what it uses); --all empties the cache (user, M6) |
The cache is shared by all projects; recently used entries of other projects survive. |
| T110 |
A wowlua library may only use constant (scalar) config and features values from wowlua:meta; table-valued run-time reads are an error, because the runtime's tables belong to the consuming addon (M6) |
Libraries get their own feature and config values without a second runtime table. |
| T111 |
Warnings and lints in dependency code are not reported to the consumer, and dependency code is never instrumented, monitored or runtime-checked (M6) |
Consumers cannot act on them; the dev tools show the addon's own code (as T97). |
| T112 |
wowlua check checks a library package on its own; wowlua build refuses non-addon packages (M6) |
Library authors check their code; only addons are installable. |
| T113 |
Workspace selection follows Cargo: -p, else the member around the current directory, else default-members, else all members. Multi-package commands run per member; commands on one package need one selected member (M6) |
Familiar behavior; no command silently picks an arbitrary member. |
| T114 |
A workspace addon's generated declaration file declares its @global functions with their signatures, values with their annotated or literal type, and classes and enums as any (M6) |
Generated from source without type inference, so mutually dependent addons need no build of each other; typed class declarations can follow. |
| T115 |
wowlua init pins the interface numbers the adopted TOCs list in wowlua.lock (user, M6) |
The first build generates the same TOCs; updating to new clients stays a deliberate types sync. |
| T116 |
.pkgmeta externals are interpreted like the BigWigs packager interprets them (URL forms, SVN tags, git sub-paths, submodules); TOC lines into an external become its files and keep their load-order position (M6) |
Migrated addons embed the same library files in the same order. |
| T117 |
In compat.pkgmeta mode, manifest entries named like an external may add non-source keys; source keys and settings .pkgmeta also defines are E4005 (M6) |
One source of truth per setting. |
| T118 |
Packager keywords apply before checking; flavor blocks need split flavor mode (or one flavor); @localization(…)@ stays unchanged with W4030; @do-not-package@ handling is T120 (M6) |
Matches the packager's output for the build profiles that exist. |
| T119 |
The M6 exit check compares a packager build (-d -z -l -u) and a wowlua build of the same commit: per flavor the same loaded files in the same order, byte-identical (in-game paths may name src/), assets identical, the same TOC directives apart from Version and X-Wowlua-*; files only the packager ships that the game never loads are listed but allowed. It runs weekly in CI (packager-compat) (user, M6) |
Defines "builds identically" for a tool that moves sources into src/ and copies only reachable library files. |
| T120 |
With compat.packager-keywords, embedded lua-library files get the packager's external processing (keywords from the library's pin, debug blocks, @do-not-package@ removal, XML no-lib-strip blocks commented out); @do-not-package@ blocks are deleted (lines included) in every build whose profile has debug: false (user, M6) |
Libraries ship without their test harnesses (LibDualSpec's /testdb), byte-identical to the packager. |
| T121 |
A workspace root that is a package itself keeps its publish section to itself; members inherit the other shared defaults (M6, BigWigs test) |
A move-folders project's root addon publishes the suite; its project IDs do not belong in every member's TOC. |
| T122 |
init keeps addon-wide ## AllowLoadGameType / ## ExcludeLoadGameType lines in addon.toc-extra besides deriving the flavors from them (M6, BigWigs test) |
The client also applies them to out-of-date loading; the packaged TOC keeps them. |
| T123 |
@project-version@ is the packager's value: the tag on HEAD, else git describe --tags --abbrev=7 (alpha tags excluded); in library files, the tag of a tag external or the short hash of a branch (the packager clones those with --depth 1). build.version and the TOC Version stay wowlua's (user, M6) |
Version strings inside converted addons (in-game version checks) match the packager's builds. |
| T124 |
With compat.packager-keywords, text files of the addon and of embedded libraries (.lua, .xml, .toc, .md, .txt) get LF line endings, like packager -u; files matching compat.plain-copy (from .pkgmeta plain-copy) stay unprocessed; init sets packager-keywords for every converted .pkgmeta project (user, M6) |
Many addons and libraries are committed with CRLF; the packager always normalizes. |
| T125 |
A build.variable-files entry may be a plain path with locales: a file loaded only for some client locales; init keeps TOC lines with [AllowLoadTextLocale …] that way (M6, Masque test) |
Masque and others load one locale file per client locale without [TextLocale]. |
| T126 |
build.flavor-load-order gives a flavor its own load order; init writes it for flavors whose TOC order contradicts the merged one, and those flavors get per-flavor TOCs in their own order (user, M6, Details test) |
Addons with per-flavor TOCs keep each flavor's exact order. |
| T127 |
workspace.external-addons: AddOns fetched and shipped as they are (a .pkgmeta external that move-folders makes an AddOn, such as DBM's voice packs), locked like dependencies, built and installed with the whole workspace, all files and their own TOC, with the packager's external processing; install marks the folders it copies with .wowlua-install (user, M6, DBM test) |
The packager ships them as separate AddOns from one package. |
| T128 |
SavedVariables and SavedVariablesPerCharacter may vary per flavor (addon.flavor-overrides); init keeps each flavor's TOC lists (supersedes T57's union for differing lists) (user, M6, Details test) |
A drop-in replacement keeps every flavor's TOC exactly. |
| T129 |
Publish IDs in TOCs come per flavor from the publish targets that serve it (flavors); init turns differing per-flavor IDs into one target per ID (supersedes T57's majority for differing IDs) (user, M6, Details test) |
Addons such as Details publish separate projects per flavor. |
| T130 |
.pkgmeta is read line by line like the packager's own reader, not as YAML: comment lines skipped, unindented key: lines start a section, indented - item and key: value lines belong to it, one pair of quotes trimmed, inline comments kept in list items, unindented list items ignored, a tab indent is an error (M6, RCLootCouncil/AllTheThings test) |
Unquoted - *.md items are invalid YAML but valid for the packager. |
| T131 |
init of a packager project assigns files by every build that loads them: XML references count from the source tree (@debug@ active), from a release (@non-debug@ uncommented) and from each flavor's blocks; files inside move-folders members belong to the member; externals reached only through files shared with a member are converted too (M6, Capping/MythicDungeonTools test) |
A file loaded only by the release or only by a member must still end up where the packager ships it. |
| T132 |
In a debug build with compat.packager-keywords, an XML reference inside a @debug@ block that does not resolve is left out with W5003 instead of E5002 (user, M6, Capping test) |
The source tree loads a member's files that way; the member addon provides them in wowlua builds. |
| T133 |
..\<Name>\ TOC lines of a move-folders member resolve against the package's other AddOns by name (the root addon and the other members) rather than the source folder's siblings; files the root shares that way and .pkgmeta ignores are not shipped (M6, TellMeWhen/GatherMate2 test) |
Once packaged, members sit next to the root addon. |
| T134 |
A TOC suffix may use - instead of _ (-Mists, -Cata) wherever no legacy meaning exists, like the client and the packager (M6, Tukui test) |
Tukui ships Tukui-Mists.toc. |
| T135 |
A byte order mark at the start of an XML file is ignored (M6, AllTheThings/MythicDungeonTools test) |
The client accepts it; AllTheThings' database XML files start with one. |
| T136 |
compat.license-output (from .pkgmeta license-output): the root's license file ships with the root addon and is copied into each workspace member's output that has none (user, M6, TellMeWhen test) |
The packager copies it into every move-folders addon. |
| T137 |
build.load-order contradictions (E1035) are checked per flavor, and a file loaded by several XML files counts at the first one that loads it for that flavor (M6, Cell test) |
Cell loads its libraries through a different XML file per flavor. |
| T138 |
init reads the packager runs of a project's GitHub workflows; when every run packages a single game (-g <game> -m <file>), flavors packaged with another project file keep its ignore, written as build.flavor-files (which now also restricts assets); a flavor without a run of its own takes its family's file when the family agrees, else the main file (user, M6, KuiNameplates test) |
KuiNameplates packages classic with a file that keeps the classic-only libraries. |
| T139 |
In projects the packager builds per game (single-game runs, or -S / enable-toc-creation), a plain TOC with ## Interface-<Game>: lines is read as one TOC per game type without a TOC of its own, each with that type's numbers and TOC flavor blocks, besides the plain TOC for retail; split builds make none for Interface-Titan, like the packager (user, M6, KuiNameplates test) |
The packager's split (-S) and single-game (-g) builds make those TOCs from it. |
| T140 |
@toc-version-<game>@ in TOCs (the NumyAddon/ToCVersions action) stands for the current interface numbers of the game's flavor (user, M6, BlizzMove test) |
Projects using the action declare their flavors only through the placeholders. |
| T141 |
.gitignore excludes only files git does not track, like git and the packager (M6, AtlasLootClassic test) |
AtlasLootClassic ignores data.lua but tracks several. |
| T142 |
With compat.packager-keywords, repository keywords are substituted in generated TOCs too (## Notes: … @project-version@) (M6, TotalRP3 test) |
The packager substitutes keywords in the whole TOC file. |
| T143 |
In split flavor mode, a flavor's variant of an XML file loads shared files by their in-game path, and the XML analysis covers what every flavor's variant loads; an XML file that loads a file missing from the output gets W5004 (M6, KuiNameplates test) |
Flavor variants live under wowlua/flavors/<id>/; @non-retail@ blocks load files only some flavors ship. |
| T144 |
In a converted packager project (compat.packager-keywords), an embedded library ships every file of its package except dotfiles, its repository's .pkgmeta ignore (for an SVN folder of trunk or a tag, the project root's .pkgmeta) and the project's build.exclude; its plain-copy files and archive libraries are copied unprocessed, its .toc / .md / .txt files get keywords, and @file-*@ take the values of the packager's clone (shallow for tags and branches) (user, M6, packager audit) |
Libraries load textures, sounds and fonts by path; the packager ships whole externals. |
| T145 |
Packager flavor blocks follow the packager's build type: a project released game by game (compat.per-game, set by init) or with one flavor applies them per flavor; a multi-version build leaves @retail@ blocks as written (W4031) and rejects @version-…@ blocks (E4335); @version-titan@ is never active, as wowlua's wrath builds are -g wrath builds (user, M6, packager audit) |
The packager processes flavor blocks only in single-game builds. |
| T146 |
A converted project's package root addon (the root addon, or compat.package-as of a workspace without one) gets the packager's changelog: the manual one processed, else a generated CHANGELOG.md of the commits since the previous release tag (title: compat.changelog-title, else the addon title); a license-output missing from the repository is downloaded from the project's wowace.com page and cached; shipped text files (.md, .txt) get keywords and LF (user, M6, packager audit) |
Packager packages contain these files. |
| T147 |
Release builds of a converted project leave out files git does not track (a note lists them) (user, M6, packager audit) |
The packager skips every untracked file. |
| T148 |
@alpha@ blocks are active in untagged builds and for tags containing "alpha" (not for -beta tags); a GitHub Actions tag build uses the tag it names (GITHUB_REF_NAME) (M6, packager audit) |
The packager's file type rules. |
| T149 |
A git dependency with tag: "latest" resolves to the newest tag among the last 50 commits of the default branch (else that branch) when locking, with the packager's version and revision values; wowlua update re-resolves it. SVN tag: latest follows trunk (user, M6, packager audit) |
The packager's tag: latest. |
| T150 |
init gives the CurseForge target the packager's relations: the .pkgmeta lists, overridden by @curseforge-project-slug: x@ markers in the project's Lua files and by every external's slug as embeddedLibrary (user, M6, packager audit) |
The packager sends these with each upload. |
| T151 |
init drops ## Interface-<Type>: lines (the packager strips them), reads TOCs other than the project's (<name> with the packager's suffixes) without block processing, maps -g 1.15.9-style versions to flavors, ignores move-folders paths outside the package, and @file-revision@ is the commit count of the file's last commit (M6, packager audit) |
Details of the packager's behavior. |
| T152 |
Converted projects also ship, like the packager's overlay of externals: the files the project commits in a library's folder where the library has none (library files win over assets), and the neighbours of a folder move-folders moved up (moved-up on the dependency). An archive with a single folder and no strip-prefix has that folder as its root. A downloaded license-output is saved byte for byte as the packager saves it (M6, Plater/WeakAuras/AtlasLootClassic/AllTheThings) |
Found by checking library folders and shipped files in the packager comparison. |
| T153 |
A converted project's TOC ## Version is its original TOC's value (compat.toc-version, one value or one per flavor, keywords substituted; "" writes none), like the packager's (supersedes T123 for the TOC line; package.version stays wowlua's) (user, M6, drop-in parity) |
Players see the same version string as with the packager. |
| T154 |
Files the packager processes get its line endings: CR LF unless every packager run in the project's workflows passes -u (compat.line-endings, written by init; plain-copy files, archives and downloaded licenses stay as they are) (supersedes T124's LF) (user, M6, drop-in parity) |
The packager's default is CR LF. |
| T155 |
A converted project ships every file the packager copies: all files of the project but dotfiles, .pkgmeta ignores, wowlua's own files and the TOCs it generates (and untracked files in release builds), processed like the packager's; a workspace without a root addon ships its root's files with its package-as AddOn (user, M6, drop-in parity) |
Packages contain the project's docs, tests and unloaded files. |
| T156 |
Externals nothing loads become "load": false dependencies (shipped, no entry files); a manual changelog the repository lacks makes the packager's generated CHANGELOG.md; the packager's processed extensions are case-sensitive (.TXT stays as it is); files shipped as they are need not be reachable (M6, WeakAuras/TellMeWhen/AllTheThings) |
Found by the stricter packager comparison. |
| T157 |
A converted packager project packages and publishes like the packager: one zip with every AddOn folder of the repository (one per released game for compat.per-game, which lists the games its workflows release), named by the packager's templates (compat.archive-name from -n), its upload label as display name, the packager's release type (the working tree does not matter), and with compat.nolib-package (enable-nolib-creation) a nolib zip for GitHub only; init gives it a GitHub target for its origin, takes -p / -w / -a project IDs from its workflows, and a release profile without bundling, minification or optimization, so releases keep matching the packager (user, M7) |
A drop-in replacement publishes what the packager published. |
| T158 |
wowlua publish of a converted packager project uploads like the packager: per package (per game) in the order CurseForge, WoWInterface, Wago, GitHub; a CurseForge upload is tried ten times and its failure aborts the rest; targets without a token are skipped; tokens come from the packager's variables (CF_API_TOKEN/CF_API_KEY, GH_API_TOKEN/GITHUB_API_TOKEN/GITHUB_OAUTH, …) and its .env file; its changelogs (manual with markup type, generated, WoWInterface BBCode, wowi-* settings), release names and release.json are the packager's; a branch push of a tagged commit on GitHub Actions publishes nothing. On a tag, its build.version is the tag and E4319 does not apply (user, M7) |
Existing workflows, secrets and release pages keep working unchanged. |
| T159 |
wowlua login uses the keyring crate (native stores, pure-Rust Secret Service), reads the token from --token, a pipe or a hidden prompt (rpassword), checks CurseForge and GitHub tokens before saving, and wowlua logout removes a token; init --from-pkgmeta leaves packager workflows untouched and prints the change to wowlua publish (user, M7) |
Tokens stay out of shell history and files; migration stays opt-in. |
| T160 |
publish.bundle-addons lives in the workspace root with the targets that upload the bundle; the first listed member (or first default member) names the zip and gives its version and changelog; the root's targets are not shared with members, which keep their own targets; root-level package/publish load bundled members beyond the default members (user, M7) |
One release of a suite without giving up per-member projects. |
| T161 |
rename-private renames the fields of module-local tables that provably do not escape and private/protected methods whose names occur nowhere but at their definitions and the accesses the checker resolved to them (in the package, its declarations, the WoW API declarations, its XML and its libraries); never instance fields, which name saved and serialized data. rename-locals keeps locals named arg. In preserve mode, the passes keep every instruction's line (user, M7) |
Renaming must never change behavior or lose saved data. |
| T162 |
cache-globals: "safe" hoists Lua 5.1's base and library functions (pairs, type, string.format, math.floor, …), WoW's Lua aliases (strsplit, tinsert, wipe, format, …) and C_* namespace tables, never their functions and never other WoW API functions (user, M7) |
A hook installed on an API function or a namespace field after load keeps working. |
| T163 |
A lua-library that calls debugstack or debuglocals (often to find its own folder) is not bundled; the build notes why. Project code gets lint W6001 (user, M7) |
Bundling must not break libraries the author cannot change. |
| T164 |
Optimization is verified by the conformance corpus at every minification pass (bytecode and lines unchanged by every pass but dedupe-strings and rename-private), differential test programs, and an in-game check of a bundled release build (user, M7) |
Thousands of real addon files catch what hand-written tests miss. |
| T165 |
Every bundle segment defines its own protected loader (__wl_load) instead of a runtime load slice; the runtime is the first file of the bundle, wrapped like the others; load errors name the addon, the file and the build id (proposal, M7) |
Bundles of plain Lua addons need no runtime, and every segment stands alone. |
| T166 |
optimize.inline inlines expression helpers only: local functions whose body is a single return <expression>, at calls whose arguments are literals or locals; multi-statement bodies and methods are not inlined, and @inline on them is W3031 (user, M7) |
The common helpers gain the most; statement inlining and methods need more machinery than they are worth now. |
| T167 |
Bundling flattens every TOC-listed XML file that only loads Lua files (<Script file> / <Include file>, the project's own embeds.xml and library XML alike) into those files, unless another XML file loads any of them; identical segments of different flavors are one shared bundle (user, M7) |
Adopted addons load their libraries through XML; without flattening, every XML file splits the bundle. |
| T168 |
rename-private leaves the methods of classes reachable from globals alone (with their whole hierarchy): @global classes, classes in the types or values of @global declarations, classes whose instances or class tables are stored in globals. Globals, fields of anything that escapes, exports and strings were never renamed (user, M7) |
Untyped addons call and hook other addons' methods by name; a renamed method would silently miss such a hook. |
| T169 |
Performance optimization is milestone M11, after the registry; its exit is a measured speed-up on a benchmark suite run on the embedded Lua 5.1, with behavior unchanged on the corpus and differential tests, and an in-game check (user, M7) |
The standard library and test runner come first and give typed code to optimize and tests to prove it unchanged. |
| T170 |
CurseForge, Wago and WoWInterface targets build unminified unless the target sets minify; W6010 warns about any minification for them (no longer about renaming on other targets). GitHub targets keep the profile's level, so a project's own builds may be fully minified (user, M7) |
The addon sites take readable code only; minified builds are for a project's own distribution. |
| T170 |
The dev window has eight tabs: overview, performance (recordings and eight views: functions, tree, flame, by event, timeline, slow, garbage, bench), monitor, log, errors, watch, inspector, hints (user, M11) |
Recordings are the main idea of performance work; the existing tabs stay as they were. |
| T171 |
The dev window follows the wowlua design system and ships Martian Mono and IBM Plex Mono (SIL OFL) in wowlua/fonts/ of dev builds, replacing Marcellus SC and Figtree; supersedes T102 (user, M11) |
wowlua has its own brand; the window and the website share one look. |
| T172 |
A recording is one concept: live, one per combat and one per boss pull made automatically, and manual ones. The last 20 automatic recordings per addon are kept; manual and export-marked ones are never dropped automatically (user, M11) |
One list and one picker for every slice of time, and any two can be compared. |
| T173 |
Recordings are compared per minute of recording; within ±1% counts as unchanged (user, M11) |
A longer pull is not slower just because it is longer. |
| T174 |
Cheap measurements run in every dev build; call paths and garbage per function are opt-in. The profile settings profile-call-paths, profile-garbage, frame-budget (1 ms) and slow-call (2 ms) give the starting values; the window changes them at run time and saves them per addon (user, M11) |
Call paths and per-call garbage cost on every call; thresholds depend on the addon. |
| T175 |
The desktop export is an HTML report (wowlua prof report) in the website style (user, M11) |
Full-size views, shareable as one file. |
| T176 |
Performance hints run live while the window is open and use P codes in the debugging range (P71xx events and scripts, P72xx garbage, P73xx frame time), explained by wowlua explain (user, M11) |
Findings read like the compiler's diagnostics and link to the same explanations. |
| T177 |
The compact HUD shows every loaded wowlua dev build, one row each; it hides while the dev window is open and during cinematics, not in pet battles (user, M11) |
Several addons in development are compared at a glance. |
| T178 |
Bench splits runs longer than 50 ms across frames and does not run in combat; it keeps the last 20 runs per addon. The slow-call log keeps the last 200 slow calls per addon (user, M11) |
A large n never freezes the game; history stays bounded. |
| T179 |
Exported recordings travel through WowluaDevDB_<Addon>; wowlua prof report finds them like wowlua dev finds the WoW installation and asks (or takes --account, --client) when several accounts or clients have data (user, M11) |
SavedVariables are the only way data leaves the game. |
| T180 |
Dev builds show an error toast for newly captured errors while the dev window is closed. Bursts within 2 s collapse, it hides after 8 s, waits for combat to end and makes no sound (user, M11; timings: proposal) |
Errors are seen when they happen, without opening /wl. |
| T181 |
The in-game performance tools are part of milestone M11 (user) |
They measure what M11's optimizations change. |
| T182 |
Load-order usage analysis tracks fields below global and ns keys (Lib.Register, ns.Tools.Run) as names of their own, follows local aliases and table identity (ns.API = SuuUI, local Roles = {} with ns.Roles = Roles), and gives fields the same W1033/W1034 as globals. A read whose field is only written through an untraceable table is W1033 only when such a write exists at load time (user, issue #1) |
Addons define their API as table fields; ordering by the root name alone loaded callers before the functions they call. Warning on every untraceable table would flood real addons. |
| T183 |
The editor extension targets VS Code; other editors use wowlua lsp directly (user, M9) |
The user's editor; the LSP stays editor-neutral. |
| T184 |
wowlua ships its own test framework (wowlua test, wowlua:test, wowlua:mock) with coverage and reports; tests need no third-party library (user, M9) |
One binary covers write, build, test and publish. |
| T185 |
Tests run on wowlua's own Lua 5.1 VM written in Rust, with WoW's Lua differences, taint and secret values, rather than stock Lua, elune (a C fork with taint, dormant since 2024) or another VM (user, M9) |
Full control over taint, secrets, coverage and reports, and no C toolchain; the cost is building and verifying a VM (open design questions in the emulation chapter, §17.9). |
| T186 |
Blizzard's FrameXML runs in tests as secure code only when test.blizzard-ui is set (user, M9) |
It is what catches addon-to-Blizzard taint, at a startup cost most tests do not need. |
| T187 |
wowlua:mock offers spies, stubs, fakes of engine functions and scenario controls; a protected action refused because of taint fails the test with its taint chain (user, M9) |
Taint is the hardest class of in-game bug to find; tests must surface it. |
| T188 |
Coverage measures lines and statements, branches (including and/or/??/?.), functions and conditions, counted by the VM; reports are terminal, JUnit, TAP, JSON, GitHub, lcov, Cobertura and a self-contained HTML report in the wowlua design system; thresholds in test.coverage are optional and fail the run (user, M9) |
Every common CI and editor format, plus a report in wowlua's own look. |
| T189 |
M9 stays one milestone; StdTest's in-game suites stay until the emulation reproduces the in-game bugs found so far (user, M9) |
The emulation earns trust by catching what the client caught. |
| T190 |
The VM runs a register bytecode of its own, compiled from the compiler's output, each instruction carrying its source span (user, M9) |
Lua 5.1's proven design for speed; coverage, taint and spans attach per instruction. A tree walker is slower; luac bytecode loses wowlua's spans. |
| T191 |
The VM's memory is managed by a tracing garbage collector over an arena, in safe Rust (user, M9) |
Cycles, __gc and weak tables behave like the client's; reference counting or no collection would not. |
| T192 |
Coroutines are stackless: each is its own VM stack (user, M9) |
No native stacks or overflow; yields still stop at pcall, metamethods and native calls, as in the client. |
| T193 |
Taint is exact: every stack slot, table field, upvalue and object carries the taint of the code that wrote it, as in the client (user, M9) |
Taint spreads through locals and upvalues in real addons; tracking only tables would miss it. Costs memory per slot, always on. |
| T194 |
A call that crashes the client (such as strsplit or unpack with thousands of results tripping its Lua assertion) fails in the emulation with an error naming the call and the client's assertion; the run continues (user, M9) |
The test shows the line that would take the game down; stopping the run or only warning would hide it or the other results. Limits come from crash-search probes, run separately from /stdtest probe. |
| T195 |
Secure (untainted) code may use secret values; what it computes from one is secret again. Addon code always runs tainted, so no probe can observe it: the rule follows Blizzard's documentation ("preventing most operations on them from occurring on tainted code paths") and is marked unverified in the emulation's coverage report (user, M9) |
Blizzard's UI code wraps values with secretwrap at their source and hands them on; a secure helper that returned plain results would leak them to addons. |
| T196 |
Taint reports show the full chain: every tainted value and execution knows the event behind its taint (a store, with what tainted the storing code, or the read that tainted an execution), so a report walks from a refused action through any number of hops of secure code back to the addon line (user, M9) |
One hop would show the Blizzard line a taint passed through, not the addon line behind it. The origin fits in a value slot's padding, and records are kept per site and cause, so it costs no memory per value. |
| T197 |
Taint is reported where a protected action is refused (the test fails) and where a test asserts that a value or handler stays secure; secure code becoming tainted is not reported by itself (user, M9) |
Blizzard code reading addon globals is common and mostly harmless; a report belongs where taint has an effect or a test asks for it. |
| T198 |
The engine's API functions are Rust natives of the VM, as the client's are C functions: they show as [C] in stack traces, raise the client's argument errors, and run outside the taint rules of Lua code; their behaviour ports the M8 test mock's (user, M9) |
Exact where tests look (stack traces, error messages, taint), at the cost of writing the behaviour in Rust. |
| T199 |
The engine's API data comes from wowlua-types-gen: each flavor's documentation as compact JSON with every key Blizzard's tables carry (crates/wowlua-api/data/<flavor>.api.json.gz), next to the .d.wowlua declarations (user, M9) |
One parser for both outputs, which stay in sync; no flag is lost in a model; the engine needs no documentation at build time. |
| T200 |
The M8 mock's stdlib tests move onto the VM and the engine as it grows, one mock feature at a time; the mlua mock is deleted once they all pass (user, M9) |
The tests then check the engine against the behaviour they already pin down. |
| T201 |
wowlua:mock sets up a simulated world of entities: tests spawn units and act on them (mock.spawnUnit, mock.player:target(u), u:damage(n)), unit tokens follow from relationships, and every change fires the events the client fires, in its order (user, M9) |
Tests describe the situation, not the client's internals; the events and API results follow as in game, so an addon's reaction to them is what is tested. |
| T202 |
mock.fireEvent fires only events the flavor documents, with the payload checked against the documentation (user, M9) |
A misspelled event or a wrong payload fails the test instead of testing nothing. |
| T203 |
Game data comes from the tests by default (mock.defineSpell, mock.defineItem); optionally wowlua data pull --wow <path> reads it from the user's own client into a local cache, and wowlua test --game-data uses it; off by default, so CI tests with test data only (user, M9) |
wowlua distributes no game data; CI has no WoW installation. |
| T204 |
With test.blizzard-ui, wowlua test downloads the flavor's Blizzard UI code (a wow-ui-source tarball at the flavor's client version) and its global strings on first use into a user cache keyed by version (user, M9) |
About 5 seconds once per flavor (7 MB), then cached; works in CI and without WoW installed. |
| T205 |
Load order: W1033 is not reported for definers whose relative order the layout fixes: both placed by build.load-order (directly or through the XML file that loads them), both loaded by one XML file (document order), or an XML file and a file it loads; this extends T73 (user, wowlua#3) |
An XML file's scripts load in document order, which wowlua keeps; a default locale file and the guarded locale files overriding it (MythicDungeonTools) are not ambiguous. On the corpus W1033 drops from 98 to 6. |
| T206 |
Load order: in a load-on-demand addon with required addon-dependencies, a load-time read of a field of a table the addon does not define (a global it never assigns, not one of its SavedVariables) is not W1034, even if the addon writes that field at run time (user, wowlua#3) |
Such an addon loads after its required addons have loaded and run, so the table's owner may have set the field (DBM-GUI reads DBM.Options, which DBM-Core sets). Only W1034 changes, never an order. |
| T207 |
Load order: code that runs only when a parameter is not a function (type(p) ~= "function", not p, p == nil, and the other branches of their opposites) is not load-time while every call reaching the function passes a function for that parameter; reassigned parameters, untraced arguments and callback invocations count as possibly anything (user, wowlua#3) |
Registration helpers check their handlers (if type(handler) ~= "function" then print(…)); that branch read the addon's locale through print in every file registering a handler (AllTheThings: 79 W1035). Sound: the branch cannot run for those calls. |
| T208 |
Lua compile results are kept between runs in $WOWLUA_HOME/cache/compile/<hash> (blake3 of the bytes, the chunk name and the Lua build), on by default; WOWLUA_NO_COMPILE_CACHE=1 or build.compile-cache: false turns it off; the least recently used entries go when it grows past build.compile-cache-size (MiB, default 256), at most once a day; cache clean removes entries unused for 30 days, --all all of them; only the CLI uses the cache, never a library build of the driver (user, wowlua#50) |
Compiling Lua for its limits was 23–32% of a build's CPU time. A warm check of AllTheThings takes 34% fewer instructions, SuuUI's 31%; a cold one about the same wall time. |
| T209 |
The debugger serves the Debug Adapter Protocol (wowlua debug) and a live UI channel: the VM's debug hooks (line, call, return, error) pause and step, also into event dispatch, scripts and timers; the frame tree streams to a viewer that sends input back (user, M12) |
Editors already speak DAP; owning the VM shows what the client hides (taint, secrets, every OnUpdate). |
| T210 |
Game data and assets (DB2 tables, textures, atlases, fonts, models) are read only from the user's own WoW installation (CASC), natively or in the browser, and never distributed; this extends T203 (user, M13) |
Blizzard's data is not wowlua's to redistribute; tests without an installation keep using test and profile data. |
| T211 |
The renderer uses wgpu; text is measured with the client's own fonts, and the engine uses those metrics for GetStringWidth, wrapping and truncation (proposal, M13) |
One renderer natively and in the browser; tests then match the client's text layout too. |
| T212 |
The compiler pipeline, the VM and the engine stay WebAssembly-compatible (wasm32-unknown-unknown): no file system, processes or threads in them; the engine's file access goes through an interface; CI builds them for wasm (user, M14) |
The browser playground and debugger need them; checking from now on keeps native-only code from creeping in. |
| T213 |
Tests start from a world profile: a data file with the character, gear, bags, position, auras, nearby units, CVars, bindings and saved variables, recorded from the client with /stdtest snapshot (names and GUIDs scrubbed) or written by hand; every part is drivable during the test (user, M9) |
A test reproduces the exact situation a bug happened in; data files are edited and diffed easily. |
| T214 |
expect is generic: expect(x) is Expect<T>, its matchers take T, and a spy keeps its function's type so toHaveBeenCalledWith checks the arguments (user, M9) |
Mistakes in tests fail at compile time, like in the code under test. |
| T215 |
Parametrized tests with @cases(list): each case runs and is reported on its own, named by its values, and is checked against the test's parameters (E8120) (user, M9) |
One test function covers a table of inputs without loops hiding which case failed. |
| T216 |
A test may wait (mock.waitFor, waitFrames, waitSeconds, waitUntil) in straight-line code: tests run as coroutines and waiting advances the virtual clock and frames; a wait past the timeout (10 virtual seconds by default) fails the test (user, M9) |
Asynchronous addon code is tested as it reads, deterministically and instantly. |
| T217 |
Widget matchers toBeShown, toBeVisible, toHaveText, toHaveSize, toBeAnchoredTo and tooltip lines (toHaveLines) (user, M9) |
GUI tests (T213) assert what the player sees. |
| T218 |
Snapshot tests: toMatchSnapshot against __snapshots__/<file>.snap, values serialized deterministically (frames as their widget tree with layout and text); wowlua test -u updates them (user, M9) |
Large outputs and frame layouts are checked without hand-written expectations, and reviewed in diffs. |
| T219 |
Compile-time tests: @expectError(code, …) passes when the function produces exactly those diagnostics; it never runs (user, M9) |
Libraries and the stdlib pin down what their types reject. |
| T220 |
Custom matchers with matcher(name, fn), typed by fn's signature and exported by libraries (user, M9) |
Domain assertions read like the built-in ones and are type-checked too. |
| T221 |
@bench functions live in test files and are run by wowlua bench (M11), not by wowlua test; specified in M9, built in M11 (proposal, M9) |
Benchmarks share the tests' setup and world; their runner belongs with the optimizations they measure. |
| T222 |
Code examples in --- doc comments run as tests; specified in M9, built with the language server's documentation work (S8) (proposal, M9) |
Library documentation cannot go stale. |
| T223 |
import internal a, B from "/module" binds a module's file-level locals and types in test files only (E8119 elsewhere); addon builds are unchanged (user, M9) |
Private helpers are tested directly without exporting them. |
| T224 |
mock.module(namespace, replacement) replaces the named exports of a module (its namespace import) for the rest of a test file, checked against the module's export types; test builds read imports at each use so every importer sees it (user, M9) |
Collaborators are faked with type safety, without dependency injection in addon code. |