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:
+2
-1
@@ -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
@@ -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.
|
||||
|
||||
Executable
+212
@@ -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()
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user