Commit Graph
18 Commits
Author SHA1 Message Date
SamandClaude Fable 5.1 fed2c96ec8 the mailbox takes typing again, and events carry their real arguments
- the mail window's send tab takes typing again: the subject, body and money boxes were taking no input while the recipient box swallowed every key, and a sent letter clears its subject and item icon instead of putting them straight back (2145)
- seven events fire the way the reference does: UNIT_DYNAMIC_FLAGS fires at all, swapping two identical stacks between bank slots fires PLAYERBANKSLOTS_CHANGED twice, the pet sheet's training points repaint, and ITEM_LOCK_CHANGED loses its invented arguments (2140)
- nine widget methods 1.12 addons probe for are in: ShaguTweaks stops recolouring textures the reference leaves alone, pfUI and _Nameplates stop raising on a tooltip's GetAnchorType, and 63 AceEvent addons get RegisterAllEvents (2142)
- six post-1.12 methods leave the widget surface (SetSize, SetShown, HasFocus, SetFormattedText and two tooltip verbs), so an addon reaching for a later client's API finds what a real 1.12 client gives it (2142, 2146)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:52:50 +07:00
SamandClaude Fable 5.1 374bc5898d an addon's font draws in its own face, and Bartender2 keeps its profile
- text shapes in the face an addon asked for: MikScrollingBattleText draws in its own Porky instead of a system sans, and Arial Narrow and Friz Quadrata are no longer drawn by the Mac's own fonts (2123)
- Bartender2 and Cartographer stop resetting their profile at every login: a stack trace opens on the caller, where AceDB reads its addon's folder; a font's reported height is a number before its face resolves, which Tablet-2.0 does arithmetic on (2121, 2129)
- a button highlight built from a texture object draws, so Bongos' bar-drag button, _Nameplates' mouseover glow and Quiver's option rows show theirs; clearing a state texture removes it instead of drawing it in every state (2124)
- a toc's repeated SavedVariables lines add up, so all 25 of CT_RaidAssist's saved globals persist instead of seven; colour-mapped TGA icons decode, so FonzAppraiser's and FuBar_WeaponRebuffFu's draw (2126, 2128)
- MikScrollingBattleText's DoT lines are back with their school tints, and the school word is capitalised; a chat line with no colour stays white, and the short colour-id form of adding a message works (2127, 2125)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:50:59 +07:00
SamandClaude Fable 5.1 c775181933 the cooldown pie survives a /reload, and Bagnon's bags keep their order
- the cooldown pie, the GCD sweep, minimap pings and the map arrow draw again after a logout, login or /reload; they went blank until a restart, which read as OmniCC killing the cooldowns (2113)
- an addon's own font on a button label keeps its face, size and outline instead of being undone every frame, MikScrollingBattleText's reported look (2112)
- an empty label reads as nothing rather than an empty string, so Cartographer's map label keeps its colour; a slider thumb with no authored size takes its art's own size, so Dewdrop's Look'n'Feel slider moves (2109, 2110)
- Bagnon's saved bag list comes back in the order it was written, so the keyring stops jumping to the front after a restart; hovering a Bagnon bag costs a third of what it did (2111, 2114)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-11 20:49:50 +07:00
SamandClaude Fable 5.1 d8de29bd9b idle sits you down and camps you, and the addons anchor, parse and draw
- five idle minutes sit you down and flag you AFK, thirty log you out through the ordinary countdown; holding a key is idle, moving the mouse is not, and a swimmer stays afloat (2092)
- a knockdown no longer wedges: the landing froze the clip that held the base-animation lock, leaving a knocked-down player on their back for the session (2098); the perf cost pill starts hidden (2099)
- Lua is exactly 1.12's 5.0 — no vararg expression, no #, no %, the remembered table size — so Cartographer loads without errors and Mik's Scrolling Battle Text shows its text, in its own shipped font now that addon TTFs load (2101, 2102, 2103)
- a widget name is a Lua global for SetPoint and its kin, so Bartender2's bars spread out; a button's NormalText disowns its justify, the raise renumbers hidden frames too, and a missing toc file warns instead of erroring (2105, 2100, 2104, 2107, 2106)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 16:35:03 +07:00
SamandClaude Fable 5.1 7fe4b37c2f guild lines are message ids, mail sends again, and the shadow fades in
- the guild lines are message ids: `ui_guild/lines.rs` carried 34 English constants and two hand-rolled fill helpers and pushed composed text onto the chat log — 2045's largest ratchet row and the one file its rule had no route for, because `UiError`'s doc claimed the 1.12 formats use at most one `%s` and one `%d` and the type was built to match, while `ERR_GUILD_PROMOTE_SSS` is "%s has promoted %s to %s." — a wrong sentence in a doc had become a wrong type, and the wrong type had made a file's worth of drift structurally necessary; `CGGameUI::DisplayError` (`0x496720`) is cdecl varargs — `add esp,4` bare, `add esp,8` with one string, `SMSG_GUILD_INVITE` pushing two with the stack check exact at `0x30 + 0x60 = 0x90`, and `SMSG_GUILD_EVENT`'s emitter tail `0x5e745f` passing one, two or three off `strCount` — so `UiError` carries an ordered heterogeneous `Vec<FillArg>` (`S(String) | D(i64)`, since `ERR_USE_LOCKED_WITH_SPELL_KNOWN_SI` is String-then-Integer) behind four constructors naming the reference's call shapes (`key`, `s`, `strings`, `args`), twenty sites migrated; what the catalog knew and composed text could not carry: `ERR_GUILD_NAME_INVALID`/`ERR_GUILD_NAME_EXISTS_S` are kind 2 — the red line — and went to chat, `ERR_GUILD_CREATE_S`/`ERR_INVITED_TO_GUILD_SS` carry the LEVELUP cue and `ERR_GUILD_PERMISSIONS` (type_tag `0x3e`) is one the reference speaks (1815), all three silent; the default arm was missing outright — `0x5e745a` pushes `0x69` = `ERR_GUILD_INTERNAL` for `0x09` and every id past `0x0d`, catalog row 105 the whole time, so `TABARD_CHANGE` had been silent under a comment calling the key unsettled; a short `strCount` is passed SHORT (0 and ≥ 4 pass zero strings) so a starved specifier reads "…promoted Furor to %s.", not "…to ."; `/ginfo` deliberately stays off the queue — `GUILD_NAME_TEMPLATE`/`GUILD_INFO_TEMPLATE` are not catalog rows, `0x5e6fb0` resolves each token through the VM and emits chat itself, and a non-row key through `Shown::keyed` would turn a chat line red — so it waits on the guild feed and goes out unkeyed, the enUS month-day-year swap with it; the `_S` arity is asserted from the key names, 22 of 22; the ratchet row is deleted, 208 over 31 files remain (2054)
- a unit's blob shadow asks the UNIT for its alpha: the lane gathered live `RenderFade`s off a unit's part entities and read "no part is fading" as 1.0 — true of a settled unit and exactly backwards for arrival, where a streamed unit holds `PendingAppearFade` with no `RenderFade` anywhere until the world is shown, so its parts were deliberately invisible while the shadow drew fully opaque; measured on a probe login: alpha 1.000 from t 0.61 to the arm at t 3.51, then 0, then the ramp — 2.9 s of dark oval under no creature, the symptom `model_render_alpha`'s own "pending ⇒ 0" law (0827) already named for emitters and ribbons; `model_fade::UnitRenderAlpha` is the one door, composed from the ROOT where `UnitAppearFade` tells pending from settled, published as one question rather than its three inputs so a caller cannot reassemble the mistake, and `DespawnFade::armed()` retires the `-1.0` sentinel from every caller; the world API wall goes 177 → 178; and wow-re settled what the shadow's alpha IS, four questions VERIFIED: it reads `CM2Model+0x180` through `0x710ca0`, not `+0x19c`; that slot carries the appear and despawn ramps, the self fade AND the CharProc-14 aura — `0x60d180` drives the same `StartAlphaFade` the appear fade does, so a stealthed unit's shadow now dims with its body — and not the per-batch M2 colour/weight tracks; and the reference draws nothing under a not-yet-shown object either (`+0x180 = 1.0 · 0.0`, written 30 bytes before the read), so this is fidelity, not taste; three module-doc claims corrected against it: the dead `0x683ea5` gate, the registration handoff (GameObjects and DynamicObjects never cast; units, players and corpses always do), and 1277's `InheritedVisibility` gate, which the reference lacks and stays ours on purpose (2055)
- the mail stationery list was empty for the whole session, and every send with it: the five `dropping command — not connected` lines at login, once named, were `ItemQuery` for all five `Stationery.dbc` rows — `feed_mail` carried no run condition and asked from the login screen before the socket existed, the asks evaporated in the io thread, and `Items::template` had already latched all five in its ask-once `pending` set, cleared on disconnect and never on connect, so they were never re-asked and the stock `SendMailFrame_Reset`'s row-1 selection left every send silently unsent; the list now waits for a self player, and `release_ask_once_latches_on_enter` clears the pending sets at world enter, the counterpart the disconnect teardown never had — a redundant re-ask costs one packet, a wrong latch costs a dead feature nobody can see is dead; and three more log lines answer for themselves: a control character is answered before the shape — a newline is a line break the markup parser consumes, not a missing glyph — so the warn stays meaningful for a glyph a face genuinely lacks; the blend-check's one-frame run is the specialization tick catching up with a material swap, a real login showing four entries clear 26 ms later, so the warn waits for a run to SURVIVE the tick and a one-frame run is recorded at debug; and the audio starve line carries the wall span it covered beside the time it counted and the position it saw advance, because a 1.0 s window still closed 0.756 s of wall after the stream started on `Time<Real>` — if they disagree the caller's clock is still wrong, if they agree the starvation is real

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 19:41:57 +07:00
SamandClaude Fable 5.1 8f0bece3be loot and pet refusals become message ids, and waterfalls flow again
- a loot refusal is a message id, not a sentence: `loot_error_text` composed eight sentences for `SMSG_LOOT_RESPONSE`'s refusals and six of them said something 1.12 never says — "too far away to loot that." for a string that ends "that corpse.", "You can't loot that from there." for "You must be facing the corpse to loot it.", and an invented "Those pockets are already empty." for a code the client has no string for; it and the `LootErrors` queue are deleted, and the refusal is raised by GlobalStrings key into `UiErrorKeys` at the net bridge, the way `fish_verdict` two functions above already did, off the reference's own eleven-entry jump table at `0x5ebc64`; a composed string had no record to read a surface or a voice off, so all eleven — `MsgKind::Error`, the red `UIErrorsFrame` line — had been going into the chat window as yellow SYSTEM lines, and the four spoken in the player's own race and gender (`DIDNT_KILL`, `BAD_FACING`, `LOCKED`, `TOO_FAR`) were silent; a second bug fell out of reading the leg: codes 4–9, 11 and the default fall through a release tail at `0x5ebac2` that zeroes `[player+0x1d28]` — our `LootLatch` — while 10, 12, 13 and 14 jump past it, so a master looter refused a hand-off no longer stands up out of the kneel at a still-open corpse; the default arm's `ja 0x5ebabd` lands on code 7's arm, so every unnamed code prints `ERR_LOOT_DIDNT_KILL` and releases (2035); and the pet has its own two message tables, not the player's with a flag: `SMSG_PET_ACTION_FEEDBACK`'s handler `0x4bdb70` decrements the byte, bounds it at 3 and jump-tables it through `0x4bdbe8` to `ERR_PET_SPELL_DEAD`, `ERR_NO_ATTACK_TARGET`, `ERR_INVALID_ATTACK_TARGET`, `ERR_PET_SPELL_NOPATH` — one per vmangos `PetFeedback` value, where our two-entry map had printed "No path available for your pet" for a dead pet, "Out of range" for no target and nothing for 3 and 4; `SMSG_PET_CAST_FAILED` is `0x6e8eb0`, a separate 142-byte index table that overrides ten reasons, six of them to `ERR_PET_SPELL_*` rows with no other raise site in the client, so `CastFail` grows a `Caster` and picks its table from it while layers 1 and 3 stay shared, as the binary shares them; `0x6e8eb0` calls neither the combat-log formatter `0x62c360` nor the error sound `0x458a50`, so a pet's refused Growl no longer prints "You fail to cast Growl: …" as the player's own failure; and the source walk that exists to catch a non-catalog key was blind to both wrong keys because all 465 catalog keys begin `ERR_`, so the tripwire moves to the funnel — `Shown::keyed` warns on a key `by_key` cannot resolve (2033)
- the hunter-pet feedback cluster, five opcodes carved off the binary where four had sat name-table-only: `SMSG_PET_TAME_FAILURE` (`0x173`) is two GlobalStrings lookups in order — `0x6e6a20` maps `reason − 1` through the eleven-arm jump table at `0x6e6ac0` to a `PETTAME_*` key and hands the resolved string to `DisplayError(ERR_TAME_FAILED)`, anything outside `1..=11` taking `PETTAME_UNKNOWNERROR`, and Call Pet and Revive Pet raise it too; `SMSG_PET_NAME_INVALID` (`0x178`) raises `ERR_INVALID_PETNAME` from the shared dispatcher `0x5e38c0`'s arm `0x5e3e33` — 1066 had called a refused rename silent on a carve that gave `0x178` the handler `0x4bdc00`, which is `0x2AF SMSG_PET_BROKEN`'s and raises `ERR_PET_BROKEN`, body unread; `SMSG_PET_ACTION_SOUND` (`0x324`) is a bark, not a sound id — its two talk values reach the bark dispatcher `0x623a40` at states 1 and 2, i.e. `CreatureSoundData` columns 28 and 27, and the shipped DBC names them from the other side: exactly four of its 406 rows carry them, the Imp, Succubus, Doomguard and Voidwalker voices, as `A_<demon>_ORDER`/`A_<demon>_KILL`; `Latch::Voice` now carries the bark state as a number and every one-shot creature vocal goes through one `play_bark` that is `0x623a40` — aggro at 0, the pet barks at 1 and 2, the death cry at 4, which had been on a bare `play_kit` off the slot so a pet bark could talk over it — with the reference's `jle` priority rule, because under the old boolean slot a demon's voice would have lost every race against its own aggro flares and been inaudible; it is a warlock-demon surface end to end — vmangos only sends it for a summoned pet, at a 10% roll per order, and a hunter's wolf has neither the columns nor the packet; and the fifth opcode surfaced because the cross-check went looking for column 29's consumer: `SMSG_PET_DISMISS_SOUND` (`0x325`), handler `0x604140`, reads column 29 twice (`0x6041c4`/`0x6041dc`) off a row resolved fresh by `CreatureModelData` id rather than the cached `[unit+0xb40]` — which is why a census over the cache had called the column dead — and plays `A_<demon>_DISMISS` at the packet's point with `z + 1.0`, no unit and no latch; column 29 is loaded, the column nothing reads is 22 (zero on all 406 rows), and the dismiss handler is built although vmangos never sends the packet (2039)
- every UV scroll and animated tint in the world had been frozen for three days: 1940's deferred realization parks a freshly built `WowModelMaterial` behind a reserved handle until something view-visible binds it, and `doodad_anim::register_uv`/`register_tint` still reached for the store alone — `Assets::get_mut` answered `None` for every registration, the slot was freed, the registry stayed empty and the shader read the pinned-zero identity row for every batch, silently, because the `else` branch was a bare `return`; the fix is the address, not the timing: `Parked::value_mut`/`value`/`holds` reach a value wherever it currently lives and `lazy::with_material_mut`/`with_material`/`holds` publish that, so the one-shot build stamp lands in the parked value and rides realization in — realizing it to stamp it would have thrown away exactly what deferral buys; the tick's eviction on `Assets::contains` read a parked material as dead and asks `lazy::holds` now, the empty-registry early-out says so out loud when marked parts exist, and two instruments ship — `WOW_MATANIM_PROBE=<secs>` prints per animated part whether it is registered, whether its loop moves, what its table row holds and whether it is drawn, and `WOW_WORLDVIEW_AT=x,y[,z]` opens the server-less viewer anywhere; measured at the reported waterfall: 0 uv / 0 tint entries before, 87 uv / 6 tint around the Elwynn valley after, its two sheets on slots 1 and 2 with live rows −0.2122 and −0.4246 — the authored 1:2 ratio (2038); and the paper doll and the inspect pane are lit from the model's right: `CGLight+0x24` is the direction the light PROPAGATES — the `CharacterModelBase` ctor `0x505680` writes `(0, 1, 0)` and `0x71bce0` negates all three components before building the SH basis — so `model_pane_light()` hands the packer the ctor's vector as written where 0638 had negated it on the way in and lit both panes from the left; the ambient `(0.7, 0.7, 0.7)`, the diffuse `(0.8, 0.8, 0.64)` and the side-light analysis all hold, which is why the A/B that approved 0638 could not tell the two readings apart (2034)
- a unit tooltip whose name query has not answered titles `UNKNOWNOBJECT`, not a blank gold row: the reference's builder `0x529fe0` reads its NAME line through `CGUnit_C::GetUnitName 0x609210` — the same function `UnitName 0x517020` delegates to — and every miss on its three legs, the null creature record included, ends at the one `FrameScript_GetText("UNKNOWNOBJECT")` tail at `0x609324`, so `unknownobject` moves into `unit` proper and the plate and the verb share it, read out of the VM's `_G` with the binary's "Unknown Being" behind a missing global; the faction line stops waiting for the creature record — its entry gate `0x612610` returns 1 when there is none, so it resolves off `UNIT_FIELD_FACTIONTEMPLATE` and keeps only `HIDE_FACTION_TOOLTIP` for when the record lands; the reference's placeholder sticks for the whole hover (stock `GameTooltip.xml` registers `UPDATE_MOUSEOVER_UNIT` alone) and our line-change repaint, which replaces it when the answer lands, is kept (2040); a unit predicate answers the NUMBER 1 or nil, never a Lua boolean: `UnitExists`, `UnitIsDead`, `UnitIsGhost`, `UnitIsDeadOrGhost` and `UnitOnTaxi` pushed a boolean and twenty-three more open-coded the right shape in eighteen places, where the reference pushes through `lua_pushnumber 0x6f3810` or `lua_pushnil 0x6f37f0`; all twenty-eight now reach 1830's one push site `binding_abi::predicate` — via `unit::unit_predicate` for a `UnitState` field, directly otherwise — `UnitIsCharmed`'s `Number(1.0)` and `ClearTarget`'s hand-branched nil included, and `UnitOnTaxi` gains the reference's `Usage:` raise (gate `0x517a48`) where a nil token had raised mlua's type error; truthiness is why it survived (`if UnitIsDead(u)` reads the same either way, and `== nil` inverts), nine `eval::<bool>` tests were vacuous under mlua's coercion and `taxi.rs` had pinned `== false`/`== true` — the one shape the reference cannot emit — so a table-driven test covers all twenty-eight on both legs, and `UnitIsAFK`/`UnitIsDND` now say they are ours (zero occurrences image-wide) (2043); and the quest log's stock title width cap — `275 − 15 − tagWidth`, live off the chain since 1944 — ellipsizes on one line rather than wrapping into the 16-unit row: 1873 declined it reading an explicit width as a wrap width, but the title FontString declares `y="10"`, and a declared height arms both overflow regimes, so a 258 px title in a 206 px box paints as one line ending in three ASCII dots — the reference's own result, its ellipsis gate at `0x771ec0` being `boxW > 0 && (maxLines != 0 || boxH > 0)`, which only an auto-height FontString escapes; two tests pin the render law on the real FRIZQT face and the geometry the stock file hands it, including the cap's per-row-button stickiness, which is the reference's too (2036)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 19:39:11 +07:00
SamandClaude Fable 5.1 3baf0c1750 Linux and Windows get their sound back, and emotes refuse while moving
- every build off macOS has been silent since the 09-02 snapshot (B356): the non-macOS device layer was a stub that opened nothing, and nothing had ever compiled the client for another target; that layer is now the same output stream over cpal — one seam, not two stacks: the ring, the render thread and the rebuild-on-device-change loop are one code on every platform, only the device file differs; the audio thread never allocates, a lost or changed default device is noticed at kira's own cadence, and realtime is `SCHED_FIFO` on unix and MMCSS "Pro Audio" on Windows, either refusable, the ring turning a refusal into a degradation; and the two instruments this bug says were missing exist — a Linux and Windows cross-compile under `clippy -D warnings`, and a live output test that opens the real device and asserts the ring never ran dry, because compiling is not sounding (1920)
- an emote that requires standing still now refuses on the move: `CheckEmoteEligible`'s `0x4000` arm writes an out-param that `DoEmote` turns into a red "You can't do that while moving!" and aborts; 20 emotes of 78 can raise the line, read off the shipped `Emotes.dbc`; SWIMMING is not in the mask, so a swimmer holding still emotes fine, and `IsSelfControlled` is TRUE for an ordinary player, so a feared or confused player emotes away happily (1904); and the cast path gains its equipped-item rung — the button greyed and pressing it still sent: the reference short-circuits to "fits" on four gates, not one, the miss being a zero `EquippedItemSubClassMask`, which means no item required at all, and the rung sits above the crowd-control validator, so a player both stunned and missing the weapon is told about the weapon; that ladder has six arms, not three — CHARMED, FLEEING and CONFUSED join (1925)
- the quest reward's money is the reference's own widget: the tail of the shared `QuestFrameItems_Update` drives `questState.."MoneyFrame"` through `MoneyFrame_Update`, and the two hand-built coin trios become one `MoneyFrameTemplate` instance each at the reference's anchors, which our coins already used; the progress panel stays on coins, being the required-money flow, not the reward one (1928); and the flight master's `!` joins the questgiver sweep, because the green `!` and the gold `!` are the SAME slot, `unit+0xb2c`, torn down once before both queries re-issue; the mouseover re-ask is refuted — the only taxi-status sender has one live caller — and the two queries are not symmetric: the questgiver's goes out on every sweep, the taxi one only when the unit's key moves or on a full sweep, as the wire shows at Dungar Longdrink: 68 questgiver status queries against 1 taxi-node one (1918)
- `GetSpellTabInfo` out of range answers `nil, nil, 0, 0` — the reference returns four values on every path that returns (`0x4b3ce0`), and ours answered a single nil copied from another binding's shape; it matters because stock `SpellBookFrame_OnLoad` calls it unconditionally and then does `id > (offset + numSpells)`, which a nil raises on and (0+0) makes true, hiding every button on the page — the behaviour to match; the argument raises the reference's usage string when absent, and the index truncates toward zero THEN decrements (1931); the perf probe gains a `WOW_PROBE_UNCAP=vsync` arm, because the uncapped present mode is a request, not a schedule — measured, it taxes the number it was meant to pin (11.80 `cpu_ms` uncapped against 14.65 under it), so the default stays uncapped; and the last three test temp paths are keyed on the pid, after concurrent runs raced on one fixed shared file

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-04 20:52:22 +07:00
SamandClaude Fable 5 31706821cd the combat log's wire completes, and PlayerModel is its own class
- the combat log's last eight wire bodies decode — PARTYKILLLOG, SPELLINSTAKILLLOG, PROCRESIST, SPELLORDAMAGE_IMMUNE, SPELLDISPELLOG, DISPEL_FAILED, ENCHANTMENTLOG, SPELLLOGEXECUTE — each layout read off vmangos's own writer and pinned by a byte-exact golden (DISPEL_FAILED carries no count, the list runs to the end of the body; SPELLLOGEXECUTE's POWER_DRAIN row is (amount, power, multiplier), verified at the client's bytes as well as the server's); with the eleven chat types that were waiting on them, 44 chat types become 55 — 29 sentence families, the death pair and the aura rows riding descriptor diffs because 1.12 has no "unit died" opcode, plus the six trailers ((glancing), (30 blocked), (12 absorbed) and kin) that had been missing from every family; also corrected: the durability-death line was going to the red error frame with hard-coded English, and SMSG_SPELLNONMELEEDAMAGELOG lacked its split-damage branch and its blocked wording
- PlayerModel is its own frame kind: SetUnit, RefreshUnit and SetRotation were published on Model, but they belong to the <PlayerModel> method table (0x84f1fc), and the client's lookup chains derived → base only, so a plain <Model> can never reach them — they got there from a strings scan read as ownership, and a strings hit answers whether a name EXISTS, not who owns it (SetUnit's one pooled string is referenced by the PlayerModel and GameTooltip tables and by no Model entry); the new PlayerModel → Model → Frame → Region chain unblocks CreateFrame("PlayerModel"), which is how pfUI builds every unit-frame portrait — and through pfUI, pfQuest and ShaguDPS
- a nil where a number goes is four shapes, not one law: a census of all 408 widget-registrar entries found 62 numeric positions that validate-or-raise, 43 that gate then stage an alternate, 64 that silently coerce to 0.0 and 5 that gate then store nothing — so SetMaxLetters(nil) completes and stores 0, "no limit" (the argument COUNT is checked exactly, the type is not), and the backdrop colour setters are asymmetric on purpose: a nil r/g/b channel is 0.0 but a nil alpha is OPAQUE, and reading every nil as 0.0 would have turned those borders transparent; both setters now also quantize ×255 into the packed 0xAARRGGBB byte quad the client actually stores, so 0.09 reads back as 23/255
- two more addon-killers: LIGHTYELLOW_FONT_COLOR_CODE was the one missing line of Fonts.xml's seven-line colour block (aux-addon concatenates it at file scope and died on its own first line), and the guard now enumerates the whole block l.4-20 instead of the names an earlier round had noticed absent; and the unit frames publish the reference's names — 1.12 has no *PowerBar anywhere, every bar is ManaBar and the colour table is ManaBarColor (8 of the top-20 addons read it by name), so ManaBarColor is the published table our own bars index into, with 22 more regions renamed to the reference's own

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-31 19:54:58 +07:00
SamandClaude Fable 5 a48441bca8 trilinear ships on, and every line of text rounds once
- the texture filter is the reference's policy — two globals, one process-wide mode, no per-texture override — registered as real CVars every sampler reads: the hardcoded trilinear + aniso 8 at five sampler sites had been read off an apitrace of an install whose `Config.wtf` set `anisotropic "16"`, a configured value taken for a default; and `trilinear` ships ON, because the registrar's `"0"` is not what a fresh install renders at — first-launch hardware detection overwrites sixteen video CVars from its hardware table, both rows a real GPU can reach set trilinear, and the reference's own `gx.log` on this machine names that row — while `anisotropic` is not among the sixteen, so anisotropy stays off
- the MSAA device clamp ran before the config was read, so a saved `gxMultisample = 8` carried from a machine that offers it still killed the render thread on one that stops at 4; the ceiling is now a property of the write itself, covering the config file, `SetCVar`, the dropdown and any writer added later — and an empty device list is no opinion, so headless runs pass through
- one line, one rounding: at a fractional display scale the per-glyph snap's f32 round-trip moved half-pixel ties by an ulp, so caps and ascenders rounded up while x-height letters stayed — the letters left the baseline, the thing the reporter photographed; the line's two anchors now round once and each glyph adds integer device-pixel offsets, so a line cannot be anything but flat at any scale, proven over 15,680 layouts — and `$WOW_DPI`'s size guard, which compared a physical request against the logical size and refused every non-1x run, now reads the unit it is denominated in
- a test that panics while holding `ENV_LOCK` no longer poisons the tests behind it — one missing knob resource had reported as twelve red tests, eleven naming the mutex and none naming the bug; there is nothing for the poison to protect, since the guard's `Drop` restores the environment during the panicking test's own unwind

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 15:15:45 +07:00
SamandClaude Fable 5 034c48f2cb raid tab art is back, set bonuses fit, and 12 voices bound the mix
- the mix is bounded the way the reference's is: FMOD gave 1.12 exactly twelve software channels and nothing else limits a priest buffing five people — ported as a 12-voice ceiling (a probe capture read 42 live voices at +13 dBFS, the limiter pumping 13 dB to hold it), plus same-kit copies started in one frame capped at two, since five sample-aligned Fortitudes are one sound +14 dB; `$WOW_SOUND_PROBE` records the mix before and after the limiter with a timeline, F9 stamps the moment
- the raid tab draws its window art again — its four corner textures were written with single backslashes, which Lua drops silently, and a sweep now reads every archive path a shipped Lua chunk names; a drag whose release never arrives (pointer left the window, a second press) ends instead of gluing the frame to the cursor for the rest of the session, and `WOW_PROBE_DRAG` drives any UI drag headlessly in a live client
- the item tooltip fits its set bonuses again: an emptied line kept its last measured height forever (a pooled cell with no way to say "empty"), and the two blank rows above the set block are the reference's own literal (a space and a newline) — a whole row each, not the empty string
- an equipped BoE or BoP piece reads Soulbound: the bind line reads the item's own flags through the client's bound predicate instead of the template's bonding, one predicate shared with the enchant cursor's bind confirm

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-25 18:25:07 +07:00
SamandClaude Fable 5 924cdd903f glyphs at their true size — the ladder that scrambled text is gone
- text rasterizes every glyph at its exact on-screen size, on demand — the fixed size ladder and its rescale-after-snap are gone, the machinery that left names scrambled and lines off their baseline after a window resize
- the worldport ack now rides the load's completion the way the real client sends it, and the near-teleport echo stands alone as the whole handshake
- a floating combat number whose anchor leaves the screen is destroyed rather than dragged to the border, and a trailing line break ends its line instead of opening a new one
- entering the world, the loading cover provably presents before the UI builds behind it, holds the world's audio while it is up, and fails closed

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:45:34 +07:00
SamandClaude Fable 5 4585a12259 the same-continent teleport fall is found and fixed
- a teleport between cities on one continent could release you before the destination had streamed in and drop you under the map — the residency publish now waits for the snap and names the tile its facts are about, so the loading screen provably holds until the ground is real
- nameplates stop jittering: plates of units you cannot see no longer shove the visible ones around, and the snap rides the correct pixel grid
- unit-name text takes its first fix — a string fits the width its own measure reported, and a re-bake reaches the screen
- books and plaques render through a real SimpleHTML widget inside a real ScrollFrame — strict parse, per-element fonts, block layout, and a scroll range measured from the whole subtree

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-16 17:43:01 +07:00
SamandClaude Opus 5 8d3d805ec8 the guild window opens: roster, ranks, notes and the message of the day
- the social window's third tab is live — the roster in both its views, the member card with its promote/demote/remove verbs, the rank editor's thirteen permissions, the guild information board, and the invite, leave, kick and note dialogs behind them
- setting your hearthstone at an innkeeper now asks before it takes and says so afterwards, and the innkeeper's gossip lines get their own icons back
- unavailable talents grey out, and every greyed icon in the client now goes through the same luminance fold the original used
- the buff tooltip's remaining-time line reads gold with the client's own hour/minute ladder, the macro editor takes a shift-click to paste a spell, trainer rows repaint after a purchase, and a long book page stops flickering its ellipsis

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-16 17:38:54 +07:00
SamandClaude Fable 5 ebb9a43cc9 the wheel binds, one AddOns screen, text at the true scale
- SetBinding accepts the mouse wheel — the refusal was ours, not the client's — so wheel-bound action buttons press and release like the real thing
- one addon load law: a single arbiter for "can this addon load, and why not", with the out-of-date version gate live
- one AddOns screen — the character-select list, wearing the authored reference layout: roster dropdown, per-character tri-state checkboxes, force-load; the ESC-menu panel is retired, since 1.12's own menu has no AddOns row
- the glyph atlas follows the window scale, so maximised text stops washing out and a title stops measuring wider than it draws; an unpositioned frame positions nothing, instead of landing at the screen origin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 19:31:56 +07:00
SamandClaude Fable 5 762646cd61 a width read answers in its own tick, and Trade stays /2
- GetStringWidth answers the moment the text is set — the client's width law is a per-glyph table, no shaping pass — so Bagnon's character dropdown stops collapsing to 40 pixels
- a chat channel keeps its number when another channel leaves: Trade stays /2 through a teleport
- the login splash, the zone music and the minimap label wait for the world under the player instead of announcing whatever tile happened to be loaded
- ending a session empties the chat window and the input box's history

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-14 19:27:04 +07:00
SamandClaude Fable 5 bdef9cd790 benilla-world: the engine becomes its own crate
- the world engine splits from the game into a benilla-world crate — 112 files, and the wall is ratcheted to zero names of the game inside the engine
- the engine's public doorway is counted and gated at 32 items, and the render materials, shaders and asset cache move down to benilla-assets
- the game's files leave the engine's asset root — game:// is its own space — and a spike binary boots the engine with no game attached
- weather rides along: snowfall emission is denominated in the reference's frame cap, and a long teleport retires the stranded precipitation field instead of leaving it behind

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 18:13:44 +07:00
SamandClaude Fable 5 cd58f90bec a chat link is one token now, and tooltips print true costs
- a chat link edits as one atomic token: the caret can't land inside it, its colors survive, one keypress removes it whole
- spell tooltips print the resolved mana cost through the reference's own table walk, and the "% of base mana" line dies
- the spirit healer's confirm comes back after a cancel and wears its warning icon
- the dressing room's pane is a live animating model, and a tried-on bow lands in the hand

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-07 16:02:39 +07:00
SamandClaude Fable 5 3b6975fdef the pet gets its frame, flooded rooms fill, and macros really fire
- the pet gets its own unit frame beside yours, and an Attack order no longer latches as the standing command
- five rooms that always read bone dry hold their water now, the Deeprun Tram's flooded stretches among them
- macros fire through the client's own chat door, and the macro window fits its tabs and scrolls its icon chooser
- the options window grows a Nameplates page and its first dropdown, and the name-display switches apply live in the world

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-05 19:03:43 +07:00