Commit Graph

371 Commits

Author SHA1 Message Date
Sam Sung db8a28ec2c Try-ons, slash targets, RestartGx and the video verbs apply in the call (#2790)
Several verbs did their work in a later drain: try-ons, the chat input's /target, /assist and /follow, RestartGx, and the engine CVar verbs (SetGamma, the video pairs, SetMultisampleFormat, the minimap zoom, the glue writes). A script's next line read the old state. LoadBindings also switched the current set, so a Character Specific tick then Cancel left the character set live.

Reference: a try-on reads the cache and lands or refuses in its call (`0x504540`, `0x504b2d`). RestartGx runs gxRestart synchronously (`0x48dab0` → `0x63ce00`), and SetMultisampleFormat restarts in its call (`0x48c79b`). LoadBindings never writes the current set (`0x4b8249`-`0x4b8252`).

Changed: those verbs apply in the call, and the slash commands call the stock verbs. The drains left at the frame each name why; SaveBindings only marks its set until shutdown (`0x4b6310`, `0x4b65e0`).

Tests: per verb, a same-chunk read of its effect.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 09:19:04 +07:00
Sam Sung 0259668162 Tutorials trigger where they happen, on a stamped lane (#2789)
Tutorials were judged once a frame by a feed. A tutorial a packet raised fired behind every packet's events. Looting fired at a corpse's first sight, even one already dead at login, and Backpack fired before its item's record arrived.

Reference: each trigger fires in the handler or input that raises it.
- the vendor and trainer lists (`0x4fad32`, `0x4d74ae`);
- Looting on the dynamic flags' bit 0 rising (`0x600482`-`0x60049b`);
- the item tutorials after the received line, once the record is known (`0x491cec`);
- Raid Groups at the roster rebuild (`0x4ba60d`).

Changed: tutorials are a stamped lane, and each site triggers where it happens. TutorialsEnabled reads live. `feed_tutorials`, `judge_selections`, `watch_windows` and `TutorialEvent` go.

Tests: a packet's tutorial falls between neighbouring packets' events, and a ClearTutorials in an earlier listener stops a later trigger.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 08:41:10 +07:00
Sam Sung 76874a1510 Profession books open in their cast's call; Create All recasts at the go (#2788)
A profession's book opened from a frame feed, after the cast verb had returned, and its "Create All" repeat recast from a drain. A listener read the book shut inside the call that opened it, and a repeat on a recipe with a cooldown refused its own recast.

Reference: `TryCastSpell` opens the book inside the call (`0x6e4bce` → `ToggleTradeSkill` `0x4fc910` / `ToggleCraftWindow` `0x4f6000`). `HandleSpellGo` runs the repeat hook (`0x6e81e7`) before its cooldown inserts (`0x6e8425`). The repeat disarms only at a failure report, a failed cancel, or the close (`0x4fd790`'s four callers).

Changed:
- the toggles run in the call;
- the repeat runs in the go ahead of the cooldowns;
- `drain_trade_skill` and `open_trade_skill` go.

Tests:
- a listener's CastSpellByName shows the book between two packets;
- Create All recasts at the go, also with a cooldown.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:45:43 +07:00
Sam Sung 2f9bd96fc2 The spell packets clear and replace bar slots at their handler (#2787)
The bar's slot clears, rank-ups and learned-ability latches were rewritten once a frame by feeds. A listener inside an unlearn or a supersede read the old bar, and ACTIONBAR_SLOT_CHANGED fired ahead of every packet event.

Reference: the unlearn handler clears the spell's slots after the book removal (`0x4e5e20` at `0x5ea253`). The supersede's `0x4e5e60` clears then sets each slot, with a send and an ACTIONBAR_SLOT_CHANGED for each (`0x4e58e0`). The learn and unlearn set the latches (`0x4b262c`, `0x4b2c83`). Each game entry zeroes the bar (`0x48f5a4`).

Changed: those writes run in their handlers, with the signals on the notice lane. The rank pass's Deviation: now runs only where login slots arrive. The bar empties at game entry.

Tests: an unlearn's listener reads the slot cleared; a supersede sends clear then set; a relog sends nothing for the last character's bar.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 07:10:23 +07:00
Sam Sung 4b322073b4 SetCVar runs its callback in the call; the view verbs seat in theirs (#2785)
SetCVar ran its change callback and its effect in the frame's later sync, and SetView, SaveView and ResetView queued their camera writes for a drain. A script that read the effect on its next line saw the old value.

Reference: `CVar::Set` (`0x63df50`) runs the row's callback inside the call (`0x63df7d`), and a refusal drops the set. CVAR_UPDATE follows from `SetCVar` (`0x488cad`). SetView writes `cameraView` in its call (`0x512f17`), and SaveView writes its view there too (`0x50fa09`).

Changed:
- the 23 callbacks, once observers, are plain functions the host runs in the call;
- the knobs they write became host state, with their readers ordered around the VM pass;
- `drain_view_requests` is gone.

Tests: SetCVar("deselectOnClick","0"), then a click on empty world on the chunk's next line, keeps the target. A view verb's CVar reads on the next line.

Part of #2202.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:35:50 +07:00
Sam Sung eee9d1441c An item record's answer walks its callback list at the packet (#2784)
An item record's answer ran its callbacks from hand-placed hooks. The equip replays ran in the drain, the quest log's refire at the frame's end, and the tooltip rebuilds at the tick, so a replay's send followed later packets' sends. A refused record also replayed every frame.

Reference: `ItemCacheReceive` (`0x55bdb0`) stores the record, then walks one callback list per record, oldest first, filled by each lookup's miss (`0x55ba30`). A refusal calls each callback with found 0 and frees the list (`0x55bea8`, `0x55bed2`).

Changed:
- `Items` keeps that list, registered at each miss and walked at the answer's handler;
- a deferred stretch's calls apply at once and its signals park.

The late callbacks stay named in the `Items` row.

Tests: an equip replay sends before a later packet's send; two parked swaps keep their order; a refused record sends nothing.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 05:10:21 +07:00
Sam Sung e41ddb36d3 Party, raid and pet tokens and the roster read live at their packets (#2783)
The party, raid and pet tokens read copies pushed once a frame, and so did the roster copy, the event-token walk and the dungeon latch. A listener inside a member's update read the last frame's party.

Reference:
- the tokens look their member or pet record up by guid at the call (`0x496400`, `0x496420`);
- the event-token walk names player, pet, target, party row, raid row, npc, mouseover (`0x515c50`);
- the dungeon latch is set at the port's new object manager (`0x401c22` → `0x495d10`), and at login with the character list's map (`0x4015f3`);
- a ready check closes at the raid rebuild's end (`0x4bac5a`).

Changed: the tokens, the token walk, the roster, the group predicates and the relations read live, and the latch moves to those places. The frame-final party and pet pushes go.

Tests: a member listener reads party1 and raid3 from its own packet, through a same-frame roster swap.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 04:27:08 +07:00
Sam Sung cdebda36fb Loot roll getters read live; roll signals fire at their packets (#2781)
The loot-roll getters read a copy pushed once a frame, and START_LOOT_ROLL, CANCEL_LOOT_ROLL and the roll lines fired from that feed. A vote also deleted its roll, where the reference keeps it listed until the result.

Reference: START fires at the start packet with the item cached, or at the item answer (`0x61b44d`, `0x61b7a7`). A vote sets only the closed byte after its CANCEL (`0x61bf2a`, `0x61bf32`). A win's line waits for the winner's name (`0x61b96a`). Time left is an unclamped 32-bit difference (`0x61b404`).

Changed: the getters read live, the rolls get their own lane, and each signal fires at the handler whose lookup lands last. One Deviation: a refused record closes the roll, where 1.12.1 leaves a dead frame.

Tests: a listener reads the live roll; START falls between neighbouring packets' events; a start and a win in one frame leave nothing listed.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 01:50:05 +07:00
Sam Sung 77cea16e72 Closes run their engine half at the packet and signal at its place (#2779)
A close that a packet handler ran fired at once when its own lane was empty, ahead of earlier packets' events on other lanes. benilla also closed the binder, pet-untrain and talent-wipe confirms itself when you walked away.

Reference: each close core releases, clears its cell and sends in the handler, then signals. The stock OnHide's close verb re-enters it: trade's release re-enters at `0x493365`, so a latched partner gets three CMSG_CANCEL_TRADE. The confirms' latches are never cleared (`0x5dfa21`); StaticPopup.lua hides them.

Changed:
- the engine half of every close runs at the handler, and the signals park behind the lane;
- one shared guard keeps a re-entered close verb off a newer session, replacing auction's own;
- the range-close systems go, and the polls test range at the call.

Tests: quest detail Accept sends its NPC, and the trade cancels and Begin go in order.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 00:18:47 +07:00
Sam Sung e23368a777 The item-text reader reads live and signals at its packets (#2778)
The book, letter and plaque reader read a copy pushed once a frame, and opened on its lane. A bank or gossip open in the same frame lost to the book, and a listener read the last frame's page.

Reference: the open, the page and record answers and the turn each run in their handler. ITEM_TEXT_BEGIN (`0x4e349f`), READY and TRANSLATION are signalled there, and a waiting open finishes in its record's callback (`0x4e3280`, `0x5f59d0`).

Changed: the getters read live, each step runs in its handler, the events go on the lane. A nested close acts only on its read. Deviation: 1.12.1's reopen closes the book when an uncached page or a letter's author lands, and benilla keeps the book open.

Tests: a listener reads its packet's page, a same-frame bank open wins, and a book read before its template arrives opens when it lands.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 23:46:11 +07:00
Sam Sung 68176302d5 A worldport frees the cooldowns and cancels open loot rolls (#2775)
A cross-map worldport kept the old cooldowns and left open loot rolls up in the new world.

Reference: the worldport's teardown timer `0x401bc0` frees every cooldown after the objects' walk (`ClearItemAndSpellCooldowns` `0x6e9ad0` at `0x401c17`). That covers the player's and the pet's lists and the item table, with no flush. Then it cancels every open roll (`CancelAllLootRolls` `0x61b290` at `0x401c2c`), signalling CANCEL_LOOT_ROLL for each one not queued, before the map load.

Changed: two legs after the objects' leg of the worldport timer, in that order. The legs run once per load.

Tests:
- a port cancels the open rolls in the next frame;
- a port empties the cooldowns, and the server's resend seeds only what it lists;
- a same-map teleport touches neither.

Fixes #2769
Fixes #2770

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 23:00:52 +07:00
Sam Sung e0329532cc Played time, pings, broadcasts, the clock and channels fire at packets (#2774)
/played, PLAYER_SKINNED, the game clock, members' minimap pings, world broadcasts and the chat channel list came from feeds that ran once a frame. A listener inside a packet read the last frame, and two /played answers in one frame kept only one.

Reference:
- TIME_PLAYED_MSG and PLAYER_SKINNED signal inside their handlers (`0x4018df`, `0x48f412`);
- `GetGameTime` reads the clock at the call (`0x515ee0`);
- the ping handler stores the point and signals MINIMAP_PING at its packet (`0x4ee48d`);
- the broadcast handlers add their chat lines themselves (`0x49dcc0`).

Changed: each fires on its lane at its handler. The clock and the channel getters read through the host, and the five feeds go.

Tests: per family, a neighbouring packet's listener reads the live value; each fails on main.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 22:37:11 +07:00
Sam Sung 6b50b9dd6a A worldport's teardown runs at the next frame's start, as one unit (#2773)
A worldport tore the old world down inside its packet: the despawn, the world handle and LOGOUT_CANCEL there, then PLAYER_LEAVING_WORLD before the frame's earlier packets' events had fired.

Reference: `SMSG_NEW_WORLD` (`0x401b00`) posts its teardown `0x401bc0` as a zero-delay timer, and a tick runs its timers before its packets. So the next tick:
- zeroes the world handle (LOGOUT_CANCEL);
- walks the objects (`0x467800`);
- sweeps at the local player's turn (`0x5dd543`).

Changed: the packet only posts the load. Three legs at the next frame's start run the connection's half, the sweep and the objects' teardown. The sweep runs ahead of the objects, so the target is still there in PLAYER_LEAVING_WORLD.

Tests:
- the port frame's events precede the leave, and its listeners still see the old world;
- a port alone leaves in the next frame;
- the leave listener sees the target.

Fixes #2763

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 21:47:03 +07:00
Sam Sung cb949ba1a5 Player scalars and the unit tokens read live; the unit feed goes (#2771)
The player's combo points, action-bar toggles and zone PvP masks, and the four unit tokens, read copies pushed once a frame by `feed_units`. A listener inside a packet saw the last frame's values.

Reference:
- `GetComboPoints` reads the player's count and banked target at the call (`0x51a190`);
- the bar toggles read `+0x102a` (`0x4e7660`);
- `GetZonePVPInfo` reads its masks at the call (`0x48d540`);
- the tooltip's health bar is redrawn by the health field's watcher (`0x52a08d` → `0x529560`).

Changed: those getters ask the live player, and `feed_units` is gone. Health notices redraw the tooltip bar. The senders' player checks read the player object. The unit-frame and world-entry harnesses run the host-lent path.

Tests:
- the scalars read as written;
- the tooltip bar follows each health packet;
- the senders gate on the player object;
- the login's XP check runs through the host.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 20:43:57 +07:00
Sam Sung 3242ef72cc Quest text, quest lines and shares fire at their packets (#2768)
The quest log's text, its turn-in and accept lines, and the share verdicts came from feeds that ran once a frame. A listener inside a quest packet read the last frame's text, and an accept line was lost when a later packet in the frame emptied its slot.

Reference: `GetQuestLogQuestText` expands its `$` macros at the call (`0x4dff89` → `0x506f70`). The slot watcher `0x5ddd80` shows the accept line inside the update packet. The turn-in lines are `0x5dc400`'s.

Changed:
- the text expands at the call;
- the log reads names and records through the host;
- the accept line, the turn-in lines and the share verdicts fire on their lanes at their packets;
- the share line's bounded name wait is kept as on main.

Tests: a listener reads its own packet's quest text, and the accept line shows at the packet that fills the slot.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 19:49:41 +07:00
Sam Sung 289b0ec5f6 Tooltips, spell targeting and the mouseover read live at the call (#2767)
Spell and aura tooltips, `SpellCanTargetUnit` and the `"mouseover"` getters read a copy pushed once a frame. A listener inside a packet saw the last frame's values, and `GetRaidTargetIndex("mouseover")` was always nil.

Reference: the setters build the view at the call (`0x52e610`, `0x52f880`). `SpellCanTargetUnit` reads the unit and range at the call (`0x6e6d00` → `0x6e6460`). A reagent's record answer rebuilds a shown spell tooltip (`0x52f820`).

Changed:
- the setters build through the call host;
- the frame feed and its re-push machinery are gone;
- targeting and the mouseover read live;
- a reagent's answer rebuilds the shown tooltip.

The hover's guard rows cite the cache callbacks that rebuild it inside packets.

Tests: a trainer listener's `SetSpell` reads its own packet's cost. Also covered: `SpellCanTargetUnit` out of range, the mouseover mark, the reagent rebuild, and the pet and aura views.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:54:06 +07:00
Sam Sung b4d0987ab0 The loot window reads live and signals at its packets (#2765)
The loot window's getters read a copy the feed pushed once a frame, and its events fired from that feed. An addon reading the window inside a loot event saw the last frame's rows.

Reference: the loot response handler fills the window and signals LOOT_OPENED at its packet. A removal signals LOOT_SLOT_CLEARED (`0x4c1f77`) and then closes an emptied window (`0x4c1f7f`). The master-loot list (`0x61c550`) names its candidates from the name cache alone.

Changed: the getters read the live window, and each event goes on the loot lane at the packet that raises it. A listener's close sends one release. A refused master-loot list frees its push (`0x491ee0`). A removal with no window open still signals its clear.

Tests: loot tests cover the order around a close, a lockbox, auto-loot and master loot.

Part of #2168.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:01:49 +07:00
Sam Sung 12ec14d6b9 World entry fires as one item at the create, in the cascade's order (#2762)
World entry was spread over the frame's systems, so `PLAYER_ENTERING_WORLD` fired before every lane item of its frame, and its cascade's resets landed out of order. In 1.12.1 the arrival `0x5dea50` runs inside the create (`PostInit 0x5debe0`) and calls the cascade `0x4908c0` in order.

- The arrival is one item on an `arrival` lane, stamped at the create. It runs its legs in order: the book replay, then each form's UPDATE_SHAPESHIFT_FORMS against the stored list, the slot pass, the cascade with its resets and corpse step, the death state and `LOGOUT_CANCEL`. A /reload pushes the cascade alone.
- System chat is held behind the display latch `0x8435fc` until the cascade releases it (`0x490974`). The leave sweep sets it, and the name and text are composed at the packet.
- The party catch-up fires after PEW.

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 16:44:55 +07:00
Sam Sung 831310c1f2 The auction sell slot asks for a missing record, redrawing when it lands (#2760)
An item put in the auction sell slot before its record was cached showed a price and deposit of 0 until something else redrew the frame. In 1.12.1, `CalculateAuctionDeposit` and `GetAuctionSellItemInfo` ask for the record with the callback `0x4ce2f0` (`0x4ce25e`, `0x4ce5e3`). It signals `NEW_AUCTION_UPDATE` when the record is found (`0x4ce2ff`).

Both getters now ask, gated on the item's object (`0x468460`), with the item's own guid (`0x4ce263`, `0x4ce5e8`). Each asking call keeps its callback, and the event goes on the house lane. On a miss they return the reference's empty shape: a deposit of 0, and `nil, nil, 1, -1, nil, 0` (`0x4ce726`). The deposit does no lookup with no house (`0x4ce1f7`). With a host, the name, icon, quality and price read live.

Tests: a record that lands later fires the event once per call and then answers; a gone object asks nothing.

Fixes #2747

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 14:22:53 +07:00
Sam Sung 53bc8245b7 Every item action sends ahead of its cursor clear's and lock's signals (#2759)
Every item action queued its packet for after its Lua call, so the cursor clear's and the lock's listeners ran before the packet went out. In 1.12.1 each sends first, then clears or locks. Examples: `AutoEquipCursorItem` (send at `0x5e1757`, clear at `0x5e1763`), the split (`0x4f9e43`, then `0x4f9e4f`), `DeleteCursorItem` (`0x4898a3`, `0x4898af`), the bank and sale arms (`0x4fa305`, then the lock at `0x4fa312`), and the merchant sell (`0x4fb7a7`, `0x4fb7b3`).

Every item send now goes to the host in place, ahead of its clear's or lock's signals, and `Model::item_calls` is gone. The projectile unlock is decided once, from the item's record (`0x5d9f30`). A set refused with no control plays no pickup sound (`0x494b8d`).

Tests cover 16 paths, in the order each sends and signals. Each fails on main.

Fixes #2752

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 14:01:42 +07:00
Sam Sung 91dbe0666e A world click's arm picks inside its call, from a key or a handler too (#2758)
A mouse command run from a key binding or a handler picked and clicked only after the record's walk returned. So a body `CameraOrSelectOrMoveStart(); CameraOrSelectOrMoveStop(); print(UnitName("target"))` printed the old target. In 1.12.1 `ArmClick` picks inside the Start's call (`0x5141ae` → `0x514810` → `0x481f00`), at the cursor stored from the last mouse event (`0x481f1d`), and the Stop dispatches inside its own.

The records' walk now lends a tracer beside the call host, so an arm picks inside its call at its record's cursor: one trace per arm, and no new schedule ambiguity. The pick traces the world alone (`0x481190`), never a nameplate. The GameObject pick set is now one resource, kept every frame ahead of the input.

Tests cover a key body, an `OnClick` body, the target event in the handler, a key-armed pick under a plate, and a chest clicked by mouse and by key.

Fixes #2749

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 13:45:10 +07:00
Sam Sung 07ba2b0e8b Mail send item, stationery list and texture read as 1.12.1 reads them (#2757)
Three mail getters differed from 1.12.1.

- **#2739.** `GetSendMailItem` asked nothing on a missing record. Now, with the attachment's object present (`0x4ae5e1`), it asks with the callback `0x4ae710`, which signals `MAIL_SEND_INFO_UPDATE` once per call on arrival.
- **#2740.** The usable stationery list was rebuilt every frame. Now it is rebuilt (`0x4ad970`) only at the world-enter init (`0x4acbd7`), at the last missing paper's answer (`0x4acbf8`), and at `GetNumStationeries` with nothing awaited (`0x4ae202`). The getters read it as last rebuilt. With no player a rebuild keeps the list (`0x4ad9aa`), and leaving the world cancels the paper waits (`0x4acc00`).
- **#2741.** `GetSelectedStationeryTexture` reads the Stationery store by id (`0x4ae3f0`), not the usable list.

Each fix has a test that fails without it.

Fixes #2739
Fixes #2740
Fixes #2741

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 12:37:13 +07:00
Sam Sung 62399b7a05 Auction getters read the house live; its events fire at their packet (#2754)
The auction window reached the interface through a per-frame feed. Its events fired ahead of the frame's other packets, its getters read a pushed copy, and a result's list request went out before its line's listeners. In 1.12.1 the auction handlers signal inside their packet, and the getters read the house at the call.

- **Live reads.** The getters read the house through the host: rows, the browse throttle (`0x4cfbb0`) and the deposit rate (`0x4ce170`).
- **The house lane.** `AUCTION_HOUSE_SHOW` fires in the hello (`0x4cd5e6`). A notice waiting on an item record shows when the record arrives (`0x4cd190`). A nested close acts on the session it closes.
- **Line first.** A started, bid-placed or outbid result shows its line first and does the rest after its listeners (`0x4cc4cc`, `0x4ccfd5`).
- `LatchCloses` is removed; nothing read it.

Each change has a test that fails without it.

Fixes #2426

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 10:50:02 +07:00
Sam Sung 78e360a25b Every saved cache asks again after a disconnect, under one deviation (#2751)
After a disconnect, a game object's name, an item name, a page of text or an NPC's text whose query was in flight at the drop stayed blank until restart. Only some saved caches cleared their lost asks, from four scattered places, and no `Deviation:` line recorded the rule.

In 1.12.1 a saved cache's ask stays "asked" for the rest of the process. `DBCacheClearUnsaved` (`0x555740`) wipes only the unsaved caches. The maintainer ruled that benilla asks again after a reconnect.

One disconnect handler now clears the in-flight asks of all eight saved caches (creatures, game objects, item names, items, NPC text, quests, page text and mail bodies). It keeps their records and refusals, under one `Deviation:` line, and the scattered clears are gone. Test: an ask in flight in each store at a disconnect is asked again after the re-login.

Fixes #2744

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 09:54:02 +07:00
Sam Sung fd3639babe A listener unregistered by the one before runs once, then the walk ends (#2750)
An event handler that unregistered the next listener ended the dispatch, as a `Deviation:` on the grounds that 1.12.1 then reads a freed node. That read is settled at the bytes. `SignalEvent` saves the successor before each handler (`0x703ee8`). The unlink zeroes the node's links (`0x70559d`, `0x7055a3`), and the free writes only the block header (`0x645eb0`), so the object survives. The unregistered listener runs once more, then the zero link ends the walk, and the listeners after it miss the event.

The dispatch now does the same, and the `Deviation:` is gone. A self-unregister still reaches its successor. A listener unregistered further on is skipped. One registered during the signal is reached unless the running listener is last. Each case has a test; the two changed ones fail on main.

Fixes #2743

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 09:19:46 +07:00
Sam Sung 15cf220df9 The world click's legs run inside the mouse commands' calls (#2748)
The world click's legs ran after the record's walk returned, so Lua after `CameraOrSelectOrMoveStop` in the same binding body, or a hook around it, read the world as the click found it. In 1.12.1 the release dispatches inside its call (`0x514b70` → `0x481f60`), and the router calls each leg directly, deferring nothing.

- The release's click now runs in the call host through a click applier, in the reference's leg order. On a unit that is trade, pet feed, greeting, then select (`0x49278e`, `0x4927e8`, `0x4927ff`, `0x49280c`). On a right click, the select comes before the interact dispatch (`0x492870`, `0x492881`).
- The press pick is traced ahead of each world press's binding (`0x481f00`).
- A charmed player's click acts on nothing (`0x481f98`), for every leg.

Each change has a test that fails without it.

Fixes #1988

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:53:21 +07:00
Sam Sung 62088a624a The container bound's deviation covers the bank and the keyring (#2746)
In 1.12.1, `GetContainerItemInfo`'s slot mapping bounds the backpack, the bank (base `+0x27`, `0x4f98ae`) and the keyring (base `+0x51`, `0x4f98c0`) only by the player list's count (`0x4f990f`). An index past a container's own size reads the next container's slots: `GetContainerItemInfo(-1, 25)` reads the first bank bag's slot.

benilla bounds all three by their own slots, but the recorded deviation named only the backpack. The maintainer ruled that the bank and the keyring take the same deviation for the same reason, a reference bug. The `Deviation:` line now covers all three. No behaviour changes.

Fixes #2617

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:26:53 +07:00
Sam Sung 194b7ee766 A path ends with the unit turned to the packet's final facing (#2745)
A path ends with the unit turned to the packet's final facing, as 1.12.1 leaves it.

- A monster move that installs a path stores its final facing on the spline (`0x618840`-`0x6188a0`). Only a move with no path turns the unit on arrival.
- At the path's end, `0x61a882` writes the facing in this order:
  - Angle, as sent;
  - Target, the heading to where that unit stands (`0x61a8c1`), from the deck-local end on a transport;
  - Spot, through `0x47f220`.
- The turn runs through the facing smoother (`0x600cd0`), never a snap. The display facing resets only at create and reactivate.
- `heading_between` ports `0x47f220`'s fixed headings for a point under the unit.

Each change has a test that fails without it.

First diagnosed and drafted by @kalistoking in #2695.

Co-authored-by: kalistoking <148718830+kalistoking@users.noreply.github.com>

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 08:07:42 +07:00
Sam Sung 1a78118fcb Mail getters read the session live; its events fire at their packet (#2742)
The mail window reached the interface through a per-frame feed. Its events fired ahead of the frame's other packets, and its getters read a pushed copy. In 1.12.1 the mail handlers signal inside their packet, and the getters read the session at the call.

- The getters read the session through the host (`MailLive`). Every mail event goes on a mail lane, stamped in the handler that raises it.
- Each answered record lookup signals one `MAIL_INBOX_UPDATE`, only when the record is found (`0x4af080`, `0x4af0a0`, `0x4af340`, `0x4af5b0`, `0x4ad010`).
- The pending step runs after the frame's packets and after the walk-away check (`0x4818c0`, `0x4818d0`).
- A close in its open's frame no longer fires `MAIL_CLOSED` without `MAIL_SHOW`, or sends its query ahead of earlier events.

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 07:37:27 +07:00
Sam Sung c1507a281c Trade getters read the session live; its events fire at their packet (#2736)
The trade window reached the interface through a per-frame feed, so its events fired ahead of the frame's other packets and its getters read a VM copy. In 1.12.1 the trade status handler (`0x4bfa08`'s cases) signals inside the packet, and the getters read the session at the call.

- The getters read the session through the host. Every trade signal goes on a trade lane, stamped in its handler.
- A request's checks run inside BEGIN_TRADE, with the ignore list ahead of the object lookup (`0x4bf750`).
- Our slots 1-6 raise no TRADE_PLAYER_ITEM_CHANGED from the status (`0x4bf2b0`).
- A close waits behind the open it closes, cancel included (`0x4bf527`, `0x493365`).
- Status 12 prints its line before the close (`0x4bf88e`).
- A chat-kind line raised in a Lua call fires inside it (`0x496842` → `0x49a870`).

Each change has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 05:37:22 +07:00
Sam Sung d9039e6db9 Pet cooldowns, happiness and unit auras are written at their packet (#2734)
Aura and pet state reached the interface from per-frame feeds, so a listener at one packet read the frame's final state. In 1.12.1 each is written where its packet or call makes it.

- **Pet GCD.** A pet cast's global cooldown is armed in the pet bar at the send (`0x6e58fb`), inside the cast.
- **Happiness.** `UNIT_HAPPINESS` fires from the field watch (`0x515e7a`) inside its packet.
- **Unit auras.** The aura getters read the unit's store live.
- **Dispel list.** It is kept from the learn (`0x4b26a2`-`0x4b26ee`), so an unlearn leaves it.
- **World entry.** The cascade rebuilds the buff cache at `0x490983`, so `PLAYER_AURAS_CHANGED` follows `PLAYER_ENTERING_WORLD` and comes ahead of the bag updates.

Each fix has a test that fails without it. Part of #2168 and #2217.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 04:56:23 +07:00
Sam Sung fcc83ee96b The mouse commands arm and release the world click at their record (#2732)
The world click was resolved in the look session, frames after the mouse command that made it. In 1.12.1, `CameraOrSelectOrMoveStart` and `TurnOrActionStart` arm it inside their call: `ArmClick 0x514810` picks under the press (`PickAtCursor 0x481190`), and the last primary's release dispatches it (`0x514b70` → `0x481f60`). The VM now keeps the command bits, and the click applies at the record whose binding made it.

Changes:
- The world frame's right-press hook `0x492c20` now runs whole, ahead of the binding (`0x483c62`, `0x483c93`): it cancels targeting, clears a pending wrap, and ends repair mode. It does not run on a press `0x492b50` eats.
- The loading cover, a bindingless right release, and a reload's cascade (`0x49093c` → `0x5144c0`) free the bits.
- The release counts its own frame's travel.

Tests cover each. The click's legs still run after the walk, not inside the call: part of #1988.

Fixes #2729

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 04:30:42 +07:00
Sam Sung 09c276fe97 Bag and item reads answer as of their packet; logout reads no player (#2728)
Bag and item reads now answer as of the listener's own packet, not the frame's end, and the logout reads what 1.12.1 has there.

- The bag getters read the player's bags, the bank and the keyring live. Item and container writes go into the view log, so a listener sees its packet's slots, stacks and bags.
- The loot close's two unlocks fire at its packet (`0x48f2bd`, `0x4c1d36`). A wrong-bag refusal names the bag its packet found (`0x5e3a1e` → `0x5ede00`).
- At logout the object manager goes before PLAYER_LOGOUT fires (`0x46771e`, `0x490c2a`). The inventory getters then answer nothing with no active player (`0x4944a0` → `0x4c8c56`). `UnitName("player")` keeps the character's name.
- The item tooltip feed follows record changes by cursor. The suffix template is copied after the frame's Lua.

Each change has a test that fails without it. flip_readiness falls to 92 rows.

Fixes #2725
Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:52:03 +07:00
Sam Sung 7c7a7f3e7c The verb census sweeps the whole suite and counts writes after a signal (#2726)
The census that finds Lua verbs whose work outruns their signals now sweeps the whole suite. It also counts state a lend writes while a handler is held, which a late listener would read and no send shows.

- Over 12,964 tests, one verb sends ahead of its signals: `UseAction`, V2's.
- An accepted item swap is in order: its send (`0x5e0fdb`) comes before the cursor clear (`0x5e0fe7`).
- One write after a held signal: a target clear's strike-cancel report waits behind the action bar's usable refresh (`0x493a08`, `0x493a18`, `0x493a69`, `0x493a73`). It is now listed, with its test.

All of it is test-only; the player build is unchanged.

Refs #2271
Refs #2202

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 01:29:40 +07:00
Sam Sung 39de572fc1 Transport props animate and cull with their ship, engine sound included (#2724)
Transports' props animate and cull with their ship, as in 1.12.1.

- A carried prop's world sphere comes from its host's pose every frame. The tick re-poses a transport's doodad defs before the cull (`0x6709a0` → `0x698f50` → `0x6a2070` → `0x6a81d0`), so a ship's parts stay drawn wherever it is.
- A transport's props play the ship's phase (`0x5f52f9` → `0x6709f0` → `0x6a24b0` → `0x6a20f0`), with window ends chained through `0x5f49b0`. A carried play cross-fades over its sequence's blend time (`0x6a2128`). The zeppelin's engine `$DSL` keys sit in its moving sequences (0xa2, 0xa3), so its engine is heard now. Before, its props looped Stand.
- The transport and lift loaders guard the reference's own faults as `Deviation:` lines: unwritten leg lengths, an unbounded append, a zero divide (`0x5f62a4`), an endless seek.
- A whole-period test of the Booty Bay–Ratchet path.

Each change has a test that fails without it.

Fixes #1210
Fixes #1221
Refs #1220

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:00:49 +07:00
Sam Sung 6ee671bcca Our player's create does its own writes inside its packet (#2723)
Our player's create now does its own writes inside the update packet, at the end of its create step (`0x5dec7b`), as 1.12.1's PostInit does:

- SelfPlayer is tagged in the create's own commands, so a later packet in the same frame finds us tagged and in control.
- The control flag and its two signals (`0x5deb56`), gated on `+0xc8` (`0x5deb49`).
- `CMSG_SET_ACTIVE_MOVER`, sent only when the mover changes (`0x6006ee`-`0x6006fb`). The mover clears at world entry, at the unit's destructor, at a release and at a control stop.
- The cascade message (`0x5deb60`), then the class family (`0x5debcc`).

The movement lock moves to its own resource. Only the arrival writes it, so a `/reload` while feared or possessing leaves it alone, since 1.12.1's reload runs only `0x4908c0`.

Each change has a test that fails without it. flip_readiness falls to 96 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 23:41:24 +07:00
Sam Sung beb1e124be Mouse moves and wheel turns are input records in arrival order (#2720)
Mouse moves and wheel turns are input records, run in arrival order among the keys and clicks, as 1.12.1 queues them. Kinds 0xa and 0xb dispatch 0xc and 0x10 (`0x424031`, `0x42417e`).

- Each move runs the hover walk (`0x7660d0`), so a frame crossed and left within one frame gets OnEnter and OnLeave.
- A wheel turn sweeps at the saved cursor (`0x766524`), and the world frame's binding runs inside it (`0x483d20`). One message is one step, from its sign alone (`0x76c1a9`). A trackpad's pixel carry is kept as a `Deviation:`, at most one step per event.
- During a long teleport's frozen frame the window stream waits. Released records take the live cursor, as each takes `GetCursorPos` when handled (`0x42dd00`).
- The pick order is reused until a frame changes: 2 µs a walk, 0.034 ms a frame at 1000 Hz polling.

Each change has a test that fails without it.

Fixes #2430

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:27:44 +07:00
Sam Sung 1343fbbd2c Helm and cloak toggles, account-data reads and the login burst's hold (#2718)
Three fixes, each with a test that fails without it.

- ShowHelm and ShowCloak send their toggle only while the live PLAYER_FLAGS bit says the opposite (`0x5ee6c4`, `0x5ee783`), and ShowingHelm/ShowingCloak read that bit (`0x48dd8b`). On the native display the call re-dresses the model at once. A display rebuild, a head or back visible-item change, or an item record landing dresses by the flag again (`0x604990`, `0x5e2541`, `0x5ed880`). The mannequin clones what the model shows (`0x476cb0`).
- Account-data readers register through `0x5afe50`. Each section is read when it settles, and its saves are skipped while it is pending. Bindings read set 1 and set 2 apart (`0x4b6240`, `0x4b64b0`), and macros merge by id (`0x4f0600`).
- The login burst is held until the entry UI has loaded, Pong excepted (`0x537b56`), as 1.12.1 loads before it logs in (`0x46c272`).

Fixes #2602
Fixes #2448
Fixes #2715

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 20:33:25 +07:00
Sam Sung 40b6613f94 Selection writes signal inside their packet or call, as 1.12.1 does (#2717)
Every write to the player's selection now signals where 1.12.1 does, inside its packet or call, not from a per-frame diff.

- The attacked and cast-at acquires select inside their packets (`0x6259fe`, `0x6e799c`). SetSelection keeps its order: the usable refresh (`0x493892`), then `PLAYER_TARGET_CHANGED` (`0x49389c`).
- Deselects (`0x493910`) run at their packet's end, on their lane, or inside the call. `CloseLoot` deselects a dead source after the loot session ends (`0x48f33d`, `0x48f369`).
- The server's swing stop and the refusal latch apply inside their packet (`0x625ab8`).
- The control flag is stored in its handler, then LOST or GAINED, then the refresh (`0x4958e0`).
- The feeds' own raises are gone, and a world-less clear fires nothing (`0x4939df`).

Each change has a test that fails without it. flip_readiness falls to 98 rows.

Refs #2168
Refs #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:50:13 +07:00
Sam Sung 05075f092e Status lines, message frames, quest panels, page text and cache wipes (#2714)
Ten fixes, each with a test that fails without it.

- Status lines are cut at 255 bytes (`0x41a049`), banners too (`0x419f19`), and an empty line is dropped without raising the severity (`0x41a05f`).
- A message frame's `bytes=N` holds N−1 bytes. A negative N empties its lines, since `0x771692` tests only zero.
- The reward panel after a progress panel plays the completion chime (`0xbe081c`, `0x500d36`).
- A panel opens only for a held unit, game object or item (`0x500c16`-`0x500c35`).
- Readable pages are rewritten into their language for the reader (`0x49b560`).
- A page of 1024 bytes or more reads empty, as `0x4191b0` does at capacity 0x400.
- World entry wipes only the four unsaved caches (`0x555740`)., keeping saved asks.
- Two comments cite the right addresses.

Fixes #2580
Fixes #2690
Fixes #2692
Fixes #2694
Fixes #2696
Fixes #2700
Fixes #2701
Fixes #2710
Fixes #2711
Fixes #2712

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

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 18:28:42 +07:00
Sam Sung f40fc60c7c Field callbacks run inside the dispatch, in the notify pass's order (#2709)
The next piece of the event-core move (#2168, #2217). The update handler's field callbacks now run inside the packet's dispatch, as the reference's notify pass does.

- A new end-of-update handler runs each changed field's callbacks in the order the pass wrote them, inside the dispatch:
  - the aura, stack and loyalty lines;
  - the death;
  - the form release and service closes;
  - the pet rename.
- The form release comes right after its own block's signals (`0x5de010`, then `0x5de07f`), so a window closes between UPDATE_BONUS_ACTIONBAR and the next packet's events.
- The stack line reads the live descriptor at notify (`0x62b822`).
- An XP award hides a death line only when it arrived in an earlier packet (`0x62c232`).

Placed-queue readers outside the dispatch go from 5 to 0. A two-packet test fails on main's order.

Part of #2168
Part of #2217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 16:48:54 +07:00
Sam Sung f65f4bf1b1 Grammar escapes, raid selection, world states and CVars follow 1.12.1 (#2708)
Fifteen UI API fixes.

- **Text and names.** FontString text, buttons, tooltips, names and bubbles apply the `|1`/`|2` grammar escapes (`0x7716f0`). Rank names count code points.
- **Raid.** The raid roster selection stores the member's guid, survives a /reload, and clears at world login.
- **World states.** Each world-state packet fires WORLD_MAP_UPDATE then UPDATE_WORLD_STATES at its place.
- **Events.** DISPLAY_SIZE_CHANGED fires below scale 0.64. UPDATE_CHAT_COLOR carries the caller's name.
- **CVars.** Four refuse what 1.12.1 refuses, and anisotropic and ErrorLevel print its console lines.
- **Calls.** GuildSetLeaderByName("target") sends no name. Repair costs read by row position. Quest reward slots count to the first empty one. OfferPetition checks its eight guards.
- **Glue and errors.** The glue VM has the file calls. Widget methods share one wrong-receiver text.


Fixes #2507
Fixes #2557
Fixes #2559
Fixes #2567
Fixes #2568
Fixes #2581
Fixes #2588
Fixes #2593
Fixes #2599
Fixes #2606
Fixes #2615
Fixes #2623
Fixes #2630
Fixes #2687
Fixes #2688

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:59:50 +07:00
Sam Sung cf90bdc592 Frames build one inherited level at a time; anchors are never retried (#2702)
1.12.1 builds a frame one inherited level at a time, template first (`0x769820`). Each level runs:
- its geometry (`0x767800`);
- its flags;
- one child loop in document order;
- its type's own part.

An XML anchor to a frame not yet built is logged, "Couldn't find relative frame", and dropped. benilla instead merged the levels and retried missed anchors later. That hid order bugs, such as Button labels built before their state textures.

- The loader now builds level by level, every frame type's part included, and the retry queue is gone.
- `<Frames>` and `<ScrollChild>` take their first element only.
- Each TitleRegion and ButtonText makes a fresh object.
- A tooltip finds its lines after OnLoad, and Minimap models read the instance's own node.

All 4,140 anchors in FrameXML and the 12 Blizzard addons resolve in this order. A test holds that.

Fixes #2574

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 15:30:17 +07:00
Sam Sung c5fc904f8c Cursor and cast drops, arrival target clear, loot and chat follow 1.12.1 (#2699)
Thirteen fixes to the cursor, world arrival, loot and chat.

- Losing control (`0x495901`) or a world port drops the cursor and the cast, inside the packet.
- Every arrival clears the target (`0x490927`), just before the world-entry events.
- A resolved roll frees its loot row, and a group coin loot prints its share line.
- A boss whisper becomes a whisper, and channel chat carries its instance number.
- The delete confirm names an uncached item.
- Bag books send CMSG_READ_ITEM and handle both replies.
- The spellbook pickup and drop sounds play.
- The quest log's gold guide arrow shows, cleared first on each select (`0x4def39`).
- A cleared loot row records its `Deviation:`.

Each change has a test that fails without it.

Fixes #2573
Fixes #2576
Fixes #2446
Fixes #2578
Fixes #2584
Fixes #2577
Fixes #2558
Fixes #2564
Fixes #2562
Fixes #2563
Fixes #2627
Fixes #2679
Fixes #2680

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 14:21:17 +07:00
Sam Sung 217a10df1b The XML loader and the SavedVariables writer follow 1.12.1 (#2693)
The XML loader and the SavedVariables writer follow 1.12.1.

- **Broken handlers.** One that fails to compile replaces the template's, reports nothing and keeps input on (`0x704c9f`).
- **Script files.** A `<Script file>` with a body runs both (`0x6ee0d4`).
- **FrameXML.log.** Load faults reach it in the client's wording. A failing script file logs at severity 2 (`0x704ae0`), and inline bodies log nothing.
- **Template levels.** Flags are read one inherited level at a time, and handlers come before a Slider's or StatusBar's values.
- **Fonts, gradients, names.** Unnamed fonts are logged, heights of a screen or more are refused, gradients take their last colours, and empty names stay unnamed.
- **Size changes.** A NaN size change fires OnSizeChanged.
- **SavedVariables.** They write bool and fractional keys, and the client's comments for skipped entries.

Fixes #2555
Fixes #2556
Fixes #2560
Fixes #2561
Fixes #2565
Fixes #2566
Fixes #2570
Fixes #2572
Fixes #2678
Fixes #2683
Fixes #2684

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 12:07:21 +07:00
Sam Sung cbd8e0fa68 Sorted particles, party snapshots, CVar floats and GetText follow 1.12.1 (#2691)
Seven issues from the UI core pass.

- Sorted particle emitters, with file flag 0x2 (`0x70fd1d`, `0x7b5d00`), now draw back to front. They use the reference's view-depth heap, so ties pop in its order (`0x7b3a5b`). Every other emitter keeps list order. (#2676)
- A party member leaving view snapshots the committed zone, `0xb4e314`, and their position at the call, as `0x5f0880` does. (#2677)
- GetDefaultLanguage answers nothing during a login's load (`0x49fcf0`). (#2665)
- GetGamma, the video getters and flat SavedVariables numbers read the CVar record's float (`0x63e135`). (#2673, #2672)
- The engine's counted and gendered text calls the Lua GetText and GetPluralIndex (`0x703bf0`, `0x703cf0`). Part of #2666.
- The pet cooldown test no longer depends on wall-clock time. (#2670)

Each change has a test that fails without it.

Fixes #2676
Fixes #2677
Fixes #2665
Fixes #2673
Fixes #2672
Fixes #2670

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 11:27:06 +07:00
Sam Sung 9f492e98f2 Held units and by-name searches read live; two frame-end feeds go (#2689)
The next piece of the event core (#2168): two more unit feeds that pushed frame-end copies into the VM are gone.

- The held-unit chain and the by-name candidates are now read live through the call host.
- The by-name searches and the emote lookup take the player's and each unit's position at the call, so a unit that moved this frame is measured where it stands.
- The object, name and record getters ask a thin held-unit query instead of building a full unit state. With the host lent, `UnitHealth("target")` drops from 3.26 µs to 1.20 µs.
- A guid that is not held as a unit is no longer a name candidate.

The frame-final feeds go from 45 to 43. `ui_unit::feed_units` stays for the next piece. Each change has a test that fails without it.

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 10:17:17 +07:00
Sam Sung 403f999892 GM tickets, cursor casts, hover, gossip and plate flashes follow 1.12.1 (#2685)
What differed from 1.12.1:
- **Ticket results** show their lines: create and update codes 1, 3 and 5, a fetch status of 0 and a failed delete (`0x5e4432`, `0x5e44ff`). Update text is cut at 1999 bytes (`0x5efacd`).
- **UPDATE_TICKET** clamps a negative age to 0 and passes the client's NaN and denormal bits for the no-ticket answer. Arguments the client fills from stack residue are a `Deviation:`.
- **Ticket status** 2 sends the no-ticket answer; status 3 fires GMSURVEY_DISPLAY (`0x5e78f0`).
- **Casting** reads the cursor first: a held spell goes through pickup, and anything else is cleared (`0x4b3310`).
- **Hover:** Disable fires OnLeave first, Enable fires OnEnter after (`0x779160`).
- **Gossip:** leaving the world resets the gossip lists (`0x4e1f60`).
- **Nameplates:** a name flashes red for 5 seconds on damage (`0x7cba77`).

Each fix has a test.

Fixes #2462 Fixes #2472 Fixes #2476 Fixes #2478 Fixes #2481 Fixes #2484 Fixes #2536

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-27 08:55:25 +07:00
Sam Sung 6aef15f7e7 Numerals and sscanf convert as 1.12.1's C runtime does (#2675)
Lua numerals and the sscanf ports used Rust's correctly rounded parse. 1.12.1's C runtime rounds its own way: `__RoundMan` rounds up only when the round bit and some bit two past it are set (`0x40fb43`), so ties truncate and `6e23` reads one ulp low. It flushes results below 2^-1022 to +0 (`0x741ed3`) and keeps 24 digits (`0x41157f`). benilla-re holds these at proof two.

One port of that conversion now lives in benilla-lua (`strtod.rs`, `ld12.rs`). It serves `str2d` and `%f`, which rounds once to float (`0x40feef`) and consumes a digitless exponent marker (`0x7451a5`). `%d` wraps in 32 bits (`0x7456c9`). The time zone is read once per process (`0x414568`), and two client faults are written as Deviations.

A table of 3487 cases generated from benilla-re's transcription, and the exe's fixed cases, pin it.

Fixes #2647 Fixes #2648 Fixes #2649 Fixes #2650 Fixes #2651 Fixes #2652

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 08:13:32 +07:00
Sam Sung ab15e9c933 Script errors reach the error handler at the raise, as 1.12.1 does (#2674)
A script error reached the `seterrorhandler` function once per frame, from a queue, after every other listener had run. In 1.12.1 the handler is the error function of the pcall each script runner makes (`0x704e4f`-`0x704e79`; the error function `0x703b40`). So it runs at the raise, inside the failing script: `this`, `event` and `arg1` are still the failing handler's, `debugstack()` shows the failing call, and the event's next listener has not run yet.

Every benilla runner now passes that error function: handlers, OnLoad, file chunks, RunScript, key bindings and slash commands. It returns the handler's result, as `0x703b40` does. The queue is gone.

A handler body that fails to compile reaches no handler (`0x704c70`). benilla's host log keeps each original fault, and `/errors` replays the ones raised before benilla's layer loaded.

Tests cover the raise-time globals, the stack, the listener order, OnUpdate and chunks.

Fixes #2656

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 07:46:07 +07:00