Commit Graph
688 Commits
Author SHA1 Message Date
chairmanpow e01ee9fcc1 ui: OnHyperlinkEnter leaves the never-fired raise list
It is fired now (5be43004), so the guard that a script name raises only while nothing fires it must drop it.
2026-10-07 00:20:32 +01:00
chairmanpow 779510bdb0 ui: register UnitCanAssist, the CanAssist gate callers like ShaguTweaks rely on
UnitCanAssist (0x516bb0) was a nil global, so ShaguTweaks libpredict raised whenever UseAction ran. It answers the player's CanAssist verdict: 1 for a unit whose reaction toward the player is friendly (5 and up), or for itself, else nil, like UnitCanAttack's shape.
2026-10-07 00:20:28 +01:00
chairmanpow ad8f6cce43 ui: GetWhoInfo answers a 0 level on a miss, so GetDifficultyColor cannot raise
A who-list miss pushed six nils, but the reference shape is (nil,nil,number,nil,nil,nil): the level of an empty who slot is 0. ShaguTweaks colors every WhoList_Update slot and calls GetDifficultyColor(level) unguarded, which does level - UnitLevel("player") and raised on the nil.
2026-10-07 00:20:23 +01:00
chairmanpow 482a983072 fix: battleground aura traps keep their inverted faction term, so flags capture
Arathi Basin's visual-only aura GameObjects (entry 180102, type 6 TRAP) were offered inside a battleground because the whole shared gate was skipped, so they won the pick over the node banner and the CMSG_GAMEOBJ_USE landed on a no-op trap (the server only played its despawn anim). The TRAP inverse is not a team marker, so it still gates: a friendly or neutral aura trap stays at priority 0, the banner (BUTTON/GOOBER/CHEST) wins, and a neutral node captures.
2026-10-06 23:58:37 +01:00
chairmanpow 5be4300401 ui: OnHyperlinkEnter/OnHyperlinkLeave fire as the cursor crosses a link span
SetScript raised "unsupported script 'OnHyperlinkEnter'" for chat-link addons, because only OnHyperlinkClick was registered. The reference fires the enter/leave pair on the owning frame with (link, text) as the hover crosses a link span, so the kinds are now accepted and fired from the pointer's move and when a hovered frame hides.
2026-10-06 23:58:32 +01:00
chairmanpow cb412c22c6 fix: silence the attach build's entity commands against a same-frame teardown
A unit destroyed or leaving range on the frame its visual is built is despawned
by apply_despawn_fade while attach_entity_visuals' plain insert commands are
still queued. Bevy's despawn only warns on a dead target, but insert and
insert_if_new panic, so the client aborted inside a battleground's stream churn.
Every entity-rooted write in attach_entity_visuals, setup_skinned_instance and
arm_sequence_clock now uses try_insert / try_insert_if_new, and the mount child's
MountChild does too, matching apply_despawn_fade's own try_despawn/try_insert.
2026-10-06 19:11:39 +01:00
chairmanpow 2796a5d78d fix: the ground clamp skips a mount child, so mounted players stop sinking
A mount child is an EntityKind::Unit but rides its host, so its Transform is
host-local (0,0,0). ground_clamp_creatures cast a world ray from that origin and
wrote the world floor height into the child's local Y, dropping the mount into
the terrain; in Alterac Valley the map origin sits on live ground, so mounted
players showed half under the map. ground_clamp_creatures and
mark_swimming_creatures now exclude MountBody, as every other mount-aware system
already does.

Fixes #4
2026-10-06 19:11:34 +01:00
chairmanpow d729fc1145 ui: define the missing ArenaTeamTemplate so Turtle's inspect arena buttons load 2026-10-06 17:37:51 +01:00
chairmanpow d6fe11800b fix: race_pvp_team maps Turtle's Goblin/Blood Elf, so a High Elf's PvP rank shows 2026-10-06 17:37:51 +01:00
chairmanpow be808eb7e6 fix: battleground objectives use the plain-use path and skip the client gate 2026-10-06 17:05:56 +01:00
chairmanpow 7e043060eb auction: GetAuctionSellItemInfo answers maxStack and link, as the patched 1.12 Lua reads 2026-10-06 17:05:56 +01:00
chairmanpow e42270f762 ui: seed the auction class tree before addons load, so Tmog's ItemDB builds 2026-10-06 17:05:56 +01:00
chairmanpow 7c1d92a9d4 upstream: finish the picked commits' build — drop WorldWriter::sent, add set_enemy_plates 2026-10-06 16:50:30 +01:00
chairmanpow 95738c891d fix: battleground GameObjects skip the faction gate, so cross-faction flags stay clickable 2026-10-06 16:50:30 +01:00
Sam SungandClaude Opus 5.5 d2e357c2ec The meeting-stone query runs at every world entry, as 1.12.1 sends it (#810)
The meeting-stone status query (`CMSG 0x296`) and its text reset ran once per UI VM, and the text drop only on leaving the world. So after a cross-map worldport benilla kept the old text and never asked again.

In 1.12.1 the world-enter cascade's meeting-stone init (`0x490a14` → `0x4c9f40`) resets the text to `UNKNOWN` and sends `0x296`, right after the battlefield init, on every pass: login, every cross-map worldport and every `/reload`. The leave-world sweep `0x490a80` drops the text (`0x490b2f` → `0x4c9f80`) on a worldport, a `/reload` and leaving the world.

The enter leg now runs on the world-enter cascade after the battlefield status. The sweep is one edge, written whenever its latch is spent, and the drop reads it. Tests cover the cascade, a worldport, a reload and the schedule order; a live trace shows `0x296` after `0x2D3` and its reply at login, on `ReloadUI()` and after `.tele`.

Fixes #99

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 16:34:16 +01:00
Sam SungandClaude Opus 5.5 b49d2e5408 The out trace logs every world send, the login handshake's included (#806)
The `out` trace tag logged only what the long-lived writer sent, drained after each command. Everything the session sends before the reader and writer split never reached it: `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM`, `CMSG_PLAYER_LOGIN`, character create and delete, the cinematic ack and logout. So a login trace could not show where `CMSG_PLAYER_LOGIN` sits against `SMSG_LOGIN_VERIFY_WORLD`, which wire-timing work needs.

The world socket has one write path, `send_packet`. It now reports each packet to an observer right after the write, and the app installs the trace there only when the `out` tag is on. The writer's sent-log and its drains are gone; the line format is unchanged.

A protocol test over the fake server checks that the handshake, a pre-split send and a writer send are all observed, in order. A live login trace now opens with `CMSG_AUTH_SESSION`, `CMSG_CHAR_ENUM` and `CMSG_PLAYER_LOGIN`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-06 16:33:51 +01:00
21024ef3f5 World-enter queries wait for the player's create, as 1.12.1 sends them (#801)
At login benilla sent `CMSG_SET_ACTIVE_MOVER`, `CMSG_QUERY_TIME`, `MSG_QUERY_NEXT_MAIL_TIME` and `CMSG_BATTLEFIELD_STATUS` right after `CMSG_PLAYER_LOGIN`, before the server had seated the player. cmangos drops them; after a worldport or `/reload` the time and mail queries never went again.

In 1.12.1 all four follow the local player's own create (`0x5dea50`): `SetActiveMover` (`0x6006e0`), then the world-enter cascade (`0x5deb60 call 0x4908c0`), whose inits send `0x1CE`, `0x284` and `0x2D3` in that order. A cross-map worldport re-creates the player and takes the same route; `/reload` re-enters the cascade alone (`0x490168`).

The four now ride the self create, in that order, and `/reload` re-sends the three. Tests cover login, the create, a worldport, the schedule order and the reload; a live trace shows the order at login, after `.tele` and on `ReloadUI()`.

Fixes #789
Fixes #100

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Elias Miettinen <eliaspmiettinen@gmail.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-06 16:33:24 +01:00
eddea59b21 Unit tooltips title the unit with its PvP name, as 1.12.1 does (#802)
A unit tooltip's title read the bare name where 1.12.1 decorates it.

The tooltip calls the name builder `0x609370` itself (`0x52a1ab`, or `0x52a1db` when there is no second part) with the decoration flag a literal `1`, the same call `UnitPVPName` makes, so no CVar gates it. With the flag on, a ranked player reads `UNIT_PVP_NAME` of the gendered `PVP_RANK_<rank>_<team>` and the name, a city protector's `PVP_MEDAL<n>` follows on a second line (`0x6093ef`), and a civilian kill (`0x612550`) reads the `PVP_RANK_CIVILIAN` prefix (`0x609461`).

The title now runs the builder `UnitPVPName` already uses. The test covers rank, gender, medal, civilian, an unranked player and a creature; an on-demand `ui-tooltip-rank` capture shows a ranked player's tooltip.

Fixes #794

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-06 16:33:02 +01:00
Mathih13andClaude Opus 5.5 d4c7081ed2 fix: TargetUnit("npc") selects the interaction NPC, as 1.12.1 does (#800)
`TargetUnit("npc")`, `AssistUnit("npc")` and `SpellTargetUnit("npc")` did nothing with a merchant, gossip or quest window open: the app's unit-token resolver answered nobody for `npc`.

In 1.12.1 the resolver (`0x515970`) matches `npc` as a whole token (`0x515bec`, an unbounded compare, so `npctarget` stays unknown) and takes the interaction NPC's guid `[0xb4e2d0]`, which `0x4930d0` writes when the window opens.

`npc` now resolves to the held unit `InteractNpc` names. Tests cover the three verbs, a closed window and a streamed-out NPC, and `/script TargetUnit("npc")` typed into the stock chat box.

Fixes #707

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-06 16:32:56 +01:00
198b6cd894 fix: overhead names carry the player's PvP title, as 1.12.1 does (#792)
- capture: a name-rank scenario shoots a ranked player's overhead name line
- Overhead names carry the player's PvP title under UnitNamePlayerPVPTitle, as 1.12.1 does (#550)
- review: bundle the rank-title inputs; doc the snapshot and rank-19 limits
- nameplates: read rank strings per VM; test the composed name line
- pvp: correct the rank-title docs; convert the credit team without a cast
- Fix the titles row comment; name-rank reuses WOLF_POS, cites :900
- rank strings: the honor feed reads them off the VM; nameplates only reads the value

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-06 16:31:56 +01:00
chairmanpow ed0c0b9972 Merge pull request 'Normalize existing files for treefmt' (#11) from caniko/benilla-octo:feat/treefmt-baseline into main
Reviewed-on: https://codeberg.org/chairmanpow/benilla-octo/pulls/11
2026-10-05 15:11:20 +02:00
Can H. Tartanoglu eecbf04d6e Normalize existing sources and documentation with treefmt
Apply the formatter baseline needed by the generated CI gate.

Verified the full treefmt check with vendored sources and generated MAP.md excluded. Parsed formatted Cargo manifests and compared their values with HEAD; all are identical.
2026-10-05 00:31:21 +02:00
chairmanpow 36c9dc2aaa ui: load TurtleWoW's Custom_Merchant frame, so the armoury's PvP gear opens instead of the trigger text 2026-10-04 16:08:45 +01:00
chairmanpow 8613d99629 perf: Linux per-thread CPU table; main_in and faults print without it 2026-10-04 15:59:43 +01:00
chairmanpow ca662f816f world: WOW_SYSTEM_TIMING times systems via bevy's trace spans, once a second 2026-10-04 15:59:42 +01:00
chairmanpow 783a8ba951 ui: define TurtleWoW's sizeof so Transmog/Shop windows do not raise on nil 2026-10-04 15:59:40 +01:00
e1272ff436 fix: level-up gains read signed, so a loss prints -2, as 1.12.1 does (#791)
- Change `LevelUpInfo` to signed to match vanilla behavior
- Add parse test for negative values in SMSG_LEVELUP_INFO, update comments
- grammar
- level-up gains: cite the %d fire and vmangos's wrapped deltas; pin the reported line

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Sam <samuv.eth@gmail.com>
2026-10-04 15:48:21 +01:00
HEOJUNFOandClaude Opus 5.5 efbf14a22a Light bands with out-of-order keys interpolate as a cycle, as in 1.12.1 (#768)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:48:21 +01:00
Sam SungandClaude Opus 5.5 e12ca6769c /wave <name> targets the named player; two DoEmote gates follow 1.12.1 (#769)
Three ways `DoEmote` and the chat talk gesture differed from 1.12.1:

- `/wave Bob` ignored the name and targeted the selection. 1.12.1 resolves a name that does not start with `%` as a player, at any range (`0x49fdb1`-`0x49fdf0`, `0x493aa0`), and sends the emote untargeted on a miss. `DoEmote(token, name)` from Lua does the same.
- A polymorphed, stunned or sleeping speaker still played the talk gesture. `UNIT_FIELD_FLAGS 0x20000000` suppresses it when the sender's name is cached (`0x49d7f7`).
- `DoEmote` sent while our unit was possessed (`0x5ef57e`). A chat-only emote now reads `Emotes.dbc` row 0's flags, so it is refused while asleep or dead (`0x5ef5b1`, `0x47db8e`).

Tests cover the name, miss, `%t` and self cases, the gesture gate, and both `DoEmote` gates against the install's row 0.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:48:21 +01:00
Sam SungandClaude Opus 5.5 212108cf86 Emote routing follows 1.12.1's upper-body and mount tables (#767)
One-shot emotes were routed by a hand-made class table that wrongly let `/pray` (75), the `/beg` family (79), and 126, 127, 131, 132 and 135 play upper-body. A mounted rider played every id on its upper body.

1.12.1 reads two byte tables:
- CLASS_A (`0x5fed90`): the ids that may play on the key bone.
- The mount set (`0x5fec80`): what goes to the mount model while mounted (`0x5fe7b5`..`0x5fe803`). Anything else plays nowhere.

What changed:
- Both tables now match the bytes, and a mount-set id is forwarded to the mount child.
- Unmounted `/eat` (61) always plays upper-body (`0x5fe834`).
- A model with no split bone arms nothing for an upper-body play (`0x5fdcc4`).

Tests pin both tables for every id 0..300, plus the mounted, eat and no-bone driver cases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:48:21 +01:00
Mathih13andClaude Opus 5.5 1ebe33b437 fix: stop shift-click opening the stack splitter on vendor bundles (#751)
- Shift-clicking water or arrows at a vendor takes one bundle, as 1.12.1 does
- Drop the merchant comments that restate the code

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:48:21 +01:00
Sam SungandClaude Opus 5.5 62600e717b A multi-line EditBox grows with its text; a Lua-made box draws its text (#729)
A multi-line EditBox kept its authored height, so a scroll frame around a long mail body or ticket had no range. A box made in Lua with no template never placed its text, so it drew nothing.

In 1.12.1, the flush's relayout sets a multi-line box's height to its insets plus its wrapped text height, or one line when empty (`0x77d4d0` at `0x77d8ad`). Every resize, including the first layout, re-seats the text at the top-left inset (`0x77a8d0` → `0x77b8c0`). `SetTextInsets`, `GetTextInsets`, `SetMultiLine` and a font change re-seat it too.

benilla now does the same. A focused multi-line box's caret and text are clipped only by a scroll frame, as the reference clips neither.

Tests cover growth and shrink, the stock mail and ticket scroll ranges, each re-seat trigger, a Lua box's first layout, and the caret in an empty box and after a trailing newline.

Fixes #722
Fixes #726

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-04 15:48:21 +01:00
chairmanpow a0d9952d90 ui: load Turtle's Transmog/Shop Templates xmls, so the windows don't raise on nil 2026-10-04 10:37:41 +01:00
chairmanpow 32657507b7 Revert "anim lod: a far on-screen rig parks, so a city crowd costs no per-bone work"
This reverts commit d9c820ea27.
2026-10-03 17:56:17 +01:00
chairmanpow 3ba52ee608 build: line tables only and unpacked debuginfo, so the dev tree stops filling the disk
DWARF is ~97% of what rustc writes here: the largest `benilla-app` codegen unit measured 145 MB,
of which 61 MB was `.debug_str` and 13 MB `.debug_info` against 1.5 MB of `.text`. That carried a
1.5 GB `libbenilla_app` rlib, a 2.9 GB test binary and an 82 GB `target/debug`. `.debug_line`
survives, so a backtrace and `gdb bt` still name a file and a line; `split-debuginfo` leaves the
rest in the object files rather than copying it into the linked binary.
2026-10-03 17:41:41 +01:00
chairmanpow 445624217a ui: load TurtleWoW's Transmog and Shop windows
Both are the client half of Turtle's custom systems, off the chain's own `FrameXML.toc`:
`Turtle_TransmogUI` hooks `GOSSIP_SHOW`, hides the stock gossip frame for its NPCs and drives
the transmog window over `TW_TRANSMOG`; `Turtle_ShopUI` (prefix `TW_SHOP`) is the custom
merchant/shop. Without these rows the server's trigger gossip text is what the stock gossip
window shows and neither window exists. Inspect-Talents and Guild-Bank stay out until
`ToggleInspect`/`SetTalentButtonLocation` and the GuildBank globals are built.
2026-10-03 17:41:41 +01:00
chairmanpow d9c820ea27 anim lod: a far on-screen rig parks, so a city crowd costs no per-bone work
`animLodDistance` (0..=300 yd, default 60, `0` off) adds a distance leg to the animation-LOD
gate: a rig past it parks its pose evaluation even when it is in frame and in a PVS-visible
room, joining the frustum and room legs. The gate reads the world camera's `GlobalTransform`
for the position. `0` restores the shipped frustum-and-room-only behaviour.
2026-10-03 17:41:40 +01:00
chairmanpow 55040a63ea Revert "shadow: skin the character caster on the GPU, so a crowd costs no CPU skin"
The GPU-skinned character caster made realtime shadows render wrong, and the
Stormwind lag it was meant to fix does not come from the shadow lanes anyway
(character shadows are off there, so the rig is torn down). Restore the
CPU-skinned merged proxy exactly as it was.
2026-10-03 17:14:49 +01:00
chairmanpow 4c15ab4bdd shadow: skin the character caster on the GPU, so a crowd costs no CPU skin
The character shadow lane CPU-skinned every admitted unit into one merged
Mesh asset and re-uploaded it each rebuild — the documented expensive half
of the lane's ~3 ms, and the part that scales with players. Replace it with
one GPU-skinned caster per admitted creature part: the part's own skinned
mesh and MeshTag, drawn through a new SkinnedShadowCasterMaterial whose
prepass vertex skins from the shared rig palette (the same rows wow_model
reads). Pose changes are free; only membership is reconciled per frame.

Palette region offsets are pushed as shader defs, so the caster shader reads
the shared light buffer's tail as a flat array and needs no copy of the
WowLight layout.
2026-10-03 16:37:38 +01:00
chairmanpow 6eaba32006 wipe: reset 5.0's remembered table size, so a refilled list counts from 1
`wipe` cleared elements with a bare `pairs` loop, which leaves Lua 5.0's remembered table size
stale (the `getsizes` side store `luaL_setn` writes). TurtleWoW's LFT fills its group list, wipes
it on the next `S2C_GROUPS_LIST_UPDATE` start, and refills; the refill's `getn` answered the old
length, so `LFT_UpdateGroupsList` read nil entries and `CustomSort` raised on `value.category`.
Take `Globals.lua:126`'s body instead: a `tremove` walk down from `getn` resets the size, then
clear the non-array keys. The test now pins the return and the size reset.
2026-10-03 16:23:39 +01:00
chairmanpow bd0e24e9a9 ui: load TurtleWoW's LFT group finder, so its minimap eye and /lft exist
benilla.toc sources the stock FrameXML off the player's chain, and it omitted the LFT
(Looking-For-Turtle) files, so the group finder never loaded. Add LFT_Data.lua, LFT.lua,
LFTTemplates.xml and LFT.xml in the reference toc's order, with `json.lua` (the codec its group
payloads decode through) and OctoWoW's blink-pause overlay last. `CharacterFrameTemplates.xml`
comes in ahead of its users: LFT's Browse/Queue tabs, and CharacterFrame.xml's own tabs, inherit
`CharacterFrameTabButtonTemplate`, which only that file defines and the reference toc lists apart.
2026-10-03 16:23:39 +01:00
chairmanpow 8c9b1d9984 TurtleWoW compat: the create screen loads races 9/10, so the character and Blood Elf stage show
The char-create catalog compared `CharBaseInfo` against the stock 1.12 rows exactly and bailed
on any difference. TurtleWoW adds class combos (Human Hunter, Orc Mage, Dwarf Warlock, ...), so
the whole catalog failed to load: `body_display` was unavailable and the glue booth assembled no
body on both the create and select screens. The creatable set is now read from `CharBaseInfo`
(the stock eight, or a patched chain's more), the stock rows are a subset guard against a
misparse, and race 9 (Goblin) and 10 (BloodElf) load their display, dial ranges and outfit like
the shipped eight.

The chain's `ChrRaces` row 10 is `BloodElf`, not the "High Elf" the code named it, and
`scene_token` mapped 9/10 to Human, so a Blood Elf stood on the Human stage. Both now resolve
(`UI_Goblin`, `UI_BloodElf`) and the display name is "Blood Elf"; the fileString is what the
race stage keys on.

The create screen hard-coded the eight vanilla races. It now splits the catalog's creatable set
into the Alliance and Horde columns, so a TurtleWoW chain shows five rows per column (Blood Elf
appended to the Alliance, Goblin to the Horde), with the tower and lower clusters shifted to
match its XML. Race-icon UVs are computed from the loaded sheet's size, so the stock four-column
sheet and TurtleWoW's widened eight-column sheet both read correctly.
2026-10-03 15:51:27 +01:00
chairmanpow 4ed0d0c5ca gossip: log each option's label, so a row's meaning is readable
A count cannot say which row opens a shop, which is greyed, or which is
TurtleWoW's `CUSTOM_MERCHANT_TRIGGER` marker. The labels are the server's own
sentence; print them with the index, icon and coded flag beside each.

Verified: cargo clippy --workspace --all-targets -- -D warnings reports nothing
in this file. No test covers a debug! line.
2026-10-03 14:08:51 +01:00
chairmanpow 67b7ef8994 spell: a shot with a real cast time opens the cast bar
The bar was suppressed for every `Attributes & 0x2` spell, the reference's
`0x6e7700` gate read literally. That gate is why Aimed Shot and Multi-Shot show
no bar in 1.12.1 — a later client added `Force Display Castbar` to fix exactly
this, so the reference behaviour is the bug.

Gate on the DBC row instead: a spell opens a bar on the wire cast time whenever
`SpellCastTimes.dbc` has a nonzero one for it. Aimed Shot's real 3 s cast does,
so it bars. The instant shots do not — they point at row 1, the instant
sentinel — so Throw, Multi-Shot, Auto Shot and wand Shoot stay bar-free, and
vmangos's 500 ms pad on a non-auto-repeat ranged cast (`SpellEntry::GetCastTime`)
cannot conjure one either. An unknown spell, or a build with no cast-time table,
keeps the reference's bar rather than losing every bar.

`SpellCastTimeCatalog::from_rows` is the constructor the DBC loader and the test
share.

Verified: `cargo test -p benilla-app -p benilla-formats` — 3833 pass, the new
`a_timed_ranged_cast_opens_the_shot_bar_but_an_instant_shot_does_not` among
them. The one failure, `assets_ui_does_not_grow`, is pre-existing on main.
2026-10-03 14:08:43 +01:00
chairmanpow 503cde608a build: green fmt and clippy on the pinned 1.98.1
The gate was already red on main, in files no round since had touched.

`cargo fmt` reflowed one wrapped assert in the benilla-ui loader tests. Clippy's
1.98 set added four lints the tree does not satisfy: `doc_lazy_continuation`
on room_claim.rs's `2b.` item (indented, as it already renders), and in
benilla-extract `chunks_exact_to_as_chunks` on the MOGI walk,
`manual_checked_ops` on the census mean, and `needless_borrow` on the class
table call. Each takes the fix clippy names.

33 errors remain in benilla-world's lighting, static_gx, water_fx and liquid
trees, from the same lint bump; they are left for their own round rather than
folded in here.

Verified: cargo fmt --all --check clean; `cargo clippy --workspace
--all-targets -- -D warnings` now fails only in benilla-world.
2026-10-03 14:08:26 +01:00
chairmanpow 3b068b15c2 worldport: re-claim our own body after a transfer
Leaving a battleground left the character unable to walk for the rest of the session. The
`settle released` line named it: `WASD held by: control-lost`.

vmangos takes control for the teleport (`SetClientControl(player, 0)`), so `Revoked` sets
`control_lost` and parks the mover, and only a later `SMSG_CLIENT_CONTROL_UPDATE` with
`allow_move = true` clears it. Nothing guarantees that packet, and nothing else clears the flag:
the worldport arm replaces the pose and the settle releases, neither asks, and `seizing` at
`wire_in.rs` wants `reseat`, which a worldport never sets. So the lock on the body we just left
parked the body we came back as.

A transfer is a mover change, so re-claim at the transfer's completion, with the ack, the way
login does — the same teardown as the reference's `SetActiveMover` reset. `reseat` rides along so
the existing seize path resets the outgoing mover's state. A possession is left alone: there the
lock is the point.

Compile-verified only — the live run is the test.
2026-10-02 18:38:56 +01:00
chairmanpow bf52a0abb1 player: say what holds WASD when a settle releases
Coming out of a worldport unable to walk was a story, because nothing in the log said whether
the hold was the settle, a lost mover or a pending reseat. `movement_suppressors` named six
states and was only ever printed by the logout smoke test; `settling` was missing from it
although the settle hold is a mover hold (`mover.rs`), and a cross-map worldport is exactly
where it fires.

One line per settle, on the frame it ends, naming the hold or `none` — so the next transfer
reports its own cause. Instrument only; no gate reads either list.
2026-10-02 18:23:09 +01:00
chairmanpow c2d83dc22e probe bg: a leave leg, so WOW_PROBE_BG can measure the worldport out
The probe's last act was a GM teleport, which never exercises the worldport a leave causes — so
it could say nothing about the scoreboard or the movement latches after one. `WOW_PROBE_BG_LEAVE=1`
swaps the flag and graveyard legs for the real thing: wait inside for the match to end, leave with
the stock `LeaveBattlefield()`, and census for 90 s across the worldport.

A solo match never reaches `min_players_per_team`, so vmangos ends it with `WINNER_NONE` about
300 s after the doors, and a non-nil winner is what the modal needs to show itself
(`BattleGround.cpp:318` is the AV exception). A census gained `SCOREFRAME IsShown=` with the three
latches that can freeze a body — `settling`, `world_stale`, `control_lost` — beside the worldport
ack, and a `moved=` column on the position so "stuck" is one number rather than an eyeball.
`PLAYER_ENTERING_WORLD` joins the tap, to see whether the world-entry event fires at all.

The tap's window is drained once per frame before the phase match, for the three in-battleground
phases, so the one-shot consume stays in `bg_probe` where `game_plugins.rs` argues for it.
2026-10-02 17:56:08 +01:00
chairmanpow 7f99a3f35a scoreboard: the board dies with the battleground, and the clear reaches the VM
Leaving a finished battleground brought the end-of-battleground panel back in the open world,
with a leave button that had nothing left to leave. Two defects, both in how the board outlived
the instance.

`SMSG_BATTLEFIELD_STATUS` is the packet that says the player is out: the active slot's map going
to zero, or that same slot losing `in_progress`. The handler already zeroed the run-time clocks on
exactly those two branches and left the scoreboard standing, so `GetBattlefieldWinner()` kept
answering the finished battleground's winner and stock Lua showed the panel again on the next
score event (`WorldStateFrame.lua:351-355`). TurtleWoW drops its own board on the same signal —
`instanceType == "none"` clears `customData` (`WorldStateFrame.lua:243-247`), which is the only
thing its `GetBattlefieldWinner()` override can fall back from. `BattlefieldQueue` now marks the
exit and the feed zeroes the board with the clocks.

Zeroing the resource alone changed nothing, because the feed could not push an empty one:
`changed = fresh.is_some() && …` withheld a cleared board, and the VM keeps whatever it was last
handed. The clear is pushed as a default board, which answers nil for the winner and fires the
`UPDATE_BATTLEFIELD_SCORE` that lets the frame drop its winner text, timer and button.

`PLAYER_ENTERING_WORLD` is not the lever: the reference fires it once per session beside
`PLAYER_LOGIN`, `PLAYER_LEAVING_WORLD` and `PLAYER_LOGOUT` (`0x490959`, `0x49096a`, `0x490b4d`,
`0x490c2a`), not per teleport.

The world-state test becomes the Lua contract it always was — the panel returns whenever the board
answers a winner — and two app-level tests pin the fix, one per exit packet. Both fail without it.
2026-10-02 17:56:00 +01:00
chairmanpow 8b81d08153 loader: an unnamed instance keeps its own name, so $parent reaches the real parent
`framexml::merge` merged the inherited template's `name` onto the instance, so an unnamed child of a named virtual template took the template's name. ItemRack's `<Frame inherits="ItemRackQueueTemplate"/>` then named its texture after the template instead of the parent frame, and `Rack.OnEvent` indexed the missing `ItemRackInv*Queue` global.

The reference reads `name` and `id` from the instance alone (`PreLoadXML 0x769770`); drop both from the merge base.
2026-10-02 15:45:38 +01:00