- the lamppost halo stops stacking after a short-range teleport: bevy registers `add_visibility_class::<Mesh3d>` as an add hook that pushes the mesh's class id onto the entity's `VisibilityClass` list on every (re)add, nothing dedups the list, the required component survives a `Mesh3d` removal, and `check_visibility` queues an entity once per entry — so 1949's parking, which removes `Mesh3d` after thirty hidden frames and re-adds it the frame the part is shown, drew a part one more time per hide-then-show cycle; opaque parts hide it at the same depth and colour, an additive glow card adds itself to itself (×2 after the first teleport that hid the lamp for half a second, ×3 after the next), and a loading screen despawns the tiles and respawns fresh entities, which is why a far teleport and a relog never showed it; the unpark queues one dedup after the hook, the test registers bevy's own hook and requirement in a bare world and reads 2 entries without it and 1 with, and on the rig, with the away camera facing the lamp past a 177 yd far clip, a build without the dedup read `stacked_parts=278` after one hop back where the landed build reads 0 (2003, 2005); the instruments that excluded the other mechanisms stay — the inspector's glow-card line names the material's additive bit, fade/unlit flags, depth write, texture residency, tag alpha and live scale, `STACKED n` on the card and `stacked_parts=` on the probe line count the duplicated class lists, and screenshot and pick bursts now keep the rig window awake, because an occluded window recaptures one stale drawable and a day of A/Bs had read identical to the decimal
- `perf::blend_check` reads the half of a glow card's look the material cannot: per `Transparent3d` item, the pipeline descriptor's colour blend against the material's additive marker — the marker folds the radial alpha into the colour in the shader and gives the pipeline its pure (ONE, ONE) add, and 0071's wrong-pipeline class was immunised only for opaque batches; mismatches count into the perf pill as a red `blend xN` the frame they happen, ride the probe line as `blend_mismatch=`, and are tracked across frames and named — object, texture, which pipeline, PERSISTING; its own first harvest was the tracker: an additive material realized this frame is extracted, prepared and drawn this frame, and a tracker reading the `Added` event a frame late called every first draw a mismatch, so it runs after `AssetEventSystems` like bevy's own specialization check; and the hook-accumulation sweep states the class so it can be swept: bevy 0.18.1's insert path fires `on_add` only when the component was absent, `on_replace` on every outgoing value, `on_remove` on removal only — a push on `on_add` accumulates across remove→re-add, a free on `on_remove` leaks across an insert-overwrite; every hook in the bevy crates we link (the visibility class on `Mesh3d`/`Mesh2d`/`Sprite`/`Text2d`/`MeshletMesh3d`, the cluster class on every bevy light) and every removal of a hooked component in our tree was read, parking's `Mesh3d` is the only accumulating pair, the UI quad pools reuse entities by insert-over and fire no add hook, and the one asymmetric freer, `PropProbeSlot`, freed its probe slot on `on_remove` — an insert over a live slot leaked it for the session, held off by a remove-then-insert discipline at the one re-seat site; it frees on `on_replace` now (`RigSkin`'s shape), the re-seat is a plain insert, and a test overwrites a live slot and reads it freed (2005)
- the stance bar loads after everything the managed-position pass reads: the stock `ShapeshiftBar_OnLoad` shows the bar, its OnShow runs `UIParent_ManageFramePositions`, and the stock pass indexes `ReputationWatchBar` unguarded — our manifest seated the bar beside the other bar files where the reference's toc has it at l.74, after the reputation frame at l.57, so the pass raised at `UIParent.lua:1618` on every login and never reached the arm that seats the shelf: the red login error and the sliver above Defensive Stance (the shelf's plate at its XML anchors, a few pixels off its seat, healed by any bottom-bar toggle re-running the pass) were one defect from 1988, whose retired copy of the pass had guarded every read — and the three-form case 2000 could not reproduce; the bar sits at the manifest's tail right before `WorldStateFrame.xml`, whose own OnLoad runs the pass and indexes the shelf textures the stance bar declares — the reference's own reason for l.74 before l.89; a manifest test states the law with the pairs read off the pass's body, and `load_default_ui` now folds the VM's handler errors into its failures, because a raise one call below an OnLoad landed only in the VM's error list and shipped through the manifest test, the full-manifest harnesses and the smoke's WARN alike (2001)
- `UnitName` answers `UNKNOWNOBJECT`, never nil, for a resolved unit whose name is still in flight: a hunter casting Call Pet with the stable window closed got `PetStable.lua:129: attempt to concatenate a nil value`, because stock `PetStable_OnEvent` repaints on `UNIT_PET` without asking whether the frame is shown and the paint concatenates `UnitName("pet")` the instant `UnitExists("pet")` is true, while the pet's name is a `CMSG_PET_NAME_QUERY` round trip away; the reference's `UnitName 0x517020` pushes nil in exactly two cases — the `"player"` fast path (`0x517083`) over an empty name buffer, and a token that resolves to GUID 0 (`0x5170c0`) — and every other path ends in a string: `CGUnit_C::GetUnitName 0x609210` looks the pet up in `petnamecache.wdb` by `UNIT_FIELD_PETNUMBER`, re-sends the query on a miss or a stale stamp, and falls to `0x609324`, the GlobalString `UNKNOWNOBJECT` ("Unknown") read through `0x703bf0`, or the literal `"Unknown Being"` at `0x860fa4` when the global is missing or empty; the binding now reads the global out of the VM's `_G` at call time, so a translated `GlobalStrings.lua` translates this too, the stock file is untouched, and 1688's "nil for an unresolved name is faithful" is corrected — the reference paints `Unknown Level 58 Boar` into the hidden frame and the next repaint reads the name; `GameTooltip:SetUnit` still titles a pending-name unit with an empty line, a next move (2002)
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Important
Issues and pull requests are closed here. benilla is a solo project developed in a private tree; this repo is its export, published as squashed snapshots, so a PR here has nothing to land on. The best way to contribute is to join the Discord and report the bugs you find. Questions and ideas are welcome in the same place.
benilla speaks the original 1.12.1 protocol, so it connects to any server the real client could, and reads its game data at runtime from your own 1.12.1 install. Every file format and the network protocol are implemented from scratch, with no original client code, no third-party WoW crates, and no bundled game assets.
What works
- Formats: readers for the full asset stack (MPQ patch chain, BLP, DBC, ADT/WDT/WDL, M2, WMO), wired into Bevy as an asset source.
- World: streamed terrain out to the horizon, portal-culled WMOs with interior lighting, doodads and ground clutter, swimmable liquids, sky and weather, and the client's own day/night lighting, fog and gamma passes.
- Models: GPU-skinned M2s with the full animation controller, a near feature-complete particle system, ribbons, and animated gameobjects from doors to lifts.
- Characters: customization end to end, the armor texture composite, weapons with sheathing and enchant glows, shapeshift forms, stealth and mounts.
- Movement: a WoW-feel controller, networked movement in both directions, the server-granted modes from slow fall to roots, a follow camera with collision, boats, zeppelins and taxi flights.
- Networking: SRP6 auth through world-session crypto, the object mirror into the ECS, and live wire coverage from movement and chat through spells, party, quests, mail, trade, vendors, bank, loot, the auction house and PvP honor.
- UI: a from-scratch FrameXML + Lua engine driving the built-in interface, from the login and
character screens through the full HUD, the classic windows (guild, macros and key bindings
included), chat, nameplates, floating combat text and tooltips; third-party addons load from
a
benilla-config/AddOns/folder beside the executable (partial: AtlasLoot and Bagnon run, see below). - Combat: melee on the faithful swing law, ranged and Auto Shot, casting with GCD and cooldowns, combo points, crowd control that really holds you, and the spell visual pipeline.
- Audio: music, ambience and SFX under the client's own selection and crossfade rules, with interior and underwater transitions and zone reverb.
Where it's going
benilla is done when a 1.12.1 player can do everything here that they could in the original client, it looks and feels the same, and it runs from a download on Windows, Linux and macOS. No dates; the order is what is likely, not a promise.
- Battlegrounds and meeting stones, then the long tail of small features that separates a working client from a finished one.
- Addons, options and performance, ongoing.
- The no-brainer fixes from VanillaFixes, SuperWoW and the like.
- Playable downloads for Windows, Linux and macOS. Linux first.
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 is what development runs against, and cMaNGOS and the rest speak the same protocol.
WOW_DATA=/path/to/WoW/Data cargo run --release -p benilla
The server defaults to localhost:3724, the stock realmd auth port. Point WOW_HOST
at any IP or hostname, appending the auth port if yours is remapped
(WOW_HOST=play.example.com:5000). Credentials go in at the login screen, or set WOW_USER /
WOW_PASS to skip it.
Early inspiration and file format guidance came from the wowemulation-dev community, and warcraft-rs in particular.
benilla is an independent fan project, not affiliated with or endorsed by Blizzard Entertainment.
It ships no Blizzard content — no art, models, sounds, maps, MPQ contents or FrameXML; you
provide your own legally obtained 1.12.1 client. The interface code under
crates/benilla-app/assets/ui/ is ours, written to the client's own layout and API names so that
the windows look right and 1.12.1 addons find the names they expect.
World of Warcraft is a trademark of Blizzard Entertainment, Inc. Our own code is licensed under MIT or Apache 2.0, at your option.