- the UIPanel registry is re-derived from the reference's bytes: the readable-item page finally registers, so a note or letter closes when a gossip window opens instead of interleaving with it line for line; the character sheet takes its real pushable-2 seat (an NPC window slides it over instead of replacing it), and the spellbook, quest log, talents and character sheet open for dead players again — a refused open now says "You can't do that when you're dead." instead of doing nothing - the gossip window waits for its greeting: the first packet carries only the options and a text id, and we used to show the frame with a blank page for the round trip the text needed — the menu now exists only once the text has answered, a mid-flight click is refused, and a revisit is instant off the cache - a bag slot whose stack count changes on the server now fires `ITEM_LOCK_CHANGED` just ahead of its `BAG_UPDATE` — the one signal 1.12 hunter addons use to detect a spent arrow — so Quiver's auto-shot bar drains per shot instead of pinning at 100%; a swap or create deliberately does not fire it, so rearranging bags can't read as a shot - the outdoor minimap tiles' gamma byte reaches the screen exactly once — the ordinary decode arm learns the same skip the fast path already had Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
120 lines
4.2 KiB
Rust
120 lines
4.2 KiB
Rust
//! The fade kit's driver (`UIFrameFade` / `BenillaFadeDriver`, UiPanels.xml) — the park/wake
|
|
//! lifecycle that keeps the per-frame fade walk off the tick while nothing is fading. Split from
|
|
//! `panel_tests.rs` (1507's round): the slot manager and the fade kit share a source file, not a
|
|
//! concern.
|
|
|
|
use benilla_ui::script::UiScript;
|
|
|
|
/// Load one shipped `assets/ui/<file>` into `s`, panicking on any loader error — the same
|
|
/// per-file loader every `ui_script` test file carries (these tests only ever need
|
|
/// `UiPanels.xml`, where the fade kit lives).
|
|
fn load_xml(s: &UiScript, file: &str) {
|
|
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 idle fade driver parks itself** (decision 1396's class, the audit's driver-hide item):
|
|
/// with `FADEFRAMES` empty there is nothing to walk, so the driver's OnUpdate hides its own frame
|
|
/// — and the engine dispatches OnUpdate only to visible frames (`tick.rs`), which stops the
|
|
/// per-frame walk cold. The probe wraps `UIFrameFadeUpdate` in a counter BEFORE the first tick:
|
|
/// ten idle ticks later it has never run, and the driver is hidden.
|
|
#[test]
|
|
fn an_idle_fade_driver_parks_itself_off_the_tick() {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_xml(&s, "UiPanels.xml");
|
|
s.resolve();
|
|
s.run(
|
|
"BENILLA_TEST_FADE_TICKS = 0\n\
|
|
local real = UIFrameFadeUpdate\n\
|
|
function UIFrameFadeUpdate(elapsed)\n\
|
|
BENILLA_TEST_FADE_TICKS = BENILLA_TEST_FADE_TICKS + 1\n\
|
|
real(elapsed)\n\
|
|
end",
|
|
)
|
|
.unwrap();
|
|
for _ in 0..10 {
|
|
s.tick(0.016);
|
|
s.resolve();
|
|
}
|
|
assert!(s.errors().is_empty(), "{:?}", s.errors());
|
|
assert!(
|
|
!s.eval::<bool>("return BenillaFadeDriver:IsShown()")
|
|
.unwrap(),
|
|
"an empty FADEFRAMES parks the driver"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<i64>("return BENILLA_TEST_FADE_TICKS").unwrap(),
|
|
0,
|
|
"a parked driver never runs the fade walk"
|
|
);
|
|
}
|
|
|
|
/// The control for the guard above: the gate must not cost the fades it gates. `UIFrameFade` (the
|
|
/// kit's single insertion point — `UIFrameFadeIn`/`Out` both land there) wakes the driver beside
|
|
/// its `table.insert`; the fade then ramps on real ticks, completes, and the driver re-parks on
|
|
/// the first empty tick after.
|
|
#[test]
|
|
fn a_started_fade_still_ramps_and_the_driver_reparks_after() {
|
|
let mut s = UiScript::new().unwrap();
|
|
s.set_screen_size(1024.0, 768.0);
|
|
load_xml(&s, "UiPanels.xml");
|
|
s.resolve();
|
|
s.run(r#"CreateFrame("Frame", "BenillaFadeProbe")"#)
|
|
.unwrap();
|
|
for _ in 0..2 {
|
|
s.tick(0.016); // settle: the driver parks
|
|
s.resolve();
|
|
}
|
|
assert!(!s
|
|
.eval::<bool>("return BenillaFadeDriver:IsShown()")
|
|
.unwrap());
|
|
|
|
s.run("UIFrameFadeIn(BenillaFadeProbe, 1.0)").unwrap();
|
|
assert!(
|
|
s.eval::<bool>("return BenillaFadeDriver:IsShown()")
|
|
.unwrap(),
|
|
"starting a fade wakes the driver"
|
|
);
|
|
assert_eq!(
|
|
s.eval::<f64>("return BenillaFadeProbe:GetAlpha()").unwrap(),
|
|
0.0,
|
|
"the fade armed at its IN startAlpha"
|
|
);
|
|
|
|
s.tick(0.5);
|
|
s.resolve();
|
|
let mid = s.eval::<f64>("return BenillaFadeProbe:GetAlpha()").unwrap();
|
|
assert!(
|
|
(mid - 0.5).abs() < 1e-3,
|
|
"half the timeToFade in, half the ramp: {mid}"
|
|
);
|
|
|
|
s.tick(0.6); // past timeToFade: the fade completes and leaves the list
|
|
s.resolve();
|
|
assert_eq!(
|
|
s.eval::<f64>("return BenillaFadeProbe:GetAlpha()").unwrap(),
|
|
1.0,
|
|
"the fade completes at its endAlpha"
|
|
);
|
|
s.tick(0.016); // the first empty tick re-parks the driver
|
|
s.resolve();
|
|
assert!(
|
|
!s.eval::<bool>("return BenillaFadeDriver:IsShown()")
|
|
.unwrap(),
|
|
"the driver re-parks once the list is empty"
|
|
);
|
|
assert!(s.errors().is_empty(), "script errors: {:?}", s.errors());
|
|
}
|