a fresh clone gets the whole project, and the maintainer's tooling moves out

What a clone lacked: the gates went green with a thousand skipped tests and said nothing, every
login script assumed a worktree-pool slot and its probe account, the client defaulted to the
maintainer's account, a docker path and a home directory were in the code, the temp files broke
on GNU coreutils, and two generators could only run against a sibling repo.

Now the test gates print how many data-gated tests skipped and why; a scripted run logs in as
the account its checkout declares in `.probe-identity` or as the three variables name, never a
default, and the client refuses a scripted login on any other account; the event and message
tables regenerate from vendored inputs, byte for byte; the docs state the real prerequisites.

Out of the tree, into the maintainer's own tooling: the worktree pool, its hooks, the perf leg
runner, the cross-platform compile and the Vulkan container leg — a way of working, and setups
not every machine has. One hook stays: no writes into the install.

Two tests that could never run are gone with their subject: the quest-timer geometry diff
compared a copy of the window that no longer exists (and joined a chain path onto a folder, so
it always skipped), and the FrameXML flag gate now reads the stock files off the player's own
patch chain instead of an extracted folder only one machine had.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 17:27:14 +07:00
co-authored by Claude Fable 5.1
parent 3e12fa1d7f
commit b05d5d18d5
106 changed files with 2145 additions and 3254 deletions
+1 -1
View File
@@ -18,7 +18,7 @@
# demand. The real-addon half of the question belongs to the vanilla addon harness (1188 phase 6).
#
# **The addon corpora are not 1.12 codebases, and that shapes what a grep over them means.**
# `~/dev/wow-addons-top20` and `~/dev/wow-addons-vanilla` hold addons that RUN on 1.12 — which is
# The vanilla addon corpus (`benilla_formats::addon_corpus`) holds addons that RUN on 1.12 — which is
# not the same as addons written for 1.12 and nothing else. Most of the big ones ship one codebase
# for several clients and pick a path at load: pfUI registers every module with a version list
# (`RegisterModule("loot", "vanilla:tbc", …)`, matched against `pfUI.expansion`), pfQuest keeps a
+13 -5
View File
@@ -12,7 +12,7 @@
# Scope rule — conservative by construction, unknown means FULL, never "probably fine":
# crates/<dir>/** → that crate (owner read off cargo metadata, not the dir name)
# assets/ui/** → benilla-app (ui_script's shipped-UI tests read it at runtime)
# *.md → no gate can change (same rule as wt.sh docs_only)
# *.md → no gate can change (gates.sh's docs-only rule)
# anything else → escalate: exec scripts/gates.sh (workspace manifests, .cargo/,
# rust-toolchain, scripts/, shaders outside crates, …)
# Change set = fork point vs main + staged + unstaged + untracked — the round's work, not the last
@@ -48,7 +48,7 @@ dirs=""
full_reason=""
while IFS= read -r f; do
case "$f" in
docs/* | *.md) ;; # provably gate-inert (wt.sh docs_only)
docs/* | *.md) ;; # provably gate-inert (gates.sh's docs-only rule)
assets/ui/*) dirs="$dirs benilla-app-DIR:crates/benilla-app" ;;
crates/*/*) d="${f#crates/}"; dirs="$dirs DIR:crates/${d%%/*}" ;;
*) full_reason="$f" ;;
@@ -109,8 +109,9 @@ for p in $pkgs; do pflags="$pflags -p $p"; done
echo "check: scope = $pkgs"
echo "check: (changed crates + everything that depends on them; full chain still runs at sync→land)"
log="$(mktemp -t benilla-check)"
trap 'rm -f "$log"' EXIT
log="$(mktemp "${TMPDIR:-/tmp}/benilla-check.XXXXXX")"
skips="$(mktemp "${TMPDIR:-/tmp}/benilla-check-skips.XXXXXX")"
trap 'rm -f "$log" "$skips"' EXIT
run() {
local name="$1"; shift
if ! "$@" >"$log" 2>&1; then
@@ -123,7 +124,14 @@ run() {
run fmt cargo fmt --all -- --check
run clippy cargo clippy $pflags --all-targets -- -D warnings
run test cargo test $pflags
run test env BENILLA_SKIP_LOG="$skips" cargo test $pflags
# The data-gated tests skip without the install or the addon corpus, and libtest swallows the
# line; the resolver logs each skip to $BENILLA_SKIP_LOG and this counts them (gates.sh says why).
if [ -s "$skips" ]; then
echo " test: $(wc -l <"$skips" | tr -d ' ') data-gated tests SKIPPED on this machine —"
sort "$skips" | uniq -c | sort -rn | sed 's/^ *\([0-9]*\) \(.*\)/ \1 × \2/'
echo " (they run where the data is: docs/CONTRIBUTING.md, \"Setting up\")"
fi
echo "CHECK GREEN (scoped: $pkgs)"
echo " (land still pays the full chain once: scripts/gates.sh — memoized, so an already-gated tree is free)"
+6 -12
View File
@@ -28,8 +28,8 @@
# scripts/cine.sh 81 --at "-8913,554,94,0"
# scripts/cine.sh 81 --keep # keep the raw log and trace paths, printed at the end
#
# The probe identity is SLOT-KEYED off this worktree's path (`docs/METHOD.md`): pool-N logs in as
# probeN. Never the director's account.
# The probe identity is the checkout's `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR
# (scripts/probe-identity.sh).
set -euo pipefail
cd "$(dirname "$0")/.."
@@ -47,15 +47,9 @@ while [ $# -gt 0 ]; do
esac
done
# Slot-keyed probe identity (docs/METHOD.md, "The local vmangos server").
slot="$(basename "$PWD")"
n="${slot#pool-}"
case "$n" in
0) word=zero ;; 1) word=one ;; 2) word=two ;; 3) word=three ;; 4) word=four ;;
5) word=five ;; 6) word=six ;; 7) word=seven ;; 8) word=eight ;; 9) word=nine ;;
*) echo "cine.sh: not in a wt.sh pool slot ($slot) — claim one first (docs/METHOD.md)" >&2; exit 2 ;;
esac
char="Probe$word"
. "$PWD/scripts/probe-identity.sh"
probe_identity cine.sh "$PWD" || exit 2
char="$PROBE_CHAR"
work="$(mktemp -d)"
cfg="$work/cfg"
@@ -79,7 +73,7 @@ echo "cine.sh: cinematic $id as $char, parked at $px $py $pz (map $pmap)"
echo "cine.sh: this takes about $((exit_at + 20)) s — the shot is played in full so the END is measured too"
env BENILLA_HOME="$cfg" WOW_UNATTENDED=1 \
WOW_USER="probe$n" WOW_PASS="pprobe$n" WOW_CHAR="$char" \
WOW_USER="$PROBE_USER" WOW_PASS="$PROBE_PASS" WOW_CHAR="$char" \
WOW_PROBE_CHAT=".go xyz $px $py $pz $pmap;.debug play cinematic $id" \
WOW_PROBE_CHAT_AT="$park_at" WOW_PROBE_CHAT_EVERY="$((play_at - park_at))" \
${key:+WOW_PROBE_KEY="$key@$key_at:3"} \
-174
View File
@@ -1,174 +0,0 @@
#!/usr/bin/env bash
# crosscheck — compile benilla for the platforms the gates cannot see.
#
# Why it exists (decision 1920, bug B356): `scripts/gates.sh` runs on the developer's Mac, so a
# file behind `cfg(not(target_os = "macos"))` is not merely unverified — it is INVISIBLE. Decision
# 1857 shipped a non-macOS audio device layer that opened nothing, every Linux and Windows build
# went silent in the 09-02 sync, and nothing in this repo could have said so, because nothing here
# had ever compiled `benilla-app` for another target. This is that missing compile.
#
# scripts/crosscheck.sh [linux|windows|all] (default: all)
#
# Run it whenever platform-conditional code changes — a `cfg(target_os)`, a `[target.'cfg(…)']`
# dependency, a `#[link]`, an `extern "system"`. It is NOT part of `gates.sh`: it costs a container
# and a second dependency graph, and most commits touch no platform seam. It is a land-time check
# for the ones that do.
#
# Scope is `-p benilla-app --lib`, deliberately: that is the crate every platform seam lives in and
# the one players run, and checking it pulls benilla-world/-assets/-formats/-ui/-protocol with it.
#
# What each arm needs, once:
# linux docker (colima is fine) — the container installs its own ALSA/udev headers.
# The source is rsynced to ~/.benilla-crosscheck/src first: a worktree on an external
# volume is not visible inside the VM, and $HOME is. The container is pinned to THIS
# machine's rustc version, not to `rust-toolchain.toml`'s `stable`: the image's stable
# ran ahead of the host's on the first run and reported eight `chunks_exact_to_as_chunks`
# findings across four crates — a newer clippy's opinion, nothing to do with Linux. The
# question this script asks is "does OUR toolchain build this for another platform".
# windows rustup target add x86_64-pc-windows-gnu + brew install mingw-w64
# (blake3 assembles for the target, so `cargo check` needs the cross assembler even
# though it never links. The *-gnu target is used rather than *-msvc for the same
# reason: `ml64.exe` has no substitute here, and nothing we compile differs between
# the two ABIs at the type level.)
#
# A missing prerequisite is a FAILURE, not a skip. The whole point is that a platform nobody
# compiled is a platform nobody supports; a green line for an arm that never ran would recreate
# exactly the hole this script closes.
set -uo pipefail
arm="${1:-all}"
here="$(cd "$(dirname "$0")/.." && pwd)"
root="$(git rev-parse --show-toplevel 2>/dev/null || true)"
[ -n "$root" ] && [ -f "$root/scripts/crosscheck.sh" ] || root="$here"
cd "$root" || exit 1
echo "cross-checking: $root"
# ── Green-stamp memoization (decision 2331; the shape of gates.sh's, 1822) ─────────────────────
# `wt.sh land` runs this script on the landing tree whenever the diff touches a platform seam, and
# a session that already ran it on the same tree should not pay the container twice. The key is
# what decided the verdict: the WORKING TREE's true content hash (a throwaway index, so `target/`
# and the install links stay ignored), the arm, this script, and rustc. CROSSCHECK_FORCE=1 re-runs.
stamp="target/.crosscheck-green"
tree_key() {
local tmpidx t
tmpidx="$(mktemp -t benilla-xcheck-idx)" && rm -f "$tmpidx" || return 1
t="$( (export GIT_INDEX_FILE="$tmpidx"
git read-tree HEAD && git add -A . && git write-tree) 2>/dev/null )"
rm -f "$tmpidx"
[ -n "$t" ] || return 1
printf '%s|%s|%s|%s' "$t" "$arm" "$(rustc -V 2>/dev/null)" \
"$(shasum "$root/scripts/crosscheck.sh" 2>/dev/null | cut -d' ' -f1)"
}
# A docs-only delta keeps the verdict, as in gates.sh (2049): a record or a stub landing on the
# stamped tree changes nothing any compiler reads.
docs_only_delta() { # $1 = stamped tree, $2 = current tree
git cat-file -e "$1^{tree}" 2>/dev/null && git cat-file -e "$2^{tree}" 2>/dev/null || return 1
[ -z "$(git diff-tree -r --name-only "$1" "$2" 2>/dev/null | grep -vE '^(docs/|[^/]*\.md$)' || true)" ]
}
key_start="$(tree_key 2>/dev/null || true)"
if [ "${CROSSCHECK_FORCE:-}" != "1" ] && [ -n "$key_start" ] && [ -f "$stamp" ]; then
old="$(cat "$stamp" 2>/dev/null)"
memo=""
if [ "$old" = "$key_start" ]; then
memo="this exact tree already passed"
elif [ "${old#*|}" = "${key_start#*|}" ] && docs_only_delta "${old%%|*}" "${key_start%%|*}"; then
memo="this tree differs from one that passed only in top-level *.md"
fi
if [ -n "$memo" ]; then
echo "CROSS-CHECK GREEN ($arm — memoized: $memo; CROSSCHECK_FORCE=1 re-runs)"
exit 0
fi
fi
WIN_TARGET=x86_64-pc-windows-gnu
LINUX_IMAGE=rust:1-bookworm
STAGE="$HOME/.benilla-crosscheck/src"
failed=0
note() { printf '\ncrosscheck: %s\n' "$*"; }
fail() { printf '\nCROSSCHECK FAILED: %s\n' "$*"; failed=1; }
run_windows() {
note "windows — cargo clippy --target $WIN_TARGET"
if ! rustup target list --installed 2>/dev/null | grep -qx "$WIN_TARGET"; then
fail "windows: target $WIN_TARGET is not installed (rustup target add $WIN_TARGET)"
return
fi
if ! command -v x86_64-w64-mingw32-gcc >/dev/null 2>&1; then
fail "windows: no x86_64-w64-mingw32-gcc (brew install mingw-w64) — blake3 needs it"
return
fi
if cargo clippy -p benilla-app --lib --target "$WIN_TARGET" -- -D warnings; then
note "windows OK"
else
fail "windows: clippy -D warnings"
fi
}
run_linux() {
note "linux — cargo clippy in $LINUX_IMAGE"
if ! docker info >/dev/null 2>&1; then
fail "linux: docker is not running (colima start)"
return
fi
mkdir -p "$STAGE" || { fail "linux: cannot create $STAGE"; return; }
# Anchored excludes ('/target/', not 'target/'): an unanchored pattern also matches
# `crates/benilla-app/src/target/`, and the missing module then reports as four unrelated
# type errors deep in bevy. Cost the first run of this script twenty minutes.
if ! rsync -a --delete \
--exclude '/target/' --exclude '/.git' --exclude '/WoW' --exclude '/WoW-era' \
--exclude '/reference/' "$root"/ "$STAGE"/; then
fail "linux: rsync to $STAGE"
return
fi
local version
version="$(rustc -V | awk '{print $2}')"
[ -n "$version" ] || { fail "linux: could not read this machine's rustc version"; return; }
note "linux — pinned to rustc $version (this machine's)"
docker volume create benilla-xcheck-target >/dev/null 2>&1
docker volume create benilla-xcheck-cargo >/dev/null 2>&1
docker volume create benilla-xcheck-rustup >/dev/null 2>&1
if docker run --rm \
-v "$STAGE":/src \
-v benilla-xcheck-target:/xtarget \
-v benilla-xcheck-cargo:/usr/local/cargo/registry \
-v benilla-xcheck-rustup:/usr/local/rustup \
-w /src -e CARGO_TARGET_DIR=/xtarget -e CARGO_BUILD_JOBS="${CROSSCHECK_JOBS:-4}" \
-e RUSTUP_TOOLCHAIN="$version" \
"$LINUX_IMAGE" bash -c '
set -euo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq >/dev/null 2>&1
apt-get install -y -qq pkg-config libasound2-dev libudev-dev >/dev/null 2>&1
rustup toolchain install -c clippy --profile minimal "$RUSTUP_TOOLCHAIN"
rustc -V
cargo clippy -p benilla-app --lib -- -D warnings
'; then
note "linux OK"
else
fail "linux: clippy -D warnings"
fi
}
case "$arm" in
linux) run_linux ;;
windows) run_windows ;;
all) run_windows; run_linux ;;
*) echo "usage: $0 [linux|windows|all]"; exit 2 ;;
esac
if [ "$failed" -eq 0 ]; then
# Stamp only a tree nobody edited under the run (gates.sh's rule): a changed key is no stamp.
key_end="$(tree_key 2>/dev/null || true)"
if [ -n "$key_end" ] && [ "$key_end" = "$key_start" ]; then
mkdir -p target && printf '%s\n' "$key_end" > "$stamp" 2>/dev/null || true
fi
echo
echo "CROSS-CHECK GREEN ($arm)"
echo " (compiling is not sounding: WOW_AUDIO_LIVE=1 cargo test -p benilla-app --lib sound::output::"
echo " runs the output layer against the machine's real device — decision 1920)"
exit 0
fi
echo
echo "CROSS-CHECK RED"
exit 1
+32 -16
View File
@@ -9,10 +9,10 @@
# the social arc, then again at the 6a land). Run this instead of composing pipelines.
set -uo pipefail
# Gate the tree you are STANDING IN, not the one this file happens to live in. Every session works
# in its own worktree (docs/METHOD.md) and reaches for the gates by absolute path — `scripts/gates.sh`
# resolved from `$0`, which is the primary checkout. That silently gated the PRIMARY: fmt-red work
# in a worktree reported ALL GATES GREEN, because the primary is always clean.
# Gate the tree you are STANDING IN, not the one this file happens to live in. A machine with
# several checkouts reaches for the gates by absolute path — `scripts/gates.sh` resolved from
# `$0`, which is another checkout. That silently gated the wrong one: fmt-red work in one
# worktree reported ALL GATES GREEN, because the other was clean.
#
# So: the git toplevel of $PWD, as long as it is a checkout of THIS repo (it has this script) — a
# stray invocation from a sibling repo like wow-5875-re falls back rather than gating that repo with
@@ -26,7 +26,7 @@ echo "gating: $root"
# ── Green-stamp memoization (decision 1822) ──────────────────────────────────────────────────────
# A green chain stamps target/.gates-green with a key of exactly what determined the verdict: the
# WORKING TREE's true content hash (untracked files included — hashed through a throwaway index, the
# real one untouched, so `target/`, `WoW` and `.wt-claimed` stay excluded by the ignore rules), the
# real one untouched, so `target/` and `WoW` stay excluded by the ignore rules), the
# toolchain, this script itself, and where the install resolver points. Re-running on an unchanged
# tree is then instant instead of ~5–8 min — which is what a land after an already-gated sync, or a
# "once more to be sure", actually costs the machine. GATES_FORCE=1 runs the chain regardless.
@@ -35,7 +35,7 @@ echo "gating: $root"
stamp="target/.gates-green"
tree_key() {
local tmpidx t
tmpidx="$(mktemp -t benilla-gates-idx)" && rm -f "$tmpidx" || return 1
tmpidx="$(mktemp "${TMPDIR:-/tmp}/benilla-gates-idx.XXXXXX")" && rm -f "$tmpidx" || return 1
t="$( (export GIT_INDEX_FILE="$tmpidx"
git read-tree HEAD && git add -A . && git write-tree) 2>/dev/null )"
rm -f "$tmpidx"
@@ -46,11 +46,10 @@ tree_key() {
resolve_wow() { cargo run -q -p benilla-formats --example where 2>/dev/null || true; }
# **A docs-only delta keeps the verdict** (decision 2049). The stamped tree and the current one are
# both real tree objects (write-tree puts them in the object store), so git can say exactly what
# differs. If every differing path is a top-level `*.md` — wt.sh's
# `docs_only` rule (0979): nothing compiles, tests, formats or `include_str!`s those, checked — then
# the chain's outcome on this tree IS the stamped one, by construction rather than by optimism. This
# is what lets `wt.sh land`'s worker land a tree the session already gated after a rebase brought in
# after its own map regeneration, without paying the chain again.
# differs. If every differing path is under `docs/` or a top-level `*.md` — nothing compiles,
# tests, formats or `include_str!`s those, checked — then the chain's outcome on this tree IS the
# stamped one, by construction rather than by optimism. This is what lets a landing that only
# regenerated the map after a rebase go through without paying the chain again.
# A stamped tree that the object store has since pruned simply fails the check, and the chain runs.
docs_only_delta() { # $1 = stamped tree, $2 = current tree
git cat-file -e "$1^{tree}" 2>/dev/null && git cat-file -e "$2^{tree}" 2>/dev/null || return 1
@@ -75,8 +74,23 @@ if [ "${GATES_FORCE:-}" != "1" ] && [ -n "$key_start" ] && [ -f "$stamp" ]; then
fi
fi
log="$(mktemp -t benilla-gates)"
trap 'rm -f "$log"' EXIT
log="$(mktemp "${TMPDIR:-/tmp}/benilla-gates.XXXXXX")"
skips="$(mktemp "${TMPDIR:-/tmp}/benilla-gates-skips.XXXXXX")"
trap 'rm -f "$log" "$skips"' EXIT
# **How hollow was that green?** The data-gated tests pass without asserting on a machine that
# lacks the install or the addon corpus, and libtest swallows the skip line — so `install.rs`'s
# `skipped` appends one to `$BENILLA_SKIP_LOG` instead, and this reads the file back after each
# test rung. A clone without the data sees the number rather than a green it cannot weigh
# (docs/CONTRIBUTING.md, "Setting up"). Where the data is, `BENILLA_REQUIRE_DATA` has already
# made every skip a failure and the file stays empty.
report_skips() { # $1 = rung name
[ -s "$skips" ] || return 0
echo " $1: $(wc -l <"$skips" | tr -d ' ') data-gated tests SKIPPED on this machine —"
sort "$skips" | uniq -c | sort -rn | sed 's/^ *\([0-9]*\) \(.*\)/ \1 × \2/'
echo " (they run where the data is: docs/CONTRIBUTING.md, \"Setting up\")"
: >"$skips"
}
# ── Timing (decision 2265 §C1) ──────────────────────────────────────────────────────────────────
# Every gate's wall seconds, printed beside its verdict and appended to `target/.gates-timing`
@@ -118,7 +132,7 @@ run clippy cargo clippy --workspace --all-targets -- -D warnings
# after the pool moved drives. Where both resolve, `BENILLA_REQUIRE_DATA=1` turns a skip into the
# failure it is. Both, not either: with the install alone every corpus test would fail for the
# honest reason. Ask the resolver for the install (the same `where` the player-tests rung and the
# enforcer use); the corpus is the symlink `wt.sh link_wow` lays at the tree's root.
# enforcer use); the corpus is the `wow-addons-vanilla` link at the tree's root.
wow_data="$(resolve_wow)"
require_data=""
if [ -n "$wow_data" ] && [ -d "$root/wow-addons-vanilla" ]; then
@@ -128,7 +142,8 @@ 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} cargo test --workspace
run test env ${require_data:+BENILLA_REQUIRE_DATA=1} BENILLA_SKIP_LOG="$skips" cargo test --workspace
report_skips test
# **doc-links** (decision 1925) — the docs are the knowledge base, so a doc link pointing at a
# DELETED item is rot, and nothing else here runs rustdoc. Deliberately narrow: it fails only on a
@@ -185,8 +200,9 @@ run player-build cargo build -p benilla --no-default-features
# and let the client-data tests say so themselves (`BENILLA_REQUIRE_DATA` is deliberately NOT
# handed down: rung 2 is compiled out here, and what this rung falsifies is the player ladder,
# not the resolver — a skip here would be the honest one).
run player-tests env ${wow_data:+WOW_DATA="$wow_data"} \
run player-tests env ${wow_data:+WOW_DATA="$wow_data"} BENILLA_SKIP_LOG="$skips" \
cargo test -p benilla-formats -p benilla-app --no-default-features --lib
report_skips player-tests
# **The enforcer** (decision 1160's second binary, made into a gate by 1164). `benilla-worldview`
# boots the engine plugin set with no server, no login, no UI and no player, and any system whose
+22 -11
View File
@@ -1,25 +1,32 @@
#!/usr/bin/env python3
r"""Generate `crates/benilla-ui/src/messages/catalog.rs` from wow-5875-re's message-catalog.tsv.
r"""Generate `crates/benilla-ui/src/messages/catalog.rs` from `reference/1.12-message-catalog.tsv`.
The TSV is the sibling repo's byte-exact sweep of build 5875's `0xb4b498` registry -- the table
`CGGameUI::DisplayError 0x496720` indexes by message id. It is *their* artifact (regenerated there
with `re/ui/extract-message-catalog.py`); this script only re-shapes it into a Rust const, so the
table ships with benilla instead of being re-read out of a sibling checkout at build time.
The TSV is wow-5875-re's byte-exact sweep of build 5875's `0xb4b498` registry -- the table
`CGGameUI::DisplayError 0x496720` indexes by message id -- vendored under `reference/` like the
other tables (it is regenerated there with `re/ui/extract-message-catalog.py`). This script only
re-shapes it into a Rust const, so the table ships with benilla and is never read out of a
sibling checkout.
python3 scripts/gen-message-catalog.py ../wow-5875-re/re/ui/message-catalog.tsv
python3 scripts/gen-message-catalog.py # the vendored table -> the .rs, in place
python3 scripts/gen-message-catalog.py <tsv> # another table
Refuses rather than emits on anything it does not understand: a non-dense id range, a duplicate key,
an unknown kind. A generated table that quietly dropped a row would be worse than no table.
"""
import os
import sys
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
TABLE = os.path.join(REPO, "reference", "1.12-message-catalog.tsv")
OUT = os.path.join(REPO, "crates", "benilla-ui", "src", "messages", "catalog.rs")
KINDS = {0: "Chat", 1: "Info", 2: "Error"}
HEADER = """\
//! The 5875 message catalog, row for row.
//!
//! @generated by `scripts/gen-message-catalog.py` from wow-5875-re `re/ui/message-catalog.tsv`
//! (itself generated by that repo's `re/ui/extract-message-catalog.py` out of `WoW.exe`).
//! @generated by `scripts/gen-message-catalog.py` from `reference/1.12-message-catalog.tsv`
//! (wow-5875-re's `re/ui/message-catalog.tsv`, extracted out of `WoW.exe` there).
//! Do not edit by hand — regenerate.
use super::{MessageRecord, MsgKind};
@@ -36,6 +43,8 @@ pub static CATALOG: [MessageRecord; {count}] = [
def main(path: str) -> int:
if not os.path.exists(path):
raise SystemExit(f"no table at {path}")
rows = []
for line in open(path):
if line.startswith("#") or line.startswith("id_hex"):
@@ -75,11 +84,13 @@ def main(path: str) -> int:
f'type_tag: {r["type_tag"]:#04x}, chat_type: {r["chat_type"]} }},\n'
)
out.append("];\n")
sys.stdout.write("".join(out))
with open(OUT, "w") as f:
f.write("".join(out))
print(f"wrote {os.path.relpath(OUT, REPO)} ({len(rows)} rows)")
return 0
if __name__ == "__main__":
if len(sys.argv) != 2:
if len(sys.argv) > 2:
raise SystemExit(__doc__)
sys.exit(main(sys.argv[1]))
sys.exit(main(sys.argv[1] if len(sys.argv) == 2 else TABLE))
+19 -12
View File
@@ -2,7 +2,7 @@
"""Regenerate `reference/1.12-events.tsv` — every FrameScript event the 1.12.1 client can
dispatch, with the ARGUMENTS its producers push.
scripts/gen-reference-events.py [--wow-re DIR] [--out FILE]
scripts/gen-reference-events.py [--catalog FILE] [--firesites FILE] [--out FILE]
**Why this exists** (decision 2140). benilla already gates two halves of the event seam: a stock
chain file listening for an event nothing fires
@@ -15,12 +15,13 @@ instances: `PLAYERBANKSLOTS_CHANGED`, `UNIT_PET_EXPERIENCE` and `UNIT_PET_TRAINI
last of which routes through `PetPaperDollFrame_OnEvent`'s `elseif ( arg1 == "pet" )` catch-all and
so reached nobody at all.
**The oracle is wow-5875-re's own two censuses**, vendored the way `1.12-shapes.tsv` is:
**The oracle is wow-5875-re's own two censuses**, vendored under `reference/` the way
`1.12-shapes.tsv` is:
- `re/events/event-catalog.tsv` eventId -> name, off the name-pointer array at `.data 0xbe1198`
- `re/events/event-firesites.tsv` every `call` AND tail-`jmp` to `FrameScript_SignalEvent`
(`0x703e50`, no varargs) and `SignalEvent2` (`0x703f50`,
printf-style), with the FORMAT STRING each site pushes
- `1.12-event-catalog.tsv` eventId -> name, off the name-pointer array at `.data 0xbe1198`
- `1.12-event-firesites.tsv` every `call` AND tail-`jmp` to `FrameScript_SignalEvent`
(`0x703e50`, no varargs) and `SignalEvent2` (`0x703f50`,
printf-style), with the FORMAT STRING each site pushes
**Three producer families, because a literal fire site is not the whole picture.** The firesites
census is over the two signal helpers' own call sites, and its own header says in capitals that an
@@ -55,8 +56,8 @@ same integer names a different event on the glue screen than in the world. wow-r
rows' names; this drops them by fire-site address as well, from the four TU ranges its header
names, so a future re-derivation that fills a name in cannot leak one through.
**The input lives outside the repo** (the sibling RE repo), so this is a manual regeneration, like
`gen-reference-globals.py` — not something CI can run.
Both inputs are in the repo, so this runs from any clone and reproduces the committed table byte
for byte; only the two inputs themselves are regenerated in the RE repo.
"""
import argparse
import os
@@ -75,7 +76,12 @@ UNIT_WINDOW_FIELDS = 0xB6
NONE = "()"
REFERENCE = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "reference"))
def rows(path):
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
@@ -87,12 +93,13 @@ def rows(path):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--wow-re", default=os.path.expanduser("~/dev/wow-5875-re"))
ap.add_argument("--out", default=os.path.join(os.path.dirname(__file__), "..", "reference", "1.12-events.tsv"))
ap.add_argument("--catalog", default=os.path.join(REFERENCE, "1.12-event-catalog.tsv"))
ap.add_argument("--firesites", default=os.path.join(REFERENCE, "1.12-event-firesites.tsv"))
ap.add_argument("--out", default=os.path.join(REFERENCE, "1.12-events.tsv"))
a = ap.parse_args()
catalog = {}
for f in rows(os.path.join(a.wow_re, "re/events/event-catalog.tsv")):
for f in rows(a.catalog):
if len(f) >= 2 and f[1]:
catalog.setdefault(int(f[0]), f[1])
if len(catalog) < 300:
@@ -110,7 +117,7 @@ def main():
e["notes"].append(note)
dropped_glue = 0
for f in rows(os.path.join(a.wow_re, "re/events/event-firesites.tsv")):
for f in rows(a.firesites):
if len(f) < 4:
continue
va = int(f[0], 16)
+11 -5
View File
@@ -2,7 +2,7 @@
"""Regenerate `reference/1.12-globals.tsv` — the 1.12.1 client's global namespace, with each name
attributed to whoever provides it.
scripts/gen-reference-globals.py [--wow-re DIR] [--out FILE]
WOW_RE=<the RE repo> scripts/gen-reference-globals.py [--wow-re DIR] [--out FILE]
**Why this exists** (decision 1189): every wrong number in the addon arc came from remembering a
list of WoW functions instead of asking the client. wow-5875-re already ships an addon
@@ -30,8 +30,9 @@ Attribution is by *definition site*, computed from a complete 1.12 shipped-UI co
- LUA_5_0 below wins over all of the above: FrameXML *clobbers* `string` in three files
(`string = getglobal(...)`), which overwrites the stdlib table rather than defining it.
**Inputs live outside the repo** — the director's 1.12 install and the sibling RE repo — so this
is a manual regeneration, like `genmap.sh`, not something CI can run. It needs:
**Inputs live outside the repo** — a 1.12 install and the RE repo's runtime capture (`--wow-re`,
or `$WOW_RE`) — so this is a manual regeneration, like `genmap.sh`; the committed table is the
surface benilla tracks. It needs:
- `<wow-re>/WoW/_w5875_fixtures/item13/W5875Capture.lua` the captured in-world `_G`
- `<wow-re>/WoW/_extracted_framexml/` FrameXML, already extracted there
@@ -50,7 +51,6 @@ import re
import subprocess
import sys
WOWRE_DEFAULT = "/Users/sam/dev/wow-5875-re"
REPO = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# The twelve addons that ship inside the MPQs. Their folders exist in a real install carrying only
@@ -249,9 +249,15 @@ def composed(name, roots, suffixes, depth=0):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--wow-re", default=WOWRE_DEFAULT)
ap.add_argument("--wow-re", default=os.environ.get("WOW_RE"))
ap.add_argument("--out", default=os.path.join(REPO, "reference", "1.12-globals.tsv"))
args = ap.parse_args()
if not args.wow_re:
sys.exit(
"this table is derived from the RE repo's runtime capture of the reference client's "
"_G: pass --wow-re or set WOW_RE. The committed reference/1.12-globals.tsv is the "
"surface benilla tracks."
)
fixture = os.path.join(args.wow_re, "WoW", "_w5875_fixtures", "item13", "W5875Capture.lua")
if not os.path.exists(fixture):
+24 -11
View File
@@ -2,7 +2,7 @@
"""Regenerate `reference/1.12-verb-events.tsv` — every FrameScript event the 1.12.1 client fires
FROM INSIDE A LUA VERB, keyed by the verb.
scripts/gen-reference-verb-events.py [--wow-re DIR] [--out FILE]
WOW_RE=<the RE repo> scripts/gen-reference-verb-events.py [--wow-re DIR] [--out FILE]
**Why this exists.** `reference/1.12-events.tsv` (2140) says what arguments an event carries and
`reference_ui`'s two name gates (1883/1889) say whether *something* fires what a stock file listens
@@ -45,8 +45,10 @@ and the unit-field bridge / token fan-out ids below 0xb6, which are state events
fire-site census is over the two signal helpers' literal call sites (its header says so in
capitals), and `shape=helper` stops one call deep on purpose. Gate on what IS here.
The input lives outside the repo (the sibling RE repo), so this is a manual regeneration, like
`gen-reference-events.py` — not something CI can run.
The fire sites and the binding shapes are the vendored `reference/` tables; the function names,
the ledgers' extents and the disassembly are the RE repo's (`--wow-re`, or `$WOW_RE`), and the
disassembly is the client's own binary, so this is a manual regeneration there. The committed
table is the surface benilla tracks.
"""
import argparse
import bisect
@@ -69,7 +71,12 @@ BRANCH = re.compile(r"^(call|jmp)\s+0x([0-9a-f]+)\s*$")
SIZE = re.compile(r"\bsize=(\d+)")
REFERENCE = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "reference"))
def rows(path):
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
@@ -81,12 +88,15 @@ def rows(path):
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--wow-re", default=os.path.expanduser("~/dev/wow-5875-re"))
ap.add_argument(
"--out",
default=os.path.join(os.path.dirname(__file__), "..", "reference", "1.12-verb-events.tsv"),
)
ap.add_argument("--wow-re", default=os.environ.get("WOW_RE"))
ap.add_argument("--out", default=os.path.join(REFERENCE, "1.12-verb-events.tsv"))
a = ap.parse_args()
if not a.wow_re:
sys.exit(
"this table is derived from the RE repo's function names, ledgers and disassembly: "
"pass --wow-re or set WOW_RE. The committed reference/1.12-verb-events.tsv is the "
"surface benilla tracks."
)
starts = set()
for f in rows(os.path.join(a.wow_re, "re/names/out/names-5875.tsv")):
@@ -111,7 +121,7 @@ def main():
starts |= set(extent)
verbs = {} # fn address -> name
for f in rows(os.path.join(a.wow_re, "re/audit/binding-shapes.tsv")):
for f in rows(os.path.join(REFERENCE, "1.12-shapes.tsv")):
if len(f) < 2:
continue
fn = int(f[1], 16)
@@ -127,7 +137,7 @@ def main():
starts |= set(verbs)
sites = [] # (site va, event name)
for f in rows(os.path.join(a.wow_re, "re/events/event-firesites.tsv")):
for f in rows(os.path.join(REFERENCE, "1.12-event-firesites.tsv")):
if len(f) < 4 or not f[0].startswith("0x") or not f[3] or f[2] == "dyn":
continue
va, eid = int(f[0], 16), int(f[2])
@@ -145,7 +155,10 @@ def main():
after_pad = False
ended = False # the previous real instruction was a `ret`
n_lines = 0
for line in open(os.path.join(a.wow_re, "system/ui/scratch/disasm-full.txt"), encoding="utf-8", errors="replace"):
disasm = os.path.join(a.wow_re, "system/ui/scratch/disasm-full.txt")
if not os.path.exists(disasm):
sys.exit(f"no disassembly at {disasm} — is --wow-re right?")
for line in open(disasm, encoding="utf-8", errors="replace"):
m = LINE.match(line)
if not m:
continue
+3 -3
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env bash
# Generate docs/MAP.md — the "what's built" map — derived entirely from what's on disk so it cannot
# drift (docs/METHOD.md / docs/METHOD.md: "the generated map … regenerated from the code, never
# hand-written"). `wt.sh land` runs it on the rebased tree and commits the file (decision 2049);
# run it by hand only to LOOK, never commit its output yourself, and never edit it by hand.
# hand-written"). It is regenerated and committed whenever a change lands (decision 2049); run
# it by hand only to LOOK, never commit its output yourself, and never edit it by hand.
# Deterministic: identical tree → identical output (no timestamps), so a diff means the structure
# actually changed.
# No `pipefail`/`-e`: greps that legitimately find nothing (a single-file lib, a crate with no bins)
@@ -125,7 +125,7 @@ field() { grep -m1 "^$2" "$1" 2>/dev/null | sed -E "s/^$2[[:space:]]*=[[:space:]
# is the source, so it cannot drift. (`winlab/` is the Windows lab laptop's, 2205/2211.)
echo "## Scripts (\`scripts/\`)"
echo
for f in scripts/*.py scripts/*.sh scripts/*.swift scripts/winlab/*; do
for f in scripts/*.py scripts/*.sh; do
[ -f "$f" ] || continue
# The summary: the header's first non-empty content after the shebang, comment or docstring
# markers stripped, joined until its first blank line, cut at a sentence end or ~150 chars.
-100
View File
@@ -1,100 +0,0 @@
#!/bin/bash
# leg.sh — the interleaved A/B leg runner (1353 law 1, mechanized).
#
# Runs N rounds of (binary A, binary B) at a pin, one slot, one body, enforcing the leg laws the
# campaign kept re-typing by hand:
# · load guard: waits for 1-min load average < 3 before every leg (1157)
# · lock guard: refuses to start — and stops between legs — while the screen is locked
# (1355: a locked screen throttles drawables at occluded_frames=0; the leg is invalid)
# · occlusion: a leg with occluded_frames != 0 is discarded and re-run, up to 2 retries (0731)
# · the whole FPS_PROBE line is kept per leg (never a grepped subset) under $LEG_DIR
#
# Usage:
# scripts/leg.sh <binaryA> <binaryB> [rounds] [rig]
# Env:
# WOW_USER/WOW_PASS/WOW_CHAR probe credentials (default probe0/pprobe0/Probezero — pool-0)
# LEG_RIG WOW_RIG spec (default: the LBRS pin, 0731)
# LEG_FRAMES / LEG_AT probe window (default 300 / 45)
# LEG_DIR where leg logs land (default: $CLAUDE_JOB_DIR/tmp or /tmp)
#
# Motion legs: export WOW_PROBE_KEY / WOW_PROBE_CAM alongside — they inherit into every leg,
# A and B alike — to measure a MOVING regime, e.g. WOW_PROBE_KEY="W@40:20"
# WOW_PROBE_CAM="30,10@40:25" with the default LEG_AT=45 puts the whole FPS window inside a
# straight walk with the camera panning (1427: parked pins cannot see the play regime the
# director reports). Streaming churn inside the window is the point, not noise — but quote
# motion legs only against motion legs.
#
# Output: one line per kept leg (cpu_ms fps sys_busy), then per-round deltas and the means.
set -euo pipefail
A="${1:?usage: leg.sh <binaryA> <binaryB> [rounds] [rig]}"
B="${2:?usage: leg.sh <binaryA> <binaryB> [rounds] [rig]}"
ROUNDS="${3:-3}"
RIG="${4:-${LEG_RIG:-at:229,24.90,-396.50,48.80}}"
USER_="${WOW_USER:-probe0}"; PASS_="${WOW_PASS:-pprobe0}"; CHAR_="${WOW_CHAR:-Probezero}"
FRAMES="${LEG_FRAMES:-300}"; AT="${LEG_AT:-45}"
DIR="${LEG_DIR:-${CLAUDE_JOB_DIR:-/tmp}/tmp}"; mkdir -p "$DIR" 2>/dev/null || DIR="/tmp"
locked() { ioreg -n Root -d1 -a | grep -A1 CGSSessionScreenIsLocked | grep -q true; }
loadwait() { until [ "$(uptime | sed -E 's/.*averages?: ([0-9]+)[.,].*/\1/')" -lt 3 ]; do sleep 10; done; }
run_leg() { # $1 binary $2 tag → prints "cpu_ms fps sys_busy" or fails
local bin="$1" tag="$2" try line occ fatal
for try in 1 2 3; do
if locked; then echo "leg.sh: screen is LOCKED — legs are invalid (1355); aborting" >&2; exit 2; fi
loadwait
WOW_UNATTENDED=1 WOW_USER="$USER_" WOW_PASS="$PASS_" WOW_CHAR="$CHAR_" WOW_RIG="$RIG" \
WOW_LIVE_FPS="$FRAMES" WOW_LIVE_FPS_AT="$AT" \
timeout 300 "$bin" >"$DIR/leg-$tag-$try.log" 2>&1 || true
line=$(grep -a "FPS_PROBE" "$DIR/leg-$tag-$try.log" | sed 's/\x1b\[[0-9;]*m//g' | tail -1)
# A FATAL is the client's own verdict that the run can never measure (refused login, missing
# character, no world by the boot deadline) — deterministic, so a retry buys the same failure.
# Abort the sitting loudly instead of burning tries × timeout (the 1371 legs sat 3 × 300 s at
# a login screen the account guard had already refused).
fatal=$(grep -a "FATAL" "$DIR/leg-$tag-$try.log" | sed 's/\x1b\[[0-9;]*m//g' | tail -1)
if [ -n "$fatal" ]; then
echo "leg.sh: $tag ABORTING the sitting — $fatal" >&2
echo "leg.sh: full log: $DIR/leg-$tag-$try.log" >&2
exit 4
fi
[ -n "$line" ] || { echo "leg.sh: $tag try $try produced no FPS_PROBE — see $DIR/leg-$tag-$try.log" >&2; continue; }
occ=$(sed -E 's/.*occluded_frames=([0-9]+).*/\1/' <<<"$line")
if [ "$occ" != "0" ]; then echo "leg.sh: $tag try $try occluded ($occ) — discarded unread" >&2; continue; fi
# Regime guard (1388 round 3): one round's legs once read 126.5 vs 60.0 fps under the same
# uncapped present mode — Metal displaySync is off either way, but the WindowServer rails a
# composited window's drawables at its display's rate, and only sometimes. Every campaign
# anchor is a railed leg (1380 "fps locked 60"), so a leg that escaped the rail measures a
# different regime and is discarded here, by machine, instead of by a human reading the
# round's deltas sideways. The probe line's display=...@hz stamp is the rail.
fps=$(grep -oE " fps=[0-9.]+" <<<"$line" | cut -d= -f2)
hz=$(grep -oE " display=[^ ]*@[0-9]+" <<<"$line" | sed 's/.*@//')
if [ -n "$fps" ] && [ -n "$hz" ] && awk -v f="$fps" -v h="$hz" 'BEGIN{exit !(f > h + 3)}'; then
echo "leg.sh: $tag try $try FREE-RUNNING (fps=$fps above the $hz Hz rail) — discarded" >&2; continue
fi
echo "$line" >>"$DIR/legs-kept.log"
grep -oE "cpu_ms=[0-9.]+" <<<"$line" | cut -d= -f2
return 0
done
echo "leg.sh: $tag failed 3 tries" >&2; exit 3
}
echo "leg.sh: $ROUNDS round(s), A=$(basename "$A") B=$(basename "$B"), rig=\"$RIG\", body=$CHAR_"
# Law 6 (1351/1353): the first leg after a build — or after the machine sat idle — is a warm-up,
# not a measurement. One discarded A-leg primes every cache the rounds then share.
# The assignment (not an inline $(...) in the echo) is load-bearing: run_leg's exit runs in the
# substitution subshell, and only an assignment's status is seen by set -e — inline, a warm-up
# abort would be discarded and the rounds would start anyway.
w=$(run_leg "$A" "warmup"); echo " warm-up (discarded): cpu_ms=$w"
declare -a AV BV
for r in $(seq 1 "$ROUNDS"); do
a=$(run_leg "$A" "r${r}A"); AV+=("$a"); echo " round $r A: cpu_ms=$a"
b=$(run_leg "$B" "r${r}B"); BV+=("$b"); echo " round $r B: cpu_ms=$b"
done
awk_list() { local IFS=,; echo "$*"; }
python3 - "$(awk_list "${AV[@]}")" "$(awk_list "${BV[@]}")" <<'EOF'
import sys
a = [float(x) for x in sys.argv[1].split(',')]
b = [float(x) for x in sys.argv[2].split(',')]
ma, mb = sum(a)/len(a), sum(b)/len(b)
print(f"mean A={ma:.2f} mean B={mb:.2f} Δ={mb-ma:+.2f} (rounds: {['%+.2f' % (y-x) for x,y in zip(a,b)]})")
EOF
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env bash
# probe-identity.sh — who a scripted run logs in as. Sourced by smoke.sh, cine.sh and
# summon-live.sh; never run on its own.
#
# A scripted run has no default account: a login kicks whoever holds the account, so the run
# logs in as the account this checkout DECLARES, or as the one the shell names, or not at all.
#
# `.probe-identity` at the tree root — `WOW_USER=…`, `WOW_PASS=…`, `WOW_CHAR=…`, one per line,
# never committed. A machine that runs several checkouts
# against one server gives each its own account this way,
# and the client refuses a scripted login from that
# checkout on any other account (run_mode::account_guard).
# WOW_USER, WOW_PASS, WOW_CHAR — the shell's, all three, when nothing is declared.
#
# probe_identity <tag> <tree root>
#
# sets PROBE_USER, PROBE_PASS, PROBE_CHAR, PROBE_DECLARED (non-empty when the file supplied them)
# and PROBE_HOST with PROBE_AUTH_PORT from WOW_HOST (default localhost:3724). Prints one line
# naming the identity; a refusal prints why, with the caller's tag, and returns 1.
probe_identity() {
local tag="$1" root="$2" file="$2/.probe-identity" v ignored=""
PROBE_DECLARED=""
if [ -f "$file" ]; then
PROBE_USER="$(sed -n 's/^WOW_USER=//p' "$file" | head -1)"
PROBE_PASS="$(sed -n 's/^WOW_PASS=//p' "$file" | head -1)"
PROBE_CHAR="$(sed -n 's/^WOW_CHAR=//p' "$file" | head -1)"
if [ -z "$PROBE_USER" ] || [ -z "$PROBE_PASS" ] || [ -z "$PROBE_CHAR" ]; then
echo "$tag: REFUSING — $file is missing one of WOW_USER, WOW_PASS, WOW_CHAR"
return 1
fi
PROBE_DECLARED=1
for v in WOW_USER WOW_PASS WOW_CHAR; do
[ -n "${!v:-}" ] && ignored="$ignored $v"
done
[ -n "$ignored" ] && echo "$tag: ignoring$ignored — this checkout declares its own account (.probe-identity)"
echo "$tag: $PROBE_USER / $PROBE_CHAR (declared by .probe-identity)"
else
if [ -z "${WOW_USER:-}" ] || [ -z "${WOW_PASS:-}" ] || [ -z "${WOW_CHAR:-}" ]; then
echo "$tag: REFUSING — no account to log in as. A scripted run takes WOW_USER, WOW_PASS"
echo " and WOW_CHAR from the environment, all three, or from a .probe-identity file"
echo " at the tree root, naming a test account on your server whose login kicks"
echo " nobody (docs/CONTRIBUTING.md, \"Setting up\")."
return 1
fi
PROBE_USER="$WOW_USER"
PROBE_PASS="$WOW_PASS"
PROBE_CHAR="$WOW_CHAR"
echo "$tag: $PROBE_USER / $PROBE_CHAR (from the environment)"
fi
# The server: WOW_HOST is `host` or `host:port`, the client's own spelling (realmlist.rs).
local host="${WOW_HOST:-localhost}"
PROBE_HOST="${host%%:*}"
PROBE_AUTH_PORT="${host##*:}"
[ "$PROBE_AUTH_PORT" != "$host" ] || PROBE_AUTH_PORT=3724
return 0
}
# Is a server listening? `probe_server_or_skip <tag>` prints a skip and returns 1 when the auth
# port does not answer — and, for a local server, when the stock world port (8085) does not
# either, because a refused world connection reads as a client bug in the log and costs a full
# build to discover. A remote server's world port comes from its realm list and is not guessed.
probe_server_or_skip() {
local tag="$1" port ports="$PROBE_AUTH_PORT"
case "$PROBE_HOST" in localhost | 127.0.0.1) ports="$ports 8085" ;; esac
for port in $ports; do
if ! (exec 3<>"/dev/tcp/$PROBE_HOST/$port") 2>/dev/null; then
echo "$tag: SKIPPED — nothing listening on $PROBE_HOST:$port (auth $PROBE_AUTH_PORT; a local"
echo " vmangos also serves the world on 8085). Start your 1.12.1 server, or point"
echo " WOW_HOST at it."
return 1
fi
done
return 0
}
+37 -48
View File
@@ -5,22 +5,24 @@
# paragraph concludes it *cannot* run the client, says so to the director, and ships a change whose
# whole subject is a live session boundary without ever having crossed one. That happened (2277).
#
# What it does: boots the real client against the local vmangos on THIS SLOT's probe account, seats
# a body in the world, `/logout`s to character select, **re-enters**, and exits — then reads the log
# back. It is the smallest run that crosses every session edge, which is exactly the set of edges
# What it does: boots the real client against the server (WOW_HOST, default localhost) as the
# account this checkout declares — or as the WOW_USER/WOW_PASS/WOW_CHAR of the shell — seats a
# body in the world, `/logout`s to character select, **re-enters**, and exits — then reads
# the log back. It is the smallest run that crosses every session edge, which is exactly the set of edges
# the unit tests can only model.
#
# It is deliberately NOT wired into `gates.sh`: it wants a server, opens a window, and costs ~45 s,
# so paying it on every commit is the director's call, not this script's. Run it when a change
# touches a session boundary — and the gates print a pointer at you either way.
#
# scripts/smoke.sh # this slot's probe account
# WOW_SMOKE_KEEP=1 scripts/smoke.sh # keep the log and print its path
# scripts/smoke.sh # the checkout's .probe-identity
# WOW_USER=u WOW_PASS=p WOW_CHAR=Name scripts/smoke.sh # or your test account
# WOW_SMOKE_KEEP=1 scripts/smoke.sh # keep the log and print its path
set -uo pipefail
# The tree we are standing in, `gates.sh`'s rule verbatim: a session works in its own worktree and
# reaches for scripts by absolute path, so `$0`'s directory is the PRIMARY checkout and gating (or
# running) that instead is the bug that made the old gate chain lie.
# The tree we are standing in, `gates.sh`'s rule verbatim: the git toplevel of $PWD when it is a
# checkout of this repo, else this file's — a script reached by absolute path from another
# checkout must not run that one.
root="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
if [ -z "$root" ] || [ ! -f "$root/scripts/smoke.sh" ]; then
root="$(cd "$(dirname "$0")/.." && pwd)"
@@ -41,52 +43,37 @@ echo "smoke: $root"
# 120 s, no cause, and nothing in the log that looks wrong.
#
# So the scrub is per-VARIABLE and up front, not per-leg: a gate whose result depends on the shell
# it was typed in is not a gate. Kept: `WOW_DATA` (where the install IS — a location, not a dial),
# `WOW_BG` (the director's "let me watch this one", which changes window stacking and nothing else),
# and `WOW_SMOKE_KEEP` (this script's own). Loud, because a session that meant to pass something
# should see it go rather than wonder why it did nothing.
# it was typed in is not a gate. Kept: `WOW_DATA` and `WOW_HOST` (where the install and the server
# ARE — locations, not dials), `WOW_BG` (the maintainer's "let me watch this one", which changes
# window stacking and nothing else), and `WOW_SMOKE_KEEP` (this script's own). Loud, because a
# session that meant to pass something should see it go rather than wonder why it did nothing.
#
# **Who the run logs in as** is decided first, by `scripts/probe-identity.sh`: the account this
# checkout declares in `.probe-identity` (a login kicks whoever holds the account, so a checkout
# never logs in as anyone else's), else the WOW_USER/WOW_PASS/WOW_CHAR of the shell — taken before
# the scrub, and the one thing the scrub does not throw away.
. "$root/scripts/probe-identity.sh"
probe_identity smoke "$root" || exit 1
user="$PROBE_USER"
pass="$PROBE_PASS"
char="$PROBE_CHAR"
inherited=""
for v in $(env | sed -n 's/^\(WOW_[A-Za-z0-9_]*\)=.*/\1/p'); do
case "$v" in WOW_DATA | WOW_BG | WOW_SMOKE_KEEP) continue ;; esac
case "$v" in WOW_DATA | WOW_HOST | WOW_BG | WOW_SMOKE_KEEP) continue ;; esac
[ -n "$PROBE_DECLARED" ] || case "$v" in WOW_USER | WOW_PASS | WOW_CHAR) continue ;; esac
inherited="$inherited $v"
unset "$v"
done
[ -n "$inherited" ] &&
echo "smoke: ignoring inherited env —$inherited (each leg names its own; a gate is not shell-dependent)"
# **The probe identity is keyed to the worktree slot** (docs/METHOD.md, "The local vmangos server"): a
# vmangos login KICKS whoever holds the account, so `one` is the director's live session and every
# other `probeN` is another session's. The slot claim is already the session mutex, so deriving the
# account from it makes probe exclusivity automatic. Refuse rather than guess.
slot="$(basename "$root" | sed -n 's/^pool-\([0-9]\)$/\1/p')"
if [ -z "$slot" ]; then
echo "smoke: REFUSING — not in a pool worktree (\`$root\`)."
echo " The probe account is keyed to the slot; logging in as the default account would"
echo " kick the director's live session. Claim one: scripts/wt.sh claim <name>"
exit 1
fi
# Spelled out literally, in docs/METHOD.md's own casing (`pool-0 → Probezero`) rather than derived: BSD
# sed has no `\U`, and deriving it cost a whole run to discover — the client dutifully reported
# `ProbeUzero` was not on the account, and then sat on the roster until the timeout.
names=(zero one two three four five six seven eight nine)
user="probe$slot"
pass="pprobe$slot"
char="Probe${names[$slot]}"
echo "smoke: slot $slot → $user / $char"
# The server, before the client: a refused connection reads as a client bug in the log and costs a
# full build to discover. Loud skip, never a silent pass — `gates.sh`'s posture for the install.
for port in 3724 8085; do
if ! (exec 3<>/dev/tcp/127.0.0.1/$port) 2>/dev/null; then
echo "smoke: SKIPPED — nothing listening on 127.0.0.1:$port (realmd 3724 / mangosd 8085)."
echo " The local vmangos lives at /Users/sam/dev/vmangos-deploy — \`docker compose up -d\`."
exit 0
fi
done
probe_server_or_skip smoke || exit 0
log="$(mktemp -t benilla-smoke)"
stamp="$(mktemp -t benilla-smoke-stamp)"
before="$(mktemp -t benilla-smoke-before)"
log="$(mktemp "${TMPDIR:-/tmp}/benilla-smoke.XXXXXX")"
stamp="$(mktemp "${TMPDIR:-/tmp}/benilla-smoke-stamp.XXXXXX")"
before="$(mktemp "${TMPDIR:-/tmp}/benilla-smoke-before.XXXXXX")"
# The stamp and the file list always go; the LOG is the one a session may want to keep. Both in one
# trap so the early `fail` exits below cannot strand a temp file.
trap 'rm -f "$stamp" "$before"; [ -n "${WOW_SMOKE_KEEP:-}" ] || rm -f "$log"' EXIT
@@ -108,8 +95,8 @@ if [ -n "${WOW_DATA:-}" ]; then
elif [ -d "$root/WoW" ]; then
install_root="$root/WoW"
fi
# Who ELSE might write in there. The install is shared — every pool slot symlinks the same tree,
# and the director runs the real 1.12 client against it constantly for RE comparison. That client
# Who ELSE might write in there. The install may be shared with the reference client, which the
# maintainer runs against the same tree constantly for RE comparison. That client
# writes its own WTF (Config.wtf, SavedVariables.lua, the per-character caches) while it runs, so a
# whole-tree mtime diff cannot tell "benilla wrote to the install" from "the reference client was
# open at the same time". Record it up front so the report below can name the right cause instead of
@@ -250,7 +237,7 @@ saving (1528: the quit root must be observed in \`Last\`, after PostUpdate's exi
# The read-only verdict (decision 1486). Reported by name: "something wrote to the install" is a
# rule violation somebody has to go and find, and the file that appeared is the whole lead.
if [ -n "$install_root" ]; then
after="$(mktemp -t benilla-smoke-after)"
after="$(mktemp "${TMPDIR:-/tmp}/benilla-smoke-after.XXXXXX")"
find -L "$install_root" -type f 2>/dev/null | sort >"$after"
appeared="$(comm -13 "$before" "$after")"
vanished="$(comm -23 "$before" "$after")"
@@ -274,6 +261,8 @@ if [ -n "$install_root" ]; then
exit 1
fi
echo " install untouched $(wc -l <"$before" | tr -d ' ') files"
else
echo " install not watched (no WoW link and no WOW_DATA — the read-only rule went unmeasured)"
fi
# ── The realm-list leg (2069) ────────────────────────────────────────────────────────────────
@@ -286,7 +275,7 @@ fi
# that hung on "Connecting" with nothing in the log at all. This drives Change Realm → Okay →
# Change Realm → Cancel → Okay against the real server and refuses anything but a completed walk.
echo "smoke: running the realm-list boundary walk (~12 s, opens a window)…"
rlog="$(mktemp -t benilla-smoke-realm)"
rlog="$(mktemp "${TMPDIR:-/tmp}/benilla-smoke-realm.XXXXXX")"
# **No `WOW_CHAR` here, deliberately** — an absent variable is invisible, so it is named instead.
# The walk drives character select; the fast path would seat a body and there would be no roster
# screen left to drive. The scrub at the top of this script is what makes the absence real, and
@@ -312,6 +301,6 @@ rp="$(printf '%s\n' "$rplain" | grep -cE 'panicked at')"
printf ' %-24s %s\n' "realm walk" \
"$(printf '%s\n' "$rplain" | sed -n 's/.*realm-smoke: done — //p' | tail -1)"
echo "SMOKE GREEN — ${sessions} logins + the realm walk, 0 errors, 0 panics, install untouched"
echo "SMOKE GREEN — ${sessions} logins + the realm walk, 0 errors, 0 panics$([ -n "$install_root" ] && echo ', install untouched')"
[ -n "${WOW_SMOKE_KEEP:-}" ] && echo "log: $log"
exit 0
+22 -36
View File
@@ -9,7 +9,7 @@
# be asked by somebody else, and the whole seam behind it (apply → feed → VM → drain → wire) is
# exactly the part unit tests cannot reach.
#
# What it does: a summoner client on an unowned probe account invites this slot's probe character
# What it does: a summoner client on a second account invites this checkout's probe character
# into a group and runs `.group summon`; the receiver reads its own CONFIRM_SUMMON dialog back
# through `ProbeLog`, presses Accept, and re-reads its zone once the teleport has landed. The
# receiver also writes the `summon` trace tag, so all three links report:
@@ -18,13 +18,12 @@
# summon fire CONFIRM_SUMMON summoner=… zone=…
# summon SEND CMSG_SUMMON_RESPONSE summoner=… n=1
#
# **Why PROBE9, and why the account guard is waved.** The worktree pool is a hard eight slots
# (0..7, decision 1037), and the probe accounts run 0..9 — so PROBE8 and PROBE9 are keyed to no
# session by construction and logging in as one cannot kick anybody. That is the ONLY thing that
# makes `WOW_ALLOW_ACCOUNT=1` legitimate here, so this script checks it rather than asserting it:
# if a pool-9 slot ever exists, it refuses instead of kicking that session out of the world.
# **The summoner is a second account the shell names** — `SUMMON_USER`, `SUMMON_PASS`,
# `SUMMON_CHAR`, a GM-level test account whose login kicks nobody. The summoner's run carries
# `WOW_ALLOW_ACCOUNT=1`, because it is not the account this checkout declares; that the shell
# named it is what makes the override legitimate.
#
# scripts/summon-live.sh # ~90 s, opens two small cornered windows
# SUMMON_USER=… SUMMON_PASS=… SUMMON_CHAR=… scripts/summon-live.sh # ~90 s, two small windows
set -uo pipefail
root="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
@@ -32,42 +31,29 @@ root="$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null || true)"
cd "$root" || exit 1
echo "summon-live: $root"
# The receiver is THIS SLOT's probe identity — smoke.sh's rule, and for its reason: a vmangos login
# kicks whoever holds the account, and the slot claim is already the session mutex.
slot="$(basename "$root" | sed -n 's/^pool-\([0-9]\)$/\1/p')"
if [ -z "$slot" ]; then
echo "summon-live: REFUSING — not in a pool worktree (\`$root\`)."
echo " Claim one: scripts/wt.sh claim <name>"
exit 1
fi
names=(zero one two three four five six seven eight nine)
rx_user="probe$slot"; rx_pass="pprobe$slot"; rx_char="Probe${names[$slot]}"
# The receiver is this run's own identity — smoke.sh's rule (scripts/probe-identity.sh), for its
# reason: a login kicks whoever holds the account.
. "$root/scripts/probe-identity.sh"
probe_identity summon-live "$root" || exit 1
rx_user="$PROBE_USER"; rx_pass="$PROBE_PASS"; rx_char="$PROBE_CHAR"
# The summoner rides a probe account NO slot can claim (see the header). Verified, not assumed.
tx_slot=9
if [ -d "$(dirname "$root")/pool-$tx_slot" ]; then
echo "summon-live: REFUSING — pool-$tx_slot exists, so probe$tx_slot is a live session's account"
echo " and logging in on it would kick that session out of the world. The pool grew"
echo " past this script's assumption; pick a higher unowned probe account."
# The summoner is the shell's to name (see the header).
if [ -z "${SUMMON_USER:-}" ] || [ -z "${SUMMON_PASS:-}" ] || [ -z "${SUMMON_CHAR:-}" ]; then
echo "summon-live: REFUSING — the summoner needs a second account: SUMMON_USER, SUMMON_PASS"
echo " and SUMMON_CHAR, a GM-level test account whose login kicks nobody."
exit 1
fi
tx_user="probe$tx_slot"; tx_pass="pprobe$tx_slot"; tx_char="Probe${names[$tx_slot]}"
tx_user="$SUMMON_USER"; tx_pass="$SUMMON_PASS"; tx_char="$SUMMON_CHAR"
echo "summon-live: $tx_char ($tx_user) summons $rx_char ($rx_user)"
for port in 3724 8085; do
if ! (exec 3<>/dev/tcp/127.0.0.1/$port) 2>/dev/null; then
echo "summon-live: SKIPPED — nothing listening on 127.0.0.1:$port (realmd 3724 / mangosd 8085)."
echo " The local vmangos lives at /Users/sam/dev/vmangos-deploy — \`docker compose up -d\`."
exit 0
fi
done
probe_server_or_skip summon-live || exit 0
# Build once, so the two `cargo run`s below start together instead of one waiting on the other's
# build lock — the timings are wall-clock from process start (smoke.sh's build-then-time rule).
echo "summon-live: building…"
cargo build -q -p benilla || { echo "summon-live: the client did not build"; exit 1; }
work="$(mktemp -d -t benilla-summon)"
work="$(mktemp -d "${TMPDIR:-/tmp}/benilla-summon.XXXXXX")"
trap '[ -n "${SUMMON_LIVE_KEEP:-}" ] || rm -rf "$work"' EXIT
# The receiver's chunk. `tostring` on every getter on purpose: a missing dialog must REPORT rather
@@ -99,7 +85,7 @@ echo "summon-live: running (~90 s, opens two windows)…"
# wherever the last run left it — which, after one green run, is the destination — so a
# before/after zone compare silently stops discriminating on the second run. It did exactly that.
# Parking in Stormwind makes "before" the same on every run, whatever the previous one did.
WOW_UNATTENDED=1 WOW_USER="$rx_user" WOW_PASS="$rx_pass" WOW_CHAR="$rx_char" \
WOW_UNATTENDED=1 WOW_NOSOUND=1 WOW_USER="$rx_user" WOW_PASS="$rx_pass" WOW_CHAR="$rx_char" \
WOW_PROBE=partner \
WOW_PROBE_CHAT=".go xyz -8913 554 94" WOW_PROBE_CHAT_AT=10 \
WOW_PROBE_LUA="$chunk" WOW_PROBE_LUA_AT=34 \
@@ -108,9 +94,9 @@ WOW_UNATTENDED=1 WOW_USER="$rx_user" WOW_PASS="$rx_pass" WOW_CHAR="$rx_char" \
timeout 150 cargo run -q -p benilla >"$work/rx.log" 2>&1 &
rx=$!
# `WOW_ALLOW_ACCOUNT=1` is the account guard's own escape hatch, and the pool-9 check above is what
# earns it. `.group summon` rather than `.summon`: the latter teleports without ever asking.
WOW_UNATTENDED=1 WOW_USER="$tx_user" WOW_PASS="$tx_pass" WOW_CHAR="$tx_char" WOW_ALLOW_ACCOUNT=1 \
# `WOW_ALLOW_ACCOUNT=1` is the account guard's own escape hatch, and the shell naming the account
# is what earns it. `.group summon` rather than `.summon`: the latter teleports without ever asking.
WOW_UNATTENDED=1 WOW_NOSOUND=1 WOW_USER="$tx_user" WOW_PASS="$tx_pass" WOW_CHAR="$tx_char" WOW_ALLOW_ACCOUNT=1 \
WOW_PROBE_CHAT="/invite $rx_char;.group summon" \
WOW_PROBE_CHAT_AT=18 WOW_PROBE_CHAT_EVERY=10 \
WOW_PROBE_EXIT_AT=62 \
-188
View File
@@ -1,188 +0,0 @@
#!/usr/bin/env bash
# vkleg — run the client on VULKAN before it ships: a Linux container, Mesa's software Vulkan
# driver (lavapipe), a virtual display, and a handful of capture scenes (decision 2344).
#
# Why it exists (2258, B390): the gates run on Metal, and Metal has no pipeline-statistics query
# set, so a render-pass shape wgpu's Vulkan validation refuses ran clean on every gate and
# aborted every Linux and Windows-on-Vulkan player at their first world frame. lavapipe offers
# `pipelineStatisticsQuery`, so the same validation runs here — and this script, pointed at the
# pre-fix commit 4404112d6, reproduces Thalyn's report verbatim ("Query 10 was started while
# query 9 was already active") in its first world frame, while main runs all four scenes clean.
#
# scripts/vkleg.sh [<commit>] build <commit> (default HEAD) and run the scenes
# scripts/vkleg.sh --check [<commit>] exit 0 if <commit>'s tree already passed, 1 if not
#
# It builds COMMITTED content (`git archive`), never a working tree: the question it answers is
# "does this commit run on Vulkan" — the one question the macOS gates cannot ask. `/eod` runs it
# on the commit the day's post covers and drafts no post over a tree that has not passed (2344).
#
# What it needs, once: colima + docker (`brew install colima docker`). It runs in its OWN colima
# profile, `vkleg` (6 CPU, 14 GiB), started on demand and stopped afterwards: a debug client on
# a software rasterizer holds ~2 GB resident and the build wants more, which the default
# profile's 8 GiB — shared with the local servers — cannot spare, and resizing that profile would restart the servers. The
# docker context is put back to what it was. VKLEG_KEEP=1 leaves the VM up for a second run.
#
# A failure is: a scene exits non-zero, writes no PNG, or logs `wgpu error` / `panicked at`.
# Memoized on the commit's tree (a docs-only delta keeps the verdict, as in gates.sh), in
# ~/.benilla-vkleg/green — outside every checkout, so a slot's verdict serves the primary's
# identical tree. VKLEG_FORCE=1 re-runs.
set -uo pipefail
here="$(cd "$(dirname "$0")/.." && pwd)"
root="$(git -C "$here" rev-parse --show-toplevel 2>/dev/null || echo "$here")"
cd "$root" || exit 1
check_only=0
if [ "${1:-}" = "--check" ]; then check_only=1; shift; fi
rev="${1:-HEAD}"
commit="$(git rev-parse --verify -q "$rev^{commit}")" || { echo "vkleg: no such commit: $rev"; exit 2; }
tree="$(git rev-parse "$commit^{tree}")"
HOME_DIR="$HOME/.benilla-vkleg"
GREEN="$HOME_DIR/green"
PROFILE=vkleg
CTX="colima-$PROFILE"
BUILD_IMAGE=rust:1-bookworm
RUN_IMAGE=debian:trixie
SCENES=(${VKLEG_SCENES:-glue-login ui-unitframes water-night inn-interior})
# ── The memo ─────────────────────────────────────────────────────────────────────────────────
docs_only_delta() { # $1 $2 = trees
[ -z "$(git diff-tree -r --name-only "$1" "$2" 2>/dev/null | grep -vE '^(docs/|[^/]*\.md$)' || true)" ]
}
passed() {
[ -f "$GREEN" ] || return 1
local t
while read -r t _; do
[ -n "$t" ] || continue
if [ "$t" = "$tree" ]; then return 0; fi
git cat-file -e "$t^{tree}" 2>/dev/null && docs_only_delta "$t" "$tree" && return 0
done < "$GREEN"
return 1
}
if [ "$check_only" = 1 ]; then
if passed; then echo "vkleg: $(git rev-parse --short "$commit") — its tree passed"; exit 0; fi
echo "vkleg: $(git rev-parse --short "$commit") — its tree has not passed"; exit 1
fi
if [ "${VKLEG_FORCE:-}" != "1" ] && passed; then
echo "VKLEG GREEN ($(git rev-parse --short "$commit") — memoized; VKLEG_FORCE=1 re-runs)"
exit 0
fi
# ── Prerequisites: a missing one is a FAILURE, never a skip (crosscheck.sh's rule) ──────────
command -v colima >/dev/null || { echo "VKLEG FAILED: no colima (brew install colima docker)"; exit 1; }
command -v docker >/dev/null || { echo "VKLEG FAILED: no docker CLI"; exit 1; }
data="${WOW_DATA:-}"
if [ -z "$data" ]; then
install="$(cd "$root/WoW" 2>/dev/null && pwd -P)" || { echo "VKLEG FAILED: no WoW install link at $root/WoW (or set WOW_DATA to its Data folder)"; exit 1; }
data="$install/Data"
fi
[ -d "$data" ] || { echo "VKLEG FAILED: $data is not a directory"; exit 1; }
case "$data" in "$HOME"/*) ;; *) echo "VKLEG FAILED: $data is outside \$HOME, which is all colima mounts"; exit 1 ;; esac
version="$(rustc -V | awk '{print $2}')"
[ -n "$version" ] || { echo "VKLEG FAILED: cannot read this machine's rustc version"; exit 1; }
# ── The VM, on demand ────────────────────────────────────────────────────────────────────────
prev_ctx="$(docker context show 2>/dev/null || true)"
started=0
if ! colima status "$PROFILE" >/dev/null 2>&1; then
echo "vkleg: starting colima profile '$PROFILE'"
colima start "$PROFILE" --cpu 6 --memory 14 --disk 80 --vm-type vz --mount-type virtiofs \
>/dev/null 2>&1 || { echo "VKLEG FAILED: colima start $PROFILE"; exit 1; }
started=1
fi
# `colima start` switches the docker context to the new profile; put the caller's back.
[ -n "$prev_ctx" ] && docker context use "$prev_ctx" >/dev/null 2>&1
finish() {
if [ "$started" = 1 ] && [ "${VKLEG_KEEP:-}" != "1" ]; then
colima stop "$PROFILE" >/dev/null 2>&1 || true
fi
[ -n "$prev_ctx" ] && docker context use "$prev_ctx" >/dev/null 2>&1
}
trap finish EXIT
# ── Stage the commit (committed content only) ────────────────────────────────────────────────
stage="$HOME_DIR/src"
out="$HOME_DIR/out"
rm -rf "$stage" "$out" && mkdir -p "$stage" "$out" || { echo "VKLEG FAILED: cannot stage in $HOME_DIR"; exit 1; }
git archive "$commit" | tar -x -C "$stage" || { echo "VKLEG FAILED: git archive $commit"; exit 1; }
echo "vkleg: $(git rev-parse --short "$commit") on lavapipe — scenes: ${SCENES[*]}"
for v in target cargo rustup; do docker --context "$CTX" volume create "benilla-vkleg-$v" >/dev/null; done
log="$out/vkleg.log"
# Two containers: BUILD on bookworm (the rust image; the target volume stays warm across runs),
# RUN on trixie. The run image is the driver: bookworm's Mesa 22.3.6 lavapipe grows the client by
# ~14 MB a frame until the OOM killer takes it (7.3 GB after 450 frames of ui-unitframes, 13.6 GB
# and killed on water-night); the same binary under trixie's Mesa 25.0.7 holds flat at 1.9 GB and
# runs twice as fast. Metal holds the same scene at 1.3 GB, so the growth was never ours (2344).
# A bookworm-built binary runs on trixie (older glibc, newer loader).
{
docker --context "$CTX" run --rm \
-v "$stage":/src:ro \
-v benilla-vkleg-target:/xtarget \
-v benilla-vkleg-cargo:/usr/local/cargo/registry \
-v benilla-vkleg-rustup:/usr/local/rustup \
-w /src -e CARGO_TARGET_DIR=/xtarget -e CARGO_BUILD_JOBS=6 \
-e CARGO_PROFILE_DEV_DEBUG=0 -e RUSTUP_TOOLCHAIN="$version" \
"$BUILD_IMAGE" bash -c '
set -uo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq >/dev/null 2>&1
apt-get install -y -qq pkg-config libasound2-dev libudev-dev libx11-dev libxkbcommon-dev \
libwayland-dev >/dev/null 2>&1 || { echo "apt failed"; exit 1; }
rustup toolchain install --profile minimal "$RUSTUP_TOOLCHAIN" >/dev/null 2>&1
# Debug info off: the full-debuginfo benilla-app compile was OOM-killed at 8 GiB.
cargo build -p benilla 2>&1 | grep -v -E "^\s*(Compiling|Downloaded|Downloading)" | tail -5
test -x /xtarget/debug/benilla || { echo "BUILD FAILED"; exit 1; }
' || { echo "BUILD FAILED"; exit 1; }
docker --context "$CTX" run --rm \
-v "$stage":/src:ro -v "$data":/wowdata:ro -v "$out":/out \
-v benilla-vkleg-target:/xtarget:ro \
-w /src -e SCENES="${SCENES[*]}" \
"$RUN_IMAGE" bash -c '
set -uo pipefail
export DEBIAN_FRONTEND=noninteractive
apt-get update -qq >/dev/null 2>&1
apt-get install -y -qq xvfb xauth mesa-vulkan-drivers libvulkan1 libxcursor1 libxrandr2 \
libxi6 libxkbcommon-x11-0 libasound2t64 libudev1 procps >/dev/null 2>&1 \
|| { echo "apt failed"; exit 1; }
cp /xtarget/debug/benilla /tmp/benilla || { echo "no binary"; exit 1; }
fail=0
for s in $SCENES; do
echo "== scene $s"
( WOW_DATA=/wowdata WOW_CAPTURE="$s" WOW_CAPTURE_OUT="/out/$s.png" \
WOW_CAPTURE_DEADLINE=900 WOW_NOSOUND=1 WOW_UNATTENDED=1 WGPU_BACKEND=vulkan \
timeout 1200 xvfb-run -a -s "-screen 0 1280x800x24" /tmp/benilla > "/out/$s.log" 2>&1
echo $? > /tmp/rc ) &
# Resident memory, sampled: on a software rasterizer the GPU memory is RAM too, and a
# scene that outgrows the VM dies by the OOM killer (exit 137) — say so with a number.
peak=0; rm -f /tmp/rc; : > "/out/$s.rss"
while [ ! -f /tmp/rc ]; do
sleep 2
r=$(ps -C benilla -o rss= 2>/dev/null | head -1 | tr -d " ")
[ -n "$r" ] && echo "$r" >> "/out/$s.rss" && [ "$r" -gt "$peak" ] && peak=$r
done
rc=$(cat /tmp/rc)
echo " peak resident $((peak / 1024)) MB"
if [ "$rc" != 0 ]; then echo " FAILED: exit $rc"; fail=1; fi
if [ ! -s "/out/$s.png" ]; then echo " FAILED: no image"; fail=1; fi
if grep -q -E "wgpu error|panicked at" "/out/$s.log"; then
echo " FAILED:"; grep -A8 -E "wgpu error|panicked at" "/out/$s.log" | head -14 | sed "s/^/ /"
fail=1
fi
[ "$rc" = 0 ] && echo " ok"
done
exit $fail
'
} 2>&1 | tee "$log"
rc=${PIPESTATUS[0]}
if [ "$rc" = 0 ]; then
mkdir -p "$HOME_DIR" && printf '%s %s %s\n' "$tree" "$(git rev-parse --short "$commit")" "$(date +%F)" >> "$GREEN"
echo
echo "VKLEG GREEN ($(git rev-parse --short "$commit"); logs and images in $out)"
exit 0
fi
echo
echo "VKLEG RED ($(git rev-parse --short "$commit"); per-scene logs in $out)"
exit 1
-1432
View File
File diff suppressed because it is too large Load Diff