merge: absorb samwhosung/main on top of the upstream merge

Same fork policy: upstream structure wins, local-unique re-instated.

Cargo.toml unions both measured dev-profile philosophies (codegen-units=1 +
lto=off from samwhosung, debug=line-tables-only + split-debuginfo from the
fork's disk measurements).

glue_booth.rs takes samwhosung's typed GlueScene (token/of_race/from_model_path)
and re-adds the fork's TurtleWoW races (Goblin=9, BloodElf=10) as variants;
their glue fog stays on Human's row, exactly as the fork's scene_fog fallback did.

ui script takes samwhosung's mouseover/hyperlink rework (world_mouseover,
minimap_blip, hyperlink.rs) over the fork's inline hover_link/span_at/pointer
path — the rework covers the same OnHyperlinkEnter/Leave behaviour.

game_plugins.rs keeps the fork's re-measured ceilings (466 / 5,509); mail.rs
keeps the explicit 13-tuple with a float daysLeft; MAP.md takes the superset.
This commit is contained in:
chairmanpow
2026-10-12 17:24:31 +01:00
403 changed files with 52938 additions and 9951 deletions
+2 -1
View File
@@ -113,7 +113,8 @@ run() {
run fmt cargo fmt --all -- --check
run clippy cargo clippy $pflags --all-targets -- -D warnings
run test env BENILLA_SKIP_LOG="$skips" cargo test $pflags
# --no-fail-fast: one red test binary does not hide the failures in the ones after it.
run test env BENILLA_SKIP_LOG="$skips" cargo test --no-fail-fast $pflags
# Data-gated tests skip without the install or the addon corpus and libtest swallows their line,
# so each skip is also logged to $BENILLA_SKIP_LOG and counted here.
if [ -s "$skips" ]; then
+2 -2
View File
@@ -123,12 +123,12 @@ else
"corpus=$([ -d "$root/wow-addons-vanilla" ] && echo yes || echo no)):" \
"data-gated tests skip silently on this machine"
fi
run test env ${require_data:+BENILLA_REQUIRE_DATA=1} BENILLA_SKIP_LOG="$skips" cargo test --workspace
run test env ${require_data:+BENILLA_REQUIRE_DATA=1} BENILLA_SKIP_LOG="$skips" cargo test --no-fail-fast --workspace
report_skips test
# test-no-install: refuses a test that reads the install without `wow_data_or_skip!`. `WOW_DATA=`
# (set, empty) is the resolver's "no install"; no build reads it, so the test binaries are reused.
run test-no-install env -u BENILLA_REQUIRE_DATA WOW_DATA= cargo test --workspace
run test-no-install env -u BENILLA_REQUIRE_DATA WOW_DATA= cargo test --no-fail-fast --workspace
# doc-links: refuses a doc link to a `crate::`, `super::`, `self::` or `Self::` path whose leaf is
# declared nowhere in the workspace. Narrow on purpose: every other unresolved link is a convention.
+212
View File
@@ -0,0 +1,212 @@
#!/usr/bin/env python3
"""Regenerate `reference/1.12-glue-surface.tsv`: every Lua function the glue screens' VM has in `_G`.
scripts/gen-reference-glue-surface.py [--shapes FILE] [--globals FILE] [--out FILE]
The inputs are vendored under `reference/`, so this runs from any clone and reproduces the
committed table byte for byte.
The glue builder (`0x46a7b0`) starts from the bare base set `0x7039e0`: a rebuild resets the Lua
state (`0x703b80` -> `0x7039e0`, at `0x46a87b`), and the first build (`0x46a505`, passing 0) skips
that reset (`je` at `0x46a879`) and takes the state the startup init left (`0x402aee`). It then
calls the glue registrar `0x46abb0`, which installs seven registrar tables and nothing else:
0x51bb20 table 0x8503f4 6 shared: in-game, `0x51bb60` calls it before its own table
0x706350 table 0x872e74 4 shared: in-game at `0x490251`; then the 17 widget method tables
0x46ddf0 table 0x8373b8 60 glue core
0x4736c0 table 0x838578 11 character select
0x471a20 table 0x8380f8 24 character create
0x46f480 table 0x837ea0 10 realm list
0x4587f0 table 0x835a50 4 shared: in-game at `0x4902a6`
Each registrar is a `0x704120` loop over its table with the bound read from its own `cmp`, the
counts above. `0x7039e0`, FrameScript's Lua init, is the base set both states share: the 36-entry
base array `0x811e28` (`1.12-shapes.tsv`'s `baselib` table), the `string`, `table`, `math` and
`bit` libraries (fields, not globals, so not rows here), `math`'s global `__pow` ([`POW`]), and
the compat chunk `0x8722e8` run at `0x703b26`. The chunk's 39 function assignments are
[`COMPAT`]: 32 bind a library function under a bare name (`tinsert = table.insert`), each row
taking that binding's `fn` and table, and 7 wrap one in a degree-converting Lua closure (`sin`,
`acos`, ...), `fn` `lua`. Its one other assignment, `PI = math.pi`, is a number. Every engine
function `1.12-globals.tsv` has that no `global` or `baselib` row of the shapes table names must be
one of these, so a chunk the list missed fails the generator.
Columns:
scope base (0x7039e0) | shared (a registrar the in-game state calls too) | glue.
registrar the function that installs the row.
fn, table_va the binding and its table; a compat alias carries the library binding it names,
or fn `lua` and table `-` for a closure; `__pow` has no table.
arity the shapes table's return arity, `?` for a closure.
world_fn a glue row's in-game namesake from any other `global` table, else `-`. The in-game
state never sees a glue row, but the name is shared: a glue verb benilla builds is a
different binding from the in-game one of the same name.
note `shape-differs` where both shapes are exact and the argument count or the return
arity differs between the glue binding and its in-game namesake.
"""
import argparse
import os
import sys
# (registrar, table_va, rows, scope), in the order `0x46abb0` calls them.
REGISTRARS = [
(0x51BB20, 0x8503F4, 6, "shared"),
(0x706350, 0x872E74, 4, "shared"),
(0x46DDF0, 0x8373B8, 60, "glue"),
(0x4736C0, 0x838578, 11, "glue"),
(0x471A20, 0x8380F8, 24, "glue"),
(0x46F480, 0x837EA0, 10, "glue"),
(0x4587F0, 0x835A50, 4, "shared"),
]
BASE_INIT = 0x7039E0
BASE_TABLE = 0x811E28
BASE_ROWS = 36
# The libraries `0x7039e0` opens, by the name the compat chunk reaches them under.
LIBRARIES = {"table": 0x822D40, "math": 0x822C60, "string": 0x822D88, "bit": 0x822C18}
# The compat chunk's function assignments, in its order: (global, "library.field"), or None for a
# Lua closure that converts degrees around the `math` function of the same name.
COMPAT = [
("foreach", "table.foreach"), ("foreachi", "table.foreachi"), ("getn", "table.getn"),
("tinsert", "table.insert"), ("tremove", "table.remove"), ("sort", "table.sort"),
("abs", "math.abs"), ("acos", None), ("asin", None), ("atan", None), ("atan2", None),
("ceil", "math.ceil"), ("cos", None), ("deg", "math.deg"), ("exp", "math.exp"),
("floor", "math.floor"), ("frexp", "math.frexp"), ("ldexp", "math.ldexp"), ("log", "math.log"),
("log10", "math.log10"), ("max", "math.max"), ("min", "math.min"), ("mod", "math.mod"),
("rad", "math.rad"), ("random", "math.random"), ("randomseed", "math.randomseed"),
("sin", None), ("sqrt", "math.sqrt"), ("tan", None),
("strbyte", "string.byte"), ("strchar", "string.char"), ("strfind", "string.find"),
("format", "string.format"), ("gsub", "string.gsub"), ("strlen", "string.len"),
("strlower", "string.lower"), ("strrep", "string.rep"), ("strsub", "string.sub"),
("strupper", "string.upper"),
]
# `luaopen_math` 0x7fb470 also sets the global `__pow` to 0x7fb050 (`0x7fb4af`-`0x7fb4d2`), which
# Lua 5.0 calls for `^`.
POW = ("__pow", 0x7FB050)
REFERENCE = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "reference"))
def rows(path, header):
if not os.path.exists(path):
sys.exit(f"no table at {path}")
for line in open(path, encoding="utf-8"):
if line.startswith("#"):
continue
f = line.rstrip("\n").split("\t")
if f and f[0] == header:
continue
yield f
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--shapes", default=os.path.join(REFERENCE, "1.12-shapes.tsv"))
ap.add_argument("--globals", default=os.path.join(REFERENCE, "1.12-globals.tsv"))
ap.add_argument("--out", default=os.path.join(REFERENCE, "1.12-glue-surface.tsv"))
a = ap.parse_args()
# name, fn, pair_va, table_va, table_kind, argc, argc_conf, arity, arity_conf, ...
shapes = [f for f in rows(a.shapes, "name") if len(f) >= 9]
by_table = {}
for f in shapes:
by_table.setdefault(int(f[3], 16), []).append(f)
out = []
def take(table_va, n, registrar, scope):
got = by_table.get(table_va, [])
if len(got) != n:
sys.exit(f"table 0x{table_va:x}: the registrar loops {n} rows, the shapes table has {len(got)}")
for f in got:
out.append({"f": f, "scope": scope, "registrar": registrar})
take(BASE_TABLE, BASE_ROWS, BASE_INIT, "base")
for registrar, table_va, n, scope in REGISTRARS:
take(table_va, n, registrar, scope)
# The base set's Lua side: the compat chunk's aliases and `__pow`, each a binding of a library
# the shapes table carries, or a Lua closure.
fields = {}
for lib, table_va in LIBRARIES.items():
for f in by_table.get(table_va, []):
fields[f"{lib}.{f[0]}"] = f
aliases = []
for name, target in COMPAT:
if target is None:
aliases.append((name, "lua", "-", "?"))
continue
f = fields.get(target)
if f is None:
sys.exit(f"compat alias {name} = {target}: no such library binding in {a.shapes}")
aliases.append((name, f[1], f[3], f[7]))
aliases.append((POW[0], f"0x{POW[1]:x}", "-", "1"))
# A 1.12 engine function no registrar row names is one of the aliases, or the list is short.
registered = {f[0] for f in shapes if f[4] in ("global", "baselib")}
alias_names = {x[0] for x in aliases}
for f in rows(a.globals, "name"):
if len(f) >= 3 and f[1] == "function" and f[2] in ("engine", "lua"):
if f[0] not in registered and f[0] not in alias_names:
sys.exit(f"{f[0]} is a 1.12 engine function no registrar and no compat alias names")
# In-game namesakes: rows of every other `global` table, which only the in-game state installs.
ours = {t for _, t, _, _ in REGISTRARS}
world = {}
for f in shapes:
if f[4] == "global" and int(f[3], 16) not in ours:
world.setdefault(f[0], []).append(f)
lines = []
differs = 0
for r in out:
f = r["f"]
name, fn, table_va, argc, argc_conf, arity, arity_conf = f[0], f[1], f[3], f[5], f[6], f[7], f[8]
world_fn, note = "-", ""
if r["scope"] == "glue" and name in world:
w = world[name]
world_fn = "|".join(x[1] for x in w)
notes = []
for x in w:
if argc_conf == "exact" and x[6] == "exact" and argc != x[5]:
notes.append(f"argc {argc} here, {x[5]} in-game ({x[1]})")
if arity_conf == "exact" and x[8] == "exact" and arity != x[7]:
notes.append(f"arity {arity} here, {x[7]} in-game ({x[1]})")
if notes:
note = "shape-differs: " + "; ".join(notes)
differs += 1
lines.append((r["scope"], f"0x{r['registrar']:x}", name, fn, table_va, arity, world_fn, note))
for name, fn, table_va, arity in aliases:
lines.append(("base", f"0x{BASE_INIT:x}", name, fn, table_va, arity, "-", ""))
names = [l[2] for l in lines]
if len(names) != len(set(names)):
sys.exit("a name appears twice in the glue state; the table is keyed by name")
count = {s: sum(1 for l in lines if l[0] == s) for s in ("base", "shared", "glue")}
path = os.path.normpath(a.out)
with open(path, "w", encoding="utf-8") as fh:
fh.write(
"# The glue screens' Lua surface in the 1.12.1 client (build 5875): every function in `_G`\n"
"# once the glue builder 0x46a7b0 has installed its registrars, before GlueXML loads.\n"
"# Generated by scripts/gen-reference-glue-surface.py from reference/1.12-shapes.tsv and\n"
"# reference/1.12-globals.tsv; the generator's header carries the registrars and the method.\n"
"#\n"
"# scope base = FrameScript's Lua init 0x7039e0, which both states run; shared = a\n"
"# registrar the in-game state calls too (0x51bb20, 0x706350, 0x4587f0); glue =\n"
"# a glue-only registrar table (0x46ddf0, 0x4736c0, 0x471a20, 0x46f480).\n"
"# fn, table_va, arity as 1.12-shapes.tsv has them. A base alias the compat chunk\n"
"# 0x8722e8 defines carries the library binding it names (`tinsert` is table\n"
"# 0x822d40's `insert`), or fn `lua` for a degree-converting closure; math's\n"
"# global `__pow` (0x7fb050) has no table.\n"
"# world_fn a glue row's in-game namesake, a different binding; `-` if none.\n"
"# note shape-differs = both shapes exact, and the glue binding takes or returns a\n"
"# different count than its in-game namesake.\n"
f"# {len(lines)} functions: {count['base']} base, {count['shared']} shared, {count['glue']} glue;"
f" {differs} glue rows differ in shape from their in-game namesake.\n"
"scope\tregistrar\tname\tfn\ttable_va\tarity\tworld_fn\tnote\n"
)
for l in lines:
fh.write("\t".join(l) + "\n")
print(f"wrote {path}: {len(lines)} functions, {differs} shape differences")
if __name__ == "__main__":
main()
+9
View File
@@ -104,6 +104,15 @@ printf '%s' "$plain" | grep -q "logout-smoke: empty roster" &&
fail "$user has no characters — the re-entry leg cannot run (make one, or rig it: WOW_RIG=…)"
printf '%s' "$plain" | grep -q "logout-smoke: done" ||
fail "the round trip never completed (no 'logout-smoke: done')"
# The logout returns to character select on the link the world was entered by and re-lists there
# (`0x5aaeb0` → `0x46b880`): a roster on that world session, and one realmd logon for the walk.
printf '%s' "$plain" | grep -q "logout-smoke: FAILED" &&
fail "$(printf '%s\n' "$plain" | sed -n 's/.*logout-smoke: FAILED — //p' | tail -1)"
printf '%s' "$plain" | grep -q "logout-smoke: roster after the logout on the same link" ||
fail "no roster came on the same link after the logout"
logons="$(printf '%s\n' "$plain" | grep -cF "net: parked at the realm list")"
[ "$logons" -eq 1 ] ||
fail "$logons realmd logons in the round trip, expected 1 — the logout logged on again"
# The re-entered character must be drivable: the leg lists what would block movement (a /logout
# root must end with its session), and anything but `none` fails.