GaruCombatText

A focused, dependency-free combat-text addon: a running tally of the damage you deal to your current target (broken out per source) and a meter of healing you receive (per spell).
Back to Files

v2.1.0

File nameGaruCombatText-v2.1.0.zip
Uploader
GarufoGarufo
Uploaded
Oct 18, 2026
Downloads
7
Size
176.5 KB
Flavors
Classic TBCForever
File ID
9247875
Type
R
Release
Supported game versions
  • 2.5.5
  • 1.60.1

What's new

Garu Combat Text

v2.1.0 (2026-10-18)

Full Changelog Previous Releases

  • Keep Thorns counted through the fight, as the trace showed it
    The first trace with Thorns up showed three things the damage-shield rule got
    wrong, which is why it only came right once the fight was over and the meter
    corrected it.
    Order. The shield's hit on the attacker is reported at once; the report of the
    melee blow on you that set it off comes 0.30-0.36 s later, every time -- as
    melee always is on this client. The rule waited 0.15 s for the blow, so every
    shield hit gave up just before it came. It now waits 0.6 s, and each blow claims
    one waiting shield hit, oldest first, so two mobs striking give two. A blow
    reported first still claims a shield hit within 0.15 s. A blow absorbed or
    blocked whole still counts: it still sets Thorns off.
    Hidden buffs. Thorns was placed at 26.20 and then not even considered at 28.24,
    still up. In combat the game hides your buffs from addons, and the reader stopped
    at the first hidden one, so a re-read mid-fight found nothing and let Thorns go.
    Now the known shields are asked for by spell (C_UnitAuras.GetPlayerAuraBySpellID)
    before any list is walked; the walk steps over hidden buffs instead of stopping;
    and in combat no shield is let go -- only out of combat, when the buffs can be
    read and it is really gone. What is read goes to the recorder as A lines.
    The blow that kills. A mob that dies in the shield's moment never has its blow
    on you reported, so a waiting shield hit on your target that cleared in that
    moment is the shield's anyway. That also needed the fight to be known after a
    /reload in the middle of one, when no PLAYER_REGEN_DISABLED comes: the state is
    now asked of the game at PLAYER_ENTERING_WORLD.
    The recording is the third fight in tests/fights/, replayed every run: 19 of 19
    hits placed. The replay learned A lines, a /reload restarting the recorder's
    clock, and PLAYER_ENTERING_WORLD on a P line. Engine tests cover the measured
    gap, two mobs, a hidden buff ahead of a shield, Thorns found by spell with the
    list hidden, and a shield kept through unreadable combat then let go after.
    Six more mutants, 39 in all.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Count Thorns and other damage shields
    Thorns damage went uncounted on both clients, for two different reasons.
    Forever: a damage shield's hit has nothing of yours to be matched to. Thorns was
    cast minutes before, on yourself; its damage lands on whatever melee-hits you,
    in that same moment. So a Nature hit with no cast, DoT or swing to explain it
    was taken for someone else's. Now, while a damage shield of yours is up, an
    otherwise unexplained hit of its school on an enemy is the shield's when it
    comes in the moment (0.15 s) a melee hit landed on you -- or, the order of the
    two not being fixed, when that hit follows within the moment. Your casts and
    DoTs are asked first, so a Wrath landing then keeps its hit. Which shields are
    up is read from your buffs (C_UnitAuras, checked for secrecy first, as FCT
    does): Thorns, Lightning Shield and Retribution Aura by spell, every rank as
    PrettyTooltip's SpellRanks lists them -- the recorder showed Thorns' description
    not loading when cast -- and any other by its text ("damage to attackers", "any
    enemy that strikes"). Only one you cast counts: another druid's Thorns on you,
    or a party paladin's aura, is theirs. A spell hitting you does not set it off.
    Classic: the combat log reports a damage shield as DAMAGE_SHIELD, which the
    feeds never listened for. Damage Dealt now counts it, Actions its resisted form,
    and Damage Taken an enemy's shield hurting you.
    Engine tests cover Thorns in both orders, without a blow on you, after a spell
    on you, beside a Wrath landing that moment, someone else's Thorns, and a shield
    known only by its text; the load test sends DAMAGE_SHIELD through the Classic
    combat log. Six more mutants, 33 in all.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Count your target, or every enemy as one set for the fight
    Damage Dealt and Actions were per target only. Each now has Count: Your target,
    as before, or Every enemy -- everything you hit, as one set of totals for the
    fight, kept through it and for Hold after combat (8 s) after, then faded. The next
    fight's first hit clears it, not combat starting: the opening blow is reported
    just before the game says combat began, and clearing then would lose it. Damage
    Taken and Healing were whole-fight already.
    The engine carries it (Meter.AddScope): a feed counting every enemy keys its
    stream once for the fight rather than by your target, keeps no per-target memory,
    and a target change no longer re-switches a stream to the key it already has --
    which, with no memory to restore from, would have wiped it.
    Classic: the combat log already reports every hit you land; the feed stops
    throwing away the ones not on your target (anything but yourself counts).
    Forever: other enemies' hits are only reported on their nameplates, so a feed
    counting every enemy reads those too -- not your target's own plate, which
    reports the same hit again, nor a friend's. Casts and DoTs already remember the
    unit they were aimed at, so a DoT ticking on the mob you left is still yours.
    Two rules are new. A swing remembers its unit, so it cannot take a hit on another
    mob. An area spell (its text says nearby enemies, all enemies within, up to N
    targets...) claims one hit on each enemy in its cast's own moment, and an area
    DoT ticks on each enemy on its own beat. And once the fight is over each spell is
    set to Blizzard's damage meter's figure, which for every enemy is exactly the
    feed's: every hit, the killing blows, and whatever the matching missed or took
    wrongly. Hits on enemies with no nameplate shown cannot be seen live; the
    meter still puts them in afterwards.
    Engine tests cover a DoT on a mob you left, one hit reported twice with a second
    bolt in flight, a friend's plate, an Arcane Explosion on three mobs, a swing that
    must not take another mob's hit, per-target mode ignoring plates, another mob's
    resist marked as not on your target, and the meter's totals after the fight. The
    load test follows every-enemy totals across a target switch and a nameplate,
    through the meter's correction to a fresh start, and on Classic counts combat-log
    hits on any enemy only when asked. Nine more mutants, 27 in all.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Keep the settings window out of chat
    Unlocking the anchors, resetting and copying each printed a line to chat, so
    working the window left a trail there. The window already shows what changed --
    its header buttons, the page, and now the info panel for a copy -- so none of
    them print any more. Typed commands still answer (/gct lock, /gct reset, /gct
    test), since nothing else would tell you they worked, and the probe's report
    stays in chat, which is the whole of what it does.
    The load test works the window -- anchors, preview, a copy, a checkbox, a reset
    -- and fails on any line written to chat; a mutant that puts one back is caught.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Copy any feed's settings to any other, or all; drop the page blurb
    Copying was fixed pairs: each feed could only take one particular other's look,
    always mirrored, always with its position. A feed page's header now has Copy
    to...: every other feed, and All other feeds. Two choices ride along and are kept
    between uses -- Position too, and Mirror left to right for two feeds that sit as
    a pair. Layout and text go across; what a feed counts (thresholds, pets, mana)
    stays, since that means something different on each. A line in chat says what
    went where.
    The grey description under each page's title is gone. Its text still shows in
    the info panel whenever no setting is hovered.
    The load test copies to one feed, to all, with and without the position, and
    mirrored, on every client including the bare one, where the menu is the old
    dropdown. Two more mutants guard it. The stub stops answering unknown lowercase
    globals with a stub: the test's own cleared lastMenu had been reading back as an
    empty menu, which hid that the bare client never reached its fallback.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Rebuild the settings window in FlareUI's style
    The old window was five tabs of controls stacked one under another, four of them
    repeating the same fifteen, with long notes inline between them. It now follows
    FlareUI (Malkkav, MIT), the way the other addons on this client look: the pages
    in a list on the left with a dot for whether each feed is on, the page's
    settings in the middle under headings that fold, and on the right what the
    hovered setting does. Blizzard's own controls throughout -- checkbox, the
    Settings stepper for choices, the minimal slider with a box to type a value --
    in FlareUI's bronze, over its tiled background.
    More compact: the explanations moved off the page into the info panel, labels
    are short, on/off settings sit two to a line, Enabled became the switch in each
    page's header, a feed that is off shows one card with Turn on, and Unlock and
    Preview sit in the window's header. The client's note (worked out on Forever,
    post-fight summary, grouped by school) is a single bronze strip; hover it for
    the whole story. Search, at the foot of the list, finds a setting on any page
    and names the page it is on. Tab order from 1.16 is kept.
    Pages are data now: each row says what it is, what it does, how to read and
    write its value and when it applies, and the four feed pages share one builder,
    so a setting exists once instead of four times. The window is drawn from that
    data after every change, which is how a setting that stops applying goes and
    comes back. Blizzard's damage-number toggle moved to a General page, with the
    Forever tools (probe, recorder), the post-fight summary's settings and Reset,
    which now asks first.
    Along the way: a reset refreshed only two of the four feeds and left Damage
    Taken and Actions looking as they were until a reload; /gct lock and /gct test
    now redraw an open window.
    The load test drives the window the way a player would -- every page, every
    checkbox clicked and clicked back, every choice picked, every slider dragged to
    both ends and typed into, search, a feed off and on, reset -- on Forever, on
    Classic, and on a bare client without Blizzard's newer templates so the
    fallbacks run too: 93, 91 and 91 controls. Four more mutants guard it. The stub
    grew honest along the way: frames keep their shown, checked and typed state and
    their scripts, sliders report through OnValueChanged, and template children
    exist only where a template made them.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Settle the killing blow from the damage meter after the fight
    The blow that kills your target was never counted, and no matcher could have
    counted it: it is never reported. In the recorded kills the killing swing's
    PLAYER_SWING and the target clearing arrive in the same frame, and the
    UNIT_COMBAT that would have followed -- a melee hit is reported about half a
    second after its swing -- has no "target" left to arrive on. A kill by a DoT
    tick was reported, in that same frame.
    The amount is not lost, only late. C_DamageMeter hides its figures during
    combat and gives the fight's totals once it ends, so a target that clears in
    combat within 0.1 s of a blow of yours is noted as a kill by that attack, and
    when the meter speaks, whatever it counted for that attack beyond what the feed
    showed is credited to the dead unit -- on screen if it is still selected, else
    into what is remembered for it (Meter:BumpGUID), so it is there on the next
    click. Only the killing attack is topped up, only by what is missing (a kill
    whose blow was reported adds nothing), and not at all when the gap is more than
    three of that attack's largest hits: that means other mobs' damage is mixed in,
    and splitting it would be a guess. Pets are left out; the meter keeps them apart.
    The recorder now notes kills (K), the meter's figure beside the feed's (M), and
    any report on the dead unit's nameplate, which would be a cheaper live fix if it
    turns out to exist.
    Both recordings replay with every hit placed (8 of 8, 18 of 18). Engine tests
    cover the settled kill, the reported kill, the refused gap and a target switch
    that is not a kill; a load test follows a kill through to the corpse's total.
    Three more mutants guard them. The harnesses also gain a real wipe(): the load
    test's catch-all stub had been answering it with a do-nothing.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Keep the trace extractor with the tests
    Turning a /gct trace recording into a replayable fight is the step that feeds
    tests/fights/, so it lives beside them rather than in a session scratchpad.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Widen the melee window to what this client actually does
    The first /gct trace from a real fight cleared the suspect and found the cause.
    PLAYER_SWING arrives as (weapon speed, swing type) -- "3.107 0" from a 3.1 s
    weapon -- so the swing type was being read from the right argument all along.
    What was wrong was the window: here a melee hit lands 0.48 to 0.52 s after its
    swing is reported, and FCT's 0.5 s window put half of all melee on each side of
    the line. One hit of 43 was placed, one of 47 was not.
    One second still sits below any weapon's swing interval, and a swing is only
    ever claimed once, so it cannot take two hits.
    The recording itself goes into tests/fights/ and is replayed through the current
    matcher on every run: every hit on the target must be placed. All 8 now are --
    Moonfire's two openers and four ticks, and both swings. Future recordings drop
    in beside it, so the matcher keeps being checked against what the game really
    sent rather than against fights written by hand. Cast lines now also record a
    spell's duration and whether it heals, which the replay needs to rebuild its
    description.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Add a fight recorder, and keep the tests in the repo
    The first real /gct probe settled the question the port stood on -- UNIT_COMBAT
    amounts are readable in combat, on the target and on you -- and raised two more:
    3 of 5 hits on the target went unplaced, and only one swing was seen in a fight
    with melee in it. Totals cannot say why. Suspect number one is PLAYER_SWING's
    payload: which argument carries the swing type was taken from FCT, never seen
    here, and reading the wrong one would drop swings silently and leave their hits
    unplaced.
    /gct trace records every cast, every swing with its payload exactly as it came,
    every UNIT_COMBAT report, and what the matcher made of each, into the saved
    settings. A /reload writes it to disk, where the fight can be read back and the
    matcher fixed from evidence.
    The probe also put "casts seen: 0" beside a placed cast: the opening cast lands
    before combat starts, so the reset at PLAYER_REGEN_DISABLED counted it in the
    previous fight. The opener now counts in the fight it opened.
    The offline checks move from a session scratchpad, where the last checker was
    lost, into tests/: run.py compiles every file under LuaJIT, replays the matcher's
    scripted fights, loads the whole addon against a stubbed game as both Forever
    and Classic, then breaks the addon seven ways on purpose and fails if any
    breakage goes unnoticed. The runtime installs into tests/.lupa (ignored); tests/
    is left out of the packaged addon.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Bring live combat text back to Forever, on UNIT_COMBAT
    The post-fight summary rested on a conclusion that was wider than what had been
    measured. The addon combat log never fires and C_DamageMeter hides its figures
    in combat -- both still true -- but UNIT_COMBAT was never tested, and it is
    exactly what is needed: the event Blizzard's portrait hit-numbers use, live and
    unhidden, carrying each hit's amount, school, crit flag or kind of miss on your
    target and on you. Forever Combat Text by Tarolg (MIT) runs on it.
    What it does not say is who made a hit or with which spell. Everything about
    your own side is readable, though -- your casts with their spell, your weapon
    swings, and each spell's own description -- so UnitCombat.lua matches each hit
    on your target to them by time and school: a DoT tick on its beat, then the
    oldest cast still in flight, then an unbeaten DoT, then a swing (including one
    reported just after its hit), then your pet. The windows are the ones FCT
    measured in real fights. It is worked out, not read: near-exact solo, and a
    teammate's same-school hit inside your window can be taken for yours.
    The feeds subscribe and Bump their meters exactly as their combat-log handlers
    do, so the whole live apparatus returns: fades, rolling windows, per-target
    memory. Damage Dealt is per spell again with crits, schools and mana spent;
    Actions is back; Damage Taken is per school, since the attacker's spell is not
    reported; Healing Received counts every heal on you, your own on their spell
    and everyone else's on one line.
    A DoT starts on a 3 s beat guess until two ticks are measured, so a first-fight
    tick landing beside a bolt in flight does not swap the two. A tick-only spell
    never waits for a direct hit, or a Corruption cast would take the bolt cast
    after it. A hidden amount still uses up its cast but adds nothing, since a
    secret can be drawn but never summed.
    /gct probe now reports, per fight, how many hits came readable and how each was
    placed. The summary stays as a one-line fallback (FORCE_SUMMARY) in case the
    probe finds amounts hidden in combat on this client.
    Co-Authored-By: Claude Opus 5.5 noreply@anthropic.com
  • Replace the two guesses about this API with what it actually returns
    /gct probe over a real fight answered both open questions, and both answers were
    no. Delete the speculative code and say plainly in the UI what is not available,
    rather than leaving feeds that quietly show the wrong thing.
    There is no per-target breakdown. EnemyDamageTaken does list one source per enemy
    -- keyed by creatureID, with sourceGUID nil, which is why the GUID-keyed capture
    found nothing -- but that source's spell rows collapse into a single entry with
    spellID 0 carrying the enemy's whole total, attributed to whoever dealt it.
    Per-enemy totals, no spell split, and these feeds are per-spell. So every corpse
    from a pull shows the pull, and CaptureByTarget goes.
    Healing received is worse: combatSpellDetails.unitName is blank on a source's own
    rows -- it is filled in only on the receiving side, which is how EnemyDamageTaken
    names the damager. Nothing can be matched against it, so the filter never
    matched and the feed was silently empty. There is no healing-received meter to
    fall back to either; the enum has HealingDone and Hps and nothing incoming. The
    feed now reads the local player's own HealingDone rows, which is the healing you
    CAST -- the same number when you heal yourself, and not the same thing at all in
    a group. The tab is retitled "Healing Cast" on this client and both the note and
    /gct status say so, because a feed labelled "received" showing cast numbers is
    the kind of wrong that never gets noticed.
    The three capture paths collapse into one DM.Capture(meterType): the local
    player's rows, whichever direction that meter happens to record.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Show the summary against what you killed, for as long as you want it
    A post-fight summary was still being displayed like live combat text: it
    appeared the instant combat dropped, wherever you happened to be looking, and
    sat there until the next pull. That reads as though the numbers belong to
    whatever you are pointing at now rather than to the fight that just ended.
    Show it like the report it is. Nothing appears when combat drops; the summary
    appears when you select what you were fighting, holds for ns.db.summary.holdSecs
    (15s by default, configurable on the Damage Dealt tab), then dims out. Selecting
    something else shows that one's figures, and going back to the first before its
    window closes brings it back -- each target carries its own window, started the
    first time you look at it, so a corpse you get to late is still readable.
    The increment machinery goes with it. Diffing successive polls only ever made
    sense while the feeds were pretending to be live; a fight's totals arrive once,
    complete, so a capture is now just "read the rows and keep them". That also
    retires the unverified baseline arithmetic: SessionType.Current is per-fight, not
    a running tally -- the API carries a separate Overall session for that, and the
    one probe we have read Current 147 against Overall 501.
    Damage Dealt additionally asks EnemyDamageTaken for a per-enemy split, so two
    corpses from one pull can show their own numbers. That part is reasoned about
    rather than measured: if the session comes back in a shape it does not
    recognise, every corpse shows the fight's total, which is what they did before.
    /gct probe dumps the session so one real fight can settle it -- and the same dump
    answers whether spell rows name their other party, which is the only thing
    holding up healing received.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Only poll the damage meter while a finished fight is settling
    The 4Hz sweep ran forever, in and out of combat. Nothing is readable during a
    fight, and once the numbers land they don't change again until the next fight
    ends, so almost every call was discarded. PumpIncoming is the expensive one: it
    walks every source in the session, which in a raid is a great many calls a second
    to throw away.
    The ticker now only runs inside a four-second window opened by
    PLAYER_REGEN_ENABLED, which also replaces the fixed 0.2/0.8/1.6/3.0s poll volley
    it used to schedule -- same coverage, less machinery. The DAMAGE_METER_* hooks
    still catch anything outside the window.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Don't blank the summary when you change target
    Targeting anything made Damage Dealt disappear. PLAYER_TARGET_CHANGED calls
    meter:Retarget, which swaps the stream to whatever was stored against the new
    target's GUID -- nothing, for a fresh target -- so the feed emptied on the spot.
    That is right when the numbers are per-target, which is what the live combat log
    gives. It is wrong in summary mode, where the figures belong to the FIGHT: the
    meter reports what happened, not who you are currently clicking on. Retarget and
    the focus equivalent are now live-mode only.
    The tab heading reads "Damage Dealt (this fight)" there rather than "(per
    target)", and "Remember each target's totals" is hidden, since neither means
    anything when the data isn't split by target.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Make the Forever build an honest post-fight summary
    It was still wearing clothes built for a live feed: rolling windows, hold timers
    and fade curves, all driven off combat start and end, with the data arriving at
    exactly the wrong moment for every one of them.
    Summary mode (ns.SummaryMode, set wherever C_DamageMeter exists) now switches that
    apparatus off rather than fighting it. Feeds never fade and the healing window
    never rolls; a fight clears them when it starts and fills them when it ends, and
    the figures stay put until the next fight. That removes both reported symptoms at
    the source rather than by retuning timings: Damage Taken faded instantly because
    its hold timer began counting when combat dropped -- the very moment the numbers
    arrived -- and Damage Dealt stuck forever because nothing ever cleared it.
    The live path is untouched, so the Classic clients keep every feed exactly as it
    was, fades and all.
    Also here:
    * ns.CombatDuration uses the meter's own session length on Forever. It used to
    time from the first hit we saw, and in summary mode we see no hits.
    * The rolling-window and keep-after-combat sliders are hidden in summary mode,
    and each affected feed's page says what the client actually allows.
    * DM.IsUsable respects IsDamageMeterAvailable; /gct status reports which source
    is feeding the addon and how long the last fight ran.
    Removed, now that the question they existed to answer is settled: Probe.lua and
    its slash branch, the combat-log diagnostics (/gct debug, DiagBump, ns.registered)
    and the DiagBump calls in EnemyText. /gct status replaces them at a fraction of
    the size.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Treat the damage meter as a post-fight summary, because that is what it is
    The live sample answered it: out of combat the totals read fine (cur=147 ovr=501),
    and the instant the fight starts every one of them stops being readable for the
    whole 12 seconds. The client withholds the meter's numbers during combat and
    releases them when it ends. Neither Current nor Overall moves mid-fight, so live
    combat text is not possible from this API -- no amount of polling or event wiring
    changes that.
    So the feeds stop pretending. Each fight clears them on PLAYER_REGEN_DISABLED, and
    PLAYER_REGEN_ENABLED polls a few times over the next three seconds because the
    session isn't finalised the moment combat drops. The result is a summary of the
    fight just finished rather than a running commentary during it.
    This also explains the two symptoms that came with the report. Damage Taken
    "faded immediately" because its post-combat hold timer starts when combat ends,
    which is exactly when the data finally arrives. Damage Dealt "stuck forever"
    because that feed has persist set, so nothing ever aged it out -- clearing at the
    start of the next fight is what it was missing.
    The live sampler now separates SECRET from nil and tracks IsDamageMeterAvailable,
    since "withheld" and "absent" would have meant different things.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Re-baseline on target change instead of forgetting the totals
    DM.Reset wiped the remembered per-spell totals, which looks harmless and isn't:
    the next poll read the session's running total against a baseline of zero and fed
    the entire fight in as one delta. Switching target dumped the whole session's
    damage into the new target's feed.
    It now flags the state instead, and the next poll adopts the current totals
    silently and counts from there.
    Also adds /gct probe live: samples DamageDone's Current and Overall totals once a
    second for 12s alongside your combat state. The reported symptom -- nothing during
    a fight, everything at the end -- says the session is finalised rather than
    updated live, and if neither total moves mid-fight then live combat text is not
    possible from this API at all and these feeds are post-fight summaries. Worth
    knowing before tuning anything else.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Rebuild the feeds on C_DamageMeter
    The probe confirmed the addon combat log is gone on Forever and showed what
    replaced it. C_DamageMeter reports per-fight AGGREGATES: a session carries
    combatSources, the player's row is marked isLocalPlayer (no GUID comparison
    needed, which is just as well), and a source's combatSpells give spellID plus a
    running totalAmount.
    Rather than rewrite Meter around absolute totals, DamageMeter.lua converts them
    back into the increments Meter already speaks: it remembers the last total per
    spell and feeds the difference. Rolling windows, fades, sorting, the radial and
    stack layouts all keep working untouched, so the feeds look as they did on a
    client with a combat log. A session's totals going backwards means a new fight,
    which drops the remembered figures rather than emitting one huge delta.
    Per feed:
    * Damage Dealt -- DamageDone, the local player's rows. Works.
    * Damage Taken -- DamageTaken, same shape. Works, minus the avoid rows.
    * Healing Recv -- the shaky one. HealingDone is keyed by the HEALER, so this
    walks every source and keeps the spell rows whose combatSpellDetails.unitName
    is me. If the client leaves that blank the feed stays empty, which is the
    right failure: showing someone else's healing as yours would be worse.
    * Actions -- switched off. Dodges, parries and misses are combat-log
    facts and this API reports damage, not misses. The options tab says so
    rather than leaving the feed silently dead.
    Also gone for good, because no field carries them: per-hit crits, spell schools
    and mana spent.
    The CLEU handlers are kept behind if not ns.UseDamageMeter, so the older
    Classic clients are unaffected and keep every feature.
    ns.Truth and ns.SpellName move into Core -- the first was written for Unbuffed
    and would have thrown nil on the first poll here.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Let /gct probe <type> skip the API survey
    The survey alone fills the chat frame, so the session/source/spell dump scrolled
    off before it could be read. Naming a meter type now prints only the data half.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Add /gct probe to survey the C_DamageMeter API on this client
    /gct debug proved the addon combat log is gone on Forever: CLEU is a valid,
    registered event that delivered nothing to the addon or to a bare probe frame
    across 4s of real combat, and C_DamageMeter is present. The feeds have to be
    rebuilt on that API.
    Designing the rewrite from a retail addon's use of C_DamageMeter would be a guess
    about a different client, and guesses about this one have not been going well. So
    before writing any of it: /gct probe dumps what is actually there -- the function
    list, the DamageMeterType and DamageMeterSessionType enum members, which
    DAMAGE_METER_* events are valid, and the real field names of a session, a combat
    source, and a spell entry, including combatSpellDetails.
    /gct probe [damage|healing|taken|enemy] picks the meter type; damage by default.
    Fields are printed through a secret-safe formatter, since the secret test has to
    precede tostring or # on any value.
    Probe.lua, its TOC line and the slash branch all come out once the feeds are
    ported -- this is reconnaissance, not a feature.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Make /gct debug self-verifying
    The first run reported zero combat-log events, but could not distinguish the three
    things that produce that: the client not delivering the event, our dispatcher
    swallowing it, or the player simply not fighting during the window.
    Now it samples combat state once a second, registers the event on a private bare
    frame as a control, reports C_EventUtils.IsEventValid, and says whether
    C_DamageMeter exists -- which is what 12.x gives addons in place of the raw combat
    log. The verdict line names whichever of the three it was.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Route combat-log events by object flags instead of GUID comparison
    Every feed gated on a GUID compare -- "is the source me", "did this land on my
    target" -- against UnitGUID("player") / UnitGUID("target"). On Midnight UnitGUID
    can come back as a secret, a secret never equals a plain string, so every event
    failed its filter. No error, no output: the preview still drew because preview
    data is handed straight to the feeds, which is exactly the reported symptom.
    The combat log already answers both questions in its own object flags, which are
    plain numbers and cannot go secret. ns.SourceIsMine / ns.UnitIsMe /
    ns.UnitIsMyTarget / ns.UnitIsMyFocus read those, and ns.SameGUID keeps the old
    compare as a fallback for clients or events that don't set the special flags --
    COMBATLOG_OBJECT_TARGET in particular may not be set on Vanilla-era content.
    ns.SameGUID also reports "no match" for a secret rather than inspecting one,
    which would throw.
    Applied to all four feeds plus both mana-attribution paths, which had the same
    compare. The per-file flag constants are gone; they live in Core now.
    Also adds /gct debug: counts combat-log events for 15s across seen / passed the
    source filter / routed to a feed, and reports whether CLEU registered at all and
    whether the player and target GUIDs are secret. That distinguishes "no events
    arrive" from "a filter drops them" without guessing, if this isn't the whole fix.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Never write a CVar during combat lockdown
    Writing a gameplay CVar is protected in combat, and doing it from tainted addon
    code pops Blizzard's "GaruCombatText has been blocked from an action only
    available to the Blizzard UI" dialog. ApplyBlizzardFCT runs from
    RefreshEnemyText, which fires while fighting.
    This is fallout from the previous commit rather than a long-standing bug: before
    it, the pre-Midnight CVar name simply did not exist on Forever, so the write
    failed harmlessly inside its pcall. Resolving the "_v2" spelling made the call
    reach a real CVar, and with it the protection.
    ns.SetCVar now records the value it wants and applies it through RunWhenSafe --
    immediately when out of combat, otherwise at PLAYER_REGEN_ENABLED -- with a
    single flusher however many CVars queue up. It also skips the write entirely when
    the CVar already holds the value, which is what most calls turn out to be, so the
    protected call happens about once per session instead of on every refresh.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Handle Midnight secret values in the severity scaler
    UnitHealthMax comes back as a secret on WoW Forever once execution is tainted by
    the addon, and Meter.lua compared it directly -- "attempt to compare local 'maxhp'
    (a secret number value)" on every OnUpdate tick of every stream.
    A secret can be stored and passed around, but ordering comparisons on one throw
    and arithmetic on one yields another secret, so a font size derived from max HP
    could never have reached SetFont anyway. When max HP is secret, scale severity
    against the largest total currently in the feed instead: that is our own
    combat-log tally, so it is always a plain number. Scaling then reads as "relative
    to the biggest number on show" rather than "relative to the target's health pool",
    with the top line always at max size -- the closest thing to the original intent
    that a secret max HP leaves available.
    ns.IsSecret wraps issecretvalue, which only exists on the Midnight-era clients.
    Co-Authored-By: Claude Opus 5 noreply@anthropic.com
  • Port to WoW Forever (interface 16001, Midnight 12.x API)
    Forever serves Classic content from the Midnight client: it reports a Classic
    WOW_PROJECT_ID but only exposes the modern namespaced API and the retail widget
    set. The TOC now declares 16001 alongside 20505, so one set of files covers both
    clients, and every call site feature-detects rather than branching on project id.
    • Spell icons go through ns.SpellTexture, which prefers C_Spell.GetSpellTexture;
      the GetSpellTexture global is gone on Midnight, so every combat-text line was
      silently losing its icon.
    • The settings dropdowns were UIDropDownMenuTemplate, which Midnight removed
      outright. They are now DropdownButton/WowStyle1DropdownTemplate driven by
      MenuUtil.CreateRadioMenu, which tracks the selection and paints the button text
      itself. The legacy branch stays for clients with no MenuUtil.
    • ns.SetCVar routes CVar writes through C_CVar and picks the "_v2" spelling
      Midnight renamed the floating-combat-text CVars to. It checks the name exists
      first, because SetCVar throws on an unknown one -- that is what skips
      floatingCombatTextCombatDamageAllAutos, which is pre-Midnight only.
      Co-Authored-By: Claude Opus 5 noreply@anthropic.com

This mod has no related projects