Lua verbs fire their events before returning, in the reference's order (#1015)

In 1.12.1 an event a Lua verb raises runs its handlers before the verb returns: `SignalEvent` (`0x703e50`) walks the listeners in place. About 35 benilla binding sites queued theirs for a later drain instead.

- Verbs now raise inside `tick::verb` steps, and the events fire as each step's model borrow drops. A step follows the reference's split wherever it signals before changing state a getter reads: the money pickup, bag pickup and place, merchant grab, repair cursor, item use, action and pet-bar pickup and drop.
- The grid events fire before `CURSOR_UPDATE`, and a clear's unlock before both, as at `0x4952b3`/`0x495455`. `ITEM_LOCK_CHANGED` carries no arguments.
- `pending_events` is host-only now.

Tests pin handler order per family, nesting from a handler, and the order changes.

Fixes #999
Fixes #582

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Sam Sung
2026-10-12 16:14:10 +07:00
committed by GitHub
co-authored by Claude Opus 5.5
parent 24c7cab983
commit aba120cdcb
26 changed files with 1042 additions and 593 deletions
+6 -9
View File
@@ -743,12 +743,11 @@ pub(super) fn install(lua: &Lua) -> mlua::Result<()> {
)?;
// ClickAuctionSellItemButton(): attach the cursor's item, or pick the attached one back up;
// every cursor write queues both CURSOR_UPDATE and ITEM_LOCK_CHANGED.
// every cursor write raises both CURSOR_UPDATE and ITEM_LOCK_CHANGED.
g.set(
"ClickAuctionSellItemButton",
lua.create_function(|lua, ()| {
let mut model = lua.app_data_mut::<Model>().expect("model app_data");
click_auction_sell_item(&mut model);
super::tick::verb(lua, click_auction_sell_item);
Ok(())
})?,
)?;
@@ -790,17 +789,15 @@ const AUCTION_INV_TYPES: [(u32, &str); 14] = [
fn click_auction_sell_item(model: &mut Model) {
match model.cursor.take() {
Some(CursorPayload::Item(item)) => {
let (bag, slot) = (item.bag, item.slot);
model.auction_sell_item = Some(item);
cursor::queue_cursor_update(model);
cursor::queue_lock_changed(model, bag, slot);
cursor::raise_cursor_update(model);
cursor::raise_lock_changed(model);
}
None => {
if let Some(item) = model.auction_sell_item.take() {
let (bag, slot) = (item.bag, item.slot);
model.cursor = Some(CursorPayload::Item(item));
cursor::queue_cursor_update(model);
cursor::queue_lock_changed(model, bag, slot);
cursor::raise_cursor_update(model);
cursor::raise_lock_changed(model);
}
}
Some(other) => {