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:
@@ -1,81 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse(EnterWorktree | Agent) guard (docs/METHOD.md):
|
||||
# refuse the HARNESS worktree tool. `scripts/wt.sh` is this repo's only worktree mechanism.
|
||||
#
|
||||
# Two doors, one room. `EnterWorktree` is the named tool; `Agent` with `isolation: "worktree"`
|
||||
# is the same worktree by another door — the subagent is spawned into a fresh
|
||||
# `.claude/worktrees/agent-<id>` checkout that no pool verb knows about (decision 2287: one was
|
||||
# found under the primary on 2026-09-16, created by a session whose method text still said to
|
||||
# use it). For `Agent` the block is conditional on that one field; every other Agent call
|
||||
# passes untouched.
|
||||
#
|
||||
# Why a guard for a tool that sounds like exactly the right thing: it IS the natural reach. The
|
||||
# session reads "every session works in its own git worktree", claims a slot, and then meets a
|
||||
# built-in tool named EnterWorktree — the name matches the rule word for word. The pull is
|
||||
# strongest right after a correct `wt.sh claim`, because the Bash tool resets cwd to the primary
|
||||
# between calls, so the printed slot path looks unusable and this looks like how you "enter" it.
|
||||
#
|
||||
# Both of its forms are wrong here, for different reasons:
|
||||
# `name` -> creates a worktree under `.claude/worktrees/`. That is precisely what the pool
|
||||
# replaced: ownerless agent worktrees, no sweeper, each paying a cold ~4.5-min dep
|
||||
# build, ~60 GB of them at the 2026-07-17 disk incident (decisions 0192, 0433 —
|
||||
# sweep step 2 still exists to reap the strays).
|
||||
# `path` -> points at a pool slot with a second tracker that has its own lifecycle opinions,
|
||||
# including removal prompts. A pool slot must never be `git worktree remove`d; its
|
||||
# warm target/ is the entire point (0192), and 0520 is the incident from a session
|
||||
# holding the wrong idea of which slot it was in.
|
||||
# So the block is absolute and needs no allowlist — same shape as guard-primary-checkout.sh.
|
||||
# There is no legitimate call here: the pool's verbs are Bash, which this never sees.
|
||||
#
|
||||
# Unscoped by design, unlike the primary-checkout guard. That one allows sibling repos because a
|
||||
# dispatched RE agent legitimately edits in wow-5875-re; this one has no such case — wow-re runs
|
||||
# its own isolated-worktree rule under its own law (docs/METHOD.md, "The cross-repo RE workflow"), and
|
||||
# an agent reaching for the harness tool there is making the same mistake in someone else's tree.
|
||||
input=$(cat)
|
||||
|
||||
# A deliberate exception names itself, the WT_ALLOW_PRIMARY / WOW_ALLOW_ACCOUNT shape (0677).
|
||||
[ -n "${WT_ALLOW_HARNESS_WORKTREE:-}" ] && exit 0
|
||||
|
||||
# No pool on this machine — a plain clone — means there is no pool to prefer: the harness tool is
|
||||
# then an ordinary worktree, and nothing here has an opinion about it (scripts/wt.sh).
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
primary=$(cd "${own:-/nonexistent}/.." 2>/dev/null && pwd) || exit 0
|
||||
pool_here() { [ -d "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" ] || [ -d "$(dirname "$1")/benilla-wt" ]; }
|
||||
pool_here "$primary" || exit 0
|
||||
|
||||
tool=$(printf '%s' "$input" | jq -r '.tool_name // empty' 2>/dev/null)
|
||||
if [ "$tool" = "Agent" ]; then
|
||||
isolation=$(printf '%s' "$input" | jq -r '.tool_input.isolation // empty' 2>/dev/null)
|
||||
[ "$isolation" = "worktree" ] || exit 0
|
||||
door="Agent isolation: worktree"
|
||||
else
|
||||
door="EnterWorktree"
|
||||
fi
|
||||
|
||||
target=$(printf '%s' "$input" | jq -r '.tool_input.path // .tool_input.name // empty' 2>/dev/null)
|
||||
|
||||
cat >&2 <<EOF
|
||||
BLOCKED — $door is not how this repo does worktrees${target:+ (asked for: $target)}.
|
||||
|
||||
\`scripts/wt.sh\` is the only worktree mechanism here (docs/METHOD.md, "Parallel sessions — one
|
||||
worktree each"). The harness tool builds worktrees under \`.claude/worktrees/\` — ownerless, with
|
||||
no sweeper and a cold ~4.5-min dependency build each; ~60 GB of those strays were the 2026-07-17
|
||||
disk incident (decisions 0192/0433). Pointing it at a pool slot instead is no better: a slot must
|
||||
never be removed, its warm target/ is the whole point.
|
||||
|
||||
If you have NOT claimed a slot yet:
|
||||
|
||||
./scripts/wt.sh claim <short-task-name> # prints the slot path on stdout
|
||||
|
||||
If you HAVE claimed one, you do not need this tool — you need the path. The Bash tool resets cwd
|
||||
to the primary between calls; that is expected, not a problem to solve. Two habits cover it
|
||||
(a dispatched agent inherits the session's slot and works there the same way — it never needs a
|
||||
tree of its own; a diagnostic that genuinely needs a clean tree is the parent's to run, on a
|
||||
synced slot):
|
||||
|
||||
Bash cd /path/to/benilla-wt/pool-N && cargo test ... # every call, prefix it
|
||||
Read/Edit /path/to/benilla-wt/pool-N/crates/... # absolute, under the slot
|
||||
|
||||
The gates, the hooks and the runtime asset reads all follow the slot on their own.
|
||||
EOF
|
||||
exit 2
|
||||
@@ -1,105 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse(Bash) guard: refuse to keep working when the PRIMARY checkout is dirty and this
|
||||
# session holds no pool slot. Exit 2 blocks the call and hands stderr back to the agent.
|
||||
#
|
||||
# **This is `guard-primary-checkout.sh`'s hole, closed.** That hook is wired to `Edit|Write|
|
||||
# NotebookEdit`, and it reasons — in a comment that was true when it was written — that "the three
|
||||
# things that genuinely write to the primary are scripts, not tool calls ... and they run as Bash,
|
||||
# which this never sees." A session editing through Bash (`sed -i`, a `python3 - <<PY` heredoc,
|
||||
# `tee`) is not covered by a single line of it. On 2026-08-25 a session did exactly that: 434 lines
|
||||
# across ten files, committed to main from the primary, while three neighbours were landing into the
|
||||
# same checkout. Nothing fired. docs/METHOD.md said, and had said for months, that such a write is
|
||||
# hook-blocked. It was not.
|
||||
#
|
||||
# **Why dirtiness and not the command.** The obvious fix is to scan the command for write verbs and
|
||||
# resolve their arguments. That is shell parsing, and it is a losing game: the write that started
|
||||
# this was a heredoc whose target path was computed inside Python, which no pattern can see. So this
|
||||
# asks a question with no parsing in it at all — *is the primary dirty, and is this session the one
|
||||
# with nowhere else to be?* It cannot be evaded, because it does not look at what you typed.
|
||||
#
|
||||
# The cost is that it fires on the command AFTER the first stray write rather than on the write
|
||||
# itself. Measured against the incident it exists for, that is one command, not 434 lines: the
|
||||
# session ran ~40 Bash calls over that work.
|
||||
#
|
||||
# **A dirty primary is never normal** (docs/METHOD.md hard rules; docs/METHOD.md "Parallel sessions"), which
|
||||
# is what makes this a cheap question to ask. The primary belongs to no session and stays clean; a
|
||||
# session that has claimed a slot is not answerable for dirt it did not make, so it passes.
|
||||
#
|
||||
# `WT_ALLOW_PRIMARY=1` overrides — in the hook's environment, or as an inline prefix on the command
|
||||
# itself, because for a Bash tool call that is where a deliberate exception is actually typed.
|
||||
input=$(cat)
|
||||
|
||||
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
||||
[ -n "$cmd" ] || exit 0
|
||||
|
||||
# The deliberate exception, named in the transcript either way.
|
||||
[ -n "${WT_ALLOW_PRIMARY:-}" ] && exit 0
|
||||
case "$cmd" in *WT_ALLOW_PRIMARY=1*) exit 0 ;; esac
|
||||
|
||||
# The remedy must never be blocked by the thing it remedies: `wt.sh claim` is the one command this
|
||||
# message asks for, and `status`/`land`/`release` are how a session gets itself unstuck.
|
||||
case "$cmd" in *wt.sh*) exit 0 ;; esac
|
||||
|
||||
# Scoped by the shared git common-dir, like the sibling hooks: another repo (wow-5875-re) has its
|
||||
# own, so a dispatched RE agent is never touched by benilla's rule.
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
[ -n "$own" ] || exit 0
|
||||
primary=$(cd "$own/.." 2>/dev/null && pwd) || exit 0
|
||||
# No pool on this machine — a plain clone — means this checkout IS the working tree: nothing to
|
||||
# guard. The pool is what makes the primary off-limits (scripts/wt.sh); without one, the rule has
|
||||
# nothing to protect.
|
||||
pool_here() { [ -d "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" ] || [ -d "$(dirname "$1")/benilla-wt" ]; }
|
||||
pool_here "$primary" || exit 0
|
||||
|
||||
# Clean primary -> nothing to protect, and this is the overwhelmingly common case: one `git status`
|
||||
# against a warm index, ~10 ms, on a tree whose only large directory (target/) is gitignored.
|
||||
dirt=$(git -C "$primary" status --porcelain 2>/dev/null | head -6)
|
||||
[ -n "$dirt" ] || exit 0
|
||||
|
||||
mine="${CLAUDE_CODE_SESSION_ID:-}"
|
||||
[ -n "$mine" ] || exit 0 # a human shell or CI — not ours to police
|
||||
|
||||
# Does this session already hold a slot? Then it has somewhere to be, and the dirt in the primary
|
||||
# is somebody else's problem to answer for — blocking here would be a gate firing on innocent work,
|
||||
# which is how a gate teaches "the gate is broken, work around it".
|
||||
#
|
||||
# **BOTH pool roots, because there are two** (decision 1726): slots are allocated on the external
|
||||
# drive, and the old internal root beside the primary is draining but still live. This hook asked
|
||||
# only the internal one — so from the day 1726 landed, a session holding an external slot answered
|
||||
# "no slot" and was blocked on its NEXT Bash call after any neighbour dirtied the primary. That is
|
||||
# a gate firing on exactly the innocent work the paragraph above says it must not, and it takes out
|
||||
# every session at once rather than the one that made the mess. `WT_POOL_ROOT` is honoured for the
|
||||
# same reason `wt.sh` honours it.
|
||||
for pool in "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" "$(dirname "$primary")/benilla-wt"; do
|
||||
for marker in "$pool"/pool-*/.wt-claimed; do
|
||||
[ -e "$marker" ] || continue
|
||||
[ "$(cut -f3 "$marker" 2>/dev/null)" = "$mine" ] && exit 0
|
||||
done
|
||||
done
|
||||
|
||||
cat >&2 <<EOF
|
||||
BLOCKED — the PRIMARY checkout is dirty and this session has not claimed a slot.
|
||||
|
||||
primary: $primary
|
||||
$(printf '%s\n' "$dirt" | sed 's/^/ /')
|
||||
|
||||
The primary belongs to no session and stays clean (docs/METHOD.md hard rules; docs/METHOD.md "Parallel
|
||||
sessions — one worktree each"). Working here reverts other sessions' edits, jams the
|
||||
whole-workspace gates with a neighbour's in-flight code, and blocks their \`wt.sh land\`.
|
||||
|
||||
If those changes are YOURS — you have been editing the primary, most likely through Bash, which
|
||||
\`guard-primary-checkout.sh\` cannot see:
|
||||
|
||||
./scripts/wt.sh claim <short-task-name> # prints a slot path
|
||||
git -C $primary diff > /tmp/carry.patch # needs WT_ALLOW_PRIMARY=1 on this call
|
||||
# …then apply it in the slot and reset the primary.
|
||||
|
||||
If they are NOT yours, do not clean them up — they are another session's live work. Claim a slot
|
||||
and work there; say so to the director if the primary stays dirty.
|
||||
|
||||
If the pool is genuinely full, \`wt.sh claim\` says so and the answer is to STOP and ask the
|
||||
director which slot to take over — never to carry on here.
|
||||
|
||||
A deliberate exception is \`WT_ALLOW_PRIMARY=1 <command>\`.
|
||||
EOF
|
||||
exit 2
|
||||
@@ -1,126 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse(Edit|Write|NotebookEdit) guard (docs/METHOD.md):
|
||||
# refuse to write into a checkout that is not this session's. Exit 2 blocks the call and hands
|
||||
# stderr back to the agent, which then claims a slot and works there.
|
||||
#
|
||||
# TWO trees are not yours, and the file name only says the first (kept, deliberately: renaming it
|
||||
# would churn `.claude/settings.json`, which every live session reads, for a naming tidy-up):
|
||||
#
|
||||
# 1 · the PRIMARY checkout — nobody's, and it stays clean (decision 0976, below);
|
||||
# 2 · another SESSION's pool slot (decision 1042).
|
||||
#
|
||||
# It fires at the FIRST edit because that is where the fix is one `wt.sh claim` and nothing has been
|
||||
# lost; unguarded, the cost lands on a BYSTANDER hours later, whose `wt.sh land` refuses on a dirty
|
||||
# primary full of work they can neither see nor move. Why a hook and not more prose: decision 0976.
|
||||
#
|
||||
# The block is absolute and needs no allowlist — there is no legitimate agent edit here. The one
|
||||
# thing that genuinely writes to the primary is a script, not a tool call (`wt.sh land` fast-forwards
|
||||
# main), and it runs as Bash, which this never sees. `WT_ALLOW_PRIMARY=1` overrides, the `WOW_ALLOW_ACCOUNT` shape
|
||||
# from 0677: structurally hard rather than merely forbidden, and a deliberate exception names itself.
|
||||
#
|
||||
# **"…which this never sees" WAS the hole, and it is why `guard-primary-bash.sh` exists.** That
|
||||
# sentence assumed only scripts and humans reach the primary through Bash. An AGENT editing with
|
||||
# `sed -i` or a `python3 - <<PY` heredoc does too, and none of it passes through this file: on
|
||||
# 2026-08-25 a session wrote 434 lines into the primary and committed them to main without one line
|
||||
# of this hook running. This one still catches the Edit/Write path at the FIRST write, which is the
|
||||
# better moment; the Bash guard is the backstop that cannot be evaded by choice of tool.
|
||||
#
|
||||
# Scoped by the shared git common-dir, like `on-stop.sh`: a sibling repo
|
||||
# (wow-5875-re) has its own, so a dispatched RE agent editing THERE is never touched by benilla's
|
||||
# rule. Paths outside any git repo — the scratchpad, `~/.benilla` — are likewise none of our business.
|
||||
input=$(cat)
|
||||
|
||||
# Edit/Write carry `file_path`; NotebookEdit carries `notebook_path`.
|
||||
path=$(printf '%s' "$input" | jq -r '.tool_input.file_path // .tool_input.notebook_path // empty' 2>/dev/null)
|
||||
[ -n "$path" ] || exit 0 # no target we can judge → allow
|
||||
|
||||
# Absolutize against the tool's own cwd — a relative path from a session sitting IN the primary is
|
||||
# exactly the case this exists to catch, and it is the one that arrives without a leading slash.
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
cwd="${cwd:-$PWD}"
|
||||
case "$path" in
|
||||
/*) ;;
|
||||
'~'/*) path="${HOME}${path#\~}" ;;
|
||||
*) path="$cwd/$path" ;;
|
||||
esac
|
||||
|
||||
# A Write targets a file that does not exist yet, and may name directories that do not either, so
|
||||
# resolve against the nearest existing ancestor rather than the path itself.
|
||||
dir=$(dirname "$path")
|
||||
while [ ! -d "$dir" ] && [ "$dir" != "/" ] && [ "$dir" != "." ]; do dir=$(dirname "$dir"); done
|
||||
[ -d "$dir" ] || exit 0
|
||||
|
||||
# Same-REPO check first (this repo's worktrees all share one common-dir; other repos don't).
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
there=$(git -C "$dir" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
[ -n "$own" ] && [ "$there" = "$own" ] || exit 0
|
||||
|
||||
# The primary checkout is the common-dir's parent — the same expression `wt.sh` computes `PRIMARY`
|
||||
# with, so the two can never disagree about which tree is the shared one. A linked worktree's
|
||||
# toplevel is its own slot path, and falls through to the slot check below.
|
||||
primary=$(cd "$own/.." 2>/dev/null && pwd) || exit 0
|
||||
# No pool on this machine — a plain clone — means this checkout IS the working tree: nothing to
|
||||
# guard. The pool is what makes the primary off-limits (scripts/wt.sh); without one, the rule has
|
||||
# nothing to protect.
|
||||
pool_here() { [ -d "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" ] || [ -d "$(dirname "$1")/benilla-wt" ]; }
|
||||
pool_here "$primary" || exit 0
|
||||
top=$(git -C "$dir" rev-parse --path-format=absolute --show-toplevel 2>/dev/null)
|
||||
[ -n "$top" ] || exit 0
|
||||
|
||||
if [ "$top" = "$primary" ]; then
|
||||
[ -n "${WT_ALLOW_PRIMARY:-}" ] && exit 0 # deliberate exception, named in the transcript
|
||||
|
||||
rel="${path#"$primary"/}"
|
||||
cat >&2 <<EOF
|
||||
BLOCKED — that write targets the PRIMARY checkout ($primary/$rel).
|
||||
|
||||
The primary belongs to no session and stays clean (docs/METHOD.md hard rules; docs/METHOD.md "Parallel
|
||||
sessions — one worktree each"). Editing it silently reverts other sessions' work, jams the
|
||||
whole-workspace gates with a neighbour's in-flight code, and blocks their \`wt.sh land\`.
|
||||
|
||||
Claim a slot and work there instead:
|
||||
|
||||
./scripts/wt.sh claim <short-task-name> # prints the slot path; cd there and carry on
|
||||
|
||||
If you already have a claimed slot, you are simply in the wrong directory — reissue this edit
|
||||
against the slot path. A deliberate exception is \`WT_ALLOW_PRIMARY=1\`.
|
||||
EOF
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── Another session's slot (decision 1042) ───────────────────────────────────────────────────────
|
||||
# 1041 stopped a foreign session at `wt.sh land`. This stops it at the first EDIT, which is 0976's
|
||||
# whole argument one level down: at the verb, the damage is already a split branch somebody has to
|
||||
# unpick by hand; at the edit, the fix is one `wt.sh claim` and nothing has happened yet. On
|
||||
# 2026-08-06 the gap between the two was an hour, a red `fmt --check` on files this session had
|
||||
# never touched, and two decision records that reverted under it mid-write.
|
||||
#
|
||||
# **Agents are safe here, and that is a measured fact, not a hope**: a dispatched subagent reports
|
||||
# the same `CLAUDE_CODE_SESSION_ID` as its parent (checked, 2026-08-06 — same value, same PID). So
|
||||
# the bulk-edit dispatches docs/METHOD.md's "Who types what" depends on, which work in their session's
|
||||
# own slot by design, pass this untouched. 1041 named that as the fact it was waiting on.
|
||||
marker="$top/.wt-claimed"
|
||||
[ -e "$marker" ] || exit 0 # free slot: unclaimed, no session to wrong
|
||||
owner=$(cut -f3 "$marker" 2>/dev/null)
|
||||
case "$owner" in "" | "-") exit 0 ;; esac # pre-1041 marker: no session recorded
|
||||
mine="${CLAUDE_CODE_SESSION_ID:-}"
|
||||
[ -n "$mine" ] || exit 0 # nothing to compare (human shell, CI)
|
||||
[ "$owner" = "$mine" ] && exit 0 # yours — the overwhelmingly common case
|
||||
[ -n "${WT_ALLOW_FOREIGN:-}" ] && exit 0 # deliberate exception, named in the transcript
|
||||
|
||||
holder=$(cut -f1 "$marker" 2>/dev/null)
|
||||
cat >&2 <<EOF
|
||||
BLOCKED — that write targets ANOTHER SESSION's worktree ($top).
|
||||
|
||||
that slot is claim '$holder', held by session $owner
|
||||
you are session $mine
|
||||
|
||||
Two sessions in one slot revert each other's edits, redden each other's gates, and split each
|
||||
other's commits at land (decisions 1041, 1042). Claim your own slot:
|
||||
|
||||
./scripts/wt.sh claim <short-task-name> # prints the slot path; cd there and carry on
|
||||
|
||||
\`wt.sh status\` tags the slot you own \`(yours)\` — if nothing is tagged, you have not claimed one.
|
||||
A deliberate exception (recovering a dead session's work, say) is \`WT_ALLOW_FOREIGN=1\`.
|
||||
EOF
|
||||
exit 2
|
||||
@@ -1,139 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse(Bash) guard (docs/METHOD.md "Who types what": *Parallel agents share one working tree —
|
||||
# whole-tree git ops are forbidden*): refuse the handful of git verbs that sweep a tree — or the
|
||||
# SHARED STASH STACK — rather than a path.
|
||||
#
|
||||
# Why a hook, on 2026-08-14: a session reaching for a one-command "revert my file, run the test,
|
||||
# put it back" repro typed `git stash push -- <path> -q`. The trailing `-q` parsed as a pathspec,
|
||||
# the push failed — and the follow-up `git stash pop` then popped a DIFFERENT session's entry
|
||||
# (`sess/death-arc`'s) into this worktree. It conflicted on docs/MAP.md and aborted, so nothing was
|
||||
# lost; the near-miss is the point. **Stashes are per-REPOSITORY, not per-worktree** — every pool
|
||||
# slot shares one stack — so `stash pop` is a whole-tree op wearing a local-looking name, and the
|
||||
# blast radius is another session's uncommitted hours. The earlier incident docs/METHOD.md records (a
|
||||
# six-agent fan-out losing hours to one agent's diagnostic `git stash`) is the same mechanism.
|
||||
#
|
||||
# The rule the prose already had, made structural — 0976's argument for the primary-checkout guard,
|
||||
# one verb over: at the command, the fix is one different command and nothing has happened; after
|
||||
# it, somebody unpicks a stash stack by hand and hopes the conflict preserved their work.
|
||||
#
|
||||
# **The block list is deliberately SHORT.** A gate that fires on legitimate work teaches "the gate
|
||||
# is broken, work around it" (the 2026-07-20 incident), so
|
||||
# this blocks only forms with no path-scoped reading:
|
||||
#
|
||||
# git stash [push|save|pop|apply|drop|clear|branch|create|store] — the shared stack
|
||||
# git reset --hard — sweeps the tree
|
||||
# git clean -f… — deletes untracked files
|
||||
# git checkout . | git checkout -- . | git restore . | … — the sweep-everything forms
|
||||
#
|
||||
# Everything else passes, including the ones this repo's own work depends on: `git stash list`/
|
||||
# `show` (read-only), `git checkout HEAD -- <path>` (the correct way to do the repro above),
|
||||
# `git checkout --detach`/`-B <branch>` (what `wt.sh` itself runs), `git restore --source=X -- path`,
|
||||
# and a soft/mixed `git reset` (which moves HEAD but leaves the working tree alone).
|
||||
#
|
||||
# Scoped by the shared git common-dir like every other hook here: a sibling repo (wow-5875-re) has
|
||||
# its own rules and falls through untouched. Note the hook only ever sees the agent's own command
|
||||
# text, so git run INSIDE our scripts (`wt.sh`) is never its business.
|
||||
#
|
||||
# `WT_ALLOW_TREE_OP=1` overrides, the `WOW_ALLOW_ACCOUNT`/`WT_ALLOW_PRIMARY` shape (0677, 0976):
|
||||
# structurally hard rather than merely forbidden, and a deliberate exception names itself.
|
||||
input=$(cat)
|
||||
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // empty' 2>/dev/null)
|
||||
[ -n "$cmd" ] || exit 0
|
||||
[ -n "${WT_ALLOW_TREE_OP:-}" ] && exit 0 # deliberate exception, named in the transcript
|
||||
|
||||
# Per SEGMENT of a (possibly compound) command, so a `git stash` riding behind a `&&` is judged
|
||||
# on its own rather than by the line's first verb.
|
||||
verdict=""
|
||||
while IFS= read -r seg; do
|
||||
# Strip a leading `cd … &&`-style residue and normalise whitespace for matching.
|
||||
s=$(printf '%s' "$seg" | sed 's/^[[:space:]]*//; s/[[:space:]]\{1,\}/ /g')
|
||||
# Only `git …` invocations, with or without a leading `-C <dir>`.
|
||||
case "$s" in
|
||||
git\ *) ;;
|
||||
*) continue ;;
|
||||
esac
|
||||
# Drop `-C <dir>` and other pre-verb globals so the verb is the next token.
|
||||
v=$(printf '%s' "$s" | sed -E 's/^git( +-[A-Za-z-]+( +[^ ]+)?)* +//')
|
||||
case "$v" in
|
||||
# ── the shared stash stack (read-only subcommands excepted) ──────────────────────────────
|
||||
stash\ list* | stash\ show*) ;;
|
||||
stash | stash\ *)
|
||||
verdict="git stash — the stash stack is shared by EVERY worktree of this repo, so this can
|
||||
capture, or restore, another live session's uncommitted work. It has already come within one
|
||||
merge conflict of doing exactly that (2026-08-14).
|
||||
|
||||
For the common case — 'revert one file, run something, put it back' — copy it aside and use the
|
||||
path-scoped restore instead:
|
||||
|
||||
cp <file> \"\$SCRATCH/<file>.mine\"
|
||||
git checkout HEAD -- <file> # path-scoped: touches nothing else, no shared state
|
||||
<run the thing>
|
||||
cp \"\$SCRATCH/<file>.mine\" <file>
|
||||
|
||||
To park work properly, commit it on your session branch — that is what the branch is for."
|
||||
break
|
||||
;;
|
||||
# ── whole-tree resets / sweeps ───────────────────────────────────────────────────────────
|
||||
reset*--hard*)
|
||||
verdict="git reset --hard — discards every uncommitted change in this worktree, including
|
||||
work belonging to a dispatched agent running in this session's slot right now.
|
||||
|
||||
Undo one path with \`git checkout HEAD -- <path>\`; move HEAD without touching files with
|
||||
\`git reset --soft\`."
|
||||
break
|
||||
;;
|
||||
clean\ *-*f* | clean\ -*f*)
|
||||
verdict="git clean -f — deletes untracked files outright, and untracked is exactly what a
|
||||
neighbouring agent's not-yet-added new file looks like.
|
||||
|
||||
Delete the specific files you mean by name, or list first with \`git clean -n\`."
|
||||
break
|
||||
;;
|
||||
checkout\ . | checkout\ --\ . | checkout\ -- | restore\ . | restore\ --\ . | checkout\ -f | checkout\ -f\ *)
|
||||
verdict="a pathless/sweep-everything checkout — it reverts the WHOLE tree, not the file you
|
||||
are looking at.
|
||||
|
||||
Name the path: \`git checkout HEAD -- <path>\` (or \`git restore -- <path>\`)."
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done <<EOF
|
||||
$(printf '%s' "$cmd" | tr ';&|\n' '\n\n\n\n')
|
||||
EOF
|
||||
|
||||
[ -n "$verdict" ] || exit 0
|
||||
|
||||
# Same-REPO check LAST — it costs a git call, and the overwhelming majority of commands never
|
||||
# reach it. The cwd resolution: a tree-wide op is judged
|
||||
# by the tree it lands in, and a `cd <slot> && …` is how every session writes one.
|
||||
cwd=$(printf '%s' "$input" | jq -r '.cwd // empty' 2>/dev/null)
|
||||
cwd="${cwd:-$PWD}"
|
||||
cd_dir=$(printf '%s' "$cmd" | tr ';&|' '\n' \
|
||||
| sed -n 's/^[[:space:]]*cd[[:space:]]\{1,\}\([^[:space:]]\{1,\}\)[[:space:]]*$/\1/p' | tail -n 1)
|
||||
if [ -n "$cd_dir" ]; then
|
||||
case "$cd_dir" in
|
||||
/*) cwd="$cd_dir" ;;
|
||||
'~'*) cwd="${HOME}${cd_dir#\~}" ;;
|
||||
*) cwd="$cwd/$cd_dir" ;;
|
||||
esac
|
||||
fi
|
||||
[ -d "$cwd" ] || cwd=$PWD
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
there=$(git -C "$cwd" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
[ -n "$own" ] && [ "$there" = "$own" ] || exit 0 # another repo's rules are its own
|
||||
primary=$(cd "$own/.." 2>/dev/null && pwd) || exit 0
|
||||
# No pool on this machine — a plain clone — means this checkout IS the working tree, shared with
|
||||
# nobody: nothing to guard. The pool is what makes the tree shared (scripts/wt.sh).
|
||||
pool_here() { [ -d "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" ] || [ -d "$(dirname "$1")/benilla-wt" ]; }
|
||||
pool_here "$primary" || exit 0
|
||||
|
||||
cat >&2 <<EOF
|
||||
BLOCKED — tree-wide git op in a SHARED working tree.
|
||||
|
||||
$verdict
|
||||
|
||||
(docs/METHOD.md, "Who types what": parallel agents share one working tree, so whole-tree git ops are
|
||||
forbidden — diagnostics use path-scoped commands only. A genuinely deliberate exception is
|
||||
WT_ALLOW_TREE_OP=1; if you need a clean tree of your own, take a worktree instead.)
|
||||
EOF
|
||||
exit 2
|
||||
@@ -51,7 +51,7 @@ while [ ! -d "$dir" ] && [ "$dir" != "/" ] && [ "$dir" != "." ]; do dir=$(dirnam
|
||||
real=$(cd -P "$dir" 2>/dev/null && pwd) || exit 0
|
||||
|
||||
# The roots to protect. `$WOW_DATA` first (the resolver's own order), then the `WoW` link every
|
||||
# checkout and pool slot carries. Both physicalised, so the symlink and its target are one root.
|
||||
# checkout carries. Both physicalised, so the symlink and its target are one root.
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
primary=$(cd "${own:-.}/.." 2>/dev/null && pwd)
|
||||
for root in "${WOW_DATA:-}" "$primary/WoW" "$cwd/WoW"; do
|
||||
|
||||
@@ -1,63 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Stop hook (docs/METHOD.md): tidy pass at the end of every turn — stamp this session's slot
|
||||
# claim so the pool can tell a live session from a dead one, and format Rust so the fmt gate is
|
||||
# green by the time anything commits. Best-effort: never block the agent from stopping (always
|
||||
# exit 0). Operates on the SESSION'S checkout (worktree-aware), guarded via the shared git common-dir
|
||||
# so it never formats a sibling repo (all benilla worktrees share one common-dir; wow-5875-re doesn't).
|
||||
#
|
||||
# It also **never formats the primary checkout**, and says so. Formatting is a write, and the primary
|
||||
# belongs to no session (docs/METHOD.md "Parallel sessions"); a session sitting there is the mistake
|
||||
# `guard-primary-checkout.sh` blocks at edit time, and this is the one place that notices the same
|
||||
# mistake arriving by any OTHER route — a `sed -i`, a `perl -pi`, a script — since it runs every
|
||||
# turn regardless of which tools were used. Warn, never block: a Stop hook that refuses to stop is a
|
||||
# trap, and this one is best-effort by design.
|
||||
own=$(git -C "$(dirname "$0")" rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
|
||||
# ── The claim HEARTBEAT ──────────────────────────────────────────────────────────────────────────
|
||||
# Stamp this session's claim marker once a turn, so "is anyone still working in that slot?" is a
|
||||
# fact we hold rather than a guess. Before this, the only activity signal a dead claim could be
|
||||
# judged on was its TIP COMMIT age, and `wt.sh` says why that is a poor one: a live session looks
|
||||
# stale the moment it lands, so the reap window had to be 24 h on BOTH clocks to be safe. The cost
|
||||
# of that safety was the director's own complaint — close a session after its work lands and the
|
||||
# slot sits unusable for a day, with nothing "obviously free" for the next session to take.
|
||||
#
|
||||
# Deliberately BEFORE the cwd checks below, and found by SESSION ID rather than by `$PWD`: a turn
|
||||
# that happened to end in the scratchpad, or in the primary, is still a turn this session was alive
|
||||
# for. Best-effort like everything else here — a failed touch must never keep a session from
|
||||
# stopping.
|
||||
mine="${CLAUDE_CODE_SESSION_ID:-}"
|
||||
if [ -n "$own" ] && [ -n "$mine" ]; then
|
||||
primary_dir=$(cd "$own/.." 2>/dev/null && pwd)
|
||||
if [ -n "$primary_dir" ]; then
|
||||
# BOTH pool roots (decision 1726): slots are allocated on the external drive and the old
|
||||
# internal root is draining. This walked only the internal one, so from the day 1726 landed the
|
||||
# heartbeat stopped stamping for every session in the new pool — and the heartbeat is the whole
|
||||
# reason `wt.sh` can tell a live claim from a dead one. A claim that never restamps looks idle
|
||||
# to `reap_idle_claims`, which is how a full `claim` takes over a slot somebody is working in.
|
||||
for pool in "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" "$(dirname "$primary_dir")/benilla-wt"; do
|
||||
for marker in "$pool"/pool-*/.wt-claimed; do
|
||||
[ -e "$marker" ] || continue
|
||||
[ "$(cut -f3 "$marker" 2>/dev/null)" = "$mine" ] || continue
|
||||
touch "$marker" 2>/dev/null || true
|
||||
break 2
|
||||
done
|
||||
done
|
||||
fi
|
||||
fi
|
||||
|
||||
root=$(git rev-parse --path-format=absolute --show-toplevel 2>/dev/null)
|
||||
cur=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null)
|
||||
[ -n "$root" ] && [ -n "$own" ] && [ "$cur" = "$own" ] || exit 0
|
||||
primary=$(cd "$own/.." 2>/dev/null && pwd)
|
||||
# No pool on this machine — a plain clone — means the checkout IS the working tree, and formatting
|
||||
# it is the whole job. The pool is what makes the primary off-limits (scripts/wt.sh).
|
||||
pool_here() { [ -d "${WT_POOL_ROOT:-/Volumes/SanDisk/benilla-wt}" ] || [ -d "$(dirname "$1")/benilla-wt" ]; }
|
||||
if [ -n "$primary" ] && [ "$root" = "$primary" ] && [ -z "${WT_ALLOW_PRIMARY:-}" ] && pool_here "$primary"; then
|
||||
echo "on-stop: this session is sitting in the PRIMARY checkout ($primary) — it belongs to no" >&2
|
||||
echo "on-stop: session and must stay clean. Not formatting it. Run './scripts/wt.sh claim <name>'" >&2
|
||||
echo "on-stop: and work in the printed slot; move anything already changed here into that slot." >&2
|
||||
exit 0
|
||||
fi
|
||||
cd "$root" || exit 0
|
||||
cargo fmt --all >/dev/null 2>&1 || true
|
||||
exit 0
|
||||
@@ -1,57 +1,16 @@
|
||||
{
|
||||
"autoMemoryEnabled": false,
|
||||
"hooks": {
|
||||
"Stop": [
|
||||
{
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/on-stop.sh\"",
|
||||
"timeout": 120
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-tree-wide-git.sh\"",
|
||||
"timeout": 10
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-primary-bash.sh\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "Edit|Write|NotebookEdit",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-primary-checkout.sh\"",
|
||||
"timeout": 10
|
||||
},
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-wow-install.sh\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"matcher": "EnterWorktree|Agent",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "\"${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-harness-worktree.sh\"",
|
||||
"timeout": 10
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -5,15 +5,19 @@ description: Run the client unattended against the local vmangos server — a li
|
||||
|
||||
The rules are in `docs/METHOD.md` ("The local server"). This is the how.
|
||||
|
||||
**Identity.** `WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<N spelled>` for `pool-N`
|
||||
(pool-4: `probe4` / `pprobe4` / `Probefour`; pool-0: `Probezero`). The client refuses a login
|
||||
about to land on a player's account or another slot's; `WOW_ALLOW_ACCOUNT=1` overrides.
|
||||
Probe accounts hold the highest GM level, so every GM command works.
|
||||
**Identity.** The account this checkout declares in `.probe-identity` at the tree root
|
||||
(`WOW_USER=`, `WOW_PASS=`, `WOW_CHAR=`, one per line, never committed), or the same three in
|
||||
the environment. A login kicks whoever holds the account, so a probe account is its own, with
|
||||
the top GM level (the GM commands the probes use all work; `.modify money` is out of reach
|
||||
even so), and from a checkout that declares one the client refuses the scripted login on any
|
||||
other account; `WOW_ALLOW_ACCOUNT=1` overrides. `scripts/smoke.sh`, `cine.sh` and
|
||||
`summon-live.sh` take the identity from `scripts/probe-identity.sh`, which applies this rule.
|
||||
|
||||
**Every run:** `WOW_UNATTENDED=1`, which reconnects instead of parking on a dialog and exits
|
||||
non-zero on a login it cannot pass. An agent adds `WOW_NOSOUND=1`. `scripts/leg.sh`,
|
||||
`smoke.sh`, `cine.sh` and `summon-live.sh` pass both already; a capture (`WOW_CAPTURE`) and a
|
||||
rig (`WOW_RIG`) imply them.
|
||||
non-zero on a login it cannot pass. An agent adds `WOW_NOSOUND=1`. The three scripts pass
|
||||
`WOW_UNATTENDED=1`; `smoke.sh` and `summon-live.sh` add `WOW_NOSOUND=1`, and `cine.sh`
|
||||
silences through a throwaway config with `MasterVolume=0` because the music path is its
|
||||
subject. A capture (`WOW_CAPTURE`) implies both; a rig (`WOW_RIG`) implies unattended only.
|
||||
|
||||
**The body.** A probe cannot die: every world entry sends `.cheat god on`. `.die` clears it for
|
||||
a death test; `WOW_GOD=off` runs unshielded. GM mode is on by default and makes reaction
|
||||
|
||||
+6
-8
@@ -6,9 +6,6 @@
|
||||
# --- Blizzard-owned content: NEVER commit ---
|
||||
# The user's legally-installed 1.12.1 client. Read at runtime; not redistributed.
|
||||
/WoW
|
||||
# The user's legally-installed Classic Era client (product wow_classic_era; the shared CASC Data
|
||||
# dir sits beside the product dir). Source of the Era UI exports for decision 0068; not redistributed.
|
||||
/WoW-era
|
||||
# Cached / extracted assets derived from the user's install.
|
||||
/extracted/
|
||||
/out/
|
||||
@@ -17,17 +14,18 @@
|
||||
|
||||
# --- benilla's own local state (decision 0954, moved out of the install by 1175) ---
|
||||
# The one folder benilla persists into: config.toml, macros/, bindings/, saved-variables.lua,
|
||||
# camera/. A dev build resolves it to the primary checkout (so the worktree pool shares one); a
|
||||
# camera/. A dev build resolves it to the main checkout (so linked worktrees share one); a
|
||||
# release build puts it beside the binary — which is why it is not called `benilla/`: that name
|
||||
# collides with the binary of the same name. Machine-local by definition — `crate::local_state`
|
||||
# is the only thing in the tree that may write it.
|
||||
/benilla-config
|
||||
|
||||
# Worktree-pool claim marker (scripts/wt.sh) — per-slot state, never content.
|
||||
/.wt-claimed
|
||||
# The account a scripted run from this checkout logs in as (scripts/probe-identity.sh and the
|
||||
# client's account guard read it): WOW_USER=, WOW_PASS=, WOW_CHAR=, one per line. A credential.
|
||||
/.probe-identity
|
||||
|
||||
# Profiling output: `--features bevy/trace_chrome` drops a multi-GB flame chart in the cwd, which
|
||||
# otherwise leaves the checkout "dirty" and blocks the worktree verbs (decision 0610).
|
||||
# otherwise leaves the checkout "dirty" (decision 0610).
|
||||
trace-*.json
|
||||
|
||||
# OS / editor cruft
|
||||
@@ -46,5 +44,5 @@ trace-*.json
|
||||
__pycache__/
|
||||
|
||||
# The vanilla addon corpus the UI engine's real-addon tests read (third-party content, decision
|
||||
# 2329): a symlink `scripts/wt.sh` lays into every slot and the primary, beside `WoW`.
|
||||
# 2329): a symlink beside `WoW`, or `BENILLA_ADDON_CORPUS`.
|
||||
/wow-addons-vanilla
|
||||
|
||||
+2
-2
@@ -58,9 +58,9 @@ sha1 = "0.10.6"
|
||||
# The one entry below is not a fork of a WoW crate at all. `lua-src` compiles the Lua the client's
|
||||
# addon VM runs on, and benilla's Lua has to accept EXACTLY the grammar the 1.12.1 client's Lua
|
||||
# accepts — no more and no less. The hunks — `third_party/lua-src/BENILLA.md` lists them — all byte-read out of the client's own parser and VM:
|
||||
# three restore what 5.1 removed (the iterator-less generic-for `for k, v in someTable do`, which
|
||||
# they restore what 5.1 removed (the iterator-less generic-for `for k, v in someTable do`, which
|
||||
# 183 of 218 corpus addons are reached by; nesting long strings; the constructor's compat
|
||||
# semicolon), three delete what 5.1 added (`...` as an expression, `#`, `%`). Both directions are
|
||||
# semicolon) and delete what 5.1 added (`...` as an expression, `#`, `%`). Both directions are
|
||||
# below the reach of any layer above the VM — an opcode and a grammar are not a library function,
|
||||
# a global, or a metamethod — and the corpus ASKS about the second direction directly: 170
|
||||
# `loadstring` sites compile a vararg expression purely to find out which interpreter they are on.
|
||||
|
||||
@@ -62,8 +62,10 @@ Not planned: other expansions or client versions, Warden (anticheat).
|
||||
## Running it
|
||||
|
||||
You need a **1.12.1 (build 5875) client install** for game data, a vanilla server to connect to,
|
||||
and stable Rust. Any 1.12.1 core works; [vmangos](https://github.com/vmangos/core) is what
|
||||
development runs against, and cMaNGOS and the rest speak the same protocol.
|
||||
stable Rust and a C compiler (the Lua is built from source; on macOS the Xcode command line
|
||||
tools, on Linux the ALSA and udev development packages). Any 1.12.1 core works;
|
||||
[vmangos](https://github.com/vmangos/core) is what development runs against, and cMaNGOS and
|
||||
the rest speak the same protocol.
|
||||
|
||||
```sh
|
||||
WOW_DATA=/path/to/WoW/Data cargo run --release -p benilla
|
||||
|
||||
@@ -150,8 +150,8 @@ smithay-clipboard = "0.7"
|
||||
|
||||
# macOS: drive a native NSCursor for a crisp hardware cursor. winit drives the cursor via AppKit
|
||||
# cursor-rects, which our continuous Metal render loop doesn't honor on mouse-move (custom cursor /
|
||||
# hide revert to the arrow); we bypass it with NSCursor + NSWindow::disableCursorRects. See
|
||||
# docs/knowledge/camera-control.md.
|
||||
# hide revert to the arrow); we bypass it with NSCursor + NSWindow::disableCursorRects
|
||||
# (`video::cursor`).
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
# The audio device layer (decision 1857, `sound::output::coreaudio`): the HAL output unit through
|
||||
# coreaudio-rs's `AudioUnit`, and the AudioObject property/listener API through the objc2 CoreAudio
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
//! is the step this probe exists for. The house id is checked to be in `1..=7` because it keys the
|
||||
//! deposit rate the sell pane quotes, and a zero would silently quote a free listing.
|
||||
//!
|
||||
//! ## The auctioneer (live-DB verified this session, `/Users/sam/dev/vmangos-deploy` → `mangos`)
|
||||
//! ## The auctioneer (live-DB verified against the local vmangos, `mangos`)
|
||||
//!
|
||||
//! Auctioneer Fitch, creature entry 8719, spawn guid 12696, map 0 (Stormwind, Trade District),
|
||||
//! pos `(-8821.53, 659.886, 97.4645)`. Her `creature_template.npc_flags` is **4096** exactly —
|
||||
@@ -57,8 +57,8 @@
|
||||
//! WOW_PROBE_AUCTION=1 WOW_NOSOUND=1 WOW_USER=probe5 WOW_PASS=pprobe5 WOW_CHAR=Probefive \
|
||||
//! cargo run -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>`, docs/METHOD.md "The local vmangos server"; this worktree is `pool-5`).
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill).
|
||||
//! Non-combat. An outer grep on `PROBE_AUCTION:` is the whole harness; the probe self-exits (the
|
||||
//! [`super::probes::ProbeExitPlugin`] pattern) once DONE.
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
//! window is first-party, no Lua click surface to drive); the live Lua VM is only touched for the
|
||||
//! bonus refusal step's `UI_ERROR_MESSAGE` observation, the mail probe's own idiom.
|
||||
//!
|
||||
//! ## The banker (live-DB verified this session, `/Users/sam/dev/vmangos-deploy` → `characters`/
|
||||
//! ## The banker (live-DB verified against the local vmangos, `characters`/
|
||||
//! `mangos` DBs)
|
||||
//!
|
||||
//! Soleil Stonemantle, creature entry 5099, spawn guid 12629, map 0 (Ironforge, The Vault), pos
|
||||
@@ -36,7 +36,8 @@
|
||||
//! WOW_DATA=WoW/Data WOW_USER=probe1 WOW_PASS=pprobe1 WOW_CHAR=Probeone \
|
||||
//! WOW_PROBE_BANK=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — this worktree is `pool-1` → `probe1`/`pprobe1`/`Probeone`).
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill).
|
||||
//! Non-combat; GM mode is left exactly as found. An outer `timeout` + grep on `PROBE_BANK:` is the
|
||||
//! whole harness; the probe self-exits ([`super::probes::ProbeExitPlugin`]'s pattern) once DONE.
|
||||
|
||||
|
||||
@@ -93,8 +93,7 @@
|
||||
//! assignment reads off it (0649, 0679).
|
||||
//!
|
||||
//! Non-combat — the probe never attacks and never stands anywhere contested; with one player in
|
||||
//! the instance there is nobody to fight. Pair with the SLOT-KEYED probe identity
|
||||
//! (`docs/METHOD.md`, "The local vmangos server").
|
||||
//! the instance there is nobody to fight. Pair with the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill).
|
||||
|
||||
use bevy::ecs::system::NonSendMut;
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -30,8 +30,7 @@
|
||||
//! answers `none`. A body left queued is not harmful, only surprising: every later login on that
|
||||
//! account carries an extra `SMSG_BATTLEFIELD_STATUS` in its burst.
|
||||
//!
|
||||
//! Non-combat. Pair with the SLOT-KEYED probe identity (`WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>`; `docs/METHOD.md`, "The local vmangos server"), and `WOW_NOSOUND=1` when
|
||||
//! Non-combat. Pair with the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill), and `WOW_NOSOUND=1` when
|
||||
//! it runs unattended. One `timeout`'d run plus a grep for `PROBE bgqueue:` is the whole harness.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
//! visible against a real server sending real bytes, which is what this probe puts in front of
|
||||
//! them.
|
||||
//!
|
||||
//! ## The innkeeper (live-DB verified this session, `/Users/sam/dev/vmangos-deploy` → `mangos` DB)
|
||||
//! ## The innkeeper (live-DB verified against the local vmangos, `mangos` DB)
|
||||
//!
|
||||
//! Innkeeper Keldamyr — `creature_template.entry = 6736`, spawn `creature.guid = 46343`, **map 1**
|
||||
//! (Teldrassil, Dolanaar), position `(9802.21, 982.608, 1313.98)`,
|
||||
@@ -77,8 +77,8 @@
|
||||
//! WOW_DATA=WoW/Data WOW_USER=probe1 WOW_PASS=pprobe1 WOW_CHAR=Probeone \
|
||||
//! WOW_PROBE_BINDER=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — this worktree is `pool-1` → `probe1`/`pprobe1`/`Probeone`;
|
||||
//! docs/METHOD.md "The local vmangos server"). Non-combat, and GM mode is left exactly as found. An
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). Non-combat, and GM mode is left exactly as found. An
|
||||
//! outer `timeout` + grep on `PROBE_BINDER:` is the whole harness; the probe self-exits once DONE.
|
||||
//!
|
||||
//! Every step SKIPs with a note rather than FAILing for an environmental problem (the NPC never
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The world-book live probe (`WOW_PROBE_BOOK=1`) — B240's instrument: what does having the item-
|
||||
//! text reader open actually cost per frame, on the real object the report names?
|
||||
//!
|
||||
//! Goudy, 2026-08-09 (`#bugs` `1535810645956632686`): *"page text with html text absolutely
|
||||
//! Reported on Discord, 2026-08-09: *"page text with html text absolutely
|
||||
//! annihilates performance"* — ~50% fps drop (62 → 36 fps, 16.0 → 28.0 ms) while the *Alliance
|
||||
//! Military Ranks* plaque's reader is up, recovering the moment it closes. That is a **frame-cost
|
||||
//! A/B**, and eyeballing an fps counter is exactly the way not to settle one (docs/METHOD.md's
|
||||
@@ -21,8 +21,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe0 WOW_PASS=pprobe0 WOW_CHAR=Probezero \
|
||||
//! WOW_PROBE_BOOK=1 cargo run -q -p benilla --release
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `probeN`/`pprobeN`/`Probe<N-spelled>`, docs/METHOD.md
|
||||
//! "The local vmangos server"). An outer `timeout` + a grep on `PROBE_BOOK:` is the whole harness;
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). An outer `timeout` + a grep on `PROBE_BOOK:` is the whole harness;
|
||||
//! the probe self-exits ([`super::probes::ProbeExitPlugin`]'s pattern) once DONE.
|
||||
|
||||
use bevy::prelude::*;
|
||||
@@ -36,8 +36,8 @@ use crate::player::Player;
|
||||
use crate::ui_item_text::ItemTextOpen;
|
||||
use crate::ui_script::{UiCostWanted, UiFrameCost};
|
||||
|
||||
/// Goudy's own reported standing position beside the plaque (his debug panel, screenshot
|
||||
/// `1535810480424222760-1`).
|
||||
/// The reporter's own standing position beside the plaque (their debug panel, in the report's
|
||||
/// screenshot).
|
||||
const PLAQUE_AT: [f32; 3] = [-8760.2, 402.3, 103.9];
|
||||
/// `GAMEOBJECT_TYPE_TEXT` — the strategy type a book/plaque carries (decision 1105).
|
||||
const GO_TYPE_TEXT: i32 = 9;
|
||||
|
||||
@@ -14,8 +14,7 @@
|
||||
//! measured off the timestamps, not eyeballed. Non-combat, safe unattended
|
||||
//! (docs/METHOD.md's rule bans unattended *combat* probes); a hearth that completes because the
|
||||
//! cancel FAILED just ports the probe character home — visible in the log as the missing cancel line. Pair with
|
||||
//! the slot-keyed probe identity (`WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<N-spelled>`,
|
||||
//! docs/METHOD.md) + `WOW_CAST_TRACE=1 WOW_PROBE_EXIT_AT=<s>`.
|
||||
//! the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill) + `WOW_CAST_TRACE=1 WOW_PROBE_EXIT_AT=<s>`.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
//! the `SMSG_CHAR_CREATE` result byte, and — unless `WOW_PROBE_CHARCREATE_KEEP=1` — deletes the
|
||||
//! character it just made ([`CharRequest::Delete`]) so the account isn't littered, logging that
|
||||
//! result too. Run it **without** `WOW_CHAR` (which would auto-enter the world and leave select),
|
||||
//! against the slot-keyed probe account (`WOW_USER=probeN WOW_PASS=pprobeN`, N = your pool slot — docs/METHOD.md "The local vmangos server"); creating/deleting characters is a
|
||||
//! against the checkout's own probe account (`.probe-identity`, or `WOW_USER`/`WOW_PASS` — the
|
||||
//! `probe` skill); creating/deleting characters is a
|
||||
//! non-combat operation, safe to run headlessly.
|
||||
|
||||
use benilla_protocol::{messages, CharAction, CharCreateReq};
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//! fork arm whose whole evidence is "no other arm accepts a charter". Those are only visible
|
||||
//! against real bytes.
|
||||
//!
|
||||
//! ## The registrar (live-DB verified this session, `/Users/sam/dev/vmangos-deploy` → `mangos` DB)
|
||||
//! ## The registrar (live-DB verified against the local vmangos, `mangos` DB)
|
||||
//!
|
||||
//! Aldwin Laughlin, the Stormwind guild registrar — `creature_template.entry = 4974`, spawn
|
||||
//! `creature.guid = 79681`, **map 0**, position `(-8885.25, 614.395, 95.2576)`,
|
||||
@@ -96,8 +96,7 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe0 WOW_PASS=pprobe0 WOW_CHAR=Probezero \
|
||||
//! WOW_PROBE_CHARTER=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — this worktree is `pool-0` → `probe0`/`pprobe0`/`Probezero`;
|
||||
//! docs/METHOD.md "The local vmangos server". **Never the default `one` account** — a login on it kicks
|
||||
//! (the checkout's probe identity, the `probe` skill. **Never a player's account** — a login on it kicks
|
||||
//! the director's live session.) `WOW_NOSOUND=1` because an unattended probe must not play zone
|
||||
//! music into the director's room; `caffeinate -dis` is **not** needed for a run this short — the
|
||||
//! whole sequence is a handful of round trips and finishes in well under a minute.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! The chest live probe (`WOW_PROBE_CHEST=1`) — **B84's instrument**: does the player kneel at an
|
||||
//! open chest?
|
||||
//!
|
||||
//! Goudy, 2026-07-26 (`#bugs` `1530708187173359646`): *"No looting/opening animation when using a
|
||||
//! Reported on Discord, 2026-07-26: *"No looting/opening animation when using a
|
||||
//! chest"*, with a benilla/reference pair at the same Mirror Lake Orchard chest — the reference
|
||||
//! gnome kneeling over the open loot window, ours standing bolt upright. Decision 0515 had shipped
|
||||
//! the kneel keyed to the `CMSG_LOOT` send alone and recorded a chest's silence as *correct*; 1471
|
||||
@@ -38,8 +38,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe0 WOW_PASS=pprobe0 WOW_CHAR=Probezero \
|
||||
//! WOW_PROBE_CHEST=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `probeN`/`pprobeN`/`Probe<N-spelled>`, `docs/METHOD.md`
|
||||
//! "The local vmangos server"). `WOW_PROBE_CHEST=<x>,<y>,<z>[,<map>]` aims it at a different
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). `WOW_PROBE_CHEST=<x>,<y>,<z>[,<map>]` aims it at a different
|
||||
//! object; the default is a live `Battered Chest` spawn (`gameobject.guid` 26756, template 2843) in
|
||||
//! the Mirror Lake stretch of Elwynn, the same corner of the world the report came from. A chest is
|
||||
//! a respawning spawn point: if someone emptied that one it will be missing for its respawn timer,
|
||||
|
||||
@@ -34,8 +34,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe0 WOW_PASS=pprobe0 WOW_CHAR=Probezero \
|
||||
//! WOW_PROBE_CLAM=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `probeN`/`pprobeN`/`Probe<N-spelled>`, `docs/METHOD.md`
|
||||
//! "The local vmangos server"). `WOW_PROBE_CLAM=<entry>` aims it at a different openable template;
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). `WOW_PROBE_CLAM=<entry>` aims it at a different openable template;
|
||||
//! the default is a Small Barnacled Clam (7973 — `Flags` LOOTABLE with `LockID = 0`, so it is
|
||||
//! openable the moment it exists: no key, no lockpicking). The probe `.additem`s its own copy and
|
||||
//! subtracts it again on the way out, so it leaves the character as it found it.
|
||||
|
||||
@@ -4,9 +4,8 @@
|
||||
//! report the seam: aboard → map flip (TRANSFER_PENDING / NEW_WORLD riding branch, logged by
|
||||
//! the net layer) → still riding → arrived docked on the far continent. Every phase edge prints
|
||||
//! a `PROBE crossing:` line, so an outer `timeout`d run + grep is the whole harness. Non-combat.
|
||||
//! Pair with the SLOT-KEYED probe identity (`WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>` for a `pool-N` worktree — docs/METHOD.md "The local vmangos server";
|
||||
//! a shared account gets kicked by parallel sessions mid-ride).
|
||||
//! Pair with the checkout's own probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR
|
||||
//! — the `probe` skill; a shared account gets kicked by parallel sessions mid-ride).
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -93,8 +93,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe3 WOW_PASS=pprobe3 WOW_CHAR=Probethree \
|
||||
//! WOW_PROBE_GMTICKET=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — this worktree is `pool-3` → `probe3`/`pprobe3`/`Probethree`;
|
||||
//! docs/METHOD.md "The local vmangos server"). Non-combat; the probe never drives the body and sends no
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). Non-combat; the probe never drives the body and sends no
|
||||
//! GM command, so GM mode and position are left exactly as found — but note that the body is not
|
||||
//! necessarily *still* when a run starts (see [`REST_EPS`]). An outer `timeout` + grep on
|
||||
//! `PROBE_GMTICKET:` is the whole harness; the probe self-exits once DONE. Nothing here is a
|
||||
@@ -163,11 +163,11 @@ const REST_TIMEOUT_SECS: f64 = 20.0;
|
||||
/// the packet, before the db-expect line stops being trustworthy (yd, WoW space).
|
||||
const STAMP_DRIFT_EPS: f32 = 0.05;
|
||||
|
||||
/// The operator's half of step 4, printed verbatim so it can be pasted.
|
||||
const DB_QUERY: &str = "cd /Users/sam/dev/vmangos-deploy && docker compose exec -T database \
|
||||
mariadb -umangos -pmangos characters -e \"SELECT ticket_id, name, \
|
||||
ticket_type, map, position_x, position_y, position_z, closed_by, message \
|
||||
FROM gm_tickets ORDER BY ticket_id DESC LIMIT 1;\"";
|
||||
/// The operator's half of step 4: the row to read back off the server's `characters` database,
|
||||
/// printed verbatim so it can be pasted into whatever reaches that database.
|
||||
const DB_QUERY: &str = "SELECT ticket_id, name, ticket_type, map, position_x, position_y, \
|
||||
position_z, closed_by, message FROM gm_tickets ORDER BY ticket_id DESC \
|
||||
LIMIT 1;";
|
||||
|
||||
pub(crate) struct ProbeGmTicketPlugin;
|
||||
|
||||
@@ -563,7 +563,8 @@ fn gm_ticket_probe(
|
||||
warn!(
|
||||
"PROBE_GMTICKET: SKIP (4 db) — operator step, by design: the client never \
|
||||
reads map/position back, so only the server's own row proves they landed. \
|
||||
After the run: {DB_QUERY} — and compare against the db-expect line(s)."
|
||||
After the run, against the server's `characters` database: {DB_QUERY} — \
|
||||
and compare against the db-expect line(s)."
|
||||
);
|
||||
let text = unique_text(" edited");
|
||||
if !run_or_skip(
|
||||
|
||||
@@ -43,8 +43,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_USER=probe4 WOW_PASS=pprobe4 WOW_CHAR=Probefour \
|
||||
//! WOW_PROBE_GOQUEST=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `probeN`/`pprobeN`/`Probe<N-spelled>`, `docs/METHOD.md`
|
||||
//! "The local vmangos server"). `WOW_PROBE_GOQUEST=<x>,<y>,<z>[,<map>]` aims it elsewhere; the
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). `WOW_PROBE_GOQUEST=<x>,<y>,<z>[,<map>]` aims it elsewhere; the
|
||||
//! default is the **Goldshire wanted poster** — `gameobject.guid` 26843, template 68
|
||||
//! `Wanted Poster`, `type` 2 `GAMEOBJECT_TYPE_QUESTGIVER`, `flags` 4 `INTERACT_COND`, offering
|
||||
//! quest 176 `Wanted: "Hogger"` (`MinLevel` 5, `QuestLevel` 11). It is a single **unpooled** spawn
|
||||
|
||||
@@ -10,8 +10,7 @@
|
||||
//! — distance, view radius, and which of the two draws it takes — so the run says *what would be
|
||||
//! on screen*, not merely that a packet arrived.
|
||||
//!
|
||||
//! Non-combat. Pair with the SLOT-KEYED probe identity (`WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>`; `docs/METHOD.md`, "The local vmangos server"), and `WOW_NOSOUND=1` when
|
||||
//! Non-combat. Pair with the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill), and `WOW_NOSOUND=1` when
|
||||
//! it runs unattended. One `timeout`'d run plus a grep for `PROBE guardpoi:` is the whole harness.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
//! `PROBE_MAIL: DONE pass=<n> fail=<m>` summary. Modeled closely on [`super::probe_taxi`] (same
|
||||
//! phase-machine shape, same trace style, same self-terminating exit).
|
||||
//!
|
||||
//! ## The GM `.send` syntax (verified `/Users/sam/wre/vmangos-src`, `Chat/Chat.cpp` +
|
||||
//! ## The GM `.send` syntax (verified against the vmangos source, `Chat/Chat.cpp` +
|
||||
//! `Commands/MiscCommands.cpp`)
|
||||
//!
|
||||
//! `sendCommandTable[]` (Chat.cpp ~l.931): `.send mail <name> "subject" "text"` is
|
||||
@@ -39,9 +39,8 @@
|
||||
//! WOW_DATA=<vanilla Data dir> WOW_USER=probe2 WOW_PASS=pprobe2 WOW_CHAR=Probetwo \
|
||||
//! WOW_PROBE_MAIL=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>`, docs/METHOD.md "The local vmangos server"; this worktree is `pool-2` →
|
||||
//! `probe2`/`pprobe2`/`Probetwo`). `WOW_CHAR` doubles as the probe's own mail-send target — read
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). `WOW_CHAR` doubles as the probe's own mail-send target — read
|
||||
//! once at world-enter, never hardcoded. An outer `timeout` + grep on `PROBE_MAIL:` is the whole
|
||||
//! harness; the probe self-exits (the [`super::probes::ProbeExitPlugin`] pattern) once DONE.
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! once in-world, periodically fire the attack-nearest core ([`AttackNearestRequest`] — the
|
||||
//! action-bar attack's own no-selection path), so the probe character fights whatever is closest
|
||||
//! while the `benilla_assets::trace` sink (`WOW_MOVE_TRACE=<path>`) records the swing→impact→spawn combat-text
|
||||
//! timeline for pair-birth analysis. Pair with the slot-keyed probe identity (docs/METHOD.md) + an outer
|
||||
//! timeline for pair-birth analysis. Pair with the checkout's probe identity (the `probe` skill) + an outer
|
||||
//! `timeout`.
|
||||
//!
|
||||
//! **Do NOT run this unattended** (director's rule, 2026-07-14, `docs/METHOD.md` "The local vmangos
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
//! WOW_USER=probe5 WOW_PASS=pprobe5 WOW_CHAR=Probefive WOW_UNATTENDED=1 WOW_NOSOUND=1 \
|
||||
//! WOW_PROBE_MODEL_CAMERA=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — docs/METHOD.md "The local vmangos server"). `WOW_TILE_TRACE=1`
|
||||
//! (the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill)). `WOW_TILE_TRACE=1`
|
||||
//! alongside it prints the leg, the record, the eye and the matrix per pane per frame.
|
||||
|
||||
use bevy::camera::visibility::RenderLayers;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
//! questions answerable at all: whether the opponent turns hostile, whether the arbiter flag
|
||||
//! plants. Non-combat (the unattended-combat ban, `docs/METHOD.md` "The local vmangos server", stands
|
||||
//! untouched: this probe answers a challenge but never swings, and a duel nobody strikes in
|
||||
//! simply times out). Pair with the slot-keyed probe identity (`WOW_USER=probeN …`, docs/METHOD.md).
|
||||
//! simply times out). Pair with the checkout's probe identity (the `probe` skill).
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! The probe **rig** (`WOW_RIG="<spec>"`, decision 0651) — one command that puts this slot's probe
|
||||
//! The probe **rig** (`WOW_RIG="<spec>"`, decision 0651) — one command that puts this checkout's probe
|
||||
//! account into a chosen **body** and hands the session a world that is ready to test.
|
||||
//!
|
||||
//! ## Why it exists
|
||||
@@ -17,7 +17,7 @@
|
||||
//! ```text
|
||||
//! WOW_RIG="tauren druid 60 gear:heal-preraid-bis spec:heal-preraid-bis at:ThunderBluff"
|
||||
//! WOW_RIG="gnome mage 39 gear:dps-39-twink gm:off"
|
||||
//! WOW_RIG="60 gear:dps-preraid-bis" # the slot's own Probe<N>, no new character
|
||||
//! WOW_RIG="60 gear:dps-preraid-bis" # the account's own probe body, no new character
|
||||
//! WOW_RIG="nightelf druid" # just a body of that shape, level 1
|
||||
//! ```
|
||||
//!
|
||||
@@ -26,14 +26,15 @@
|
||||
//! **Race + class name a character, so the rig owns the pick.** vmangos has no class-change command
|
||||
//! (`.character race` exists; there is no `.character class`), so a different class *is* a different
|
||||
//! character — which makes creating one the honest primitive rather than a fallback. The name is
|
||||
//! derived, never invented: `<Race3><Class3><slot-word>[f]`, e.g. Tauren Druid on `pool-1` →
|
||||
//! `Taudruone`. Deterministic means the *next* session reuses the same body instead of littering a
|
||||
//! second one, and the pattern is what makes eviction safe (below). Omit race+class and the rig
|
||||
//! configures whatever `WOW_CHAR`/the slot default already logs in as.
|
||||
//! derived, never invented: `<Race3><Class3><word>[f]`, the word being this checkout's
|
||||
//! (`run_mode::rig_suffix`), e.g. a Tauren Druid for `Probeone` → `Taudruone`. Deterministic means
|
||||
//! the *next* session reuses the same body instead of littering a second one, and the pattern is
|
||||
//! what makes eviction safe (below). Omit race+class and the rig configures whatever `WOW_CHAR`
|
||||
//! already logs in as.
|
||||
//!
|
||||
//! **The roster is a cache with an eviction policy.** `CharactersPerRealm` is 10, and there are 40
|
||||
//! valid race/class pairs, so a busy account fills. The rig does not hardcode the limit: it tries
|
||||
//! the create, and only on `CHAR_CREATE_SERVER_LIMIT` evicts **the rig-named character on this slot
|
||||
//! the create, and only on `CHAR_CREATE_SERVER_LIMIT` evicts **the rig-named character of this checkout
|
||||
//! that is cheapest to rebuild** — lowest level first, ties broken by oldest — then retries once.
|
||||
//! Nothing outside the rig's own naming pattern is ever deleted, so `Probe<N>` and anything a human
|
||||
//! made are untouchable.
|
||||
@@ -44,7 +45,7 @@
|
||||
//! It always revives a dead or ghost body, whether or not you asked — that is the 121× command, and
|
||||
//! there is no session that wants to keep testing on a corpse.
|
||||
//!
|
||||
//! ## The GM verbs behind it (all verified against `/Users/sam/wre/vmangos-src`)
|
||||
//! ## The GM verbs behind it (all verified against the vmangos source, github.com/vmangos/core)
|
||||
//!
|
||||
//! | rig token | command | needs | note |
|
||||
//! |---|---|---|---|
|
||||
@@ -225,7 +226,7 @@ impl RigSpec {
|
||||
fn describe(&self) -> String {
|
||||
let body = self
|
||||
.body
|
||||
.map_or("this slot's probe character".into(), |(r, c, g)| {
|
||||
.map_or("the account's probe character".into(), |(r, c, g)| {
|
||||
format!(
|
||||
"{} {} {}",
|
||||
if g == 1 { "female" } else { "male" },
|
||||
@@ -360,13 +361,13 @@ fn drive_rig(
|
||||
messages::CHAR_CREATE_SUCCESS => rig.phase = RigPhase::AwaitRoster,
|
||||
messages::CHAR_CREATE_SERVER_LIMIT if !rig.evicted => {
|
||||
rig.evicted = true;
|
||||
match crate::run_mode::slot_word().and_then(|slot| {
|
||||
evictable(&rig.roster, rig_char_name(&rig.spec).as_deref(), slot)
|
||||
match crate::run_mode::rig_suffix().and_then(|word| {
|
||||
evictable(&rig.roster, rig_char_name(&rig.spec).as_deref(), &word)
|
||||
}) {
|
||||
Some((guid, name)) => {
|
||||
warn!(
|
||||
"rig: the account is full — evicting the cheapest rig character to rebuild, {name} \
|
||||
(guid {guid}). Only rig-named characters on this slot are ever \
|
||||
(guid {guid}). Only this checkout's rig-named characters are ever \
|
||||
deleted; Probe<N> and hand-made characters are never touched."
|
||||
);
|
||||
let _ = pick.0.send(CharRequest::Delete(guid));
|
||||
@@ -518,7 +519,7 @@ fn build_steps(spec: &RigSpec, dead: bool) -> Vec<String> {
|
||||
/// `game_tele` name. The facing is load-bearing exactly when a motion probe walks from the
|
||||
/// pin: `W` follows the BODY's facing, and each probe character keeps whatever facing it last
|
||||
/// had — an unpinned facing sent probe0 and probe4 down different routes from the same point,
|
||||
/// which invalidated a cross-slot A/B before 1462's sitting caught it.
|
||||
/// which invalidated a cross-checkout A/B before 1462's sitting caught it.
|
||||
fn parse_point(at: &str) -> Option<(i32, f32, f32, f32, Option<f32>)> {
|
||||
let mut parts = at.split(',').map(str::trim);
|
||||
let map = parts.next()?.parse().ok()?;
|
||||
@@ -536,9 +537,9 @@ fn parse_point(at: &str) -> Option<(i32, f32, f32, f32, Option<f32>)> {
|
||||
|
||||
fn rig_char_name(spec: &RigSpec) -> Option<String> {
|
||||
let (race, class, gender) = spec.body?;
|
||||
let slot = crate::run_mode::slot_word()?;
|
||||
let word = crate::run_mode::rig_suffix()?;
|
||||
let name = format!(
|
||||
"{}{}{slot}{}",
|
||||
"{}{}{word}{}",
|
||||
race_code(race)?,
|
||||
class_code(class)?,
|
||||
if gender == 1 { "f" } else { "" }
|
||||
@@ -550,45 +551,45 @@ fn rig_char_name(spec: &RigSpec) -> Option<String> {
|
||||
Some(chars.next()?.to_ascii_uppercase().to_string() + &chars.as_str().to_ascii_lowercase())
|
||||
}
|
||||
|
||||
/// The rig-named character on this slot that costs least to lose: **lowest level first**, ties
|
||||
/// The rig-named character of this checkout that costs least to lose: **lowest level first**, ties
|
||||
/// broken by oldest (the enum is ordered by `create_time` — vmangos `HandleCharEnumOpcode` — so an
|
||||
/// earlier index *is* older). Level is the proxy for invested setup: a level-1 body is 15 seconds to
|
||||
/// rebuild, a geared 60 is a minute and a talent tree. Evicting by age alone would throw away the
|
||||
/// most valuable body on the account first, which the live fill test made obvious.
|
||||
///
|
||||
/// Anything that is not a rig name for `slot` — `Probe<N>`, a hand-made character, another slot's
|
||||
/// leftovers — is invisible here, and that is what makes automatic deletion safe.
|
||||
/// Anything that is not a rig name for `word` — the probe body itself, a hand-made character,
|
||||
/// another checkout's leftovers — is invisible here, and that is what makes automatic deletion safe.
|
||||
///
|
||||
/// `slot` is a **parameter, not a `slot_word()` call inside**: reading the ambient slot here made
|
||||
/// the eviction test pass only in the worktree it was written in (`pool-1`, whose rig names end
|
||||
/// `…one`) and fail in every other slot — and in the primary checkout, where `slot_word()` is
|
||||
/// `None` and nothing matches at all. A unit test's answer must not depend on which directory the
|
||||
/// build happened in.
|
||||
/// `word` is a **parameter, not a `rig_suffix()` call inside**: reading the ambient word here made
|
||||
/// the eviction test pass only in the checkout it was written in (whose rig names end `…one`) and
|
||||
/// fail in every other — and in a checkout with no declaration, where `rig_suffix()` is `None` and
|
||||
/// nothing matches at all. A unit test's answer must not depend on which directory the build
|
||||
/// happened in.
|
||||
fn evictable(
|
||||
roster: &[benilla_protocol::Character],
|
||||
want: Option<&str>,
|
||||
slot: &str,
|
||||
word: &str,
|
||||
) -> Option<(u64, String)> {
|
||||
roster
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_, c)| {
|
||||
!want.is_some_and(|w| c.name.eq_ignore_ascii_case(w)) && is_rig_name(&c.name, slot)
|
||||
!want.is_some_and(|w| c.name.eq_ignore_ascii_case(w)) && is_rig_name(&c.name, word)
|
||||
})
|
||||
.min_by_key(|(age, c)| (c.level, *age))
|
||||
.map(|(_, c)| (c.guid, c.name.clone()))
|
||||
}
|
||||
|
||||
/// Whether a roster name was minted by [`rig_char_name`] for this slot: a known race code, a known
|
||||
/// class code, this slot's word, and nothing but an optional `f` after it.
|
||||
fn is_rig_name(name: &str, slot: &str) -> bool {
|
||||
/// Whether a roster name was minted by [`rig_char_name`] for this checkout: a known race code, a
|
||||
/// known class code, this checkout's word, and nothing but an optional `f` after it.
|
||||
fn is_rig_name(name: &str, word: &str) -> bool {
|
||||
let lower = name.to_ascii_lowercase();
|
||||
let Some(rest) = lower.get(3..).and_then(|r| r.get(3..)) else {
|
||||
return false;
|
||||
};
|
||||
let has_codes = RACE_CODES.iter().any(|(_, c)| lower.starts_with(c))
|
||||
&& CLASS_CODES.iter().any(|(_, c)| lower[3..].starts_with(c));
|
||||
has_codes && (rest == slot || rest == format!("{slot}f"))
|
||||
has_codes && (rest == word || rest == format!("{word}f"))
|
||||
}
|
||||
|
||||
/// Race id → the 3-letter name code. `Nel`/`Und` rather than the DBC's own prefixes: unambiguous,
|
||||
@@ -770,7 +771,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn the_derived_name_is_deterministic_and_fits_the_server_limit() {
|
||||
// `<Race3><Class3><slot-word>[f]`, normalized the way vmangos normalizes a player name.
|
||||
// `<Race3><Class3><word>[f]`, normalized the way vmangos normalizes a player name.
|
||||
let name = |spec: &str, slot: &str| {
|
||||
let s = RigSpec::parse(spec).unwrap();
|
||||
let (race, class, gender) = s.body.unwrap();
|
||||
@@ -835,9 +836,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn eviction_spends_the_body_that_is_cheapest_to_rebuild() {
|
||||
// The slot is passed in, never read from the ambient build directory — these names are
|
||||
// pool-1's, and taking the slot from `slot_word()` made this test pass in pool-1 and fail
|
||||
// in every other worktree (and in the primary checkout, where it is `None`).
|
||||
// The word is passed in, never read from the ambient checkout — these names are one
|
||||
// checkout's, and taking the word from `rig_suffix()` made this test pass there and fail
|
||||
// in every other checkout (and in one with no declaration, where it is `None`).
|
||||
let slot = "one";
|
||||
// Roster order IS create order (vmangos enumerates by `create_time`).
|
||||
let roster = [
|
||||
@@ -859,8 +860,8 @@ mod tests {
|
||||
);
|
||||
// Nothing rig-named on this slot ⇒ nothing to evict; the caller errors rather than guessing.
|
||||
assert_eq!(evictable(&roster[..2], None, slot), None);
|
||||
// And another slot's leftovers are invisible: the same roster, read as pool-3, evicts
|
||||
// nothing. This is the assertion that would have caught the ambient-slot read.
|
||||
// And another checkout's leftovers are invisible: the same roster, read with another word,
|
||||
// evicts nothing. This is the assertion that would have caught the ambient read.
|
||||
assert_eq!(evictable(&roster, None, "three"), None);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
//! | *any pure questgiver with an offer* | `0x02`, GOSSIP clear | Questgiver | quest frame |
|
||||
//! | Alma Jainrose (812) | `0x10` TRAINER only | Trainer | trainer frame |
|
||||
//!
|
||||
//! The flag column is live-DB verified this session (`vmangos-deploy` → `mangos`,
|
||||
//! The flag column is live-DB verified against the local vmangos (`mangos`,
|
||||
//! `SELECT entry, name, npc_flags FROM creature_template`), and reported again from the wire on
|
||||
//! every run — a template edit shows up as a note beside the reading rather than as a mystery FAIL.
|
||||
//!
|
||||
@@ -48,7 +48,7 @@
|
||||
//! WOW_DATA=WoW/Data WOW_USER=probe4 WOW_PASS=pprobe4 WOW_CHAR=Probefour \
|
||||
//! WOW_UNATTENDED=1 WOW_PROBE_SERVICE=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — docs/METHOD.md "The local vmangos server"). Non-combat, GM mode
|
||||
//! (the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill)). Non-combat, GM mode
|
||||
//! left as found, nothing bought and nothing turned in: every leg opens a window and closes it
|
||||
//! client-side, which is what the reference's own close does (`ui_gossip`: there is no
|
||||
//! `CMSG_GOSSIP_CLOSE` in 1.12).
|
||||
@@ -215,7 +215,7 @@ struct ServiceProbe {
|
||||
/// NPC look identical in a SKIP line otherwise — and on a loaded machine the first is far more
|
||||
/// likely: at load 61 this probe logged `frame hitch: ~1010 ms` on repeat (the ~1 fps regime
|
||||
/// decisions 0713/0777/1355 name) and its 20-second scan window bought about twenty samples.
|
||||
/// `leg.sh` answers the same problem with a load guard (1157); a probe that does one thing and
|
||||
/// A leg runner answers the same problem with a load guard (1157); a probe that does one thing and
|
||||
/// exits is better served by reporting what it actually got.
|
||||
polls: u32,
|
||||
/// Latched once [`Phase::Done`] has fired its exit (never re-fire on a later frame).
|
||||
@@ -426,7 +426,7 @@ fn service_probe(
|
||||
// frames a second this leg never really looked (0713/0777/1355).
|
||||
true =>
|
||||
" — THE OBSERVER WAS STARVED, so this SKIP says nothing about \
|
||||
the NPC. Re-run on an idle machine (`uptime`; leg.sh's guard \
|
||||
the NPC. Re-run on an idle machine (`uptime`; a leg runner's guard \
|
||||
is load < 3).",
|
||||
false =>
|
||||
" — the machine was keeping up, so the `.go` was refused, the \
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
//! WOW_NOSOUND=1 WOW_UNATTENDED=1 WOW_USER=probe3 WOW_PASS=pprobe3 WOW_CHAR=Probethree \
|
||||
//! WOW_PROBE_STONE=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — `pool-N` → `probeN`/`pprobeN`/`Probe<N-spelled>`, `docs/METHOD.md`
|
||||
//! "The local vmangos server"). `WOW_PROBE_STONE=<x>,<y>,<z>[,<map>]` aims it elsewhere.
|
||||
//! (the checkout's probe identity — `.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR; the `probe`
|
||||
//! skill). `WOW_PROBE_STONE=<x>,<y>,<z>[,<map>]` aims it elsewhere.
|
||||
//!
|
||||
//! **It leaves the probe body at the stone's minimum level**, deliberately and without putting it
|
||||
//! back: the level is the probe's own instrument (leg 2 needs one side of the band and leg 3 the
|
||||
|
||||
@@ -7,8 +7,7 @@
|
||||
//! arrival distance to the destination node's DBC position, and measured flight duration vs the
|
||||
//! DBC prediction `Σ path-segment length ÷ 32 yd/s` (`PLAYER_FLIGHT_SPEED`) — timing measured,
|
||||
//! never eyeballed (decision 0404). An outer `timeout`d run + grep is the whole harness.
|
||||
//! Non-combat. Pair with the SLOT-KEYED probe identity (`WOW_USER=probeN WOW_PASS=pprobeN
|
||||
//! WOW_CHAR=Probe<N-spelled>`; docs/METHOD.md "The local vmangos server"). Uses `.taxicheat on` so
|
||||
//! Non-combat. Pair with the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill). Uses `.taxicheat on` so
|
||||
//! the fresh probe character can fly to an unvisited node (and so the SHOWTAXINODES mask
|
||||
//! exercises the full-network branch); the 110-copper fare is DB-seeded (see the Wait phase —
|
||||
//! `.modify money` outranks the probe account).
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
//! WOW_USER=probe1 WOW_PASS=pprobe1 WOW_CHAR=Probeone WOW_UNATTENDED=1 WOW_NOSOUND=1 \
|
||||
//! WOW_PROBE_VENDOR_SWAP=1 cargo run -q -p benilla
|
||||
//! ```
|
||||
//! (the slot-keyed probe identity — docs/METHOD.md "The local vmangos server").
|
||||
//! (the checkout's probe identity (`.probe-identity`, or WOW_USER/WOW_PASS/WOW_CHAR — the `probe` skill)).
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
//! - **`terrain`** is the MCNK height under it, which is what the clamp finds when a building's own
|
||||
//! collider has not attached yet.
|
||||
//!
|
||||
//! Pair it with the slot-keyed probe identity and a `.go` to the reported spot:
|
||||
//! Pair it with the checkout's probe identity (the `probe` skill) and a `.go` to the reported spot:
|
||||
//!
|
||||
//! ```text
|
||||
//! WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<n> WOW_NOSOUND=1 \
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
//! - **`meshes`** counts render descendants (the whole subtree, not just direct children — a GO
|
||||
//! model hangs its submeshes under an anim host).
|
||||
//!
|
||||
//! Pair it with the slot-keyed probe identity and a `.go` to the reported spot:
|
||||
//! Pair it with the checkout's probe identity (the `probe` skill) and a `.go` to the reported spot:
|
||||
//!
|
||||
//! ```text
|
||||
//! WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<n> WOW_NOSOUND=1 \
|
||||
|
||||
@@ -272,7 +272,7 @@ fn drive_live_fps(
|
||||
// settle window on top — a run still worldless this far past it is stranded on a
|
||||
// glue screen (dead server, refused login the marker arms didn't catch) and every
|
||||
// second more is the 1371 sitting's dead wall-clock again. `FATAL` is the marker
|
||||
// leg.sh keys on.
|
||||
// a leg runner keys on.
|
||||
if time.elapsed_secs() > probe.at + BOOT_DEADLINE_SECS {
|
||||
error!(
|
||||
"live-fps: FATAL — still not in world {:.0}s past the probe delay; a measurement run with no world is dead. exiting",
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
//! Off a transport `deck=` reads `-` and the deck columns are the world ones, which is the control:
|
||||
//! this census must not change on the ground, where a streak *should* smear behind a moving host.
|
||||
//!
|
||||
//! Pair it with the slot-keyed probe identity and a `.go` onto the Thunder Bluff lift:
|
||||
//! Pair it with the checkout's probe identity (the `probe` skill) and a `.go` onto the Thunder Bluff lift:
|
||||
//!
|
||||
//! ```text
|
||||
//! WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<n> WOW_NOSOUND=1 \
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
//! never to be confused with one that built nothing, which is the distinction the census exists
|
||||
//! to keep.
|
||||
//!
|
||||
//! Pair it with the slot-keyed probe identity and a `.go` to the reported spot:
|
||||
//! Pair it with the checkout's probe identity (the `probe` skill) and a `.go` to the reported spot:
|
||||
//!
|
||||
//! ```text
|
||||
//! WOW_USER=probeN WOW_PASS=pprobeN WOW_CHAR=Probe<n> WOW_NOSOUND=1 \
|
||||
|
||||
@@ -581,7 +581,7 @@ fn apply_roster_policy(
|
||||
warn!("char select: WOW_CHAR={name} not on this account — showing roster");
|
||||
// A driverless run can never pick a different row itself — parked here it
|
||||
// burns its whole wall-clock. Same home as the login arm's verdict, so the
|
||||
// marker leg.sh greps for is written in exactly one place (1371, 1769).
|
||||
// marker a leg runner greps for is written in exactly one place (1371, 1769).
|
||||
if std::env::var_os("WOW_LOGIN_SMOKE").is_none()
|
||||
&& crate::run_mode::fatal_when_driverless(&format!(
|
||||
"WOW_CHAR={name} is not on this account"
|
||||
|
||||
@@ -49,7 +49,7 @@ pub(crate) fn install(build: BuildId) {
|
||||
// The standing test affordance — `WOW_CRASH_INJECT=<at_secs>`, one deliberate main-thread panic
|
||||
// mid-run — is `perf::crash_inject`, a dev root: this module ships to players and knows nothing
|
||||
// of the `dev` seam (run_mode's one-door rule, 1176), and an instrument's home is with the
|
||||
// instruments. Its module doc carries the crosscheck incident that moved it there.
|
||||
// instruments. Its module doc carries the cross-platform incident that moved it there.
|
||||
|
||||
/// Re-entrancy latch: a panic *inside* the hook (a poisoned lock, a failed write) must not
|
||||
/// recurse into it. Never cleared on purpose — after one report the process is on its way out,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! catalog ([`EmoteSounds`], promoted from `crate::sound`, decision 0070 slice 4) and hand off —
|
||||
//! after the **receive-side posture gate** ([`receive_eligible`]).
|
||||
//!
|
||||
//! **Verified against vmangos** (`/Users/sam/wre/vmangos-src/src/game/Handlers/ChatHandler.cpp`
|
||||
//! **Verified against vmangos** (vmangos `src/game/Handlers/ChatHandler.cpp`
|
||||
//! `HandleTextEmoteOpcode` + `src/game/Unit.cpp` `Unit::HandleEmote`/`HandleEmoteCommand`,
|
||||
//! 2026-07-03): a one-shot text emote (`/wave`, `Emotes.dbc EmoteType == 0`) sends **both**
|
||||
//! `SMSG_TEXT_EMOTE` (the chat line + voice — `EmoteKind::Text`) and `SMSG_EMOTE`
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
//! 1. **`$BENILLA_HOME`** — explicit override (tests point it at a tempdir; a shared-config setup
|
||||
//! points it wherever it likes).
|
||||
//! 2. **`<project folder>/benilla-config/`** — `#[cfg(feature = "dev")]` only, so dev runs across the
|
||||
//! worktree pool keep one predictable place. Gated for the same reason the install resolver's
|
||||
//! worktrees of the repo keep one predictable place. Gated for the same reason the install resolver's
|
||||
//! project-folder probe is: a shipped binary must not carry the build machine's source tree.
|
||||
//! (`.gitignore` carries `/benilla-config` for it.)
|
||||
//! 3. **`<exe dir>/benilla-config/`** — the release answer: your settings sit next to the program that
|
||||
@@ -80,11 +80,12 @@ pub(crate) fn home() -> Option<PathBuf> {
|
||||
/// this binary was actually built in.
|
||||
///
|
||||
/// This is the one place 1175's §4 needed a correction on contact with how we work. `benilla/` used
|
||||
/// to hang off `$WOW_DATA`, and every pool slot symlinks `WoW` to the same install — so there has
|
||||
/// always been exactly ONE settings folder no matter which of the eight slots built the binary.
|
||||
/// Resolving to `CARGO_MANIFEST_DIR` instead would give eight, and the director's keybinds, macros
|
||||
/// and camera pose would appear to reset whenever a session happened to claim a different slot.
|
||||
/// That is a silent, recurring surprise, and it is not what "one predictable place" meant.
|
||||
/// to hang off `$WOW_DATA`, and every worktree of the repo points `WoW` at the same install — so
|
||||
/// there has always been exactly ONE settings folder no matter which worktree built the binary.
|
||||
/// Resolving to `CARGO_MANIFEST_DIR` instead would give one per worktree, and the player's
|
||||
/// keybinds, macros and camera pose would appear to reset whenever a session happened to build
|
||||
/// in a different one. That is a silent, recurring surprise, and it is not what "one predictable
|
||||
/// place" meant.
|
||||
///
|
||||
/// A linked worktree's `.git` is a **file** reading `gitdir: <primary>/.git/worktrees/<slot>`, so
|
||||
/// the primary is derivable with no git binary and no build script: walk up to the common dir and
|
||||
@@ -556,7 +557,7 @@ mod tests {
|
||||
/// One test rather than a `#[cfg]`-ed pair, because the seam is `run_mode::dev_source_dir()`'s
|
||||
/// to know and nothing else's (1179): a player build has no source dir, so the state folder
|
||||
/// must sit beside the binary; a dev build has one, and must resolve to the PRIMARY checkout
|
||||
/// so the eight pool slots keep sharing a single settings folder — exactly as they did when it
|
||||
/// so every worktree keeps sharing a single settings folder — exactly as they did when it
|
||||
/// hung off the shared install. The dev half is asserted structurally (a `.git` *file* means a
|
||||
/// linked worktree, and then the answer must be somewhere else), so it says the same thing
|
||||
/// whether it runs in the primary or in a slot.
|
||||
@@ -586,7 +587,7 @@ mod tests {
|
||||
assert_ne!(
|
||||
h,
|
||||
here.join(STATE_DIR),
|
||||
"a linked worktree must not get its own settings folder — the pool shares one"
|
||||
"a linked worktree must not get its own settings folder — the worktrees share one"
|
||||
);
|
||||
assert!(
|
||||
h.parent().unwrap().join(".git").is_dir(),
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
//! Cinematics/TOS side of the reference screen is deliberately cut (the director's call).
|
||||
//!
|
||||
//! This module owns the **credential policy** — the 0193 §3 mirror for the IO thread's pre-logon
|
||||
//! park: the env fast path (any of `WOW_USER`/`WOW_PASS`/`WOW_CHAR` explicitly set auto-submits
|
||||
//! park: the env fast path (`WOW_USER` and `WOW_PASS` both set auto-submits
|
||||
//! with the old `one`/`pone` defaults, so every probe/smoke invocation keeps working), the
|
||||
//! pending-credentials resubmit (paced at the flat 3 s, app-side — the IO thread never sleeps),
|
||||
//! and the director's typed submit. A *refused* code (bad password) clears the intent and shows
|
||||
@@ -470,7 +470,7 @@ fn drive_policy(
|
||||
// nobody at the keyboard: a login failure no resubmit can change would leave it parked on a
|
||||
// dialog for its whole wall-clock, and every retry a runner grants it is spent the same way.
|
||||
// Those failures exit non-zero instead, on one greppable marker — "login: FATAL" — that
|
||||
// leg.sh keys on (decision 1371).
|
||||
// a leg runner keys on (decision 1371).
|
||||
//
|
||||
// **Nobody is here only if the run says so** (decision 1769). Whether the client may end the
|
||||
// run itself is [`crate::run_mode::fatal_when_driverless`]'s to answer; the two facts this
|
||||
@@ -479,20 +479,20 @@ fn drive_policy(
|
||||
let empty = GlueStrings::default();
|
||||
let strings = strings.as_deref().unwrap_or(&empty);
|
||||
|
||||
// The env fast path, once (decision 0539 §3): any of WOW_USER/WOW_PASS/WOW_CHAR explicitly
|
||||
// set → auto-submit env-with-defaults, so every probe/smoke/harness invocation keeps working.
|
||||
// The env fast path, once (decision 0539 §3): WOW_USER and WOW_PASS both set → auto-submit
|
||||
// them, so every probe/smoke/harness invocation keeps working. There is no default account.
|
||||
// The login smoke drives its own credentials instead.
|
||||
if !attempt.intent.env_read {
|
||||
attempt.intent.env_read = true;
|
||||
// Purely "are the credentials in the environment?" (decision 1769) — whether anybody is
|
||||
// here to *react* is a different fact with a different home, `run_mode::unattended`.
|
||||
if crate::run_mode::env_login() && std::env::var_os("WOW_LOGIN_SMOKE").is_none() {
|
||||
let user = std::env::var("WOW_USER").unwrap_or_else(|_| "one".into());
|
||||
let pass = std::env::var("WOW_PASS").unwrap_or_else(|_| "pone".into());
|
||||
let user = std::env::var("WOW_USER").unwrap_or_default();
|
||||
let pass = std::env::var("WOW_PASS").unwrap_or_default();
|
||||
// The account guard (decision 0649): a vmangos login KICKS whoever holds the account,
|
||||
// so an unattended run from a pool slot must not authenticate as the director's `one`
|
||||
// or a neighbouring slot's probe. Only the *automated* path is gated — a typed login
|
||||
// is the director's own and is never second-guessed.
|
||||
// so an unattended run authenticates as the account its checkout declares
|
||||
// (`.probe-identity`) and nothing else. Only the *automated* path is gated — a typed
|
||||
// login is the player's own and is never second-guessed.
|
||||
match crate::run_mode::account_guard(&user) {
|
||||
Ok(()) => {
|
||||
info!("login: env fast path — auto-submitting as {user}");
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
/// (`getrusage(RUSAGE_SELF)`).
|
||||
///
|
||||
/// The perf probes report wall-clock frame time, which on this machine is not a usable regression
|
||||
/// instrument: parallel session worktrees build on the same 14 cores, and two identical probe runs
|
||||
/// instrument: parallel builds share the same cores, and two identical probe runs
|
||||
/// of the same pin came back 49.6 ms and 28.6 ms apart purely on machine load. CPU-per-frame moves
|
||||
/// with the work we actually do, not with who else is compiling — and it is the metric the Mac
|
||||
/// report is written in ("250 % CPU at 59 fps" against 1.12.1's "100 % at 160"), so a probe that
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! players and knows nothing of the `dev` seam, so its injector cannot sit beside it under a
|
||||
//! `cfg`. And it was first armed from inside the stall sampler, which is macOS-only
|
||||
//! (`/usr/bin/sample`) — so on Linux and Windows the injector was dead code, `-D warnings` made
|
||||
//! that a red `scripts/crosscheck.sh` on both, and the macOS gates stayed green for six days. An
|
||||
//! that a red cross-platform compile on both, and the macOS gates stayed green for six days. An
|
||||
//! instrument's cfg is `dev`, never a platform's; `PerfPlugin` arms this on every platform.
|
||||
|
||||
use bevy::prelude::*;
|
||||
|
||||
@@ -69,7 +69,7 @@ const HUD_REFRESH_SECS: f32 = 0.25;
|
||||
/// records the open gap: every campaign anchor was measured on a binary that draws this overlay,
|
||||
/// at a cost booked as "est 0.4–1.2 ms CPU + unquantified GPU" — an estimate, never a measurement,
|
||||
/// because nothing could turn the fixture off without also changing the binary. One env var makes
|
||||
/// it an interleaved A/B on *one* binary instead (`scripts/leg.sh`), so the constant baked into
|
||||
/// it an interleaved A/B on *one* binary instead (a leg runner), so the constant baked into
|
||||
/// every anchor becomes a number. The meters keep sampling either way: only the drawing stops,
|
||||
/// which is the half being priced — and with the default flipped, the *unmeasured* leg is now the
|
||||
/// one nobody is running.
|
||||
|
||||
@@ -137,7 +137,7 @@ fn arm_injectors(app: &mut App) {
|
||||
}
|
||||
// The third standing injector — `crash::arm_injector`, a deliberate main-thread panic — is
|
||||
// armed by `PerfPlugin` itself, beside this plugin's registration, not from here: this
|
||||
// module is macOS-only and the crash injector is not (its doc has the crosscheck incident).
|
||||
// module is macOS-only and the crash injector is not (its doc has the cross-platform incident).
|
||||
}
|
||||
|
||||
fn watchdog(dir: Option<std::path::PathBuf>) {
|
||||
|
||||
@@ -1161,7 +1161,7 @@ fn feed_gx_aspect(
|
||||
/// 1607.** 1444 shipped it on; 1559 turned it off on the director's look-call (a full-rate doll
|
||||
/// reads as smoother); the 08-25 weak-GPU perf reports (B329) then measured what that costs — a
|
||||
/// body-pane booth's off-screen pass every frame, ~1.6 ms at 1600×900 and **7.6 ms at 4K**
|
||||
/// (`sess/perfregress` A/B), paid on exactly the weak GPUs that reported. The director retested
|
||||
/// (an A/B leg), paid on exactly the weak GPUs that reported. The director retested
|
||||
/// the 30 fps doll and it reads fine, so the cheaper default is back. Full-rate is one
|
||||
/// `/script SetCVar("boothHalfRate", 0)` away for anyone who wants the smoother cadence.
|
||||
///
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
//! establish — which is precisely the player build. The banner now registers beside the stamp
|
||||
//! itself, in `lib::run`, where it is always compiled.
|
||||
//!
|
||||
//! The other half of decision 0649 — the pre-connect **account guard**, which keeps a session
|
||||
//! running inside a worktree pool slot from logging in as somebody else's account — lives in
|
||||
//! The other half of decision 0649 — the pre-connect **account guard**, which keeps a scripted
|
||||
//! run from logging in as an account its checkout did not declare — lives in
|
||||
//! [`crate::run_mode`] now, not here: it is consulted by the login policy, and 1174's seam does not
|
||||
//! let gameplay call an instrument. Its reasoning went with it.
|
||||
|
||||
@@ -435,7 +435,7 @@ fn findings(
|
||||
ShieldReport::Arming | ShieldReport::Armed =>
|
||||
"This is the default. Re-run with WOW_GM=off for those readings — safe, \
|
||||
because the probe shield (decision 0677) keeps the body alive without it.",
|
||||
// Not a probe body — the director's own account, or a bystander. `WOW_GM` would be
|
||||
// Not a probe body — a player's account, or a plain test account. `WOW_GM` would be
|
||||
// inert here (the shield only ever commands `probe<N>`, 0677), and saying otherwise
|
||||
// sends the reader after a switch that does nothing. The state is also PERSISTED:
|
||||
// vmangos saves it in `characters.extra_flags` bit 0 and `GM.LoginState = 2`
|
||||
|
||||
@@ -159,9 +159,9 @@ impl ProbeShield {
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether `user` is a probe account — `probe` followed by digits, the slot-keyed identity every
|
||||
/// unattended session logs in with (docs/METHOD.md "The local vmangos server"). Nothing else is ours to
|
||||
/// modify: `one` is the director's, and a bystander test account is not a probe.
|
||||
/// Whether `user` is a probe account — `probe` followed by digits, the identity every unattended
|
||||
/// run logs in with (the `probe` skill). Nothing else is ours to
|
||||
/// modify: a player's account is theirs, and a plain test account is not a probe.
|
||||
fn is_probe_account(user: &str) -> bool {
|
||||
// Byte-wise (a &str slice at 5 would panic mid-char on non-ASCII input; bytes can't).
|
||||
let Some((prefix, digits)) = user.as_bytes().split_at_checked(5) else {
|
||||
@@ -405,7 +405,7 @@ mod tests {
|
||||
for user in ["probe0", "probe7", "PROBE12"] {
|
||||
assert!(is_probe_account(user), "{user} is a probe account");
|
||||
}
|
||||
// The director's account, and bystanders: never touched.
|
||||
// A player's account, and plain test accounts: never touched.
|
||||
for user in ["one", "two", "probe", "probeone", "aprobe1", "probe1x"] {
|
||||
assert!(!is_probe_account(user), "{user} is NOT a probe account");
|
||||
}
|
||||
|
||||
+115
-123
@@ -69,21 +69,19 @@ pub(crate) fn capture_ui_opted_in() -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// **Do the credentials come from the environment?** (`$WOW_USER` / `$WOW_PASS` / `$WOW_CHAR` —
|
||||
/// the login screen's env fast path.)
|
||||
/// **Do the credentials come from the environment?** (`$WOW_USER` and `$WOW_PASS`, both — the
|
||||
/// login screen's env fast path.)
|
||||
///
|
||||
/// The player answer is `false`, and it is the default: with none of these set the client opens at
|
||||
/// the login screen and waits for somebody to type. Setting any of them says *"log in without
|
||||
/// waiting for me to type"* — and **that is the only thing it says.** It is not a claim about who
|
||||
/// is in the room; [`unattended`] is the fact for that, and keeping the two apart is decision 1769.
|
||||
///
|
||||
/// One environment fact, several readers, no shared symbol — the module doc's pattern, same as
|
||||
/// [`scenario_active`], and the reason the roster's `WOW_CHAR` fast path asks here instead of
|
||||
/// re-reading the three names.
|
||||
/// The player answer is `false`, and it is the default: with neither set the client opens at the
|
||||
/// login screen and waits for somebody to type. Setting both says *"log in without waiting for me
|
||||
/// to type"* — and **that is the only thing it says.** It is not a claim about who is in the room;
|
||||
/// [`unattended`] is the fact for that, and keeping the two apart is decision 1769. There is no
|
||||
/// default account: one credential without the other is a typed login, and `$WOW_CHAR` alone is
|
||||
/// the roster's fast path after one (`char_select`), not a login.
|
||||
pub(crate) fn env_login() -> bool {
|
||||
["WOW_USER", "WOW_PASS", "WOW_CHAR"]
|
||||
["WOW_USER", "WOW_PASS"]
|
||||
.iter()
|
||||
.any(|k| std::env::var_os(k).is_some())
|
||||
.all(|k| std::env::var_os(k).is_some())
|
||||
}
|
||||
|
||||
/// **Is there nobody here?** (`$WOW_UNATTENDED`, or the two runs that are automated by
|
||||
@@ -107,8 +105,8 @@ pub(crate) fn env_login() -> bool {
|
||||
///
|
||||
/// `WOW_CAPTURE` and `WOW_RIG` are folded in because they *cannot* be a person: a capture authors
|
||||
/// the camera and a rig drives the body, so a run that sets either has already said what it is and
|
||||
/// cannot forget to. Everything else declares — `scripts/leg.sh`, `smoke.sh`, `cine.sh`,
|
||||
/// `summon-live.sh` and the ad-hoc probe recipe in `docs/METHOD.md` all pass `WOW_UNATTENDED=1`.
|
||||
/// cannot forget to. Everything else declares — `scripts/smoke.sh`, `cine.sh`, `summon-live.sh`
|
||||
/// and the ad-hoc probe recipe in the `probe` skill all pass `WOW_UNATTENDED=1`.
|
||||
///
|
||||
/// Read by whatever may act *instead of* a person: the lost-session verdict (decision 1262 — the
|
||||
/// session-loss readers never call this directly, [`crate::net::DisconnectedMessage::new`] asks
|
||||
@@ -124,14 +122,14 @@ pub(crate) fn unattended() -> bool {
|
||||
/// (decision 1371's `FATAL` marker, resting on 1769's fact.)
|
||||
///
|
||||
/// `true` only for a run that has declared itself [`unattended`]: it exits non-zero on the one
|
||||
/// greppable marker `scripts/leg.sh` keys on, rather than parking a driverless run on a dialog for
|
||||
/// greppable marker a leg runner keys on, rather than parking a driverless run on a dialog for
|
||||
/// its whole wall-clock. Otherwise the dialog stays up for whoever is there.
|
||||
///
|
||||
/// The `false` arm still speaks, when the credentials came from the environment: a probe that
|
||||
/// quietly parks for 300 s instead of failing in 5 is precisely what 1769's default trades away,
|
||||
/// so the log names the declaration that buys the old behaviour back. Both arms live here because
|
||||
/// three call sites — two in [`crate::login`], one in [`crate::char_select`] — must not drift
|
||||
/// apart on either the verdict or the marker `leg.sh` greps for.
|
||||
/// apart on either the verdict or the marker a leg runner greps for.
|
||||
pub(crate) fn fatal_when_driverless(why: &str) -> bool {
|
||||
if unattended() {
|
||||
error!("login: FATAL — {why}; exiting");
|
||||
@@ -248,108 +246,101 @@ pub(crate) fn dev_source_dir() -> Option<&'static std::path::Path> {
|
||||
}
|
||||
|
||||
/// The pre-connect **account guard**, consulted by the login policy's env fast path
|
||||
/// ([`crate::login`]). Returns `Err(explanation)` when this build lives in a worktree pool slot and
|
||||
/// the fast path is about to authenticate as an account that belongs to somebody else — the
|
||||
/// director's `one` (a login KICKS their live session mid-play) or another slot's `probeN` (the
|
||||
/// kicked client's 0065 teardown despawns every net entity, so a parallel session's probe reads a
|
||||
/// unit-less world and prints garbage; it happened, docs/METHOD.md records it).
|
||||
/// ([`crate::login`]). Returns `Err(explanation)` when this checkout declares the account its
|
||||
/// scripted runs log in as ([`declared_identity`]) and the fast path is about to authenticate as
|
||||
/// anything else: a login KICKS whoever holds the account — a player mid-session, or another
|
||||
/// checkout's probe, whose kicked client (the 0065 teardown) despawns every net entity, so that
|
||||
/// run's next sample reads a unit-less world and prints garbage.
|
||||
///
|
||||
/// Slot identity comes from the compiled-in manifest path, because that is what the pool guarantees
|
||||
/// is unique per session: every slot has its own checkout and its own `target/`. Outside a pool slot
|
||||
/// (the primary checkout, which is the director's) the guard is inert — it has no business having an
|
||||
/// opinion about a login it cannot attribute. That is why this lives here and not in `preflight`
|
||||
/// The declaration is a file, never the build directory: a path is nobody's identity. Without
|
||||
/// one (a plain clone, a player build) the guard is inert — it has no business having an opinion
|
||||
/// about a login it cannot attribute. That is why this lives here and not in `preflight`
|
||||
/// (decision 0649's home for it, until 1174 made `preflight` dev-only): gameplay's login policy
|
||||
/// calls it, and gameplay may not call an instrument.
|
||||
///
|
||||
/// `WOW_ALLOW_ACCOUNT=1` is the escape hatch for the rare deliberate cross-account run; it turns the
|
||||
/// refusal into a warning rather than silence, because the kick still happens.
|
||||
pub(crate) fn account_guard(user: &str) -> Result<(), String> {
|
||||
guard_for(compiled_slot(), user)
|
||||
guard_for(declared_identity().as_ref(), user)
|
||||
}
|
||||
|
||||
/// The pool slot this binary was *compiled* in, or `None`.
|
||||
///
|
||||
/// A player build is never in a pool slot, so 1174 read `env!("CARGO_MANIFEST_DIR")`
|
||||
/// unconditionally on the grounds that the ladder collapses to `Ok(())` anyway. It does — but the
|
||||
/// path is still a **string in the shipped binary**, naming the build machine's home directory,
|
||||
/// which is the one thing decision 1175 exists to end (its falsifier is literally
|
||||
/// `strings <player binary> | grep -c '/Users/…'`). It costs one `cfg` to not ship it, and it is
|
||||
/// what makes that falsifier readable: after this, the only source-tree paths left in a player
|
||||
/// binary are debuginfo and panic metadata, never data the program acts on.
|
||||
/// What `.probe-identity` at the project root declares: the account a scripted run from this
|
||||
/// checkout logs in as (`WOW_USER=`, `WOW_PASS=`, `WOW_CHAR=`, one per line; never committed —
|
||||
/// `.gitignore` carries it). A machine that runs several checkouts against one server gives each
|
||||
/// its own account this way, and `scripts/probe-identity.sh` reads the same file.
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct DeclaredIdentity {
|
||||
pub(crate) user: String,
|
||||
pub(crate) character: String,
|
||||
}
|
||||
|
||||
/// The declaration, read off the project folder — `dev` only, the install resolver's own
|
||||
/// project-folder rung: a player build has no source tree to name (decision 1175), and no
|
||||
/// declared identity to keep.
|
||||
#[cfg(feature = "dev")]
|
||||
fn compiled_slot() -> Option<u32> {
|
||||
pool_slot(env!("CARGO_MANIFEST_DIR"))
|
||||
pub(crate) fn declared_identity() -> Option<DeclaredIdentity> {
|
||||
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.ancestors()
|
||||
.nth(2)?;
|
||||
parse_identity(&std::fs::read_to_string(root.join(".probe-identity")).ok()?)
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "dev"))]
|
||||
fn compiled_slot() -> Option<u32> {
|
||||
pub(crate) fn declared_identity() -> Option<DeclaredIdentity> {
|
||||
None
|
||||
}
|
||||
|
||||
/// [`account_guard`]'s decision, with the slot passed in so the ladder is testable from any
|
||||
/// checkout (the real one reads a compile-time path that differs per worktree).
|
||||
fn guard_for(slot: Option<u32>, user: &str) -> Result<(), String> {
|
||||
let Some(slot) = slot else {
|
||||
/// The file's three lines, as an identity — `None` unless the account and the character are
|
||||
/// both there (the password is the scripts' business, never this crate's).
|
||||
#[cfg_attr(not(feature = "dev"), allow(dead_code))]
|
||||
fn parse_identity(text: &str) -> Option<DeclaredIdentity> {
|
||||
let field = |key: &str| {
|
||||
text.lines()
|
||||
.filter_map(|l| l.trim().strip_prefix(key)?.strip_prefix('='))
|
||||
.map(|v| v.trim().trim_matches('"').to_string())
|
||||
.find(|v| !v.is_empty())
|
||||
};
|
||||
Some(DeclaredIdentity {
|
||||
user: field("WOW_USER")?,
|
||||
character: field("WOW_CHAR")?,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`account_guard`]'s decision, with the declaration passed in so it is testable without a file.
|
||||
fn guard_for(declared: Option<&DeclaredIdentity>, user: &str) -> Result<(), String> {
|
||||
let Some(id) = declared else {
|
||||
return Ok(());
|
||||
};
|
||||
let mine = format!("probe{slot}");
|
||||
let user_lc = user.to_ascii_lowercase();
|
||||
if user_lc == mine {
|
||||
if user.eq_ignore_ascii_case(&id.user) {
|
||||
return Ok(());
|
||||
}
|
||||
let whose = if user_lc == "one" {
|
||||
"the DIRECTOR's account — logging in on it kicks them out of their live session mid-play"
|
||||
} else if user_lc.starts_with("probe") && user_lc[5..].chars().all(|c| c.is_ascii_digit()) {
|
||||
"ANOTHER worktree slot's probe account — logging in on it kicks that session's probe out \
|
||||
of the world, and its next sample reads a unit-less world"
|
||||
} else {
|
||||
return Ok(()); // a bystander account (`two`, a fresh test account): not ours to police
|
||||
};
|
||||
Err(format!(
|
||||
"the env fast path is about to log in as `{user}` from pool-{slot}, and that is {whose}. \
|
||||
This slot's identity is WOW_USER=probe{slot} WOW_PASS=pprobe{slot} \
|
||||
WOW_CHAR=Probe{spelled} (docs/METHOD.md \"The local vmangos server\").",
|
||||
spelled = spell_digit(slot)
|
||||
"the env fast path is about to log in as `{user}`, and this checkout's .probe-identity \
|
||||
declares `{}` (WOW_CHAR={}). A login kicks whoever holds `{user}` — a player mid-session, \
|
||||
or another checkout's probe.",
|
||||
id.user, id.character
|
||||
))
|
||||
}
|
||||
|
||||
/// This slot's index as the word the probe identity spells it with (`pool-4` → `"four"`), or `None`
|
||||
/// outside a pool slot. The one place anything else should ask "which session am I?" — the rig keys
|
||||
/// its derived character names off it, and the preflight banner names it.
|
||||
pub(crate) fn slot_word() -> Option<&'static str> {
|
||||
compiled_slot().map(spell_digit)
|
||||
/// The word a rig appends to the bodies it mints for this checkout (`<Race3><Class3><word>[f]`),
|
||||
/// so that several checkouts against one server never collide on a character name: the declared
|
||||
/// character, lowercased, without a leading `probe` — a probe fleet names its bodies
|
||||
/// `Probe<word>`, and the word is the identity. `None` without a declaration; the rig then
|
||||
/// configures the body it logged in as.
|
||||
pub(crate) fn rig_suffix() -> Option<String> {
|
||||
Some(suffix_of(&declared_identity()?.character))
|
||||
}
|
||||
|
||||
/// The pool slot index in a `…/benilla-wt/pool-<N>/…` manifest path; `None` for the primary
|
||||
/// checkout and any non-pool worktree. Dev-only, because [`compiled_slot`] is its only caller and
|
||||
/// a player build must not name a manifest path at all (1175).
|
||||
#[cfg(any(feature = "dev", test))]
|
||||
fn pool_slot(manifest_dir: &str) -> Option<u32> {
|
||||
let mut parts = manifest_dir.split('/');
|
||||
while let Some(part) = parts.next() {
|
||||
if part == "benilla-wt" {
|
||||
return parts.next()?.strip_prefix("pool-")?.parse().ok();
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The probe character's name suffix for slot `n` — vmangos names carry no digits, so the pool
|
||||
/// index is spelled (`pool-4` → `Probefour`).
|
||||
fn spell_digit(n: u32) -> &'static str {
|
||||
match n {
|
||||
0 => "zero",
|
||||
1 => "one",
|
||||
2 => "two",
|
||||
3 => "three",
|
||||
4 => "four",
|
||||
5 => "five",
|
||||
6 => "six",
|
||||
7 => "seven",
|
||||
8 => "eight",
|
||||
9 => "nine",
|
||||
_ => "<n>",
|
||||
}
|
||||
fn suffix_of(character: &str) -> String {
|
||||
let name = character.to_ascii_lowercase();
|
||||
let word = name
|
||||
.strip_prefix("probe")
|
||||
.filter(|w| !w.is_empty())
|
||||
.unwrap_or(&name);
|
||||
word.chars()
|
||||
.filter(char::is_ascii_alphabetic)
|
||||
.take(6)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -479,44 +470,45 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_guard_only_polices_accounts_that_belong_to_someone() {
|
||||
// Our own slot's probe: the whole point of the identity.
|
||||
assert!(guard_for(Some(4), "probe4").is_ok());
|
||||
assert!(guard_for(Some(4), "PROBE4").is_ok()); // vmangos accounts are case-insensitive
|
||||
// The director's account, and a neighbouring slot's probe: both kick a live session.
|
||||
let director = guard_for(Some(4), "one").unwrap_err();
|
||||
assert!(director.contains("DIRECTOR") && director.contains("WOW_USER=probe4"));
|
||||
fn the_fast_path_is_the_declared_account_or_refused() {
|
||||
let id = parse_identity("WOW_USER=probe4\nWOW_PASS=pprobe4\nWOW_CHAR=Probefour\n").unwrap();
|
||||
// Our own account: the whole point of the declaration.
|
||||
assert!(guard_for(Some(&id), "probe4").is_ok());
|
||||
assert!(guard_for(Some(&id), "PROBE4").is_ok()); // vmangos accounts are case-insensitive
|
||||
|
||||
// Anyone else's account — a player's, another checkout's probe — kicks a live session.
|
||||
let player = guard_for(Some(&id), "one").unwrap_err();
|
||||
assert!(player.contains("probe4") && player.contains("kicks"));
|
||||
// The override hint belongs to the caller that can act on it, not to the reason.
|
||||
assert!(!director.contains("WOW_ALLOW_ACCOUNT"));
|
||||
assert!(guard_for(Some(4), "probe7")
|
||||
.unwrap_err()
|
||||
.contains("ANOTHER worktree slot"));
|
||||
// A bystander account is nobody's to police, and outside a pool slot we have no standing.
|
||||
assert!(guard_for(Some(4), "two").is_ok());
|
||||
assert!(!player.contains("WOW_ALLOW_ACCOUNT"));
|
||||
assert!(guard_for(Some(&id), "probe7").is_err());
|
||||
// No declaration, no standing.
|
||||
assert!(guard_for(None, "one").is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_probe_character_name_spells_the_slot() {
|
||||
// vmangos player names carry no digits, so `Probe4` cannot exist — the pool index is spelled.
|
||||
assert!(guard_for(Some(0), "one")
|
||||
.unwrap_err()
|
||||
.contains("WOW_CHAR=Probezero"));
|
||||
assert!(guard_for(Some(9), "one")
|
||||
.unwrap_err()
|
||||
.contains("WOW_CHAR=Probenine"));
|
||||
fn the_declaration_needs_the_account_and_the_character() {
|
||||
assert_eq!(
|
||||
parse_identity("WOW_USER=probe4\nWOW_PASS=x\nWOW_CHAR=\"Probefour\"\n"),
|
||||
Some(DeclaredIdentity {
|
||||
user: "probe4".into(),
|
||||
character: "Probefour".into()
|
||||
})
|
||||
);
|
||||
assert!(parse_identity("WOW_USER=probe4\nWOW_PASS=x\n").is_none());
|
||||
assert!(parse_identity("WOW_USER=\nWOW_CHAR=Probefour\n").is_none());
|
||||
assert!(parse_identity("").is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_slot_is_read_off_the_manifest_path() {
|
||||
assert_eq!(
|
||||
pool_slot("/Users/sam/dev/benilla-wt/pool-7/crates/benilla"),
|
||||
Some(7)
|
||||
);
|
||||
assert_eq!(pool_slot("/Users/sam/dev/benilla-wow/crates/benilla"), None);
|
||||
assert_eq!(
|
||||
pool_slot("/Users/sam/dev/benilla-wow/.claude/worktrees/x/crates/benilla"),
|
||||
None
|
||||
);
|
||||
fn the_rig_word_is_the_declared_character_without_its_probe_prefix() {
|
||||
// vmangos player names carry no digits, so a probe fleet spells its index: the word is
|
||||
// what follows `Probe`, and the rig's `Taudru<word>` keeps one body per checkout.
|
||||
assert_eq!(suffix_of("Probefour"), "four");
|
||||
assert_eq!(suffix_of("Probezero"), "zero");
|
||||
// Any other name is its own word, cut to what a 12-character name has room for.
|
||||
assert_eq!(suffix_of("Tester"), "tester");
|
||||
assert_eq!(suffix_of("Longcharname"), "longch");
|
||||
assert_eq!(suffix_of("Probe"), "probe");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -801,7 +801,7 @@ mod tests {
|
||||
/// and nothing above it is, so a failure names the platform file and nothing else.
|
||||
///
|
||||
/// It is the whole verification story off macOS, where the gates never compile this code at
|
||||
/// all: `scripts/crosscheck.sh` type-checks Linux and Windows, and this runs the result.
|
||||
/// all: a cross-platform compile type-checks Linux and Windows, and this runs the result.
|
||||
///
|
||||
/// WOW_AUDIO_LIVE=1 cargo test -p benilla-app --lib sound::output:: -- --nocapture
|
||||
///
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! UI sounds — the app side of the Lua `PlaySound` seam (decision 0070 §4, the one deliberate
|
||||
//! UI-crate overlap cut from the sound worktree), plus the **per-item item-gesture sounds**
|
||||
//! UI-crate overlap cut when the sound crate was split), plus the **per-item item-gesture sounds**
|
||||
//! (decision 0091): the bag-drag pickup/put-down, and the loot-window pickup.
|
||||
//!
|
||||
//! Three triggers, all 2D SFX plays, all after the UI input pass so a click's sound plays the
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
//!
|
||||
//! ## What vmangos actually does, and what that costs us
|
||||
//!
|
||||
//! Verified against `/Users/sam/wre/vmangos-src` (`Handlers/GMTicketHandler.cpp`, `GMTicketMgr.cpp`,
|
||||
//! Verified against the vmangos source (`Handlers/GMTicketHandler.cpp`, `GMTicketMgr.cpp`,
|
||||
//! `Commands/TicketCommands.cpp`) — four behaviours a client author has to design around:
|
||||
//!
|
||||
//! 1. **Silence is a legal answer.** Create returns with no packet at all when the queue is off,
|
||||
|
||||
@@ -71,7 +71,7 @@ fn read_source_tree(req: &str) -> Option<String> {
|
||||
///
|
||||
/// It exists because a number taken from the corpus harness is meaningless without it. In a dev
|
||||
/// build [`read`] prefers the SOURCE TREE, so editing an `assets/ui` file changes what a survey
|
||||
/// loads **with no rebuild** — and this repo's worktrees are routinely shared by several agents at
|
||||
/// loads **with no rebuild** — and a tree is routinely edited by several agents at
|
||||
/// once. Three separate measurements this arc were taken across a moving tree and one of them
|
||||
/// landed a wrong attribution in a decision record: 87/218 was credited to a single table when a
|
||||
/// controlled A/B later put it at 75, with the other twelve belonging to a neighbour's uncommitted
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! **The reference's frame FLAGS are the test oracle** — the flag half of decision 0675's "the
|
||||
//! reference file is the test oracle", and the gate for the class decision 1739 closed.
|
||||
//!
|
||||
//! `framexml_diff` guards the *numbers* a transcribed window carries. Nothing guarded its *flags*,
|
||||
//! A window's numbers are the stock file's own now. Nothing guarded its *flags*,
|
||||
//! and they went quietly wrong at scale: on 2026-08-30 a sweep found **47 frames** the reference
|
||||
//! marks `toplevel` and ours did not (so no window ever came to the front), **23** the reference
|
||||
//! makes mouse-interactive and ours did not (so a click on a window's own background fell through
|
||||
@@ -71,11 +71,10 @@
|
||||
//! can hide a divergence, never invent one — so nothing it misses turns into a false failure. It
|
||||
//! would be a `KNOWN` entry's reason if any frame still needed one.
|
||||
//!
|
||||
//! The whole module skips cleanly with no install — `_extracted_framexml/` is a gitignored
|
||||
//! Blizzard asset, like every other client-data test here.
|
||||
//! The whole module skips cleanly with no install — the reference corpus is read off the
|
||||
//! player's own patch chain, like every other client-data test here.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
use benilla_ui::framexml::{self, Element, TopLevel};
|
||||
|
||||
@@ -232,34 +231,54 @@ const KNOWN: &[Known] = &[
|
||||
// window is the reference's own now, scroll children and all.
|
||||
];
|
||||
|
||||
/// The extracted reference FrameXML directory, or `None` when the install isn't there.
|
||||
fn reference_dir() -> Option<PathBuf> {
|
||||
let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("../../WoW/_extracted_framexml");
|
||||
dir.is_dir().then_some(dir)
|
||||
/// Every reference FrameXML document, read off the player's own patch chain in the client's
|
||||
/// load order: the files `FrameXML.toc` lists, and what each `<Include>`s, each once. `None`
|
||||
/// without an install. Off the chain, never an extracted folder, so the gate is armed on any
|
||||
/// machine with the install rather than on the one that happened to extract it.
|
||||
fn reference_documents() -> Option<Vec<framexml::ParsedDocument>> {
|
||||
let data = benilla_formats::wow_data()?;
|
||||
let chain = benilla_formats::open_chain(&data).ok()?;
|
||||
let toc = chain.read("Interface\\FrameXML\\FrameXML.toc").ok()?;
|
||||
let toc = benilla_ui::toc::Toc::parse(&benilla_ui::source::decode(&toc));
|
||||
let mut queue: Vec<String> = toc
|
||||
.files
|
||||
.iter()
|
||||
.filter(|f| f.to_ascii_lowercase().ends_with(".xml"))
|
||||
.map(|f| format!("Interface\\FrameXML\\{f}"))
|
||||
.collect();
|
||||
let mut seen = HashSet::new();
|
||||
let mut docs = Vec::new();
|
||||
while !queue.is_empty() {
|
||||
let path = queue.remove(0);
|
||||
if !seen.insert(path.to_ascii_lowercase()) {
|
||||
continue;
|
||||
}
|
||||
let Ok(bytes) = chain.read(&path) else {
|
||||
continue;
|
||||
};
|
||||
// Blizzard ships a UTF-8 BOM on some of these and stray high bytes in comments; the parse
|
||||
// is what matters, so read lossily rather than refusing the file.
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
|
||||
continue;
|
||||
};
|
||||
let dir = path.rsplit_once('\\').map(|(d, _)| d).unwrap_or("");
|
||||
for item in &doc.items {
|
||||
if let TopLevel::Include(file) = item {
|
||||
queue.push(format!("{dir}\\{file}"));
|
||||
}
|
||||
}
|
||||
docs.push(doc);
|
||||
}
|
||||
Some(docs)
|
||||
}
|
||||
|
||||
/// Every named *frame* element in the reference corpus, keyed by name — templates and instances
|
||||
/// alike, nested `<Frames>` included, `$parent`-relative names excluded (they repeat across
|
||||
/// templates and name nothing on their own).
|
||||
fn reference_frames() -> Option<HashMap<String, Element>> {
|
||||
let dir = reference_dir()?;
|
||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||
.ok()?
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().is_some_and(|e| e.eq_ignore_ascii_case("xml")))
|
||||
.collect();
|
||||
paths.sort();
|
||||
|
||||
let mut out: HashMap<String, Element> = HashMap::new();
|
||||
for path in paths {
|
||||
// Blizzard ships a UTF-8 BOM on some of these and stray high bytes in comments; the parse
|
||||
// is what matters, so read lossily rather than refusing the file.
|
||||
let bytes = std::fs::read(&path).ok()?;
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
|
||||
continue;
|
||||
};
|
||||
for doc in reference_documents()? {
|
||||
for item in &doc.items {
|
||||
if let TopLevel::Template(el) | TopLevel::Instance(el) = item {
|
||||
collect_named(el, &mut out);
|
||||
@@ -315,24 +334,8 @@ fn collect_nesting(
|
||||
|
||||
/// The reference's nesting table, built over the same corpus [`reference_frames`] reads.
|
||||
fn reference_nesting() -> Option<HashMap<String, Option<String>>> {
|
||||
let dir = reference_dir()?;
|
||||
let mut paths: Vec<PathBuf> = std::fs::read_dir(&dir)
|
||||
.ok()?
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| p.extension().is_some_and(|e| e.eq_ignore_ascii_case("xml")))
|
||||
.collect();
|
||||
paths.sort();
|
||||
|
||||
let mut out: HashMap<String, Option<String>> = HashMap::new();
|
||||
for path in paths {
|
||||
let Ok(bytes) = std::fs::read(&path) else {
|
||||
continue;
|
||||
};
|
||||
let text = String::from_utf8_lossy(&bytes);
|
||||
let Ok(doc) = framexml::parse(text.trim_start_matches('\u{feff}')) else {
|
||||
continue;
|
||||
};
|
||||
for doc in reference_documents()? {
|
||||
for item in &doc.items {
|
||||
if let TopLevel::Template(el) | TopLevel::Instance(el) = item {
|
||||
collect_nesting(el, None, &mut out);
|
||||
@@ -436,14 +439,8 @@ fn describe(frame: &str, flag: Flag, ours: &str, theirs: &str) -> String {
|
||||
/// `parent="UIParent"` back on `BlackoutWorld` and it names that.
|
||||
#[test]
|
||||
fn the_shipped_frames_carry_the_references_flags() {
|
||||
// Two gates, because the corpus and the manifest are two assets: the extracted reference
|
||||
// dir below, and the chain `load_default_ui` reads — under `WOW_DATA=` (1451) the first is
|
||||
// still there and the second is not.
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let Some(reference) = reference_frames() else {
|
||||
eprintln!("skipping: no extracted reference FrameXML (WoW/_extracted_framexml)");
|
||||
return;
|
||||
};
|
||||
let reference = reference_frames().expect("the reference FrameXML off the player's chain");
|
||||
let nesting = reference_nesting().expect("the same corpus the frames came from");
|
||||
assert!(
|
||||
reference.len() > 500,
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
//! **The reference file is the test oracle** (decision 0675) — the shared half of it.
|
||||
//!
|
||||
//! A transcribed window's geometry errors are invisible to every behavioural test: the frames
|
||||
//! load, the buttons click, the lists populate, and only the *look* is wrong. 0675 built the
|
||||
//! detector for `FriendsFrame` after two rounds of the director's eye caught what a one-number
|
||||
//! diff would have caught on its first run, and closed with "for the next transcribed window,
|
||||
//! write the diff test first". This is that scraper, lifted out of `friends_tests` so the next
|
||||
//! window costs a call rather than a copy (`QuestTimerFrame` is the first to pay the lower price).
|
||||
//!
|
||||
//! Deliberately narrow: it does not try to understand the XML, only to notice that a number moved.
|
||||
//! Two properties make it a guard rather than a comfort, and both are the caller's to keep —
|
||||
//! **known-benign differences are an explicit list, never a pattern**, so a new difference cannot
|
||||
//! hide inside a tolerance; and **it must be verified to fail** (perturb one number by 1 px and
|
||||
//! watch it name the element).
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// The extracted reference FrameXML file of that name, or `None` when the install isn't there
|
||||
/// (these tests skip cleanly without it — it is a gitignored Blizzard asset).
|
||||
pub(super) fn reference(file: &str) -> Option<PathBuf> {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("../../WoW/_extracted_framexml")
|
||||
.join(file);
|
||||
path.is_file().then_some(path)
|
||||
}
|
||||
|
||||
/// Named element → the `<AbsDimension>` pairs inside it, up to the next named element.
|
||||
///
|
||||
/// Hand-rolled rather than a regex: this crate has no regex dependency, and adding one for a scan
|
||||
/// of two files is a worse trade than twenty lines of `find`.
|
||||
///
|
||||
/// `$parent*` names repeat across templates (every row template has a `$parentText`), so a bare
|
||||
/// name would compare one template's column against another's. Each is qualified by the template
|
||||
/// it lives in — the nearest preceding real name.
|
||||
fn scrape(text: &str) -> Vec<(String, Vec<(f32, f32)>)> {
|
||||
const TAGS: &[&str] = &[
|
||||
"<Button name=\"",
|
||||
"<Frame name=\"",
|
||||
"<EditBox name=\"",
|
||||
"<ScrollFrame name=\"",
|
||||
"<FontString name=\"",
|
||||
"<Texture name=\"",
|
||||
"<CheckButton name=\"",
|
||||
];
|
||||
// Every (offset, name) where a named element starts, in document order.
|
||||
let mut marks: Vec<(usize, String)> = Vec::new();
|
||||
for tag in TAGS {
|
||||
let mut from = 0;
|
||||
while let Some(hit) = text[from..].find(tag) {
|
||||
let at = from + hit;
|
||||
let name_start = at + tag.len();
|
||||
let Some(len) = text[name_start..].find('"') else {
|
||||
break;
|
||||
};
|
||||
marks.push((at, text[name_start..name_start + len].to_string()));
|
||||
from = name_start + len;
|
||||
}
|
||||
}
|
||||
marks.sort_by_key(|(at, _)| *at);
|
||||
|
||||
let mut owner = String::new();
|
||||
marks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, (at, name))| {
|
||||
let end = marks.get(i + 1).map_or(text.len(), |(next, _)| *next);
|
||||
let key = if let Some(child) = name.strip_prefix("$parent") {
|
||||
format!("{owner}/{child}")
|
||||
} else {
|
||||
owner = name.clone();
|
||||
name.clone()
|
||||
};
|
||||
(key, dimensions(&text[*at..end]))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Every `<AbsDimension x=".." y=".."/>` in `chunk`, in order.
|
||||
fn dimensions(chunk: &str) -> Vec<(f32, f32)> {
|
||||
const OPEN: &str = "<AbsDimension x=\"";
|
||||
let mut out = Vec::new();
|
||||
let mut rest = chunk;
|
||||
while let Some(hit) = rest.find(OPEN) {
|
||||
rest = &rest[hit + OPEN.len()..];
|
||||
let Some((x, tail)) = rest.split_once('"') else {
|
||||
break;
|
||||
};
|
||||
let Some(y_at) = tail.find("y=\"") else { break };
|
||||
let Some((y, tail)) = tail[y_at + 3..].split_once('"') else {
|
||||
break;
|
||||
};
|
||||
if let (Ok(x), Ok(y)) = (x.parse(), y.parse()) {
|
||||
out.push((x, y));
|
||||
}
|
||||
rest = tail;
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Assert every element benilla's `ours` file shares with the reference `reference` file carries
|
||||
/// the reference's `<AbsDimension>` numbers.
|
||||
///
|
||||
/// - `ours` is a path under `assets/ui/`; names are matched to the reference's after stripping the
|
||||
/// house `Benilla` prefix (a window whose frames keep the bare reference names — decision 0591
|
||||
/// §3 — matches unchanged).
|
||||
/// - `expected` lists the *deliberate* deviations by their REFERENCE name. Every entry earns a
|
||||
/// comment at the call site naming its reason; that is what keeps this a guard.
|
||||
/// - `min_compared` fails the test if fewer elements paired up than that, so a scrape or a naming
|
||||
/// change that quietly stops comparing anything is caught rather than reported as a pass.
|
||||
///
|
||||
/// Elements ours has and the reference does not (our own template faces) are skipped, not failed.
|
||||
pub(super) fn assert_geometry_matches(
|
||||
ours: &str,
|
||||
reference: &Path,
|
||||
expected: &[&str],
|
||||
min_compared: usize,
|
||||
) {
|
||||
assert_geometry_matches_text(
|
||||
ours,
|
||||
&std::fs::read_to_string(reference).unwrap(),
|
||||
expected,
|
||||
min_compared,
|
||||
);
|
||||
}
|
||||
|
||||
/// [`assert_geometry_matches`] against reference TEXT rather than a file on disk.
|
||||
///
|
||||
/// The split exists because not every reference file is *on* disk: `_extracted_framexml/` holds
|
||||
/// FrameXML only, and a window whose specification is partly a `Blizzard_*` **addon** — the raid
|
||||
/// grid is the first, decision 1549 — has to read its half straight out of the patch chain
|
||||
/// (`benilla_formats::open_chain`, the same door `mpqx` uses). Reading it rather than requiring
|
||||
/// someone to have extracted it first is what keeps that half of the diff ARMED instead of
|
||||
/// skipped, and it writes nothing into the install.
|
||||
pub(super) fn assert_geometry_matches_text(
|
||||
ours: &str,
|
||||
reference_text: &str,
|
||||
expected: &[&str],
|
||||
min_compared: usize,
|
||||
) {
|
||||
let ours_text = std::fs::read_to_string(
|
||||
Path::new(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("assets/ui")
|
||||
.join(ours),
|
||||
)
|
||||
.unwrap();
|
||||
let theirs: HashMap<String, Vec<(f32, f32)>> = scrape(reference_text).into_iter().collect();
|
||||
|
||||
let mut compared = 0;
|
||||
let mut drifted = Vec::new();
|
||||
for (name, dims) in scrape(&ours_text) {
|
||||
let key = name.replace("Benilla", "");
|
||||
let Some(ref_dims) = theirs.get(&key) else {
|
||||
continue; // ours alone (the local template faces) — nothing to compare against
|
||||
};
|
||||
compared += 1;
|
||||
if dims != *ref_dims && !expected.contains(&key.as_str()) {
|
||||
drifted.push(format!("{key}: ours {dims:?} != ref {ref_dims:?}"));
|
||||
}
|
||||
}
|
||||
assert!(
|
||||
compared >= min_compared,
|
||||
"only {compared} elements of {ours} matched by name (expected >= {min_compared}) — the \
|
||||
scrape or the naming broke"
|
||||
);
|
||||
assert!(
|
||||
drifted.is_empty(),
|
||||
"{ours} geometry differs from the reference FrameXML:\n {}",
|
||||
drifted.join("\n ")
|
||||
);
|
||||
}
|
||||
@@ -1179,10 +1179,6 @@ mod duel_tests;
|
||||
#[cfg(test)]
|
||||
mod enchant_confirm_tests;
|
||||
|
||||
/// The shared reference-geometry diff (decision 0675) every transcribed window's test calls.
|
||||
#[cfg(test)]
|
||||
mod framexml_diff;
|
||||
|
||||
/// Its FLAG twin (decision 1739): the whole-tree sweep for `toplevel`/mouse/`id` against the
|
||||
/// reference, read off the loaded engine rather than off our XML.
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -181,30 +181,3 @@ fn a_world_entry_paints_a_quest_already_running() {
|
||||
// gap reads 89 through the `−1`.
|
||||
assert_eq!(row_text(&s, 1), "1 Min 29 Secs ");
|
||||
}
|
||||
|
||||
/// **The reference file is the test oracle** (decision 0675). Scrapes both this window and the
|
||||
/// extracted reference `QuestTimerFrame.xml` for `<AbsDimension>` pairs per named element and
|
||||
/// asserts every shared element's numbers match — the guard that catches a transcription whose
|
||||
/// frames all load, click and populate while every number is wrong.
|
||||
///
|
||||
/// Verified to fail: perturbing `QuestTimer1`'s TOP offset from -30 to -29 reports
|
||||
/// `QuestTimer1: ours [(140.0, 16.0), (0.0, -29.0)] != ref [(140.0, 16.0), (0.0, -30.0)]`.
|
||||
#[test]
|
||||
fn the_window_geometry_matches_the_reference_framexml() {
|
||||
let _data = benilla_formats::wow_data_or_skip!();
|
||||
let Some(reference) =
|
||||
super::framexml_diff::reference("Interface\\FrameXML\\QuestTimerFrame.xml")
|
||||
else {
|
||||
eprintln!("skipping: no extracted FrameXML");
|
||||
return;
|
||||
};
|
||||
// No exemptions: this window's frames keep the reference's bare names (decision 0591 §3 — the
|
||||
// manage pass and the ref's own row lookup both resolve by literal name), and every number in
|
||||
// it is the reference's. If an entry ever needs to go here, it names its reason.
|
||||
super::framexml_diff::assert_geometry_matches(
|
||||
"Interface\\FrameXML\\QuestTimerFrame.xml",
|
||||
&reference,
|
||||
&[],
|
||||
22,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -314,8 +314,8 @@ fn the_reported_html_page_draws_as_blocks_not_as_its_own_markup() {
|
||||
);
|
||||
}
|
||||
|
||||
/// **B342, on the reported page, through the real archives.** Goudy, 2026-08-27 (`#bugs`
|
||||
/// `1542371921486811236`): *"html images in books are not scaled correctly"* — the Alliance crest
|
||||
/// **B342, on the reported page, through the real archives.** Reported on Discord,
|
||||
/// 2026-08-27: *"html images in books are not scaled correctly"* — the Alliance crest
|
||||
/// on *A Treatise on Military Ranks* drawn several times the reference's size with the page's own
|
||||
/// text over it, beside a 1.12.1 shot of the same page for comparison.
|
||||
///
|
||||
|
||||
@@ -74,7 +74,6 @@ fn is_instrument(rel: &str) -> bool {
|
||||
"/bin/",
|
||||
"debug_panel",
|
||||
"shape_gate",
|
||||
"framexml_diff",
|
||||
"resolve_bench",
|
||||
]
|
||||
.iter()
|
||||
|
||||
@@ -21,9 +21,7 @@ use std::process::Command;
|
||||
/// Why it exists: people run benilla from a clone of the public snapshot repo, and a report ("this
|
||||
/// looks wrong", "it crashed here") is only actionable if we know *which* code they ran. A git sha
|
||||
/// is the only id that can't drift — the crate version is a permanent `0.1.0` and there are no
|
||||
/// releases to number. For a public clone the stamp is the **snapshot** sha, which maps back to the
|
||||
/// private commit exactly (`pubsync` tags it `pub/<public-short-sha>`); for our own worktrees it is
|
||||
/// the private commit itself.
|
||||
/// releases to number. The stamp is the commit the binary was built from, on any checkout.
|
||||
///
|
||||
/// It emits four `rustc-env` vars. The three git ones come back **empty** when git can't answer (a
|
||||
/// source zip with no `.git`, or no `git` on PATH), which the runtime side reports as an unknown
|
||||
@@ -45,8 +43,8 @@ use std::process::Command;
|
||||
/// A stamp is worthless if it can report a commit the binary isn't. The rerun triggers below are
|
||||
/// therefore exactly the files that change when HEAD moves: `HEAD` itself (checkout, rebase,
|
||||
/// detach) and the ref it names (a commit rewrites `refs/heads/<branch>`, not `HEAD`). Both are
|
||||
/// resolved with `git rev-parse --git-path`, which applies the worktree rules — in a `wt.sh` pool
|
||||
/// slot `.git` is a *file*, `HEAD` lives in `<primary>/.git/worktrees/<slot>/`, and `refs/heads/*`
|
||||
/// resolved with `git rev-parse --git-path`, which applies the worktree rules — in a linked
|
||||
/// worktree `.git` is a *file*, `HEAD` lives in `<main>/.git/worktrees/<name>/`, and `refs/heads/*`
|
||||
/// stay in the common dir. Only paths that exist are emitted: cargo reruns a build script whose
|
||||
/// watched path is *missing* on every build, and every one of those reruns relinks the app crate.
|
||||
///
|
||||
|
||||
@@ -163,6 +163,18 @@ pub fn skipped(what: &str, looked_in: &[PathBuf]) {
|
||||
what,
|
||||
looked_in,
|
||||
);
|
||||
// `$BENILLA_SKIP_LOG`: libtest swallows the line above, so a gate hands in a file and counts
|
||||
// the skips after the run — a clone without the data sees how hollow its green is.
|
||||
if let Some(log) = std::env::var_os("BENILLA_SKIP_LOG").filter(|p| !p.is_empty()) {
|
||||
use std::io::Write;
|
||||
if let Ok(mut f) = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(log)
|
||||
{
|
||||
let _ = writeln!(f, "{what}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// [`skipped`] with the switch passed in, so the refusal is testable without touching the
|
||||
@@ -183,15 +195,14 @@ fn skipped_under(required: bool, what: &str, looked_in: &[PathBuf]) {
|
||||
///
|
||||
/// Two rungs, the install's own shape: **`$BENILLA_ADDON_CORPUS`**, then
|
||||
/// **`<project folder>/wow-addons-vanilla`** — `dev` only, the same `CARGO_MANIFEST_DIR` hop as
|
||||
/// the install's rung 2. That folder is a gitignored symlink `scripts/wt.sh` lays into every pool
|
||||
/// slot and the primary (`link_wow`), exactly as it lays `WoW`; the corpus itself lives beside the
|
||||
/// primary checkout.
|
||||
/// the install's rung 2. That folder is a gitignored symlink beside `WoW`, laid by whoever set the
|
||||
/// checkout up; the corpus itself lives outside the tree.
|
||||
///
|
||||
/// **Why one rung and a link, not a walk up the tree.** Five test files carried their own copy of
|
||||
/// this resolver, and every copy looked for a *sibling* of the checkout — the manifest's ancestors
|
||||
/// two to four, joined with the folder name. That found the corpus from the primary and from the
|
||||
/// old pool root beside it, and nothing once the pool moved to the external drive (2026-08-30):
|
||||
/// from `/Volumes/SanDisk/benilla-wt/pool-N` the three hops name three folders that do not exist.
|
||||
/// from a worktree on another volume the three hops name three folders that do not exist.
|
||||
/// Every land gates in a slot, so thirty tests — a ratchet among them — skipped at every land for
|
||||
/// three weeks. A resolver that looks *outside* the checkout answers according to where the
|
||||
/// checkout happens to sit; the install learned this in 1175, and this is the same rule for the
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
//! **WoW coordinates throughout** (the Bevy transform is the consumer's job).
|
||||
//!
|
||||
//! This transcribes the structural skeleton of vmangos's own transport-path builder and mover:
|
||||
//! - `TransportMgr::GenerateWaypoints` (`/Users/sam/wre/vmangos-src/src/game/Transports/
|
||||
//! - `TransportMgr::GenerateWaypoints` (vmangos `src/game/Transports/
|
||||
//! TransportMgr.cpp:105-334`) — the keyframe list, the map-change/teleport skip dance, the
|
||||
//! per-leg Catmull-Rom splines, the `DistSinceStop`/`DistUntilStop` modular walks, the
|
||||
//! four-regime `TimeTo` trapezoid, and the `ArriveTime`/`DepartureTime` accumulation.
|
||||
|
||||
@@ -213,7 +213,7 @@ fn decodes_a_blp_icon_to_png() {
|
||||
.read_file("Interface/Icons/Spell_Holy_ArcaneIntellect.blp")
|
||||
.expect("read spell icon BLP");
|
||||
|
||||
// Keyed by pid, like every other temp path in the workspace: eight pool slots run
|
||||
// Keyed by pid, like every other temp path in the workspace: several worktrees run
|
||||
// `cargo test --workspace` concurrently, and a FIXED shared name means one run's
|
||||
// cleanup deletes the file another is about to read. That is not hypothetical — the
|
||||
// taxi twin below reddened a land gate exactly that way (decision 1918).
|
||||
|
||||
@@ -24,9 +24,8 @@
|
||||
//! pprobeM] [host]`. With one account it runs the CHANNEL case alone (the server echoes a channel
|
||||
//! line back to its own sender, so one session is a complete round trip). With two it also runs
|
||||
//! the PARTY case, which needs a real group: the first account invites the second, the second
|
||||
//! accepts, and the addon line goes out over `/p`. **Use `probe8`/`probe9` for the second
|
||||
//! identity** — those two accounts sit above `WT_MAX_SLOTS`, so no live session's slot owns them
|
||||
//! (decision 0530's slot-keyed rule).
|
||||
//! accepts, and the addon line goes out over `/p`. **The second identity is a probe account no
|
||||
//! checkout declares** — a login kicks whoever holds the account (decision 0530).
|
||||
//!
|
||||
//! Server prerequisites, both checked by the probe's own output rather than assumed:
|
||||
//! `AddonChannel = 1` (off ⇒ the server silently drops every addon send) and
|
||||
@@ -63,8 +62,7 @@ fn probe_char_name(user: &str) -> Option<String> {
|
||||
}
|
||||
|
||||
fn login(host: &str, user: &str, pass: &str) -> Result<(WorldSession, String)> {
|
||||
let name =
|
||||
probe_char_name(user).context("account must be a slot-keyed probeN (decision 0530)")?;
|
||||
let name = probe_char_name(user).context("account must be a probeN (decision 0530)")?;
|
||||
let logon = benilla_protocol::logon(host, user, pass)?;
|
||||
let addr = logon
|
||||
.realms
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
//! second `SMSG_SPELL_GO`) timestamps the server's cooldown end relative to the first GO.
|
||||
//!
|
||||
//! Run: `cargo run -p benilla-protocol --example cooldown_probe -- probeN pprobeN [host]` — the
|
||||
//! slot-keyed probe account (docs/METHOD.md "The local vmangos server"; NEVER `one`, the director's
|
||||
//! account — a probe login there kicks their live session). Two caveats: this is a COMBAT probe,
|
||||
//! so it runs director-supervised (docs/METHOD.md: no unattended combat probes), and `.learn` is
|
||||
//! probe account (the `probe` skill; never a player's account — a probe login there kicks their
|
||||
//! live session). Two caveats: this is a COMBAT probe, so it runs supervised (docs/METHOD.md: no
|
||||
//! unattended combat probes), and `.learn` is
|
||||
//! SEC_DEVELOPER (5); probe accounts are gmlevel 6 (SEC_ADMINISTRATOR), so it lands. The vmangos
|
||||
//! console for the run and restore it after.
|
||||
|
||||
@@ -43,10 +43,10 @@ fn main() -> Result<()> {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let user = args
|
||||
.next()
|
||||
.context("usage: cooldown_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: cooldown_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let pass = args
|
||||
.next()
|
||||
.context("usage: cooldown_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: cooldown_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let host = args.next().unwrap_or_else(|| "localhost".into());
|
||||
|
||||
let logon = benilla_protocol::logon(&host, &user, &pass)?;
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
//! decode against the server DB's `creature_template.faction`.
|
||||
//!
|
||||
//! Run: `cargo run -p benilla-protocol --example faction_probe -- probeN pprobeN [host]`
|
||||
//! — the slot-keyed probe account (docs/METHOD.md "The local vmangos server"). The account is a
|
||||
//! REQUIRED arg with no default: a default was once `one`, the director's account, where a
|
||||
//! probe login kicks their live session (decision 0530).
|
||||
//! — a probe account (the `probe` skill). The account is a REQUIRED arg with no default: a
|
||||
//! default is somebody's account, and a probe login there kicks their live session (decision
|
||||
//! 0530).
|
||||
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
@@ -16,10 +16,10 @@ fn main() -> Result<()> {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let user = args
|
||||
.next()
|
||||
.context("usage: faction_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: faction_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let pass = args
|
||||
.next()
|
||||
.context("usage: faction_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: faction_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let host = args.next().unwrap_or_else(|| "localhost".into());
|
||||
|
||||
let logon = benilla_protocol::logon(&host, &user, &pass)?;
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
//! HORDE character's say must echo back (the server accepted the tongue), a dot-command must
|
||||
//! answer (it survived the pre-parse `KnowsLanguage` gate), and the split-writer path must carry
|
||||
//! the tongue too. Run: `cargo run -p benilla-protocol --example horde_chat_probe -- probeN
|
||||
//! pprobeN [host]` — the slot-keyed probe account (docs/METHOD.md "The local vmangos server"; it
|
||||
//! once hardcoded the retired shared `three` identity, decision 0530). Creates the orc
|
||||
//! pprobeN [host]` — a probe account (the `probe` skill; it once hardcoded a shared identity,
|
||||
//! decision 0530). Creates the orc
|
||||
//! `Orc<N-spelled>` on this account on first run (names are realm-unique, so the orc keys to
|
||||
//! the slot too). Exists because a hardcoded-Common send silently ate every Horde character's
|
||||
//! chat and commands (decision 0392) — this is the one-shot regression check for that whole path.
|
||||
@@ -27,14 +27,12 @@ fn main() -> Result<()> {
|
||||
let mut args = std::env::args().skip(1);
|
||||
let user = args
|
||||
.next()
|
||||
.context("usage: horde_chat_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: horde_chat_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let pass = args
|
||||
.next()
|
||||
.context("usage: horde_chat_probe -- <probeN> <pprobeN> [host] (slot-keyed account)")?;
|
||||
.context("usage: horde_chat_probe -- <probeN> <pprobeN> [host] (a probe account)")?;
|
||||
let host = args.next().unwrap_or_else(|| "localhost".into());
|
||||
let orc = orc_name(&user).context(
|
||||
"account must be a slot-keyed probeN (docs/METHOD.md \"The local vmangos server\")",
|
||||
)?;
|
||||
let orc = orc_name(&user).context("account must be a probeN (the `probe` skill)")?;
|
||||
|
||||
let logon = benilla_protocol::logon(&host, &user, &pass)?;
|
||||
let addr = logon
|
||||
|
||||
@@ -219,8 +219,8 @@ struct Cli {
|
||||
/// `SMSG_UPDATE_WORLD_STATE` — what the NPC-text `$<n>w`/`$<n>e` tokens read): teleport to
|
||||
/// Elwynn, hop to Stormwind to force a zone-change init, and `.debug send worldstate` one
|
||||
/// synthetic pair to require back. Prints every state received. Needs a **SEC_DEVELOPER**
|
||||
/// account: `.debug send worldstate` is gmlevel 5, under the 6 the slot-keyed probe
|
||||
/// accounts carry (decision 0450), so the update leg needs a temporary grant.
|
||||
/// account: `.debug send worldstate` is gmlevel 5, under the 6 the probe accounts
|
||||
/// carry (decision 0450), so the update leg needs a temporary grant.
|
||||
#[arg(long)]
|
||||
worldstate: bool,
|
||||
|
||||
@@ -231,7 +231,7 @@ struct Cli {
|
||||
/// the same server spline machinery as any creature (not a teleport / not a knockback). Prints
|
||||
/// the self spline in full (facing kind, duration, waypoints, flying bit) so the ride + the
|
||||
/// `CMSG_MOVE_SPLINE_DONE` ack it obliges can be built from the real numbers. Needs a GM warrior
|
||||
/// (any slot-keyed probe account, gmlevel 6).
|
||||
/// (any probe account, gmlevel 6).
|
||||
#[arg(long)]
|
||||
charge: bool,
|
||||
|
||||
|
||||
@@ -125,7 +125,7 @@ impl Probe for Aura {
|
||||
"--aura: spell 1126 never appeared in UNIT_FIELD_AURA. Either the descriptor field \
|
||||
index is wrong, or the GM `.aura` command was refused — it needs gmlevel >= 4 \
|
||||
(VERIFIED vmangos `Chat/Chat.cpp:1229`: SEC_BASIC_ADMIN, which is 4 in \
|
||||
`shared/Common.h:142`), and the slot-keyed probe accounts are gmlevel 6, so this \
|
||||
`shared/Common.h:142`), and the probe accounts are gmlevel 6, so this \
|
||||
probe cannot run as `probeN` without a temporary grant (docs/METHOD.md, decision 0450's \
|
||||
precedent for --worldstate).",
|
||||
)?;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
//!
|
||||
//! **The update leg needs a temporary grant.** `.debug send worldstate` sits in
|
||||
//! `debugSendCommandTable` at **SEC_DEVELOPER = 5** (vmangos `Chat.cpp:323`) — one above the
|
||||
//! gmlevel 6 the slot-keyed probe accounts carry (decisions 0450/0651). Without it the server answers
|
||||
//! gmlevel 6 the probe accounts carry (decisions 0450/0651). Without it the server answers
|
||||
//! *"This command is not available to you."* in chat and sends no packet, and the probe fails on
|
||||
//! the update leg while the init leg still passes.
|
||||
//!
|
||||
|
||||
@@ -34,7 +34,7 @@ use anyhow::{anyhow, Context, Result};
|
||||
use benilla_srp::{NormalizedString, PublicKey, SrpClientChallenge, SESSION_KEY_LENGTH};
|
||||
|
||||
/// The realmd (auth/login) server port — the stock one a vmangos `realmd` listens on, which our
|
||||
/// deploy maps straight through (`3724:3724` in the compose file at `vmangos-deploy`).
|
||||
/// deploy maps straight through (`3724:3724` in a docker deploy's compose file).
|
||||
pub const AUTH_PORT: u16 = 3724;
|
||||
/// The 1.12.1 client build we present to the server.
|
||||
pub const CLIENT_BUILD: u16 = 5875;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! Provision a slot-keyed probe account for the local vmangos `realmd` DB (decision 0450: a
|
||||
//! session in `pool-N` probes as `probeN`/`pprobeN`/`Probe<N-spelled>`). Growing the worktree
|
||||
//! pool means provisioning the matching account first; this is that recipe as one command
|
||||
//! (decision 0530 — the pool once grew without it, leaving a slot with no working identity).
|
||||
//! Provision a probe account for the local vmangos `realmd` DB (decision 0450: a scripted run
|
||||
//! logs in as `probeN`/`pprobeN`/`Probe<N-spelled>`, one account per checkout that runs probes).
|
||||
//! A new checkout means provisioning its account first; this is that recipe as one command
|
||||
//! (decision 0530 — a checkout once went without it, and had no working identity).
|
||||
//!
|
||||
//! No DB access here — it prints the SQL and the char-create probe line; run those yourself.
|
||||
//!
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! 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};
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
//! reference's `0xbc6040` insertion-ordered cache (`benilla::ui_aura`), a cache position is simply a
|
||||
//! 0-based index into `model.auras["player"]`. No second structure exists, and none is needed.
|
||||
//!
|
||||
//! # Byte-verified signatures (`~/dev/wow-5875-re/WoW/WoW.exe`, `system/ui/ledger.tsv`)
|
||||
//! # Byte-verified signatures (the 1.12.1 binary, wow-5875-re `system/ui/ledger.tsv`)
|
||||
//!
|
||||
//! Read off the disassembly, not off a wiki — a later client changed every one of these shapes, and a
|
||||
//! wrong shape fails *silently*.
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
//!
|
||||
//! ## Why this exists
|
||||
//!
|
||||
//! 1.12's own FrameXML never touches the API — `grep -c FontObject` over
|
||||
//! `wow-5875-re/WoW/_extracted_framexml/*.lua` is **0** — so the whole surface is addon-facing, and
|
||||
//! the corpus (`/Users/sam/dev/wow-addons-vanilla`, 218 addons) is where the demand is:
|
||||
//! 1.12's own FrameXML never touches the API — `grep -c FontObject` over the reference
|
||||
//! FrameXML's own `.lua` is **0** — so the whole surface is addon-facing, and
|
||||
//! the addon corpus (`benilla_formats::addon_corpus`, 218 addons) is where the demand is:
|
||||
//!
|
||||
//! | call shape | sites | note |
|
||||
//! |---|---|---|
|
||||
|
||||
@@ -47,7 +47,7 @@ fn dispatch_bench() {
|
||||
// A plain Lua table with a table `__index` — the floor this path could reach, and what the
|
||||
// reference client's flat `{name, lua_CFunction}` probe costs in C.
|
||||
// **The MINIMUM of [`ROUNDS`] runs, never the mean.** This machine builds while it measures —
|
||||
// a neighbouring worktree's `cargo` saturates ten cores — and a mean of a contended loop reads
|
||||
// another build on the same machine saturates ten cores — and a mean of a contended loop reads
|
||||
// whatever else was running. The minimum is the closest thing to the uncontended cost that a
|
||||
// shared machine can report, and it is what makes two rows of one run comparable at all.
|
||||
let floor = |what: &str, chunk: &str| {
|
||||
|
||||
@@ -105,7 +105,7 @@ pub fn background_run() -> bool {
|
||||
/// `WOW_PROBE…`-named env that captures pixels has to be excluded by name here, since the bare
|
||||
/// `WOW_PROBE` prefix would otherwise sweep it in.
|
||||
const NO_PIXEL_ENV_PREFIXES: &[&str] = &[
|
||||
// Paired with its entry above, and it has to be BOTH or neither: `leg.sh`, `cine.sh` and
|
||||
// Paired with its entry above, and it has to be BOTH or neither: `cine.sh` and
|
||||
// `summon-live.sh` all set `WOW_UNATTENDED` beside a `WOW_PROBE*`/`WOW_RIG` that IS no-pixel,
|
||||
// so listing it only as a background env would make it the one non-no-pixel member of every
|
||||
// such run and the all-of rule would grow all three back to the full 1600x900 that decision
|
||||
|
||||
+27
-8
@@ -24,7 +24,8 @@ fork, and forks are welcome.
|
||||
## How a change is judged
|
||||
|
||||
1. `scripts/gates.sh` is green: fmt, clippy with warnings denied, the workspace tests, the
|
||||
player build.
|
||||
doc-link and render-pass lints, the player build with its own tests, and the engine boot
|
||||
checks.
|
||||
2. The reference fact is stated: what 1.12.1 does, and where that is known from (the client's
|
||||
behaviour you observed, a DBC field, a FrameXML line, a packet capture). The names and shapes
|
||||
under `reference/` are the surface benilla tracks.
|
||||
@@ -34,12 +35,30 @@ fork, and forks are welcome.
|
||||
|
||||
## Setting up
|
||||
|
||||
- A 1.12.1 install of your own: `WOW_DATA=<path>`, or a `WoW` link at the repo root. benilla
|
||||
reads it and never writes into it.
|
||||
- A server to test against: any 1.12.1 server. The project runs against a local vmangos
|
||||
(`WOW_HOST`, `WOW_USER`, `WOW_PASS`).
|
||||
- `cargo play` builds and runs the play profile. `scripts/check.sh` verifies a round of work;
|
||||
`scripts/gates.sh` is what a pull request must pass.
|
||||
- Bugs, questions and ideas go to the Discord linked from the README. Issues are off on purpose.
|
||||
- **The toolchain.** Stable Rust (`rust-toolchain.toml` adds clippy and rustfmt) and a C
|
||||
compiler, because the client's Lua is vendored and built from source. On macOS the Xcode
|
||||
command line tools, which also supply libclang for the audio bindings; on Linux the ALSA and
|
||||
udev development packages and pkg-config. `python3` runs two of the gates, and `jq` the hooks.
|
||||
- **A 1.12.1 install of your own.** `WOW_DATA=<its Data folder>`, or a `WoW` link at the repo
|
||||
root, which only a dev build sees: the player build looks for `Data/` or `WoW/Data/` beside
|
||||
the binary. benilla reads the install and never writes into it. `WOW_DATA=` (set, empty)
|
||||
means "no install", which is how the no-install boot is tested on a machine that has one.
|
||||
- **Without the install, green is hollow.** About a thousand tests read the install and skip
|
||||
when it is absent; six more read a corpus of vanilla addons (`BENILLA_ADDON_CORPUS=<a folder
|
||||
of addons>`, or a `wow-addons-vanilla` link at the root), third-party content that is not in
|
||||
this repo. `scripts/gates.sh` and `scripts/check.sh` print how many tests skipped and why.
|
||||
Where the data is, `BENILLA_REQUIRE_DATA=1` turns a skip into a failure, and the gates set it
|
||||
themselves when the install and the corpus both resolve.
|
||||
- **A server to test against.** Any 1.12.1 server; `WOW_HOST` names it (default
|
||||
`localhost:3724`). A scripted run has no default account: `WOW_USER`, `WOW_PASS` and
|
||||
`WOW_CHAR` name a test account on your server whose login kicks nobody, all three, either in
|
||||
the environment or in a `.probe-identity` file at the repo root (one per line, never
|
||||
committed), and `scripts/smoke.sh` (the live login gate) refuses without them. The probes
|
||||
drive the body with GM commands, so give that account the top GM level.
|
||||
- **The loop.** `cargo play` builds and runs the play profile. `scripts/check.sh` verifies a
|
||||
round of work; `scripts/gates.sh` is the full chain, and it opens a window for the engine boot
|
||||
checks, so it needs a display. Work on a branch.
|
||||
- Bugs, questions and ideas go to the Discord linked from the README. Issues are off on purpose,
|
||||
and pull requests are not open yet.
|
||||
|
||||
Working with an AI agent is expected. The agent reads `AGENTS.md`, and the same rules bind it.
|
||||
|
||||
+17
-37
@@ -36,8 +36,8 @@ comment naming the reference fact and why we differ. Anything else is a fork.
|
||||
|
||||
- **Never commit anything from the install.** Art, models, sounds, maps, data: the install is
|
||||
read at runtime from a gitignored path, and everyone provides their own. The one exception is
|
||||
interface code: FrameXML and GlueXML run off the player's own patch chain, and the copies
|
||||
already under `assets/ui` stay until they retire.
|
||||
interface code: FrameXML and GlueXML run off the player's own patch chain, and our own
|
||||
counterparts under `assets/ui` stay until they retire.
|
||||
- **The install is read-only.** benilla never writes into the WoW folder: no screenshot, log,
|
||||
cache or scratch file. A hook blocks such a write at the tool call, and `scripts/smoke.sh`
|
||||
fails a run that leaves the install changed.
|
||||
@@ -69,45 +69,31 @@ comment naming the reference fact and why we differ. Anything else is a fork.
|
||||
## Gates
|
||||
|
||||
- **The land gates**: `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets
|
||||
-- -D warnings`, `cargo test --workspace`, the player build without the `dev` feature and the
|
||||
engine boot checks, as `scripts/gates.sh`. `wt.sh land` runs it on the tree that lands; it is
|
||||
memoized on the tree.
|
||||
-- -D warnings`, `cargo test --workspace`, the doc-link and render-pass lints, the player
|
||||
build without the `dev` feature with its own tests, and the engine boot checks, as
|
||||
`scripts/gates.sh`. It runs on the tree that lands, before it lands; it is memoized on the
|
||||
tree. The tests that read the install or the addon corpus skip where the data is absent, and
|
||||
the gate prints the count; where the data is, a skip is a failure (`BENILLA_REQUIRE_DATA=1`).
|
||||
- **Per round**: `scripts/check.sh`, scoped to the changed crates and their dependents. Run each
|
||||
gate once per round; `tee` the output to a file and grep the file.
|
||||
- **A clean run** is the fourth gate: `scripts/smoke.sh`, a live login and logout against the
|
||||
local server. For wire work the gate is a live run with a trace (`WOW_MOVE_TRACE=<path>`,
|
||||
tags filtered), never a capture: capture mode has no network.
|
||||
- **Platform seams**: a `cfg(target_os)`, a `[target.'cfg(…)']` dependency, a `#[link]` or an
|
||||
`extern "system"` is invisible to the macOS gates. `scripts/crosscheck.sh` compiles Linux and
|
||||
Windows, and the land worker runs it when the diff touches a seam.
|
||||
- **Vulkan**: `scripts/vkleg.sh` runs the client on Vulkan in a container before a release,
|
||||
because Metal cannot ask what Vulkan validation refuses.
|
||||
- Hooks enforce only two things: no writes into the install, and the worktree rule on a machine
|
||||
with a pool. Everything else is a gate or discipline.
|
||||
|
||||
## Worktrees
|
||||
|
||||
On a machine with a pool, every session works in its own slot and `scripts/wt.sh` is the only
|
||||
way to get one. `claim <name>` at the start; `land <name>` at the end, which rebases,
|
||||
regenerates the map, runs the full gate chain, fast-forwards and pushes main and releases the
|
||||
slot, one land at a time; `release` to abandon; `status` to see the pool. Never the primary
|
||||
checkout, which belongs to no session and stays clean, and never the harness worktree tool or a
|
||||
hand-rolled `git worktree add`. Work against the slot by path: prefix Bash calls with
|
||||
`cd <slot> &&` and give edits absolute paths under it. No free slot means stop and ask which
|
||||
one to take over, never fall back to the primary. Hooks enforce all of this.
|
||||
|
||||
Sessions share the machine, so a kill is scoped to a PID, never to a command pattern:
|
||||
`pkill -f "cargo test"` takes out every neighbour's gates along with yours.
|
||||
|
||||
On a plain clone there is no pool: work on a branch and open a pull request.
|
||||
`extern "system"` is invisible to the gates of the platform you are on. Say in the commit
|
||||
which platforms you built.
|
||||
- One hook: no writes into the install. Everything else is a gate or discipline.
|
||||
|
||||
## The local server
|
||||
|
||||
benilla talks to a local vmangos (build 5875); `WOW_HOST`, `WOW_USER`, `WOW_PASS` and `WOW_CHAR`
|
||||
override the defaults. These bind every session and every agent:
|
||||
benilla talks to any 1.12.1 server: `WOW_HOST` names it (default `localhost`), `WOW_USER` and
|
||||
`WOW_PASS` together log in without typing, `WOW_CHAR` picks the character. There is no default
|
||||
account. These bind every session and every agent:
|
||||
|
||||
- A probe logs in as its own probe account (`probeN` for `pool-N`), never as a player's: a login
|
||||
kicks whoever holds the account.
|
||||
- A scripted run logs in as the account its checkout declares in `.probe-identity` (the three
|
||||
variables, one per line, never committed), or as the three variables name; never as a
|
||||
player's, because a login kicks whoever holds the account, and the client refuses a scripted
|
||||
login from a checkout on any account but the declared one.
|
||||
- Every unattended run says so: `WOW_UNATTENDED=1`. An agent's run is silent: `WOW_NOSOUND=1`.
|
||||
- No unattended combat probes.
|
||||
- Anything about hostility, reaction colour, nameplates, threat, damage or breath runs with
|
||||
@@ -115,9 +101,3 @@ override the defaults. These bind every session and every agent:
|
||||
- Read the preflight banner before debugging anything else.
|
||||
|
||||
The mechanics are the `probe` skill.
|
||||
|
||||
## Agents in a shared tree
|
||||
|
||||
Parallel agents share one working tree: whole-tree git ops (`stash`, `reset --hard`,
|
||||
`clean -f`, a pathless `checkout`) are forbidden, diagnostics use path-scoped commands, and an
|
||||
agent never gets a tree of its own.
|
||||
|
||||
@@ -0,0 +1,428 @@
|
||||
# FrameScript C->Lua event catalog — eventId -> name, extracted from WoW.exe 1.12.1.5875
|
||||
# source: the event-name pointer array at .data 0xbe1198 (stride 4, 0x225=549 slots), RE 2026-06-21,
|
||||
# BASE CORRECTED 2026-07-21 (was 0xbe11d8 -> every id was 16 too low; see
|
||||
# system/object-layer/scratch/lootroll-chat-and-lifecycle.md §3). The base is VERIFIED at
|
||||
# 0x48fea1-0x48feab: `mov edx,0x225; mov ecx,0xbe1198; call 0x703d90`, and 0x703d90 builds the
|
||||
# runtime 16-byte-stride dispatch table [0xceef68] as table[i].name = strdup(nameArray[i])
|
||||
# (0x703e06-0x703e2e), so the id passed to FrameScript_SignalEvent 0x703e50 / 0x703f50 IS this index.
|
||||
# NB slots 0..15 carry no `mov imm32` name write in this scan (they are set from a register at
|
||||
# 0x51acbf and neighbours); coverage is unchanged from the pre-correction extraction (383 rows).
|
||||
0 UNIT_PET
|
||||
2 UNIT_PET
|
||||
16 UNIT_HEALTH
|
||||
17 UNIT_MANA
|
||||
18 UNIT_RAGE
|
||||
19 UNIT_FOCUS
|
||||
20 UNIT_ENERGY
|
||||
21 UNIT_HAPPINESS
|
||||
22 UNIT_MAXHEALTH
|
||||
23 UNIT_MAXMANA
|
||||
24 UNIT_MAXRAGE
|
||||
25 UNIT_MAXFOCUS
|
||||
26 UNIT_MAXENERGY
|
||||
27 UNIT_MAXHAPPINESS
|
||||
28 UNIT_LEVEL
|
||||
29 UNIT_FACTION
|
||||
30 UNIT_DISPLAYPOWER
|
||||
40 UNIT_FLAGS
|
||||
41 UNIT_AURA
|
||||
107 UNIT_AURA
|
||||
120 UNIT_ATTACK_SPEED
|
||||
121 UNIT_ATTACK_SPEED
|
||||
122 UNIT_RANGEDDAMAGE
|
||||
128 UNIT_DAMAGE
|
||||
129 UNIT_DAMAGE
|
||||
130 UNIT_DAMAGE
|
||||
131 UNIT_DAMAGE
|
||||
132 UNIT_LOYALTY
|
||||
135 UNIT_PET_EXPERIENCE
|
||||
136 UNIT_PET_EXPERIENCE
|
||||
137 UNIT_DYNAMIC_FLAGS
|
||||
143 UNIT_PET_TRAINING_POINTS
|
||||
144 UNIT_STATS
|
||||
145 UNIT_STATS
|
||||
146 UNIT_STATS
|
||||
147 UNIT_STATS
|
||||
148 UNIT_STATS
|
||||
149 UNIT_RESISTANCES
|
||||
150 UNIT_RESISTANCES
|
||||
151 UNIT_RESISTANCES
|
||||
152 UNIT_RESISTANCES
|
||||
153 UNIT_RESISTANCES
|
||||
154 UNIT_RESISTANCES
|
||||
155 UNIT_RESISTANCES
|
||||
159 UNIT_ATTACK_POWER
|
||||
160 UNIT_ATTACK_POWER
|
||||
161 UNIT_ATTACK_POWER
|
||||
162 UNIT_RANGED_ATTACK_POWER
|
||||
163 UNIT_RANGED_ATTACK_POWER
|
||||
164 UNIT_RANGED_ATTACK_POWER
|
||||
165 UNIT_RANGEDDAMAGE
|
||||
166 UNIT_RANGEDDAMAGE
|
||||
167 UNIT_MANA
|
||||
174 UNIT_MANA
|
||||
182 UNIT_COMBAT
|
||||
183 UNIT_NAME_UPDATE
|
||||
184 UNIT_PORTRAIT_UPDATE
|
||||
185 UNIT_MODEL_CHANGED
|
||||
186 UNIT_INVENTORY_CHANGED
|
||||
187 UNIT_CLASSIFICATION_CHANGED
|
||||
188 ITEM_LOCK_CHANGED
|
||||
189 PLAYER_XP_UPDATE
|
||||
190 PLAYER_REGEN_DISABLED
|
||||
191 PLAYER_REGEN_ENABLED
|
||||
192 PLAYER_AURAS_CHANGED
|
||||
193 PLAYER_ENTER_COMBAT
|
||||
194 PLAYER_LEAVE_COMBAT
|
||||
195 PLAYER_TARGET_CHANGED
|
||||
196 PLAYER_CONTROL_LOST
|
||||
197 PLAYER_CONTROL_GAINED
|
||||
198 PLAYER_FARSIGHT_FOCUS_CHANGED
|
||||
199 PLAYER_LEVEL_UP
|
||||
200 PLAYER_MONEY
|
||||
201 PLAYER_DAMAGE_DONE_MODS
|
||||
202 PLAYER_COMBO_POINTS
|
||||
203 ZONE_CHANGED
|
||||
204 ZONE_CHANGED_INDOORS
|
||||
205 ZONE_CHANGED_NEW_AREA
|
||||
206 MINIMAP_ZONE_CHANGED
|
||||
207 MINIMAP_UPDATE_ZOOM
|
||||
208 SCREENSHOT_SUCCEEDED
|
||||
209 SCREENSHOT_FAILED
|
||||
210 ACTIONBAR_SHOWGRID
|
||||
211 ACTIONBAR_HIDEGRID
|
||||
212 ACTIONBAR_PAGE_CHANGED
|
||||
213 ACTIONBAR_SLOT_CHANGED
|
||||
214 ACTIONBAR_UPDATE_STATE
|
||||
215 ACTIONBAR_UPDATE_USABLE
|
||||
216 ACTIONBAR_UPDATE_COOLDOWN
|
||||
217 UPDATE_BONUS_ACTIONBAR
|
||||
218 PARTY_MEMBERS_CHANGED
|
||||
219 PARTY_LEADER_CHANGED
|
||||
220 PARTY_MEMBER_ENABLE
|
||||
221 PARTY_MEMBER_DISABLE
|
||||
222 PARTY_LOOT_METHOD_CHANGED
|
||||
223 SYSMSG
|
||||
224 UI_ERROR_MESSAGE
|
||||
225 UI_INFO_MESSAGE
|
||||
226 UPDATE_CHAT_COLOR
|
||||
227 CHAT_MSG_ADDON
|
||||
228 CHAT_MSG_SAY
|
||||
229 CHAT_MSG_PARTY
|
||||
230 CHAT_MSG_RAID
|
||||
231 CHAT_MSG_GUILD
|
||||
232 CHAT_MSG_OFFICER
|
||||
233 CHAT_MSG_YELL
|
||||
234 CHAT_MSG_WHISPER
|
||||
235 CHAT_MSG_WHISPER_INFORM
|
||||
236 CHAT_MSG_EMOTE
|
||||
237 CHAT_MSG_TEXT_EMOTE
|
||||
238 CHAT_MSG_SYSTEM
|
||||
239 CHAT_MSG_MONSTER_SAY
|
||||
240 CHAT_MSG_MONSTER_YELL
|
||||
241 CHAT_MSG_MONSTER_WHISPER
|
||||
242 CHAT_MSG_MONSTER_EMOTE
|
||||
243 CHAT_MSG_CHANNEL
|
||||
244 CHAT_MSG_CHANNEL_JOIN
|
||||
245 CHAT_MSG_CHANNEL_LEAVE
|
||||
246 CHAT_MSG_CHANNEL_LIST
|
||||
247 CHAT_MSG_CHANNEL_NOTICE
|
||||
248 CHAT_MSG_CHANNEL_NOTICE_USER
|
||||
249 CHAT_MSG_AFK
|
||||
250 CHAT_MSG_DND
|
||||
251 CHAT_MSG_COMBAT_LOG
|
||||
252 CHAT_MSG_IGNORED
|
||||
253 CHAT_MSG_SKILL
|
||||
254 CHAT_MSG_LOOT
|
||||
255 CHAT_MSG_MONEY
|
||||
256 CHAT_MSG_RAID_LEADER
|
||||
257 CHAT_MSG_RAID_WARNING
|
||||
258 LANGUAGE_LIST_CHANGED
|
||||
259 TIME_PLAYED_MSG
|
||||
260 SPELLS_CHANGED
|
||||
261 CURRENT_SPELL_CAST_CHANGED
|
||||
262 SPELL_UPDATE_COOLDOWN
|
||||
263 SPELL_UPDATE_USABLE
|
||||
264 CHARACTER_POINTS_CHANGED
|
||||
265 SKILL_LINES_CHANGED
|
||||
266 ITEM_PUSH
|
||||
267 LOOT_OPENED
|
||||
268 LOOT_SLOT_CLEARED
|
||||
269 LOOT_CLOSED
|
||||
270 PLAYER_LOGIN
|
||||
271 PLAYER_LOGOUT
|
||||
272 PLAYER_ENTERING_WORLD
|
||||
273 PLAYER_LEAVING_WORLD
|
||||
274 PLAYER_ALIVE
|
||||
275 PLAYER_DEAD
|
||||
276 PLAYER_CAMPING
|
||||
277 PLAYER_QUITING
|
||||
278 LOGOUT_CANCEL
|
||||
279 RESURRECT_REQUEST
|
||||
280 PARTY_INVITE_REQUEST
|
||||
281 PARTY_INVITE_CANCEL
|
||||
282 GUILD_INVITE_REQUEST
|
||||
283 GUILD_INVITE_CANCEL
|
||||
284 GUILD_MOTD
|
||||
285 TRADE_REQUEST
|
||||
286 TRADE_REQUEST_CANCEL
|
||||
287 LOOT_BIND_CONFIRM
|
||||
288 EQUIP_BIND_CONFIRM
|
||||
289 AUTOEQUIP_BIND_CONFIRM
|
||||
290 USE_BIND_CONFIRM
|
||||
291 DELETE_ITEM_CONFIRM
|
||||
292 CURSOR_UPDATE
|
||||
293 ITEM_TEXT_BEGIN
|
||||
294 ITEM_TEXT_TRANSLATION
|
||||
295 ITEM_TEXT_READY
|
||||
296 ITEM_TEXT_CLOSED
|
||||
297 GOSSIP_SHOW
|
||||
298 GOSSIP_ENTER_CODE
|
||||
299 GOSSIP_CLOSED
|
||||
300 QUEST_GREETING
|
||||
301 QUEST_DETAIL
|
||||
302 QUEST_PROGRESS
|
||||
303 QUEST_COMPLETE
|
||||
304 QUEST_FINISHED
|
||||
305 QUEST_ITEM_UPDATE
|
||||
306 TAXIMAP_OPENED
|
||||
307 TAXIMAP_CLOSED
|
||||
308 QUEST_LOG_UPDATE
|
||||
309 TRAINER_SHOW
|
||||
310 TRAINER_UPDATE
|
||||
311 TRAINER_CLOSED
|
||||
312 CVAR_UPDATE
|
||||
313 TRADE_SKILL_SHOW
|
||||
314 TRADE_SKILL_UPDATE
|
||||
315 TRADE_SKILL_CLOSE
|
||||
316 MERCHANT_SHOW
|
||||
317 MERCHANT_UPDATE
|
||||
318 MERCHANT_CLOSED
|
||||
319 TRADE_SHOW
|
||||
320 TRADE_CLOSED
|
||||
321 TRADE_UPDATE
|
||||
322 TRADE_ACCEPT_UPDATE
|
||||
323 TRADE_TARGET_ITEM_CHANGED
|
||||
324 TRADE_PLAYER_ITEM_CHANGED
|
||||
325 TRADE_MONEY_CHANGED
|
||||
326 PLAYER_TRADE_MONEY
|
||||
327 BAG_OPEN
|
||||
328 BAG_UPDATE
|
||||
329 BAG_CLOSED
|
||||
330 BAG_UPDATE_COOLDOWN
|
||||
331 LOCALPLAYER_PET_RENAMED
|
||||
332 UNIT_ATTACK
|
||||
333 UNIT_DEFENSE
|
||||
334 PET_ATTACK_START
|
||||
335 PET_ATTACK_STOP
|
||||
336 UPDATE_MOUSEOVER_UNIT
|
||||
337 SPELLCAST_START
|
||||
338 SPELLCAST_STOP
|
||||
339 SPELLCAST_FAILED
|
||||
340 SPELLCAST_INTERRUPTED
|
||||
341 SPELLCAST_DELAYED
|
||||
342 SPELLCAST_CHANNEL_START
|
||||
343 SPELLCAST_CHANNEL_UPDATE
|
||||
344 SPELLCAST_CHANNEL_STOP
|
||||
345 PLAYER_GUILD_UPDATE
|
||||
346 QUEST_ACCEPT_CONFIRM
|
||||
347 PLAYERBANKSLOTS_CHANGED
|
||||
348 BANKFRAME_OPENED
|
||||
349 BANKFRAME_CLOSED
|
||||
350 PLAYERBANKBAGSLOTS_CHANGED
|
||||
351 FRIENDLIST_UPDATE
|
||||
352 IGNORELIST_UPDATE
|
||||
353 PET_BAR_UPDATE
|
||||
354 PET_BAR_UPDATE_COOLDOWN
|
||||
355 PET_BAR_SHOWGRID
|
||||
356 PET_BAR_HIDEGRID
|
||||
357 MINIMAP_PING
|
||||
358 CHAT_MSG_COMBAT_MISC_INFO
|
||||
359 CRAFT_SHOW
|
||||
360 CRAFT_UPDATE
|
||||
361 CRAFT_CLOSE
|
||||
362 MIRROR_TIMER_START
|
||||
363 MIRROR_TIMER_PAUSE
|
||||
364 MIRROR_TIMER_STOP
|
||||
365 WORLD_MAP_UPDATE
|
||||
366 WORLD_MAP_NAME_UPDATE
|
||||
367 AUTOFOLLOW_BEGIN
|
||||
368 AUTOFOLLOW_END
|
||||
370 CINEMATIC_START
|
||||
371 CINEMATIC_STOP
|
||||
372 UPDATE_FACTION
|
||||
373 CLOSE_WORLD_MAP
|
||||
374 OPEN_TABARD_FRAME
|
||||
375 CLOSE_TABARD_FRAME
|
||||
376 TABARD_CANSAVE_CHANGED
|
||||
377 SHOW_COMPARE_TOOLTIP
|
||||
378 GUILD_REGISTRAR_SHOW
|
||||
379 GUILD_REGISTRAR_CLOSED
|
||||
380 DUEL_REQUESTED
|
||||
381 DUEL_OUTOFBOUNDS
|
||||
382 DUEL_INBOUNDS
|
||||
383 DUEL_FINISHED
|
||||
384 TUTORIAL_TRIGGER
|
||||
385 PET_DISMISS_START
|
||||
386 UPDATE_BINDINGS
|
||||
387 UPDATE_SHAPESHIFT_FORMS
|
||||
388 WHO_LIST_UPDATE
|
||||
389 UPDATE_LFG
|
||||
390 PETITION_SHOW
|
||||
391 PETITION_CLOSED
|
||||
392 EXECUTE_CHAT_LINE
|
||||
393 UPDATE_MACROS
|
||||
394 UPDATE_TICKET
|
||||
395 UPDATE_CHAT_WINDOWS
|
||||
396 CONFIRM_XP_LOSS
|
||||
397 CORPSE_IN_RANGE
|
||||
398 CORPSE_IN_INSTANCE
|
||||
399 CORPSE_OUT_OF_RANGE
|
||||
400 UPDATE_GM_STATUS
|
||||
401 PLAYER_UNGHOST
|
||||
402 BIND_ENCHANT
|
||||
403 REPLACE_ENCHANT
|
||||
404 TRADE_REPLACE_ENCHANT
|
||||
405 PLAYER_UPDATE_RESTING
|
||||
406 UPDATE_EXHAUSTION
|
||||
407 PLAYER_FLAGS_CHANGED
|
||||
408 GUILD_ROSTER_UPDATE
|
||||
409 GM_PLAYER_INFO
|
||||
410 MAIL_SHOW
|
||||
411 MAIL_CLOSED
|
||||
412 SEND_MAIL_MONEY_CHANGED
|
||||
413 SEND_MAIL_COD_CHANGED
|
||||
414 MAIL_SEND_INFO_UPDATE
|
||||
415 MAIL_SEND_SUCCESS
|
||||
416 MAIL_INBOX_UPDATE
|
||||
417 BATTLEFIELDS_SHOW
|
||||
418 BATTLEFIELDS_CLOSED
|
||||
419 UPDATE_BATTLEFIELD_STATUS
|
||||
420 UPDATE_BATTLEFIELD_SCORE
|
||||
421 AUCTION_HOUSE_SHOW
|
||||
422 AUCTION_HOUSE_CLOSED
|
||||
423 NEW_AUCTION_UPDATE
|
||||
424 AUCTION_ITEM_LIST_UPDATE
|
||||
425 AUCTION_OWNED_LIST_UPDATE
|
||||
426 AUCTION_BIDDER_LIST_UPDATE
|
||||
427 PET_UI_UPDATE
|
||||
428 PET_UI_CLOSE
|
||||
429 ADDON_LOADED
|
||||
430 VARIABLES_LOADED
|
||||
431 MACRO_ACTION_FORBIDDEN
|
||||
432 ADDON_ACTION_FORBIDDEN
|
||||
433 MEMORY_EXHAUSTED
|
||||
434 MEMORY_RECOVERED
|
||||
435 START_AUTOREPEAT_SPELL
|
||||
436 STOP_AUTOREPEAT_SPELL
|
||||
437 PET_STABLE_SHOW
|
||||
438 PET_STABLE_UPDATE
|
||||
439 PET_STABLE_UPDATE_PAPERDOLL
|
||||
440 PET_STABLE_CLOSED
|
||||
441 CHAT_MSG_COMBAT_SELF_HITS
|
||||
442 CHAT_MSG_COMBAT_SELF_MISSES
|
||||
443 CHAT_MSG_COMBAT_PET_HITS
|
||||
444 CHAT_MSG_COMBAT_PET_MISSES
|
||||
445 CHAT_MSG_COMBAT_PARTY_HITS
|
||||
446 CHAT_MSG_COMBAT_PARTY_MISSES
|
||||
447 CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS
|
||||
448 CHAT_MSG_COMBAT_FRIENDLYPLAYER_MISSES
|
||||
449 CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS
|
||||
450 CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES
|
||||
451 CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS
|
||||
452 CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES
|
||||
453 CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS
|
||||
454 CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES
|
||||
455 CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_HITS
|
||||
456 CHAT_MSG_COMBAT_CREATURE_VS_CREATURE_MISSES
|
||||
457 CHAT_MSG_COMBAT_FRIENDLY_DEATH
|
||||
458 CHAT_MSG_COMBAT_HOSTILE_DEATH
|
||||
459 CHAT_MSG_COMBAT_XP_GAIN
|
||||
460 CHAT_MSG_COMBAT_HONOR_GAIN
|
||||
461 CHAT_MSG_SPELL_SELF_DAMAGE
|
||||
462 CHAT_MSG_SPELL_SELF_BUFF
|
||||
463 CHAT_MSG_SPELL_PET_DAMAGE
|
||||
464 CHAT_MSG_SPELL_PET_BUFF
|
||||
465 CHAT_MSG_SPELL_PARTY_DAMAGE
|
||||
466 CHAT_MSG_SPELL_PARTY_BUFF
|
||||
467 CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE
|
||||
468 CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF
|
||||
469 CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE
|
||||
470 CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF
|
||||
471 CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE
|
||||
472 CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF
|
||||
473 CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE
|
||||
474 CHAT_MSG_SPELL_CREATURE_VS_PARTY_BUFF
|
||||
475 CHAT_MSG_SPELL_CREATURE_VS_CREATURE_DAMAGE
|
||||
476 CHAT_MSG_SPELL_CREATURE_VS_CREATURE_BUFF
|
||||
477 CHAT_MSG_SPELL_TRADESKILLS
|
||||
478 CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF
|
||||
479 CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS
|
||||
480 CHAT_MSG_SPELL_AURA_GONE_SELF
|
||||
481 CHAT_MSG_SPELL_AURA_GONE_PARTY
|
||||
482 CHAT_MSG_SPELL_AURA_GONE_OTHER
|
||||
483 CHAT_MSG_SPELL_ITEM_ENCHANTMENTS
|
||||
484 CHAT_MSG_SPELL_BREAK_AURA
|
||||
485 CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE
|
||||
486 CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS
|
||||
487 CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE
|
||||
488 CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS
|
||||
489 CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE
|
||||
490 CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS
|
||||
491 CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE
|
||||
492 CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS
|
||||
493 CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE
|
||||
494 CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS
|
||||
495 CHAT_MSG_SPELL_FAILED_LOCALPLAYER
|
||||
496 CHAT_MSG_BG_SYSTEM_NEUTRAL
|
||||
497 CHAT_MSG_BG_SYSTEM_ALLIANCE
|
||||
498 CHAT_MSG_BG_SYSTEM_HORDE
|
||||
499 RAID_ROSTER_UPDATE
|
||||
500 UPDATE_PENDING_MAIL
|
||||
501 UPDATE_INVENTORY_ALERTS
|
||||
502 UPDATE_TRADESKILL_RECAST
|
||||
503 OPEN_MASTER_LOOT_LIST
|
||||
504 UPDATE_MASTER_LOOT_LIST
|
||||
505 START_LOOT_ROLL
|
||||
506 CANCEL_LOOT_ROLL
|
||||
507 CONFIRM_LOOT_ROLL
|
||||
508 INSTANCE_BOOT_START
|
||||
509 INSTANCE_BOOT_STOP
|
||||
510 LEARNED_SPELL_IN_TAB
|
||||
511 DISPLAY_SIZE_CHANGED
|
||||
512 CONFIRM_TALENT_WIPE
|
||||
513 CONFIRM_BINDER
|
||||
514 MAIL_FAILED
|
||||
515 CLOSE_INBOX_ITEM
|
||||
516 CONFIRM_SUMMON
|
||||
517 BILLING_NAG_DIALOG
|
||||
518 IGR_BILLING_NAG_DIALOG
|
||||
519 MEETINGSTONE_CHANGED
|
||||
520 PLAYER_SKINNED
|
||||
521 TABARD_SAVE_PENDING
|
||||
522 UNIT_QUEST_LOG_CHANGED
|
||||
523 PLAYER_PVP_KILLS_CHANGED
|
||||
524 PLAYER_PVP_RANK_CHANGED
|
||||
525 INSPECT_HONOR_UPDATE
|
||||
526 UPDATE_WORLD_STATES
|
||||
527 AREA_SPIRIT_HEALER_IN_RANGE
|
||||
528 AREA_SPIRIT_HEALER_OUT_OF_RANGE
|
||||
529 CONFIRM_PET_UNLEARN
|
||||
530 PLAYTIME_CHANGED
|
||||
531 UPDATE_LFG_TYPES
|
||||
532 UPDATE_LFG_LIST
|
||||
533 CHAT_MSG_COMBAT_FACTION_CHANGE
|
||||
534 START_MINIGAME
|
||||
535 MINIGAME_UPDATE
|
||||
536 READY_CHECK
|
||||
537 RAID_TARGET_UPDATE
|
||||
538 GMSURVEY_DISPLAY
|
||||
539 UPDATE_INSTANCE_INFO
|
||||
541 CHAT_MSG_RAID_BOSS_EMOTE
|
||||
542 COMBAT_TEXT_UPDATE
|
||||
543 LOTTERY_SHOW
|
||||
544 CHAT_MSG_FILTERED
|
||||
545 QUEST_WATCH_UPDATE
|
||||
546 CHAT_MSG_BATTLEGROUND
|
||||
547 CHAT_MSG_BATTLEGROUND_LEADER
|
||||
548 LOTTERY_ITEM_UPDATE
|
||||
|
@@ -0,0 +1,533 @@
|
||||
# C->Lua event fire sites — every call AND TAIL-JMP to FrameScript_SignalEvent (0x703e50, ecx=eventId)
|
||||
# (the 85 `jmp 0x703e50` sites were absent until 2026-08-21 — the census had been written over `call`
|
||||
# alone, so a tail-jmp fire read as "this event has no fire site here"; see re/audit/re-sweep-status.md)
|
||||
# and SignalEvent2 (0x703f50, [esp]=eventId + printf-style arg_format). eventId resolved via event-catalog.tsv.
|
||||
# eventId "dyn" = computed (e.g. a UnitField update loop); blank name = eventId has no entry in the name table.
|
||||
#
|
||||
# CAVEAT (2026-09-04, staticpopup round): the `arg_format` column is the FORMAT STRING the site pushes,
|
||||
# which is a declaration, NOT a census of what the caller actually pushed. The marshaller 0x704f10
|
||||
# consumes one vararg per directive (%f eight bytes, everything else four), so a site whose format
|
||||
# declares more than its caller pushed hands Lua undefined values. `re/audit/signalevent2-fmt-args.py`
|
||||
# audits all 149 sites (0 unadjudicated) and finds exactly THREE such sites -- read that column with
|
||||
# these in mind: 0x496230 TRADE_REPLACE_ENCHANT "%s%s%d" declares 3 and pushes 2; 0x5e4527 and
|
||||
# 0x5e7960 UPDATE_TICKET "%d%s%f%f%f%d%d" declare 10 dwords and push 7 (their sibling 0x5e45e2 pushes
|
||||
# the full 12). Every other site's format matches its caller. See method.md §6.
|
||||
#
|
||||
# CAVEAT (2026-09-03, found while carving `UpdateSpells` for benilla; the answer was already on `main`
|
||||
# in system/glue/): the `name` column resolves every id through event-catalog.tsv, which is the
|
||||
# IN-GAME table (0xbe1198, 549 entries). 0x703d90 is called TWICE with different populations --
|
||||
# 0x46a88f with (0xb41e70, 26) for the GLUE phase and 0x48feab with (0xbe1198, 549) for the world --
|
||||
# and it rebuilds [0xceef68] wholesale from whichever it is handed, so THE SAME INTEGER NAMES A
|
||||
# DIFFERENT EVENT IN THE TWO PHASES and 0x703e50 bounds-checks nothing. 22 rows here sit in a
|
||||
# `Source\Glue\` TU (0x46aa34; 0x46bc9d-0x46c52e inside 0x46b930; 0x46e73d; 0x47461e) and their ids
|
||||
# belong to the GLUE space: name them from system/glue/scratch/glue-event-name-map.md, never from
|
||||
# event-catalog.tsv. 0x46aa34 was the one row where that produced a WRONG name (id 22 read as
|
||||
# UNIT_MAXHEALTH; it is FRAMES_LOADED -- glue-event-name-map.md's own control, with GlueParent.lua:94
|
||||
# as the independent oracle); it is blanked below. The other 21 were already blank, because those ids
|
||||
# are NULL slots in the in-game table. Owed: the glue node names them in its own artifact.
|
||||
#
|
||||
# CAVEAT 2 (2026-09-03, carving UNIT_FLAGS for benilla): AN EVENT WITH NO ROW HERE IS NOT AN EVENT
|
||||
# WITH NO PRODUCER. This census is over the two signal helpers' own call/jmp sites, and the whole
|
||||
# per-field UNIT_* family reaches them through a WRAPPER: 0x515e50(&guid, id) fans out over the unit
|
||||
# tokens and fires 0x703f50(id, "%s", token) at 0x515e7a -- the "?" row below. Its 38 callers carry
|
||||
# the id in edx, and one of them, 0x51bd5b in the UpdateField watch callback 0x51bd50, computes it as
|
||||
# (registered field byte offset >> 2). So ids 0x00-0xb5 -- every named unit descriptor field,
|
||||
# UNIT_FLAGS 40 included -- are produced there and appear nowhere in this file.
|
||||
# SCOPE WIDENED 2026-09-07 (PLAYER_FLAGS round, benilla dispatch): "0x00-0xb5" is the COMPUTED-id
|
||||
# subset only, and reading it as the whole absent set is a trap -- most 0x515e50 sites carry a
|
||||
# LITERAL id in edx, and those are absent from this file too, at any magnitude. The literal set is
|
||||
# {0x10 0x16 0x1c 0x1d 0x1e 0x29 0xb7 0xb8 0xb9 0xba 0xbb 0x159 0x197 0x20a}. Notably 0x197 = 407
|
||||
# PLAYER_FLAGS_CHANGED (0x5eea35 mov edx,0x197 / 0x5eea3d call 0x515e50, inside the PLAYER_FLAGS
|
||||
# delta handler 0x5ee990) sits OUTSIDE 0x00-0xb5, so a reader checking 407 against the range above
|
||||
# would wrongly conclude it has no producer. It has one; it is simply not a SignalEvent call site.
|
||||
# See system/object-layer/scratch/player-flags-delta-arms.md. Mechanism, the
|
||||
# bounded four-way census that makes 0x51bd50 the SOLE producer of 40, and the fire condition
|
||||
# (a repe cmpsb of the watched span against the object's shadow, at 0x4655bb) are in
|
||||
# system/ui/scratch/unit-field-event-bridge.md. Same shape for 0x5de4b0 (id = its userdata,
|
||||
# arg1 hardcoded "player") and 0x49b199 (id from the table at 0x8062e0).
|
||||
fire_site_va dispatcher eventId name arg_format
|
||||
0x4018df SignalEvent2 259 TIME_PLAYED_MSG %d%d
|
||||
0x46aa34 SignalEvent 22
|
||||
0x46b04d SignalEvent2 3 %s
|
||||
0x46b0ac SignalEvent2 3 %s%s
|
||||
0x46b0d2 SignalEvent2 3 %s
|
||||
0x46b1a4 SignalEvent2 3 %s%s
|
||||
0x46b1e7 SignalEvent2 3 %s%s
|
||||
0x46b26c SignalEvent2 3 %s%s
|
||||
0x46b2c4 SignalEvent2 3 %s%s
|
||||
0x46b31e SignalEvent2 3 %s%s
|
||||
0x46b388 SignalEvent2 23 UNIT_MAXMANA %s
|
||||
0x46b3c0 SignalEvent2 3 %s%s
|
||||
0x46b48b SignalEvent 5
|
||||
0x46b4a8 SignalEvent2 23 UNIT_MAXMANA %s
|
||||
0x46b4cd SignalEvent2 23 UNIT_MAXMANA %s
|
||||
0x46b5a5 SignalEvent2 3 %s%s
|
||||
0x46b5c2 SignalEvent2 23 UNIT_MAXMANA %s
|
||||
0x46b848 SignalEvent2 0 UNIT_PET %s
|
||||
0x46b8af SignalEvent2 3 %s%s
|
||||
0x46b8fa SignalEvent2 3 %s%s
|
||||
0x46b964 SignalEvent2 21 UNIT_HAPPINESS %s
|
||||
0x46bac8 SignalEvent 5
|
||||
0x46bb15 SignalEvent2 25 UNIT_MAXFOCUS %d%d%d%d%d%d%d%d%d%d
|
||||
0x46bc9d SignalEvent2 3 %s%s
|
||||
0x46bdfc SignalEvent2 4 %s%s
|
||||
0x46be81 SignalEvent2 4 %s%s
|
||||
0x46be96 SignalEvent2 4 %s
|
||||
0x46bee7 SignalEvent2 3 %s%s
|
||||
0x46bf6f SignalEvent2 3 %s%s%s
|
||||
0x46bf91 SignalEvent 5
|
||||
0x46c04a SignalEvent2 4 %s
|
||||
0x46c08f SignalEvent 5
|
||||
0x46c0b4 SignalEvent2 ? %s
|
||||
0x46c0dd SignalEvent 5
|
||||
0x46c103 SignalEvent2 4 %s
|
||||
0x46c129 SignalEvent 5
|
||||
0x46c133 SignalEvent 12
|
||||
0x46c15e SignalEvent2 4 %s
|
||||
0x46c183 SignalEvent2 3 %s%s
|
||||
0x46c1a2 SignalEvent 13
|
||||
0x46c298 SignalEvent2 3 %s%s
|
||||
0x46c52e SignalEvent 15
|
||||
0x46c5b0 SignalEvent2 3 %s%s
|
||||
0x46c62e SignalEvent 2 UNIT_PET
|
||||
0x46c68f SignalEvent2 3 %s%s
|
||||
0x46e73d SignalEvent 9
|
||||
0x46e9cb SignalEvent 9
|
||||
0x46ea1a SignalEvent 9
|
||||
0x46eb44 SignalEvent2 18 UNIT_RAGE %d%d
|
||||
0x46ec26 SignalEvent 9
|
||||
0x46ec42 SignalEvent2 18 UNIT_RAGE %d%d
|
||||
0x47120c SignalEvent2 3 %s%s
|
||||
0x47256d SignalEvent 7
|
||||
0x4725e1 SignalEvent 7
|
||||
0x472732 SignalEvent 7
|
||||
0x47276c SignalEvent2 8 %d
|
||||
0x4735d2 SignalEvent2 23 UNIT_MAXMANA %s
|
||||
0x47461e SignalEvent 5
|
||||
0x474b39 SignalEvent2 3 %s%s
|
||||
0x474b94 SignalEvent 17 UNIT_MANA
|
||||
0x474c81 SignalEvent 16 UNIT_HEALTH
|
||||
0x474cb3 SignalEvent2 3 %s%s
|
||||
0x488cd5 SignalEvent2 312 CVAR_UPDATE %s%s
|
||||
0x48edda SignalEvent 370 CINEMATIC_START
|
||||
0x48f095 SignalEvent 371 CINEMATIC_STOP
|
||||
0x48f3eb SignalEvent2 279 RESURRECT_REQUEST %s
|
||||
0x48f412 SignalEvent2 520 PLAYER_SKINNED %d
|
||||
0x48f42b SignalEvent2 280 PARTY_INVITE_REQUEST %s
|
||||
0x48f445 SignalEvent 281 PARTY_INVITE_CANCEL
|
||||
0x48f45c SignalEvent2 282 GUILD_INVITE_REQUEST %s%s
|
||||
0x48f475 SignalEvent 283 GUILD_INVITE_CANCEL
|
||||
0x48fa17 SignalEvent 526 UPDATE_WORLD_STATES
|
||||
0x490959 SignalEvent 270 PLAYER_LOGIN
|
||||
0x49096a SignalEvent 272 PLAYER_ENTERING_WORLD
|
||||
0x490b4d SignalEvent 273 PLAYER_LEAVING_WORLD
|
||||
0x490b77 SignalEvent 371 CINEMATIC_STOP
|
||||
0x490c2a SignalEvent 271 PLAYER_LOGOUT
|
||||
0x49156a SignalEvent 430 VARIABLES_LOADED
|
||||
0x491be8 SignalEvent2 266 ITEM_PUSH %d%s
|
||||
0x49211a SignalEvent dyn
|
||||
0x492124 SignalEvent 399 CORPSE_OUT_OF_RANGE
|
||||
0x4922d1 SignalEvent 527 AREA_SPIRIT_HEALER_IN_RANGE
|
||||
0x4929b3 SignalEvent 336 UPDATE_MOUSEOVER_UNIT
|
||||
0x492f5c SignalEvent 511 DISPLAY_SIZE_CHANGED
|
||||
0x4930c7 SignalEvent 209 SCREENSHOT_FAILED
|
||||
0x49389c SignalEvent 195 PLAYER_TARGET_CHANGED
|
||||
0x493a73 SignalEvent 195 PLAYER_TARGET_CHANGED
|
||||
0x4945ca SignalEvent2 ? %s
|
||||
0x49468b SignalEvent2 182 UNIT_COMBAT %s%s%s%d%d
|
||||
0x49493f SignalEvent 205 ZONE_CHANGED_NEW_AREA
|
||||
0x49495f SignalEvent ?
|
||||
0x4949cb SignalEvent 206 MINIMAP_ZONE_CHANGED
|
||||
0x4949e7 SignalEvent 206 MINIMAP_ZONE_CHANGED
|
||||
0x494add SignalEvent 431 MACRO_ACTION_FORBIDDEN
|
||||
0x494afb SignalEvent2 432 ADDON_ACTION_FORBIDDEN %s
|
||||
0x494c54 SignalEvent 292 CURSOR_UPDATE
|
||||
0x494d10 SignalEvent 200 PLAYER_MONEY
|
||||
0x49515e SignalEvent 292 CURSOR_UPDATE
|
||||
0x495248 SignalEvent 200 PLAYER_MONEY
|
||||
0x4952cc SignalEvent 292 CURSOR_UPDATE
|
||||
0x4953c0 SignalEvent2 291 DELETE_ITEM_CONFIRM %s%d
|
||||
0x495415 SignalEvent 188 ITEM_LOCK_CHANGED
|
||||
0x495455 SignalEvent 188 ITEM_LOCK_CHANGED
|
||||
0x49557d SignalEvent 188 ITEM_LOCK_CHANGED
|
||||
0x49563d SignalEvent2 433 MEMORY_EXHAUSTED %d
|
||||
0x495656 SignalEvent 434 MEMORY_RECOVERED
|
||||
0x4956e9 SignalEvent 518 IGR_BILLING_NAG_DIALOG
|
||||
0x495700 SignalEvent2 517 BILLING_NAG_DIALOG %d
|
||||
0x495741 SignalEvent 518 IGR_BILLING_NAG_DIALOG
|
||||
0x49581e SignalEvent2 517 BILLING_NAG_DIALOG %d
|
||||
0x49587d SignalEvent 275 PLAYER_DEAD
|
||||
0x495893 SignalEvent 274 PLAYER_ALIVE
|
||||
0x4958f7 SignalEvent 197 PLAYER_CONTROL_GAINED
|
||||
0x49590b SignalEvent 196 PLAYER_CONTROL_LOST
|
||||
0x4960a3 SignalEvent 402 BIND_ENCHANT
|
||||
0x4960e4 SignalEvent2 403 REPLACE_ENCHANT %s%s
|
||||
0x496230 SignalEvent2 404 TRADE_REPLACE_ENCHANT %s%s%d
|
||||
0x49630d SignalEvent dyn
|
||||
0x496359 SignalEvent 508 INSTANCE_BOOT_START
|
||||
0x49636e SignalEvent 509 INSTANCE_BOOT_STOP
|
||||
0x4963ce SignalEvent 516 CONFIRM_SUMMON
|
||||
0x4996be SignalEvent 395 UPDATE_CHAT_WINDOWS
|
||||
0x49971b SignalEvent2 226 UPDATE_CHAT_COLOR %s%f%f%f
|
||||
0x499793 SignalEvent2 226 UPDATE_CHAT_COLOR %s%f%f%f
|
||||
0x499939 SignalEvent 395 UPDATE_CHAT_WINDOWS
|
||||
0x499993 SignalEvent2 226 UPDATE_CHAT_COLOR %s%f%f%f
|
||||
0x499a03 SignalEvent2 226 UPDATE_CHAT_COLOR %s%f%f%f
|
||||
0x49a95f SignalEvent2 227 CHAT_MSG_ADDON %s%s%s%s
|
||||
0x49b199 SignalEvent2 ? %s%s%s%s%s%s%d%d%s%d
|
||||
0x49b975 SignalEvent 258 LANGUAGE_LIST_CHANGED
|
||||
0x49bddc SignalEvent 526 UPDATE_WORLD_STATES
|
||||
0x49e1a7 SignalEvent 539 UPDATE_INSTANCE_INFO
|
||||
0x49eb21 SignalEvent 395 UPDATE_CHAT_WINDOWS
|
||||
0x49eb5f SignalEvent 395 UPDATE_CHAT_WINDOWS
|
||||
0x4a00a9 SignalEvent 395 UPDATE_CHAT_WINDOWS
|
||||
0x4a098b SignalEvent2 226 UPDATE_CHAT_COLOR %s%f%f%f
|
||||
0x4a6ce4 SignalEvent 365 WORLD_MAP_UPDATE
|
||||
0x4a7835 SignalEvent 373 CLOSE_WORLD_MAP
|
||||
0x4aa15b SignalEvent 418 BATTLEFIELDS_CLOSED
|
||||
0x4aa308 SignalEvent 420 UPDATE_BATTLEFIELD_SCORE
|
||||
0x4aa7d1 SignalEvent 417 BATTLEFIELDS_SHOW
|
||||
0x4aab05 SignalEvent 419 UPDATE_BATTLEFIELD_STATUS
|
||||
0x4ac195 SignalEvent 366 WORLD_MAP_NAME_UPDATE
|
||||
0x4acd42 SignalEvent 410 MAIL_SHOW
|
||||
0x4acda3 SignalEvent 411 MAIL_CLOSED
|
||||
0x4ace19 SignalEvent 412 SEND_MAIL_MONEY_CHANGED
|
||||
0x4ace23 SignalEvent 413 SEND_MAIL_COD_CHANGED
|
||||
0x4ace2d SignalEvent 415 MAIL_SEND_SUCCESS
|
||||
0x4ad02a SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4ad16a SignalEvent 514 MAIL_FAILED
|
||||
0x4ad5d6 SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4ad605 SignalEvent 500 UPDATE_PENDING_MAIL
|
||||
0x4ad66b SignalEvent 500 UPDATE_PENDING_MAIL
|
||||
0x4ad772 SignalEvent2 515 CLOSE_INBOX_ITEM %d
|
||||
0x4ad7a3 SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4ad87d SignalEvent2 515 CLOSE_INBOX_ITEM %d
|
||||
0x4ad8ae SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4ad8fc SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4ad966 SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4adc44 SignalEvent 412 SEND_MAIL_MONEY_CHANGED
|
||||
0x4adc88 SignalEvent 413 SEND_MAIL_COD_CHANGED
|
||||
0x4add9d SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4adeba SignalEvent 500 UPDATE_PENDING_MAIL
|
||||
0x4ae0de SignalEvent 414 MAIL_SEND_INFO_UPDATE
|
||||
0x4ae71f SignalEvent 414 MAIL_SEND_INFO_UPDATE
|
||||
0x4ae98d SignalEvent 414 MAIL_SEND_INFO_UPDATE
|
||||
0x4aeaf8 SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4af08f SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4af0af SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4af34f SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4af5bf SignalEvent 416 MAIL_INBOX_UPDATE
|
||||
0x4b2904 SignalEvent 387 UPDATE_SHAPESHIFT_FORMS
|
||||
0x4b2b92 SignalEvent2 510 LEARNED_SPELL_IN_TAB %d
|
||||
0x4b2e4e SignalEvent 387 UPDATE_SHAPESHIFT_FORMS
|
||||
0x4b302f SignalEvent 260 SPELLS_CHANGED
|
||||
0x4b31ba SignalEvent 262 SPELL_UPDATE_COOLDOWN
|
||||
0x4b3246 SignalEvent 263 SPELL_UPDATE_USABLE
|
||||
0x4b325a SignalEvent 261 CURRENT_SPELL_CAST_CHANGED
|
||||
0x4b54ad SignalEvent2 384 TUTORIAL_TRIGGER %d
|
||||
0x4b5890 SignalEvent2 384 TUTORIAL_TRIGGER %d
|
||||
0x4b649d SignalEvent 386 UPDATE_BINDINGS
|
||||
0x4b805b SignalEvent 386 UPDATE_BINDINGS
|
||||
0x4ba1a2 SignalEvent 499 RAID_ROSTER_UPDATE
|
||||
0x4ba348 SignalEvent 537 RAID_TARGET_UPDATE
|
||||
0x4ba53a SignalEvent 536 READY_CHECK
|
||||
0x4ba57b SignalEvent 499 RAID_ROSTER_UPDATE
|
||||
0x4ba5db SignalEvent 537 RAID_TARGET_UPDATE
|
||||
0x4babef SignalEvent 499 RAID_ROSTER_UPDATE
|
||||
0x4bada6 SignalEvent 499 RAID_ROSTER_UPDATE
|
||||
0x4badb0 SignalEvent 218 PARTY_MEMBERS_CHANGED
|
||||
0x4bc85b SignalEvent2 2 UNIT_PET %s
|
||||
0x4bc907 SignalEvent 428 PET_UI_CLOSE
|
||||
0x4bc911 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bc956 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bc979 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bcb6d SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bcc81 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bcdcb SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bce75 SignalEvent 355 PET_BAR_SHOWGRID
|
||||
0x4bce85 SignalEvent 356 PET_BAR_HIDEGRID
|
||||
0x4bce95 SignalEvent 354 PET_BAR_UPDATE_COOLDOWN
|
||||
0x4bd438 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bd6b4 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bd722 SignalEvent2 385 PET_DISMISS_START %d
|
||||
0x4bdb54 SignalEvent 353 PET_BAR_UPDATE
|
||||
0x4bf260 SignalEvent2 322 TRADE_ACCEPT_UPDATE %d%d
|
||||
0x4bf29f SignalEvent2 322 TRADE_ACCEPT_UPDATE %d%d
|
||||
0x4bf414 SignalEvent2 323 TRADE_TARGET_ITEM_CHANGED %d
|
||||
0x4bf452 SignalEvent2 323 TRADE_TARGET_ITEM_CHANGED %d
|
||||
0x4bf487 SignalEvent2 324 TRADE_PLAYER_ITEM_CHANGED %d
|
||||
0x4bf4ab SignalEvent 326 PLAYER_TRADE_MONEY
|
||||
0x4bf4b5 SignalEvent 200 PLAYER_MONEY
|
||||
0x4bf4d6 SignalEvent 325 TRADE_MONEY_CHANGED
|
||||
0x4bf522 SignalEvent 320 TRADE_CLOSED
|
||||
0x4bf630 SignalEvent 326 PLAYER_TRADE_MONEY
|
||||
0x4bf63a SignalEvent 200 PLAYER_MONEY
|
||||
0x4bf644 SignalEvent 319 TRADE_SHOW
|
||||
0x4bf705 SignalEvent 319 TRADE_SHOW
|
||||
0x4bf832 SignalEvent 286 TRADE_REQUEST_CANCEL
|
||||
0x4bfaef SignalEvent2 324 TRADE_PLAYER_ITEM_CHANGED %d
|
||||
0x4bfbea SignalEvent 326 PLAYER_TRADE_MONEY
|
||||
0x4bfbf5 SignalEvent 200 PLAYER_MONEY
|
||||
0x4bfc3f SignalEvent2 324 TRADE_PLAYER_ITEM_CHANGED %d
|
||||
0x4bfdce SignalEvent 326 PLAYER_TRADE_MONEY
|
||||
0x4c034f SignalEvent 321 TRADE_UPDATE
|
||||
0x4c1dac SignalEvent 269 LOOT_CLOSED
|
||||
0x4c1ee6 SignalEvent 267 LOOT_OPENED
|
||||
0x4c1f77 SignalEvent2 268 LOOT_SLOT_CLEARED %d
|
||||
0x4c1fe0 SignalEvent 267 LOOT_OPENED
|
||||
0x4c21d4 SignalEvent2 287 LOOT_BIND_CONFIRM %u
|
||||
0x4c2240 SignalEvent 267 LOOT_OPENED
|
||||
0x4c276f SignalEvent2 268 LOOT_SLOT_CLEARED %d
|
||||
0x4c2862 SignalEvent2 268 LOOT_SLOT_CLEARED %d
|
||||
0x4c28d1 SignalEvent 503 OPEN_MASTER_LOOT_LIST
|
||||
0x4c290f SignalEvent2 287 LOOT_BIND_CONFIRM %u
|
||||
0x4c2b37 SignalEvent 267 LOOT_OPENED
|
||||
0x4c2fbf SignalEvent 504 UPDATE_MASTER_LOOT_LIST
|
||||
0x4c3e48 SignalEvent 543 LOTTERY_SHOW
|
||||
0x4c4123 SignalEvent 548 LOTTERY_ITEM_UPDATE
|
||||
0x4c4cd5 SignalEvent 534 START_MINIGAME
|
||||
0x4c4cdf SignalEvent 535 MINIGAME_UPDATE
|
||||
0x4c4d0a SignalEvent 535 MINIGAME_UPDATE
|
||||
0x4c6f56 SignalEvent 525 INSPECT_HONOR_UPDATE
|
||||
0x4c7098 SignalEvent2 264 CHARACTER_POINTS_CHANGED %d%d
|
||||
0x4c7110 SignalEvent2 186 UNIT_INVENTORY_CHANGED %s
|
||||
0x4c728d SignalEvent2 347 PLAYERBANKSLOTS_CHANGED %s
|
||||
0x4c72e0 SignalEvent2 186 UNIT_INVENTORY_CHANGED %s
|
||||
0x4c8085 SignalEvent 501 UPDATE_INVENTORY_ALERTS
|
||||
0x4c8509 SignalEvent2 186 UNIT_INVENTORY_CHANGED %s
|
||||
0x4ca38f SignalEvent 519 MEETINGSTONE_CHANGED
|
||||
0x4cac9b SignalEvent 437 PET_STABLE_SHOW
|
||||
0x4cad88 SignalEvent 438 PET_STABLE_UPDATE
|
||||
0x4cae3a SignalEvent 440 PET_STABLE_CLOSED
|
||||
0x4cb3bf SignalEvent 438 PET_STABLE_UPDATE
|
||||
0x4cba5f SignalEvent 439 PET_STABLE_UPDATE_PAPERDOLL
|
||||
0x4cc5d0 SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cc703 SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cc769 SignalEvent 426 AUCTION_BIDDER_LIST_UPDATE
|
||||
0x4cd09e SignalEvent 426 AUCTION_BIDDER_LIST_UPDATE
|
||||
0x4cd143 SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cd301 SignalEvent 425 AUCTION_OWNED_LIST_UPDATE
|
||||
0x4cd3a8 SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cd5e6 SignalEvent 421 AUCTION_HOUSE_SHOW
|
||||
0x4cd602 SignalEvent 422 AUCTION_HOUSE_CLOSED
|
||||
0x4cd678 SignalEvent 423 NEW_AUCTION_UPDATE
|
||||
0x4cd8cf SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cdcef SignalEvent 425 AUCTION_OWNED_LIST_UPDATE
|
||||
0x4cdddf SignalEvent 426 AUCTION_BIDDER_LIST_UPDATE
|
||||
0x4ce03e SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4ce09e SignalEvent 425 AUCTION_OWNED_LIST_UPDATE
|
||||
0x4ce101 SignalEvent 426 AUCTION_BIDDER_LIST_UPDATE
|
||||
0x4ce2ff SignalEvent 423 NEW_AUCTION_UPDATE
|
||||
0x4ce576 SignalEvent 423 NEW_AUCTION_UPDATE
|
||||
0x4cf29f SignalEvent 424 AUCTION_ITEM_LIST_UPDATE
|
||||
0x4cf2bf SignalEvent 425 AUCTION_OWNED_LIST_UPDATE
|
||||
0x4cf2df SignalEvent 426 AUCTION_BIDDER_LIST_UPDATE
|
||||
0x4d117a SignalEvent2 408 GUILD_ROSTER_UPDATE %d
|
||||
0x4d1188 SignalEvent 408 GUILD_ROSTER_UPDATE
|
||||
0x4d2ff2 SignalEvent 265 SKILL_LINES_CHANGED
|
||||
0x4d346f SignalEvent 265 SKILL_LINES_CHANGED
|
||||
0x4d3598 SignalEvent 265 SKILL_LINES_CHANGED
|
||||
0x4d3e3a SignalEvent 265 SKILL_LINES_CHANGED
|
||||
0x4d4a88 SignalEvent2 380 DUEL_REQUESTED %s
|
||||
0x4d4aa5 SignalEvent 381 DUEL_OUTOFBOUNDS
|
||||
0x4d4ac5 SignalEvent 382 DUEL_INBOUNDS
|
||||
0x4d4b6a SignalEvent 383 DUEL_FINISHED
|
||||
0x4d569e SignalEvent 372 UPDATE_FACTION
|
||||
0x4d56f6 SignalEvent 372 UPDATE_FACTION
|
||||
0x4d5984 SignalEvent 372 UPDATE_FACTION
|
||||
0x4d5db0 SignalEvent 372 UPDATE_FACTION
|
||||
0x4d7468 SignalEvent 311 TRAINER_CLOSED
|
||||
0x4d7b73 SignalEvent 309 TRAINER_SHOW
|
||||
0x4d83f9 SignalEvent 310 TRAINER_UPDATE
|
||||
0x4d8ca0 SignalEvent 310 TRAINER_UPDATE
|
||||
0x4d8cc0 SignalEvent 310 TRAINER_UPDATE
|
||||
0x4d8ce0 SignalEvent 310 TRAINER_UPDATE
|
||||
0x4dba96 SignalEvent 306 TAXIMAP_OPENED
|
||||
0x4dc15f SignalEvent 307 TAXIMAP_CLOSED
|
||||
0x4de534 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4de816 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4ded98 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4dedba SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4dedd6 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4def20 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4df28f SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4df840 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4e07ef SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4e081f SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4e083f SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4e1559 SignalEvent 308 QUEST_LOG_UPDATE
|
||||
0x4e1fdc SignalEvent 299 GOSSIP_CLOSED
|
||||
0x4e22b5 SignalEvent 297 GOSSIP_SHOW
|
||||
0x4e237e SignalEvent2 298 GOSSIP_ENTER_CODE %d
|
||||
0x4e332d SignalEvent 296 ITEM_TEXT_CLOSED
|
||||
0x4e33a2 SignalEvent 296 ITEM_TEXT_CLOSED
|
||||
0x4e34a4 SignalEvent 293 ITEM_TEXT_BEGIN
|
||||
0x4e367c SignalEvent 295 ITEM_TEXT_READY
|
||||
0x4e3783 SignalEvent 295 ITEM_TEXT_READY
|
||||
0x4e3860 SignalEvent 295 ITEM_TEXT_READY
|
||||
0x4e437f SignalEvent 192 PLAYER_AURAS_CHANGED
|
||||
0x4e501d SignalEvent 217 UPDATE_BONUS_ACTIONBAR
|
||||
0x4e58c5 SignalEvent 210 ACTIONBAR_SHOWGRID
|
||||
0x4e58d5 SignalEvent 211 ACTIONBAR_HIDEGRID
|
||||
0x4e5998 SignalEvent2 213 ACTIONBAR_SLOT_CHANGED %d
|
||||
0x4e5bf5 SignalEvent 214 ACTIONBAR_UPDATE_STATE
|
||||
0x4e5c53 SignalEvent 215 ACTIONBAR_UPDATE_USABLE
|
||||
0x4e5c65 SignalEvent 216 ACTIONBAR_UPDATE_COOLDOWN
|
||||
0x4e7655 SignalEvent 212 ACTIONBAR_PAGE_CHANGED
|
||||
0x4e82a9 SignalEvent 219 PARTY_LEADER_CHANGED
|
||||
0x4e82c2 SignalEvent 219 PARTY_LEADER_CHANGED
|
||||
0x4e8495 SignalEvent 218 PARTY_MEMBERS_CHANGED
|
||||
0x4e85c5 SignalEvent 218 PARTY_MEMBERS_CHANGED
|
||||
0x4e86b6 SignalEvent2 220 PARTY_MEMBER_ENABLE %d
|
||||
0x4e87fa SignalEvent 222 PARTY_LOOT_METHOD_CHANGED
|
||||
0x4e8b0e SignalEvent 531 UPDATE_LFG_TYPES
|
||||
0x4e8c70 SignalEvent 531 UPDATE_LFG_TYPES
|
||||
0x4e8d46 SignalEvent 532 UPDATE_LFG_LIST
|
||||
0x4e8f45 SignalEvent 532 UPDATE_LFG_LIST
|
||||
0x4ee48d SignalEvent2 357 MINIMAP_PING %s%f%f
|
||||
0x4f0dfb SignalEvent 393 UPDATE_MACROS
|
||||
0x4f153c SignalEvent2 392 EXECUTE_CHAT_LINE %s
|
||||
0x4f402c SignalEvent 391 PETITION_CLOSED
|
||||
0x4f41b4 SignalEvent 390 PETITION_SHOW
|
||||
0x4f430c SignalEvent 390 PETITION_SHOW
|
||||
0x4f433f SignalEvent 390 PETITION_SHOW
|
||||
0x4f43ad SignalEvent 390 PETITION_SHOW
|
||||
0x4f5003 SignalEvent 378 GUILD_REGISTRAR_SHOW
|
||||
0x4f503a SignalEvent 379 GUILD_REGISTRAR_CLOSED
|
||||
0x4f588d SignalEvent 374 OPEN_TABARD_FRAME
|
||||
0x4f58f8 SignalEvent 375 CLOSE_TABARD_FRAME
|
||||
0x4f5fef SignalEvent 361 CRAFT_CLOSE
|
||||
0x4f6017 SignalEvent 361 CRAFT_CLOSE
|
||||
0x4f6040 SignalEvent 359 CRAFT_SHOW
|
||||
0x4f661a SignalEvent 360 CRAFT_UPDATE
|
||||
0x4f6bf0 SignalEvent 360 CRAFT_UPDATE
|
||||
0x4f8495 SignalEvent 350 PLAYERBANKBAGSLOTS_CHANGED
|
||||
0x4f84df SignalEvent 349 BANKFRAME_CLOSED
|
||||
0x4f8522 SignalEvent 348 BANKFRAME_OPENED
|
||||
0x4f8e8a SignalEvent2 328 BAG_UPDATE %d
|
||||
0x4f92b5 SignalEvent2 329 BAG_CLOSED %d
|
||||
0x4f9343 SignalEvent2 328 BAG_UPDATE %d
|
||||
0x4f93bf SignalEvent2 328 BAG_UPDATE %d
|
||||
0x4f93d5 SignalEvent 330 BAG_UPDATE_COOLDOWN
|
||||
0x4f9434 SignalEvent2 327 BAG_OPEN %d
|
||||
0x4f9469 SignalEvent2 327 BAG_OPEN %d
|
||||
0x4facaa SignalEvent 317 MERCHANT_UPDATE
|
||||
0x4fad92 SignalEvent 316 MERCHANT_SHOW
|
||||
0x4fae2c SignalEvent 318 MERCHANT_CLOSED
|
||||
0x4faea5 SignalEvent 317 MERCHANT_UPDATE
|
||||
0x4faebd SignalEvent 317 MERCHANT_UPDATE
|
||||
0x4fb115 SignalEvent 317 MERCHANT_UPDATE
|
||||
0x4fb575 SignalEvent 317 MERCHANT_UPDATE
|
||||
0x4fc884 SignalEvent 315 TRADE_SKILL_CLOSE
|
||||
0x4fc930 SignalEvent 315 TRADE_SKILL_CLOSE
|
||||
0x4fc99f SignalEvent 313 TRADE_SKILL_SHOW
|
||||
0x4fd164 SignalEvent 314 TRADE_SKILL_UPDATE
|
||||
0x4fd725 SignalEvent 314 TRADE_SKILL_UPDATE
|
||||
0x4fd745 SignalEvent 314 TRADE_SKILL_UPDATE
|
||||
0x4fd765 SignalEvent 314 TRADE_SKILL_UPDATE
|
||||
0x4fd781 SignalEvent 502 UPDATE_TRADESKILL_RECAST
|
||||
0x4fd7a8 SignalEvent 502 UPDATE_TRADESKILL_RECAST
|
||||
0x4fd7e1 SignalEvent 502 UPDATE_TRADESKILL_RECAST
|
||||
0x500ee5 SignalEvent 300 QUEST_GREETING
|
||||
0x50105d SignalEvent 303 QUEST_COMPLETE
|
||||
0x501120 SignalEvent 302 QUEST_PROGRESS
|
||||
0x501217 SignalEvent 304 QUEST_FINISHED
|
||||
0x501903 SignalEvent 305 QUEST_ITEM_UPDATE
|
||||
0x5019fe SignalEvent2 346 QUEST_ACCEPT_CONFIRM %s%s
|
||||
0x515e7a SignalEvent2 ? %s
|
||||
0x51f5ad SignalEvent2 429 ADDON_LOADED %s
|
||||
0x5aae97 SignalEvent 278 LOGOUT_CANCEL
|
||||
0x5aaf17 SignalEvent ?
|
||||
0x5aaf72 SignalEvent 278 LOGOUT_CANCEL
|
||||
0x5ab2f5 SignalEvent 6
|
||||
0x5ad9f2 SignalEvent 388 WHO_LIST_UPDATE
|
||||
0x5ae141 SignalEvent 388 WHO_LIST_UPDATE
|
||||
0x5ae867 SignalEvent 351 FRIENDLIST_UPDATE
|
||||
0x5ae92d SignalEvent 352 IGNORELIST_UPDATE
|
||||
0x5d48a8 SignalEvent 200 PLAYER_MONEY
|
||||
0x5d859a SignalEvent 188 ITEM_LOCK_CHANGED
|
||||
0x5d9222 SignalEvent 290 USE_BIND_CONFIRM
|
||||
0x5d94b9 SignalEvent 188 ITEM_LOCK_CHANGED
|
||||
0x5dd15e SignalEvent2 545 QUEST_WATCH_UPDATE %d
|
||||
0x5ddd6e SignalEvent 347 PLAYERBANKSLOTS_CHANGED
|
||||
0x5ddf4b SignalEvent 200 PLAYER_MONEY
|
||||
0x5ddffa SignalEvent 202 PLAYER_COMBO_POINTS
|
||||
0x5de14e SignalEvent 198 PLAYER_FARSIGHT_FOCUS_CHANGED
|
||||
0x5de167 SignalEvent 198 PLAYER_FARSIGHT_FOCUS_CHANGED
|
||||
0x5de4c1 SignalEvent2 ? %s
|
||||
0x5de4e8 SignalEvent 406 UPDATE_EXHAUSTION
|
||||
0x5dea2a SignalEvent 401 PLAYER_UNGHOST
|
||||
0x5deb20 SignalEvent2 213 ACTIONBAR_SLOT_CHANGED %d
|
||||
0x5deecb SignalEvent2 162 UNIT_RANGED_ATTACK_POWER %s
|
||||
0x5df837 SignalEvent2 396 CONFIRM_XP_LOSS %d
|
||||
0x5dfa3f SignalEvent2 512 CONFIRM_TALENT_WIPE %d
|
||||
0x5dfc5f SignalEvent2 529 CONFIRM_PET_UNLEARN %d
|
||||
0x5dfec3 SignalEvent2 513 CONFIRM_BINDER %s
|
||||
0x5e05fc SignalEvent 521 TABARD_SAVE_PENDING
|
||||
0x5e0907 SignalEvent 376 TABARD_CANSAVE_CHANGED
|
||||
0x5e0ee4 SignalEvent2 288 EQUIP_BIND_CONFIRM %d
|
||||
0x5e16c6 SignalEvent2 289 AUTOEQUIP_BIND_CONFIRM %d
|
||||
0x5e27b5 SignalEvent 345 PLAYER_GUILD_UPDATE
|
||||
0x5e2b38 SignalEvent2 28 UNIT_LEVEL %s
|
||||
0x5e3b20 SignalEvent 267 LOOT_OPENED
|
||||
0x5e413e SignalEvent2 199 PLAYER_LEVEL_UP %d%d%d%d%d%d%d%d%d
|
||||
0x5e4527 SignalEvent2 394 UPDATE_TICKET %d%s%f%f%f%d%d
|
||||
0x5e45e2 SignalEvent2 394 UPDATE_TICKET %d%s%f%f%f%d%d
|
||||
0x5e461d SignalEvent2 394 UPDATE_TICKET %d%s
|
||||
0x5e4689 SignalEvent2 400 UPDATE_GM_STATUS %d
|
||||
0x5e5e7b SignalEvent2 545 QUEST_WATCH_UPDATE %d
|
||||
0x5e6c66 SignalEvent 218 PARTY_MEMBERS_CHANGED
|
||||
0x5e7172 SignalEvent 521 TABARD_SAVE_PENDING
|
||||
0x5e728d SignalEvent2 284 GUILD_MOTD %s
|
||||
0x5e7960 SignalEvent2 394 UPDATE_TICKET %d%s%f%f%f%d%d
|
||||
0x5e797b SignalEvent 538 GMSURVEY_DISPLAY
|
||||
0x5e79cc SignalEvent2 364 MIRROR_TIMER_STOP %s
|
||||
0x5e7a11 SignalEvent2 363 MIRROR_TIMER_PAUSE %s%d
|
||||
0x5e7a9a SignalEvent2 362 MIRROR_TIMER_START %s%d%d%d%d%s
|
||||
0x5e9b78 SignalEvent2 221 PARTY_MEMBER_DISABLE %d
|
||||
0x5e9ba5 SignalEvent2 364 MIRROR_TIMER_STOP %s
|
||||
0x5ec68b SignalEvent 296 ITEM_TEXT_CLOSED
|
||||
0x5ec6c5 SignalEvent2 294 ITEM_TEXT_TRANSLATION %f
|
||||
0x5ed97f SignalEvent2 332 UNIT_ATTACK %s
|
||||
0x5ed99f SignalEvent2 332 UNIT_ATTACK %s
|
||||
0x5ed9b3 SignalEvent2 333 UNIT_DEFENSE %s
|
||||
0x5ed9c7 SignalEvent2 162 UNIT_RANGED_ATTACK_POWER %s
|
||||
0x5eeaf7 SignalEvent 405 PLAYER_UPDATE_RESTING
|
||||
0x5eeb6f SignalEvent 530 PLAYTIME_CHANGED
|
||||
0x5ef037 SignalEvent 391 PETITION_CLOSED
|
||||
0x5ef16b SignalEvent 379 GUILD_REGISTRAR_CLOSED
|
||||
0x5ef29c SignalEvent 390 PETITION_SHOW
|
||||
0x5fefb8 SignalEvent 274 PLAYER_ALIVE
|
||||
0x5ff533 SignalEvent 191 PLAYER_REGEN_ENABLED
|
||||
0x5ff79f SignalEvent 335 PET_ATTACK_STOP
|
||||
0x604b17 SignalEvent 331 LOCALPLAYER_PET_RENAMED
|
||||
0x604c1b SignalEvent 427 PET_UI_UPDATE
|
||||
0x60fb84 SignalEvent 368 AUTOFOLLOW_END
|
||||
0x610071 SignalEvent2 367 AUTOFOLLOW_BEGIN %s
|
||||
0x61b2c7 SignalEvent2 506 CANCEL_LOOT_ROLL %d
|
||||
0x61b452 SignalEvent2 505 START_LOOT_ROLL %d%d
|
||||
0x61b6f3 SignalEvent2 506 CANCEL_LOOT_ROLL %d
|
||||
0x61b7ac SignalEvent2 505 START_LOOT_ROLL %d%d
|
||||
0x61bc12 SignalEvent2 506 CANCEL_LOOT_ROLL %d
|
||||
0x61be8b SignalEvent2 507 CONFIRM_LOOT_ROLL %d%d
|
||||
0x61bf2a SignalEvent2 506 CANCEL_LOOT_ROLL %d
|
||||
0x6256ff SignalEvent 193 PLAYER_ENTER_COMBAT
|
||||
0x625778 SignalEvent 194 PLAYER_LEAVE_COMBAT
|
||||
0x6268d3 SignalEvent2 542 COMBAT_TEXT_UPDATE %s%s%s
|
||||
0x62694d SignalEvent2 542 COMBAT_TEXT_UPDATE %s%s%s
|
||||
0x62699c SignalEvent2 542 COMBAT_TEXT_UPDATE %s%s
|
||||
0x6269c3 SignalEvent2 542 COMBAT_TEXT_UPDATE %s%s
|
||||
0x6269e2 SignalEvent2 542 COMBAT_TEXT_UPDATE %s
|
||||
0x6d902a SignalEvent 207 MINIMAP_UPDATE_ZOOM
|
||||
0x6d95b8 SignalEvent 207 MINIMAP_UPDATE_ZOOM
|
||||
0x6e224a SignalEvent 340 SPELLCAST_INTERRUPTED
|
||||
0x6e4a50 SignalEvent 338 SPELLCAST_STOP
|
||||
0x6e5952 SignalEvent 435 START_AUTOREPEAT_SPELL
|
||||
0x6e70c3 SignalEvent 528 AREA_SPIRIT_HEALER_OUT_OF_RANGE
|
||||
0x6e74d0 SignalEvent 338 SPELLCAST_STOP
|
||||
0x6e7532 SignalEvent2 341 SPELLCAST_DELAYED %d
|
||||
0x6e75d0 SignalEvent2 342 SPELLCAST_CHANNEL_START %d%s
|
||||
0x6e7611 SignalEvent2 343 SPELLCAST_CHANNEL_UPDATE %d
|
||||
0x6e7627 SignalEvent 344 SPELLCAST_CHANNEL_STOP
|
||||
0x6e7a53 SignalEvent2 337 SPELLCAST_START %s%d
|
||||
0x6ea170 SignalEvent 436 STOP_AUTOREPEAT_SPELL
|
||||
|
Can't render this file because it contains an unexpected character in line 3 and column 38.
|
@@ -0,0 +1,470 @@
|
||||
# build 5875 CGGameUI message catalog (the 0xb4b498 table indexed by 0x496720)
|
||||
# 465 rows, ids 0x0..0x1d0; consumer bound `cmp id,0x1d1; jge` at 0x49672c
|
||||
# kind 0 -> 0x49a870(text, chan); 1 -> UI_INFO_MESSAGE 0xe1; 2 -> UI_ERROR_MESSAGE 0xe0; 3 -> console 0x63cd00
|
||||
# regenerate (in wow-5875-re): python3 re/ui/extract-message-catalog.py <WoW.exe> > re/ui/message-catalog.tsv
|
||||
id_hex id_dec row_va key kind sound type_tag chan
|
||||
0x0 0 0xb4b498 ERR_INV_FULL 2 NONE 0x0 10
|
||||
0x1 1 0xb4b4ac ERR_BANK_FULL 2 NONE 0x44 10
|
||||
0x2 2 0xb4b4c0 ERR_CANT_EQUIP_LEVEL_I 2 NONE 0x2 10
|
||||
0x3 3 0xb4b4d4 ERR_CANT_EQUIP_SKILL 2 NONE 0x29 10
|
||||
0x4 4 0xb4b4e8 ERR_CANT_EQUIP_EVER 2 NONE 0x3 10
|
||||
0x5 5 0xb4b4fc ERR_CANT_EQUIP_RANK 2 NONE 0x44 10
|
||||
0x6 6 0xb4b510 ERR_CANT_EQUIP_REPUTATION 2 NONE 0x44 10
|
||||
0x7 7 0xb4b524 ERR_PROFICIENCY_NEEDED 2 NONE 0x30 10
|
||||
0x8 8 0xb4b538 ERR_WRONG_SLOT 2 NONE 0x1b 10
|
||||
0x9 9 0xb4b54c ERR_BAG_FULL 2 NONE 0x1d 10
|
||||
0xa 10 0xb4b560 ERR_INTERNAL_BAG_ERROR 2 NONE 0x44 10
|
||||
0xb 11 0xb4b574 ERR_DESTROY_NONEMPTY_BAG 2 NONE 0x44 10
|
||||
0xc 12 0xb4b588 ERR_BAG_IN_BAG 2 NONE 0x1a 10
|
||||
0xd 13 0xb4b59c ERR_TOO_MANY_SPECIAL_BAGS 2 NONE 0x44 10
|
||||
0xe 14 0xb4b5b0 ERR_TRADE_EQUIPPED_BAG 2 NONE 0x44 10
|
||||
0xf 15 0xb4b5c4 ERR_AMMO_ONLY 2 NONE 0x1c 10
|
||||
0x10 16 0xb4b5d8 ERR_NO_SLOT_AVAILABLE 2 NONE 0x44 10
|
||||
0x11 17 0xb4b5ec ERR_WRONG_BAG_TYPE 2 NONE 0x44 10
|
||||
0x12 18 0xb4b600 ERR_ITEM_MAX_COUNT 2 NONE 0x1e 10
|
||||
0x13 19 0xb4b614 ERR_NOT_EQUIPPABLE 2 NONE 0x2c 10
|
||||
0x14 20 0xb4b628 ERR_CANT_STACK 2 NONE 0x44 10
|
||||
0x15 21 0xb4b63c ERR_CANT_SWAP 2 NONE 0x44 10
|
||||
0x16 22 0xb4b650 ERR_SLOT_EMPTY 2 NONE 0x44 10
|
||||
0x17 23 0xb4b664 ERR_ITEM_NOT_FOUND 2 NONE 0x44 10
|
||||
0x18 24 0xb4b678 ERR_TOO_FEW_TO_SPLIT 2 NONE 0x44 10
|
||||
0x19 25 0xb4b68c ERR_SPLIT_FAILED 2 NONE 0x44 10
|
||||
0x1a 26 0xb4b6a0 ERR_NOT_A_BAG 2 NONE 0x19 10
|
||||
0x1b 27 0xb4b6b4 ERR_NOT_OWNER 2 NONE 0x44 10
|
||||
0x1c 28 0xb4b6c8 ERR_ONLY_ONE_QUIVER 2 NONE 0x44 10
|
||||
0x1d 29 0xb4b6dc ERR_NO_BANK_SLOT 2 NONE 0x44 10
|
||||
0x1e 30 0xb4b6f0 ERR_NO_BANK_HERE 2 NONE 0x44 10
|
||||
0x1f 31 0xb4b704 ERR_ITEM_LOCKED 2 NONE 0x3d 10
|
||||
0x20 32 0xb4b718 ERR_2HANDED_EQUIPPED 2 NONE 0x2a 10
|
||||
0x21 33 0xb4b72c ERR_VENDOR_NOT_INTERESTED 2 NONE 0x44 10
|
||||
0x22 34 0xb4b740 ERR_VENDOR_HATES_YOU 2 NONE 0x44 10
|
||||
0x23 35 0xb4b754 ERR_VENDOR_SOLD_OUT 2 NONE 0x44 10
|
||||
0x24 36 0xb4b768 ERR_VENDOR_TOO_FAR 2 NONE 0x44 10
|
||||
0x25 37 0xb4b77c ERR_NOT_ENOUGH_MONEY 2 NONE 0x28 10
|
||||
0x26 38 0xb4b790 ERR_RECEIVE_ITEM_S 0 ITEMGENERICSOUND 0x44 10
|
||||
0x27 39 0xb4b7a4 ERR_DROP_BOUND_ITEM 2 NONE 0x4 10
|
||||
0x28 40 0xb4b7b8 ERR_TRADE_BOUND_ITEM 2 NONE 0x3b 10
|
||||
0x29 41 0xb4b7cc ERR_TRADE_QUEST_ITEM 2 NONE 0x3b 10
|
||||
0x2a 42 0xb4b7e0 ERR_TRADE_GROUND_ITEM 2 NONE 0x44 10
|
||||
0x2b 43 0xb4b7f4 ERR_TRADE_BAG 2 NONE 0x44 10
|
||||
0x2c 44 0xb4b808 ERR_SPELL_FAILED_S 2 NONE 0x44 10
|
||||
0x2d 45 0xb4b81c ERR_ITEM_COOLDOWN 2 NONE 0x5 10
|
||||
0x2e 46 0xb4b830 ERR_POTION_COOLDOWN 2 NONE 0x2f 10
|
||||
0x2f 47 0xb4b844 ERR_FOOD_COOLDOWN 2 NONE 0x7 10
|
||||
0x30 48 0xb4b858 ERR_SPELL_COOLDOWN 2 NONE 0xc 10
|
||||
0x31 49 0xb4b86c ERR_ABILITY_COOLDOWN 2 NONE 0x32 10
|
||||
0x32 50 0xb4b880 ERR_SPELL_ALREADY_KNOWN_S 2 NONE 0xd 10
|
||||
0x33 51 0xb4b894 ERR_PET_SPELL_ALREADY_KNOWN_S 2 NONE 0x44 10
|
||||
0x34 52 0xb4b8a8 ERR_PROFICIENCY_GAINED_S 0 NONE 0x44 23
|
||||
0x35 53 0xb4b8bc ERR_SKILL_GAINED_S 0 NONE 0x44 23
|
||||
0x36 54 0xb4b8d0 ERR_SKILL_UP_SI 0 NONE 0x44 23
|
||||
0x37 55 0xb4b8e4 ERR_LEARN_SPELL_S 0 NONE 0x44 10
|
||||
0x38 56 0xb4b8f8 ERR_LEARN_ABILITY_S 0 NONE 0x44 10
|
||||
0x39 57 0xb4b90c ERR_LEARN_RECIPE_S 0 NONE 0x44 10
|
||||
0x3a 58 0xb4b920 ERR_INVITE_PLAYER_S 0 NONE 0x44 10
|
||||
0x3b 59 0xb4b934 ERR_INVITE_SELF 2 NONE 0x44 10
|
||||
0x3c 60 0xb4b948 ERR_INVITED_TO_GROUP_S 0 NONE 0x44 10
|
||||
0x3d 61 0xb4b95c ERR_ALREADY_IN_GROUP_S 0 NONE 0x14 10
|
||||
0x3e 62 0xb4b970 ERR_PLAYER_BUSY_S 0 NONE 0x44 10
|
||||
0x3f 63 0xb4b984 ERR_NEW_LEADER_S 0 NONE 0x44 10
|
||||
0x40 64 0xb4b998 ERR_NEW_LEADER_YOU 0 NONE 0x44 10
|
||||
0x41 65 0xb4b9ac ERR_LEFT_GROUP_S 0 NONE 0x44 10
|
||||
0x42 66 0xb4b9c0 ERR_LEFT_GROUP_YOU 0 NONE 0x44 10
|
||||
0x43 67 0xb4b9d4 ERR_GROUP_DISBANDED 0 NONE 0x44 10
|
||||
0x44 68 0xb4b9e8 ERR_DECLINE_GROUP_S 0 igPlayerInviteDecline 0x44 10
|
||||
0x45 69 0xb4b9fc ERR_JOINED_GROUP_S 0 NONE 0x44 10
|
||||
0x46 70 0xb4ba10 ERR_UNINVITE_YOU 0 NONE 0x44 10
|
||||
0x47 71 0xb4ba24 ERR_BAD_PLAYER_NAME_S 0 NONE 0x44 10
|
||||
0x48 72 0xb4ba38 ERR_NOT_IN_GROUP 0 NONE 0x44 10
|
||||
0x49 73 0xb4ba4c ERR_TARGET_NOT_IN_GROUP_S 0 NONE 0x44 10
|
||||
0x4a 74 0xb4ba60 ERR_GROUP_FULL 0 NONE 0x8 10
|
||||
0x4b 75 0xb4ba74 ERR_NOT_LEADER 0 NONE 0x44 10
|
||||
0x4c 76 0xb4ba88 ERR_PLAYER_DIED_S 0 NONE 0x44 10
|
||||
0x4d 77 0xb4ba9c ERR_GUILD_CREATE_S 0 LEVELUP 0x44 10
|
||||
0x4e 78 0xb4bab0 ERR_GUILD_INVITE_S 0 NONE 0x44 10
|
||||
0x4f 79 0xb4bac4 ERR_INVITED_TO_GUILD_SS 0 LEVELUP 0x44 10
|
||||
0x50 80 0xb4bad8 ERR_ALREADY_IN_GUILD_S 0 NONE 0x44 10
|
||||
0x51 81 0xb4baec ERR_ALREADY_INVITED_TO_GUILD_S 0 NONE 0x44 10
|
||||
0x52 82 0xb4bb00 ERR_INVITED_TO_GUILD 0 NONE 0x44 10
|
||||
0x53 83 0xb4bb14 ERR_ALREADY_IN_GUILD 0 NONE 0x44 10
|
||||
0x54 84 0xb4bb28 ERR_GUILD_ACCEPT 0 NONE 0x44 10
|
||||
0x55 85 0xb4bb3c ERR_GUILD_DECLINE_S 0 NONE 0x44 10
|
||||
0x56 86 0xb4bb50 ERR_GUILD_PERMISSIONS 0 NONE 0x3e 10
|
||||
0x57 87 0xb4bb64 ERR_GUILD_JOIN_S 0 NONE 0x44 10
|
||||
0x58 88 0xb4bb78 ERR_GUILD_FOUNDER_S 0 NONE 0x44 10
|
||||
0x59 89 0xb4bb8c ERR_GUILD_PROMOTE_SSS 0 NONE 0x44 10
|
||||
0x5a 90 0xb4bba0 ERR_GUILD_DEMOTE_SSS 0 NONE 0x44 10
|
||||
0x5b 91 0xb4bbb4 ERR_GUILD_QUIT_S 0 NONE 0x44 10
|
||||
0x5c 92 0xb4bbc8 ERR_GUILD_LEAVE_S 0 NONE 0x44 10
|
||||
0x5d 93 0xb4bbdc ERR_GUILD_REMOVE_SS 0 NONE 0x44 10
|
||||
0x5e 94 0xb4bbf0 ERR_GUILD_REMOVE_SELF 0 NONE 0x44 10
|
||||
0x5f 95 0xb4bc04 ERR_GUILD_DISBAND_S 0 NONE 0x44 10
|
||||
0x60 96 0xb4bc18 ERR_GUILD_DISBAND_SELF 0 NONE 0x44 10
|
||||
0x61 97 0xb4bc2c ERR_GUILD_LEADER_S 0 NONE 0x44 10
|
||||
0x62 98 0xb4bc40 ERR_GUILD_LEADER_SELF 0 NONE 0x44 10
|
||||
0x63 99 0xb4bc54 ERR_GUILD_PLAYER_NOT_FOUND_S 0 NONE 0x44 10
|
||||
0x64 100 0xb4bc68 ERR_GUILD_PLAYER_NOT_IN_GUILD_S 0 NONE 0x44 10
|
||||
0x65 101 0xb4bc7c ERR_GUILD_PLAYER_NOT_IN_GUILD 0 NONE 0x44 10
|
||||
0x66 102 0xb4bc90 ERR_GUILD_CANT_PROMOTE_S 0 NONE 0x44 10
|
||||
0x67 103 0xb4bca4 ERR_GUILD_CANT_DEMOTE_S 0 NONE 0x44 10
|
||||
0x68 104 0xb4bcb8 ERR_GUILD_NOT_IN_A_GUILD 0 NONE 0x44 10
|
||||
0x69 105 0xb4bccc ERR_GUILD_INTERNAL 0 NONE 0x44 10
|
||||
0x6a 106 0xb4bce0 ERR_GUILD_LEADER_IS_S 0 NONE 0x44 10
|
||||
0x6b 107 0xb4bcf4 ERR_GUILD_LEADER_CHANGED_SS 0 NONE 0x44 10
|
||||
0x6c 108 0xb4bd08 ERR_GUILD_DISBANDED 0 NONE 0x44 10
|
||||
0x6d 109 0xb4bd1c ERR_GUILD_NOT_ALLIED 0 NONE 0x44 10
|
||||
0x6e 110 0xb4bd30 ERR_GUILD_LEADER_LEAVE 0 NONE 0x44 10
|
||||
0x6f 111 0xb4bd44 ERR_GUILD_RANKS_LOCKED 0 NONE 0x44 10
|
||||
0x70 112 0xb4bd58 ERR_GUILD_RANK_IN_USE 0 NONE 0x44 10
|
||||
0x71 113 0xb4bd6c ERR_GUILD_RANK_TOO_HIGH_S 0 NONE 0x44 10
|
||||
0x72 114 0xb4bd80 ERR_GUILD_RANK_TOO_LOW_S 0 NONE 0x44 10
|
||||
0x73 115 0xb4bd94 ERR_GUILD_NAME_INVALID 2 NONE 0x44 10
|
||||
0x74 116 0xb4bda8 ERR_GUILD_NAME_EXISTS_S 2 NONE 0x44 10
|
||||
0x75 117 0xb4bdbc ERR_GUILD_ENTER_NAME 2 NONE 0x44 10
|
||||
0x76 118 0xb4bdd0 ERR_GUILD_NAME_TOO_SHORT 2 NONE 0x44 10
|
||||
0x77 119 0xb4bde4 ERR_GUILD_NAME_MIXED_LANGUAGES 2 NONE 0x44 10
|
||||
0x78 120 0xb4bdf8 ERR_GUILD_NAME_PROFANE 2 NONE 0x44 10
|
||||
0x79 121 0xb4be0c ERR_GUILD_NAME_RESERVED 2 NONE 0x44 10
|
||||
0x7a 122 0xb4be20 ERR_GUILD_NAME_INVALID_SPACE 2 NONE 0x44 10
|
||||
0x7b 123 0xb4be34 ERR_GUILD_NAME_NAME_CONSECUTIVE_SPACES 2 NONE 0x44 10
|
||||
0x7c 124 0xb4be48 ERR_NO_GUILD_CHARTER 2 NONE 0x44 10
|
||||
0x7d 125 0xb4be5c ERR_OUT_OF_RANGE 2 NONE 0xa 10
|
||||
0x7e 126 0xb4be70 ERR_PLAYER_DEAD 2 NONE 0x44 10
|
||||
0x7f 127 0xb4be84 ERR_CLIENT_LOCKED_OUT 2 NONE 0x44 10
|
||||
0x80 128 0xb4be98 ERR_KILLED_BY_S 0 NONE 0x44 10
|
||||
0x81 129 0xb4beac ERR_LOOT_LOCKED 2 NONE 0x21 10
|
||||
0x82 130 0xb4bec0 ERR_LOOT_TOO_FAR 2 NONE 0x23 10
|
||||
0x83 131 0xb4bed4 ERR_LOOT_DIDNT_KILL 2 NONE 0x1f 10
|
||||
0x84 132 0xb4bee8 ERR_LOOT_BAD_FACING 2 NONE 0x20 10
|
||||
0x85 133 0xb4befc ERR_LOOT_NOTSTANDING 2 NONE 0x44 10
|
||||
0x86 134 0xb4bf10 ERR_LOOT_STUNNED 2 NONE 0x44 10
|
||||
0x87 135 0xb4bf24 ERR_LOOT_NO_UI 2 NONE 0x44 10
|
||||
0x88 136 0xb4bf38 ERR_LOOT_WHILE_INVULNERABLE 2 NONE 0x44 10
|
||||
0x89 137 0xb4bf4c ERR_QUEST_ACCEPTED_S 0 QUESTADDED 0x44 10
|
||||
0x8a 138 0xb4bf60 ERR_QUEST_COMPLETE_S 0 igQuestListComplete 0x44 10
|
||||
0x8b 139 0xb4bf74 ERR_QUEST_FAILED_S 0 igQuestFailed 0x44 10
|
||||
0x8c 140 0xb4bf88 ERR_QUEST_FAILED_BAG_FULL_S 0 igQuestFailed 0x44 10
|
||||
0x8d 141 0xb4bf9c ERR_QUEST_FAILED_MAX_COUNT_S 0 igQuestFailed 0x44 10
|
||||
0x8e 142 0xb4bfb0 ERR_QUEST_FAILED_LOW_LEVEL 0 igQuestFailed 0x44 10
|
||||
0x8f 143 0xb4bfc4 ERR_QUEST_FAILED_MISSING_ITEMS 0 igQuestFailed 0x44 10
|
||||
0x90 144 0xb4bfd8 ERR_QUEST_FAILED_WRONG_RACE 0 igQuestFailed 0x44 10
|
||||
0x91 145 0xb4bfec ERR_QUEST_FAILED_NOT_ENOUGH_MONEY 0 igQuestFailed 0x44 10
|
||||
0x92 146 0xb4c000 ERR_QUEST_ONLY_ONE_TIMED 0 igQuestFailed 0x44 10
|
||||
0x93 147 0xb4c014 ERR_QUEST_NEED_PREREQS 0 igQuestFailed 0x44 10
|
||||
0x94 148 0xb4c028 ERR_QUEST_ALREADY_ON 0 igQuestFailed 0x44 10
|
||||
0x95 149 0xb4c03c ERR_QUEST_REWARD_EXP_I 0 NONE 0x44 10
|
||||
0x96 150 0xb4c050 ERR_QUEST_REWARD_ITEM_S 0 NONE 0x44 10
|
||||
0x97 151 0xb4c064 ERR_QUEST_REWARD_MONEY_S 0 NONE 0x44 10
|
||||
0x98 152 0xb4c078 ERR_QUEST_MUST_CHOOSE 2 igQuestFailed 0x44 10
|
||||
0x99 153 0xb4c08c ERR_QUEST_LOG_FULL 2 igQuestFailed 0x44 10
|
||||
0x9a 154 0xb4c0a0 ERR_COMBAT_DAMAGE_SSI 0 NONE 0x44 10
|
||||
0x9b 155 0xb4c0b4 ERR_INSPECT_S 0 NONE 0x44 10
|
||||
0x9c 156 0xb4c0c8 ERR_CANT_USE_ITEM 2 NONE 0x33 10
|
||||
0x9d 157 0xb4c0dc ERR_MUST_EQUIP_ITEM 2 NONE 0x31 10
|
||||
0x9e 158 0xb4c0f0 ERR_PASSIVE_ABILITY 2 NONE 0x44 10
|
||||
0x9f 159 0xb4c104 ERR_2HSKILLNOTFOUND 2 NONE 0x44 10
|
||||
0xa0 160 0xb4c118 ERR_NO_ATTACK_TARGET 2 NONE 0x26 10
|
||||
0xa1 161 0xb4c12c ERR_INVALID_ATTACK_TARGET 2 NONE 0xb 10
|
||||
0xa2 162 0xb4c140 ERR_ATTACK_STUNNED 2 NONE 0x44 10
|
||||
0xa3 163 0xb4c154 ERR_ATTACK_PACIFIED 2 NONE 0x44 10
|
||||
0xa4 164 0xb4c168 ERR_ATTACK_MOUNTED 2 NONE 0x44 10
|
||||
0xa5 165 0xb4c17c ERR_ATTACK_FLEEING 2 NONE 0x44 10
|
||||
0xa6 166 0xb4c190 ERR_ATTACK_CONFUSED 2 NONE 0x44 10
|
||||
0xa7 167 0xb4c1a4 ERR_ATTACK_CHARMED 2 NONE 0x44 10
|
||||
0xa8 168 0xb4c1b8 ERR_ATTACK_DEAD 2 NONE 0x44 10
|
||||
0xa9 169 0xb4c1cc ERR_ATTACK_PREVENTED_BY_MECHANIC_S 2 NONE 0x44 10
|
||||
0xaa 170 0xb4c1e0 ERR_TAXISAMENODE 2 NONE 0x44 10
|
||||
0xab 171 0xb4c1f4 ERR_TAXINOSUCHPATH 1 NONE 0x44 10
|
||||
0xac 172 0xb4c208 ERR_TAXIUNSPECIFIEDSERVERERROR 2 NONE 0x44 10
|
||||
0xad 173 0xb4c21c ERR_TAXINOTENOUGHMONEY 1 NONE 0x36 10
|
||||
0xae 174 0xb4c230 ERR_TAXITOOFARAWAY 1 NONE 0x44 10
|
||||
0xaf 175 0xb4c244 ERR_TAXINOVENDORNEARBY 1 NONE 0x44 10
|
||||
0xb0 176 0xb4c258 ERR_TAXINOTVISITED 1 NONE 0x44 10
|
||||
0xb1 177 0xb4c26c ERR_TAXIPLAYERBUSY 2 NONE 0x44 10
|
||||
0xb2 178 0xb4c280 ERR_TAXIPLAYERALREADYMOUNTED 2 NONE 0x44 10
|
||||
0xb3 179 0xb4c294 ERR_TAXIPLAYERSHAPESHIFTED 2 NONE 0x44 10
|
||||
0xb4 180 0xb4c2a8 ERR_TAXIPLAYERMOVING 1 NONE 0x44 10
|
||||
0xb5 181 0xb4c2bc ERR_TAXINOPATHS 2 NONE 0x44 10
|
||||
0xb6 182 0xb4c2d0 ERR_TAXINOTSTANDING 1 NONE 0x44 10
|
||||
0xb7 183 0xb4c2e4 ERR_NO_REPLY_TARGET 0 NONE 0x44 10
|
||||
0xb8 184 0xb4c2f8 ERR_GENERIC_NO_TARGET 2 NONE 0x2d 10
|
||||
0xb9 185 0xb4c30c ERR_INITIATE_TRADE_S 0 NONE 0x44 10
|
||||
0xba 186 0xb4c320 ERR_TRADE_REQUEST_S 0 LEVELUP 0x44 10
|
||||
0xbb 187 0xb4c334 ERR_TRADE_BLOCKED_S 0 NONE 0x44 10
|
||||
0xbc 188 0xb4c348 ERR_TRADE_TARGET_DEAD 2 NONE 0x44 10
|
||||
0xbd 189 0xb4c35c ERR_TRADE_TOO_FAR 2 NONE 0x44 10
|
||||
0xbe 190 0xb4c370 ERR_TRADE_CANCELLED 1 NONE 0x44 10
|
||||
0xbf 191 0xb4c384 ERR_TRADE_COMPLETE 1 NONE 0x44 10
|
||||
0xc0 192 0xb4c398 ERR_TRADE_BAG_FULL 2 NONE 0x44 10
|
||||
0xc1 193 0xb4c3ac ERR_TRADE_TARGET_BAG_FULL 2 NONE 0x44 10
|
||||
0xc2 194 0xb4c3c0 ERR_TRADE_MAX_COUNT_EXCEEDED 2 NONE 0x44 10
|
||||
0xc3 195 0xb4c3d4 ERR_TRADE_TARGET_MAX_COUNT_EXCEEDED 2 NONE 0x44 10
|
||||
0xc4 196 0xb4c3e8 ERR_ALREADY_TRADING 2 NONE 0x44 10
|
||||
0xc5 197 0xb4c3fc ERR_MOUNT_INVALIDMOUNTEE 2 NONE 0x44 10
|
||||
0xc6 198 0xb4c410 ERR_MOUNT_TOOFARAWAY 2 NONE 0x44 10
|
||||
0xc7 199 0xb4c424 ERR_MOUNT_ALREADYMOUNTED 2 NONE 0x44 10
|
||||
0xc8 200 0xb4c438 ERR_MOUNT_NOTMOUNTABLE 2 NONE 0x44 10
|
||||
0xc9 201 0xb4c44c ERR_MOUNT_NOTYOURPET 2 NONE 0x44 10
|
||||
0xca 202 0xb4c460 ERR_MOUNT_OTHER 2 NONE 0x44 10
|
||||
0xcb 203 0xb4c474 ERR_MOUNT_LOOTING 2 NONE 0x44 10
|
||||
0xcc 204 0xb4c488 ERR_MOUNT_RACECANTMOUNT 2 NONE 0x44 10
|
||||
0xcd 205 0xb4c49c ERR_MOUNT_SHAPESHIFTED 2 NONE 0x44 10
|
||||
0xce 206 0xb4c4b0 ERR_DISMOUNT_NOPET 2 NONE 0x44 10
|
||||
0xcf 207 0xb4c4c4 ERR_DISMOUNT_NOTMOUNTED 2 NONE 0x44 10
|
||||
0xd0 208 0xb4c4d8 ERR_DISMOUNT_NOTYOURPET 2 NONE 0x44 10
|
||||
0xd1 209 0xb4c4ec ERR_SPELL_FAILED_TOTEMS 2 NONE 0x44 10
|
||||
0xd2 210 0xb4c500 ERR_SPELL_FAILED_REAGENTS 2 NONE 0x44 10
|
||||
0xd3 211 0xb4c514 ERR_SPELL_FAILED_REAGENTS_GENERIC 2 NONE 0x44 10
|
||||
0xd4 212 0xb4c528 ERR_SPELL_FAILED_EQUIPPED_ITEM 2 NONE 0x44 10
|
||||
0xd5 213 0xb4c53c ERR_SPELL_FAILED_EQUIPPED_ITEM_CLASS_S 2 NONE 0x44 10
|
||||
0xd6 214 0xb4c550 ERR_SPELL_FAILED_SHAPESHIFT_FORM_S 2 NONE 0x44 10
|
||||
0xd7 215 0xb4c564 ERR_BADATTACKFACING 2 NONE 0x44 10
|
||||
0xd8 216 0xb4c578 ERR_BADATTACKPOS 2 NONE 0x44 10
|
||||
0xd9 217 0xb4c58c ERR_CHEST_IN_USE 2 NONE 0x34 10
|
||||
0xda 218 0xb4c5a0 ERR_USE_CANT_OPEN 2 NONE 0x44 10
|
||||
0xdb 219 0xb4c5b4 ERR_USE_LOCKED 2 NONE 0x3d 10
|
||||
0xdc 220 0xb4c5c8 ERR_DOOR_LOCKED 2 NONE 0x3d 10
|
||||
0xdd 221 0xb4c5dc ERR_BUTTON_LOCKED 2 NONE 0x3d 10
|
||||
0xde 222 0xb4c5f0 ERR_USE_LOCKED_WITH_ITEM_S 2 NONE 0x44 10
|
||||
0xdf 223 0xb4c604 ERR_USE_LOCKED_WITH_SPELL_S 2 NONE 0x44 10
|
||||
0xe0 224 0xb4c618 ERR_USE_LOCKED_WITH_SPELL_KNOWN_SI 2 NONE 0x44 10
|
||||
0xe1 225 0xb4c62c ERR_USE_TOO_FAR 2 NONE 0x39 10
|
||||
0xe2 226 0xb4c640 ERR_USE_BAD_ANGLE 2 NONE 0x44 10
|
||||
0xe3 227 0xb4c654 ERR_USE_OBJECT_MOVING 2 NONE 0x44 10
|
||||
0xe4 228 0xb4c668 ERR_USE_SPELL_FOCUS 1 NONE 0x44 10
|
||||
0xe5 229 0xb4c67c ERR_USE_DESTROYED 2 NONE 0x44 10
|
||||
0xe6 230 0xb4c690 ERR_SET_LOOT_FREEFORALL 0 NONE 0x44 10
|
||||
0xe7 231 0xb4c6a4 ERR_SET_LOOT_ROUNDROBIN 0 NONE 0x44 10
|
||||
0xe8 232 0xb4c6b8 ERR_SET_LOOT_MASTER 0 NONE 0x44 10
|
||||
0xe9 233 0xb4c6cc ERR_SET_LOOT_GROUP 0 NONE 0x44 10
|
||||
0xea 234 0xb4c6e0 ERR_SET_LOOT_NBG 0 NONE 0x44 10
|
||||
0xeb 235 0xb4c6f4 ERR_SET_LOOT_THRESHOLD_S 0 NONE 0x44 10
|
||||
0xec 236 0xb4c708 ERR_NEW_LOOT_MASTER_S 0 NONE 0x44 10
|
||||
0xed 237 0xb4c71c ERR_SPECIFY_MASTER_LOOTER 2 NONE 0x44 10
|
||||
0xee 238 0xb4c730 ERR_TAME_FAILED 2 NONE 0x44 10
|
||||
0xef 239 0xb4c744 ERR_CHAT_WHILE_DEAD 2 NONE 0x44 10
|
||||
0xf0 240 0xb4c758 ERR_CHAT_WRONG_FACTION 0 NONE 0x44 10
|
||||
0xf1 241 0xb4c76c ERR_CHAT_PLAYER_NOT_FOUND_S 0 NONE 0x44 10
|
||||
0xf2 242 0xb4c780 ERR_NEWTAXIPATH 1 TaxiNodeDiscovered 0x44 10
|
||||
0xf3 243 0xb4c794 ERR_NO_PET 2 NONE 0x44 10
|
||||
0xf4 244 0xb4c7a8 ERR_NOTYOURPET 2 NONE 0x44 10
|
||||
0xf5 245 0xb4c7bc ERR_PET_NOT_RENAMEABLE 2 NONE 0x44 10
|
||||
0xf6 246 0xb4c7d0 ERR_NULL_PETNAME 2 NONE 0x44 10
|
||||
0xf7 247 0xb4c7e4 ERR_INVALID_PETNAME 2 NONE 0x44 10
|
||||
0xf8 248 0xb4c7f8 ERR_QUEST_OBJECTIVE_COMPLETE_S 1 NONE 0x44 10
|
||||
0xf9 249 0xb4c80c ERR_QUEST_UNKNOWN_COMPLETE 1 NONE 0x44 10
|
||||
0xfa 250 0xb4c820 ERR_QUEST_ADD_KILL_SII 1 NONE 0x44 10
|
||||
0xfb 251 0xb4c834 ERR_QUEST_ADD_FOUND_SII 1 NONE 0x44 10
|
||||
0xfc 252 0xb4c848 ERR_QUEST_ADD_ITEM_SII 1 NONE 0x44 10
|
||||
0xfd 253 0xb4c85c ERR_CANNOTCREATEDIRECTORY 2 NONE 0x44 10
|
||||
0xfe 254 0xb4c870 ERR_CANNOTCREATEFILE 2 NONE 0x44 10
|
||||
0xff 255 0xb4c884 ERR_PLAYER_WRONG_FACTION 2 NONE 0x44 10
|
||||
0x100 256 0xb4c898 ERR_BANKSLOT_FAILED_TOO_MANY 2 NONE 0x44 10
|
||||
0x101 257 0xb4c8ac ERR_BANKSLOT_INSUFFICIENT_FUNDS 2 NONE 0x16 10
|
||||
0x102 258 0xb4c8c0 ERR_BANKSLOT_NOTBANKER 2 NONE 0x44 10
|
||||
0x103 259 0xb4c8d4 ERR_FRIEND_DB_ERROR 0 NONE 0x44 10
|
||||
0x104 260 0xb4c8e8 ERR_FRIEND_LIST_FULL 0 NONE 0x44 10
|
||||
0x105 261 0xb4c8fc ERR_FRIEND_ADDED_S 0 NONE 0x44 10
|
||||
0x106 262 0xb4c910 ERR_FRIEND_ONLINE_SS 0 FRIENDJOINGAME 0x44 10
|
||||
0x107 263 0xb4c924 ERR_FRIEND_OFFLINE_S 0 NONE 0x44 10
|
||||
0x108 264 0xb4c938 ERR_FRIEND_NOT_FOUND 0 NONE 0x44 10
|
||||
0x109 265 0xb4c94c ERR_FRIEND_WRONG_FACTION 0 NONE 0x44 10
|
||||
0x10a 266 0xb4c960 ERR_FRIEND_REMOVED_S 0 NONE 0x44 10
|
||||
0x10b 267 0xb4c974 ERR_FRIEND_ERROR 0 NONE 0x44 10
|
||||
0x10c 268 0xb4c988 ERR_FRIEND_ALREADY_S 0 NONE 0x44 10
|
||||
0x10d 269 0xb4c99c ERR_FRIEND_SELF 0 NONE 0x44 10
|
||||
0x10e 270 0xb4c9b0 ERR_IGNORE_FULL 0 NONE 0x44 10
|
||||
0x10f 271 0xb4c9c4 ERR_IGNORE_SELF 0 NONE 0x44 10
|
||||
0x110 272 0xb4c9d8 ERR_IGNORE_NOT_FOUND 0 NONE 0x44 10
|
||||
0x111 273 0xb4c9ec ERR_IGNORE_ALREADY_S 0 NONE 0x44 10
|
||||
0x112 274 0xb4ca00 ERR_IGNORE_ADDED_S 0 NONE 0x44 10
|
||||
0x113 275 0xb4ca14 ERR_IGNORE_REMOVED_S 0 NONE 0x44 10
|
||||
0x114 276 0xb4ca28 ERR_IGNORE_AMBIGUOUS 0 NONE 0x44 10
|
||||
0x115 277 0xb4ca3c ERR_ONLY_ONE_BOLT 2 NONE 0x44 10
|
||||
0x116 278 0xb4ca50 ERR_ONLY_ONE_AMMO 2 NONE 0x44 10
|
||||
0x117 279 0xb4ca64 ERR_SPELL_FAILED_EQUIPPED_SPECIFIC_ITEM 2 NONE 0x44 10
|
||||
0x118 280 0xb4ca78 ERR_WRONG_BAG_TYPE_SUBCLASS 2 NONE 0x1c 10
|
||||
0x119 281 0xb4ca8c ERR_CANT_WRAP_STACKABLE 2 NONE 0x44 10
|
||||
0x11a 282 0xb4caa0 ERR_CANT_WRAP_EQUIPPED 2 NONE 0x44 10
|
||||
0x11b 283 0xb4cab4 ERR_CANT_WRAP_WRAPPED 2 NONE 0x44 10
|
||||
0x11c 284 0xb4cac8 ERR_CANT_WRAP_BOUND 2 NONE 0x44 10
|
||||
0x11d 285 0xb4cadc ERR_CANT_WRAP_UNIQUE 2 NONE 0x44 10
|
||||
0x11e 286 0xb4caf0 ERR_CANT_WRAP_BAGS 2 NONE 0x44 10
|
||||
0x11f 287 0xb4cb04 ERR_OUT_OF_MANA 2 NONE 0xf 10
|
||||
0x120 288 0xb4cb18 ERR_OUT_OF_RAGE 2 NONE 0x3f 10
|
||||
0x121 289 0xb4cb2c ERR_OUT_OF_FOCUS 2 NONE 0x44 10
|
||||
0x122 290 0xb4cb40 ERR_OUT_OF_ENERGY 2 NONE 0x40 10
|
||||
0x123 291 0xb4cb54 ERR_OUT_OF_HEALTH 2 NONE 0x44 10
|
||||
0x124 292 0xb4cb68 ERR_LOOT_GONE 2 NONE 0x44 10
|
||||
0x125 293 0xb4cb7c ERR_MOUNT_FORCEDDISMOUNT 2 NONE 0x44 10
|
||||
0x126 294 0xb4cb90 ERR_AUTOFOLLOW_TOO_FAR 2 NONE 0x44 10
|
||||
0x127 295 0xb4cba4 ERR_UNIT_NOT_FOUND 2 NONE 0x44 10
|
||||
0x128 296 0xb4cbb8 ERR_INVALID_FOLLOW_TARGET 2 NONE 0x44 10
|
||||
0x129 297 0xb4cbcc ERR_INVALID_INSPECT_TARGET 2 NONE 0x44 10
|
||||
0x12a 298 0xb4cbe0 ERR_GUILDEMBLEM_SUCCESS 2 NONE 0x44 10
|
||||
0x12b 299 0xb4cbf4 ERR_GUILDEMBLEM_INVALID_TABARD_COLORS 2 NONE 0x44 10
|
||||
0x12c 300 0xb4cc08 ERR_GUILDEMBLEM_NOGUILD 2 NONE 0x44 10
|
||||
0x12d 301 0xb4cc1c ERR_GUILDEMBLEM_NOTGUILDMASTER 2 NONE 0x44 10
|
||||
0x12e 302 0xb4cc30 ERR_GUILDEMBLEM_NOTENOUGHMONEY 2 NONE 0x28 10
|
||||
0x12f 303 0xb4cc44 ERR_GUILDEMBLEM_INVALIDVENDOR 2 NONE 0x44 10
|
||||
0x130 304 0xb4cc58 ERR_EMBLEMERROR_NOTABARDGEOSET 2 NONE 0x44 10
|
||||
0x131 305 0xb4cc6c ERR_SPELL_OUT_OF_RANGE 2 NONE 0x2e 10
|
||||
0x132 306 0xb4cc80 ERR_COMMAND_NEEDS_TARGET 0 NONE 0x44 10
|
||||
0x133 307 0xb4cc94 ERR_NOAMMO_S 2 NONE 0x1 10
|
||||
0x134 308 0xb4cca8 ERR_TOOBUSYTOFOLLOW 2 NONE 0x44 10
|
||||
0x135 309 0xb4ccbc ERR_DUEL_REQUESTED 0 LEVELUP 0x44 10
|
||||
0x136 310 0xb4ccd0 ERR_DUEL_CANCELLED 1 NONE 0x44 10
|
||||
0x137 311 0xb4cce4 ERR_DEATHBINDALREADYBOUND 2 NONE 0x44 10
|
||||
0x138 312 0xb4ccf8 ERR_DEATHBIND_SUCCESS_S 0 NONE 0x44 10
|
||||
0x139 313 0xb4cd0c ERR_NOEMOTEWHILERUNNING 2 NONE 0x44 10
|
||||
0x13a 314 0xb4cd20 ERR_ZONE_EXPLORED 1 NONE 0x44 10
|
||||
0x13b 315 0xb4cd34 ERR_ZONE_EXPLORED_XP 0 NONE 0x44 10
|
||||
0x13c 316 0xb4cd48 ERR_INVALID_ITEM_TARGET 2 NONE 0x33 10
|
||||
0x13d 317 0xb4cd5c ERR_IGNORING_YOU_S 0 NONE 0x44 10
|
||||
0x13e 318 0xb4cd70 ERR_FISH_NOT_HOOKED 1 NONE 0x44 10
|
||||
0x13f 319 0xb4cd84 ERR_FISH_ESCAPED 1 NONE 0x44 10
|
||||
0x140 320 0xb4cd98 ERR_SPELL_FAILED_NOTUNSHEATHED 1 NONE 0x44 10
|
||||
0x141 321 0xb4cdac ERR_PETITION_OFFERED_S 0 NONE 0x44 10
|
||||
0x142 322 0xb4cdc0 ERR_PETITION_SIGNED 0 igPlayerInviteAccept 0x44 10
|
||||
0x143 323 0xb4cdd4 ERR_PETITION_SIGNED_S 0 igPlayerInviteAccept 0x44 10
|
||||
0x144 324 0xb4cde8 ERR_PETITION_DECLINED_S 0 igPlayerInviteDecline 0x44 10
|
||||
0x145 325 0xb4cdfc ERR_PETITION_ALREADY_SIGNED 2 igPlayerInviteDecline 0x44 10
|
||||
0x146 326 0xb4ce10 ERR_PETITION_IN_GUILD 2 igPlayerInviteDecline 0x44 10
|
||||
0x147 327 0xb4ce24 ERR_PETITION_CREATOR 2 igPlayerInviteDecline 0x44 10
|
||||
0x148 328 0xb4ce38 ERR_PETITION_NOT_ENOUGH_SIGNATURES 2 igPlayerInviteDecline 0x44 10
|
||||
0x149 329 0xb4ce4c ERR_PETITION_NOT_SAME_SERVER 2 igPlayerInviteDecline 0x44 10
|
||||
0x14a 330 0xb4ce60 ERR_SPELL_UNLEARNED_S 0 NONE 0x44 10
|
||||
0x14b 331 0xb4ce74 ERR_PET_SPELL_ROOTED 2 NONE 0x44 10
|
||||
0x14c 332 0xb4ce88 ERR_PET_SPELL_AFFECTING_COMBAT 2 NONE 0x44 10
|
||||
0x14d 333 0xb4ce9c ERR_PET_SPELL_OUT_OF_RANGE 2 NONE 0x44 10
|
||||
0x14e 334 0xb4ceb0 ERR_PET_SPELL_NOT_BEHIND 2 NONE 0x44 10
|
||||
0x14f 335 0xb4cec4 ERR_PET_SPELL_TARGETS_DEAD 2 NONE 0x44 10
|
||||
0x150 336 0xb4ced8 ERR_PET_SPELL_DEAD 2 NONE 0x44 10
|
||||
0x151 337 0xb4ceec ERR_PET_SPELL_NOPATH 2 NONE 0x44 10
|
||||
0x152 338 0xb4cf00 ERR_ITEM_CANT_BE_DESTROYED 2 NONE 0x44 10
|
||||
0x153 339 0xb4cf14 ERR_TICKET_ALREADY_EXISTS 2 NONE 0x44 10
|
||||
0x154 340 0xb4cf28 ERR_TICKET_CREATE_ERROR 2 NONE 0x44 10
|
||||
0x155 341 0xb4cf3c ERR_TICKET_UPDATE_ERROR 2 NONE 0x44 10
|
||||
0x156 342 0xb4cf50 ERR_TICKET_DB_ERROR 2 NONE 0x44 10
|
||||
0x157 343 0xb4cf64 ERR_TICKET_NO_TEXT 2 NONE 0x44 10
|
||||
0x158 344 0xb4cf78 ERR_OBJECT_IS_BUSY 2 NONE 0x44 10
|
||||
0x159 345 0xb4cf8c ERR_EXHAUSTION_WELLRESTED 0 NONE 0x44 10
|
||||
0x15a 346 0xb4cfa0 ERR_EXHAUSTION_RESTED 0 NONE 0x44 10
|
||||
0x15b 347 0xb4cfb4 ERR_EXHAUSTION_NORMAL 0 NONE 0x44 10
|
||||
0x15c 348 0xb4cfc8 ERR_EXHAUSTION_TIRED 0 NONE 0x44 10
|
||||
0x15d 349 0xb4cfdc ERR_EXHAUSTION_EXHAUSTED 0 NONE 0x44 10
|
||||
0x15e 350 0xb4cff0 ERR_NO_ITEMS_WHILE_SHAPESHIFTED 2 NONE 0x44 10
|
||||
0x15f 351 0xb4d004 ERR_CANT_INTERACT_SHAPESHIFTED 2 NONE 0x44 10
|
||||
0x160 352 0xb4d018 ERR_MAIL_QUEST_ITEM 2 NONE 0x3b 10
|
||||
0x161 353 0xb4d02c ERR_MAIL_BOUND_ITEM 2 NONE 0x3b 10
|
||||
0x162 354 0xb4d040 ERR_MAIL_CONJURED_ITEM 2 NONE 0x44 10
|
||||
0x163 355 0xb4d054 ERR_MAIL_BAG 2 NONE 0x44 10
|
||||
0x164 356 0xb4d068 ERR_MAIL_TO_SELF 2 NONE 0x44 10
|
||||
0x165 357 0xb4d07c ERR_MAIL_TARGET_NOT_FOUND 2 NONE 0x44 10
|
||||
0x166 358 0xb4d090 ERR_MAIL_DATABASE_ERROR 2 NONE 0x44 10
|
||||
0x167 359 0xb4d0a4 ERR_MAIL_SENT 1 NONE 0x44 10
|
||||
0x168 360 0xb4d0b8 ERR_NOT_HAPPY_ENOUGH 2 NONE 0x44 10
|
||||
0x169 361 0xb4d0cc ERR_USE_CANT_IMMUNE 2 NONE 0x44 10
|
||||
0x16a 362 0xb4d0e0 ERR_CANT_BE_DISENCHANTED 2 NONE 0x44 10
|
||||
0x16b 363 0xb4d0f4 ERR_CANT_USE_DISARMED 2 NONE 0x44 10
|
||||
0x16c 364 0xb4d108 ERR_AUCTION_QUEST_ITEM 2 NONE 0x3b 10
|
||||
0x16d 365 0xb4d11c ERR_AUCTION_BOUND_ITEM 2 NONE 0x3b 10
|
||||
0x16e 366 0xb4d130 ERR_AUCTION_CONJURED_ITEM 2 NONE 0x44 10
|
||||
0x16f 367 0xb4d144 ERR_AUCTION_WRAPPED_ITEM 2 NONE 0x44 10
|
||||
0x170 368 0xb4d158 ERR_AUCTION_LOOT_ITEM 2 NONE 0x44 10
|
||||
0x171 369 0xb4d16c ERR_AUCTION_BAG 2 NONE 0x44 10
|
||||
0x172 370 0xb4d180 ERR_AUCTION_DATABASE_ERROR 2 NONE 0x44 10
|
||||
0x173 371 0xb4d194 ERR_AUCTION_BID_OWN 2 NONE 0x44 10
|
||||
0x174 372 0xb4d1a8 ERR_AUCTION_BID_INCREMENT 2 NONE 0x44 10
|
||||
0x175 373 0xb4d1bc ERR_AUCTION_HIGHER_BID 2 NONE 0x44 10
|
||||
0x176 374 0xb4d1d0 ERR_AUCTION_MIN_BID 2 NONE 0x44 10
|
||||
0x177 375 0xb4d1e4 ERR_AUCTION_REPAIR_ITEM 2 NONE 0x44 10
|
||||
0x178 376 0xb4d1f8 ERR_AUCTION_STARTED 0 NONE 0x44 10
|
||||
0x179 377 0xb4d20c ERR_AUCTION_REMOVED 0 NONE 0x44 10
|
||||
0x17a 378 0xb4d220 ERR_AUCTION_OUTBID_S 0 NONE 0x44 10
|
||||
0x17b 379 0xb4d234 ERR_AUCTION_WON_S 0 NONE 0x44 10
|
||||
0x17c 380 0xb4d248 ERR_AUCTION_SOLD_S 0 NONE 0x44 10
|
||||
0x17d 381 0xb4d25c ERR_AUCTION_EXPIRED_S 0 NONE 0x44 10
|
||||
0x17e 382 0xb4d270 ERR_AUCTION_REMOVED_S 0 NONE 0x44 10
|
||||
0x17f 383 0xb4d284 ERR_AUCTION_BID_PLACED 0 NONE 0x44 10
|
||||
0x180 384 0xb4d298 ERR_LOGOUT_FAILED 2 NONE 0x44 10
|
||||
0x181 385 0xb4d2ac ERR_QUEST_PUSH_SUCCESS_S 0 NONE 0x44 10
|
||||
0x182 386 0xb4d2c0 ERR_QUEST_PUSH_INVALID_S 0 NONE 0x44 10
|
||||
0x183 387 0xb4d2d4 ERR_QUEST_PUSH_ACCEPTED_S 0 NONE 0x44 10
|
||||
0x184 388 0xb4d2e8 ERR_QUEST_PUSH_DECLINED_S 0 NONE 0x44 10
|
||||
0x185 389 0xb4d2fc ERR_QUEST_PUSH_TOO_FAR_S 0 NONE 0x44 10
|
||||
0x186 390 0xb4d310 ERR_QUEST_PUSH_BUSY_S 0 NONE 0x44 10
|
||||
0x187 391 0xb4d324 ERR_QUEST_PUSH_LOG_FULL_S 0 NONE 0x44 10
|
||||
0x188 392 0xb4d338 ERR_QUEST_PUSH_ONQUEST_S 0 NONE 0x44 10
|
||||
0x189 393 0xb4d34c ERR_QUEST_PUSH_ALREADY_DONE_S 0 NONE 0x44 10
|
||||
0x18a 394 0xb4d360 ERR_RAID_GROUP_ONLY 2 NONE 0x44 10
|
||||
0x18b 395 0xb4d374 ERR_RAID_GROUP_FULL 2 NONE 0x44 10
|
||||
0x18c 396 0xb4d388 ERR_PVP_KILL_HONORABLE 1 NONE 0x44 10
|
||||
0x18d 397 0xb4d39c ERR_PVP_KILL_DISHONORABLE 1 NONE 0x44 10
|
||||
0x18e 398 0xb4d3b0 ERR_SPELL_FAILED_ALREADY_AT_FULL_HEALTH 2 NONE 0x44 10
|
||||
0x18f 399 0xb4d3c4 ERR_SPELL_FAILED_ALREADY_AT_FULL_POWER_S 2 NONE 0x44 10
|
||||
0x190 400 0xb4d3d8 ERR_AUTOLOOT_MONEY_S 0 NONE 0x44 10
|
||||
0x191 401 0xb4d3ec ERR_GENERIC_STUNNED 2 NONE 0x44 10
|
||||
0x192 402 0xb4d400 ERR_TARGET_STUNNED 2 NONE 0x44 10
|
||||
0x193 403 0xb4d414 ERR_MUST_REPAIR_DURABILITY 2 NONE 0x44 10
|
||||
0x194 404 0xb4d428 ERR_RAID_YOU_JOINED 0 NONE 0x44 10
|
||||
0x195 405 0xb4d43c ERR_RAID_YOU_LEFT 0 NONE 0x44 10
|
||||
0x196 406 0xb4d450 ERR_RAID_MEMBER_ADDED_S 0 NONE 0x44 10
|
||||
0x197 407 0xb4d464 ERR_RAID_MEMBER_REMOVED_S 0 NONE 0x44 10
|
||||
0x198 408 0xb4d478 ERR_CLICK_ON_ITEM_TO_FEED 2 NONE 0x44 10
|
||||
0x199 409 0xb4d48c ERR_TOO_MANY_CHAT_CHANNELS 0 NONE 0x44 10
|
||||
0x19a 410 0xb4d4a0 ERR_LOOT_ROLL_PENDING 2 NONE 0x44 10
|
||||
0x19b 411 0xb4d4b4 ERR_LOOT_PLAYER_NOT_FOUND 2 NONE 0x44 10
|
||||
0x19c 412 0xb4d4c8 ERR_NOT_IN_RAID 0 NONE 0x44 10
|
||||
0x19d 413 0xb4d4dc ERR_LOGGING_OUT 2 NONE 0x44 10
|
||||
0x19e 414 0xb4d4f0 ERR_TARGET_LOGGING_OUT 2 NONE 0x44 10
|
||||
0x19f 415 0xb4d504 ERR_NOT_WHILE_MOUNTED 2 NONE 0x44 10
|
||||
0x1a0 416 0xb4d518 ERR_NOT_WHILE_SHAPESHIFTED 2 NONE 0x44 10
|
||||
0x1a1 417 0xb4d52c ERR_NOT_IN_COMBAT 2 NONE 0x44 10
|
||||
0x1a2 418 0xb4d540 ERR_NOT_WHILE_DISARMED 2 NONE 0x44 10
|
||||
0x1a3 419 0xb4d554 ERR_PET_BROKEN 2 NONE 0x44 10
|
||||
0x1a4 420 0xb4d568 ERR_TALENT_WIPE_ERROR 2 NONE 0x44 10
|
||||
0x1a5 421 0xb4d57c ERR_FEIGN_DEATH_RESISTED 2 NONE 0x44 10
|
||||
0x1a6 422 0xb4d590 ERR_MEETING_STONE_IN_QUEUE_S 0 NONE 0x44 10
|
||||
0x1a7 423 0xb4d5a4 ERR_MEETING_STONE_LEFT_QUEUE_S 0 NONE 0x44 10
|
||||
0x1a8 424 0xb4d5b8 ERR_MEETING_STONE_OTHER_MEMBER_LEFT 0 NONE 0x44 10
|
||||
0x1a9 425 0xb4d5cc ERR_MEETING_STONE_PARTY_KICKED_FROM_QUEUE 0 NONE 0x44 10
|
||||
0x1aa 426 0xb4d5e0 ERR_MEETING_STONE_MEMBER_STILL_IN_QUEUE 0 NONE 0x44 10
|
||||
0x1ab 427 0xb4d5f4 ERR_MEETING_STONE_SUCCESS 0 NONE 0x44 10
|
||||
0x1ac 428 0xb4d608 ERR_MEETING_STONE_IN_PROGRESS 0 NONE 0x44 10
|
||||
0x1ad 429 0xb4d61c ERR_MEETING_STONE_MEMBER_ADDED_S 0 NONE 0x44 10
|
||||
0x1ae 430 0xb4d630 ERR_MEETING_STONE_GROUP_FULL 2 NONE 0x44 10
|
||||
0x1af 431 0xb4d644 ERR_MEETING_STONE_NOT_LEADER 2 NONE 0x44 10
|
||||
0x1b0 432 0xb4d658 ERR_MEETING_STONE_INVALID_LEVEL 2 NONE 0x44 10
|
||||
0x1b1 433 0xb4d66c ERR_MEETING_STONE_MUST_BE_LEADER 2 NONE 0x44 10
|
||||
0x1b2 434 0xb4d680 ERR_MEETING_STONE_NO_RAID_GROUP 2 NONE 0x44 10
|
||||
0x1b3 435 0xb4d694 ERR_GUILDEMBLEM_SAME 2 NONE 0x44 10
|
||||
0x1b4 436 0xb4d6a8 ERR_EQUIP_TRADE_ITEM 2 NONE 0x44 10
|
||||
0x1b5 437 0xb4d6bc ERR_PVP_TOGGLE_ON 1 NONE 0x44 10
|
||||
0x1b6 438 0xb4d6d0 ERR_PVP_TOGGLE_OFF 1 NONE 0x44 10
|
||||
0x1b7 439 0xb4d6e4 ERR_GROUP_JOIN_BATTLEGROUND_DESERTERS 1 NONE 0x44 10
|
||||
0x1b8 440 0xb4d6f8 ERR_GROUP_JOIN_BATTLEGROUND_S 1 NONE 0x44 10
|
||||
0x1b9 441 0xb4d70c ERR_GROUP_JOIN_BATTLEGROUND_FAIL 2 NONE 0x44 10
|
||||
0x1ba 442 0xb4d720 ERR_GROUP_JOIN_BATTLEGROUND_TOO_MANY 2 NONE 0x44 10
|
||||
0x1bb 443 0xb4d734 ERR_INVALID_PROMOTION_CODE 2 NONE 0x44 10
|
||||
0x1bc 444 0xb4d748 ERR_BG_PLAYER_JOINED_SS 0 NONE 0x44 10
|
||||
0x1bd 445 0xb4d75c ERR_BG_PLAYER_LEFT_S 0 NONE 0x44 10
|
||||
0x1be 446 0xb4d770 ERR_RESTRICTED_ACCOUNT 2 NONE 0x44 10
|
||||
0x1bf 447 0xb4d784 ERR_PLAY_TIME_EXCEEDED 2 NONE 0x44 10
|
||||
0x1c0 448 0xb4d798 ERR_APPROACHING_PARTIAL_PLAY_TIME 0 NONE 0x44 10
|
||||
0x1c1 449 0xb4d7ac ERR_APPROACHING_NO_PLAY_TIME 0 NONE 0x44 10
|
||||
0x1c2 450 0xb4d7c0 ERR_UNHEALTHY_TIME 0 NONE 0x44 10
|
||||
0x1c3 451 0xb4d7d4 ERR_CHAT_RESTRICTED 0 NONE 0x44 10
|
||||
0x1c4 452 0xb4d7e8 ERR_MAIL_REACHED_CAP 2 NONE 0x44 10
|
||||
0x1c5 453 0xb4d7fc ERR_INVALID_RAID_TARGET 2 NONE 0x44 10
|
||||
0x1c6 454 0xb4d810 ERR_RAID_LEADER_READY_CHECK_START_S 0 NONE 0x44 10
|
||||
0x1c7 455 0xb4d824 ERR_READY_CHECK_IN_PROGRESS 2 NONE 0x44 10
|
||||
0x1c8 456 0xb4d838 ERR_DUNGEON_DIFFICULTY_FAILED 0 NONE 0x44 10
|
||||
0x1c9 457 0xb4d84c ERR_DUNGEON_DIFFICULTY_CHANGED_S 0 NONE 0x44 10
|
||||
0x1ca 458 0xb4d860 ERR_TRADE_WRONG_REALM 2 NONE 0x44 10
|
||||
0x1cb 459 0xb4d874 ERR_CHAT_PLAYER_AMBIGUOUS_S 0 NONE 0x44 10
|
||||
0x1cc 460 0xb4d888 ERR_LOOT_CANT_LOOT_THAT_NOW 2 NONE 0x44 10
|
||||
0x1cd 461 0xb4d89c ERR_LOOT_MASTER_INV_FULL 2 NONE 0x44 10
|
||||
0x1ce 462 0xb4d8b0 ERR_LOOT_MASTER_UNIQUE_ITEM 2 NONE 0x44 10
|
||||
0x1cf 463 0xb4d8c4 ERR_LOOT_MASTER_OTHER 2 NONE 0x44 10
|
||||
0x1d0 464 0xb4d8d8 ERR_FILTERING_YOU_S 0 NONE 0x44 10
|
||||
|
@@ -50,7 +50,7 @@
|
||||
# cfg-truncated / indirect-branch / iteration-cap = an edge the walk could not
|
||||
# resolve; such a row's arity is not covered by the soundness claim above.
|
||||
#
|
||||
# regenerate: ~/wre/pyvenv/bin/python re/audit/binding-shapes.py > re/audit/binding-shapes.tsv
|
||||
# regenerate (in wow-5875-re): python3 re/audit/binding-shapes.py > re/audit/binding-shapes.tsv
|
||||
name fn pair_va table_va table_kind argc argc_conf arity arity_conf kinds kinds_conf notes
|
||||
AbandonQuest 0x4dfe00 0x84b128 0x84b0f0 global 0 exact 0 exact () agree
|
||||
AbandonSkill 0x4d3ab0 0x849d08 0x849cf8 global 1 exact 0 exact () agree
|
||||
|
||||
|
+25
-8
@@ -5,7 +5,10 @@ against them on any day without a re-derivation. Not documentation, not state: d
|
||||
generator beside it in `scripts/`.
|
||||
|
||||
Nothing here is Blizzard content. A catalogue of *names* is not the thing named — the same reason
|
||||
`decisions/` may quote a byte address and `MAP.md` may name a file.
|
||||
a comment may quote a byte address and `docs/MAP.md` may name a file. The `1.12-*.tsv` files are
|
||||
the surface benilla's gates read; the three `1.12-event-*.tsv` / `1.12-message-catalog.tsv`
|
||||
tables are the inputs two of the generators run from, vendored from wow-5875-re so that
|
||||
regeneration needs no sibling checkout.
|
||||
|
||||
## `1.12-globals.tsv`
|
||||
|
||||
@@ -34,7 +37,7 @@ corpus:
|
||||
| origin | count | meaning |
|
||||
|---|---|---|
|
||||
| `engine` | 1,104 | the C client provides it. **benilla implements these in Rust.** |
|
||||
| `framexml` | 20,427 | the shipped UI defines it. **benilla transcribes these into `assets/ui/`.** |
|
||||
| `framexml` | 20,427 | the shipped UI defines it. **The stock files run off the player's own install** (`ui_script::reference_ui`). |
|
||||
| `lua` | 24 | Lua 5.0's own runtime. mlua gives us these. |
|
||||
|
||||
Of the `framexml` rows, **1,986 carry type `lod`**: defined by the shipped UI but absent from the
|
||||
@@ -48,9 +51,9 @@ wholesale: a name defined in shipped Lua is FrameXML's, and hardcoding it in Rus
|
||||
error even when it works.
|
||||
|
||||
**Regenerating** — `scripts/gen-reference-globals.py`, whose header carries the full method and
|
||||
the traps. It needs the director's 1.12 install and the sibling RE repo, so it is a manual
|
||||
regeneration like `genmap.sh`, not something CI can run. The artifact is stable: it describes a
|
||||
client that shipped in 2006.
|
||||
the traps. It needs a 1.12 install and the RE repo's runtime capture of the reference client's
|
||||
`_G` (`WOW_RE=<the RE repo>`), so it is a manual regeneration like `genmap.sh`, not something
|
||||
CI can run. The artifact is stable: it describes a client that shipped in 2006.
|
||||
|
||||
**Reading it** — `scripts/api-coverage.sh` is the instrument. It asks a real `UiScript::new()`
|
||||
what benilla exposes and reports `have / missing / beyond-1.12`, always with 1178's split.
|
||||
@@ -61,8 +64,20 @@ global that 1.12 does not have and nobody wrote down why.
|
||||
|
||||
wow-5875-re's binding-shape table (what each Lua verb takes and returns; decision 1842, gated by
|
||||
`ui_script/shape_gate.rs`) and the event table with the arguments each producer pushes
|
||||
(decision 2140, `scripts/gen-reference-events.py`, gated by `ui_script/event_shape_gate.rs`).
|
||||
Each file's header carries its column contract.
|
||||
(decision 2140, gated by `ui_script/event_shape_gate.rs`). Each file's header carries its column
|
||||
contract.
|
||||
|
||||
**Regenerating** — `1.12-shapes.tsv` is vendored as wow-5875-re writes it.
|
||||
`scripts/gen-reference-events.py` derives `1.12-events.tsv` from two more vendored tables,
|
||||
`1.12-event-catalog.tsv` (event id → name, off the client's name-pointer array) and
|
||||
`1.12-event-firesites.tsv` (every fire site with the format string it pushes), so it runs from
|
||||
any clone and reproduces the committed file byte for byte.
|
||||
|
||||
## `1.12-message-catalog.tsv`
|
||||
|
||||
The client's message registry (`0xb4b498`: id, key, kind, sound, chat type), vendored from
|
||||
wow-5875-re; `scripts/gen-message-catalog.py` re-shapes it into
|
||||
`crates/benilla-ui/src/messages/catalog.rs`, the table `CGGameUI::DisplayError` indexes.
|
||||
|
||||
## `1.12-verb-events.tsv`
|
||||
|
||||
@@ -79,7 +94,9 @@ getter's fire is a cache-miss re-query, a state event benilla's feeds fire off t
|
||||
site is attributed to a function against wow-re's verified extents (the ledgers' `size=`) plus the
|
||||
disassembly's padding boundaries, and a site past its candidate's extent is left out rather than
|
||||
mis-filed (decision 2257: four of the first table's rows were callbacks reached by pointer, or a
|
||||
site past an extent). It reads the sibling RE repo's disassembly, so it is manual, like the others.
|
||||
site past an extent). The fire sites and the shapes are the vendored tables above; the function
|
||||
names, the ledgers and the disassembly are the RE repo's (`WOW_RE=<the RE repo>`), so this one
|
||||
is a manual regeneration there.
|
||||
|
||||
**Reading it** — `crates/benilla-app/src/ui_script/verb_event_gate.rs` is the gate: for every
|
||||
pair whose verb benilla registers, the module that registers the verb fires the event, or the
|
||||
|
||||
@@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user