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:
@@ -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
@@ -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
@@ -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"} \
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
@@ -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
@@ -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
|
||||
Executable
+74
@@ -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
@@ -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
@@ -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 \
|
||||
|
||||
@@ -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
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user