Commit Graph

896 Commits

Author SHA1 Message Date
Sam Sung 1fc327efa5 Trainer filters, groups and NPC windows follow 1.12.1 (#2261)
The trainer window, its filters and the NPC service windows' lifecycle follow 1.12.1.
- `SetTrainerServiceTypeFilter` takes `"all"`, an exclusive third argument and its own errors; `GetTrainerServiceTypeFilter` matches case-blind (`0x4da260`, `0x4da510`).
- A mount or pet trainer always builds its known group first (`0x4d7609`); rows of state 3 and up show under no filter (`0x4da390`).
- An item record a getter waited on re-derives, re-sorts and fires, once per callback (`0x4d9140`, `0x4d7d40`).
- An NPC that loses a service flag closes that window (`0x60b420`).
- No window fires its event when the NPC's name arrives; `QUEST_ITEM_UPDATE` comes only from the item callback (`0x5018c0`).

Tests pin each address. Built on macOS.

Fixes #2241
Fixes #2242
Fixes #2243
Fixes #2244
Fixes #1494
Fixes #203

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:54:16 +07:00
Sam Sung 14d1153179 Bag, lock, bank and money events fire in their watchers' runs (#2260)
Piece 4 of the event order: the bag, lock, bank-slot and money events fire inside their watchers' runs, on the item lane, at their packet's stamp (Part of #2168).
- `BAG_UPDATE`, `BAG_CLOSED` and `PLAYERBANKSLOTS_CHANGED` come from the watchers (`0x4f9480`'s five callers, `0x4f8db0`, `0x4f90d0`, `0x4f91a0`, `0x5ddcf0`), not a diff.
- `PLAYERBANKSLOTS_CHANGED` has its two producers (`0x5ddcf0`, `0x4c7180`).
- The unlocks open each run, for the item now in the seat (`0x5ddd2f`).
- `PLAYER_MONEY` is a coinage notice (`0x5ddf4b`).
- Events stamped before the frame's first bag write read the bags as they stood.
- The guard now fails on a host-held queue a packet fills with no row.

Goldens pin each event against another packet's notice and line.

Fixes #2165

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 01:36:04 +07:00
Sam Sung b63b2bb1a0 Console commands and CVars follow 1.12.1; Lua's date and time are local (#2257)
The console commands and CVar behaviours 1.12.1 has, and Lua's local time.
- `date` and `time` use the local zone, `time{}` by `_mktime`'s sum (`0x740811`, `0x740836`); screenshots and the logon challenge's bias follow.
- In fullscreen `GetCVar("gxResolution")` answers the screen's own size; the windowed size is what is saved.
- The twelve world commands (`0x66f6c0`), with `showDetailDoodads` gating the clutter and `waterRipples` the wakes.
- `quit`, `ver`, `setmap`, `showplayer` (`0x6400e0`, `0x4049e0`) and `SkySunGlare` (`0x6d1dc0`).
- `ffx`, `ffxGlow`, `ffxDeath` and `ffxRectangle` print enabled or disabled (`0x6cde30`).
- `widescreen` 0 gives the four 4:3 modes and a 4:3 portrait aspect.

Tests pin each address. Built on macOS; Linux and Windows compiled.

Fixes #2231
Fixes #2232
Fixes #1628
Fixes #1641
Fixes #1667
Fixes #2098
Fixes #1687
Fixes #1665
Part of #1577

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 23:28:59 +07:00
Sam Sung 6115f6bb20 Pet bar, unit, cooldowns and stable follow 1.12.1; verbs act in the call (#2254)
The pet bar, pet unit, portrait, cooldowns and stable follow 1.12.1, and the pet and stable verbs send inside their call (Part of #2202).
- Pet cooldowns from `SMSG_PET_SPELLS` start back-dated through `0x6e2c60`, with only bit 31 masked (`0x4bdaab`).
- Feeding casts first and binds the food only on success (`0x6ea266`); `DropItemOnUnit` takes any token naming the pet (`0x48da6c`).
- `CastPetAction` presses empty slots, drops a held action and plays its sound (`0x4be330`).
- A pet with no object exists, unseen, with an empty portrait (`0x4e80cb`, `0x51a0ce`).
- `UNIT_COMBAT` fires once per token naming the unit (`0x494600`).
- A pet rename evicts the name cache by pet number (`0x604ac6`).
- `UnstablePet` and the stable drops send to the stored master, as `0x4caf20` does.

Tests pin each address. Built on macOS.

Fixes #2161
Fixes #1598
Fixes #298
Fixes #180
Fixes #2006
Fixes #340
Fixes #187
Fixes #1414

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:11:20 +07:00
Sam Sung 533d3ce5a1 Bag, cursor and doll verbs follow 1.12.1 and send inside their call (#2253)
The bag, cursor and paper-doll verbs follow 1.12.1 and send inside their call (Part of #2202).
- `EquipCursorItem` acts only on a held item (`0x489660`).
- The enchant pick exits silently on a miss and tests the charter flag first (`0x495d60`, `0x495e05`, `0x5da2e3`).
- `PutItemInBag` and `PutItemInBackpack` follow the bag worker: a vendor row buys into the bag, and a backpack item dropped on the backpack cancels (`0x4c7c00`).
- A world click drops a held vendor row or bar item on every leg (`0x492c90`).
- Bank bags are no bags to the container verbs until the bank opens (`0x4f93e0`).
- A bag item with template page text reads as readable (`0x5d9e10`).

A vendor row the list no longer holds, and the backpack's 0x71 bound (`0x4f990f`), are recorded Deviations. Tests pin each address. Built on macOS.

Fixes #2155
Fixes #2154
Fixes #2153
Fixes #152
Fixes #151
Fixes #149
Fixes #146

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:19:38 +07:00
Sam Sung 4a1d1fc7ea Skill and reputation lists keep their folds; their verbs act in the call (#2239)
The skill and reputation lists keep their folds and selection, and the talent, skill and reputation verbs act inside their call (Part of #2202).
- A standing change keeps the reputation folds: the mask `0x84a0a4` is keyed by header (`0x4d5b16`, `0x4d5bdc`).
- `SetSelectedFaction` and `GetFactionInfo` treat a header as a row with its own values (`0x4d6bec`, `0x4d654f`).
- `CollapseSkillHeader` and `ExpandSkillHeader` fold the row's group and fire `SKILL_LINES_CHANGED` (`0x4d3210`, `0x4d3270`, `0x4d2ed0`).
- The skill watchers rebuild the list on a max or bonus change and speak the skill-up line (`0x5dda6c`, `0x5de36a`).
- `GetTradeSkillLine` adds the permanent bonus to rank and max (`0x4fddf7`).
- `FactionToggleAtWar` writes the at-war bit into the reputation store, not only the pane's copy (`0x4d5fd0`).

The fold-by-position quirk is a recorded Deviation. Tests pin each address. Built on macOS.

Fixes #1238
Fixes #1231
Fixes #1230
Fixes #274
Fixes #272
Fixes #271
Fixes #288

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:43:20 +07:00
Sam Sung 7d1c4f5e9c Item, item-push and loot events fire in packet order (#2236)
Item, item-push and loot events now fire in packet order, on the stamped drain of #2225 (Part of #2168).
- Each item watcher's callback fires whole at its packet, in its handler's order (`0x5d94e1`-`0x5d9519`, `0x5d85ce`-`0x5d8606`, `0x5d8798`-`0x5d87c1`).
- One record answer's callbacks run in the order they were asked (`0x5d8321`).
- The loot window's steps and the item push share one lane: a waiting copier ends at its record's answer (`0x4c2afc`), and a waiting push shows there (`0x491ee0`).
- Two responses in one frame give each window its own LOOT_OPENED and LOOT_CLOSED.
- The owed close of #2230 still settles before the window opens.

`BAG_UPDATE` and `ITEM_LOCK_CHANGED` remain in the guard, with their reason. Goldens pin each interleaving against another packet's update. Built on macOS.

Part of #2168

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:45:54 +07:00
Sam Sung 716ffdd172 Guild roster, charters and tabards act as 1.12.1's do (#2235)
The guild roster, charters and tabard designer now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- `SetGuildRosterShowOffline` compares the integer it is given (`0x4d0f70`), and re-sorts only on a change with members held. The flag and the selection outlive a `/reload`.
- The away, busy and GM tags, overhead and in `GetGuildRosterInfo`, read the `CHAT_FLAG_*` globals (`0x5eca17`, `0x4d13ed`).
- `BuyGuildCharter`, `RenamePetition` and `SortGuildRoster` raise their own usage lines.
- `RenamePetition` with no charter open does nothing (`0x4f495e`).
- `ClosePetition` fires `PETITION_CLOSED` while the record is still readable (`0x4f402c`).

Tests pin each address. Built on macOS.

Fixes #1437
Fixes #371
Fixes #2124
Fixes #2123

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 18:24:34 +07:00
Sam Sung ff69fb08e8 A gather's owed loot close no longer shuts the window its response opens (#2234)
Since #2209, gathering a herb, mining a vein or opening a chest opened the loot window and closed it in the same frame. The gather cast's `SMSG_SPELL_GO` owes the loot close `0x48f200` from its precast tail (`0x60d0ee`-`0x60d10b`), and the reference runs it inside the GO. That close returns at once when no window is open (`0x48f23f`), so it never takes the window the following `SMSG_LOOT_RESPONSE` opens. benilla applied the owed close a packet late, in the drain, and shut the new window. A loot response now settles a close owed ahead of it first, at rest or on move start, before it opens its window. Tests pin both variants, a close owed after the response, and two responses in one drain. Built on macOS.

Fixes #2230

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 17:26:40 +07:00
Sam Sung b493903e54 Trainer, taxi and merchant windows act as 1.12.1's do (#2233)
The trainer, flight-master and merchant windows now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- **`IsTalentTrainer`** answers 1 with no trainer open (`0x4d8ed0`).
- **The trade-skill and learn-spell queries** read each service spell's effects (`0x4d9f70`).
- **`GetTrainerServiceAbilityReq`** counts a known higher rank as met (`0x4d96e0`).
- **Trainer rows** sort as the client's one persistent list, with a case-sensitive compare. The order is pinned against the client's own sort, run in benilla-re's emulator.
- **`CloseTaxiMap` and `TakeTaxiNode`** act in the call. A map with fewer than two known nodes shows `ERR_TAXINOPATHS` and opens nothing (`0x5ecf39`).
- **Buying an item a quest needs** shows its progress line (`0x5dced0`).
- **The merchant's sale and drop verbs** send as `0x4fb760` does.

Tests pin each address. Built on macOS.

Fixes #1272
Fixes #213
Fixes #212
Fixes #166
Fixes #2148
Fixes #2147
Fixes #1804

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 16:22:46 +07:00
Sam Sung 601438a77c Quest windows, log and gossip act as 1.12.1's do (#2229)
The quest windows, the quest log and gossip now follow 1.12.1, and their verbs act inside their call (Part of #2202).
- The quest log is ordered by the client's two `qsort` passes (`0x4de751`, `0x4dea63`), and collapse is keyed by group id.
- `GetAbandonQuestItems` lists the quest items an abandon destroys.
- `GetQuestLogRewardMoney` adds the level-60 money.
- `GetQuestReward` latches, so a double click sends one choice.
- An elite quest shows its tutorial.
- Rewards the player can't use read unusable, and an item of inventory type 0 reads quality -1.
- A coded gossip option asks for its code (`GOSSIP_ENTER_CODE`).
- A close drops the NPC inside its call (`0x501201`, `0x4e1fcc`).
- The gossip list reads at most 15 options (`0x4e2722`).

Tests pin each address, and a golden pins the code's bytes. Built on macOS.

Fixes #2194
Fixes #1508
Fixes #1507
Fixes #386
Fixes #385
Fixes #29
Fixes #1513

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:47:40 +07:00
Sam Sung 0367a2c218 Cast, form, group and auction events fire in packet order (#2226)
More producers now fire their events in packet order, from the stamped drain of #2225 (Part of #2168).
- **Cast reports** fire at their packet's place.
- **Form and combo-point changes** come from their field watchers (`0x5dda18`, `0x5dd9d0`), so `UPDATE_BONUS_ACTIONBAR` fires on every form change (`0x4e4fc0`).
- **The group list** fires its events and lines in the handler's order (`0x5e6c19`-`0x5e6e52`).
- **The auction handlers'** lines and list steps fire at their packets. A page waiting on its items sorts at the answer.
- **Each aura-applications byte** has its own watcher (`0x60423b`).
- **The trade offer** is trimmed inside the coinage watcher, at its packet (`0x5ddf30`).

The guard lists every producer still left, with its reason. Goldens pin each interleaving against another packet's line. Built on macOS.

Part of #2168

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 14:07:23 +07:00
Sam Sung c18315fdcb UI events fire in packet order from one stamped drain (#2225)
In 1.12.1 each packet's handler runs whole, and its events fire inside it in arrival order (`0x5384d0`, `0x537aa0`). benilla fired each producer's queue in system order instead. This lays the foundation:
- One stamp clock orders every queued event.
- A single drain fires them by stamp, with nested items placed straight after their cause.
- Chat lines and field notices are its first two lanes.
- A line waiting on a player name, creature record or item fires at its answer's place (`0x49ccc0`, `0x6294b0`).
- A notice's own chat line fires `CHAT_MSG_SYSTEM` in place, as `DisplayError` does (`0x496720` → `0x49a870`).

A guard test lists every handler-filled queue not yet on a lane, so the rest of #2168 can follow. A depth cap on nesting is a written `Deviation:` until #2202 finishes. Goldens pin the order. Built on macOS.

Part of #2168
Fixes #2173

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 09:38:05 +07:00
Sam Sung 2766b03b9a The trade window opens, links, enchants and accepts as 1.12.1 does (#2223)
The trade window now follows 1.12.1, and the trade verbs act inside their call (Part of #2202).
- The open leg folds held coins only for a trade started by a drop (`0x4bf5f1`), with one `PLAYER_TRADE_MONEY`.
- Bag, equipped and trade-slot links go through one path (`0x52ae00`), with the enchant and suffix.
- `ClickTargetTradeButton(7)` with an enchant asks before replacing one (`TRADE_REPLACE_ENCHANT`), then casts at the partner's slot.
- Dropping or clicking an item onto a player starts or fills a trade (`0x48d960`, `0x4925d0`), and a refused placement changes nothing.
- The accept packet carries the partner's status word.
- The accept and un-accept signals fire before their packets.
- The money events fire where `0x4bf2b0` fires them.

Wire goldens pin the new bodies. Built on macOS.

Fixes #277
Fixes #279
Fixes #280
Fixes #281
Fixes #88
Fixes #292

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:51:10 +07:00
Sam Sung 1958f4fe32 The /who search, list and chat lines follow 1.12.1's builder (#2221)
The /who search, its list and its chat lines now follow 1.12.1, and the who and LFG verbs act inside their call (Part of #2202).
- The filter is built token by token, as `0x5aebb0` does. Tags match a case-insensitive substring of the localized zone, race and class names, the walk stops at the fourth term, and an unclosed quote drops its token.
- The sort order resets at each world entry (`0x5adc50`).
- An id with no row reads `UNKNOWN`, in the list and in chat.
- The answer keeps at most 50 entries (`0x5adf87`), and the count line gets the raw count.
- `GetWhoInfo`'s bounds and miss tuple match `0x5ad71a` and `0x5ad836`.

Three `Deviation:` lines name the memory and platform quirks not copied. Built on macOS.

Fixes #39
Fixes #1329
Fixes #1114
Fixes #181
Fixes #200

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 08:28:00 +07:00
Sam Sung 0689a07471 The mailbox reads, lists and sends mail as 1.12.1 does (#2219)
The mailbox now follows 1.12.1's inbox and send code, and the mail verbs act inside their call (Part of #2202).
- Expired mail stays listed.
- Mail from an ignored player is dropped on type 0 only.
- Delete and return use the reference's tests, and the postage sum matches.
- Reply follows `0x4aefa5`.
- `GetInboxText` reads the mail's template id, and `CMSG_MAIL_CREATE_TEXT_ITEM` now carries it, pinned byte for byte.
- A row with no paper takes the first usable one, in `0x4ada90`'s order.
- The send slot refuses as `0x4adf38` does.
- The profanity filter masks subjects and bodies.
- The header icons and non-player sender names come from the bytes.
- Keeping the wire id where the reference crashes on a null paper is a written `Deviation:`.

Tests pin each address. Built on macOS.

Fixes #1241
Fixes #1235
Fixes #247
Fixes #240
Fixes #55
Fixes #140

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 06:21:55 +07:00
Sam Sung 85ba44b4d5 Party and raid verbs act in their call with 1.12.1's gates and lines (#2218)
The party and raid verbs now act inside their call, as 1.12.1's do (Part of #2202). Their gates and lines now match the reference:
- `ConfirmReadyCheck` sends nothing once the deadline has passed (`0x4bb2f0`), and sends its answer as one byte (`0x4bc0bf`).
- `DoReadyCheck` works for the leader only, refuses while a check runs, and shows its start line in place.
- A ready check survives a `/reload`. Only the leader's roster leg closes it (`0x4bac5a`-`0x4bacb4`).
- `UninviteByName` uses its usage line (`0x48a610`), and so do `InviteByName` and the promote verbs.
- Name lookups answer their waits in the order they were asked (`0x55f426`).
- A groupmate's death shows `ERR_PLAYER_DIED_S` (`0x4e7f70`).
- A `/reload` seeds the group without firing group events.
- An invite fires its event, then shows its line (`0x5e6747`, `0x5e6752`).

Tests pin each address. Built on macOS.

Fixes #2122
Fixes #1436
Fixes #2200
Fixes #2183
Fixes #2208
Fixes #2022

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 04:52:46 +07:00
Sam Sung b888674377 The friends and ignore lists follow 1.12.1's FriendList (#2216)
The friends and ignore lists went wrong:
- Names showed blank, online friends came unsorted, and the list announced itself early.
- Several status results printed the wrong line or kept the wrong rows.
- The ignore list had no 25-slot cap, and an ignored player's emote still printed.

The reference:
- The list handler (`0x5ae350`) names a slot only from the cache, and signals at a pending count of 0.
- The CRT `qsort` with comparators `0x5ae870` and `0x5ae940` sorts the lists.
- Statuses follow the table at `0x5ace1c`, and refused names send their delete (`0x5acec8`, `0x5acf8c`).
- `SMSG_TEXT_EMOTE` drops an ignored sender (`0x49dc3d`).

The friend and ignore verbs act inside their call, and listeners see the list as signalled. Each fact is tested against its address.

Part of #2202

Fixes #133
Fixes #137
Fixes #138
Fixes #1109
Fixes #1112
Fixes #1113
Fixes #1668
Fixes #1769
Fixes #1328
Fixes #765

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 03:20:51 +07:00
Sam Sung 98e0a93496 Auction commands, results and notices act as 1.12.1's do (#2213)
Auction commands went wrong:
- A bid or cancel was sent with no local check.
- Results showed the wrong line or none.
- Notices re-queried instead of editing the lists, and the list verbs sent unarmed.
- Outbid auctions never reached the Bids tab.

The reference:
- `PlaceAuctionBid` and `CancelAuction` refuse in the call (`0x4cf762`-`0x4cf8a8`, `0x4d0338`).
- The result table `0x4cc7b4` maps 1 through `0x622630`, 4 to `0x17` and 13 to `0x1be`, and 5 always shows `0x175`.
- Notices remove through `0x4cdfe0`, and patches signal one event each, in the handler.
- The one-shot flags `0xb7263c`/`0xb72640` gate the list verbs.

Every auction verb but the close now acts inside its call. Each fact is tested against its address.

Part of #2202

Fixes #47
Fixes #48
Fixes #252
Fixes #1525
Fixes #1526
Fixes #1527
Fixes #1533
Fixes #2195
Fixes #2196
Fixes #2197
Fixes #2198

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 01:43:58 +07:00
Sam Sung 521e9f22fe Deaths, auto-repeat stops and attack stops follow 1.12.1 (#2211)
After a death or a stop:
- A cast at a unit that died ran on, and Auto Shot kept firing after its target's death.
- Stopping a repeat left its kit running and fired its events a frame late.
- An attack restarted within a round trip of a stop sent no swing.
- A possessed unit's area melee ordered no attack, and its ground cursor measured from you.
- A game object's go drew a caster's kit.

The reference:
- `OnDied` (`0x605860`) cancels a cast whose target dies (`0x6058e0`), then deselects and stops the repeat (`0x605901`, `0x605906`).
- `StopAutoRepeatSpell` (`0x6ea080`) reaps, pops and signals where the key clears.
- The stop word `+0xc54` lets a start swing again (`0x5eccda`).
- A go's caster legs need a unit (`0x6e7f8b`).

Each fact is tested against its address.

Fixes #2177
Fixes #44
Fixes #2187
Fixes #2013
Fixes #2140
Fixes #2171
Fixes #2172
Fixes #2188

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:45:08 +07:00
Sam Sung aa25486d78 Loot verbs, packets and auto-loot act where 1.12.1 acts (#2209)
Loot went wrong:
- Takes, closes and master gives queued, then ran in a fixed order.
- The last row closed a frame late.
- Auto-loot opened before it swept, read Shift when the loot arrived, and looted over an open window.
- `GetNumLootItems` counted taken rows, and a response for an object the client lacked opened a window.

The reference:
- `LootSlot` is the confirm form (`0x4c2e70`). A row click takes after its `<OnClick>` (`0x4c1867`). `CloseLoot` zeroes only the source (`0x4c1da0`).
- `SMSG_LOOT_REMOVED` closes an empty window in the packet (`0x4c1f8d`).
- The sweep runs before `LOOT_OPENED` (`0x4c1ed3`, `0x4c2240`). Each interaction stores the byte (`0x5df460`), and `CanLootNow` refuses an open session (`0x5ec1e1`).
- A missing object shows `ERR_LOOT_DIDNT_KILL` (`0x5ebb3f`).

Each fact is tested against its address, with the stock `LootFrame` for the replacing window.

Part of #2202

Fixes #2167
Fixes #2181
Fixes #2180
Fixes #2190
Fixes #1228
Fixes #784
Fixes #2189
Fixes #72

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 00:10:48 +07:00
Sam Sung 784715dc36 Chat channels take, free and name their slots as 1.12.1 does (#2204)
Chat channels went wrong:
- An eleventh join printed nothing, and only `YOU_LEFT` freed a slot.
- Lookups by number found suspended and pending channels.
- Admin verbs sent a number as the name.
- Defense broadcasts reached unconfirmed channels.
- Trade outside a city took "City", and zone channels answered full names.
- `/chatlist` printed a line of its own.

The reference:
- A join takes its slot when sent (`0x49edf3`), and refusals free it (`0x49c446`). A freed slot keeps its row id, so the walk skips it (`0x49bef0`).
- Number lookups take confirmed slots only. Admin verbs resolve the slot (`0x49e880`).
- The city word needs flag 0x20 and a city (`0x49a2d0`).

Channel verbs, `DoEmote`, `UninviteByName`, `SendAddonMessage` and `ListChannels` now act inside their call. Each fact is tested against its address.

Part of #2202

Fixes #276
Fixes #2169
Fixes #229
Fixes #2159
Fixes #225
Fixes #223
Fixes #230
Fixes #231

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 21:31:16 +07:00
Sam Sung ef3d591b7f The selection holds a guid, and the token verbs reach any unit (#2199)
Targeting went wrong for units the client does not hold:
- A groupmate out of range could not be targeted.
- Assist with nothing to assist was silent.
- `StartDuelUnit`, `SetRaidTarget` and `InviteToParty` ignored most tokens.

The reference:
- `SetSelection` (`0x493540`) stores a guid whose object is not held (`0x4935c8`), and `TargetUnit` selects a group member by guid (`0x489a64`).
- The roster tears that selection down for an offline seat (`0x4e845a`), a departed member (`0x5e6df7`) and a lost pet (`0x5e2719`).
- A guid-only select plays `igCharacterSelect` (`0x4936f7`). A long-range spell at it sends untargeted (`0x6e545b`).
- Assist shows `ERR_GENERIC_NO_TARGET` (`0x489c0e`), and the token verbs resolve any token (`0x515970`).

Every reader of the selection now reads the guid: the target frame, the cast binder, the swing, the plates and the portrait. Each fact is tested against its address.

Fixes #1965
Fixes #1234
Fixes #322
Fixes #174
Fixes #199
Fixes #2178
Fixes #2179

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:49:55 +07:00
Sam Sung 591358af1b Auction lists sort, count down and refuse sells as 1.12.1's do (#2193)
Auction lists went wrong:
- They kept the server's order until a header click, and re-sorted every frame.
- Time Left, High Bidder, Lvl and Seller sorted on the wrong keys, and names matched case.
- Time left froze at arrival, and expired rows stayed.
- The sell slot took items 1.12.1 refuses.

The reference:
- World entry seeds eight-key sort stacks (`0x4cc236`). A page sorts once, when its last item record lands (`0x4cc9e8`).
- The comparator `0x4cd940` and the CRT `qsort` (`0x73f727`) order the rows, ties included.
- Time left counts down from the deadline, and rows 10 s past it leave the lists (`0x4cdfe0`).
- The sell slot refuses in `0x4ce362`'s order.

benilla now has one CRT `qsort`, which the frame batches, macros, guild roster and addon list share. Each fact is tested against its address.

Fixes #260
Fixes #428
Fixes #253
Fixes #254
Fixes #255
Fixes #256
Fixes #1539
Fixes #258
Fixes #1524
Fixes #2191

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:18:37 +07:00
Sam Sung 9f683d74cc Spell packets, cancels and the cast slot follow 1.12.1, with no timers (#2186)
Spell packets and cast state went wrong:
- A start or go with no record, or a non-unit's start, drew a cast.
- An enemy casting at you never became your target.
- Cooldown packets for other guids were dropped.
- Esc and move cancels reaped the wrong spell.
- Timers freed the cast slot.
- Spell state outlived a relog.
- Openers were scanned by id.

The reference:
- Both handlers exit first (`0x6e7876`, `0x6e7cd7`).
- A start aimed at you selects its caster (`0x6e799c`).
- Only the pet's guid routes to the pet (`0x6e9696`).
- The cancel reaps the record's spell (`0x6e4a8e`).
- No timer frees the slot; an inventory refusal cancels its item's cast (`0x5e3ab3`).
- World entry zeroes the spell state (`0x6e7150`).
- Openers keep the server's order (`0x5f83d0`).

Hold stops are now keyed by spell (`0x614167`). Each fact is tested against its address.

Fixes #2162
Fixes #1302
Fixes #1292
Fixes #1301
Fixes #2163
Fixes #2025
Fixes #1327
Fixes #293

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:38:25 +07:00
Sam Sung 86ef431dec Verbs apply inside their call, and packet events fire in wire order (#2184)
A verb called from Lua, or a packet's event, applied a frame later, after the call or handler had returned. 1.12.1 applies each verb inside its call and fires each event at its packet.

What changed:
- Verbs apply inside the call: targeting, item picks, refused casts' log lines.
- A packet's chat items and field notices fire in wire order.
- Combat, unghost and death-tutorial events fire at their packets.
- Signals raised while something waits are replayed with the lock and cursor state they had.
- Loot picks and bind confirms are one call-ordered queue. `LOOT_BIND_CONFIRM` fires before its slot is stored (`0x4c290f`, `0x4c2917`).
- A forked script no longer panics on a lend, and two comments now match the reference.

Each order is tested against its address.

Fixes #1993
Fixes #2045
Fixes #2051
Fixes #1419
Fixes #2016
Fixes #1987
Fixes #1237
Fixes #2139
Fixes #797
Fixes #2094

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 16:20:34 +07:00
Sam Sung 802ecec668 Party and raid roster lines, getters and verbs follow 1.12.1 (#2182)
The party and raid roster answered and sent where 1.12.1 does not:
- Join and leave lines printed for names the cache did not hold.
- A raid joined with no roster never asked for its icons.
- The loot getters needed a group.
- `GetPartyLeaderIndex` answered 0 after a list with no leader.
- The subgroup verbs sent for a held subgroup or an uncached name.
- `UninviteFromParty` and `PromoteToPartyLeader` took only party1-4.

The reference:
- The party leave line asks the cache with no callback (`0x5e6d15`).
- Raid lines wait on theirs (`0x4bacd0`, `0x4bad30`).
- A first raid list sends `MSG_RAID_TARGET_UPDATE` 0xff (`0x4babf4`).
- The getters read the stored cells.
- The leader cell is zeroed by every list (`0x4e85b5`).
- Both verbs resolve any token (`0x515970`).

What changed: the roster follows each of those. Each is tested against its address.

Fixes #2000
Fixes #2001
Fixes #2003
Fixes #2086
Fixes #2087
Fixes #2088
Fixes #2089

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 15:52:07 +07:00
Sam Sung 3387c8225d Unit getters read the unit they name; item events fire for every player (#2176)
The unit getters answered for the player alone:
- Stat functions gave zeros past player and pet.
- `GetGuildInfo` failed on chained tokens.
- A group member out of view had no race, class or sex.
- `UnitRangedDamage` ignored the weapon's school.
- `UnitIsUnit` resolved equal strings first.
- `UNIT_INVENTORY_CHANGED` fired only for the player.
- Drunk thresholds printed nothing and left `UnitLevel` alone.
- `CanInspect` answered nil.

The reference:
- `UnitIsUnit` compares the strings first.
- The getters read the named unit's fields and the name cache.
- The visible-item watch (`0x51bc3c`) signals for every player holding a token.
- The drunk watcher (`0x5e2a90`) prints and lowers the level.

What changed: each getter reads the unit its token names. The item watches, the ammo callback (`0x4c84f0`) and the inspect view signal where the reference does.

Each fact is tested against its address.

Fixes #1544
Fixes #718
Fixes #1239
Fixes #1232
Fixes #2005
Fixes #169
Fixes #1408
Fixes #1157
Fixes #1409

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 13:41:28 +07:00
Sam Sung f6d2d44df8 Pet casts, CastSpellByName and two chat verbs run as 1.12.1 runs them (#2170)
A possessed unit's spell and a pet spell with AttributesEx4 0x20 went out as CMSG_PET_ACTION. `CastSpellByName` never found pet spells and misparsed names. `SendChatMessage` sent `%t` and `%n` unexpanded and passed any `|` escape. `RandomRoll` sent any arguments.

The reference:
- `CastSpellByName` cuts the name and rank at the first `(` and `)` and falls back to the pet's book (0x4b3950).
- A unit caster runs the cast ladder, picking its own target (0x6e4ef4) and ordering Attack after the send (0x6131a0).
- `SendChatMessage` expands the tokens and raises on a bad escape inside its call (0x49f1e0).
- `RandomRoll` checks its arguments first (0x48c7b0).

What changed:
- A `CMSG_PET_CAST_SPELL` writer with byte-exact goldens.
- The unit caster's ladder covers its pick, autoSelfCast and the cursor's binds.
- Both chat verbs now apply inside their calls.
- The channel slots follow every step.

Each fact is tested against its address.

Fixes #639
Fixes #168
Fixes #406
Fixes #1331
Fixes #1332
Fixes #1531

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 12:43:31 +07:00
Sam Sung 47db337f09 Spell cooldowns insert and fire at 1.12.1's sites (#2166)
Cooldown events fired on each record insert. 1.12.1 fires them at its flush sites (`0x4b31b0`). Several inserts also took values the reference doesn't.

- The insert (`0x6e12c0`) and StartCooldown (`0x6e2c60`) now fire nothing. Each packet and cast site flushes once.
- `SMSG_SPELL_COOLDOWN` timers go through op 11, the ranged pad and op 21 (`0x6e9460`).
- An item's game-object cooldown uses its block's category, else the spell's. It waits for the item record and inserts nothing for entry 0 (`0x6e8425`).
- Initial-spells cooldowns are held and back-dated through StartCooldown.
- A NOT_READY with argument 0, and every failed cast, clear the spell's records and flush.
- Bag, worn and tooltip queries ask for a missing item record. Its landing flushes (`0x6e2f70`).

Tests pin each flush site and insert value.

Fixes #2060
Fixes #679
Fixes #2061
Fixes #2063
Fixes #2064
Fixes #2066
Fixes #2076
Fixes #2077

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 10:42:40 +07:00
Sam Sung fa83536426 Sit, sheathe and attack start read 1.12.1's precast and refusals (#2164)
Sitting and Z skipped 1.12.1's refusals, and the cast-busy state they test was modelled as a ranged hold.

- The precast (`+0xc8c`, bit `0x400`) is a unit state of its own: set at the send for every spell with a visual (`0x60d020`), ended at its GO, a failure or a move (`0x60d040`), releasing loot as `0x60d0ee` does.
- Sitting (`0x5ed430`) and Z (`0x5eb480`) refuse in the reference's order, mounted through precasting; a sit closes loot and stops the attack (`0x5f0790`).
- A relic class's Z never reaches the ranged state (`0x611cf0`), and a morph keeps the committed sheath.
- Starting an attack aborts a ranged precast, and stops before the swing for Slam (`0x5ecb70`).
- Emotes wait out the busy bit.

Tests pin each refusal, end and tail.

Fixes #1249
Fixes #1590
Fixes #1591
Fixes #1603
Fixes #2084
Fixes #2137
Fixes #770
Fixes #2152

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 09:50:01 +07:00
andi79h-claude 4d6f9ecc8a The target-of-target rows only write their globals, as 1.12's options do (#2150)
1.12's options declare Show Target of Target as a bare uvar
(UIOptionsFrame.lua:65) and the mode is read on save
(UIOptionsFrame.lua:365); neither calls TargetofTarget_Update. The frame
follows from TargetFrame_OnUpdate and TargetofTarget_OnUpdate on the next
frame (TargetFrame.lua:492-501). Our rows called it as their applyFunc,
with the row as `this`; the function refreshes `this`'s debuffs
(RefreshBuffs(this, 0, "targettarget"), TargetFrame.lua:538), so with a
target's target up the switch raised "BuffFrame.lua:281: attempt to index
local 'debuffBorder' (a nil value)" (seen on a Linux client).

Verified: the_target_of_target_switch_leaves_the_frame_to_its_own_updates
(with a target's target up: no error, the frame shows after one tick on
and hides after one tick off) fails with the applyFunc put back, with
exactly that error, and passes without it; the options and tot_frame
tests pass on a deDE install; cargo test -p benilla-app --lib 5145,
clippy -D warnings, fmt. Built on macOS only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 07:23:38 +07:00
Sam Sung e568853aae Client-side cast gates refuse what 1.12.1 refuses before the send (#2156)
Several of 1.12.1's client-side cast gates were missing, so the server refused casts the client refuses itself:

- The caster validator (`0x6094f0`) now runs its rungs in the bytes' order. Indoors and outdoors are judged by the entity outdoors test (`0x6706f0`).
- The crowd-control exemption checks every aura type (`0x6e9ca0`).
- The binder stops at the first arm that holds, with that arm's guards (`0x6e5b40`).
- TryCast refuses three more cases:
  - ranged without ammo, which also greys the button;
  - ranged without a ranged weapon (`0x6e4e13`);
  - the while-looting effects (`0x6e4ca2`).
- A cast on cooldown with no target says not ready.

Tests pin each rung and the greying through the bar's own site.

The learn and tame rungs remain.

Part of #1202
Fixes #1581
Fixes #1586
Fixes #675
Fixes #1199
Fixes #1294
Fixes #2132

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 06:45:38 +07:00
Sam Sung 84adc39ad8 Taxi routes, fares and node verbs follow 1.12.1 (#2151)
The taxi map's routes, prices and node list differed from 1.12.1's.

- The search (`0x4dbce0`) weighs each hop by its flight path's points. It runs depth first over the map's slots, and keeps the first route on a tie. Gadgetzan to Sun Rock now flies through Freewind and Crossroads for 10s70c.
- A known node with no route is listed as NONE, with the start fare.
- The other faction's flight points are skipped by race (`0x4dbd2d`).
- `TakeTaxiNode` refuses a mounted player (0xb2), the current node (0xaa) and a node with no path (0xab).
- A closed map keeps its slots (`0x4dc0f0`).
- Out-of-range slots raise or answer INVALID, and the coordinate getters read bare numbers.

Tests replay the issue's routes over the shipped tables and pin each verb's refusal.

Fixes #204
Fixes #205
Fixes #1236
Fixes #1576
Fixes #1229

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:44:27 +07:00
Sam Sung 66b73cd6d4 Cast failures report and cancel as 1.12.1 does (#2149)
The cast-failure reporter skipped arms of 1.12.1's errorId chain and its pet text. A move also cancelled a cast from the wire word, not from the press.

- The player's chain now runs whole, in the bytes' order (`0x6e1aab`-`0x6e1c5f`). Every id, 0x2c included, formats through its key's live global (`0x4967d7`), and an empty line is still logged.
- Spells aimed at the pet show their `_PET` text (`0x6e1b1b`). An item-target refusal reads `ERR_INVALID_ITEM_TARGET`.
- The full-power line names the player's own power (`0x6e211f`). The pet's handler stays silent when it has no record or finds no name.
- A movement command press cancels the cast whoever drives (`0x515090`). Only a channel needs the player as the mover (`0x515180`).

Tests pin each arm, the pet lines and the cancel through the controller.

Fixes #2135
Fixes #1582
Fixes #1584
Fixes #1637
Fixes #1201
Fixes #2141
Fixes #2142

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 05:28:48 +07:00
Sam Sung fcb42ab40a Script calls apply inside their verb, in one ordered VM pass (#2143)
In 1.12.1 a verb's effect applies inside the call, whether from a click, `OnUpdate` or an event handler. Here, calls from `OnUpdate` and handlers queued until the next lend, so `TargetByName(name)` then `UnitName("target")` read the old target.

- Every system that runs Lua lends the call host around its body, in one ordered VM pass; the cross-entry queue is gone.
- Seams that changed units through `Commands` queue unit edits, flushed ahead of their readers, so sync points stay at 54 (main: 53).
- Late drains apply before the paint; a CVar or follow ask lands the same frame.
- The actionable ambiguity ceiling falls from 3079 to 1750.

Tests: an `OnUpdate` cast and a handler's cast arm inside the call, three same-frame drains, and a census that each schedule flushes after every edit user.

Lua run inside an engine turn still waits for the turn's end.

Part of #1993

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 03:43:48 +07:00
Sam Sung c6ecbcb048 Five comments cite the reference facts they name (#2134)
Five comments stated a wrong reference fact or named code that does not exist:

- `shown_pet_power_type` cited `UnitPowerType`'s member-record read (`0x5179d2`) as its pet leg, which is `0x5179ff`.
- `race_creature_type.rs` named a path that does not exist.
- `drag.rs` cited the unreached copies at `0x425666`/`0x425766`, where the live converter is `0x766f70`, called from `0x766103` and `0x7662f3`.
- `handler_prof` claimed an app-data refusal benilla-mlua does not have.
- `MacroView` cited the save format by file offset, where its address is `0x84cb60`, pushed at `0x4f0b61`.

The NVIDIA present `Deviation:` also gains where the fault is: the driver's own DXGI present, with NVIDIA's present layer disabled too.

Comments only, so no test changes.

Fixes #671
Fixes #676
Fixes #1106
Fixes #1991
Fixes #1245

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 02:21:31 +07:00
Sam Sung 3575b1238f Object updates run 1.12.1's passes, holds and create-hook asks (#2131)
The update ingest did not do what 1.12.1 does with the same bytes:
- It dropped the header flag.
- It released and destroyed held objects at once.
- It re-asked records on every create.
- It replayed enchant times at an item's write.
- It read character names uncapped.
- It ignored `SMSG_PLAYER_SKINNED`.

Reference: the header byte runs a first pass over guids with top nibble 6 or 8 (`0x4651c1`, `0x4660d8`). A held object is only marked release-pending (`0x464920`) until its hold drops (`0x468410`). Asks come from the create hooks alone (`0x5fb880`, `0x5f7d81`). Enchant times replay when the record applies (`0x5d85b7`). Names read with a 0x30 cap, and an overrun empties the list (`0x5b4523`). `PLAYER_SKINNED` fires with the byte's truth (`0x5e7d52`).

Changed: each follows these, and tests pin each. The faults the passes have on malformed packets are recorded, not copied.

Fixes #2053
Fixes #2052
Fixes #2054
Fixes #2055
Fixes #2083
Fixes #87

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 02:00:00 +07:00
Sam Sung 74c9430e40 TradeSkill order, selection, folds and the craft button follow 1.12.1 (#2130)
Four TradeSkill and Craft answers differed from 1.12.1:
- Recipes of one colour listed the lowest item level first.
- `SelectTradeSkill` on a header kept the old selection.
- The sub-class folds ignored a non-header row.
- `GetCraftButtonToken` always answered `CREATE`.

Reference: the recipe comparator returns +1 when the first row's ItemLevel is smaller, so the higher level sorts first (`0x4fd4e6`). `0x4fc9b0` stores 0 for a header or an out-of-range index. The folds raise unless `0x4fd650` finds a header, and below 1 they fold every group (`0x5000c0`, `0x500140`). `0x4f6c90` answers the word at the open craft type in the table at `0x84d0f4`: USE, TRAIN, DISGUISE, ENSCRIBE.

Changed: each follows these, and tests pin each.

Fixes #155
Fixes #1260
Fixes #1261
Fixes #876

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 01:44:25 +07:00
kalistoking 33d49c3494 Vsync on is a paced AutoNoVsync on NVIDIA Vulkan on Windows (maximize crashes the driver in Fifo) (#2058)
NVIDIA's Vulkan driver (610.88 seen) faults inside vkQueuePresentKHR on the first present after a
swapchain resize while the present mode is Fifo, FifoRelaxed or AutoVsync; a maximize is a resize.
Mailbox, AutoNoVsync and DX12 survive. present_choice picks the mode from vsync, the render
adapter's vendor and backend, the OS and $WOW_PRESENT; the pairing above gets AutoNoVsync with the
loop paced to the monitor's refresh rate, everything else keeps Fifo. $WOW_PRESENT=fifo restores
it, $WOW_NOVSYNC is unchanged. The boot window still starts on Fifo; apply_present_mode re-runs
when the adapter first appears and swaps the boot mode alone.

Built and unit-tested on Windows only; the cfg!(windows) arm is a value, not a cfg, so the other
platforms compile the same code.
2026-10-21 00:58:27 +07:00
Sam Sung 848ede6a4b Font objects, CopyFontObject and guild names follow 1.12.1 (#2127)
Three Lua surfaces answered unlike 1.12.1:
- A Font object's `[0]` was a number, so saved variables and `type(v[0])` guards treated fonts unlike every other object.
- `CopyFontObject` pushed the source's values and held them.
- Guild and charter names went to the server with no client name check.

Reference: the saved-variable writer reads `[0]` as a light userdata (`0x704623`). `0x7841d0` merges what the source holds, then holds nothing (`0x7841f4`) and follows the source's font. `0x6c9b70` runs the name validator with a space as its one extra character, the profanity lists on, and a 24-unit cap. `0x4f51ec` maps its codes to the refusal lines.

Changed: Fonts take the object id counter, and the loader's `parent=` tests the Frame type. A font node keeps its held word apart from its values. The guild check runs before the send. Tests pin each.

Fixes #1997
Fixes #1983
Fixes #1399

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 00:38:44 +07:00
Sam Sung 275feba882 Macros sort, hold ids and bind pet spells as 1.12.1's do (#2126)
Macros were held by index and kept in creation order:

- A sort or delete moved what a bar slot held.
- `EditMacro` trimmed and capped differently and returned nothing.
- `CastSpellByName` took the wrong quoted span.
- A macro casting a pet spell never bound the pet's book.

Reference: each scope sorts by the CRT `qsort` with `_strnicmp` (`0x73f727`, `0x4f0e10`). Slots hold the macro id, with bit 24 set for a character macro, and `DeleteMacro` clears every slot holding it (`0x4e5d10`). `EditMacro` returns the index after the re-sort. The quoted name ends at the next quote (`0x4efef0`). Names bind the player's book, then the pet's (`0x4b3bc0`).

Changed: the table, the slots, the cursor and the saved files follow these, and tests pin each. Bar slots an earlier build saved for character macros are rewritten once to the new ids.

Fixes #1502
Fixes #1505
Fixes #1187
Fixes #196
Fixes #197

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 00:23:45 +07:00
Sam Sung b3fbaa86d4 World clicks, attack starts and /target follow 1.12.1's gates (#2117)
Clicks and attack starts passed gates 1.12.1 refuses at:

- A right press with a cursor payload went on to the world.
- StartAttack ran without control or while pacified.
- A charmed player's right-click selected and interacted.
- Talk ignored shapeshift forms.
- A plate right-click while targeting skipped the interact.
- A `/target` miss said nothing.
- `/follow` read the cast slot.

Reference: `0x7662c0` runs the press pre-hook `0x492b50` before any hit-test, which drops a payload and consumes the press. StartAttack returns on `0x20000` or lost control (`0x5ecbaa`, `0x5ecbc0`) for every caller. The router tests `CHARMEDBY` first (`0x481f98`), which plates skip (`0x4949f0`). Talk tests form flag `0x8` (`0x5f0150`). `0x489dbd` picks the miss message, and `/follow` reads `UNIT_CHANNEL_SPELL` (`0x60ffb9`).

Changed: each gate follows these, and tests pin each.

Fixes #321
Fixes #381
Fixes #785
Fixes #1305
Fixes #1397
Fixes #52
Fixes #1326

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 23:31:37 +07:00
Sam Sung 70432f0566 TGA, spell text, MCNR and DBC readers read what 1.12.1 reads (#2106)
The readers differed from 1.12.1:

- A TGA's descriptor bit 4 mirrored it.
- A 32-bit TGA with no alpha bits drew with byte 3 as alpha.
- Spell-text slot digits 0 and 4-9 read a wrong slot and `$g`/`$l` parsed their own way.
- MCNR normals were named `x, z, y`.
- A DBC record of the wrong size was read.

Reference:

- No TGA read path tests bit 4.
- `0x5a37e0` admits truecolour only at 24 bits besides alpha.
- A refused texture draws opaque white (`0x44a260`) at sizes `0x4484ae` admits.
- Only '1'-'9' is a slot digit (`0x507827`), and `0x508180`/`0x5082a0` parse the selectors.
- MCNR is X, Y, Z (`0x6aff10`).
- The loader refuses any other record size (`0x545e36`).

Changed: each reader follows these; three TGA edge cases carry `Deviation:` lines. Three BigWigs ready-check icons now draw white, as in 1.12.1.

Fixes #1679
Fixes #1693
Fixes #1684
Fixes #710
Fixes #799
Fixes #15
Fixes #17
Fixes #2102

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 22:18:28 +07:00
Sam Sung 33067c38ac Console commands and CVar defaults follow 1.12.1's registrations (#2104)
Seven console commands were not registered: `port`, `worldport`, `setrawpos`, `whois`, `togglehelm`, `togglecloak` and `cameraClip`. Three CVar rows also left the reference: `SoundBufferSize` shipped 100, `WorldDetail` was marked as a 1.12 CVar, and `gamma` shipped `1.000000`.

Reference:
- `0x4049e0` registers the movement and toggle commands, `port` and `setrawpos` splitting with the comma (`0x82ef90`) and `worldport` without it (`0x82ecc8`).
- `whois` sends its name (`0x5adee0`).
- `cameraClip` flips or sets its word (`0x50ddc0`).
- `0x457520` registers `SoundBufferSize` 50 above 1500 MHz, which current x86 parts read, and `0x402d70` registers `gamma` as `1.0`.

Changed: each command is registered with its category and its sends, which have byte-exact goldens. `SoundBufferSize` and the mix-ahead ring run at 50, `WorldDetail` is benilla's own row, and `gamma` registers `1.0`. Tests pin each.

Fixes #1639
Fixes #1663
Fixes #1749
Fixes #1270
Fixes #1279
Fixes #1281

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 21:51:38 +07:00
Sam Sung 374556aef3 The game clock and its server messages follow 1.12.1 (#2097)
The game-speed, game-time update and game-time set messages were dropped. The login time packet also applied a malformed body and kept unset fields as 31 and 63.

Reference: each handler reads its body, refuses one not exactly its length with the "Malformed message" line, and decodes an all-ones field as -1 (`0x642b30`):

- **Set** steps back a minute and advances (`0x6434d0`), so an unset field lands at 00:01.
- **Sync** steps forward or banks a skip count (`0x6436c0`).
- **Frame tick** pays the skip (`0x6435a0`). It runs from world entry to logout (`0x401684`, `0x401f04`).
- **Rate** is clamped to [1/60, 60], NaN kept (`0x643820`).

Changed: the four bodies parse with exact-length goldens, and the clock is a port of the reference's, ticking only in the world. The day number uses UTC civil days, a written deviation from `mktime`. Tests pin each step.

Fixes #2056
Fixes #2082

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:46:30 +07:00
Sam Sung 44e4032a1f Combat-log lines take 1.12.1's types, names, XP forms and arguments (#2096)
Combat-log lines read wrong in nine ways:

- Your split damage and extra attacks were damage-typed.
- The XP line ignored the group float.
- An item-name wait dropped its line after 120 frames.
- Game objects and items printed "Unknown".
- An XP kill also printed a death line.
- Templates with unused or positional arguments were refused.
- Open-lock effects wrote nothing.

Reference: split form 2 (`0x62df92`) and the extra-attack line (`0x62b190`) type through the buff table unless the spell is hostile. The XP line compares `ftol(xp / float)` with `xp` (`0x637cd0`). The wait list never expires (`0xc4e208`). `0x6264e0` names by type mask. The XP packet marks the victim (`0x60a0e6`). `vsnoprintf` binds `%N$` and skips unused arguments (`0x669888`). `0x62dc10` writes the open-lock line.

Changed: each follows its formatter; one `fill_exact` serves both fills. Tests pin each.

Fixes #2067
Fixes #2073
Fixes #2068
Fixes #2071
Fixes #2039
Fixes #2041
Fixes #2044
Fixes #2043
Fixes #2046

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 20:26:40 +07:00
Sam Sung 914b44b42d Group leader tests, loot lines, kicks and subgroups follow 1.12.1 (#2091)
Four group behaviours were wrong:

- `IsRaidLeader` and `IsPartyLeader` misread a raid leader outside your subgroup.
- A loot method, master or threshold change printed no line.
- The uninvite menus always kicked by name.
- `SetRaidSubgroup` outside 1-8 did nothing.

Reference:

- Both leader tests compare the leader cell with your guid; only `IsPartyLeader` guards a zero cell (`0x4e9141`).
- The group list stores the loot cells and prints each change (`0x4e86f0`, jump table `0x4e880c`). The master line needs a cached name; the threshold reads `ITEM_QUALITY<n>_DESC`.
- `0x5e9510` kicks a held player by name and anything else with `CMSG_GROUP_UNINVITE_GUID`.
- `SetRaidSubgroup` raises its usage line first (`0x4bb9fe`).

Changed: the leader tests read the guids, the list stores and prints the loot cells, the kicks follow `0x5e9510`, and the subgroup is checked first. Tests pin each.

Fixes #141
Fixes #70
Fixes #267
Fixes #266

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:33:39 +07:00
Sam Sung b39d928f6f Action bar answers and events follow 1.12.1's producers (#2090)
The action bar answered and signalled where 1.12.1 does not:

- `IsActionInRange` measured distance before the target gates.
- `GetActionCooldown` answered enable 1 for slots that reach no cooldown query.
- `IsCurrentAction` held item slots against nothing.
- The cooldown and usable events fired off a button's own state.

Reference: `0x4e56f0` runs `0x6e4440`'s relation and implicit-target gates first. Enable starts 0 (`0x4e6f0d`) and only the query sets it (`0x6e1406`). The cast keeps its item's guid (`0xcecab0`). `BAG_UPDATE_COOLDOWN` comes from `0x4f93d0`'s callers, `ACTIONBAR_UPDATE_USABLE` from `0x4e5c00`'s four, and `SPELL_UPDATE_USABLE` from `0x4b31c0`'s fourteen. Placement signals only 213.

Changed: a usable cache refreshes at those producers. The push half runs before the unit snapshots and the events after them, so handlers read this frame's state, after the control edge. Tests pin each answer and the order.

Fixes #1528
Fixes #1532
Fixes #1537
Fixes #1596
Fixes #1597

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 19:16:13 +07:00
Sam Sung 58f3b05b98 Glue cleans realm names, seeds agreements once and flags new characters (#2085)
Four glue-screen behaviours were wrong.

- **Realm names.** The list showed realm names raw.
- **Agreement flags.** The terms-agreement flags were reseeded from the CVars on every glue VM.
- **New characters.** A new character's record kept level 0 and no new-character flag.
- **PreviewRider.** Its comment misstated the sheathe.

Reference: the realm list fill turns each of `"*/:<>?\|` into a space and cuts trailing spaces and dots (`0x5b2450`, `0x5b24b0`). The agreement flags are seeded once per process. A level-0 record stores level 1 and sets `+0x10a` (tested at `0x46c203`), and that character enters at its race's intro eye with no loading tip. The select build holds each weapon in hand (`0x472c8a`, `0x47a15d`).

Changed: names are cleaned at the fill, the flags live in one shared set, and the roster stores the flag and looks up the intro eye at the pick. Tests pin each.

Fixes #1215
Fixes #1317
Fixes #1318
Fixes #1217

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

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-20 18:59:32 +07:00