//! 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 `