The script host now runs on benilla-lua alone. The vendored C Lua 5.1.5 (`third_party/lua-src`) and mlua are removed, about 17k lines. `vm_parity` checks benilla-lua against the answers the C build recorded on Linux and macOS, and `front_end` checks every corpus chunk's compiled shape against the C build's digest. `GRAMMAR.md` records each grammar change from 5.1, by client address.
A handler's `this`, `event` and `arg1`… are bound in one VM call. An event fire saves and restores `event`, while the handler runner saves only `this` (`0x704d50`).
Ours on the branch: VM fixes from review (hook dispatch counts, registers and frames around a collection, protected-call handlers, library errors) and `vm_parity`'s two-VM leftovers removed.
Tests: the full gate chain on the merged tree, `vm_parity` and `front_end` among them. Live: smoke and a stock-glue login. Load time is level with C Lua, and memory is within 7%.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
With no WoW install found, or a `Data` folder whose archives will not open, benilla booted into a window of textless buttons, since fonts, art and the stock interface all come from the install.
It now ends before any window exists. A native error dialog (`rfd`: AppKit, Win32, `zenity` on Linux) names every folder searched, or the folder and the archive error, and says where to put the install; then it exits 1. An unattended run prints the same text to stderr and exits without a dialog.
1.12.1 ends at a fatal dialog for a present but corrupt base archive (`0x403b6f`) and only logs a missing one (`0x403b00`); ending at no install at all is recorded as a `Deviation:`.
Tests cover both messages and the failing exit; both cases were run live unattended. Built on macOS, Linux and Windows.
Fixes#836🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A dev build maps a linked worktree's `benilla-config/` to the main checkout's, so a developer's worktrees share one settings folder. That included a worktree that declares its own account in `.probe-identity`: an attended run from it, or a scripted one without `WOW_UNATTENDED`, read and wrote the main checkout's CVars, bindings, macros and saved variables while logged in as another account. A branch with a wrong settings migration or scripted key presses changed the player's own client, with nothing to show it.
`local_state::shared_root` now keeps such a worktree's state in its own `benilla-config/`. A worktree without `.probe-identity`, and the main checkout, resolve as before. CONTRIBUTING and `.gitignore` say so.
Test: `the_state_root_is_the_folder_or_its_primary_checkout` adds a worktree with a `.probe-identity`, which keeps its own root, and a main checkout with one, which is unchanged; it fails without the change. `the_state_folder_lands_where_the_build_says` follows the rule.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock 1.12 glue screens are now the only glue screens, so benilla's native login, realm list, character select and create screens are deleted (−17k lines). So are the native switch, its census tests, the art-extent module and its extract command.
What still drove the native screens now presses the stock ones through their own Lua verbs (`capture/glue_press.rs`):
- the rig;
- `WOW_PROBE_CHARCREATE`;
- the `glue-login`, `glue-charcreate` and `glue-realmlist` captures.
The three world-entry tests run on the glue VM. The schedule's ambiguity ceiling drops from 3939 to 3348.
Live: smoke, a glue round trip, a stock pick, a rig teleport, a create and delete, all three captures, and the player build's boot.
Fixes#856🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The stock 1.12 GlueXML screens are now the default in every build: login, realm list, character select and create. benilla's native screens run only in a dev build with `WOW_GLUE_NATIVE=1`, or under the rig and capture runs, which still drive them, until the native screens are deleted. `WOW_GLUE_STOCK` is gone.
On the way:
- An unattended reconnect re-dials the kept realm and lists the roster. Under the stock screens it stalled at the realm list.
- `WOW_CHARSELECT_PICK` selects on the stock screen.
- `smoke.sh` counts the stock run's VMs and fonts.
The stock screens fix the native screens' defects listed below.
Tests cover the switch, the pick and the reconnect. Live: smoke, a kick and re-entry, the player build's boot.
Part of #856.
Fixes#849Fixes#850Fixes#834Fixes#812Fixes#360Fixes#463Fixes#79Fixes#977🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`docs/DEVIATIONS.md` lists every place benilla deliberately differs from 1.12.1, generated by `scripts/genmap.sh` at every land beside `docs/MAP.md`:
- The settings that ship off the reference default, with each one's reason, from the `Deviates` rows.
- Each `Deviation:` comment's first sentence, linked to where it lives, grouped by crate and module.
A test fails if a `Deviates` row takes a shape the generator would misread. README, METHOD and CONTRIBUTING link the doc.
Fixes#1289🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A fresh-clone walk-through as a new contributor found four gaps in the docs:
- No quick test loop: `scripts/check.sh` has nothing to check on a clean clone, and `cargo test -p <crate>` was not mentioned. "The loop" now says both.
- The doc-link lint is a full `cargo doc` of the workspace; METHOD and the script's header now say so.
- What to cite while a benilla-re gap is still open: the client address, with the benilla-re issue or pull request linked.
- When `reference/PIN` moves: only with a change that needs a newer benilla-re row, to a commit on its main. Stated in CONTRIBUTING and `reference/README.md`.
Docs only.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributors and their agents had no word on how to use the reference beyond a link.
- `AGENTS.md` sends an agent to benilla-re before it builds: clone it beside the repo and grep it, or look an address up on its site.
- `docs/CONTRIBUTING.md` gets "The reference": how to look a fact up (the site, `symbols/`, `tables/`, `systems/`), how to weigh a row's confidence, and that a gap or an error goes to benilla-re as an issue or a finding.
- `docs/METHOD.md`, the pull request template and the issue chooser say the same in a line each.
Docs only; `scripts/check.sh` is green.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
benilla's reference facts come from reading the 1.12.1 client at its bytes. That work is now public as [benilla-re](https://github.com/samwhosung/benilla-re): one row per function, global and field, a doc per system, and the data tables, each cited by client address.
This links it from the README, from METHOD's "The reference first" and from CONTRIBUTING's step on stating the reference fact, so a contributor knows where a client address leads. Docs only; no code changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An unattended run (`WOW_UNATTENDED`, `WOW_RIG`) resolved the same `benilla-config/` as a person. In a dev build, every linked worktree maps that to the primary checkout's folder. So a probe read the player's CVars, bindings and layout, and wrote its realm, settings and window positions back over them.
`local_state::home` now gives an unattended run a fresh folder of its own (`$TMPDIR/benilla-run-<pid>/benilla-config`) and prints it at boot. `BENILLA_HOME` still pins one, and a capture still resolves none. CONTRIBUTING's "Running it unattended" says so.
Test: `an_unattended_run_keeps_its_own_state_folder` checks the rig path, the override and the capture, and that the folder is never the resident one.
Fixes#975🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Player reports and the trace, in one batch.
- SetFactionInactive/SetFactionActive raise `UPDATE_FACTION`, so the list moves at once.
- NPC greetings: the latch, a dead unit, then the reaction pair with the active mover (`0x60c270`); a dead or ghost player hears none. Only a passing click re-tracks the NPC, and a window hello resets it (`0x60c3d1`).
- "You are now <rank> with <faction>." prints on first contact and rank change (`0x62c670`), in packet order, with the real gender.
- A standing packet prints its "increased by" line for the first entry only, never a hidden one (`0x4d57dd`).
- `WOW_MOVE_TRACE` carries each packet's bytes as hex; chat bodies are opt-in, and dot/bang commands and channel passwords are always redacted.
Tests cover each gate, the line order and the redaction.
Fixes#927Fixes#882Fixes#881Fixes#867Fixes#965Fixes#966Fixes#967🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A crate built on benilla through `run_with` has its own `benilla-config/`, but could not ask where it is: `local_state::home` is crate-private. A crate that ships an addon has to write it into that folder's `AddOns/`, where benilla discovers addons and resolves their art and fonts, so without the path it needed a fork.
- `benilla_app::config_dir()` answers `local_state::home()`, asked from `run_with` on, since a dev build resolves it from the launcher's folder set there.
- CONTRIBUTING's "Building on top" says a crate's files and addons go there, and names `run_with` and this folder as the two seams opened for crates alone.
- The example launcher logs its `AddOns/` folder through it, so the example, which clippy and the tests build, is the outside caller that keeps it public.
No 1.12.1 behaviour changes.
Fixes#807🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- `docs/CONTRIBUTING.md`, "What does not": what 1.12 client mods add (a new key binding, a spell queue, new Lua functions, graphics past 1.12's) is named as a feature 1.12.1 does not have, so that what benilla does is what 1.12.1 does.
- A new "Building on top" section: such a feature lives in a crate started through `benilla_app::run_with`, or in a fork. A client started that way says `extended` in its build line. The crate keeps its own settings. benilla makes a piece of itself public to a crate only when its own 1.12.1 work creates it, never for the crate alone, with no stable API: a crate pins the revision it builds on.
- "What gets in": whatever the stock 1.12.1 client loads from a patch, benilla loads.
- `docs/METHOD.md`'s rule for every change points there, where it said "Anything else is a fork."
Docs only; no code or platform seam touched.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
benilla's own interface now loads as one layer on top of the stock UI, as `docs/METHOD.md` now states: the core loads the stock FrameXML rows, the layer (`assets/ui/layer.toc`) loads after the last of them and before the third-party addons, and like FrameXML it has no AddOn API row and fires no `ADDON_LOADED` (the reference loads `FrameXML.toc` outside `AddOn_Load`, `UI_Init` `0x48fbf0`).
- Moved into the layer unchanged: `ScrollTemplates.xml`, `KeyBindingsPage.xml`, `OptionsFrame.xml`, `ContainerFrameAdapters.xml`, `SpellBookAdapters.xml`, `ScriptLogFrame.xml`. `GameMenuFrame.xml` stays the core's last row until #553.
- The host's Lua fixes to stock code (`FCF_ChatTabFadeFinished`, the `DurabilityFrame` re-seat) move into the layer's `FrameXMLFixes.xml`; the resize re-seat stays host-side as an engine adaptation.
- The core now loads `StatsFrame.xml` and runs `LocaleProperties.lua` once, from `UIParent.xml`, so its chain rows are exactly the stock `FrameXML.toc` rows but the menu; the order is #555.
- A dev build boots without the layer under `WOW_STOCK_UI=1`; the host's calls into the layer are guarded.
Verified: new tests for the layer's file list, the core list against the stock toc, the load position (a `_G` definition log), absence from the AddOn API and the stock-only boot, each failing with the change undone; a before/after dump of every global, frame, draw order and saved variable differs only by `StatsFrame` and the moved fixes' source; `scripts/gates.sh` and `scripts/smoke.sh` green, and a live logout/re-login under `WOW_STOCK_UI=1` clean. Built on macOS; no platform seam touched.
Part of #552Fixes#542Fixes#543🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A crate built on top of benilla now adds its own Bevy plugins through `benilla_app::run_with(build, extend)`: `extend` gets the client fully built, after the game plugins, the asset loaders and the `ExtractSchedule` executor flip, and before the probe fleet, which sees what it adds. `run` is `run_with` with nothing added, so the stock client is unchanged.
- A client started through `run_with` carries `extended` in its build id (`BuildId::summary`), so the startup banner, the crash report and the debug panel's footer all say so, and a report from an extended client reads apart from stock. The entry point sets the flag; a launcher leaves it to `..Default::default()`.
- `crates/benilla-app/examples/extended_launcher.rs` is a launcher with a plugin of its own, compiled by the gates. Its header is the shape a crate of its own copies: the manifest (benilla-app without default features and `dev` forwarded, the workspace's bevy, and the root's `[patch]` and `[profile.dev]`, which cargo reads from the root manifest only), the build script's stamp, and a `Cargo.lock` started from benilla's.
- `tests/launcher_shape.rs` holds that documented manifest to the root `Cargo.toml` and the `benilla` launcher's, so a patch or profile added later cannot leave the documented shape building a client that is not benilla's.
- `docs/CONTRIBUTING.md`: a feature 1.12.1 does not have lives in its own crate on top of benilla, started through `run_with`.
Fixes#483🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Logging in to an account or realm with no characters lands on the empty character list. Until now the login cycle created a male Human Warrior on the server, named `WOW_CHAR` or "One", on every login that found an empty roster, a typed one included and again after Change Realm.
- **The reference.** The client builds `CMSG_CHAR_CREATE` in one place, `0x5aac50`, reached only from the glue binding `CreateCharacter` (`0x4719e0` → `0x4711d0` → `0x46b290` → `0x5aac50`), which stock `CharacterCreate.lua` calls only from `CharacterCreate_Okay`. With an empty roster, stock `CharacterSelect.lua`'s `UpdateCharacterList` disables Enter World and Delete and shows Create New Character, which benilla's select screen already does.
- **The change.** The create-if-empty step is gone from the per-realm cycle, and `NetConfig`, which existed only for it, goes with it. The create screen's own path is untouched.
- **Scripted runs.** `WOW_CHAR` must now be on the account: a driverless run whose `WOW_CHAR` is missing fails at select, as it already did on a non-empty roster, and a player's run shows the list. `docs/CONTRIBUTING.md` says so, and names `WOW_PROBE_CHARCREATE` as the explicit way to make one.
Checked: `an_empty_account_parks_at_select_without_creating_a_character` drives the real login cycle against a fake realmd (the server half of SRP6) and a fake world server answering an empty roster. It fails with the old step restored (the client sends `CMSG_CHAR_CREATE` on its own) and passes without it. Live against a local vmangos on a fresh account with no characters: before, the server gained a level-1 "One"; after, the client parks on the empty list and the account still holds no character. With a `WOW_CHAR` not on the account, a driverless run exits at select, and nothing is created. Gates and the smoke run green on macOS; no platform seam touched.
Fixes#343🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A maintainer reviews it against the reference and usually finishes it on the contributor's branch
(a rebase, a fix, a test, a comment) rather than sending it back, so "Allow edits by maintainers"
stays ticked; it lands as one squash-merged commit with the contributor as its author, or, when
what lands would be mostly ours, as our own commit crediting them as co-author.
Docs only; no gate reads it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The gates of scripts/gates.sh that need neither the install nor a display (fmt, clippy, the
workspace tests without an install, the doc-link and render-pass lints, the player build and its
tests), on ubuntu-latest, so a red pull request is seen before anyone builds it. A branch of this
repository skips the job: it runs the full chain before it lands. The job reads the repository
only, keeps no credentials in the checkout, pins its two actions by commit, drops debuginfo for
the runner's disk and caches dependencies. gates.sh and CONTRIBUTING say what runs where.
actionlint passes; the system packages are the ones CONTRIBUTING names for Linux. The workflow's
first run is a manual one on main once it lands.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The probe skill was a Claude-only file, and its promises held on one machine: its shield needs an
account named probe and digits, which CONTRIBUTING never asks for, and its long-run advice was
macOS's. The switches every checkout has for an unattended run are plain text in CONTRIBUTING
now, read by any tool; METHOD points there, AGENTS.md drops the skills folder, and .gitignore
leaves .claude/ to each person.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A committed .claude/settings.json bound everyone who opens the repo in Claude Code: it switched
their auto memory off, and its one hook guarded the install against Claude's Edit and Write
tools only, never a write through the shell or by another agent. The rule it backed is enforced
for every tool by the code and by scripts/smoke.sh's install watch, which METHOD now cites. jq is
no longer a prerequisite, and .gitignore shares only the skills under .claude/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The workflow that closed every pull request on arrival is gone, the README's closed-doors
notice becomes a short Contributing section, and CONTRIBUTING.md says how to report a bug.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
465 tests loaded the stock interface off the install without declaring
it, and 25 addon tests assumed the install beside the corpus; each now
opens with `wow_data_or_skip!`. The gates run the workspace tests a
second time with `WOW_DATA=`, so a test that reads the install
undeclared fails at land instead of on a contributor's clone. The skip
log writes each line in one call so parallel tests do not interleave.
Verified: the workspace tests with `WOW_DATA=` in a checkout that has
the addon corpus pass 6972, fail 0, and log 1555 skips; on a fresh
clone of main before this change they failed 465.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
Until now this repo was an export of a private tree, pushed as squashed snapshots. From here on
it is the working tree itself, and the private repo is archived.
The root holds AGENTS.md and the Rust and GitHub files only. Everything an agent needs to orient
is in docs/: METHOD.md (the rules and how the project is built, 123 lines, project method only),
MAP.md (generated from the code at every land) and CONTRIBUTING.md (what gets in and how a
change is judged). One skill, `probe`, holds the procedure for running the client unattended
against the local server.
Brought over because the project needs it: reference/ (the 1.12 name catalogues 34 source
files read; the export failed its own tests without them), the gate chain and the pool
(scripts/), the hooks that enforce the install rule and the worktree rule, the full .gitignore,
the `cargo play` alias, and the original wording of eleven source files the export had reworded.
Left out: the decision records (archived with the private repo), the export tooling, the
operator's Discord and triage loop, the lab-laptop scripts and the instruments nothing
references, which live outside the repo now. What read the records is gone from the tooling:
no corpus lint, no records index, no `reserve` verb, no number-collision check.
Three hooks are gone: fmt-at-commit and the file-size nudge were redundant with the gates, and
the assets/ui guard is now a test (`assets_ui_does_not_grow`) that fails for everyone, not only
for a Claude Code session. The remaining pool hooks stand down on a machine without a pool.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>