1.2.1-release
What's new
tag e0b19a7626a892d1be40c414ec2e2f1294f69f84 1.2.1-release
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Jul 26 20:11:00 2026 +0530
commit 0abaf716443cb75c8757139127a9abc3755bdfb9
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Jul 26 14:40:01 2026 +0000
Chore: prep for 1.2.1 release
Patch, not a minor: the release is bugfixes and internals with no new
user-facing features. The headline is issue #8, where the addon tracked
nothing at all on non-English clients. There is a saved-variables schema
bump (v2 -> v3) behind it, but it migrates transparently on login and each
profile is caught as it activates, so nothing is asked of the user.
Version stamped in KickCD.toc and NS.VERSION. README gains a Version History
row and a rewritten "What's new" section.
Two deliberate departures worth noting for the next bump:
* The "What's new" section carries BOTH releases -- 1.2.1's fixes, then an
"Also in 1.2.0" subsection with the previous release's bullets. 1.2.1 is a
small patch riding on a large feature release, so anyone arriving from
1.1.x sees the whole picture. The convention is that this section covers
only the current release, so the "Also in 1.2.0" block should be dropped
when 1.3.0 lands rather than inherited.
* `/kcd debug duration` was added and removed inside this same release
window, so it appears in neither the release notes nor the history row.
Users never saw it; the commits carry that history instead.
docs/smoke-tests.md's "saved by v1.2.0 or earlier" is deliberately left
alone -- it states which builds carry the pre-v3 schema, which is still
true, and bumping it would break the upgrade-path smoke test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YD1nuzd8pJxh3HfL1NaMha
commit 6183e828036d5aecf4f68cb2edbc41346aaf0d66
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Jul 26 14:30:38 2026 +0000
Remove the DurationObject probe and two dead exports; sync docs
The `/kcd debug duration` probe was scaffolding. It existed to answer one
question -- which DurationObject getters are usable in combat -- and it
answered it: every getter except HasSecretValues() is secret-tainted,
booleans included, so there is no Lua-side way to detect a cooldown change
mid-fight. Keeping a diagnostic command around for a question that is now
settled is dead weight, so it goes: Compat.DebugDuration, the DEBUG_COMMANDS
entry, its test suite, and the doc rows describing it.
The measured findings STAY in midnight-quirks.md -- that table is the
expensive part and re-deriving it would mean another round-trip through a
live client. The pointers to the removed command are replaced with
instructions for re-measuring by hand after a patch.
Also reverted the argument-remainder threading in runDebug that was added
for that verb. No other debug subcommand takes an argument, so leaving it
would be speculative plumbing with no caller.
Dead exports removed:
* `Util.ResetSpecNameCache` -- added earlier the same day as a "test/reload
hook" and never called, including by the tests. The locale maps are
self-healing (a build that saw no client data isn't cached, so the next
call retries), so nothing needed it.
* `Util.RegisterTargetEvent` -- superseded by RegisterUnitCastEvent, zero
call sites. The surrounding comment block that described it is updated
too.
Doc sync against current code:
* module-map: corrected a stale claim that IconGrid_Render consumes
`IconGrid._isTargetCasting` -- that hook is gone; the glow trigger reads
the per-instance `inst.isCasting` resolver. Added IconGrid.Icon, the
Icon:Apply state/time split, and the MaterialChange log gate.
* data-flow: new section on why SPELL_STATE fires ~10x/sec per spell on
cooldown and what that forces on the logging and render paths -- the
single most surprising thing about this pipeline for anyone reading it
fresh.
* agent-context: two hard rules that were only recorded in ARCHITECTURE or
in code comments -- never derive a persisted key from a localised string
(issue #8), and the DurationObject secrecy result.
Tests 151 -> 146 (the probe suite is gone); no coverage lost elsewhere.
Frozen audit/review/plan bundles still reference RegisterTargetEvent and are
deliberately left untouched -- they are dated records of what was true when
written, not live docs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YD1nuzd8pJxh3HfL1NaMha
commit 9fc0d8b1b320c056fd7d1974b6e7bc878761c756
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Jul 26 14:16:44 2026 +0000
Cut redundant icon render work; probe the 12.0 DurationObject API
Two related changes: measuring what the duration API can actually do, and
acting on the answer.
`/kcd debug duration [spellID]` (Compat.DebugDuration) probes every
DurationObject getter on the live client and reports whether each is present
and whether its return is secret-tainted right now. Blizzard documents the
methods but not their combat secrecy, and this addon's history is a catalog
of APIs that read fine out of combat and error the moment it opens. Every
call is pcall'd and every result rendered through the issecretvalue gate, so
the probe can never itself be the thing that errors. Debug verbs now receive
their argument remainder, so a verb can take a parameter.
The measured answer (12.0.7, recorded in midnight-quirks.md): IN COMBAT EVERY
GETTER IS SECRET, including the booleans -- HasExpired, HasStarted, IsActive
and IsZero all come back tainted, and a secret boolean cannot be branched on
at all. Only HasSecretValues() stays plain.
That settles a question this addon has been working around blind. There is NO
Lua-side way to detect "this spell's cooldown changed" in combat, so
Cooldowns:StateChanged's identity compare is not a shortcut -- it is the only
signal available. It over-fires (the API mints a fresh object per call) but
never under-fires, and nothing can replace it. The emit rate is therefore
fixed, and the only thing left to cut is what each emit costs.
So Icon:Apply now splits its work. The alpha/tint/GCD curve evaluations, the
swipe re-arm and the countdown text are time-varying and still run on every
payload. Glow (four LibCustomGlow calls), the charges badge and the Show/Hide
calls depend only on plain state fields and are gated behind plainStateMoved
-- measured at 35% fewer widget calls per repeat apply, which for a spell
parked on a 60s cooldown is most of a minute of pure waste.
Two details that keep the gate honest. A config-driven re-apply passes
force=true: it hands back the SAME state table, so the gate would otherwise
skip exactly the work the config change needed. And charges stay outside the
gate entirely -- they can be secret, a secret cannot be compared, and
SetFormattedText renders one fine, so freezing the badge to save two calls
would strand a stale count on screen for a whole fight.
Also corrects Compat.GetSpellCooldownDuration's docstring: it returns a
ZEROED object, not nil, for a spell that is off cooldown, so a non-nil handle
is not an "is on cooldown" signal.
Adding C_CurveUtil to the test mock revealed that branch 1 of Icon:Apply --
the main full-cooldown render path -- had never been exercised headlessly:
BuildCurves bailed without the API and every case fell through to the idle
branch. Now covered, along with a duration-object mock mirroring the two
traits that matter (fresh per call, possibly secret).
Tests 140 -> 151. Smoke-tests gains 9c for the visuals the mock cannot judge
-- swipe smoothness and the GCD_UPPER alpha step in a cooldown's final 1.6s,
which is the transition most at risk from over-gating.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YD1nuzd8pJxh3HfL1NaMha
commit 0a696cf99e1cdc8dee43ab4d9ecc49c4c59cdb54
Author: Tushar Saxena <tushar.saxena@gmail.com>
Date: Sun Jul 26 13:52:11 2026 +0000
Fix: key spell lists on numeric spec IDs, not localised names (#8)
The per-spec spell-list key was derived from GetSpecializationInfo's
SECOND return -- the localised display name -- while defaults/Spells.lua
shipped English keys. On any non-English client the two never met: a frFR
Elemental Shaman derived "ELEMENTAIRE", looked up spells.SHAMAN.ELEMENTAIRE,
got nil, and tracked nothing at all. Silently, because an absent spec list
is indistinguishable from a deliberately emptied one. Only specs spelled
identically in English worked, which is why it looked class-specific.
Spec keys are now Blizzard's numeric specID (Const.SPEC, verified against
the live ChrSpecialization export). Localised names stay for DISPLAY only:
the Spells dropdown labels in the player's own language and the slash
commands accept a localised name, but output always echoes the English
token so pasted bug reports read the same in every locale.
Database:MigrateSpecKeys carries v2 profiles forward (schema v3). It is
shape-driven rather than version-gated: schemaVersion is per-ACCOUNT but
spells are per-PROFILE, so a gated step would migrate only whichever
profile was active at upgrade and strand the rest. Unresolvable keys are
left in place rather than dropped, and a string key never overwrites an
existing numeric one.
Also in this change:
* Settings > Spells now follows an in-game spec swap while open. It only
re-seeded on a fresh Settings open, so the dropdown stayed pinned to the
old spec until the window was fully closed and reopened.
* The rebuild debug line now names the class/spec IDs and every watched and
skipped spellID, so a pasted log is diagnosable on its own -- which is
exactly what this issue needed.
* Cooldown-handle churn no longer floods the debug console.
C_Spell.GetSpellCooldownDuration mints a fresh object per call, so a
spell parked on an unchanged cooldown compared unequal on every poll and
logged ~10 lines/sec for the whole cooldown. The re-emit is load-bearing
(Icon:Apply re-evaluates its alpha/tint/GCD curves off the emitted
object and nothing else re-runs them), so the emit is unchanged and only
the log is gated, on a new MaterialChange predicate.
Recorded the locale rule as an ARCHITECTURE invariant and documented the
CooldownDuration trap -- plus the wider DurationObject method surface this
addon does not yet use -- in docs/midnight-quirks.md.
Tests 111 -> 140, including a frFR client suite driving the reported
scenario end to end. luacheck clean.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YD1nuzd8pJxh3HfL1NaMha
This mod has no additional files

