creatures keep their first corner, and characters never share files

- a creature walking a path no longer cuts its first corner: the path's first waypoint was dropped at decode (2342)
- two characters whose names differ only in accented or non-Latin letters no longer share macros, bindings and SavedVariables; a file that failed to load is not overwritten at logout (2342)
- custom chat channels survive a logout, a collapsed quest header keeps its quests watched, and a trackpad swipe scrolls as far as it adds up to (2342)
- a hearth off a zeppelin no longer lands back on its deck, and an opened lockbox with loot left in it unlocks (2342)
- every snapshot runs on Vulkan, in a container, before it ships (2344)

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sam
2026-09-23 12:19:22 +07:00
co-authored by Claude Opus 5.5
parent 9c49f10882
commit 293b8e57fb
60 changed files with 1876 additions and 425 deletions
+48
View File
@@ -226,6 +226,54 @@ fn a_chosen_error_handler_hears_engine_caught_errors_and_the_default_does_not_du
);
}
/// **Every handler path reaches the chosen handler, not only `OnEvent`.** The widget scripts —
/// `OnClick`, `OnValueChanged`, the edit box's and keyboard's, the tooltip's — pushed their errors
/// onto the host channel alone, so an addon's broken click handler was a terminal line and never
/// the dialog, while the same error from `OnEnter` did pop it (decision 1305's "every
/// engine-caught script error", which these paths had never joined).
#[test]
fn a_widget_handler_error_reaches_the_chosen_error_handler() {
let mut s = crate::script::UiScript::new().unwrap();
s.set_screen_size(1024.0, 768.0);
s.run(
"caught = {} seterrorhandler(function(m) table.insert(caught, m) end) \
local b = CreateFrame('Button') \
b:SetScript('OnClick', function() error('click boom') end) \
b:Click() \
local sl = CreateFrame('Slider') \
sl:SetMinMaxValues(0, 10) \
sl:SetScript('OnValueChanged', function() error('slide boom') end) \
sl:SetValue(5)",
)
.unwrap();
s.dispatch_script_errors_to_handler();
let caught = s
.eval::<String>("return table.concat(caught, ' | ')")
.unwrap();
assert!(caught.contains("click boom"), "OnClick: {caught}");
assert!(caught.contains("slide boom"), "OnValueChanged: {caught}");
}
/// **A listener that unregisters itself does not rob the next one** — on the engine-internal
/// dispatch too (`fire_global`: `ADDON_LOADED` for a `LoadAddOn`, `UPDATE_FACTION` from the
/// faction-header verbs). That walk stepped an index over the live list, so the hook-once idiom
/// (`this:UnregisterEvent(event)` inside the handler) shifted the successor into the slot just
/// visited and it never heard the event — the 1324 bug, fixed in `fire_event` and not here.
#[test]
fn a_self_unregistering_listener_does_not_rob_its_successor_on_the_internal_dispatch() {
let s = crate::script::UiScript::new().unwrap();
s.run(
r#"log = ""
local a = CreateFrame("Frame") a:RegisterEvent("UPDATE_FACTION")
a:SetScript("OnEvent", function() log = log .. "a" this:UnregisterEvent("UPDATE_FACTION") end)
local b = CreateFrame("Frame") b:RegisterEvent("UPDATE_FACTION")
b:SetScript("OnEvent", function() log = log .. "b" end)
ExpandFactionHeader(0)"#,
)
.unwrap();
assert_eq!(s.eval::<String>("return log").unwrap(), "ab");
}
/// **A handler that itself raises cannot recurse the error path** — its failure is recorded on
/// the host channel only, never re-queued, and the batch stops on the first handler failure.
#[test]