promotional bannermobile promotional banner

CombatAnalytics

CombatAnalytics is a combat session tracking addon for World of Warcraft: Midnight

File Details

1.9-release

  • R
  • May 22, 2026
  • 610.79 KB
  • 81
  • 12.0.5
  • Retail

File Name

CombatAnalytics-1.9-release.zip

Supported Versions

  • 12.0.5

tag 55eda37c235ba4499fac3b2b941284576af2975a 1.9-release
Author:    Lazar Dilov <ldilov@yahoo.com>
Date:    Sat May 23 01:54:38 2026 +0300

Major fixes

commit f00be2635d72840d74aabcba40a9dcaae6154fb9
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Sat May 23 01:36:56 2026 +0300

    fix(stats): sum both versatility sources to match character sheet

    GetVersatilityBonus returns only non-rating % (buffs, PvP scaling,
    War Mode). GetCombatRatingBonus returns only the rating-derived %.
    The character sheet sums both. Old elseif fallback picked one and
    dropped the other, showing ~4% in Build Overview while the sheet
    showed ~24% during arena/BG PvP scaling.

commit 01224165f189affbe268c001815fba473cff7701
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Sat May 23 00:56:10 2026 +0300

    fix(summary): stop a nil session sub-table from blanking the dashboard

    SummaryView:Refresh dereferences session.metrics and session.totals
    directly in ~45 places. CombatHistoryView, DummyBenchmarkView and
    SuggestionsView all guard these with `or {}`; SummaryView did not. A
    session that reaches the dashboard before metrics/totals are populated
    (interrupted finalize, pre-metrics schema) makes the first nil deref
    throw, which aborts the entire render and leaves the tab a wall of
    blank, unlabeled bars.

    - CombatStore migration now normalizes session.totals to a table, next
      to the existing session.metrics guard. session.totals was the one
      unprotected sub-table.
    - SummaryView:Refresh normalizes session.metrics/session.totals once
      before rendering, covering sessions finalized in-session that never
      pass through migration.

commit 01224165f189affbe268c001815fba473cff7701
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Sat May 23 00:56:10 2026 +0300

    fix(summary): stop a nil session sub-table from blanking the dashboard

    SummaryView:Refresh dereferences session.metrics and session.totals
    directly in ~45 places. CombatHistoryView, DummyBenchmarkView and
    SuggestionsView all guard these with `or {}`; SummaryView did not. A
    session that reaches the dashboard before metrics/totals are populated
    (interrupted finalize, pre-metrics schema) makes the first nil deref
    throw, which aborts the entire render and leaves the tab a wall of
    blank, unlabeled bars.

    - CombatStore migration now normalizes session.totals to a table, next
      to the existing session.metrics guard. session.totals was the one
      unprotected sub-table.
    - SummaryView:Refresh normalizes session.metrics/session.totals once
      before rendering, covering sessions finalized in-session that never
      pass through migration.

commit f9d83d3c4a3da5d1360471d0cdb529ba1de6b8ab
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Sat May 23 00:52:38 2026 +0300

    feat(counters): surface Solo Shuffle win rate vs enemy specs

    The Counters screen showed "No data" for enemy specs the player had
    only met in Solo Shuffle rounds. The `specs` aggregate records one
    opponent (primaryOpponent.specId) per match, but a Solo Shuffle match
    is a single session of six rounds against rotating opponents, so five
    of six round outcomes and two of three specs were dropped.

    - ArenaRoundTracker: complete the previously-dead `opponentSpecs` field.
      EndRound now records each round's enemy specs and flags the round
      `specsIncomplete` when a slot's spec cannot be resolved;
      CopyStateIntoSession persists the flag onto the session.
    - CombatStore: add GetSoloShuffleWinRateVsSpec — an on-demand scan of
      db.combats computing round-level win rate vs a spec. Excludes
      irregular, nil-result and specsIncomplete rounds; counts a spec at
      most once per round; reports `matches` (distinct sessions) so the
      sample size is honest. Kept fully separate from the `specs` bucket.
    - CounterGuideView: add a memoized "Solo Shuffle: NN% (R rounds ·
      M matches) est." line below the match-level win-rate gauge. Round
      units and match units stay visually distinct, never merged. Also
      carries pending Counters-view refinements from prior work.

    Forward-only: Solo Shuffle matches recorded before the producer fix
    carry no per-round spec data and cannot be backfilled.

    test_SoloShuffleSpecWinRate.lua adds 15 cases for the round-scan helper.

commit 48c1ce08a6ff02ebb173005cbbf86e8d0d615c24
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Sat May 23 00:44:57 2026 +0300

    fix(damage-meter): report the real reason a damage import failed

    The PvP Performance Dashboard showed "Damage import failed: enable
    Blizzard's Damage Meter" even when the meter was already enabled. The
    import-status pipeline collapsed every failure into one code and the UI
    mapped that code to a single misleading hint.

    - DamageMeterService:ImportSession now writes a precise importStatus on
      every return-false path. Zero-candidate results are disambiguated via
      #allSessions: no DM sessions at all -> FAILED_DAMAGE_METER_UNAVAILABLE;
      sessions exist but none matched the fight -> FAILED_NO_CANDIDATE.
    - CombatTracker no longer hardcodes FAILED_NO_CANDIDATE on every import
      failure (SetImportAuthority unconditionally overwrites importStatus);
      it now passes the precise status the service resolved.
    - SummaryView replaces the partial 3-branch hint logic with a complete
      per-status hint table. failed_no_candidate no longer tells the user to
      enable a meter that is already on.

    Adds regression coverage for the unavailable, zero-session, and
    no-matching-candidate paths.

commit 32fe13a1f26e960c1ea4acd2c8a0e056e0864735
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Wed May 20 23:51:11 2026 +0300

    docs(design): Solo Shuffle per-round fusion design + spike harness

    Design artifacts for the deferred "Solo Shuffle per-round damage" item.
    Not wired into the addon — design skeleton + a throwaway in-game probe.

    The item is blocked on one unverified question: how C_DamageMeter
    segments Solo Shuffle rounds. The skeleton covers BOTH possible answers
    as co-equal capture strategies, so whichever the spike confirms needs
    minimal rewiring:
    - Strategy A: snapshot the newest Expired C_DamageMeter session each
      PostRound (works if the meter spawns one session per round).
    - Strategy B: delta-track the Current session — round N damage =
      cumulative(N) - cumulative(N-1) (works if the meter keeps one
      continuous match session). Negative delta clamps to 0; a dropped
      capture uses a carry-forward baseline; a mid-match session-ID change
      disqualifies B.
    Dual capture records both signals every round; selectCaptureStrategy
    picks A or B post-match; a single shared _applyScaledFusion scales the
    chosen per-round shape to the authoritative scoreboard total. Fused
    per-round values carry estimated (never high) confidence.

    soloshuffle-spike-harness.lua is a standalone /cadmspike diagnostic —
    one Solo Shuffle run determines which strategy (or neither) is viable.

    Both files pass luac -p. No addon code touched — implementation stays
    gated on the in-game spike result.

commit aea9ca3d3828e936a4ad08c39594a0bcf2846f51
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Wed May 20 23:36:13 2026 +0300

    feat(metrics): per-metric provenance + confidence-gated coaching

    Coaching scores were presented with uniform authority — a pressureScore
    from an authoritative C_DamageMeter import looked identical to one from a
    cast-count estimate. Under WoW 12.0.5's restrictive combat API, where
    damage data is frequently degraded, that uniformity actively misleads.

    Per-metric provenance (schema v10):
    - Constants: new METRIC_CONFIDENCE enum (high/estimated/low/unknown).
    - Metrics.ComputeDerivedMetrics now writes session.metrics.provenance —
      one {confidence, basis} record per derived score. Damage metrics map
      from importedTotals.totalAuthority; timeline metrics from
      coverage.visibleCasts; CC metrics from coverage.ccReceived;
      survivabilityScore from authority + absorb-data presence. Purely
      additive — no metric value changes (asserted by test).
    - CoverageService:Finalize now runs before metric derivation so
      timeline/CC provenance is accurate rather than UNKNOWN.
    - SuggestionEngine: damage-derived suggestions gate on the provenance of
      the metric they read — LOW suppresses, ESTIMATED downgrades severity
      and tags the suggestion summary_derived.
    - SuggestionsView: per-metric confidence tags replace reliance on the
      single session-wide trust badge; pre-v10 sessions fall back to it.
    - CombatStore: v10 migration gate (no backfill — provenance source is
      unrecoverable for stored sessions).

    Also folds in the scoreboard-anchor stickiness fix (Codex adversarial
    review finding): a late non-authoritative C_DamageMeter re-import could
    overwrite an already-anchored arena total. RestoreScoreboardAnchorIfRegressed
    reinstates the anchor in the deferred and post-finalize retry paths
    unless the re-import is genuinely authoritative.

    Tests: test_MetricProvenance.lua (11), test_ScoreboardAnchor.lua (6).
    luac -p clean; full suite failure count unchanged at the 56 baseline.

commit dcbc3f0afb4aa201028f61a3bbaba4a8721775d2
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Wed May 20 11:11:44 2026 +0300

    feat(metrics): anchor arena damage totals from the post-match scoreboard

    When C_DamageMeter import fails for an arena session, pressure/burst/
    survivability scores collapsed to zero because session.totals.damageDone
    stayed 0. WoW Midnight 12.0.5 restricts CLEU in PvP, so C_DamageMeter is
    the only live damage source and it fails often.

    Add a terminal fallback: when import yields no usable total for a
    non-Solo-Shuffle arena session, substitute the player's damage row from
    the post-match PvP scoreboard (C_PvP.GetScoreInfo, already captured into
    session.postMatchScores by HarvestPostMatchData). The scoreboard total is
    authoritative for the match damage TOTAL, so sustained-pressure metrics
    become trustworthy; it carries no per-spell timing, so burst/window
    metrics stay approximate — hence the new ANCHORED_FROM_SCOREBOARD import
    status maps to the "estimated" authority tier, not "authoritative".

    - Constants: new IMPORT_STATUS.ANCHORED_FROM_SCOREBOARD in estimated tier.
    - DamageMeterService: public GetScoreboardPlayerDamage accessor.
    - CombatTracker: ApplyScoreboardAnchorIfNeeded — shared, idempotent,
      self-healing method called from all three persist paths (FinalizeSession,
      the deferred re-import, the post-finalize one-shot retry) so a failed
      re-import cannot downgrade an already-anchored session's authority.

    Scope: arena only. Solo Shuffle excluded — its scoreboard spans all 6
    rounds while a session is one round (per-round fusion deferred pending an
    in-game spike). Battlegrounds excluded by context. Dummy/duel unaffected.
    Robust to secret values: if post-match damageDone is secret, SanitizeNumber
    zeroes it and the anchor silently no-ops.

    Tests: 5 new GetScoreboardPlayerDamage cases. luac -p clean, no regressions.

commit 4f0a94d88987d9f71b2977da0bc784a192037240
Author: Lazar Dilov <ldilov@yahoo.com>
Date:   Wed May 20 02:40:18 2026 +0300

    fix(classifier): record sessions for 12.0.5 training dummies

    Silvermoon's new Training Dummy (NPC 243214) is classified as "normal"
    rather than "trivial"/"minus", so SessionClassifier's allow-list
    classification gate rejected it after name matching. With the CLEU
    fallback removed in 831f753 (T031), no session was ever created and
    every analytics page was empty.

    - Catalog: add CID 243214 to TRAINING_DUMMY_CREATURE_IDS so creature-ID
      detection scores 100 and bypasses the classification gate entirely.
    - Classifier: invert the UnitClassification gate to a deny-list
      (elite / rareelite / rare / worldboss) so name-matched dummies with
      any non-combatant classification still promote.
    - Zone fallback: new IsInPracticeZone() + PRACTICE_ZONE_MAP_IDS catches
      unknown-CID NPCs whose name contains "dummy" inside capital cities,
      gated by the same classification deny-list to avoid false positives
      on vendors and guards.
    - Fix latent Addon:Warn signature so trace logs capture actual error
      detail from pcall-guarded sites (CombatStore UpdateAggregatesForSession
      / MigrateSchema failures were dropping the formatted message).

    Tests: 5 new SessionClassifier cases (normal-class name match, elite
    rejection, zone fallback positive/negative, non-practice-zone negative).
    luac -p clean. Pre-existing test failures unchanged.