superwow: the chat bubbles are widgets — CREATE_CHATBUBBLE and pfUI's skin
The last real SuperWoW feature, and one piece of work closed three things: `CREATE_CHATBUBBLE` (which could not honestly be fired, because its payload can only be the bubble), `C_ChatBubbles.GetAllChatBubbles` (which answered an empty list, truthfully, for the same reason) and pfUI's whole bubble skin, which walks that list. `benilla-ui`'s `script/chat_bubble.rs` is a pool of real frames — anonymous WorldFrame children with a backdrop (`0x4b0a35` IS `SetBackdrop`), a tail texture and a FontString, which is what the reference's own `CGChatBubbleFrame` is and how addons of the era found bubbles before SuperWoW published the event. The app keeps everything a bubble's existence depends on — the spawn gate, the lifetime, the fade, the range re-test, the stacking sort and the text measurement that sizes the frame — and hands it over as `BubbleState`: the same split `vplates.rs` already makes for the plates, so the engine decides what is on screen, the widget layer decides what Lua sees, and neither can disagree about a bubble because there is one feed. Three things improved by the move rather than being preserved by it: - The bubbles sit at frame levels 2, 3, 4… over the V-plates at level 1, which is the reference's own order (`0x4b12d5`-`0x4b1312`). The quad band had them UNDERNEATH — a deviation `overlay_z` carried in a comment. That band is gone and the lane is down to one tenant. - The art is loaded, tiled and bleed-inset by the UI pass like any other frame's, so this file drops its own texture resource and its own half-texel inset helper. - The system moved BEFORE the paint. It sat in `UiQuadAppend`, the window *after* the extract: right for an appender, a frame late for a feed. The test that matters transcribes pfUI's own `ScanBubbles` loop — it finds the FontString with the line and its chat-type colour, blanks the one Texture region and parents its own frame to the bubble — and then checks the EXTRACT for drawn quads: both backdrop halves, the tail, and the text at the em the app measured the box with. A test that only checked the widgets would have passed with nothing on screen. Two architectural gates caught this and both were answered rather than silenced. The one-shot gate: `drive_bubbles` now holds the VM and drains a queue, so it is EXEMPT with the argument — the queue's product is app-side state, the widget sync is idempotent and binds on the first frame there is a WorldFrame, and gating the system instead would pile lines up and spawn them all at once with fresh lifetimes. The VM wall: its ceiling rises by one with the reason, which is that this file is `vplates.rs`'s exact twin. `CREATE_CHATBUBBLE` fires per APPEARANCE, not per pool slot ever built — the opposite of `NAME_PLATE_CREATED`'s rule, because a recycled frame has been re-anchored, re-sized and re-texted since it was last seen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01SgstFWtn1RxEJ5kVrpgYwn
This commit is contained in:
+28
-11
@@ -40,7 +40,7 @@ nothing behind it.
|
||||
| `UNIT_CASTEVENT` | done | `crates/benilla-app/src/superwow_events.rs`: a `CastEventFeed` queue the cast handlers push into, drained once per tick by one VM-holding system that fires the event — the pattern `ui_mirror.rs` already uses, and the only way across, since `spell/net.rs`'s `Lifecycle`/`Scene` bundles deliberately hold no `UiScript` (`game_plugins.rs`'s `every_vm_holder_in_update_declares_its_side_of_the_tick` gates that). Edges wired: `START` from `SMSG_SPELL_START` with its wire cast time as `arg5` (an instant raises none — its only edge is `CAST`, which is what the wiki's own START/CAST split means), `CAST` from `SMSG_SPELL_GO`, `FAIL` from `SMSG_SPELL_FAILED_OTHER`, `CHANNEL` from `MSG_CHANNEL_START` with its duration. GUIDs go out in the same 16-hex-digit spelling every other `compat/` surface uses, so `arg1`/`arg2` compare directly against a `UnitExists`/`UnitGUID` return. Not wired: `MAINHAND`/`OFFHAND` — benilla's melee-swing path raises no cast edge at all, so there is nothing to forward. `MSG_CHANNEL_START` names no caster (1.12 sends it for the local player's own channel only), so `CHANNEL`'s `arg1` is self and its `arg2` is 0 |
|
||||
| `RAW_COMBATLOG` | **done** | `ui_chat/frames.rs`'s `route` — the one funnel every combat line already passes on its way to a window and to `WoWCombatLog.txt`. Two arguments, as its one consumer in grom's corpus destructures them: the printed sentence, and the same sentence with each unit name spelled as its GUID (`PendingCombat::guid_fills`, composed a second time through the same `compose_line`, so the two can only ever differ in the name slots). Fired **before** the `CHAT_MSG_*`, which is what "raw" means upstream — it hooks the combat-log parse, ahead of the client's own formatting. Gated on `has_listener` — the gate nampower's combat-log family already uses — **or on SuperWoW's raw log being open**, since that file is the same `arg2` form written to disk; the composition is skipped under the same one test (`frames::wants_raw_form`), because a raid's log is tens of lines a second. A variant whose template says "you" keeps saying it, because that word is in the *template* and not in a fill — and its consumer depends on exactly that, reading the word and then asking `UnitExists("player")` for its own guid. **Why this row came first:** of SuperWoW's whole surface, it and `CombatLogAdd` are the only two names grom's addons call that this client lacked (`scripts/dll-surface.py --wanted`), and `SuperCleveRoidMacros/Utility.lua:10206` funnels **five** subsystems through this single event — immunity and resist learning, hidden-CC ("afflicted by") detection, debuff-duration learning off the GUID half, and the reactive-proc window that lights **Overpower** and **Revenge**. Its own comment there is why the last of those can come from nowhere else: "SPELL_GO only provides hit/miss COUNTS, not miss TYPES, so it cannot distinguish dodge (Overpower proc) from parry/block/resist". This does **not** by itself fix the Overpower complaint: that needs the dodged-*ability* sentence to be emitted at all, which is its own open item (`benilla-overpower-aura-spelldodged`) |
|
||||
| `CombatLogAdd("text" [, addToRawLog])` | **done** | `compat/superwow.rs` queues the line; `ui_chat/logging.rs`'s `sync_chat_logging` appends it to `WoWCombatLog.txt` through the same `record` every routed line uses, so it is stamped identically and one writer owns the file. **The second argument names a FILE**, not the event: `Logs\WoWRawCombatLog.txt` is its own literal in the DLL's string pool, a few strings on from `Logs\WoWCombatLog.txt`, and it is the GUID-form log — the same lines `RAW_COMBATLOG` carries as `arg2`. That corrects a first reading of the name ("also fire the event"), which was an inference from the name alone. Dropped while a log is closed, which is upstream's behaviour too. A non-string argument is ignored rather than raising: its one real caller is a debug-trace helper (`BigWigs/Plugins/MageTools.lua:761`) and a raise there would take the addon down for something the player cannot act on. **It is also a detection name**: `SpellRequests.lua:6` is `if CombatLogAdd then hasSuperWow = true`, which gates that whole module |
|
||||
| `CREATE_CHATBUBBLE` | **blocked, deliberately not fired** | the last of SuperWoW's three events, and the one that cannot be made good: its payload can only be the bubble, and this client's bubbles are not Lua frames — they are a 2-D world overlay (`chat_bubble.rs`), as the reference's own `CGChatBubbleFrame` is. The same wall `C_ChatBubbles.GetAllChatBubbles` hit, and firing it argless would be *worse* than silence (an addon reaching for `arg1`'s regions raises, once per bubble). See the manifest section below for what the one piece of work behind it would close. `SUPERWOW_COMBAT_LOG_EVENT`, which this row used to carry, is a ghost |
|
||||
| `CREATE_CHATBUBBLE` | **done** | the last of SuperWoW's three events, and it needed the bubbles to become **widgets** first: its payload can only be the bubble, and this client drew them straight into the overlay quad lane. `benilla-ui`'s `script/chat_bubble.rs` is now a pool of real frames — anonymous `WorldFrame` children with a backdrop, a tail texture and a `FontString`, which is what the reference's own `CGChatBubbleFrame` is — driven by the app's existing feed, the same split `vplates.rs` already makes for the plates. `arg1` is the frame, which is the only sensible payload and a reading rather than a measurement (the DLL publishes no usage string for an event). Fired **per appearance**, not per pool slot ever built, which is the opposite of `NAME_PLATE_CREATED`'s rule and deliberate: what an addon wants from this is "here is a bubble to skin or move, now", and a recycled frame has been re-anchored, re-sized and re-texted since it was last seen |
|
||||
| `NameplateRange` | done | one CVar, two readers: `nameplates.rs`'s `NameConfig::range_sq` (the overhead name — introduces a cull where the reference has none at all) and `vplates.rs`'s `VPlateMode::range_sq` (replaces the reference's own hardcoded 20 yd V-key-plate range). Default `80`, the top of the mod's documented 10-80 range — no shipped default is published, so this is the least-restrictive honest choice, not a confirmed number |
|
||||
| `ChatBubbleRange` | done | `chat_bubble.rs`'s `BubbleConfig::range_sq`, replacing the reference's hardcoded 20 yd. Default `20` (today's exact value) — no shipped default published for this one either, within the mod's documented 10-200 yd range |
|
||||
| `NameplateMotion` | **done, two of its four values** | the investigation this row asked for had an answer already in the tree: benilla *does* spread plates, with the reference's own SmartScreenRect solver (`crate::smart_rect`, bucket 0). So `0` ("Overlap") is that solver bypassed — the plate sits on its own projected point and nothing is claimed either, since a claim with no solve is half a spread — and `1` ("Default spread") is it, which is today's behaviour and the registered default. `2` ("Smart spread") and `3` ("Compact spread") are the mod's own alternative algorithms, unknowable from here, and **fall back to 1**: a documented silent-equal rather than a refused row, because the benign direction is plates that still do not overlap, and refusing would leave a player no way to turn the spread off at all. The four meanings come from SuperAPI's own panel (`SuperAPIOptions.lua:20-25`), the DLL publishing only the name. `NameplateVerticalSpacing`, which this row used to carry, is **not in the DLL's string pool at all** — another ghost |
|
||||
@@ -108,15 +108,32 @@ that site can only subtract; the other reading would bubble creatures with the m
|
||||
which is a surprise no setting advertises. All four are welded to their struct literals in
|
||||
`cvars.rs`'s own defaults-mirror test, the `benilla-cvar-default-trap` discipline.
|
||||
|
||||
**`CREATE_CHATBUBBLE` cannot be made good, and the reason is already written up elsewhere in this
|
||||
file.** Its payload can only be the bubble, and this client's bubbles are not Lua frames at all —
|
||||
they are a 2-D world overlay drawn by `chat_bubble.rs`, the same way the reference's own
|
||||
`CGChatBubbleFrame` is drawn. That is the identical wall `C_ChatBubbles.GetAllChatBubbles` hit, and
|
||||
it answers an empty list for it. Firing the event argless would be worse than not firing: an addon
|
||||
written against upstream would reach for `arg1`'s regions and raise, once per bubble. **The real
|
||||
item behind both** is making the native bubbles addressable from Lua — a frame with a texture and a
|
||||
`FontString` per live bubble, kept in step with the overlay's own spawn, stacking and expiry rules —
|
||||
and that one piece of work closes the event, the namespace and pfUI's bubble skinning together.
|
||||
**`CREATE_CHATBUBBLE` is done, and so is `C_ChatBubbles.GetAllChatBubbles`** — one piece of work
|
||||
closed both, plus pfUI's bubble skin. The wall was that a bubble's payload can only be the bubble
|
||||
and this client's bubbles were not Lua frames: they were drawn straight into the overlay quad lane,
|
||||
faithful on screen and unreachable from Lua. They are a **widget pool** now
|
||||
(`benilla-ui`'s `script/chat_bubble.rs`): anonymous `WorldFrame` children with a backdrop
|
||||
(`0x4b0a35` is `SetBackdrop`), a tail texture and a `FontString` — which is what the reference's own
|
||||
`CGChatBubbleFrame` is, and how addons of the era found bubbles before SuperWoW published the event.
|
||||
|
||||
The app keeps everything a bubble's *existence* depends on — the spawn gate, the lifetime, the fade,
|
||||
the range re-test, the stacking sort and the text measurement that sizes the frame — and hands the
|
||||
result over per live bubble, the same split `vplates.rs` makes for the plates. Three things improved
|
||||
by the move rather than being preserved by it:
|
||||
|
||||
- The bubbles now sit at frame **levels 2, 3, 4…** over the V-plates at level 1, which is the
|
||||
reference's own order (`0x4b12d5`-`0x4b1312`); the quad band had them *underneath*, a deviation
|
||||
`ui_pass::overlay_z` carried in a comment. That band is gone, and the lane is down to one tenant.
|
||||
- The art is loaded, tiled and bleed-inset by the UI pass like any other frame's, so
|
||||
`chat_bubble.rs` no longer carries its own texture resource or its own half-texel inset.
|
||||
- The system moved **before** the paint. It had been inside `UiQuadAppend`, the window *after* the
|
||||
extract — right for an appender, a frame late for a feed.
|
||||
|
||||
**What a test had to prove, and does**: pfUI's own `ScanBubbles` loop, transcribed, finds the
|
||||
`FontString` with the line and its chat-type colour, blanks the one Texture region and parents its
|
||||
own frame to the bubble; and a bubble handed over as state comes out of the *extract* as drawn quads
|
||||
— both backdrop halves, the tail, and the text at the em the app measured the box with. A test that
|
||||
only checked the widgets would have passed with nothing on screen.
|
||||
|
||||
And the CVars it registers, as names: `NameplateRange`, `NameplateMotion`, `ChatBubbleRange`,
|
||||
`ChatBubblesRaid`, `ChatBubblesBattleground`, `ChatBubblesWhisper`, `ChatBubblesCreatures`,
|
||||
@@ -351,7 +368,7 @@ a loot roll"; it does not, because that module never runs.** The call path was n
|
||||
|
||||
| Area | Status | Notes |
|
||||
|---|---|---|
|
||||
| `C_ChatBubbles.GetAllChatBubbles` | done, **empty list** | The one genuinely hot absence the sweep found, and reproducible: `pfUI.bubbles:ScanBubbles()` raised `attempt to index global 'C_ChatBubbles'`, and pfUI runs it the frame after **every** `CHAT_MSG_SAY`/`YELL`/`MONSTER_SAY`/`MONSTER_YELL` within earshot. Answers an empty list, which is the truthful count rather than a stub: bubbles here are drawn natively by `chat_bubble.rs` as a 2-D world overlay, exactly as the reference's own `CGChatBubbleFrame` is, so there are no Lua frames to hand over and no regions a script can repaint. The cost is that bubbles keep this client's look instead of pfUI's; making them addressable (a frame + texture + `FontString` per live bubble, kept in step with the native spawn/stack/expiry rules) is the real fix and is not built |
|
||||
| `C_ChatBubbles.GetAllChatBubbles` | **done, the live bubbles** | The one genuinely hot absence the sweep found, and reproducible: `pfUI.bubbles:ScanBubbles()` raised `attempt to index global 'C_ChatBubbles'`, and pfUI runs it the frame after **every** `CHAT_MSG_SAY`/`YELL`/`MONSTER_SAY`/`MONSTER_YELL` within earshot. It answered an *empty list* for a round — truthful, because bubbles were overlay quads with no Lua frame to hand over — and that row said making them addressable was "the real fix and is not built". It is built: `benilla-ui`'s `script/chat_bubble.rs` is a pool of real frames and this answers them in pool order, live ones only, a fresh table per call. The order is stable for a bubble's whole life, which is what an addon keying a cache on the frame needs, and is deliberately **not** the stacking order, which changes every frame with the camera. pfUI's own loop over it is transcribed as a test |
|
||||
| `C_Spell.GetSpellDispelType` | done | The other hot one: `libs/libdebuff.lua:379` calls it once per debuff while walking aura slots 33-48, so it runs for every debuff on every scanned unit — continuously, in combat, which is when the debuff display matters. Backed by widening [`SpellLookup`] with `Spell.dbc`'s `Dispel` column, the route this file already named for the `C_Spell` predicates; `SpellDisplay` carried it already, so nothing new had to be parsed. Answers the **raw id**, not `SpellDispelType.dbc`'s localized name, because the caller keys its own `dispelTypeMap` on the number and a name would make a localized realm answer something that table cannot match. `0` ("no dispel class") answers **nil**, which the call site's `if dispelId and dispelId > 0` already reads the same way. The book arm takes it from the probe alongside `reagents` — a spellbook slot carries neither column |
|
||||
| `Item:CreateFromItemLink` | done | Keyed on the id inside the link, which is the same object here: every `ItemMixin` getter reads a `C_Item.*ByID`, and a 1.12 link carries nothing per-item beyond the id — no bonus ids, no modifiers, no scaling — so the link and id forms cannot disagree about anything the mixin exposes. A link naming no item (nil, a spell or quest link, a malformed string) yields an **empty** item rather than raising, which is what the call site's own `if item and not item:IsItemEmpty()` expects. Built for correctness, not for a live error: its only caller is the loot-history module, which never runs |
|
||||
| `UnitSpellHaste` | done, **0** | 0 is the *answer*, not a stub: 1.12 has no haste stat anywhere — not a `UNIT_FIELD`, not an item mod, not a spell modifier — so every unit genuinely has 0% spell haste and the real DLL has nothing else to read either. pfUI's player module calls it unguarded into a `string.format("%.1f%%")` (`modules/player.lua:87`) whenever its haste readout is on, so the absence raised once per 0.25s tick and took the neighbouring "+N SP" line down with it |
|
||||
|
||||
Reference in New Issue
Block a user