//! The Lua scripting host — the engine-free VM that turns the arena/layout/order *model* into a //! live, addon-facing runtime (decision 0068). No Bevy, no GPU: this module embeds mlua's Lua 5.1 //! (the Classic Era version) and wires it to [`crate::widget`]'s frame arena, [`crate::layout`]'s //! anchor resolver, and [`crate::order`]'s draw-order traversal, exposing the WoW FrameScript object //! model and the slice-of stdlib addons expect. //! //! ## Ground truth //! //! - **RF-0023** (`wow-5875-re/system/ui/ui.md` §"The Lua frame object model"): a frame's Lua value //! is a table `T` with `T[0] = lightuserdata(handle)`, one *shared* metatable //! `__framescript_meta` whose `__index` dispatches by method name; named frames auto-publish to //! `_G` non-overwriting. See [`object`]. //! - **RF-0025** (`ui.md` §"FrameScript handler-firing context"): a handler is fired via `pcall` //! with `this`/`event`/`arg1..argN` set as **globals**, saved-then-restored around the call //! (nesting-safe). We also pass the *modern* `(self, event, ...)` arguments the Era addons expect //! (the transition-era client did both). See [`event`]. //! //! ## The `LUAI_MAXCSTACK` discipline (decision 0068, probe A) //! //! Probe A found the hard constraint that Rust must **never** hold thousands of persistent mlua //! handles (each owned `Table`/`Function` occupies a slot on mlua's reference thread, capped by the //! vendored build's `LUAI_MAXCSTACK = 8000`). So this host holds **zero** persistent Lua handles in //! Rust: every piece of Lua-side state — the two shared metatables, the two method tables, the //! wrapper cache (`id → wrapper table`), and every frame's script closures (`id → {name → fn}`) — //! lives in the Lua **registry** under named keys and is fetched transiently per call. The Rust-side //! [`Model`] is *plain data* (arena, layout inputs, id↔handle maps, region visuals, event //! registrations, errors) stored in `lua.app_data`, reachable from callbacks via `RefCell`-style //! dynamic borrows. Every callback takes a *short* borrow and drops it before re-entering Lua. //! //! ## Identity: ids, not handle bits //! //! RF-0023 stores the `CScriptObject*` in the lightuserdata. Benilla's [`crate::widget::FrameHandle`] //! is an opaque generational handle with private fields (no bit accessor), so we mint a stable `u32` //! **id** per handle and store *that* in the lightuserdata (`id as *mut c_void`); [`Model`] owns the //! `id ↔ handle` bijection. The id doubles as the [`crate::layout::Handle`] used by the layout graph //! (frames only; the screen root is the reserved id [`SCREEN`]). This is faithful to RF-0023's intent //! (an opaque identity in a lightuserdata) — only the *encoding* differs. mod action; mod action_bar_toggles; mod addon_message; mod auction; mod aura; mod backdrop; mod bank; mod battlefield_positions; mod battlefield_queue; mod battlefield_score; mod bind_confirm; mod binder; mod binding_abi; mod dialog_verbs; mod tutorial; mod worldmap_arrow; // The five camera views + FlipCameraYaw — the reference's `UIUtil\Camera.cpp` Lua surface. mod button; mod camera_view; mod channel; mod char_stats; mod chat_misc; mod chat_send; mod chat_types; mod chat_window; mod clip; mod colorselect; mod container; mod craft; mod cursor; mod death; pub mod diagnostics; mod editbox; pub mod instance; pub(crate) use editbox::{adopt_text_region, editbox_text_region_wrapper}; pub(crate) mod addon; pub mod addon_gate; mod client; mod cvars; mod dressup; mod duel; pub(crate) mod event; mod extract; mod follow; pub(crate) mod font; mod font_block; mod gm_ticket; mod gossip; mod guild; mod handler_prof; mod screenshot; mod tabard; pub use handler_prof::HandlerRow; /// The widget-method surface measurement — shared by the `dump_widget_methods` example and the /// widget-surface gate (decision 2142). mod surface; pub use surface::widget_method_census; mod inspect; mod item_stats; mod item_text; pub mod keybind; mod keyboard; mod layout; mod layout_cache; mod loot; mod loot_roll; mod lua50; mod macros; mod mail; mod measure; mod merchant; mod messageframe; mod minimap; pub mod nameplate; pub use nameplate::{PlateGeometry, PlateState}; mod model; mod modelframe; mod net_stats; pub(crate) mod object; pub use object::frame_kind_from_tag; mod party; mod pet; mod petition; mod pointer; mod pvp; mod quest; mod quest_log; pub(crate) mod region; /// The 19 Region-map methods, shared by frames and regions the way the reference shares them /// (decision 1501) — its header is the byte-verified chain and the bug that found the split. mod region_map; mod reputation; mod saved; mod scrollframe; mod session; mod shapeshift; mod simplehtml; mod skills; mod slash; mod slider; mod social; mod sound; mod spellbook; mod stable; mod statusbar; mod stdlib; mod summon; mod talent; mod taxi; mod tick; mod tooltip; mod tooltip_item; mod tooltip_spell; mod tooltip_unit; mod ui_errors; pub use tooltip_unit::TooltipTint; mod trade; mod tradeskill; mod trainer; mod types; mod unit; mod weapon_enchant; /// The `/who` list's seven-key sort chain and its comparator — its header is the whole /// mechanism, including why a repeated header click reverses (decision 2030). mod who_sort; mod worldmap; mod worldstate; mod worn_display; pub use action::{ActionSlot, ActionState, ActionUse}; pub use addon::AddOnInfo; pub use addon_message::{AddonDistribution, AddonSend}; pub use auction::{ AuctionBid, AuctionCategory, AuctionItemRow, AuctionListState, AuctionQuery, AuctionStartRequest, AuctionState, AuctionSubCategory, BIDDER, LIST, OWNER, SORT_KEYS, }; pub use aura::{AuraState, TrackingState}; pub use backdrop::{inset_atlas_bleed, pieces, Backdrop, BackdropPiece, Insets}; pub use bank::BankState; pub use battlefield_positions::{BattlefieldFlagView, BattlefieldPositionView}; pub use battlefield_queue::{BattlefieldListView, BattlefieldMapInfo, BattlefieldQueueSlot}; pub use battlefield_score::{BattlefieldScoreRow, BattlefieldScores, BattlefieldStatColumn}; pub use bind_confirm::PendingEquipAnswer; pub use camera_view::{CameraViewRequest, CAMERA_VIEW_COUNT}; pub use channel::{ChannelCommand, ZoneChannelRow}; pub use char_stats::{ weapon_subclass_skill, BankBagSlots, InvSlotView, InventorySlots, UnitCombatStats, BANK_BAG_SLOT_COUNT, INVENTORY_SLOT_COUNT, SKILL_DEFENSE, SKILL_UNARMED, }; pub use chat_misc::EmoteRequest; pub use chat_send::ChatSend; pub use chat_types::ChatTypeColor; pub use chat_window::{message_group_index, ChatWindowLook, MESSAGE_GROUPS}; pub use container::{ BagAutoStore, ContainerMove, ContainerSlot, ContainerState, EnchantView, PendingWrap, PetitionSlotView, RandomPropertyView, UiCursorMode, }; pub use craft::{CraftReagent, CraftRecipe, CraftState, CraftTooltip}; pub use cursor::money::coin_icon; pub use cursor::{ CursorAction, CursorItem, CursorMacro, CursorMerchantItem, CursorMoney, CursorPayload, CursorPetAction, CursorSpell, CursorStablePet, EnchantConfirm, WorldPick, EQUIPMENT_BAG, }; pub use cvars::{MultisampleFormat, CVAR_NAMEPLATE_ENEMIES, CVAR_NAMEPLATE_FRIENDS}; pub use death::{DeathAction, DeathUiState}; pub use dressup::DressUpIntent; pub use duel::DuelRequest; pub use follow::FollowRequest; pub use gm_ticket::{GmTicketIntent, GmTicketWrite}; pub use gossip::{GossipMenu, GossipOptionView, GossipQuestRow}; pub use guild::{ GuildMemberInfo, GuildRankEdit, GuildRankInfo, GuildRequest, GuildState, LastOnline, UnitGuild, MAX_RANKS, MIN_RANKS, RANK_RIGHT_BITS, }; pub use modelframe::ModelPaneFrame; pub use petition::{ validate_guild_name, PetitionRecordView, PetitionRequest, PetitionState, PETITION_TYPE_CHARTER, PETITION_TYPE_PETITION, }; pub use tabard::{ emblem_mask_path, TabardHost, TabardIntent, EMBLEM_MASK_TOKEN, TABARD_COUNTS, TABARD_CREATION_COST, }; pub use worldmap_arrow::ARROW_MODEL; pub(crate) use button::{set_label_font_justify_h_lua, LabelFont}; pub use inspect::{InspectView, UnitReach}; pub use item_stats::{item_usable, ItemSetView, ItemTemplateView, PlayerReqState}; pub use item_text::ItemTextState; pub use layout_cache::{FrameLayout, LayoutPoint}; pub use loot::{LootRow, LootState}; pub use loot_roll::{LootRollEntry, LootRollsState}; pub use macros::{MacroState, MacroView, MAX_MACROS, MAX_MACRO_BODY, MAX_MACRO_NAME}; pub use mail::{MailInboxRow, MailInvoice, MailSendRequest, MailState, StationeryView}; pub use measure::TextMeasure; pub use merchant::{ItemStatsHead, MerchantItem, MerchantState}; pub(crate) use minimap::apply_model_attrs as apply_minimap_model_attrs; pub(crate) use model::Model; pub use model::{FontProbe, TextureProbe, TextureSizeProbe}; pub use party::{PartyMemberInfo, PartyRequest, PartyState, RaidMemberInfo, SavedInstanceInfo}; pub use pet::{PetActionView, PetStats}; pub use pvp::{HonorState, InspectHonorData}; pub use quest::{ QuestAction, QuestItemView, QuestPanel, QuestRewardSpell, QuestSelect, QuestState, }; pub use quest_log::{QuestLogDetail, QuestLogEntryView, QuestLogObjectiveView, QuestLogState}; pub(crate) use region::{apply_font_parts, implicit_creation_anchor_lua}; pub use reputation::{FactionEntry, ReputationSend, ReputationState}; pub use session::SessionRequest; pub use shapeshift::ShapeshiftFormView; pub(crate) use simplehtml::{ apply_element_font_parts as apply_simplehtml_font_parts, element_of_xml_tag as simplehtml_element_of_xml_tag, }; pub use skills::{SkillEntry, SkillsState}; pub use social::{FriendInfo, SocialRequest, SocialState, WhoInfo}; pub use sound::SoundRequest; pub use spellbook::{ resolve_spell_by_name, PetBookState, SpellBookState, SpellSlotView, SpellTabView, }; pub use stable::{StableIntent, StablePetSlot, StableState, NUM_STABLE_SLOTS}; pub use summon::SummonConfirmUiState; pub use talent::{TalentPrereqView, TalentTabView, TalentUiState, TalentView}; pub use taxi::{TaxiNodeType, TaxiUiNode, TaxiUiState}; pub use tooltip_spell::SpellTooltipView; pub use trade::{TradeSideState, TradeSlotItem, TradeState, TRADE_SLOTS}; pub use tradeskill::{TradeSkillDifficulty, TradeSkillReagent, TradeSkillRecipe, TradeSkillState}; pub use trainer::{ TrainerAbilityReq, TrainerGroup, TrainerService, TrainerServiceCategory, TrainerSkillReq, TrainerState, TrainerTooltip, TRAINER_GROUP_KNOWN, }; pub use types::{ BlendMode, EditAction, EditBoxTextUi, EditOutcome, EditUnit, ExtractedQuad, FontObject, FontShadow, Gradient, JustifyH, JustifyV, LineMeasureRequest, MeasureRequest, Outline, QuadContent, ScriptValue, TexCoords, }; pub(crate) use types::{FontExplicit, MeasuredText, RegionData}; pub use unit::{ grey_band, level_reads_unknown, power_token, unit_is_grey, SelectionRequest, UnitState, }; pub use weapon_enchant::WeaponEnchant; pub use who_sort::{WhoSortChain, WhoSortKey}; pub use worldmap::{ WorldMapContinentView, WorldMapLandmarkView, WorldMapOverlayView, WorldMapState, WorldMapZoneView, }; pub use worldstate::WorldStateUiView; pub use worn_display::WornDisplay; use mlua::Lua; use crate::layout::Rect; use crate::order::ZTarget; use crate::widget::{FrameHandle, KindState}; // Registry key names — the only place Lua-side roots are kept alive (the MAXCSTACK discipline). const REG_FRAME_META: &str = "__benilla_frame_meta"; const REG_REGION_META: &str = "__benilla_region_meta"; const REG_FRAME_METHODS: &str = "__benilla_frame_methods"; const REG_REGION_METHODS: &str = "__benilla_region_methods"; /// The full table's registry key, exposed for the exhaustiveness gate in `tests::reference_surface` /// — the split's correctness is a property of what the VM HOLDS, not of the source that builds it. #[cfg(test)] pub(crate) const REG_REGION_METHODS_FOR_TEST: &str = REG_REGION_METHODS; /// The **title region's** method table + metatable — the 19 Region methods and nothing else. const REG_TITLE_METHODS: &str = "__benilla_title_methods"; const REG_TITLE_META: &str = "__benilla_title_meta"; /// The two region LEAF tables + metatables — Texture and FontString each answer their own map. const REG_TEXTURE_METHODS: &str = "__benilla_texture_methods"; const REG_TEXTURE_META: &str = "__benilla_texture_meta"; const REG_FONTSTRING_METHODS: &str = "__benilla_fontstring_methods"; const REG_FONTSTRING_META: &str = "__benilla_fontstring_meta"; /// The stdlib's out-of-the-box error handler, kept by identity so /// [`UiScript::dispatch_script_errors_to_handler`] can tell "nobody chose a handler" (skip — the /// default already reports into the host channel) from "FrameXML or an addon installed one" /// (dispatch — that is what the pair exists for). Stored at [`stdlib::install`] time, the one /// moment the default is known to be what `geterrorhandler()` answers. const REG_DEFAULT_ERRORHANDLER: &str = "__benilla_default_errorhandler"; /// The **Region method map** (`0xcf54b4`) — the 19 names every region leaf reaches through its own /// lookup's fallback, carved in wow-re `system/ui/scratch/font-object-lua-surface.md` and asserted /// as a SET by `tests::reference_surface`. Named here because two things need the same list: that /// test, and the title region's narrower method table (1250 §5). /// Names on **both** region leaves — and each leaf registers its own copy, so these are NOT on the /// Region map and must not be hoisted into it (wow-re /// `system/ui/scratch/texture-fontstring-method-split.md`, stated there as a trap in as many words). /// `GetDrawLayer` is the setter's pair on both leaves (Texture `0x79a6c0`, FontString `0x79c660`) /// and is here now — it was carved as absent, which was true and was a gap, not a decision; pfUI's /// `GetNoNameObject` reads it off every child of a frame it reskins. pub(crate) const REGION_LEAF_SHARED: [&str; 9] = [ "SetDrawLayer", "GetDrawLayer", "SetVertexColor", "SetAlpha", "GetAlpha", "Show", "Hide", "IsVisible", "IsShown", ]; /// **Texture-only.** Note `GetVertexColor` sits here while `SetVertexColor` is shared — an asymmetry /// no reasonable partition invents, and the carve calls it out. Also note the client's Texture map /// has `SetGradientAlpha` where FontString has `SetAlphaGradient`: a near-miss pair, and we install /// only the FontString one. /// /// **Every name here is the client's own now.** Three of ours were parked in this list pending a /// per-name check, and all three are gone: `SetRotation` (1.12 registers it only on PlayerModel, /// `0x84f1fc`/`0x505f00`, and the world-map arrow that justified it is a Model frame now — see /// `region/paint.rs` at its old site), `SetPortraitToTexture` (1.12 has it as an engine GLOBAL, /// never a Texture method — `region.rs` registers it there, and a test pins the absence), and /// `SetSize`, the Era geometry verb that was in neither client map and that nothing outside our /// own test scaffolding called (decision 2142's census). The 1244/1245 landing partitioned rather /// than pruned and said the pruning was a separate question per name; this is the answer to three /// of them. /// /// Going the other way: `GetBlendMode`, `SetTexCoordModifiesRect` and `GetTexCoordModifiesRect` /// joined the list, all three of them in the client's Texture map `0x87c128` (`0x79a890` / /// `0x79c080` / `0x79c120`), and all three of them missing here until then. pub(crate) const TEXTURE_ONLY_METHODS: [&str; 12] = [ "SetGradient", "SetGradientAlpha", "GetTexture", "SetTexture", "GetTexCoord", "SetTexCoord", "SetBlendMode", "GetBlendMode", "SetTexCoordModifiesRect", "GetTexCoordModifiesRect", "SetDesaturated", "GetVertexColor", ]; /// **FontString-only** — the font/text/justify/shadow block plus the string metrics. /// /// **Every name here is the client's own now.** Three of ours were parked in this list: /// `GetStringHeight` went first (1251 — byte-verified absent, and ours was a byte-identical /// duplicate of `GetHeight`), and `SetFormattedText` (not among the client's 32) and /// `SetSize` (the Era geometry verb the Texture list carried too) went with 2142's census, which /// found neither in the stock chain nor in either addon corpus. The era spelling of /// `SetFormattedText` is `SetText(format(fmt, ...))`; of `SetSize`, `SetWidth` + `SetHeight`. pub(crate) const FONTSTRING_ONLY_METHODS: [&str; 21] = [ "SetFont", "GetFont", "SetFontObject", "GetFontObject", "SetTextColor", "GetTextColor", "SetShadowColor", "GetShadowColor", "SetShadowOffset", "GetShadowOffset", "SetJustifyH", "GetJustifyH", "SetJustifyV", "GetJustifyV", "SetText", "GetText", "SetTextHeight", "GetStringWidth", "SetNonSpaceWrap", "CanNonSpaceWrap", "SetAlphaGradient", ]; pub(crate) const REGION_MAP_METHODS: [&str; 19] = [ "GetObjectType", "IsObjectType", "GetName", "GetParent", "SetParent", "GetCenter", "GetLeft", "GetRight", "GetTop", "GetBottom", "GetWidth", "SetWidth", "GetHeight", "SetHeight", "GetNumPoints", "GetPoint", "SetPoint", "SetAllPoints", "ClearAllPoints", ]; const REG_WRAPPERS: &str = "__benilla_wrappers"; const REG_SCRIPTS: &str = "__benilla_scripts"; /// The reserved layout [`crate::layout::Handle`] of the screen root (the client's `CSimpleTop` / /// `UIParent`), whose rect is the physical screen. Top-level frames whose `SetPoint` omits a /// `relativeTo` anchor to it. Real frame ids are minted from `1` upward so they never collide. pub const SCREEN: crate::layout::Handle = 0; /// The FrameScript handler kinds this host models. The first five are the lifecycle/event set; the /// six mouse handlers are driven by the hit-testing API in [`pointer`] ([`UiScript::mouse_move`] / /// [`mouse_button`](UiScript::mouse_button) / [`mouse_wheel`](UiScript::mouse_wheel)) — the app-side /// event feed (net/window → these calls) is the Bevy side's job (decision 0068). `OnValueChanged` /// is shared by the StatusBar (RF-28 `+0x32c`) and the Slider (`+0x330`; decision 0250) — one name, /// each kind dispatching to its own value-changed slot. The eight /// `On*Pressed`/text/focus slots are the EditBox's specialized scripts (RF-0082 §2): a focused EditBox /// fires ONLY these, never generic `OnKeyDown`/`OnChar` (its C++ override replaces those slots). /// `OnHorizontalScroll`/`OnVerticalScroll`/`OnScrollRangeChanged` are the ScrollFrame's own slots /// (decision 0112; the reference's `[+0x32c]`/`[+0x334]`/`[+0x33c]`, script-name map `0x786c40`). /// `OnHorizontalScroll` joined the other two with the horizontal offset pair — it is fired by /// `SetHorizontalScroll`, which is what earns it the row below. /// `OnDragStart`/`OnDragStop`/`OnReceiveDrag` are the drag trio (decision 0216 §3) — driven by /// `RegisterForDrag` + the same mouse path as the six mouse handlers above, not a separate one. /// `OnColorSelect` is the ColorSelect's own slot (RF-28 `+0x338`), fired by its `SetColorRGB`. /// /// **Every name here is FIRED by something.** A kind that the engine can accept but never raise is /// strictly worse than the `SetScript: unsupported script` error it replaces — the addon's handler /// silently never runs and nothing anywhere says so (the bug class decisions 1203/1205/1211 each /// record). So a script name earns its row here only together with the code that fires it, and the /// names the reference has that we do NOT fire stay OUT, with the reason recorded at /// [`crate::script::object::events_regions::set_script`]. /// /// **This list is FLAT; the reference's set is per widget type** (RF-0028's script-name→slot /// resolvers: base map `0x76a0d0` + the type's own additions — a `` has no `OnClick`). That /// divergence is deliberate and measured, and the 1751 migration has been shrinking the debt it /// covers. Our own FrameXML used to rely on it in 9 places; seven of them were /// `PlayerFrame`/`TargetFrame`/`PetFrame`/`PartyMemberFrame1-4` declared as mouse-enabled /// ``s carrying `OnClick` in our transcribed `UnitFrames.xml`, and that file is gone — the /// reference declares all seven as `