merge: absorb samwhosung/main on top of the upstream merge

Same fork policy: upstream structure wins, local-unique re-instated.

Cargo.toml unions both measured dev-profile philosophies (codegen-units=1 +
lto=off from samwhosung, debug=line-tables-only + split-debuginfo from the
fork's disk measurements).

glue_booth.rs takes samwhosung's typed GlueScene (token/of_race/from_model_path)
and re-adds the fork's TurtleWoW races (Goblin=9, BloodElf=10) as variants;
their glue fog stays on Human's row, exactly as the fork's scene_fog fallback did.

ui script takes samwhosung's mouseover/hyperlink rework (world_mouseover,
minimap_blip, hyperlink.rs) over the fork's inline hover_link/span_at/pointer
path — the rework covers the same OnHyperlinkEnter/Leave behaviour.

game_plugins.rs keeps the fork's re-measured ceilings (466 / 5,509); mail.rs
keeps the explicit 13-tuple with a float daysLeft; MAP.md takes the superset.
This commit is contained in:
chairmanpow
2026-10-12 17:24:31 +01:00
403 changed files with 52938 additions and 9951 deletions
+239 -21
View File
@@ -1,38 +1,137 @@
//! The UI session's lifecycle, all edges and nothing per-frame: `Startup` installs a boot VM
//! (strings and emote tokens); world entry builds a fresh VM and loads the in-game UI and every
//! addon onto it; leaving the world runs the reference's shutdown tail and installs a boot
//! VM again; `ReloadUI()` is the two edges back to back without leaving the world.
//! (strings and emote tokens), or the stock glue screens' VM under `WOW_GLUE_STOCK=1`; world entry
//! builds a fresh VM and loads the in-game UI and every addon onto it; leaving the world runs the
//! reference's shutdown tail and installs the glue-side VM again; `ReloadUI()` is the two edges
//! back to back without leaving the world.
use bevy::prelude::*;
use benilla_assets::LockRecover;
use benilla_ui::script::UiScript;
use benilla_ui::script::{GlueNotice, UiScript};
use super::manifest::silenced_ui_load;
use super::{load_ingame_ui, CursorPayloadHeld, PlayerUiHover, UiClock, UiKeyboardCapture};
use crate::ui_script::addons;
pub(crate) fn setup_script(world: &mut World) {
install_boot_vm(world);
install_glue_side_vm(world, super::glue::GlueScreen::Login);
}
/// Install the VM the world holds outside the world: the stock glue screens' under
/// [`super::glue::GlueStock`], put up on `screen`, else a boot VM.
///
/// The IO thread's roster policy follows the VM that is up: the screen asks for its roster only
/// when the stock glue VM was built and its character select screen loaded, so a run whose stock VM failed to build (logged as an error
/// by [`new_vm`]) falls back to the park's own enumeration instead of waiting on a screen that
/// never asks.
fn install_glue_side_vm(world: &mut World, screen: super::glue::GlueScreen) {
let stock_asked = world
.get_resource::<super::glue::GlueStock>()
.is_some_and(|s| s.0);
let stock_up = if stock_asked {
install_glue_vm(world, screen)
} else {
install_boot_vm(world);
false
};
if stock_asked && !stock_up {
error!("ui_script: WOW_GLUE_STOCK=1 but the stock glue VM did not come up with its character select screen; the roster is fetched at the park");
}
if let Some(policy) = world.get_resource::<crate::net::RosterPolicy>() {
policy
.0
.store(stock_up, std::sync::atomic::Ordering::SeqCst);
}
}
/// Build and install a boot VM, the one the world holds outside an in-game UI session: the
/// strings the feeds resolve lines from and the emote tokens `/`-commands are built from
/// (`ui_chat::commands`), no frames and no font objects. Installed at `Startup` and at every
/// [`end_ui_session`].
/// [`end_ui_session`], unless the stock glue screens stand there instead ([`install_glue_vm`]).
fn install_boot_vm(world: &mut World) {
let Some(script) = new_vm(world) else {
return;
};
load_global_strings(world, &script);
load_emote_tokens(world, &script);
world.insert_non_send_resource(script);
if let Some(script) = strings_vm(world) {
world.insert_non_send_resource(script);
}
}
/// A fresh VM on the process clock with the addon asset probes wired, or `None` (the world's VM
/// removed) when the VM cannot start.
fn new_vm(world: &mut World) -> Option<UiScript> {
let mut script = match UiScript::new() {
/// A boot VM's contents on a VM of their own: `GlobalStrings.lua` and the emote tokens, for a
/// reader that needs them where the world holds the glue VM. `None` (the world's VM removed) when
/// the VM cannot start.
pub(crate) fn strings_vm(world: &mut World) -> Option<UiScript> {
let script = new_vm(world, UiScript::new)?;
load_global_strings(world, &script);
load_emote_tokens(world, &script);
Some(script)
}
/// Build the glue VM, load the stock GlueXML onto it, install it and put `screen` up: the glue
/// builder `0x46a7b0` then `SET_GLUE_SCREEN` (`0x402b65` at startup, `0x402090` back from the
/// world). It holds what the glue state holds and nothing of the boot VM's: no `GlobalStrings.lua`,
/// no emote tokens.
fn install_glue_vm(world: &mut World, screen: super::glue::GlueScreen) -> bool {
let Some(mut script) = new_vm(world, UiScript::new_glue) else {
return false;
};
seat_raster_seam_for_load(world, &mut script);
seed_agreements(world, &mut script);
let start = std::time::Instant::now();
let failures = super::glue::load_glue(&mut script);
super::glue::finish_glue_load_log(&mut script);
start_server_alert(world, &script);
super::glue::set_glue_screen(&mut script, screen);
info!(
"ui_script: stock glue up in {:.0} ms on \"{}\" ({} load failure(s))",
start.elapsed().as_secs_f32() * 1000.0,
screen.key(),
failures.len()
);
// Up means the screen that asks for the roster exists: the stock files loaded far enough to
// define the character select frame and its `OnShow`, not merely that a VM started.
let charselect = script
.eval::<bool>(
"return type(CharacterSelect) == 'table' and type(CharacterSelect_OnShow) == 'function'",
)
.unwrap_or(false);
world.insert_non_send_resource(script);
charselect
}
/// Start the server alert's fetch, as the glue manager's init does after the GlueXML load and
/// before the first screen (`0x46a541`-`0x46a564`, `0x402b36`): the global `SERVER_ALERT_URL` as
/// the glue files left it, so `GlueLocalization` wins over `GlueStrings`, fetched once per
/// process ([`super::server_alert::begin_once`]).
fn start_server_alert(world: &mut World, script: &UiScript) {
let url = script.global_text("SERVER_ALERT_URL");
if let Some(fetch) = super::server_alert::begin_once(&url) {
if let Some(mut mgr) = world.get_resource_mut::<super::glue_mgr::GlueMgr>() {
mgr.set_alert_fetch(fetch);
}
}
}
/// Read the four agreement CVars into the glue VM and the manager before any screen is set, as
/// the reference does (`0x46a400` at `0x402b36`, then `SET_GLUE_SCREEN` at `0x402b65`): the login
/// screen's `OnShow` asks them at once, and nothing asks again.
fn seed_agreements(world: &mut World, script: &mut UiScript) {
let Some(cvars) = world.get_resource::<crate::cvars::Cvars>() else {
return;
};
let notices = super::glue_mgr::read_agreements(cvars);
for notice in GlueNotice::ALL {
script.set_glue_notice(notice, notices[notice as usize]);
}
if let Some(mut mgr) = world.get_resource_mut::<super::glue_mgr::GlueMgr>() {
mgr.set_notices(notices);
}
}
/// A fresh VM from `make` on the process clock with the addon asset probes wired, or `None` (the
/// world's VM removed) when the VM cannot start.
fn new_vm<E: std::fmt::Display>(
world: &mut World,
make: fn() -> Result<UiScript, E>,
) -> Option<UiScript> {
let mut script = match make() {
Ok(s) => s,
Err(e) => {
error!("ui_script: VM init failed: {e}");
@@ -155,7 +254,7 @@ pub(crate) fn arm_entry_ui_load(world: &mut World) {
/// `GlobalStrings.lua` and `Fonts.xml` as its first rows, and `ChatFrame.lua` the emote tokens.
fn mint_entry_vm(world: &mut World) {
crate::cvars::fold_dying_vm_cvars(world);
if let Some(script) = new_vm(world) {
if let Some(script) = new_vm(world, UiScript::new) {
world.insert_non_send_resource(script);
}
}
@@ -657,8 +756,24 @@ pub(crate) fn shutdown_ui_state(
/// shutdown tail, then end this session's Lua state, so no frame, global or addon upvalue reaches
/// the next login. The reference's state outlives `0x490bd0` and is replaced twice through
/// `0x703b80`, in `ShutdownGame` (at `0x491231`) and then in the glue build (`0x46a7b0`), so the
/// character screen runs on a state of its own. Ours is a fresh boot VM.
/// character screen runs on a state of its own. Ours is a fresh boot VM, or the stock glue's
/// ([`install_glue_side_vm`]).
pub(crate) fn end_ui_session(world: &mut World) {
close_ui_session(world);
// `0x401ee0`'s glue leg: `"charselect"` after a logout, `"login"` after a disconnect
// (`0x402084`); the state this exit enters says which.
let screen = match world
.get_resource::<State<crate::char_select::ClientState>>()
.map(|s| *s.get())
{
Some(crate::char_select::ClientState::CharSelect) => super::glue::GlueScreen::CharSelect,
_ => super::glue::GlueScreen::Login,
};
install_glue_side_vm(world, screen);
}
/// [`end_ui_session`] up to the new VM: the shutdown tail, the folds and the host values reset.
fn close_ui_session(world: &mut World) {
// A VM still parked (the load never ran) goes back into the world first.
unpark_boot_vm(world);
// An entry load still armed means no in-game UI was built, and the tail would overwrite the
@@ -699,8 +814,6 @@ pub(crate) fn end_ui_session(world: &mut World) {
if let Some(mut minimap) = world.get_resource_mut::<crate::minimap::MinimapWidget>() {
minimap.0 = None;
}
install_boot_vm(world);
}
/// A `ReloadUI()` waiting to run: set when [`crate::ui_logout`] drains
@@ -731,7 +844,10 @@ pub(crate) fn run_pending_reload(world: &mut World) {
return;
}
info!("ui_script: ReloadUI — ending the UI session and building a new one");
end_ui_session(world);
// A boot VM, never the glue's: the reload builds no glue (`0x495664` then `0x495669`), and the
// load replaces this VM at once.
close_ui_session(world);
install_boot_vm(world);
load_ingame_ui_seated(world, LoadSeat::Live);
// `UI_Init` ends by re-entering the world-enter cascade (`0x490168`), its sends included, but
// only with the active player set (`0x490166 je`): our own player's entity exists. Not
@@ -994,6 +1110,108 @@ mod tests {
);
}
/// The IO thread's roster policy follows the VM that came up: the screen asks only when the
/// stock glue VM stands, so a boot VM leaves the park to fetch the roster.
#[test]
fn the_roster_policy_follows_the_vm_that_stands() {
use std::sync::atomic::Ordering;
for stock in [true, false] {
let mut world = World::new();
let policy = crate::net::RosterPolicy(Default::default());
policy.0.store(!stock, Ordering::SeqCst);
world.insert_resource(policy.clone());
world.insert_resource(super::super::glue::GlueStock(stock));
world.insert_resource(State::new(crate::char_select::ClientState::Login));
setup_script(&mut world);
// The stock policy needs the stock files to have loaded: no install, no screen.
let up = stock && benilla_formats::wow_data().is_some();
assert_eq!(policy.0.load(Ordering::SeqCst), up, "stock {stock}");
}
}
/// The agreements are read from their CVars before the first screen is set (`0x46a400` at
/// `0x402b36`, `SET_GLUE_SCREEN` at `0x402b65`): a returning player's login shows the boxes,
/// not the EULA, and a first launch still shows the EULA.
#[test]
fn the_login_screen_comes_up_on_the_accepted_agreements() {
use crate::char_select::ClientState;
let _ = benilla_formats::wow_data_or_skip!();
let screens = |cvars: crate::cvars::Cvars| {
let mut world = World::new();
world.insert_resource(super::super::glue::GlueStock(true));
world.insert_resource(State::new(ClientState::Login));
world.insert_resource(cvars);
world.init_resource::<super::super::glue_mgr::GlueMgr>();
setup_script(&mut world);
world
.non_send_resource::<UiScript>()
.eval::<(bool, bool, bool)>(
"return TOSFrame:IsShown() == 1, AccountLoginUI:IsShown() == 1, \
AccountLoginAccountEdit:IsVisible() == 1",
)
.unwrap()
};
let mut accepted = crate::cvars::Cvars::with_value("readEULA", "1");
accepted.set("readTOS", "1");
assert_eq!(
screens(accepted),
(false, true, true),
"accepted agreements go straight to the login boxes"
);
assert_eq!(
screens(crate::cvars::Cvars::default()),
(true, false, false),
"a first launch is asked for the EULA"
);
}
/// Under `WOW_GLUE_STOCK=1` the world holds the glue VM where it holds a boot VM, at startup
/// on the login screen (`0x402b65`) and back from the world on the screen the exit enters
/// (`0x402090`); a `ReloadUI()` never builds the glue.
#[test]
fn the_stock_glue_stands_where_the_boot_vm_does() {
use crate::char_select::ClientState;
use benilla_ui::script::VmKind;
let shown = |world: &World, frame: &str| {
benilla_formats::wow_data().is_none()
|| world
.non_send_resource::<UiScript>()
.eval::<bool>(&format!("return {frame}:IsShown() == 1"))
.unwrap()
};
let mut world = World::new();
world.insert_resource(super::super::glue::GlueStock(true));
world.insert_resource(State::new(ClientState::Login));
setup_script(&mut world);
assert_eq!(
world.non_send_resource::<UiScript>().vm_kind(),
VmKind::Glue
);
assert!(shown(&world, "AccountLogin"), "the boot screen is login");
world.insert_resource(State::new(ClientState::CharSelect));
world.init_resource::<PendingEntryUiLoad>();
end_ui_session(&mut world);
assert_eq!(
world.non_send_resource::<UiScript>().vm_kind(),
VmKind::Glue
);
assert!(
shown(&world, "CharacterSelect"),
"a logout puts the character screen up"
);
world.insert_resource(State::new(ClientState::InWorld));
world.insert_resource(crate::run_mode::CaptureMode);
world.insert_resource(ReloadUiPending(true));
run_pending_reload(&mut world);
assert_eq!(
world.non_send_resource::<UiScript>().vm_kind(),
VmKind::InGame,
"the reload's VM is a boot VM, never the glue's"
);
}
/// The reference re-makes its Lua state inside `UI_Init` (`0x48fe97`).
#[test]
fn the_world_entry_builds_its_own_vm_rather_than_adopting_the_glue_phases() {