From 7eca017eb0e96fe4325adc3100fe48d34891bdf9 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 4 Aug 2026 18:54:37 +0700 Subject: [PATCH] the Options window goes era, and settings finally persist MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - one era-style Options window: window chrome, category list, search box and its own ESC rung, with the game menu reshaped to match - settings are real CVars now: GetCVar/SetCVar reach Lua, and your changes persist as a diff in benilla/config.toml - all local state lives in one visible benilla/ folder beside your Data/ — nothing hides in platform config dirs - UI text stays crisp at any scale and survives a mid-session resolution change: glyphs measure in the environment they raster in Co-Authored-By: Claude Fable 5 --- Cargo.lock | 53 ++ README.md | 131 ++-- banner.jpg | Bin 0 -> 182068 bytes crates/benilla-ui/src/loader/regions.rs | 20 +- crates/benilla-ui/src/script/cvars.rs | 247 +++++++ crates/benilla-ui/src/script/editbox/seam.rs | 9 + .../benilla-ui/src/script/era_atlas_tests.rs | 127 ++++ crates/benilla-ui/src/script/extract.rs | 22 +- crates/benilla-ui/src/script/layout.rs | 45 +- crates/benilla-ui/src/script/mod.rs | 68 +- crates/benilla-ui/src/script/model.rs | 25 +- crates/benilla-ui/src/script/region.rs | 62 +- crates/benilla-ui/src/script/tests/measure.rs | 102 +++ crates/benilla-ui/src/script/types.rs | 47 +- crates/benilla/Cargo.toml | 6 + crates/benilla/assets/ui/GameMenuFrame.xml | 150 ++-- crates/benilla/assets/ui/OptionsFrame.xml | 665 ++++++++++++++++++ crates/benilla/assets/ui/UiPanels.xml | 39 +- crates/benilla/src/assets/era.rs | 87 +++ crates/benilla/src/assets/mod.rs | 20 + crates/benilla/src/capture/fixtures.rs | 12 + crates/benilla/src/capture/mod.rs | 26 + crates/benilla/src/capture/scenarios.rs | 15 + crates/benilla/src/cvars.rs | 431 ++++++++++++ crates/benilla/src/local_state.rs | 150 ++++ crates/benilla/src/main.rs | 38 +- crates/benilla/src/ui_script/escape_tests.rs | 41 ++ crates/benilla/src/ui_script/extract/mod.rs | 48 +- crates/benilla/src/ui_script/extract/text.rs | 24 +- .../benilla/src/ui_script/game_menu_tests.rs | 112 ++- crates/benilla/src/ui_script/mod.rs | 36 +- crates/benilla/src/ui_script/options_tests.rs | 228 ++++++ 32 files changed, 2876 insertions(+), 210 deletions(-) create mode 100644 banner.jpg create mode 100644 crates/benilla-ui/src/script/cvars.rs create mode 100644 crates/benilla-ui/src/script/era_atlas_tests.rs create mode 100644 crates/benilla/assets/ui/OptionsFrame.xml create mode 100644 crates/benilla/src/assets/era.rs create mode 100644 crates/benilla/src/cvars.rs create mode 100644 crates/benilla/src/local_state.rs create mode 100644 crates/benilla/src/ui_script/options_tests.rs diff --git a/Cargo.lock b/Cargo.lock index 32aab707..868825d4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -594,7 +594,10 @@ dependencies = [ "objc2-foundation 0.2.2", "rand 0.8.6", "raw-window-handle", + "serde", + "serde_json", "smithay-clipboard", + "toml", ] [[package]] @@ -6184,6 +6187,15 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + [[package]] name = "sha1" version = "0.10.6" @@ -6749,6 +6761,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +[[package]] +name = "toml" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", +] + +[[package]] +name = "toml_datetime" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" +dependencies = [ + "serde", +] + [[package]] name = "toml_datetime" version = "0.7.5+spec-1.1.0" @@ -6767,6 +6800,20 @@ dependencies = [ "serde_core", ] +[[package]] +name = "toml_edit" +version = "0.22.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + [[package]] name = "toml_edit" version = "0.23.10+spec-1.0.0" @@ -6800,6 +6847,12 @@ dependencies = [ "winnow 1.0.3", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "tracing" version = "0.1.44" diff --git a/README.md b/README.md index fa220a7d..ead5c97f 100644 --- a/README.md +++ b/README.md @@ -1,94 +1,77 @@ -# benilla +
+ +

benilla

+

A from-scratch World of Warcraft 1.12.1 client in Rust and Bevy

+

+ Discord + YouTube devlog + License +

+
-[![Discord](https://img.shields.io/badge/chat-Discord-5865F2?logo=discord&logoColor=white)](https://discord.gg/wJSJx467G4) -[![YouTube](https://img.shields.io/badge/devlog-YouTube-FF0000?logo=youtube)](https://www.youtube.com/playlist?list=PLdCnpZNKxyb8) -[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue)](#license) +> [!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](https://discord.gg/wJSJx467G4) and report +> the bugs you find. Questions and ideas are welcome in the same place. -A from-scratch **World of Warcraft 1.12.1 client** in **Rust + [Bevy](https://bevy.org)**, speaking -the vanilla protocol to a [vmangos](https://github.com/vmangos/core) server. Every file format and -the network protocol are implemented from scratch — no original client code, no third-party WoW -crates, and no bundled game assets: benilla reads its data at runtime from your own 1.12.1 install. +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 & assets** — in-repo readers for the full asset stack (MPQ patch chain, BLP, DBC, - ADT/WDT/WDL, M2, WMO), wired into Bevy as an asset source, plus extraction CLI tools. -- **World** — streamed terrain with the distant horizon, portal-culled WMOs with interior lighting, +- **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, glow, and gamma passes. -- **Models & animation** — GPU-skinned M2s with the full animation controller (cross-fades, - one-shots, fidgets), a near feature-complete particle system, ribbons, and animated gameobjects - (doors, chests, mailboxes, lifts). -- **Characters** — customization end to end, the armor-region texture composite, held weapons with - sheathing and enchant glows, shapeshift forms, stealth's fade, and mounts. -- **Movement & travel** — a WoW-feel controller (run/strafe/jump/swim/step-up), networked movement - in both directions, the server-granted modes (slow fall, levitate, water walking, roots and - stuns), a follow-camera with collision, ridden boats and zeppelins, and taxi flights. -- **Networking** — SRP6 auth through world-session crypto, the object-descriptor mirror into the - ECS, and live wire coverage for movement, chat, spells and auras, party, duels, inspect, the - social lists, mail, trade, vendors, bank, trainers, professions, quests, loot with group rolls, - item use and locks, and death. -- **UI** — a from-scratch FrameXML + Lua UI engine driving the built-in interface: login and - character screens, the full HUD (cast bars, breath and fatigue timers, combo points), the classic - windows (bags and keyring, spellbook, talents, quests, professions, social, inspect, mail, - merchant, trade, bank, world map, …), chat with its slash commands, nameplates, floating combat - text, chat bubbles, and tooltips. -- **Combat** — melee with the faithful swing law, ranged and Auto Shot, casting with GCD, cooldowns - and the cancel ladder, combo points, ground-targeted casting, crowd control that really holds you - (roots, stuns, the Ice Block freeze), and the spell-visual pipeline (kits, missiles, impacts, - ground rings). -- **Audio** — music, ambience, and SFX under the client's own selection and crossfade rules, with - interior/underwater transitions and zone reverb. + 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 + and loot. +- **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, chat, nameplates, floating combat + text and tooltips. +- **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. ## Not built yet -Guilds (past the name over a head), the auction house, hunter/warlock pet control (the pet bar), -battlegrounds, honor and the zone-control PvP overlays (the PvP flag itself shows), the fishing -minigame, camera-possession spells (Mind Vision, Sentry Totem), the try-on preview (dressing -room), the macros/options/key-binding screens, and third-party addon loading — the Lua/FrameXML -engine currently runs only the built-in UI. +Guilds past the name over a head, the auction house, the hunter and warlock pet bar, battlegrounds +and honor, the fishing minigame, the dressing room, macros and key bindings, and third-party +addons; the Lua engine runs only the built-in UI so far. ## Running it -You need a **1.12.1 (build 5875) client install** for game data, a -[vmangos](https://github.com/vmangos/core) server to connect to, and stable Rust. +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](https://github.com/vmangos/core) is what +development runs against, and cMaNGOS and the rest speak the same protocol. ```sh WOW_DATA=/path/to/WoW/Data cargo run --release -p benilla ``` -The server defaults to `localhost:3724`, where a stock vmangos `realmd` listens — point `WOW_HOST` -at any IP or hostname, and append an 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. +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. -## Contributing +--- -benilla is a solo project and isn't taking pull requests for now. Bug reports and questions are -welcome — bring them to the [Discord](https://discord.gg/wJSJx467G4) (issues are disabled here on -purpose). Comments throughout the code cite numbered -decision records and method notes from a private development log; the citations won't resolve -here, but the reasoning they carry is kept in the comments themselves. +Early inspiration and file format guidance came from the +[wowemulation-dev](https://github.com/wowemulation-dev) community, and +[warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) in particular. -Development happens in that private tree, and this repo is its published export: **each commit here -is one squashed snapshot** of everything that landed since the last one, titled with what changed — -not an atomic change, which is why they're large and why there are few of them. - -## Acknowledgements - -The [wowemulation-dev](https://github.com/wowemulation-dev) community — and -[warcraft-rs](https://github.com/wowemulation-dev/warcraft-rs) in particular — was an early -inspiration and a source of guidance on the client's file formats. benilla's readers ended up -written from scratch, but their preservation work made starting far less daunting. And none of -this runs without [vmangos](https://github.com/vmangos/core) on the other end of the socket. - -## Legal - -benilla is an independent, fan-made project, not affiliated with or endorsed by Blizzard -Entertainment. It contains no Blizzard code or assets and never will; you must provide your own -legally-obtained 1.12.1 client for game data. World of Warcraft is a trademark of Blizzard -Entertainment, Inc. - -## License - -Licensed under either of the [Apache License 2.0](LICENSE-APACHE) or the [MIT -license](LICENSE-MIT), at your option. +benilla is an independent fan project, not affiliated with or endorsed by Blizzard Entertainment. +It contains no Blizzard code or assets, and you provide your own legally obtained 1.12.1 client +for game data. World of Warcraft is a trademark of Blizzard Entertainment, Inc. Licensed under +[MIT](LICENSE-MIT) or [Apache 2.0](LICENSE-APACHE), at your option. diff --git a/banner.jpg b/banner.jpg new file mode 100644 index 0000000000000000000000000000000000000000..36b2def438d453612b3df12f08bb66c73ccc79c1 GIT binary patch literal 182068 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 z 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 { ) { let color = children_named(region, "Color").next().map(color_of); if is_texture { - if let Some(file) = region.attr("file") { + if let Some(atlas) = region.attr("atlas") { + // `atlas="member"` (decision 0950): the Era dialect's texture reference — one + // SetAtlas resolves sheet + UV sub-rect (+ nominal size with `useAtlasSize`) + // through the app-pushed manifest table; `file=` remains the 1.12 MPQ path. + self.call_region( + region_wrapper, + "SetAtlas", + (atlas.to_string(), region.attr_bool("useAtlasSize")), + dbg, + ); + if let Some(c) = color { + self.call_region( + region_wrapper, + "SetVertexColor", + (c[0], c[1], c[2], c[3]), + dbg, + ); + } + } else if let Some(file) = region.attr("file") { self.call_region(region_wrapper, "SetTexture", file.to_string(), dbg); if let Some(c) = color { // A texture with both a file and a is a tint (vertex color). diff --git a/crates/benilla-ui/src/script/cvars.rs b/crates/benilla-ui/src/script/cvars.rs new file mode 100644 index 00000000..9c5e4462 --- /dev/null +++ b/crates/benilla-ui/src/script/cvars.rs @@ -0,0 +1,247 @@ +//! The **CVar table** — the client's named-console-variable store, engine side (decision 0954). +//! +//! In the reference every player setting is a CVar: a case-insensitively named string value with +//! a registered default, read and written from Lua through `GetCVar`/`SetCVar`/`GetCVarDefault` +//! (the 1.12 options panels are thin UI over this table). benilla keeps the same shape at the +//! same seam: the **host registers** the vars it actually backs with engine knobs +//! ([`super::UiScript::register_cvars`] — the honest-tree rule: no key without a knob), Lua +//! reads/writes them synchronously, and each Lua write lands on the **change queue** the host +//! drains per frame ([`super::UiScript::take_cvar_changes`]) to sync its resources and mark the +//! config file dirty. Host-side writes ([`super::UiScript::set_cvar_host`] — the loaded config, +//! an env override) do NOT ride the queue: the host already knows, and an echo would re-dirty +//! the file it just loaded. +//! +//! Values are **strings**, like the client's (`GetCVar` returns a string; consumers parse and +//! clamp at their own edge). An unknown name warns once and no-ops — every benilla CVar is +//! host-registered, so an unknown key is a typo or an unshipped feature, never a storage slot. +//! Divergence, disclosed: the client also surfaces `RegisterCVar` to Lua; nothing in our shipped +//! XML calls it, so it is not modeled until something does. + +use mlua::{Lua, Value}; + +use super::Model; + +/// One registered CVar: the host's spelling (for change events and the config file), the live +/// value, and the registered default (`GetCVarDefault`, and the saver's "only write what moved"). +#[derive(Clone, Debug)] +pub(crate) struct CvarSlot { + /// The registered spelling (`MasterVolume`), reported on change events and in snapshots; + /// the table key is the lowercase form (the client's lookups are case-insensitive). + pub name: String, + pub value: String, + pub default: String, +} + +impl super::UiScript { + /// Register the host-backed CVars (name, default) — boot-time, idempotent. A re-register of + /// a live table refreshes defaults but never clobbers a value someone already set. + pub fn register_cvars<'a>(&mut self, vars: impl IntoIterator) { + let mut model = self.model_mut(); + for (name, default) in vars { + let key = name.to_ascii_lowercase(); + match model.cvars.get_mut(&key) { + Some(slot) => slot.default = default.to_string(), + None => { + model.cvars.insert( + key, + CvarSlot { + name: name.to_string(), + value: default.to_string(), + default: default.to_string(), + }, + ); + } + } + } + } + + /// Host-side write (loaded config, env override): sets the value WITHOUT queueing a change + /// event — the host is the caller, an echo would re-dirty the file it just read. Unknown + /// names warn once, same posture as the Lua side. + pub fn set_cvar_host(&mut self, name: &str, value: &str) { + let mut model = self.model_mut(); + let key = name.to_ascii_lowercase(); + match model.cvars.get_mut(&key) { + Some(slot) => slot.value = value.to_string(), + None => warn_unknown(&mut model, name), + } + } + + /// Read one CVar's live value (host side). + pub fn cvar(&self, name: &str) -> Option { + self.model_mut() + .cvars + .get(&name.to_ascii_lowercase()) + .map(|s| s.value.clone()) + } + + /// Snapshot the whole table as `(name, value, default)` — the saver writes the entries whose + /// value moved off the default, and nothing else (the config file stays a diff, not a dump). + pub fn cvars_snapshot(&self) -> Vec<(String, String, String)> { + self.model_mut() + .cvars + .values() + .map(|s| (s.name.clone(), s.value.clone(), s.default.clone())) + .collect() + } + + /// Drain the `(name, new_value)` changes Lua `SetCVar` queued since the last call — the + /// host's cue to sync its resources and mark the config dirty. Names are the registered + /// spelling regardless of the caller's casing. + pub fn take_cvar_changes(&mut self) -> Vec<(String, String)> { + std::mem::take(&mut self.model_mut().cvar_changes) + } +} + +/// Push the warn-once for an unknown CVar name into the model's warning stream. +fn warn_unknown(model: &mut Model, name: &str) { + let key = name.to_ascii_lowercase(); + if model.cvars_warned.insert(key) { + model.warnings.push(format!( + "unknown CVar '{name}' (not host-registered) — ignored" + )); + } +} + +/// Coerce the Lua argument to the string the table stores: the client stringifies numbers and +/// booleans the same way (`SetCVar("MusicVolume", 0.4)` is the common call shape). +fn value_to_string(v: &Value) -> Option { + match v { + Value::String(s) => s.to_str().ok().map(|s| s.to_owned()), + Value::Integer(i) => Some(i.to_string()), + Value::Number(n) => Some(n.to_string()), + Value::Boolean(b) => Some(if *b { "1".into() } else { "0".into() }), + _ => None, + } +} + +/// Register the `GetCVar`/`SetCVar`/`GetCVarDefault` globals. +pub(super) fn install(lua: &Lua) -> mlua::Result<()> { + lua.globals().set( + "GetCVar", + lua.create_function(|lua, name: String| { + let mut model = lua.app_data_mut::().expect("model app_data"); + match model.cvars.get(&name.to_ascii_lowercase()) { + Some(slot) => Ok(Value::String(lua.create_string(&slot.value)?)), + None => { + warn_unknown(&mut model, &name); + Ok(Value::Nil) + } + } + })?, + )?; + lua.globals().set( + "GetCVarDefault", + lua.create_function(|lua, name: String| { + let mut model = lua.app_data_mut::().expect("model app_data"); + match model.cvars.get(&name.to_ascii_lowercase()) { + Some(slot) => Ok(Value::String(lua.create_string(&slot.default)?)), + None => { + warn_unknown(&mut model, &name); + Ok(Value::Nil) + } + } + })?, + )?; + lua.globals().set( + "SetCVar", + lua.create_function(|lua, args: mlua::MultiValue| { + let mut it = args.iter(); + let (Some(Value::String(name)), Some(v)) = (it.next(), it.next()) else { + return Err(mlua::Error::runtime("Usage: SetCVar(\"name\", value)")); + }; + let name = name.to_str()?.to_owned(); + let Some(value) = value_to_string(v) else { + return Err(mlua::Error::runtime("Usage: SetCVar(\"name\", value)")); + }; + let mut model = lua.app_data_mut::().expect("model app_data"); + let key = name.to_ascii_lowercase(); + match model.cvars.get_mut(&key) { + Some(slot) => { + if slot.value != value { + slot.value = value.clone(); + let reg_name = slot.name.clone(); + model.cvar_changes.push((reg_name, value)); + } + } + None => warn_unknown(&mut model, &name), + } + Ok(()) + })?, + ) +} + +#[cfg(test)] +mod tests { + use crate::script::UiScript; + + fn script_with_volume() -> UiScript { + let mut s = UiScript::new().unwrap(); + s.register_cvars([("MusicVolume", "0.4"), ("MasterVolume", "1.0")]); + s + } + + #[test] + fn the_table_round_trips_and_queues_changes_case_insensitively() { + let mut s = script_with_volume(); + // Registration seeds value = default; reads are case-insensitive. + assert_eq!( + s.eval::(r#"return GetCVar("musicvolume")"#) + .unwrap(), + "0.4" + ); + // A Lua write queues ONE change under the REGISTERED spelling; the number stringifies. + s.run(r#"SetCVar("MUSICVOLUME", 0.7)"#).unwrap(); + assert_eq!( + s.take_cvar_changes(), + vec![("MusicVolume".to_string(), "0.7".to_string())] + ); + assert!(s.take_cvar_changes().is_empty(), "drained"); + // The default is unmoved and separately readable. + assert_eq!( + s.eval::(r#"return GetCVarDefault("MusicVolume")"#) + .unwrap(), + "0.4" + ); + assert_eq!(s.cvar("MusicVolume").as_deref(), Some("0.7")); + // A write to the same value queues nothing (quiet frames stay quiet). + s.run(r#"SetCVar("MusicVolume", "0.7")"#).unwrap(); + assert!(s.take_cvar_changes().is_empty()); + } + + #[test] + fn host_writes_do_not_echo_and_snapshots_carry_defaults() { + let mut s = script_with_volume(); + s.set_cvar_host("MasterVolume", "0.25"); + assert!( + s.take_cvar_changes().is_empty(), + "a host write must not re-dirty the config it just loaded" + ); + assert_eq!( + s.eval::(r#"return GetCVar("MasterVolume")"#) + .unwrap(), + "0.25" + ); + let snap = s.cvars_snapshot(); + let master = snap.iter().find(|(n, _, _)| n == "MasterVolume").unwrap(); + assert_eq!((master.1.as_str(), master.2.as_str()), ("0.25", "1.0")); + } + + #[test] + fn unknown_names_warn_once_and_no_op() { + let mut s = script_with_volume(); + assert!(s + .eval::(r#"return GetCVar("bogusKnob") == nil"#) + .unwrap()); + s.run(r#"SetCVar("bogusKnob", 1)"#).unwrap(); + s.run(r#"SetCVar("bogusKnob", 2)"#).unwrap(); + assert!(s.take_cvar_changes().is_empty()); + let warns: Vec = s.take_warnings(); + assert_eq!(warns.len(), 1, "warn-once: {warns:?}"); + assert!(warns[0].contains("bogusKnob")); + // Registration is idempotent and never clobbers a live value. + s.set_cvar_host("MusicVolume", "0.9"); + s.register_cvars([("MusicVolume", "0.4")]); + assert_eq!(s.cvar("MusicVolume").as_deref(), Some("0.9")); + } +} diff --git a/crates/benilla-ui/src/script/editbox/seam.rs b/crates/benilla-ui/src/script/editbox/seam.rs index 8321a5a3..8f6560d1 100644 --- a/crates/benilla-ui/src/script/editbox/seam.rs +++ b/crates/benilla-ui/src/script/editbox/seam.rs @@ -26,6 +26,13 @@ impl UiScript { let font = d.and_then(|d| d.font_path.clone()); let height = d.and_then(|d| d.font_height); let outline = d.map(|d| d.outline).unwrap_or_default(); + // The box's effective_scale rides the request ([`types::EditBoxAdvanceRequest::scale`]): + // the host measures at the drawn raster size so the advances land on the drawn glyphs. + let scale = model + .arena + .frame(h) + .map(|f| f.effective_scale) + .unwrap_or(1.0); // A multiline box also needs its wrap rows — at the text region's resolved width, the // same width the draw wraps at. Folded into the cache key: a resize re-wraps. let wrap_width = multi_line @@ -44,6 +51,7 @@ impl UiScript { height.map(f32::to_bits).hash(&mut hasher); (outline as u8).hash(&mut hasher); wrap_width.map(f32::to_bits).hash(&mut hasher); + scale.to_bits().hash(&mut hasher); let key = hasher.finish(); let id = model.frame_id(h); let Some(KindState::EditBox(eb)) = model.arena.frame_mut(h).map(|f| &mut f.kind_state) @@ -64,6 +72,7 @@ impl UiScript { font, height, outline, + scale, text: display, wrap_width, key, diff --git a/crates/benilla-ui/src/script/era_atlas_tests.rs b/crates/benilla-ui/src/script/era_atlas_tests.rs new file mode 100644 index 00000000..a279165e --- /dev/null +++ b/crates/benilla-ui/src/script/era_atlas_tests.rs @@ -0,0 +1,127 @@ +//! The Era atlas seam (decision 0950): `atlas=` in XML, `SetAtlas`/`GetAtlas` at runtime, and the +//! warn-once miss drain — against a hand-pushed table. Disk and manifest.json belong to the app; +//! these tests prove the script/loader side alone. + +use crate::framexml; +use crate::loader; +use crate::script::{EraAtlasEntry, UiScript}; + +/// The real `options_innerframe` numbers (fdid 1318750, rect [1,150,887,768] in 1024×1024) so a +/// regression here reads like the manifest it mirrors. +fn harness() -> UiScript { + let mut s = UiScript::new().unwrap(); + s.set_screen_size(1024.0, 768.0); + s.set_era_atlases([ + ( + "options_innerframe".to_string(), + EraAtlasEntry { + file: "era:textures/1318750.blp".to_string(), + uv: [1.0 / 1024.0, 887.0 / 1024.0, 150.0 / 1024.0, 768.0 / 1024.0], + size: [886.0, 618.0], + }, + ), + ( + "checkbox-minimal".to_string(), + EraAtlasEntry { + file: "era:textures/4614134.blp".to_string(), + uv: [1.0 / 64.0, 31.0 / 64.0, 1.0 / 64.0, 30.0 / 64.0], + size: [30.0, 29.0], + }, + ), + ]); + s +} + +#[test] +fn xml_atlas_attribute_resolves_texture_uv_and_size() { + let mut s = harness(); + let doc = framexml::parse( + r#" + + + + + + + + "#, + ) + .unwrap(); + let report = loader::load(&s, &doc, &|_| None); + assert!(report.errors.is_empty(), "errors: {:?}", report.errors); + + // Case-insensitive resolve; the canonical (lowercase) name is what GetAtlas reports. + assert_eq!( + s.eval::("return TInner:GetAtlas()").unwrap(), + "options_innerframe" + ); + let (l, r, t, b): (f32, f32, f32, f32) = s.eval("return TInner:GetTexCoord()").unwrap(); + assert!((l - 1.0 / 1024.0).abs() < 1e-6 && (r - 887.0 / 1024.0).abs() < 1e-6); + assert!((t - 150.0 / 1024.0).abs() < 1e-6 && (b - 768.0 / 1024.0).abs() < 1e-6); + // useAtlasSize applied the member's nominal size. + assert_eq!(s.eval::("return TInner:GetWidth()").unwrap(), 886.0); + assert_eq!(s.eval::("return TInner:GetHeight()").unwrap(), 618.0); + // Nothing missed. + assert!(s.take_era_atlas_misses().is_empty()); +} + +#[test] +fn runtime_setatlas_swaps_and_settexture_clears() { + let s = harness(); + let doc = framexml::parse( + r#" + + + + + + + + "#, + ) + .unwrap(); + let report = loader::load(&s, &doc, &|_| None); + assert!(report.errors.is_empty(), "errors: {:?}", report.errors); + + // The category-row idiom: swap atlas at runtime (hover/selected), size riding along. + s.run("TTex:SetAtlas(\"checkbox-minimal\", true)").unwrap(); + assert_eq!( + s.eval::("return TTex:GetAtlas()").unwrap(), + "checkbox-minimal" + ); + assert_eq!(s.eval::("return TTex:GetWidth()").unwrap(), 30.0); + + // A plain SetTexture makes the region ordinary again — GetAtlas goes nil. + s.run("TTex:SetTexture(\"Interface\\\\Buttons\\\\UI-Panel-Button-Up\")") + .unwrap(); + assert!(s.eval::("return TTex:GetAtlas() == nil").unwrap()); +} + +#[test] +fn unknown_atlas_is_a_warn_once_miss_not_an_error() { + let mut s = harness(); + let doc = framexml::parse( + r#" + + + + + + + + "#, + ) + .unwrap(); + // The load survives (a stale extraction must not kill the whole XML pass) … + let report = loader::load(&s, &doc, &|_| None); + assert!(report.errors.is_empty(), "errors: {:?}", report.errors); + // … the region draws nothing … + assert!(s.eval::("return TTex:GetAtlas() == nil").unwrap()); + // … and the miss names itself exactly once, then the drain clears. + s.run("TTex:SetAtlas(\"not-extracted-yet\")").unwrap(); + assert_eq!( + s.take_era_atlas_misses(), + vec!["not-extracted-yet".to_string()] + ); + assert!(s.take_era_atlas_misses().is_empty()); +} diff --git a/crates/benilla-ui/src/script/extract.rs b/crates/benilla-ui/src/script/extract.rs index c4ab81bf..e979c42a 100644 --- a/crates/benilla-ui/src/script/extract.rs +++ b/crates/benilla-ui/src/script/extract.rs @@ -24,10 +24,11 @@ impl UiScript { // it is clipped by (nested ScrollFrames intersect). let scroll_sources = scroll_clip_sources(&model); for (target, zkey) in list { - let (rect, alpha, content, clip) = match target { + let (rect, alpha, content, clip, scale) = match target { ZTarget::Frame(fh) => { let frame = model.arena.frame(fh); let alpha = frame.map(|f| f.effective_alpha).unwrap_or(1.0); + let scale = frame.map(|f| f.effective_scale).unwrap_or(1.0); let clip = effective_clip(&model, &scroll_sources, fh); // A Minimap widget's own slot carries its zoom out to the app renderer (the // tile/mask/arrow draw — decision 0203); every other frame slot is bare. @@ -50,7 +51,13 @@ impl UiScript { } _ => QuadContent::Frame, }; - (model.resolved.get(&fh).copied(), alpha, content, clip) + ( + model.resolved.get(&fh).copied(), + alpha, + content, + clip, + scale, + ) } ZTarget::Region(rh) => { let region = model.arena.region(rh); @@ -210,7 +217,10 @@ impl UiScript { rotation: data.rotation, } }; - (rect, alpha, content, clip) + // A region draws at its owner's scale — same single hop as alpha (a region + // has no scale of its own; `propagation.md`'s product lives on frames). + let scale = owner_frame.map(|f| f.effective_scale).unwrap_or(1.0); + (rect, alpha, content, clip, scale) } }; out.push(ExtractedQuad { @@ -220,6 +230,7 @@ impl UiScript { alpha, content, clip, + scale, }); // A ScrollingMessageFrame emits, on top of its own (empty) frame slot, one Text quad per // visible ring line — stacked bottom-up (newest at the frame's bottom edge), each carrying @@ -233,13 +244,14 @@ impl UiScript { // stay behind them. At the bare slot the chat window's hover box painted over its // own messages. Both clip like the frame's own slot (the frame IS the clipped owner // when it's a scroll child). - Self::emit_backdrop(&model, fh, fr, zkey.raw(), alpha, clip, &mut out); + let paint = super::layout::FramePaint { alpha, scale }; + Self::emit_backdrop(&model, fh, fr, zkey.raw(), paint, clip, &mut out); Self::emit_message_lines( &model, fh, fr, zkey.content(order::DrawLayer::Artwork).raw(), - alpha, + paint, clip, &mut out, ); diff --git a/crates/benilla-ui/src/script/layout.rs b/crates/benilla-ui/src/script/layout.rs index 59662ed5..06502a86 100644 --- a/crates/benilla-ui/src/script/layout.rs +++ b/crates/benilla-ui/src/script/layout.rs @@ -10,6 +10,16 @@ use super::{ Outline, QuadContent, UiScript, SCREEN, }; +/// The paint a frame slot's derived quads inherit — its `effective_alpha` and `effective_scale` +/// travelling together (backdrop pieces and message-frame ring lines are emitted BY the frame, +/// so they wear the frame's own factors exactly like [`ExtractedQuad::alpha`]/`scale` on the +/// slot itself). +#[derive(Clone, Copy)] +pub(super) struct FramePaint { + pub(super) alpha: f32, + pub(super) scale: f32, +} + /// A 128-bit rolling fingerprint of everything [`UiScript::resolve_layout`] *reads*, so a resolve /// whose inputs are byte-identical to the last one can be skipped outright. /// @@ -598,6 +608,14 @@ impl UiScript { if !is_fs { continue; } + // The owner's effective_scale: the host measures at the drawn raster size + // ([`MeasureRequest::scale`]), and the key carries it so a SetScale re-measures. + let scale = model + .arena + .region(rh) + .and_then(|r| model.arena.frame(r.owner)) + .map(|f| f.effective_scale) + .unwrap_or(1.0); let d = model.region_data.get(&rh).expect("live region data"); let text = d.text.clone().unwrap_or_default(); let wrap_width = d.size.map(|s| s.0).filter(|w| *w > 0.0); @@ -607,7 +625,7 @@ impl UiScript { let outline = d.outline; // The shared key recipe ([`RegionData::measure_key`]) — the metric reads // (region.rs) check the stored measure against the same hash. - let key = d.measure_key(); + let key = d.measure_key(scale); if d.measured.map(|m| m.key) == Some(key) { continue; } @@ -619,6 +637,7 @@ impl UiScript { text_height, wrap_width, outline, + scale, text, key, }); @@ -650,6 +669,11 @@ impl UiScript { if wrap_width <= 1.0 { continue; // unresolved/degenerate width — nothing meaningful to wrap against } + let scale = model + .arena + .frame(fh) + .map(|f| f.effective_scale) + .unwrap_or(1.0); let (font, height, _, outline) = Self::message_frame_font(&model, fh); let frame_id = model.frame_id(fh); let Some(frame) = model.arena.frame(fh) else { @@ -665,6 +689,7 @@ impl UiScript { height.map(f32::to_bits).hash(&mut hasher); wrap_width.to_bits().hash(&mut hasher); (outline as u8).hash(&mut hasher); + scale.to_bits().hash(&mut hasher); let key = hasher.finish(); if line.rows_key == key { continue; @@ -676,6 +701,7 @@ impl UiScript { height, wrap_width, outline, + scale, text: line.text.clone(), key, }); @@ -710,13 +736,13 @@ impl UiScript { /// frame has none). Pieces carry the frame slot's `z` — behind the frame's regions (which sort /// after it) and, among themselves, bg-then-border in paint order (the stable z-sort keeps the /// emission order). Each piece's rect is its screen bounding box; the app resolves the texture - /// from the piece path and multiplies the tint by `frame_alpha`. + /// from the piece path and multiplies the tint by the [`FramePaint`]'s alpha. pub(super) fn emit_backdrop( model: &Model, fh: FrameHandle, fr: Rect, z: u64, - frame_alpha: f32, + paint: FramePaint, clip: Option, out: &mut Vec, ) { @@ -751,7 +777,7 @@ impl UiScript { target: ZTarget::Frame(fh), z, rect: Some(Rect::new(bottom, left, top, right)), - alpha: frame_alpha, + alpha: paint.alpha, content: QuadContent::Backdrop { path, color, @@ -759,6 +785,7 @@ impl UiScript { tile: piece.tile, }, clip, + scale: paint.scale, }); } } @@ -782,7 +809,7 @@ impl UiScript { fh: FrameHandle, fr: Rect, z: u64, - frame_alpha: f32, + paint: FramePaint, clip: Option, out: &mut Vec, ) { @@ -796,7 +823,10 @@ impl UiScript { return; } let (font, font_height, font_shadow, outline) = Self::message_frame_font(model, fh); - let pitch = font_height.unwrap_or(14.0); + // The band grid lives in the frame's RESOLVED (scale-multiplied) rect, so the row pitch + // rides the frame scale exactly like the glyphs it must coincide with (the font height + // itself is frame-local). + let pitch = font_height.unwrap_or(14.0) * paint.scale; if pitch <= 0.0 || fr.top <= fr.bottom { return; } @@ -825,7 +855,7 @@ impl UiScript { target: ZTarget::Frame(fh), z, rect: Some(band), - alpha: frame_alpha, + alpha: paint.alpha, clip: line_clip, content: QuadContent::Text { text: Some(line.text.clone()), @@ -850,6 +880,7 @@ impl UiScript { outline, alpha_gradient: None, }, + scale: paint.scale, }); } } diff --git a/crates/benilla-ui/src/script/mod.rs b/crates/benilla-ui/src/script/mod.rs index d19caa49..6ec823bd 100644 --- a/crates/benilla-ui/src/script/mod.rs +++ b/crates/benilla-ui/src/script/mod.rs @@ -50,7 +50,10 @@ mod cursor; mod death; mod editbox; pub(crate) use editbox::adopt_text_region; +mod cvars; mod duel; +#[cfg(test)] +mod era_atlas_tests; mod event; mod extract; mod follow; @@ -143,9 +146,9 @@ pub use trainer::{ TrainerState, }; pub use types::{ - EditAction, EditBoxTextUi, EditOutcome, EditUnit, ExtractedQuad, FontObject, FontShadow, - JustifyH, JustifyV, LineMeasureRequest, MeasureRequest, Outline, QuadContent, ScriptValue, - TexCoords, + EditAction, EditBoxTextUi, EditOutcome, EditUnit, EraAtlasEntry, ExtractedQuad, FontObject, + FontShadow, JustifyH, JustifyV, LineMeasureRequest, MeasureRequest, Outline, QuadContent, + ScriptValue, TexCoords, }; pub(crate) use types::{MeasuredText, RegionData}; pub use unit::{grey_band, level_reads_unknown, power_token, unit_is_grey, UnitState}; @@ -266,6 +269,7 @@ impl UiScript { pvp::install(&lua)?; death::install(&lua)?; aura::install(&lua)?; + cvars::install(&lua)?; sound::install(&lua)?; pointer::install(&lua)?; action::install(&lua)?; @@ -328,6 +332,27 @@ impl UiScript { } } + /// Replace the Era atlas table (decision 0950) — pushed once at boot, before the XML loads. + /// Keys are stored lowercased (the DB2 dump's own convention); `SetAtlas`/`atlas=` match + /// case-insensitively. The app builds the entries from `WoW-era/_extracted_ui/manifest.json`; + /// the script side only stores resolved paint, never touching disk. + pub fn set_era_atlases(&mut self, entries: impl IntoIterator) { + let mut model = self.model_mut(); + model.era_atlas = entries + .into_iter() + .map(|(k, v)| (k.to_ascii_lowercase(), v)) + .collect(); + } + + /// Drain the atlas names `SetAtlas` failed to resolve (each reported once, sorted) — the app + /// logs them as WARNs pointing at `scripts/era-extract.py`: a stale extraction must name + /// itself, never silently draw blank. + pub fn take_era_atlas_misses(&mut self) -> Vec { + let mut misses: Vec = self.model_mut().era_atlas_missing.drain().collect(); + misses.sort(); + misses + } + /// Push the modifier-key state (shift, ctrl, alt) behind `IsShiftKeyDown`/`IsControlKeyDown`/ /// `IsAltKeyDown`. The app's input pass calls this BEFORE feeding the frame's mouse events, so /// a click handler's modifier fork (the reference's shift-split / ctrl-dressup / @@ -497,6 +522,43 @@ impl UiScript { } } + /// Drop every cached host text metric — FontString measures, message-line row counts, editbox + /// advance tables. The host calls this when its **raster environment** changes (a window + /// resize / fullscreen toggle / uiScale move — anything that shifts the px-per-unit seam its + /// answers were taken under): glyph advances step to whole pixels at the drawn raster size, + /// so a metric measured under one environment does not rescale to another — the font-size + /// snap alone moves a string's unit width by several percent, which is exactly enough for a + /// boot-size measure to fail the post-resize ellipsis fit test and truncate text that fits + /// (the director's fullscreen "Contr..." rows). The environment is the host's to watch — the + /// engine deliberately never sees the seam scale — so staleness is the host's to declare; the + /// round-trips re-answer on the frames that follow (the same one-frame convergence every + /// measure already has). + pub fn invalidate_text_measures(&mut self) { + let mut model = self.model_mut(); + for d in model.region_data.values_mut() { + d.measured = None; + } + // The frame-side caches key on content hashes; a bumped stored key can never equal the + // recomputed one, so the next needing-measure sweep re-requests without this method + // having to know what the keys hash. + for (_, frame) in model.arena.iter_frames_mut() { + match &mut frame.kind_state { + KindState::ScrollingMessage(smf) => { + for line in &mut smf.lines { + line.rows_key = line.rows_key.wrapping_add(1); + } + } + KindState::EditBox(eb) => { + eb.advances_key = eb.advances_key.wrapping_add(1); + } + _ => {} + } + } + // Measured extents are auto-size inputs — the layout gate's read set, same as + // [`Self::set_measured_text`]'s. + model.touch_layout(); + } + /// Feed the number font's per-digit advance widths (logical px, `'0'..='9'` in order) as the /// Lua global table `BENILLA_DIGIT_W` (keyed by digit *string*, `["0"].."["9"]`). The /// synchronous half of the money layout's metrics (the real `SmallMoneyFrame` calls diff --git a/crates/benilla-ui/src/script/model.rs b/crates/benilla-ui/src/script/model.rs index 4dd3a0c4..6bc5e0c7 100644 --- a/crates/benilla-ui/src/script/model.rs +++ b/crates/benilla-ui/src/script/model.rs @@ -6,8 +6,8 @@ use crate::widget::{FrameHandle, RegionHandle, WidgetArena}; use super::{ backdrop, bank, char_stats, container, craft, cursor, death, duel, follow, gossip, inspect, item_text, loot, loot_roll, mail, merchant, party, quest, quest_log, session, skills, slider, - social, spellbook, taxi, trade, tradeskill, trainer, ActionSlot, AuraState, FontObject, - ItemTemplateView, PlayerReqState, RegionData, ScriptValue, SoundRequest, UnitState, + social, spellbook, taxi, trade, tradeskill, trainer, ActionSlot, AuraState, EraAtlasEntry, + FontObject, ItemTemplateView, PlayerReqState, RegionData, ScriptValue, SoundRequest, UnitState, }; // ───────────────────────────────────────────────────────────────────────────────────────────── @@ -71,6 +71,13 @@ pub(crate) struct Model { pub(crate) chat_tab: bool, /// Region visuals (texture path/color/text) + region layout (anchors/size/justify). pub(crate) region_data: HashMap, + /// The Era atlas table (decision 0950): lowercased member name → resolved paint. Pushed once + /// by [`super::UiScript::set_era_atlases`] before the XML loads; read by `SetAtlas` and the + /// loader's `atlas=` attribute. + pub(crate) era_atlas: HashMap, + /// Atlas names `SetAtlas` could not resolve — warn-once, drained by + /// [`super::UiScript::take_era_atlas_misses`] for app-side WARNs. + pub(crate) era_atlas_missing: HashSet, /// Per-frame installed [`Backdrop`] (the tooltip/dialog/panel plate — `` or /// `SetBackdrop`). Absent ⇒ the frame draws no plate. Stored beside the arena like `region_data` /// (the arena models structure, not paint). The client's `frame+0x1ac` pointer. @@ -199,6 +206,15 @@ pub(crate) struct Model { /// [`UiScript::take_sounds`] drain — the outbound Lua→app intent seam ([`sound`]). pub(crate) sound_queue: Vec, + /// The CVar table (decision 0954, [`super::cvars`]): lowercase name → slot. Host-registered + /// only; Lua reads/writes through `GetCVar`/`SetCVar`/`GetCVarDefault`. + pub(crate) cvars: HashMap, + /// `(registered name, new value)` per Lua `SetCVar` since the app's last + /// [`super::UiScript::take_cvar_changes`] drain — the knob-sync + config-dirty cue. + pub(crate) cvar_changes: Vec<(String, String)>, + /// Unknown CVar names already warned about (warn-once, the era-atlas-miss posture). + pub(crate) cvars_warned: HashSet, + /// The action-slot snapshot (keyed by Lua action id 1..120) + the stance page offset the app /// pushes, and the `UseAction` intents it drains — the action seam ([`action`]). pub(crate) actions: HashMap, @@ -686,6 +702,8 @@ impl Model { link_spans: HashMap::new(), chat_tab: false, region_data: HashMap::new(), + era_atlas: HashMap::new(), + era_atlas_missing: HashSet::new(), backdrops: HashMap::new(), font_objects: HashMap::new(), region_resolved: HashMap::new(), @@ -722,6 +740,9 @@ impl Model { session_requests: Vec::new(), pvp_toggles: 0, sound_queue: Vec::new(), + cvars: HashMap::new(), + cvar_changes: Vec::new(), + cvars_warned: HashSet::new(), actions: HashMap::new(), action_states: HashMap::new(), bonus_bar_offset: 0, diff --git a/crates/benilla-ui/src/script/region.rs b/crates/benilla-ui/src/script/region.rs index 529eaea4..93212177 100644 --- a/crates/benilla-ui/src/script/region.rs +++ b/crates/benilla-ui/src/script/region.rs @@ -231,9 +231,11 @@ fn install_region_methods(lua: &Lua) -> mlua::Result<()> { Value::String(s) if s.to_str()?.is_empty() => { data.texture = None; data.color = None; + data.atlas = None; } Value::String(s) => { data.texture = Some(s.to_str()?.to_string()); + data.atlas = None; } Value::Number(_) | Value::Integer(_) => { data.color = Some([ @@ -248,6 +250,7 @@ fn install_region_methods(lua: &Lua) -> mlua::Result<()> { Value::Nil => { data.texture = None; data.color = None; + data.atlas = None; } _ => {} } @@ -271,6 +274,7 @@ fn install_region_methods(lua: &Lua) -> mlua::Result<()> { data.texture = Some(path); data.circular = true; data.portrait_unit = None; + data.atlas = None; Ok(()) })?, )?; @@ -404,7 +408,15 @@ fn install_region_methods(lua: &Lua) -> mlua::Result<()> { let rh = region_handle_of(lua, this)?; let model = lua.app_data_ref::().expect("model"); let d = model.region_data.get(&rh); - let m = d.and_then(|d| d.measured.filter(|m| m.key == d.measure_key())); + // The key carries the owner's effective_scale ([`RegionData::measure_key`]) — the same + // recipe the request loop stamps, or every read under a SetScale'd owner reports stale. + let scale = model + .arena + .region(rh) + .and_then(|r| model.arena.frame(r.owner)) + .map(|f| f.effective_scale) + .unwrap_or(1.0); + let m = d.and_then(|d| d.measured.filter(|m| m.key == d.measure_key(scale))); let size = d.and_then(|d| d.size); let w = m.map(|m| m.w).or(size.map(|s| s.0)).unwrap_or(0.0); let h = m.map(|m| m.h).or(size.map(|s| s.1)).unwrap_or(0.0); @@ -628,6 +640,54 @@ fn install_region_methods(lua: &Lua) -> mlua::Result<()> { Ok((l, r, t, b)) })?, )?; + // SetAtlas(name[, useAtlasSize]) — the Era atlas resolve (decision 0950): one call sets the + // sheet texture + UV sub-rect from the app-pushed table ([`super::UiScript::set_era_atlases`], + // baked offline from the UiTextureAtlas DB2s by scripts/era-extract.py), and with + // useAtlasSize the member's nominal size too. The reference's SetAtlas hard-errors on an + // unknown name; ours blanks the region and records a warn-once miss instead + // ([`super::UiScript::take_era_atlas_misses`]) — a stale extraction must not kill the whole + // XML load, but it must name itself. + m.set( + "SetAtlas", + lua.create_function( + |lua, (this, name, use_size): (Table, String, Option)| { + let rh = region_handle_of(lua, &this)?; + let mut model = lua.app_data_mut::().expect("model"); + let key = name.to_ascii_lowercase(); + let Some(entry) = model.era_atlas.get(&key).cloned() else { + model.era_atlas_missing.insert(key); + let d = model.region_data.entry(rh).or_default(); + d.texture = None; + d.atlas = None; + return Ok(()); + }; + let d = model.region_data.entry(rh).or_default(); + d.circular = false; + d.portrait_unit = None; + d.texture = Some(entry.file); + d.tex_coords = Some(TexCoords::Rect(entry.uv)); + d.atlas = Some(key); + if use_size.unwrap_or(false) { + let new = Some((entry.size[0], entry.size[1])); + let changed = !size_bits_eq(d.size, new); + d.size = new; + if changed { + model.touch_layout(); + } + } + Ok(()) + }, + )?, + )?; + // GetAtlas() → the member name the last SetAtlas applied (nil for an ordinary texture). + m.set( + "GetAtlas", + lua.create_function(|lua, this: Table| { + let rh = region_handle_of(lua, &this)?; + let model = lua.app_data_ref::().expect("model"); + Ok(model.region_data.get(&rh).and_then(|d| d.atlas.clone())) + })?, + )?; // SetFontObject("GameFontNormal") — re-point this FontString at a named virtual Font object: its // resolved paint (face/height/color/outline) is copied into the region as the new default. An // explicit SetFont/SetTextColor afterward overrides. An unknown name errors (never silently no-op). diff --git a/crates/benilla-ui/src/script/tests/measure.rs b/crates/benilla-ui/src/script/tests/measure.rs index 67b52a2f..d4301b77 100644 --- a/crates/benilla-ui/src/script/tests/measure.rs +++ b/crates/benilla-ui/src/script/tests/measure.rs @@ -168,3 +168,105 @@ fn zero_width_fontstring_autosizes_to_its_line() { assert_eq!(v_left, 119.0); assert!(s.errors().is_empty(), "{:?}", s.errors()); } + +/// The frame-scale seam (0219 §2's divergence, closed): a `SetScale`'d frame's quads carry its +/// `effective_scale` out to the renderer — the rect is already scale-multiplied by layout, and +/// the renderer needs the same factor for the GLYPH raster size — and the measure round-trip +/// rides it too: the request names the scale (the host measures at the drawn size), the cache +/// key holds it (a re-scale re-measures), all before any quad draws stale text. +#[test] +fn frame_scale_rides_the_quads_and_the_measure_key() { + let mut s = script(); + s.set_screen_size(800.0, 600.0); + s.run( + r#" + local w = CreateFrame("Frame", "Win") + w:SetPoint("TOPLEFT", 0, -100); w:SetSize(400, 300) + w:SetScale(0.8) + local t = w:CreateFontString("ScaledLabel", "ARTWORK") + t:SetText("Options") + t:SetPoint("TOPLEFT", 10, -10) + "#, + ) + .unwrap(); + s.resolve(); + // The measure request carries the owner's effective scale. + let reqs = s.fontstrings_needing_measure(); + assert_eq!(reqs.len(), 1); + let r = &reqs[0]; + assert_eq!(r.scale, 0.8, "request names the drawn-size scale"); + let old_key = r.key; + let (id, key) = (r.id, r.key); + s.set_measured_text(&[(id, 50.0, 16.0, key)]); + s.resolve(); + assert!(s.fontstrings_needing_measure().is_empty(), "cache warm"); + // Every quad of the scaled frame carries the scale — frame slot and region alike. + let quads = s.extract(); + let label = quads + .iter() + .find(|q| matches!(&q.content, QuadContent::Text { text: Some(t), .. } if t == "Options")) + .expect("label quad"); + assert_eq!(label.scale, 0.8); + // The label's rect is scale-multiplied by layout (width 50 × 0.8 = 40) — the quad's scale is + // for the glyph raster, not a second rect multiply. + let lr = label.rect.expect("label rect"); + assert!((lr.width() - 40.0).abs() < 0.01, "width {}", lr.width()); + // A re-scale invalidates the measure key: the same text re-measures at the new drawn size. + s.run(r#"Win:SetScale(1.25)"#).unwrap(); + s.resolve(); + let reqs = s.fontstrings_needing_measure(); + assert_eq!(reqs.len(), 1, "SetScale re-measures"); + assert_eq!(reqs[0].scale, 1.25); + assert_ne!(reqs[0].key, old_key, "key carries the scale"); + assert!(s.errors().is_empty(), "{:?}", s.errors()); +} + +/// The host's raster-environment invalidation ([`UiScript::invalidate_text_measures`]): a warm +/// FontString measure cache re-requests after the call — same content, same key (the key hashes +/// content, not the host's seam scale; the STALENESS is the host's to declare, which is the whole +/// seam). This is the engine half of the fullscreen-truncation fix: measures answered under one +/// seam scale kept satisfying fit tests run under another, and the ellipsis ate fitting text +/// ("Contr...", director 2026-08-04; reproduced end-to-end by `WOW_RESIZE`). +#[test] +fn invalidate_text_measures_reopens_the_round_trip() { + let mut s = script(); + s.set_screen_size(800.0, 600.0); + s.run( + r#" + local w = CreateFrame("Frame", "Win") + w:SetPoint("TOPLEFT", 0, -100); w:SetSize(400, 300) + local t = w:CreateFontString("Label", "ARTWORK") + t:SetText("Keybindings") + t:SetPoint("TOPLEFT", 10, -10) + "#, + ) + .unwrap(); + s.resolve(); + let reqs = s.fontstrings_needing_measure(); + assert_eq!(reqs.len(), 1); + let (id, key) = (reqs[0].id, reqs[0].key); + s.set_measured_text(&[(id, 74.0, 12.0, key)]); + s.resolve(); + assert!(s.fontstrings_needing_measure().is_empty(), "cache warm"); + assert_eq!( + s.eval::("return Label:GetStringWidth()").unwrap(), + 74.0 + ); + + s.invalidate_text_measures(); + // The measured extent is gone (GetStringWidth back to its unmeasured 0)… + assert_eq!( + s.eval::("return Label:GetStringWidth()").unwrap(), + 0.0, + "stale measure dropped, not served" + ); + // …and the round-trip reopens with the SAME content key — the request is the host's cue to + // re-measure under its new environment; nothing about the region itself changed. + let reqs = s.fontstrings_needing_measure(); + assert_eq!(reqs.len(), 1, "re-requests after invalidation"); + assert_eq!( + reqs[0].key, key, + "content key unchanged — only the answer was stale" + ); + assert!(s.errors().is_empty(), "{:?}", s.errors()); +} diff --git a/crates/benilla-ui/src/script/types.rs b/crates/benilla-ui/src/script/types.rs index 4f2b412e..dfe16b1f 100644 --- a/crates/benilla-ui/src/script/types.rs +++ b/crates/benilla-ui/src/script/types.rs @@ -287,6 +287,25 @@ pub struct ExtractedQuad { /// frame is a ScrollFrame's scroll child, or any descendant of one — nested ScrollFrames /// intersect (see [`UiScript::extract`](super::UiScript::extract)'s `effective_clip`). `None` = unclipped, the common case. pub clip: Option, + /// The owning frame's `effective_scale` (`propagation.md`: `parentScale · ownScale`). The + /// `rect` above already carries it — the layout solver multiplies every placement by the + /// owner's scale — but glyph METRICS don't live in the rect: the renderer must rasterize a + /// `Text` quad's font at `font_height × scale` (and scale the shadow offset with it) or a + /// `SetScale`'d window draws scaled boxes full of unscaled text, the exact divergence + /// decision 0219 §2 recorded. 1.0 for the unscaled common case. + pub scale: f32, +} + +/// One Era atlas member (decision 0950): the resolved paint `SetAtlas`/`atlas=` apply. The app +/// builds these from `WoW-era/_extracted_ui/manifest.json` — `file` is an app-resolvable texture +/// path (the `era:` scheme), `uv` the normalized `[left, right, top, bottom]` sub-rect +/// ([`TexCoords::Rect`] order), `size` the member's nominal draw size in UI units (what +/// `useAtlasSize` applies). +#[derive(Clone, Debug, PartialEq)] +pub struct EraAtlasEntry { + pub file: String, + pub uv: [f32; 4], + pub size: [f32; 2], } /// The visual state of a region leaf (`Texture`/`FontString`), stored beside the arena because @@ -315,6 +334,10 @@ pub(crate) struct RegionData { pub(crate) alpha: Option, /// A texture path (`SetTexture("Interface\\...")`). pub(crate) texture: Option, + /// The Era atlas member this texture was last set from (`SetAtlas`, decision 0950) — what + /// `GetAtlas` reports. Bookkeeping only: `texture`/`tex_coords`/`size` carry the resolved + /// paint; any later `SetTexture` clears it (an ordinary region again). + pub(crate) atlas: Option, /// A color: a solid fill (`SetTexture(r,g,b,a)`) or a vertex tint (`SetVertexColor`). pub(crate) color: Option<[f32; 4]>, /// This region is a **portrait**: draw its texture masked to the inscribed circle (set by @@ -406,7 +429,10 @@ impl RegionData { /// insets on it). The layout solver's own read (layout.rs) deliberately does NOT key-check: /// it keeps the last-known box until the fresh measure lands, so lines never collapse for the /// round-trip frame. - pub(crate) fn measure_key(&self) -> u64 { + /// `scale` is the owner frame's `effective_scale`: it's in the key because the host measures + /// at the drawn raster size ([`MeasureRequest::scale`]), so a `SetScale` under a cached + /// measure must invalidate it exactly like a font-size change. + pub(crate) fn measure_key(&self, scale: f32) -> u64 { use std::hash::{Hash, Hasher}; let mut hasher = std::collections::hash_map::DefaultHasher::new(); self.text.clone().unwrap_or_default().hash(&mut hasher); @@ -418,6 +444,7 @@ impl RegionData { // Outline is in the key because it changes measured WIDTH under the client's step law // (an outlined font steps +1px more per glyph — GlyphStepBase 0x5ca2b0). (self.outline as u8).hash(&mut hasher); + scale.to_bits().hash(&mut hasher); hasher.finish() } } @@ -437,6 +464,12 @@ pub struct EditBoxAdvanceRequest { /// The resolved glyph outline — the per-glyph step bias (+1px, GlyphStepBase 0x5ca2b0) /// changes every advance. pub outline: Outline, + /// The box frame's `effective_scale`. The host measures at the drawn raster size + /// (`height × scale × seam`) but divides the answer by the SEAM ALONE, so the advances come + /// back in screen UI units — the space the engine's ÷seam mouse feed and the box's resolved + /// (scale-multiplied) rect both live in. Contrast [`MeasureRequest::scale`], whose answer is + /// frame-local. + pub scale: f32, /// The DISPLAY string (mask-aware) the table indexes. pub text: String, /// `Some(width)` for a multiline box: also answer the wrapped-row starts + row pitch at this @@ -499,6 +532,12 @@ pub struct MeasureRequest { /// The resolved glyph outline — it biases the client's per-glyph step (+1px, GlyphStepBase /// 0x5ca2b0), so the measured width depends on it. pub outline: Outline, + /// The owner frame's `effective_scale`. The host must measure at the DRAWN raster size — + /// `font_height × scale × its own screen seam` — and divide the px answer back by the full + /// product: glyph advances step to whole pixels (the step law), so a measure taken at the + /// unscaled size is not `scale ×` the drawn one, and an auto-sized FontString in a scaled + /// frame would mis-fit its own text (spurious wrap → spurious "..." ellipsis). + pub scale: f32, pub text: String, /// Cache key — pass back verbatim. pub key: u64, @@ -520,10 +559,14 @@ pub struct LineMeasureRequest { pub index: u32, pub font: Option, pub height: Option, - /// The frame's resolved inner width — the wrap constraint. + /// The frame's resolved inner width — the wrap constraint. Already scale-multiplied (it is + /// the resolved rect's width), unlike `height`, which is the font's local size. pub wrap_width: f32, /// The resolved glyph outline (step-law width bias, as on [`MeasureRequest`]). pub outline: Outline, + /// The frame's `effective_scale` — the host wraps at the drawn raster size + /// (`height × scale × seam`), as on [`MeasureRequest::scale`]. + pub scale: f32, pub text: String, /// Cache key — pass back verbatim. pub key: u64, diff --git a/crates/benilla/Cargo.toml b/crates/benilla/Cargo.toml index b68dd929..d196aadf 100644 --- a/crates/benilla/Cargo.toml +++ b/crates/benilla/Cargo.toml @@ -50,6 +50,12 @@ crossbeam-channel = "0.5" benilla-assets = { path = "../benilla-assets" } benilla-formats = { path = "../benilla-formats" } benilla-protocol = { path = "../benilla-protocol" } +# The Era atlas manifest (decision 0950): `WoW-era/_extracted_ui/manifest.json`, written offline by +# scripts/era-extract.py and read once at boot. Both already in the tree (benilla-assets carries +# serde; serde_json rides Bevy transitively) — declared so the call site is an honest dependency. +serde = { version = "1", features = ["derive"] } +serde_json = "1" +toml = "0.8" # ECS-native physics: collider storage + broadphase BVH + shape-casts, with a thin custom kinematic # character controller on top (decision 0009). Pinned 0.6: it targets Bevy 0.18 (verified single, # unified bevy 0.18.1 in the tree); 0.7.0 jumps to Bevy 0.19 and would duplicate bevy. avian wraps diff --git a/crates/benilla/assets/ui/GameMenuFrame.xml b/crates/benilla/assets/ui/GameMenuFrame.xml index ae857099..d3576b72 100644 --- a/crates/benilla/assets/ui/GameMenuFrame.xml +++ b/crates/benilla/assets/ui/GameMenuFrame.xml @@ -37,28 +37,47 @@ and `GameMenuFrame` are Era API surface an addon addresses by name. SCOPE / divergences (noted, not bugs): - - **Five buttons ship DISABLED**: Video/Sound/Interface Options, Key Bindings, Macros have no - panel behind them and won't for a long time. The director's call (this session) is that they - stay in the ladder — the frame keeps the ref's exact 195x246 and its exact silhouette — wearing - the template's own `-Disabled` art and grey label rather than being cut or left as clickable - no-ops. Each goes live by deleting one `self:Disable()` and writing the ref OnClick that is - already quoted beside it. (MicroMenu.xml made the opposite call for its dead icons, and says - why: three grey icons mid-row read as a broken bar. A menu of labels with some greyed is the - ordinary idiom — it reads as "not yet", which is the truth.) + - **The ladder is the ERA menu's** (director's call on the 0951 review — supersedes 0951 + §3's five-button collapse): the Era client's own ESC menu shape, greyed where benilla + has nothing behind the entry yet. Geometry quoted from the era dump @ 1.15.9 (Gethe + wow-ui-source, classic_era, the 0950 pin): + · Blizzard_SharedXML/Classic/Frame/MainMenuFrameTemplates.xml l.18-33 — the menu is a + VerticalLayoutFrame, buttonTemplate MainMenuFrameButtonTemplate, padding + top/bottom/left/right = 32/28/28/28, spacing 0; l.11-16 — the button is a 144x21 + UIPanelButtonTemplate with the white/white/grey label trio (the SAME art + trio the + 1.12 GameMenuButtonTemplate carries — no era-only art in this menu at all). + · Shared/Frame/MainMenuFrameTemplates.lua l.61-71 — AddSection() puts a 20-unit gap + (the next button's topPadding) between groups; AddCloseButton uses the same 20. + · Shared/GameMenuFrame.lua l.62-162 — the Era ladder: Options · [gap] · AddOns, Edit + Mode, Support, Macros · [gap] · Logout, Exit Game · [gap] · Return to Game. (Store / + Rewards / What's New / Ratings are structurally off in Era — store disabled, no + account store, IsStandard() false, koKR only.) + · The layout math those constants produce, precomputed here (this engine has no layout + frames): width 28+144+28 = 200; button tops 32/73/94/115/136/177/198/239; height + 239+21+28 = 288. Anchored as a TOPLEFT chain so the gaps read in the XML. + · The header — Classic/Dialog/DialogTemplates.xml l.11-43 (ClassicDialogHeaderTemplate): + the same 256x64 UI-DialogBox-Header art, text = MAINMENU_BUTTON, swapped to + GameFontNormalMed1 (13px) by MainMenuFrameMixin:OnLoad. Both header idioms (1.12's + TOP+12 texture / era's 39-tall frame with the art centered) seat the art within half + a unit of each other; the 1.12 transcription is kept, the font and string are era's. + - **AddOns / Edit Mode / Support / Macros ship DISABLED** — nothing behind them yet + (`GameMenuButton_Pending`): the template's own `-Disabled` art + grey label, exactly + how the era menu greys a dead entry (SetEnabled(false) → DisabledFont). Each goes live + by deleting one `GameMenuButton_Pending` call and writing its OnClick. + - The Options label stays a literal "Options": 1.12 GlobalStrings has no era + GAMEMENU_OPTIONS (checked; its OPTIONS_MENU is "Options Menu", a different string). - The ref frame is `toplevel="true" movable="true" parent="UIParent"`. Dropped: this engine has no cross-file `parent=` attachment (SkillFrame.xml's header comment), no frame-level raise, and `movable` is inert in the ref too (GameMenuFrame defines no drag scripts, so it never actually moves). `frameStrata="DIALOG"` is what puts it over the world UI, and that is kept. - - No `OptionsFrameCancel` rung in ToggleGameMenu (UiPanels.xml) — no OptionsFrame exists. --> - + + + + + + - + + @@ -168,7 +199,10 @@ - + + @@ -176,40 +210,58 @@ - + + + - - - - - - - @@ -220,7 +272,7 @@ re-read that state. --> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + getglobal(self:GetName() .. "Label"):SetText("Gameplay") + getglobal(self:GetName() .. "Bg"):SetAtlas("Options_CategoryHeader_1", true) + + + + + + + + + + + + + + + + + + getglobal(self:GetName() .. "Label"):SetText("System") + getglobal(self:GetName() .. "Bg"):SetAtlas("Options_CategoryHeader_2", true) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + if this:GetText() == "" then + OptionsFrameSearchBoxInstructions:Show() + OptionsFrameSearchBoxClearButton:Hide() + else + OptionsFrameSearchBoxInstructions:Hide() + OptionsFrameSearchBoxClearButton:Show() + end + + this:ClearFocus() + + + + + + + + + + + + + + OptionsFrame_UpdateScale() + OptionsFrame_SelectCategory(this.selectedCategory or "Controls") + + + + diff --git a/crates/benilla/assets/ui/UiPanels.xml b/crates/benilla/assets/ui/UiPanels.xml index 6a99dbe0..c11a1cb1 100644 --- a/crates/benilla/assets/ui/UiPanels.xml +++ b/crates/benilla/assets/ui/UiPanels.xml @@ -89,6 +89,13 @@ -- every non-center panel (`CanOpenPanels` below). That is the rule behind "you can't open -- anything with the ESC menu up". whileDead: dying is exactly when you want to log out. UIPanelWindows["GameMenuFrame"] = { area = "center", pushable = 0, whileDead = 1 } + -- The options window (decision 0950, OptionsFrame.xml) — the ref's own registration + -- verbatim (`UIPanelWindows["OptionsFrame"] = { area = "center", pushable = 0, + -- whileDead = 1 }`, UIParent.lua l.16): the second native-center row. Same MODE as the + -- menu above — it takes the screen, and dying is exactly when you might want to retune + -- the client. (The ref's l.17-18 register SoundOptionsFrame/UIOptionsFrame the same way; + -- those panels are this one window's category rows now, so the rows have no frames.) + UIPanelWindows["OptionsFrame"] = { area = "center", pushable = 0, whileDead = 1 } -- The world map (decision 0203 phase 2) — the ref's own line verbatim (UIParent.lua l.30) -- and the first **full** row. It was a plain toplevel until now, showing itself with a bare -- `WorldMapFrame:Show()` and so bypassing this manager entirely: with the game menu up it @@ -495,8 +502,9 @@ -- cancels the cast and ONLY the cast; the target drops on a later press once nothing -- else claims it; and only a press with NOTHING left to eat opens the menu. -- - -- Absent branches, and why: no OptionsFrame (nothing to cancel — GameMenuFrame.xml's five - -- disabled buttons are exactly that gap). ONE benilla ADDITION, ahead of everything: a held cursor item is + -- Every reference branch is present now (the OptionsFrame rung landed with decision + -- 0950 — ours hides rather than cancel-clicks; its own comment below says why). ONE + -- benilla ADDITION, ahead of everything: a held cursor item is -- dropped (ClearCursor). The cursor-item model (decision 0068 T2) has no real-client ESC -- handling to mirror — vanilla leaves the item on the cursor — so this is a deliberate -- benilla convenience (ESC as the universal "get me out" key). Guarded so it stays inert @@ -509,8 +517,13 @@ -- abort the whole chain there (the `CloseMenus and` guard two rungs down, same reason). local menu = getglobal("GameMenuFrame") - -- The micro button's toggle (l.1466-1480). + -- The micro button's toggle (l.1466-1480). Its own first move is the ref's too + -- (l.1467-1469): an open options window is put away before the menu toggles. if clicked then + local options = getglobal("OptionsFrame") + if options and options:IsVisible() then + HideUIPanel(options) + end if menu and menu:IsVisible() then PlaySound("igMainMenuQuit") HideUIPanel(menu) @@ -532,11 +545,21 @@ if StaticPopup_EscapePressed() then return end - -- The menu itself closes next (l.1485-1487) — before every close/cancel rung below, so - -- ESC with the menu up only puts the menu away. (The OptionsFrame rung the reference - -- checks between these two has no benilla frame; GameMenuFrame.xml's disabled buttons - -- ARE that gap.) Note the sound: closing by ESC plays igMainMenuQuit, the same kit the - -- Exit Game button uses — the reference's choice, not a copy/paste slip here. + -- The options window next (l.1483-1484, live since decision 0950 — this rung sat + -- unfilled while GameMenuFrame.xml's disabled buttons were the gap). The ref + -- cancel-clicks (`OptionsFrameCancel:Click()`); ours hides: benilla applies changes + -- live, there is no pending-change model for a Cancel to roll back, so putting the + -- window away IS the whole gesture. Looked up like the menu above (a headless panel + -- test loads this file without OptionsFrame.xml behind it). + local options = getglobal("OptionsFrame") + if options and options:IsVisible() then + HideUIPanel(options) + return + end + -- The menu itself closes next (l.1485-1487) — before every close/cancel rung below, + -- so ESC with the menu up only puts the menu away. Note the sound: closing by ESC + -- plays igMainMenuQuit, the same kit the Exit Game button uses — the reference's + -- choice, not a copy/paste slip here. if menu and menu:IsVisible() then PlaySound("igMainMenuQuit") HideUIPanel(menu) diff --git a/crates/benilla/src/assets/era.rs b/crates/benilla/src/assets/era.rs new file mode 100644 index 00000000..289be16c --- /dev/null +++ b/crates/benilla/src/assets/era.rs @@ -0,0 +1,87 @@ +//! The app side of the Era atlas seam (decision 0950): read the extractor's manifest once at +//! boot and turn it into the [`UiScript`](benilla_ui::script::UiScript) atlas table, whose +//! entries carry `era:`-scheme texture paths that [`super::WorldAssets::sprite_texture`] +//! resolves from disk. Runtime never touches CASC or DB2 — `scripts/era-extract.py` baked the +//! `UiTextureAtlas` indirection offline into `WoW-era/_extracted_ui/{manifest.json, +//! textures/.blp}`; a missing or stale extraction WARNs and draws blank, never fails the +//! load (the misses name themselves through `take_era_atlas_misses`). + +use benilla_ui::script::EraAtlasEntry; +use serde::Deserialize; + +/// The extracted Era UI assets root — through the `WoW-era` symlink `wt.sh link_wow` provisions +/// in every slot (same `CARGO_MANIFEST_DIR` convention as the MPQ `Data` default elsewhere in +/// this crate: the path follows the worktree that built the binary). +pub(crate) fn era_ui_root() -> std::path::PathBuf { + std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../WoW-era/_extracted_ui") +} + +/// `manifest.json`, exactly as `scripts/era-extract.py` writes it (the fields we consume). +#[derive(Deserialize)] +struct Manifest { + build: String, + atlases: std::collections::HashMap, +} + +#[derive(Deserialize)] +struct Member { + /// Relative to the extraction root (`textures/.blp`). + file: String, + /// The sheet's dimensions in pixels. + atlas_size: [f32; 2], + /// Pixel edges `[left, top, right, bottom)` within the sheet. + rect: [f32; 4], + /// Nominal draw size in UI units (`useAtlasSize`). + size: [f32; 2], +} + +/// Load and normalize the manifest into UiScript table entries: pixel rects become the +/// `[left, right, top, bottom]` UV form `TexCoords::Rect` stores; files ride the `era:` scheme. +/// `None` = no usable manifest (absent install, extraction never run, or a parse error — each +/// WARNs with the fix spelled out; the distinction matters when diagnosing a blank window). +pub(crate) fn load_era_atlases() -> Option> { + let path = era_ui_root().join("manifest.json"); + let bytes = match std::fs::read(&path) { + Ok(b) => b, + Err(e) => { + bevy::log::warn!( + "era: no UI manifest at {} ({e}) — Era-styled windows will draw blank; \ + run scripts/era-extract.py", + path.display() + ); + return None; + } + }; + let manifest: Manifest = match serde_json::from_slice(&bytes) { + Ok(m) => m, + Err(e) => { + bevy::log::warn!( + "era: manifest.json unparseable ({e}) — re-run scripts/era-extract.py" + ); + return None; + } + }; + bevy::log::debug!( + "era: atlas manifest for build {} — {} members", + manifest.build, + manifest.atlases.len() + ); + Some( + manifest + .atlases + .into_iter() + .map(|(name, m)| { + let [aw, ah] = m.atlas_size; + let [l, t, r, b] = m.rect; + ( + name, + EraAtlasEntry { + file: format!("era:{}", m.file), + uv: [l / aw, r / aw, t / ah, b / ah], + size: [m.size[0], m.size[1]], + }, + ) + }) + .collect(), + ) +} diff --git a/crates/benilla/src/assets/mod.rs b/crates/benilla/src/assets/mod.rs index 0dbc0e0e..582b9121 100644 --- a/crates/benilla/src/assets/mod.rs +++ b/crates/benilla/src/assets/mod.rs @@ -27,6 +27,9 @@ use benilla_formats::{open_chain, read_texture_mip_chain, read_texture_rgba, Cha mod images; pub(crate) use images::*; +/// The Era atlas manifest side of decision 0950 (`era:` texture scheme + the UiScript table). +pub(crate) mod era; + /// Shared asset store: the **one** open patch chain plus dedup caches, used by every model/texture /// load (terrain layers, doodads, WMOs, creatures, GameObjects). Replaces the three independent /// chains we used to open. Deduping by path/material means each unique BLP is decoded + uploaded @@ -178,6 +181,23 @@ impl WorldAssets { path: &str, images: &mut Assets, ) -> Option> { + // `era:` (decision 0950): a loose extracted file under + // `WoW-era/_extracted_ui/`, never the MPQ chain — the scheme string is its own cache key + // (verbatim, case-sensitive: the manifest writes it, authors copy it). + if let Some(rel) = path.strip_prefix("era:") { + if let Some(cached) = self.sprites.get(path) { + return cached.clone(); + } + let loaded = std::fs::read(era::era_ui_root().join(rel)) + .ok() + .and_then(|bytes| benilla_formats::blp_to_rgba(&bytes).ok()) + .map(|(w, h, rgba)| images.add(sprite_image(w, h, rgba))); + if loaded.is_none() { + warn!("era: sprite '{path}' unreadable — stale extraction? run scripts/era-extract.py"); + } + self.sprites.insert(path.to_string(), loaded.clone()); + return loaded; + } // FrameXML/DBC texture references are canonically EXTENSIONLESS // (`Interface\Buttons\UI-Quickslot2`) — the real client appends `.blp` at resolve. Paths // that already carry an extension (`textures\moon.blp`) pass through unchanged. diff --git a/crates/benilla/src/capture/fixtures.rs b/crates/benilla/src/capture/fixtures.rs index 422668c4..136e75b9 100644 --- a/crates/benilla/src/capture/fixtures.rs +++ b/crates/benilla/src/capture/fixtures.rs @@ -919,6 +919,18 @@ pub(super) fn seed_ui_fixture( selection.target = Some(wolf); selection.guid = Some(WOLF_GUID); } + UiFixture::Options => { + let Some(script) = script else { + return; + }; + // Static window, opened through the live panel path — nothing else to seed. What the + // capture pins: the era chrome (nine-slice seams, right-edge straddle), the tab + // plates, the search-box seat, the category list art with Controls selected (the + // OnShow default), and the window's fit scale. + if let Err(e) = script.run("ShowUIPanel(OptionsFrame)") { + warn!("capture: ui-options seed failed to open the window: {e}"); + } + } UiFixture::SpellBook => { // The director's own report reproduced (decision 0228): a HUMAN WARRIOR (race 1, // class 1) who learned Fireball + Mind Flay via a GM command during testing. The book diff --git a/crates/benilla/src/capture/mod.rs b/crates/benilla/src/capture/mod.rs index eaa2b014..206e998a 100644 --- a/crates/benilla/src/capture/mod.rs +++ b/crates/benilla/src/capture/mod.rs @@ -358,6 +358,8 @@ struct CaptureCtx { /// The frozen clock is in force ([`CAPTURE_FRAME_DT`]) — always, except under a perf probe, /// whose entire measurement *is* the real frame cost. frozen_clock: bool, + /// `$WOW_RESIZE=WxH` already applied (once, at first settle) — see the `Building` arm. + resized: bool, /// Probe samples (frame ms). probe_samples: Vec, /// Process CPU seconds at the first sampled frame — the window baseline for the probe line's @@ -365,6 +367,15 @@ struct CaptureCtx { probe_cpu_start: Option, } +/// `$WOW_RESIZE=WxH` — resize the window to this (logical px) once the image first settles, +/// then settle again before shooting. The mid-session resolution-change instrument (see the +/// `Building` arm). `None` when unset or malformed. +fn resize_request() -> Option<(u32, u32)> { + let v = std::env::var("WOW_RESIZE").ok()?; + let (w, h) = v.split_once('x')?; + Some((w.parse().ok()?, h.parse().ok()?)) +} + /// The present mode a perf probe uncaps to (also the live probe's — see `probes.rs`). /// /// `AutoNoVsync`, measured, not assumed: explicit `Immediate` on macOS/Metal is a trap — A/B'd @@ -587,6 +598,7 @@ impl Plugin for CapturePlugin { ui_seeded: false, probe_frames, frozen_clock, + resized: false, probe_samples: Vec::new(), probe_cpu_start: None, }) @@ -732,6 +744,20 @@ fn drive_capture( } if watch.stable < stable_frames() && !capped { Phase::Building(n + 1) + } else if let Some((rw, rh)) = resize_request().filter(|_| !ctx.resized) { + // `$WOW_RESIZE=WxH` (logical px): a mid-session resolution change, applied only + // once the image has SETTLED at the boot size — the whole UI has been built and + // its text measured under the boot scale before the window changes, which is + // exactly the fullscreen-toggle flow a fresh boot at the target size can't + // exercise (stale text-metric caches were invisible to every same-size capture). + // The stability watch restarts and the shot photographs the post-resize frame. + ctx.resized = true; + if let Ok(mut w) = windows.single_mut() { + w.resolution.set(rw as f32, rh as f32); + } + watch.stable = 0; + info!("capture: resized to {rw}x{rh}, re-settling"); + Phase::Building(0) } else if fixture_age.is_some() { if let Some(state) = fx_state.as_deref_mut() { state.armed = true; // scene ready — the fixture spawns now, age clock clean diff --git a/crates/benilla/src/capture/scenarios.rs b/crates/benilla/src/capture/scenarios.rs index d75b4e07..fdd55d4b 100644 --- a/crates/benilla/src/capture/scenarios.rs +++ b/crates/benilla/src/capture/scenarios.rs @@ -88,6 +88,11 @@ pub(super) enum UiFixture { /// capture checks the header (left-flush, Say-white), the typed text past the live insets, /// and the three-piece input border. ChatEdit, + /// The era-styled Options window (decisions 0950/0951), opened through the live panel path — + /// the look-pass instrument for the whole options arc: chrome nine-slice seams, tab plates, + /// search-box seat, category list art, the window scale. Static (no server state touched), + /// so its pixels move only when the window or the atlas seam does. + Options, /// A **floating overhead name with the river surface behind it** — the world-text-vs-liquid /// draw-order instrument. A named unit stands 25 yd out in the Elwynn river (the `water-noon` /// camera), so its name projects onto the water *beyond* it: the exact geometry of the @@ -811,6 +816,16 @@ pub(super) const ON_DEMAND: &[Scenario] = &[ minute: 720, ui: Some(UiFixture::ChatEdit), }, + // The era Options window over the ground scene. Run with + // `WOW_CAPTURE_UI=1 WOW_CAPTURE=ui-options`. + Scenario { + name: "ui-options", + map: MAP_AZEROTH, + eye: GROUND_EYE, + look: GROUND_LOOK, + minute: 720, + ui: Some(UiFixture::Options), + }, // An overhead NAME with the river surface behind it — the world-text-vs-liquid draw-order // instrument (see [`UiFixture::NameWater`]). Same camera as `water-noon`, plus a named unit // out in the water. Run with `WOW_CAPTURE=name-water`. diff --git a/crates/benilla/src/cvars.rs b/crates/benilla/src/cvars.rs new file mode 100644 index 00000000..617da869 --- /dev/null +++ b/crates/benilla/src/cvars.rs @@ -0,0 +1,431 @@ +//! benilla's **CVar host** — registration, knob sync, and the client's first persistence +//! (decision 0954). The engine holds the table and the Lua API ([`benilla_ui::script`]'s +//! `GetCVar`/`SetCVar`); this module is everything host-side: +//! +//! - **The registered set** ([`REGISTERED`]): only vars backed by a real engine knob (the +//! honest-tree rule). Defaults are the code's own truths — the sound quartet is the client's +//! verified CVar registration defaults (wow-re `benilla-pins.md` B10, quoted in +//! [`crate::sound::SoundConfig`]), `uiScale`/`farclip` are benilla's shipped defaults — +//! and a test welds each string to the constant it mirrors so they cannot drift. +//! - **Boot**: read `benilla/config.toml` ([`crate::local_state`]) and apply it to the knob +//! resources; when the UI VM exists, register the table and push the resolved session values +//! so `GetCVar` answers what the client is actually doing. +//! - **Sync**: drain Lua `SetCVar` changes into the knob resources each frame and mark the +//! config dirty. +//! - **Save**: dirty + one quiet second → rewrite `config.toml` atomically (and flush on +//! `AppExit`). The file holds **only values that moved off their default** — a diff, not a +//! dump — plus any entries this build doesn't know (a newer build's keys survive a downgrade; +//! same posture for a hand-added key: preserved verbatim, warned once). +//! +//! **Env overrides win for the session and never touch the file**: `WOW_UI_SCALE`/`WOW_FARCLIP` +//! beat the loaded config (they exist to make taste iteration a relaunch — pinning one into the +//! config would make the A/B sticky), the session runs and saves around them, and the file keeps +//! whatever it already said for those keys. + +use std::collections::{BTreeMap, HashSet}; +use std::time::Instant; + +use bevy::prelude::*; + +use crate::sound::SoundConfig; +use crate::ui_script::UiScaleCvar; +use crate::view::{ViewDistance, FARCLIP_RANGE}; +use benilla_ui::script::UiScript; + +/// The host-backed CVars: `(registered name, default)`. Grows one row per knob a settings page +/// actually wires — never ahead of the knob (see the module doc). +pub(crate) const REGISTERED: &[(&str, &str)] = &[ + ("MasterVolume", "1"), + ("SoundVolume", "1"), + ("MusicVolume", "0.4"), + ("AmbienceVolume", "0.6"), + ("uiScale", "0.9"), + ("farclip", "777"), +]; + +/// `config.toml`'s shape: a `[cvars]` table of `Name = "value"` strings (CVars are strings in +/// the reference too; consumers parse and clamp at their edge). BTreeMap so the file is stably +/// sorted on every save. +#[derive(serde::Serialize, serde::Deserialize, Default)] +struct LocalConfig { + #[serde(default)] + cvars: BTreeMap, +} + +/// The persistence state: what the file said, which keys the environment overrides this +/// session, and the dirty/debounce pair. +#[derive(Resource, Default)] +pub(crate) struct CvarPersist { + /// The file's `[cvars]` entries, verbatim spelling — the merge base every save starts from + /// (unknown keys ride through untouched, env-overridden keys keep their stored value). + file: BTreeMap, + /// Lowercased names whose value came from an env var this session (never saved). + env_overridden: HashSet, + /// The engine table has been registered + seeded (once, when the VM first exists). + registered: bool, + /// A change since the last save; `last_change` drives the one-quiet-second debounce. + dirty: bool, + last_change: Option, +} + +/// How long a dirty config sits before the save fires — long enough to coalesce a slider drag, +/// short enough that a crash loses one gesture, not a session ("write-on-change, debounced"). +const SAVE_QUIET: std::time::Duration = std::time::Duration::from_secs(1); + +pub(crate) struct CvarPlugin; + +impl Plugin for CvarPlugin { + fn build(&self, app: &mut App) { + app.init_resource::() + .add_systems(Startup, load_config) + .add_systems(Update, (sync_cvars, save_config).chain()); + } +} + +/// Apply one CVar to its knob resource (parse + the knob's own clamp). `false` = not a knob this +/// build knows (the caller decides whether that warns or rides through). +fn apply_to_knobs( + name: &str, + value: &str, + sound: &mut SoundConfig, + scale: &mut UiScaleCvar, + view: &mut ViewDistance, +) -> bool { + let Ok(v) = value.parse::() else { + warn!("cvar {name}: unparseable value '{value}' ignored"); + return true; // known key, bad value — consumed, resource keeps its truth + }; + match name.to_ascii_lowercase().as_str() { + "mastervolume" => sound.master = v.clamp(0.0, 1.0), + "soundvolume" => sound.sfx = v.clamp(0.0, 1.0), + "musicvolume" => sound.music = v.clamp(0.0, 1.0), + "ambiencevolume" => sound.ambience = v.clamp(0.0, 1.0), + "uiscale" => scale.0 = v.clamp(0.5, 1.5), + "farclip" => view.farclip = v.clamp(*FARCLIP_RANGE.start(), *FARCLIP_RANGE.end()), + _ => return false, + } + true +} + +/// Startup: read `benilla/config.toml` (absent file = all defaults, not an error) and apply it +/// to the knob resources — except keys the environment overrides this session (their resources +/// already read the env var in their `Default`s). The VM does not exist yet; [`sync_cvars`] +/// seeds the table when it does. +fn load_config( + mut persist: ResMut, + mut sound: ResMut, + mut scale: ResMut, + mut view: ResMut, +) { + if std::env::var_os("WOW_UI_SCALE").is_some() { + persist.env_overridden.insert("uiscale".into()); + } + if std::env::var_os("WOW_FARCLIP").is_some() { + persist.env_overridden.insert("farclip".into()); + } + let Some(path) = crate::local_state::config_path() else { + return; // hermetic capture, or no install — session-only state + }; + let text = match std::fs::read_to_string(&path) { + Ok(t) => t, + Err(e) if e.kind() == std::io::ErrorKind::NotFound => return, + Err(e) => { + warn!("config: cannot read {}: {e}", path.display()); + return; + } + }; + let cfg: LocalConfig = match toml::from_str(&text) { + Ok(c) => c, + Err(e) => { + // A malformed file is preserved, not clobbered: nothing loads, but nothing saves + // over it either until a change actually happens — and the warn names the file. + warn!( + "config: {} is malformed ({e}) — running on defaults", + path.display() + ); + return; + } + }; + let known: HashSet = REGISTERED + .iter() + .map(|(n, _)| n.to_ascii_lowercase()) + .collect(); + for (name, value) in &cfg.cvars { + let key = name.to_ascii_lowercase(); + if !known.contains(&key) { + warn!("config: unknown cvar '{name}' — preserved, not applied"); + continue; + } + if persist.env_overridden.contains(&key) { + info!("config: {name} overridden by env for this session (file value kept)"); + continue; + } + apply_to_knobs(name, value, &mut sound, &mut scale, &mut view); + } + persist.file = cfg.cvars; +} + +/// Per frame: seed the VM's table once it exists (registered set + the RESOLVED session values, +/// so `GetCVar` reflects env overrides and the loaded config alike), then drain Lua `SetCVar` +/// changes into the knob resources and mark the config dirty. +fn sync_cvars( + script: Option>, + mut persist: ResMut, + mut sound: ResMut, + mut scale: ResMut, + mut view: ResMut, +) { + let Some(mut script) = script else { + return; + }; + if !persist.registered { + script.register_cvars(REGISTERED.iter().copied()); + let session: [(&str, String); 6] = [ + ("MasterVolume", sound.master.to_string()), + ("SoundVolume", sound.sfx.to_string()), + ("MusicVolume", sound.music.to_string()), + ("AmbienceVolume", sound.ambience.to_string()), + ("uiScale", scale.0.to_string()), + ("farclip", view.farclip.to_string()), + ]; + for (name, value) in session { + script.set_cvar_host(name, &value); + } + persist.registered = true; + } + for (name, value) in script.take_cvar_changes() { + if apply_to_knobs(&name, &value, &mut sound, &mut scale, &mut view) { + persist.dirty = true; + persist.last_change = Some(Instant::now()); + } + } +} + +/// Compose the file to save: the previous file as the merge base, every registered var that +/// moved off its default written, every one back at its default removed — env-overridden keys +/// untouched (the session value is the env's, not the player's). +fn compose_file( + previous: &BTreeMap, + env_overridden: &HashSet, + snapshot: &[(String, String, String)], +) -> BTreeMap { + let mut out = previous.clone(); + for (name, value, default) in snapshot { + let key = name.to_ascii_lowercase(); + if env_overridden.contains(&key) { + continue; + } + // Match any existing entry case-insensitively so a hand-edited spelling doesn't fork. + let existing = out.keys().find(|k| k.eq_ignore_ascii_case(name)).cloned(); + if value == default { + if let Some(k) = existing { + out.remove(&k); + } + } else { + out.insert(existing.unwrap_or_else(|| name.clone()), value.clone()); + } + } + out +} + +/// The file's header comment — where these values come from and where the law lives. +const HEADER: &str = "\ +# benilla local config (decision 0954) — CVar values that moved off their defaults. +# Managed by the client; hand edits are read on next launch and preserved on save. +"; + +/// Dirty + one quiet second (or the app exiting) → rewrite `config.toml` atomically. +fn save_config( + script: Option>, + mut persist: ResMut, + mut exits: MessageReader, +) { + let exiting = exits.read().next().is_some(); + if !persist.dirty { + return; + } + let quiet = persist + .last_change + .is_none_or(|t| t.elapsed() >= SAVE_QUIET); + if !(quiet || exiting) { + return; + } + let Some(script) = script else { return }; + let Some(path) = crate::local_state::config_path() else { + persist.dirty = false; // hermetic/session-only: nothing to write, stop retrying + return; + }; + let snapshot = script.cvars_snapshot(); + let cvars = compose_file(&persist.file, &persist.env_overridden, &snapshot); + let body = toml::to_string(&LocalConfig { + cvars: cvars.clone(), + }) + .expect("string map serializes"); + match crate::local_state::write_atomic(&path, &format!("{HEADER}{body}")) { + Ok(()) => { + persist.file = cvars; + persist.dirty = false; + } + Err(e) => { + warn!("config: cannot write {}: {e}", path.display()); + persist.dirty = false; // don't retry every frame into the same error + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::ui_script::DEFAULT_UI_SCALE; + + /// Every registered default IS the code constant it mirrors — parse-compared so "1" vs + /// "1.0" cannot fail it, welded so neither side can drift alone. + #[test] + fn registered_defaults_mirror_the_code_truths() { + let d: BTreeMap<&str, f32> = REGISTERED + .iter() + .map(|(n, v)| (*n, v.parse::().unwrap())) + .collect(); + let sound = SoundConfig::default(); + assert_eq!(d["MasterVolume"], sound.master); + assert_eq!(d["SoundVolume"], sound.sfx); + assert_eq!(d["MusicVolume"], sound.music); + assert_eq!(d["AmbienceVolume"], sound.ambience); + assert_eq!(d["uiScale"], DEFAULT_UI_SCALE); + // ViewDistance::default() reads $WOW_FARCLIP; the registered default mirrors the + // env-less 777 literal (view.rs doc: "Default 777"). + assert_eq!(d["farclip"], 777.0); + } + + #[test] + fn apply_parses_clamps_and_reports_unknowns() { + let mut sound = SoundConfig::default(); + let mut scale = UiScaleCvar(0.9); + let mut view = ViewDistance { farclip: 777.0 }; + assert!(apply_to_knobs( + "MusicVolume", + "0.7", + &mut sound, + &mut scale, + &mut view + )); + assert_eq!(sound.music, 0.7); + // Clamps are the knob's own: volume to [0,1], farclip to FARCLIP_RANGE. + assert!(apply_to_knobs( + "mastervolume", + "7", + &mut sound, + &mut scale, + &mut view + )); + assert_eq!(sound.master, 1.0); + assert!(apply_to_knobs( + "farclip", "50", &mut sound, &mut scale, &mut view + )); + assert_eq!(view.farclip, *FARCLIP_RANGE.start()); + // A bad value is consumed (known key) and the resource keeps its truth. + assert!(apply_to_knobs( + "uiScale", "banana", &mut sound, &mut scale, &mut view + )); + assert_eq!(scale.0, 0.9); + assert!(!apply_to_knobs( + "bogus", "1", &mut sound, &mut scale, &mut view + )); + } + + #[test] + fn compose_writes_the_diff_and_preserves_what_it_does_not_own() { + let previous: BTreeMap = [ + ("FutureKnob".to_string(), "3".to_string()), // a newer build's key: preserved + ("uiScale".to_string(), "0.8".to_string()), // env-overridden this session + ("farclip".to_string(), "400".to_string()), // will return to default + ] + .into(); + let env: HashSet = ["uiscale".to_string()].into(); + let snapshot = vec![ + // (name, value, default) + ("MusicVolume".into(), "0.7".into(), "0.4".into()), // moved: written + ("MasterVolume".into(), "1".into(), "1".into()), // default: absent + ("uiScale".into(), "1.2".into(), "0.9".into()), // env value: file keeps 0.8 + ("farclip".into(), "777".into(), "777".into()), // back to default: removed + ]; + let out = compose_file(&previous, &env, &snapshot); + assert_eq!(out.get("MusicVolume").map(String::as_str), Some("0.7")); + assert!(!out.contains_key("MasterVolume")); + assert_eq!(out.get("uiScale").map(String::as_str), Some("0.8")); + assert!(!out.contains_key("farclip")); + assert_eq!(out.get("FutureKnob").map(String::as_str), Some("3")); + } + + /// End to end on a real App: a pre-written `config.toml` loads into the knobs at Startup, a + /// Lua `SetCVar` drains into the knobs and — on the exit flush — lands back in the file as a + /// diff (the moved value present, the untouched ones absent). This is the whole 0954 slice-1 + /// loop in one place: file → knobs → VM table → Lua write → knobs → file. + #[test] + fn a_lua_setcvar_lands_in_config_toml_end_to_end() { + use crate::local_state::test_env::{EnvGuard, ENV_LOCK}; + let _l = ENV_LOCK.lock().unwrap(); + let tmp = std::env::temp_dir().join(format!("benilla-cvar-e2e-{}", std::process::id())); + std::fs::remove_dir_all(&tmp).ok(); + let _c = EnvGuard::unset("WOW_CAPTURE"); + let _u = EnvGuard::unset("WOW_UI_SCALE"); + let _f = EnvGuard::unset("WOW_FARCLIP"); + let _h = EnvGuard::set("BENILLA_HOME", tmp.to_str().unwrap()); + crate::local_state::write_atomic( + &tmp.join("config.toml"), + "[cvars]\nMusicVolume = \"0.1\"\n", + ) + .unwrap(); + + let mut app = App::new(); + app.add_plugins(bevy::MinimalPlugins) + .insert_resource(SoundConfig::default()) + .insert_resource(UiScaleCvar(DEFAULT_UI_SCALE)) + .insert_resource(ViewDistance { farclip: 777.0 }) + .add_plugins(CvarPlugin); + app.insert_non_send_resource(UiScript::new().unwrap()); + + // Startup: the file's MusicVolume reaches the knob; Update: the VM table seeds from it. + app.update(); + assert_eq!(app.world().resource::().music, 0.1); + assert_eq!( + app.world_mut() + .non_send_resource_mut::() + .cvar("MusicVolume") + .as_deref(), + Some("0.1") + ); + + // The Lua write (what a settings slider will do) reaches the knob on the next frame… + app.world_mut() + .non_send_resource_mut::() + .run(r#"SetCVar("MusicVolume", 0.75)"#) + .unwrap(); + app.update(); + assert_eq!(app.world().resource::().music, 0.75); + + // …and the exit flush writes the diff: the moved value, nothing at its default. + app.world_mut().write_message(AppExit::Success); + app.update(); + let text = std::fs::read_to_string(tmp.join("config.toml")).unwrap(); + assert!(text.contains("MusicVolume = \"0.75\""), "{text}"); + assert!(!text.contains("MasterVolume"), "defaults stay out:\n{text}"); + let back: LocalConfig = toml::from_str(&text).unwrap(); + assert_eq!(back.cvars.len(), 1, "a diff, not a dump: {text}"); + std::fs::remove_dir_all(&tmp).ok(); + } + + #[test] + fn the_toml_round_trips() { + let cfg = LocalConfig { + cvars: [("MusicVolume".to_string(), "0.7".to_string())].into(), + }; + let text = format!("{HEADER}{}", toml::to_string(&cfg).unwrap()); + let back: LocalConfig = toml::from_str(&text).unwrap(); + assert_eq!(back.cvars, cfg.cvars); + // The header survives as comments; a hand edit with comments parses too. + let hand = "# my note\n[cvars]\nFarclip = \"500\"\n"; + let parsed: LocalConfig = toml::from_str(hand).unwrap(); + assert_eq!(parsed.cvars.get("Farclip").map(String::as_str), Some("500")); + } +} diff --git a/crates/benilla/src/local_state.rs b/crates/benilla/src/local_state.rs new file mode 100644 index 00000000..66b9d095 --- /dev/null +++ b/crates/benilla/src/local_state.rs @@ -0,0 +1,150 @@ +//! Where benilla keeps **local state** — THE LAW (decision 0954). Every file benilla persists on +//! a player's machine — config today; realmlist, caches, per-character state as they arrive — +//! lives in **one visible folder, `benilla/`, beside the WoW install's own `Data/`**. Never +//! scattered into the install, never hidden in a platform config dir: the 1.12 client is itself +//! fully portable (`WTF/`, `realmlist.wtf`, `Cache/` all in-folder — verified on the live +//! install), its community expects the drop-in-a-folder shape, and one folder answers "what here +//! is benilla's?" at a glance (the director's call, 2026-08-04; prior art: Dolphin's +//! `portable.txt`, DREAMM's config-next-to-exe — we skip the marker file because benilla always +//! knows the install root from `$WOW_DATA`). +//! +//! **Every feature with local state resolves its paths through this module** — nothing else in +//! the tree may compute a persistence path (grep `local_state::` to see every resident). New +//! files get a name here and a line in decision 0954's layout table. +//! +//! Resolution, in order: +//! 1. **`$BENILLA_HOME`** — explicit override (tests point it at a tempdir; a second install or +//! a shared-config setup points it wherever it likes). +//! 2. **`/benilla/`** — the parent of `$WOW_DATA` (default `WoW/Data` → `WoW/ +//! benilla/`). Gated on the Data dir actually existing, so a dev run without an install never +//! plants a phantom folder in some working directory. +//! +//! **Capture/probe runs are hermetic**: with `$WOW_CAPTURE` set every path resolves to `None` — +//! a deterministic capture must not read one machine's saved settings or write anything back +//! (the harness owns its inputs; decision 0008's reproducibility posture). `None` is also the +//! honest answer when no install exists: features degrade to session-only state, warn once, and +//! keep running. + +use std::io::Write; +use std::path::{Path, PathBuf}; + +/// The one benilla-state folder, or `None` when persistence is off (hermetic capture run, or no +/// resolvable install). Existence is NOT guaranteed — [`write_atomic`] creates it on first write; +/// readers just try their file and treat absent as defaults. +pub(crate) fn home() -> Option { + if std::env::var_os("WOW_CAPTURE").is_some() { + return None; // hermetic: captures neither read nor write player state + } + if let Some(over) = std::env::var_os("BENILLA_HOME") { + return Some(PathBuf::from(over)); + } + let data = PathBuf::from(std::env::var("WOW_DATA").unwrap_or_else(|_| "WoW/Data".into())); + if !data.is_dir() { + return None; // no install, nothing to sit beside — session-only state + } + Some(data.parent().unwrap_or(Path::new(".")).join("benilla")) +} + +/// `benilla/config.toml` — the CVar overrides (the `Config.wtf` analog; see `crate::cvars`). +pub(crate) fn config_path() -> Option { + home().map(|h| h.join("config.toml")) +} + +/// Write a state file atomically: parent dirs created, contents to `.tmp`, then rename — +/// a crash mid-write leaves the old file intact, never a truncated one. The one write path for +/// every resident of the folder. +pub(crate) fn write_atomic(path: &Path, contents: &str) -> std::io::Result<()> { + if let Some(dir) = path.parent() { + std::fs::create_dir_all(dir)?; + } + let tmp = path.with_extension("tmp"); + { + let mut f = std::fs::File::create(&tmp)?; + f.write_all(contents.as_bytes())?; + f.sync_all()?; + } + std::fs::rename(&tmp, path) +} + +/// Shared test plumbing for anything that toggles the persistence env vars: `std::env::set_var` +/// is process-global and cargo runs tests in threads, so every such test takes [`ENV_LOCK`] and +/// scopes its overrides in [`EnvGuard`]s (restore-on-drop). Used here and by `crate::cvars`'s +/// end-to-end test. +#[cfg(test)] +pub(crate) mod test_env { + pub(crate) static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(()); + + pub(crate) struct EnvGuard(&'static str, Option); + impl EnvGuard { + pub(crate) fn set(key: &'static str, value: &str) -> Self { + let old = std::env::var_os(key); + std::env::set_var(key, value); + Self(key, old) + } + pub(crate) fn unset(key: &'static str) -> Self { + let old = std::env::var_os(key); + std::env::remove_var(key); + Self(key, old) + } + } + impl Drop for EnvGuard { + fn drop(&mut self) { + match &self.1 { + Some(v) => std::env::set_var(self.0, v), + None => std::env::remove_var(self.0), + } + } + } +} + +#[cfg(test)] +mod tests { + use super::test_env::{EnvGuard, ENV_LOCK}; + use super::*; + + #[test] + fn the_home_law_override_then_install_root_and_hermetic_captures() { + let _l = ENV_LOCK.lock().unwrap(); + let tmp = std::env::temp_dir().join(format!("benilla-ls-{}", std::process::id())); + std::fs::create_dir_all(tmp.join("Data")).unwrap(); + + // 2 · install root: home = parent-of-Data /benilla. + let _c = EnvGuard::unset("WOW_CAPTURE"); + let _h = EnvGuard::unset("BENILLA_HOME"); + let _d = EnvGuard::set("WOW_DATA", tmp.join("Data").to_str().unwrap()); + assert_eq!(home(), Some(tmp.join("benilla"))); + assert_eq!(config_path(), Some(tmp.join("benilla/config.toml"))); + + // 1 · the explicit override wins over the install root. + let _h2 = EnvGuard::set("BENILLA_HOME", tmp.join("elsewhere").to_str().unwrap()); + assert_eq!(home(), Some(tmp.join("elsewhere"))); + + // 0 · hermetic: a capture run resolves nothing, even with an override set. + let _c2 = EnvGuard::set("WOW_CAPTURE", "ui-options"); + assert_eq!(home(), None); + std::fs::remove_dir_all(&tmp).ok(); + } + + #[test] + fn no_install_means_no_phantom_folder() { + let _l = ENV_LOCK.lock().unwrap(); + let _c = EnvGuard::unset("WOW_CAPTURE"); + let _h = EnvGuard::unset("BENILLA_HOME"); + let _d = EnvGuard::set("WOW_DATA", "/nonexistent/benilla-test/Data"); + assert_eq!(home(), None); + } + + #[test] + fn write_atomic_creates_dirs_and_replaces_whole_files() { + let _l = ENV_LOCK.lock().unwrap(); + let tmp = std::env::temp_dir().join(format!("benilla-wa-{}", std::process::id())); + std::fs::remove_dir_all(&tmp).ok(); + let path = tmp.join("nested/config.toml"); + write_atomic(&path, "a = \"1\"\n").unwrap(); + assert_eq!(std::fs::read_to_string(&path).unwrap(), "a = \"1\"\n"); + write_atomic(&path, "a = \"2\"\n").unwrap(); + assert_eq!(std::fs::read_to_string(&path).unwrap(), "a = \"2\"\n"); + assert!(!path.with_extension("tmp").exists(), "tmp renamed away"); + std::fs::remove_dir_all(&tmp).ok(); + } +} diff --git a/crates/benilla/src/main.rs b/crates/benilla/src/main.rs index 9c172800..086b53ad 100644 --- a/crates/benilla/src/main.rs +++ b/crates/benilla/src/main.rs @@ -42,6 +42,7 @@ mod combat_text; mod cooldowns; mod creature_anim; mod cursor; +mod cvars; mod dbg_trace; mod death; mod debug_panel; @@ -64,6 +65,7 @@ mod items; mod lighting; mod liquid; mod loading_screen; +mod local_state; mod login; mod map_proj; mod mesh_tag; @@ -302,16 +304,29 @@ fn main() -> AppExit { resolution: if capturing && std::env::var("WOW_CAPTURE_UI").as_deref() == Ok("1") { - match std::env::var("WOW_CAPTURE").as_deref() { - Ok("ui-actionbar") => UVec2::new(1300, 260), - Ok("vplates") => UVec2::new(1024, 768), - // The director's small-window shape: short enough that the action bar - // strip overlaps the chat edit box rows — the overlap is the subject. - Ok("ui-chatedit") => UVec2::new(566, 377), - // The fullscreen map's chrome is a centered 1024×768 block; a hair of - // margin shows the blackout doing its job. - Ok("ui-worldmap") => UVec2::new(1100, 800), - _ => UVec2::new(640, 700), + // `$WOW_WIN` overrides here too — the resolution-A/B instrument for UI + // scenarios (a scale-dependent text bug looks fine at the scenario's + // default size and truncates at fullscreen heights). + if let Some(win) = std::env::var("WOW_WIN").ok().and_then(|v| { + let (w, h) = v.split_once('x')?; + Some(UVec2::new(w.parse().ok()?, h.parse().ok()?)) + }) { + win + } else { + match std::env::var("WOW_CAPTURE").as_deref() { + Ok("ui-actionbar") => UVec2::new(1300, 260), + Ok("vplates") => UVec2::new(1024, 768), + // The director's small-window shape: short enough that the action bar + // strip overlaps the chat edit box rows — the overlap is the subject. + Ok("ui-chatedit") => UVec2::new(566, 377), + // The fullscreen map's chrome is a centered 1024×768 block; a hair of + // margin shows the blackout doing its job. + Ok("ui-worldmap") => UVec2::new(1100, 800), + // The 920×724 era options window wants air on every side so the + // straddling right-edge tile and the hung close X stay in frame. + Ok("ui-options") => UVec2::new(1200, 900), + _ => UVec2::new(640, 700), + } } .into() } else { @@ -608,6 +623,9 @@ fn main() -> AppExit { // `SetPortraitTexture`-bound region (the modern high-res 2D model bake). .add_plugins(PortraitPlugin) .add_plugins((TextInputPlugin, UiScriptPlugin)) + // The CVar host (decision 0954): registration, knob sync, config.toml persistence. After + // UiScriptPlugin only for reading order — its systems gate on the VM existing anyway. + .add_plugins(cvars::CvarPlugin) // The unit snapshot + event feed (decision 0068 §3): pushes ECS game state into the VM as the // plain data the `Unit*` bindings read, and fires the matching WoW events. .add_plugins(UiUnitPlugin) diff --git a/crates/benilla/src/ui_script/escape_tests.rs b/crates/benilla/src/ui_script/escape_tests.rs index 8a20ec80..62e489fe 100644 --- a/crates/benilla/src/ui_script/escape_tests.rs +++ b/crates/benilla/src/ui_script/escape_tests.rs @@ -159,6 +159,47 @@ fn escape_is_consumed_by_a_focused_editbox_and_leaves_windows_open() { assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } +/// The options rung (decision 0950; the ref's own `elseif OptionsFrame:IsVisible()` at +/// UIParent.lua l.1483-1484, BETWEEN the popup rung and the menu rung): ESC with the options +/// window up closes it — hide, not cancel-click: benilla applies changes live — and eats the +/// press, so the menu does NOT open on the same stroke. Only the NEXT press, with nothing left +/// to eat, opens the menu (one eater per press, the 0449 law). +#[test] +fn escape_closes_the_options_window_before_opening_the_menu() { + let mut s = UiScript::new().unwrap(); + s.set_screen_size(1024.0, 768.0); + load_xml(&s, "Fonts.xml"); + load_xml(&s, "UiPanels.xml"); + load_xml(&s, "OptionsFrame.xml"); + load_xml(&s, "GameMenuFrame.xml"); + + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + assert!(s.eval::("return OptionsFrame:IsVisible()").unwrap()); + assert!( + !s.eval::("return GameMenuFrame:IsVisible()").unwrap(), + "the menu is down — the options rung is what must eat this press" + ); + + // Press 1: the options rung eats it — the window closes and the menu stays down. + s.run("ToggleGameMenu()").unwrap(); + assert!( + !s.eval::("return OptionsFrame:IsVisible()").unwrap(), + "ESC closed the options window" + ); + assert!( + !s.eval::("return GameMenuFrame:IsVisible()").unwrap(), + "…and did NOT also open the menu — one eater per press" + ); + + // Press 2: nothing left to eat — the menu opens. + s.run("ToggleGameMenu()").unwrap(); + assert!( + s.eval::("return GameMenuFrame:IsVisible()").unwrap(), + "the next press reaches the ladder's open-the-menu tail" + ); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + /// ESC closes an open stack-split spinner (decision 0216 §6/slice 2, StackSplit.xml): this engine /// has no plain-frame keyboard capture (the real client's own `StackSplitFrame` OnKeyDown ESCAPE /// arm can't be driven), so the hook rides `ToggleGameMenu`'s shared chain instead — checked diff --git a/crates/benilla/src/ui_script/extract/mod.rs b/crates/benilla/src/ui_script/extract/mod.rs index 56604c00..fc8a5e01 100644 --- a/crates/benilla/src/ui_script/extract/mod.rs +++ b/crates/benilla/src/ui_script/extract/mod.rs @@ -66,9 +66,13 @@ pub(super) fn drive_script( // This frame's `` widget slot, parked for `minimap::emit_minimap` (the UiQuadAppend // producer that fills the hole with tile/arrow quads — decision 0203 phase 1). mut minimap_widget: ResMut, - // Whether the digit-advance feed has run (once, on the first frame the atlas exists — the - // atlas itself bakes once and never re-scales, see `load_fonts_and_build_atlas`). + // Whether the digit-advance feed has run (once per raster environment: on the first frame + // the atlas exists, and again after every seam-scale change — `last_seam` below re-arms it). mut digits_fed: Local, + // The seam scale the engine's text-metric caches were answered under. When `s` moves (window + // resize / fullscreen toggle / uiScale change), every cached measure is stale — see the + // invalidation below. + mut last_seam: Local, // The uiScale dial folded into the seam scale (decision 0584). ui_scale: Res, // ── The extract gate's memory (decision 0740): last frame's conversion inputs ───────────── @@ -96,6 +100,20 @@ pub(super) fn drive_script( // way in (input.rs), and measures ÷s on the way back. At a 768-tall window with the dial at // 1 the whole pipeline is bit-identical to the pre-virtual behavior. let s = super::seam_scale(h, ui_scale.0); + // The raster environment moved (resize / fullscreen / uiScale): every text metric the engine + // caches — FontString measures, chat row counts, editbox advance tables, the digit feed — was + // answered under the OLD `s`, and integer-stepped advances measured at one raster size do not + // rescale to another (the font-size snap alone shifts a string's unit width by several + // percent — enough that a boot-size measure fails the fullscreen fit test and the ellipsis + // eats fitting text: the director's "Contr..." rows, reproduced by `WOW_RESIZE`). Declare the + // staleness at the seam and let every round-trip re-answer under the new `s`. + if *last_seam != s { + if *last_seam != 0.0 { + script.invalidate_text_measures(); + *digits_fed = false; + } + *last_seam = s; + } // Phase spans (visible under `bevy/trace_chrome`): this system is the biggest flat CPU cost // on an idle frame, and the ledger can only rank what has a name — tick (Lua OnUpdate), // resolve (layout), measure (the text round-trips), extract (tree walk + rasterize), diff. @@ -214,11 +232,13 @@ pub(super) fn drive_script( let n = crate::ui_text::measure_wrapped_rows( atlas, &r.text, + // wrap_width is the RESOLVED frame width (scale already in it) — ×s only; + // the font height is frame-local — ×s × the frame's scale, the drawn size. r.wrap_width * s, crate::ui_text::FontSpec { path: r.font.as_deref(), // The band's own drawn px (one-to-one regime; inert ≤ 32). - height: crate::ui_text::drawn_px(r.height, None, s), + height: crate::ui_text::drawn_px(r.height, None, s * r.scale), outline: r.outline, // step-law width bias, as in the measures above paint_halo: true, // measure never paints; irrelevant here alpha_gradient: None, @@ -261,7 +281,11 @@ pub(super) fn drive_script( if let Some(req) = script.editbox_advances_request() { let spec = crate::ui_text::FontSpec { path: req.font.as_deref(), - height: crate::ui_text::drawn_px(req.height, None, s), + // Measured at the DRAWN raster size (seam × the box frame's scale) but divided + // back by the seam alone: the advances return in screen UI units, the space the + // engine's ÷s mouse feed and the box's scale-multiplied rect live in (the + // request's `scale` doc). + height: crate::ui_text::drawn_px(req.height, None, s * req.scale), outline: req.outline, paint_halo: true, // measure never paints alpha_gradient: None, // alpha never changes metrics @@ -549,6 +573,7 @@ pub(super) fn drive_script( ebox: text_ui.as_ref(), screen_h: h, scale: s, + font_scale: eq.scale, caret_pinned: capture.is_some(), }, &mut out, @@ -652,16 +677,21 @@ fn measure_fontstrings( let measures: Vec<(u32, f32, f32, u64)> = requests .iter() .map(|r| { + // The full raster scale: seam × the owner frame's effective_scale. Measure at the + // exact drawn size and divide the whole product back out, so the frame-LOCAL answer + // times the layout's scale lands on the drawn glyphs to the pixel (integer-stepped + // advances don't commute with scaling — the request's `scale` doc). + let rs = s * r.scale; let (w, h) = crate::ui_text::measure_text( atlas, &r.text, - r.wrap_width.map(|w| w * s), + r.wrap_width.map(|w| w * rs), crate::ui_text::FontSpec { path: r.font.as_deref(), - // The render pass's exact drawn px (two regimes × the virtual scale) — + // The render pass's exact drawn px (two regimes × the full scale) — // measure == render, and Lua's GetStringWidth/Height echo the DRAWN size - // (0x772890); results divide back to UI units below. - height: crate::ui_text::drawn_px(r.height, r.text_height, s), + // (0x772890); results divide back to frame-local UI units below. + height: crate::ui_text::drawn_px(r.height, r.text_height, rs), // The TRUE outline: THICK biases the client's step law (+1px per glyph — // GlyphStepBase 0x5ca2b0, THICK-only per outline-bake-tint.md) and any outline // adds the +2r line pitch, so measure must see it. @@ -670,7 +700,7 @@ fn measure_fontstrings( alpha_gradient: None, // alpha never changes metrics }, ); - (r.id, w / s, h / s, r.key) + (r.id, w / rs, h / rs, r.key) }) .collect(); script.set_measured_text(&measures); diff --git a/crates/benilla/src/ui_script/extract/text.rs b/crates/benilla/src/ui_script/extract/text.rs index 30509f4c..ed6fc943 100644 --- a/crates/benilla/src/ui_script/extract/text.rs +++ b/crates/benilla/src/ui_script/extract/text.rs @@ -44,6 +44,13 @@ pub(super) struct TextHost<'a> { /// this converts the remaining unit-space inputs — font heights, shadow offsets, the /// engine's editbox caret/selection x-offsets — into the same px space. pub scale: f32, + /// The owning frame's `effective_scale` ([`benilla_ui::script::ExtractedQuad::scale`]): the + /// rect already carries it; the FONT metrics — glyph raster size and shadow offset, both + /// frame-local — multiply by it here (the real client's text rides `SetScale`; 0219 §2's + /// divergence, closed). NOT applied to the editbox caret/selection/advance x-offsets: those + /// arrive in screen UI units (the advance measure already rode the scale — see + /// [`benilla_ui::script::EditBoxAdvanceRequest`]'s `scale` doc). + pub font_scale: f32, /// Captures pin the caret blink ON (deterministic pixels); live, the engine's phase decides. pub caret_pinned: bool, } @@ -62,10 +69,15 @@ pub(super) fn emit( let base_color = style.color.unwrap_or([1.0, 1.0, 1.0, 1.0]); let spec = crate::ui_text::FontSpec { path: style.font.as_deref(), - // The drawn px under the two size regimes × the 768-virtual scale (`drawn_px`, decision - // 0582): one-to-one text unit-caps at 32 then scales; a SetTextHeight override scales - // uncapped. The shadow twin below inherits the spec (`..spec`). - height: crate::ui_text::drawn_px(style.font_height, style.text_height, host.scale), + // The drawn px under the two size regimes × the 768-virtual scale × the owner's frame + // scale (`drawn_px`, decision 0582): one-to-one text unit-caps at 32 (a frame-LOCAL cap, + // like the seam scale it precedes) then scales; a SetTextHeight override scales uncapped. + // The shadow twin below inherits the spec (`..spec`). + height: crate::ui_text::drawn_px( + style.font_height, + style.text_height, + host.scale * host.font_scale, + ), outline: style.outline, paint_halo: true, alpha_gradient: style.alpha_gradient, @@ -87,8 +99,8 @@ pub(super) fn emit( // below has to admit the ink it puts under the fill. let shadow_delta = style.shadow.map(|sh| { ( - shadow_offset_px(sh.offset[0] * host.scale), - shadow_offset_px(-sh.offset[1] * host.scale), + shadow_offset_px(sh.offset[0] * host.scale * host.font_scale), + shadow_offset_px(-sh.offset[1] * host.scale * host.font_scale), ) }); // A message-frame ring line's scissor follows the seat, at the bottom edge only. diff --git a/crates/benilla/src/ui_script/game_menu_tests.rs b/crates/benilla/src/ui_script/game_menu_tests.rs index df5e3acb..8c178b78 100644 --- a/crates/benilla/src/ui_script/game_menu_tests.rs +++ b/crates/benilla/src/ui_script/game_menu_tests.rs @@ -1,11 +1,12 @@ //! The shipped `assets/ui/GameMenuFrame.xml` — the frame ESC opens (decision 0674). //! -//! What these guard, in order: the ref geometry (195×246 and the eight-button ladder, because the -//! whole point of shipping five dead buttons is that the frame keeps its exact silhouette); the -//! disabled five; the three live buttons' wire intents and sounds; the ESC ladder's two new rungs -//! (open when nothing is left to eat, close before everything else); the micro button's `clicked` -//! toggle; the native-center rule that makes the menu take the screen (windows close on the way in, -//! and nothing opens while it is up); and the camp/quit countdown dialogs end to end. +//! What these guard, in order: the ladder geometry (the ERA menu shape — 200×288, eight rungs in +//! three 20-gapped sections, the era layout engine's own numbers precomputed in the XML's header +//! note); the four greyed pending entries; the live buttons' wire intents and sounds; +//! the ESC ladder's two new rungs (open when nothing is left to eat, close before everything +//! else); the micro button's `clicked` toggle; the native-center rule that makes the menu take +//! the screen (windows close on the way in, and nothing opens while it is up); and the camp/quit +//! countdown dialogs end to end. The Options button's own click path is options_tests'. use benilla_ui::script::{ ContainerSlot, ContainerState, LootRow, LootState, SessionRequest, SoundRequest, UiScript, @@ -44,25 +45,25 @@ fn harness() -> UiScript { harness_with(&[]) } -/// The eight buttons, top to bottom — the ref's own order (GameMenuFrame.xml l.48-186). +/// The eight buttons, top to bottom — the ERA ladder (the director's call on the 0951 review; +/// GameMenuFrame.xml's header SCOPE note quotes the era source). const LADDER: [&str; 8] = [ "GameMenuButtonOptions", - "GameMenuButtonSoundOptions", - "GameMenuButtonUIOptions", - "GameMenuButtonKeybindings", + "GameMenuButtonAddOns", + "GameMenuButtonEditMode", + "GameMenuButtonSupport", "GameMenuButtonMacros", "GameMenuButtonLogout", "GameMenuButtonQuit", "GameMenuButtonContinue", ]; -/// The frame's geometry, quoted from the reference: 195×246 centered, each button 144×21, the first -/// centered on the frame's TOP at −37, each next 1 px under the last, and Return to Game hung 16 -/// under Exit Game — the single gap that makes the last entry read as separate. Shipping the five -/// unbacked buttons disabled (rather than cutting them) is exactly what keeps this true, so it is -/// the first thing to break if someone "tidies" the ladder. +/// The ladder geometry — the era layout engine's own numbers (MainMenuFrameTemplates: padding +/// 32/28/28/28, spacing 0, AddSection gap 20): 200×288, each button 144×21 at x=28, tops at +/// 32/73/94/115/136/177/198/239 — three sections split by the 20-unit gaps after Options, after +/// Macros, and before Return to Game. First thing to break if the era shape is ever "tidied". #[test] -fn the_menu_has_the_reference_frame_and_button_ladder() { +fn the_menu_has_the_era_frame_and_button_ladder() { let mut s = harness(); s.run("ShowUIPanel(GameMenuFrame)").unwrap(); s.resolve(); @@ -70,14 +71,12 @@ fn the_menu_has_the_reference_frame_and_button_ladder() { let (w, h) = s .eval::<(f64, f64)>("return GameMenuFrame:GetWidth(), GameMenuFrame:GetHeight()") .unwrap(); - assert_eq!((w, h), (195.0, 246.0), "the ref frame size"); + assert_eq!((w, h), (200.0, 288.0), "the era frame size"); let top = s.eval::("return GameMenuFrame:GetTop()").unwrap(); - // Button i's own top, measured down from the frame's: the first is centered at −37 (so its top - // is 37 − 21/2 = 26.5 below), then a 22 px stride (21 tall + the 1 px gap), with the last - // pushed a further 15 (its −16 anchor instead of −1). - let mut expected = top - 26.5; - for (i, name) in LADDER.iter().enumerate() { + let left = s.eval::("return GameMenuFrame:GetLeft()").unwrap(); + const TOPS: [f64; 8] = [32.0, 73.0, 94.0, 115.0, 136.0, 177.0, 198.0, 239.0]; + for (name, down) in LADDER.iter().zip(TOPS) { let (bw, bh, btop, bleft) = s .eval::<(f64, f64, f64, f64)>(&format!( "return {name}:GetWidth(), {name}:GetHeight(), {name}:GetTop(), {name}:GetLeft()" @@ -89,41 +88,64 @@ fn the_menu_has_the_reference_frame_and_button_ladder() { "{name} is a GameMenuButtonTemplate" ); assert!( - (btop - expected).abs() < 0.001, - "{name} top: expected {expected}, got {btop}" + (btop - (top - down)).abs() < 0.001, + "{name} top: expected {} down {down}, got {}", + top - down, + btop ); - // Every button is centered in the frame (the ladder is one column). assert!( - (bleft - (512.0 - 72.0)).abs() < 0.001, - "{name} is centered in the 1024-wide screen" + (bleft - (left + 28.0)).abs() < 0.001, + "{name} sits at the era 28-unit left padding" ); - expected -= if i == 6 { 21.0 + 16.0 } else { 21.0 + 1.0 }; } assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } -/// The five with nothing behind them are DISABLED — the director's call: the ladder keeps the ref's -/// shape, and a button that does nothing looks like it. The three that work are enabled. +/// The four entries with nothing behind them — AddOns, Edit Mode, Support, Macros — are DISABLED +/// (the pending idiom: grey label, `-Disabled` art, exactly how the era menu greys a dead entry). +/// Everything else in the ladder is live. #[test] -fn the_five_unbacked_buttons_are_disabled_and_the_three_live_ones_are_not() { +fn the_unbacked_entries_are_disabled_and_the_rest_are_live() { let s = harness(); s.run("ShowUIPanel(GameMenuFrame)").unwrap(); - for name in &LADDER[..5] { + for name in [ + "GameMenuButtonAddOns", + "GameMenuButtonEditMode", + "GameMenuButtonSupport", + "GameMenuButtonMacros", + ] { assert!( !s.eval::(&format!("return {name}:IsEnabled()")) .unwrap(), "{name} has no panel behind it and must read that way" ); } - for name in &LADDER[5..] { + for name in [ + "GameMenuButtonOptions", + "GameMenuButtonLogout", + "GameMenuButtonQuit", + "GameMenuButtonContinue", + ] { assert!( s.eval::(&format!("return {name}:IsEnabled()")) .unwrap(), "{name} is live" ); } - // The labels are the reference GlobalStrings, read back through the globals (not literals). + // The pending labels read the era strings (AddOns through the ADDONS global). + assert_eq!( + s.eval::("return GameMenuButtonAddOns:GetText()") + .unwrap(), + "AddOns" + ); + assert_eq!( + s.eval::("return GameMenuButtonEditMode:GetText()") + .unwrap(), + "Edit Mode" + ); + // Continue's label is the reference GlobalString, read back through the global; Options is + // the one literal (1.12 GlobalStrings has no GAMEOPTIONS_MENU — the XML says so). assert_eq!( s.eval::("return GameMenuButtonContinue:GetText()") .unwrap(), @@ -132,7 +154,7 @@ fn the_five_unbacked_buttons_are_disabled_and_the_three_live_ones_are_not() { assert_eq!( s.eval::("return GameMenuButtonOptions:GetText()") .unwrap(), - "Video Options" + "Options" ); assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); } @@ -688,3 +710,23 @@ fn backpack() -> ContainerState { slots, } } + +/// The menu wears the shared era-window scale on show (`ERA_WINDOW_SCALE`, OptionsFrame.xml — +/// the stand-in for the era client's UIParent px-per-unit): in the era the menu and the options +/// window draw at the SAME density (the menu has no scale of its own; options' checkFit caps at +/// 1), and ours drew the menu 28% larger relative to the options window until it rode the same +/// knob ("esc menu scale too big" — director, 2026-08-04). Loaded WITH OptionsFrame.xml, the +/// real load order; the bare-menu harness elsewhere exercises the `or 1` guard implicitly. +#[test] +fn the_menu_rides_the_shared_era_window_scale() { + let mut s = harness_with(&["OptionsFrame.xml"]); + s.run("ShowUIPanel(GameMenuFrame)").unwrap(); + s.resolve(); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); + let ran = s.eval::("return BENILLA_MENU_ONSHOW or -99").unwrap(); + println!("handler ran probe: {ran}"); + let k = s.eval::("return GameMenuFrame:GetScale()").unwrap(); + let want = s.eval::("return ERA_WINDOW_SCALE").unwrap(); + assert!((k - want).abs() < 1e-6, "menu scale {k} != knob {want}"); + assert!((want - 0.78).abs() < 1e-6, "the knob itself moved: {want}"); +} diff --git a/crates/benilla/src/ui_script/mod.rs b/crates/benilla/src/ui_script/mod.rs index e489d76a..a4775c01 100644 --- a/crates/benilla/src/ui_script/mod.rs +++ b/crates/benilla/src/ui_script/mod.rs @@ -135,7 +135,7 @@ impl Default for UiScaleCvar { /// The shipped default (the reference's slider spans 0.64..1.0; 1.0 read oversized to the /// director's eye — the taste call this dial exists for). -const DEFAULT_UI_SCALE: f32 = 0.9; +pub(crate) const DEFAULT_UI_SCALE: f32 = 0.9; /// The app's `uiScale`: `WOW_UI_SCALE=` if set (clamped to the plausible dial range), else /// [`DEFAULT_UI_SCALE`] — the env override makes taste iteration a relaunch, not a rebuild. @@ -202,7 +202,9 @@ impl Plugin for UiScriptPlugin { arbitrate_pointer_over_ui .after(UiInput) .before(WorldStage::Input), - ); + ) + // Runtime era-atlas misses surface as WARNs the frame they happen (decision 0950). + .add_systems(Update, warn_era_atlas_misses.after(UiInput)); // `WOW_CAPTURE_UI=1` on a capture: override the "player" token with a synthetic snapshot so // the unit frames are populated in a server-less capture. Ordered after the real feed (so it @@ -238,7 +240,7 @@ fn arbitrate_pointer_over_ui( } fn setup_script(world: &mut World) { - let script = match UiScript::new() { + let mut script = match UiScript::new() { Ok(s) => s, Err(e) => { error!("ui_script: VM init failed: {e}"); @@ -247,6 +249,12 @@ fn setup_script(world: &mut World) { }; load_global_strings(world, &script); load_emote_tokens(world, &script); + // The Era atlas table (decision 0950) — pushed BEFORE the XML loads so `atlas=` attributes + // resolve during materialization. A missing manifest already WARNed inside the loader; the + // windows then draw blank and every unresolved name is drained as a miss below. + if let Some(entries) = crate::assets::era::load_era_atlases() { + script.set_era_atlases(entries); + } // The unit frames are the default player UI — always loaded in a normal run. Captures stay // pristine (world-render baselines) unless WOW_CAPTURE_UI=1 opts the UI in. let capturing = world.contains_resource::(); @@ -255,9 +263,23 @@ fn setup_script(world: &mut World) { // assertion (`shipped_xml_tests`), not a second reporting channel. let _ = load_default_ui(&script); } + // Boot-time atlas misses (an `atlas=` or OnLoad `SetAtlas` the table couldn't serve) name + // themselves once, here; runtime misses drain per frame in `warn_era_atlas_misses`. + for name in script.take_era_atlas_misses() { + warn!("ui_script: unresolved era atlas '{name}' — stale extraction? run scripts/era-extract.py"); + } world.insert_non_send_resource(script); } +/// Per-frame drain of runtime `SetAtlas` misses (hover/selection swaps naming an atlas the boot +/// table lacks) — warn-once per name, the boot drain's live twin. Free when nothing missed. +fn warn_era_atlas_misses(script: Option>) { + let Some(mut script) = script else { return }; + for name in script.take_era_atlas_misses() { + warn!("ui_script: unresolved era atlas '{name}' — stale extraction? run scripts/era-extract.py"); + } +} + /// Execute the real `Interface\FrameXML\GlobalStrings.lua` off the patch chain into the VM — /// the reference boots FrameXML with exactly this file FIRST, and it is the source of every /// localized string global the UI reads (the cast-fail display's whole message set, 0427). @@ -599,6 +621,11 @@ fn load_default_ui(script: &UiScript) -> Vec { // alongside QuestFrame (same arc); the 'L' binding below calls its ToggleQuestLog(). "QuestLogFrame.xml", "ChatFrame.xml", + // The options window (decision 0950): the era-skinned OptionsFrame the game menu's + // Options button opens. Needs only Fonts' objects and UiPanels' panel manager (both far + // above); sits right before GameMenuFrame so the menu — which reaches it by name at + // click time — stays the last, outermost layer. + "OptionsFrame.xml", // The game menu (decision 0674): the frame ESC opens, plus the CAMP/QUIT dialogs and their // event driver. LAST on purpose — it is the outermost layer of the shell, it depends on // everything it touches being loaded (UiPanels' popup engine + panel manager, MicroMenu's @@ -903,6 +930,9 @@ mod escape_tests; #[cfg(test)] mod game_menu_tests; +#[cfg(test)] +mod options_tests; + #[cfg(test)] mod delete_item_tests; diff --git a/crates/benilla/src/ui_script/options_tests.rs b/crates/benilla/src/ui_script/options_tests.rs new file mode 100644 index 00000000..6a242611 --- /dev/null +++ b/crates/benilla/src/ui_script/options_tests.rs @@ -0,0 +1,228 @@ +//! The shipped `assets/ui/OptionsFrame.xml` — the era-skinned options window (decision 0950), +//! slice A: the shell. +//! +//! What these guard: the file loads clean inside the real neighbourhood (Fonts + UiPanels + +//! GameMenuFrame); the menu's Options button is the door in (menu down, options up, on the ref's +//! own kit); Controls is the default category and the page title follows the selection; both +//! close spellings put the window away; and — against a hand-pushed atlas table, the +//! era_atlas_tests idiom — the selected row actually wears `options_list_active` and drops it +//! when the selection moves. The harness pushes NO atlas table by default: `SetAtlas` is a +//! warn-once no-op there by design (a missing extraction must not kill the UI), which is exactly +//! why the one atlas-backed test pushes its own two members. + +use benilla_ui::script::{EraAtlasEntry, SoundRequest, UiScript}; + +/// The window's real neighbourhood, in the manifest's own order (options before the menu — the +/// game_menu_tests::harness_with idiom, minus the extras this file never needs). +fn harness() -> UiScript { + harness_on(UiScript::new().unwrap()) +} + +/// Load the four files onto a prepared script — split out so the atlas test can push its table +/// BEFORE the XML loads, the way the app does (ui_script::setup_script). +fn harness_on(mut s: UiScript) -> UiScript { + s.set_screen_size(1024.0, 768.0); + for file in [ + "Fonts.xml", + "UiPanels.xml", + "OptionsFrame.xml", + "GameMenuFrame.xml", + ] { + let text = std::fs::read_to_string( + std::path::Path::new(env!("CARGO_MANIFEST_DIR")) + .join("assets/ui") + .join(file), + ) + .unwrap(); + let doc = benilla_ui::framexml::parse(&text).unwrap(); + let report = benilla_ui::loader::load(&s, &doc, &|_| None); + assert!( + report.errors.is_empty(), + "{file}: loader errors: {:?}", + report.errors + ); + // The dialect announces DROPPED subtrees as warnings, not errors — for the new file, + // a warning is a silently-missing piece of chrome, so it fails here. + if file == "OptionsFrame.xml" { + assert!( + report.warnings.is_empty(), + "{file}: loader warnings (dropped subtrees?): {:?}", + report.warnings + ); + } + } + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); + s +} + +/// The door in: the game menu's Options button (its ref OnClick + the explicit hide) swaps the +/// two native-center frames — menu down, options up holding the center slot — on the ref's own +/// igMainMenuOption kit. +#[test] +fn the_menu_options_button_swaps_the_menu_for_the_options_window() { + let mut s = harness(); + s.run("ShowUIPanel(GameMenuFrame)").unwrap(); + let _ = s.take_sounds(); + + s.run("GameMenuButtonOptions:Click()").unwrap(); + assert!( + s.eval::("return OptionsFrame:IsVisible()").unwrap(), + "the options window opened" + ); + assert!( + !s.eval::("return GameMenuFrame:IsVisible()").unwrap(), + "…and the menu went away first" + ); + assert!( + s.eval::("return GetCenterFrame():GetName() == \"OptionsFrame\"") + .unwrap(), + "the window holds the native-center slot the menu vacated" + ); + assert!(s + .take_sounds() + .contains(&SoundRequest::KitName("igMainMenuOption".into()))); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + +/// Controls is the default page (the OnShow seat), and the page title is the selected row's own +/// label — including the one key whose label differs from it (ActionBars → "Action Bars"). +#[test] +fn controls_is_the_default_category_and_the_title_reads_it() { + let s = harness(); + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + + assert_eq!( + s.eval::("return OptionsFrame.selectedCategory") + .unwrap(), + "Controls" + ); + assert_eq!( + s.eval::("return OptionsFrameContainerTitle:GetText()") + .unwrap(), + "Controls" + ); + // The row labels are the era category tree's. + assert_eq!( + s.eval::("return OptionsFrameCategoryListRowActionBars:GetText()") + .unwrap(), + "Action Bars" + ); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + +/// Clicking a category row moves the selection and the page title with it — and the selection +/// survives a close/reopen (the OnShow re-applies the last seat, not the default). +#[test] +fn clicking_a_row_moves_the_selection_and_the_page_title() { + let s = harness(); + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + + s.run("OptionsFrameCategoryListRowGraphics:Click()") + .unwrap(); + assert_eq!( + s.eval::("return OptionsFrame.selectedCategory") + .unwrap(), + "Graphics" + ); + assert_eq!( + s.eval::("return OptionsFrameContainerTitle:GetText()") + .unwrap(), + "Graphics" + ); + + // Close and reopen: still Graphics, not Controls. + s.run("HideUIPanel(OptionsFrame)").unwrap(); + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + assert_eq!( + s.eval::("return OptionsFrameContainerTitle:GetText()") + .unwrap(), + "Graphics" + ); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + +/// Both close spellings — the red Close and the corner X — hide the window on the shared +/// igMainMenuClose kit, and the Defaults button (nothing to default this slice) reads disabled. +#[test] +fn both_close_buttons_hide_the_window_and_defaults_is_disabled() { + let mut s = harness(); + + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + assert!( + !s.eval::("return OptionsFrameContainerDefaults:IsEnabled()") + .unwrap(), + "Defaults is dead until the pages slice brings rows to default" + ); + let _ = s.take_sounds(); + s.run("OptionsFrameCloseButton:Click()").unwrap(); + assert!(!s.eval::("return OptionsFrame:IsVisible()").unwrap()); + assert!(s + .take_sounds() + .contains(&SoundRequest::KitName("igMainMenuClose".into()))); + + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + let _ = s.take_sounds(); + s.run("OptionsFrameClosePanelButton:Click()").unwrap(); + assert!(!s.eval::("return OptionsFrame:IsVisible()").unwrap()); + assert!(s + .take_sounds() + .contains(&SoundRequest::KitName("igMainMenuClose".into()))); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +} + +/// The row art, against a hand-pushed two-member atlas table (real manifest numbers, the +/// era_atlas_tests idiom): the selected row's bg wears `options_list_active` at atlas size, and +/// a moved selection strips the old row bare (`SetTexture(nil)` → GetAtlas nil). The window's +/// OTHER atlas members (nine-slice, divider, inner panel) stay unserved here on purpose — they +/// drain as warn-once misses, never errors. +#[test] +fn the_selected_row_wears_the_active_atlas_and_yields_it_on_reselect() { + let mut s = UiScript::new().unwrap(); + s.set_era_atlases([ + ( + "options_list_active".to_string(), + EraAtlasEntry { + file: "era:textures/1318750.blp".to_string(), + uv: [604.0 / 1024.0, 791.0 / 1024.0, 1.0 / 1024.0, 22.0 / 1024.0], + size: [187.0, 21.0], + }, + ), + ( + "options_list_hover".to_string(), + EraAtlasEntry { + file: "era:textures/1318750.blp".to_string(), + uv: [793.0 / 1024.0, 980.0 / 1024.0, 1.0 / 1024.0, 22.0 / 1024.0], + size: [187.0, 21.0], + }, + ), + ]); + let s = harness_on(s); + + s.run("ShowUIPanel(OptionsFrame)").unwrap(); + assert_eq!( + s.eval::("return OptionsFrameCategoryListRowControlsBg:GetAtlas()") + .unwrap(), + "options_list_active", + "the default selection wears the active art" + ); + // useAtlasSize rode along: the bg is the member's nominal 187×21, wider than the 175 row — + // the era's own look. + assert_eq!( + s.eval::("return OptionsFrameCategoryListRowControlsBg:GetWidth()") + .unwrap(), + 187.0 + ); + + s.run("OptionsFrameCategoryListRowAudio:Click()").unwrap(); + assert!( + s.eval::("return OptionsFrameCategoryListRowControlsBg:GetAtlas() == nil") + .unwrap(), + "the old row stripped bare when the selection moved" + ); + assert_eq!( + s.eval::("return OptionsFrameCategoryListRowAudioBg:GetAtlas()") + .unwrap(), + "options_list_active" + ); + assert!(s.errors().is_empty(), "script errors: {:?}", s.errors()); +}