script errors reach the player, and the bars stop lying

- an addon error shows the red script-error dialog the reference shows, and a runaway load loop can no longer freeze world entry
- food and drink show stack counts from the first login, and a paladin's aura bar lights the aura that is actually on
- logging out no longer erases the character from saved-bag addons: the player's absence is no data
- MPQ patches are discovered the way the client discovers them, and a slow arrival holds in place instead of falling through the world

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Sam
2026-08-14 19:32:44 +07:00
co-authored by Claude Fable 5
parent 047dd3444e
commit e665b0bfc4
39 changed files with 1385 additions and 215 deletions
@@ -90,28 +90,45 @@ fn error_message_returns_its_argument_and_can_be_replaced() {
assert!(s.errors().is_empty(), "{:?}", s.errors());
}
/// The default error handler is still ours, deliberately — BasicControls.xml drops the
/// reference's `seterrorhandler(_ERRORMESSAGE)` so a runtime fault stays visible to
/// `UiScript::errors()` (and therefore to the corpus harness) instead of becoming a modal.
/// The divergence is stated at the site; this is what pins it.
/// The reference's closing line is transcribed since decision 1305: BasicControls installs
/// `seterrorhandler(_ERRORMESSAGE)`, so a script error pops the dialog like the real client's —
/// and the host channel stays sighted because the ENGINE records every caught error before the
/// dispatch ever runs (that dual channel is what dissolved the old divergence, whose guard this
/// test used to be).
#[test]
fn the_default_error_handler_is_not_replaced_by_error_message() {
let s = basic_controls();
fn error_message_is_the_installed_handler_and_the_host_channel_stays_sighted() {
let mut s = basic_controls();
assert!(
!s.eval::<bool>("return geterrorhandler() == _ERRORMESSAGE")
s.eval::<bool>("return geterrorhandler() == _ERRORMESSAGE")
.unwrap(),
"installing _ERRORMESSAGE as the default would blind every instrument in the arc"
"the reference's own default handler is installed (1305)"
);
// ...but the pair still works, which is what an addon's pcall wrapper actually calls.
// An addon's pcall wrapper calls geterrorhandler()(msg) — with _ERRORMESSAGE installed that
// pops the dialog, exactly as it does in the real client.
s.run(r#"geterrorhandler()("reported")"#).unwrap();
assert!(
s.errors().iter().any(|e| e.contains("reported")),
"geterrorhandler()(msg) reaches the error channel: {:?}",
s.eval::<bool>("return ScriptErrors:IsVisible()").unwrap(),
"the dialog is the report now"
);
assert_eq!(
s.eval::<String>("return ScriptErrors_Message:GetText()")
.unwrap(),
"reported"
);
// And an ENGINE-caught error still reaches `UiScript::errors()` — the instruments' channel —
// with the dispatch adding the dialog on top, not instead.
s.run("BrokenProbe = CreateFrame('Frame') BrokenProbe:RegisterEvent('B271_PROBE') BrokenProbe:SetScript('OnEvent', function() error('fault') end)")
.unwrap();
s.fire_event("B271_PROBE", vec![]);
assert!(
s.errors().iter().any(|e| e.contains("fault")),
"engine-caught errors stay on the host channel: {:?}",
s.errors()
);
s.dispatch_script_errors_to_handler();
assert!(
!s.eval::<bool>("return ScriptErrors:IsVisible()").unwrap(),
"and it does NOT pop the dialog"
s.eval::<bool>("return ScriptErrors:IsVisible()").unwrap(),
"and the dispatch keeps the dialog up for the player"
);
}