Skip to content

fix(engine): accuracy fixes + /phd debug diagnostic (#44)#45

Merged
Xerrion merged 6 commits into
masterfrom
fix/44-accuracy-and-diagnostic
Apr 29, 2026
Merged

fix(engine): accuracy fixes + /phd debug diagnostic (#44)#45
Xerrion merged 6 commits into
masterfrom
fix/44-accuracy-and-diagnostic

Conversation

@Xerrion

@Xerrion Xerrion commented Apr 29, 2026

Copy link
Copy Markdown
Owner

Closes #44.

Summary

Fixes the consistent ~5-10% expected-damage overestimate reported in #44 (two independent root causes) and adds a new /phd debug diagnostic to investigate the related missing-overlay symptom (e.g. Hellfire).

Bug A: crit double-count (data fix)

Core/StateCollector.lua reads GetSpellCritChance(school) and GetCritChance() per-school. The WoW API value already reflects passive talents that grant school-wide spell crit, global all-spell crit, weapon-conditional melee crit (when the weapon is wielded), and form-conditional crit (when in form). Engine/ModifierCalc.lua was then adding mods.critBonus (sourced from TalentMap MOD.CRIT_BONUS entries) on top, double-counting the same talents.

The audit (per-talent verbatim TBC tooltip lookups via the wow-addon agent) classified 35 candidate entries:

  • 17 REMOVE (full) - school-wide, universal, weapon-conditional, or form-conditional crit talents already in the API value: Sharpened Claws, Natural Perfection, Lethal Shots, Survival Instincts, Critical Mass, Pyromaniac, Holy Power, Combat Expertise (TBC tooltip grants Expertise+Stamina, not crit - data bug), Sanctified Seals, Holy Specialization, Malice, Dagger Specialization, Fist Weapon Specialization, Demonic Tactics, Backlash, Poleaxe Specialization, Cruelty.
  • 2 REMOVE (partial) - the MOD.CRIT_BONUS effect was removed from Arcane Instability and Force of Will. Their MOD.DAMAGE_MULTIPLIER effects remain (no per-school spell-damage API to subsume them).
  • 17 KEEP - single-spell or per-spell-list crit talents the per-school API scalar cannot encode: Improved Moonfire, Focused Starlight, Improved Regrowth, Arcane Impact, Improved Flamestrike, Incineration, Empowered Frostbolt, Sanctified Light, Purifying Power, Puncturing Wounds, Improved Ambush, Call of Thunder, Tidal Mastery, Improved Searing Pain, Devastation (spans Shadow+Fire so cannot be encoded in any single GetSpellCritChance(school)), Improved Overpower, plus the surviving DAMAGE_MULTIPLIER effects on the two partials.

Each removal carries an inline tombstone comment citing this PR. New tests/test_crit_double_count.lua enumerates the removed keys and asserts the partial-keep entries no longer carry CRIT_BONUS effects. 11 existing test files were updated with recomputed combined-build crit math.

Bug B: stack-aware aura modifiers

StateCollector previously wrote state.auras[scope][spellID] = true, discarding auraData.applications. ModifierCalc then applied the AuraMap value at full strength even for a single stack of a 5-stack aura. Producers and consumers were out of sync on what "present" means.

Fixed end-to-end:

  • Core/StateCollector.lua: stores auraData.applications or 1 in all three aura-write sites.
  • Engine/ModifierCalc.lua: ApplyAuraEntry accepts applications and computes stackFactor = stacks / maxStacks, threading it through ApplyEffect (for both regular and countField effects). Type coercion at the boundary preserves boolean back-compat: entries without maxStacks see factor 1.0; stack-aware entries treat true as 1 stack.
  • Data/AuraMap_Warlock.lua, Data/AuraMap_Priest.lua: Shadow Vulnerability (17800/17803) and Shadow Weaving (15258) get maxStacks = 5 with at-max values.
  • Data/AuraMap_Mage.lua: new Fire Vulnerability entry (spellID 22959, the debuff applied by Improved Scorch) - maxStacks = 5, value = 0.15, school filter Fire.
  • Data/AuraMap_Shaman.lua: corrected pre-existing Healing Way bug (29203) - was value = 0.06 flat which under-applied at 2-3 stacks; now value = 0.18, maxStacks = 3 so stack 1 = +6%, stack 3 = +18% per its own comment.

New tests/test_stack_scaling.lua (22 cases) covers linear scaling, max-stack clamping, legacy boolean back-compat, school filtering, no-maxStacks baseline (Curse of Elements 27228), and end-to-end via Pipeline.Calculate.

Bug C (diagnostic only): /phd debug [slot]

To investigate the user-reported missing-overlay symptom on Hellfire (root cause unknown - the spell data is complete and the engine has no skip path), this PR ships a chat-print diagnostic instead of guessing at a fix.

/phd debug dumps slots 1-12; /phd debug N dumps a single slot (1-120). Output shows action type, raw and base spellIDs, ns.SpellData presence, and Pipeline result or error. The Hellfire failure mode (action present, base spellID unresolved or absent from ns.SpellData) is surfaced as base spellID: no resolution | SpellData: absent. Once a user runs this in-game on a non-overlaying slot, the root cause should be obvious and a follow-up issue can be filed.

Defensive pcall around both Pipeline.Calculate and GetMacroSpell so one bad slot doesn''t abort the bar dump. No engine or data changes; only Presentation/Diagnostics.lua and Core/Init.lua (slash dispatch + help line).

Non-goals (deferred)

  • Hellfire root cause / fix: deferred until /phd debug reveals what is actually missing. Filing a follow-up issue once a user reports the diagnostic output.
  • Partial-resist modeling: not added in this PR. With Bugs A and B fixed, the residual gap may be partial-resist driven; that requires runtime data we do not yet have, and adding it speculatively risks turning the new conservative number into an underestimate.

Verification

  • luacheck . --exclude-files Libs/: 0 warnings, 0 errors in 80 files.
  • lua tests/bootstrap.lua: 117 spells, 110 talents, 55 auras, 4 engine modules.
  • busted --verbose: not runnable locally (MinGW/luasystem missing); CI is the gate. New regression tests: tests/test_crit_double_count.lua (Bug A), tests/test_stack_scaling.lua (Bug B, 22 cases). All previously-passing tests updated to match the new ground truth where the removed crit talents and stack-aware auras were asserted.

Plan

.plans/44-accuracy-and-diagnostic.md is included in this PR for reviewer reference.

Summary by CodeRabbit

  • New Features

    • Added a /phd debug [slot] diagnostic to inspect action-bar slot spell resolution and pipeline outputs.
  • Bug Fixes

    • Eliminated duplicate talent crit counting so reported crit chances are corrected.
    • Made aura effects scale by actual stack count (including Healing Way) and improved spell/effect ID resolution.
  • Documentation

    • Added an implementation roadmap for accuracy and diagnostics.
  • Tests

    • Added and updated tests validating stack scaling, crit accounting, and spell-ID resolution.

Bug D: Channel-spell dual-ID resolution (Phase 5, added after #45 opened)

User ran /phd debug 6 (the new diagnostic from Phase 3) and got:

[13:14:54] PhDamage:   actionType: spell | id: 27213 | subType: spell
[13:14:54] PhDamage:   base spellID: no resolution | SpellData: absent

The diagnostic surfaced a real data bug: TBC channeled AoE spells using "Aura #23 Periodic Trigger Spell" have TWO spell IDs per rank. The channel-cast ID is what GetActionInfo returns (player-facing). The effect ID is the per-tick damage proc.

Hellfire R4 was uniquely MIXED: PhDamage stored effect ID 27214 in the rank''s spellID slot instead of channel-cast 27213. When the user dragged Hellfire from the spellbook (where it sits as the rank-4 cast spell), the action bar resolved to 27213 and PhDamage''s reverse-map didn''t know about it.

Architectural fix

Three duplicated reverse-map builders (in Presentation/ActionBar.lua, Presentation/Tooltip.lua, Presentation/Diagnostics.lua) consolidated into one new module ns.SpellResolver. Pure-Lua, no WoW APIs, lazy-built lookup. Maps any base/rank-cast/effect ID to (baseID, rankIndex).

Schema change

Data/SpellData_*.lua rank entries gain an optional effectID field. Purely additive - no existing engine reader cares. SpellResolver indexes both spellID and effectID into the same lookup table.

Cross-class audit

All four TBC channeled AoE spells using the same Aura #23 mechanic were audited:

Spell Class Ranks All channel-cast IDs were correct?
Hellfire Warlock 4 NO - R4 mixed (fixed)
Rain of Fire Warlock 5 YES (effectID added per rank)
Blizzard Mage 7 YES (effectID added per rank)
Volley Hunter 4 YES (effectID added per rank)
Hurricane Druid 4 YES (effectID added per rank)

24 effectID entries total. Source: TBC-Twinhead per-rank pages.

Out of scope

These channeled spells use different mechanics (Periodic Drain, Trigger Missile) - single ID per rank, no fix needed: Drain Life, Drain Soul, Health Funnel (Warlock), Mind Flay (Priest), Arcane Missiles (Mage), Tranquility (Druid).

Test coverage

tests/test_spell_resolver.lua (21 cases): Hellfire base/cast/effect resolution for all 4 ranks, type guards, Rebuild idempotence, plus cross-class verification of one rank cast+effect ID per audited spell.

Files

  • New: Core/SpellResolver.lua, tests/test_spell_resolver.lua
  • Modified: PhDamage.toc, Presentation/{ActionBar,Tooltip,Diagnostics}.lua, Data/SpellData_{Warlock,Mage,Hunter,Druid}.lua, tests/bootstrap.lua, AGENTS.md

Hellfire data correction (added with the dual-ID resolver fix)

While verifying Hellfire's spell IDs against TBC-Twinhead, the lookup also surfaced incorrect totalDmg and level values for ranks 1-4. Corrected per authoritative tooltips:

Rank spellID per-tick (Twinhead) totalDmg before totalDmg after level before level after
1 1949 83 255 1245 12 30
2 11683 139 510 2085 28 42
3 11684 208 825 3120 44 54
4 27213 306 1395 4590 68 68

Convention totalDmg = perTick * numTicks confirmed by reading Engine/SpellCalc.lua (channel/DoT/hybrid paths all divide by numTicks). Cross-checked Drain Life R8 (108/tick * 5 ticks = 540, stored 540) confirming the convention is correctly applied elsewhere - Hellfire is an isolated data error, not class-wide.

Xerrion added 3 commits April 29, 2026 13:03
The WoW API GetSpellCritChance(school) and GetCritChance() already include
all passive talents that grant school-wide spell crit, global all-spell
crit, weapon-conditional melee crit (when the weapon is wielded), and
form-conditional crit (when in form). The TalentMap was re-applying these
on top of the API value, causing a 1-3% overestimate per learned rank.

Removed 17 entries entirely and stripped the CRIT_BONUS effect from 2
multi-effect entries (Arcane Instability, Force of Will - DAMAGE_MULTIPLIER
effects retained). 16 KEEP entries (single-spell or per-spell-list crit)
remain because the per-school API scalar cannot encode them.

Each removal carries an explanatory tombstone comment referencing
plan 44, Bug A. New tests/test_crit_double_count.lua enumerates all 17
removed keys and asserts the 2 partial-keep entries no longer carry
CRIT_BONUS effects. 11 existing test files updated with recomputed
combined-build crit math.
…44)

Previously StateCollector wrote `state.auras[scope][spellID] = true` and
ModifierCalc applied the AuraMap value at full strength on the first
stack, ignoring auraData.applications. This caused overestimates against
Shadow Vulnerability, Shadow Weaving, Fire Vulnerability, and Healing Way
at sub-max stacks.

StateCollector now stores auraData.applications (defaulting to 1).
ApplyAuraEntry takes an applications param and computes
stackFactor = stacks / maxStacks, threading it through ApplyEffect for
both regular and countField effects. Type coercion at the boundary
preserves boolean back-compat for non-stack-aware entries (factor 1.0).

AuraMap data:
- Shadow Vulnerability (17800/17803): maxStacks=5, value=0.20
- Shadow Weaving (15258, Warlock + Priest): maxStacks=5, value=0.10
- Fire Vulnerability (22959, new Mage entry for Improved Scorch debuff):
  maxStacks=5, value=0.15, school=Fire
- Healing Way (29203): corrected from value=0.06 flat to value=0.18
  maxStacks=3 (its own comment said +6%/stack to 3 stacks)

22-case tests/test_stack_scaling.lua covers linear scaling, max-stack
clamp, legacy boolean back-compat, school filter, no-maxStacks baseline,
and end-to-end Pipeline integration.
Surfaces why a populated action slot might lack a damage overlay -
particularly the Hellfire-style failure where a spell is on the bar
but its base spellID is missing from ns.SpellData.

Default `/phd debug` dumps slots 1-12; `/phd debug N` dumps a single
slot (1-120). Output shows action type, raw and base spellIDs,
SpellData presence, Pipeline result or error. Defensive pcall around
Pipeline.Calculate and GetMacroSpell so a single bad slot doesn't abort
the dump. No engine or data changes.
@coderabbitai

coderabbitai Bot commented Apr 29, 2026

Copy link
Copy Markdown

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ab45436e-d43a-4357-ae67-d7c740574d41

📥 Commits

Reviewing files that changed from the base of the PR and between 4a9d70d and 567d5ac.

📒 Files selected for processing (13)
  • .plans/44-accuracy-and-diagnostic.md
  • AGENTS.md
  • Core/SpellResolver.lua
  • Data/SpellData_Druid.lua
  • Data/SpellData_Hunter.lua
  • Data/SpellData_Mage.lua
  • Data/SpellData_Warlock.lua
  • PhDamage.toc
  • Presentation/ActionBar.lua
  • Presentation/Diagnostics.lua
  • Presentation/Tooltip.lua
  • tests/bootstrap.lua
  • tests/test_spell_resolver.lua
✅ Files skipped from review due to trivial changes (1)
  • AGENTS.md
🚧 Files skipped from review as they are similar to previous changes (1)
  • Presentation/Diagnostics.lua

📝 Walkthrough

Walkthrough

Adds a roadmap and implements fixes: talent crit double-count removal, stack-aware aura scaling end-to-end, a /phd debug [slot] diagnostic, a new SpellResolver module with spell/effectID resolution, multiple SpellData effectID additions, Presentation wiring updates, and extensive test coverage for the changes.

Changes

Cohort / File(s) Summary
Plan & Docs
/.plans/44-accuracy-and-diagnostic.md, AGENTS.md
New plan documenting four-phase roadmap and diagnostic; documents new ns.SpellResolver responsibility.
Slash Command & Diagnostics
Core/Init.lua, Presentation/Diagnostics.lua
Adds /phd debug [slot] routing and Diagnostics.PrintDebug(slot) to dump GetActionInfo, macro resolution, SpellResolver mapping, and Pipeline.Calculate outcomes with guarded error handling.
Spell resolution core
Core/SpellResolver.lua, PhDamage.toc
New cached ns.SpellResolver with Resolve/Rebuild; added to load order.
State collection
Core/StateCollector.lua
StateCollector records aura applications counts (numbers, default 1) instead of boolean presence for player/target auras.
Modifier application
Engine/ModifierCalc.lua
ApplyEffect/ApplyAuraEntry accept stack/applications info; compute stackFactor = stacks/maxStacks (clamp/legacy boolean→1) and scale modifiers/count-effects accordingly; ApplyModifiers forwards stored application counts.
Spell data changes
Data/SpellData_*.lua
Data/SpellData_Druid.lua, ..._Hunter.lua, ..._Mage.lua, ..._Warlock.lua
Adds per-rank effectID fields and adjusts some rank damage/level values (notably warlock channel spells).
AuraMap stack metadata
Data/AuraMap_Mage.lua, ..._Priest.lua, ..._Shaman.lua, ..._Warlock.lua
Convert prior boolean-assumed auras to stack-aware entries by adding maxStacks and adjust effect values where appropriate (e.g., Healing Way adjusted to aggregate value and maxStacks=3; Fire-only 22959 defined).
TalentMap removals (crit de-dup)
Data/TalentMap_*.lua
..._Druid.lua, ..._Hunter.lua, ..._Mage.lua, ..._Paladin.lua, ..._Priest.lua, ..._Rogue.lua, ..._Warlock.lua, ..._Warrior.lua
Removed MOD.CRIT_BONUS entries that duplicate crit already provided by StateCollector/APIs; retained non-crit effects and added explanatory comments.
Presentation wiring
Presentation/ActionBar.lua, Presentation/Tooltip.lua
ActionBar and Tooltip now delegate rank/effect resolution to ns.SpellResolver.Resolve; removed local reverse-mapping and Tooltip.GetSpellIDMap.
Diagnostics & bootstrap tests
tests/bootstrap.lua, tests/test_spell_resolver.lua
Bootstrap loads SpellResolver and asserts presence; adds comprehensive SpellResolver tests including Hellfire R4 cast/effect mappings and Rebuild behavior.
New & updated tests
tests/*.lua (many)
tests/test_crit_double_count.lua, tests/test_stack_scaling.lua, plus updates to many talent/auras tests
New regression suites for crit double-count and stack-scaling; updated many existing tests to remove talent crit assertions (now provided via stats) and convert aura presence to numeric stack counts. Most test changes adjust assertions and scenarios accordingly.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title accurately summarizes the main changes: accuracy fixes for crit double-counting and stack-aware auras, plus addition of /phd debug diagnostic command.
Linked Issues check ✅ Passed All three primary bugs from #44 are addressed: Bug A (crit double-count via TalentMap removals with test coverage), Bug B (stack-aware auras via StateCollector/ModifierCalc changes and maxStacks data), and Bug C (/phd debug diagnostic). Bug D (SpellResolver for channel dual-ID resolution) is an added improvement directly supporting Hellfire overlay diagnosis.
Out of Scope Changes check ✅ Passed All changes directly address stated objectives: TalentMap crit removals, aura stack scaling, diagnostic command, and SpellResolver implementation. No unrelated refactoring, dependency upgrades, or infrastructure changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 60 minutes.

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
.plans/44-accuracy-and-diagnostic.md (1)

216-217: Add an explicit in-game spellID verification step for AuraMap edits.

Since this phase adds/updates stacking auras (including Improved Scorch), include a required verification step using /dump GetPlayerAuraBySpellID(id) for each touched spellID before declaring verification complete.

Based on learnings: Applies to Data/AuraMap_*.lua : Verify aura spellIDs in-game using /dump GetPlayerAuraBySpellID(id) command.

Also applies to: 238-242

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.plans/44-accuracy-and-diagnostic.md around lines 216 - 217, Add an explicit
in-game spellID verification step for any AuraMap edits: when changing
Data/AuraMap_*.lua (e.g., entries for Improved Scorch and other stacking auras)
run and record the `/dump GetPlayerAuraBySpellID(id)` output for each modified
spellID and include those dumps in the PR description; update your audit
checklist and the PR checklist (referenced near the stack-scaling tests in
tests/test_stack_scaling.lua and the Improved Scorch entry) to require this
verification before marking verification complete.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.plans/44-accuracy-and-diagnostic.md:
- Around line 48-73: The plan is inconsistent: the summary "18 REMOVE" doesn't
match the 19 rows in the REMOVE table and the global instruction at the end
("delete whole entry blocks for all REMOVE rows") would erase partially-kept
effects like CRIT_BONUS vs DAMAGE_MULTIPLIER; fix by updating the REMOVE summary
count to match the table (or remove one row from the table), and change the
blanket-delete instruction so it only removes the CRIT_BONUS effects listed for
talents such as Arcane Instability and Force of Will while preserving their
DAMAGE_MULTIPLIER effects and any other partial-keeps (e.g., Sanctified Seals
should drop the entry body but preserve non-crit effects per note); also ensure
entries referencing weapon-specific auras (Dagger Specialization, Fist Weapon
Specialization, Poleaxe Specialization) remove their obsolete "weaponType
filter" TODO comments rather than deleting the whole talent, and keep references
to methods that consume these effects (GetCritChance, GetRangedCritChance,
GetSpellCritChance) consistent with the adjusted removals.

In `@tests/test_rogue_auras.lua`:
- Around line 550-556: The test currently uses a loose check
(assert.is_true(result.critChance >= 1.0)) which won't fail if critChance
exceeds the intended cap; change the assertion in the "Cold Blood alone caps at
1.00" test to assert the clamped value exactly (e.g. assert.are.equal(1.0,
result.critChance) or assert.is_true(result.critChance == 1.0)) so that
Pipeline.Calculate's critChance result is verified to be exactly 1.0 for the
given state (makeRogueState + aura 14177).

---

Nitpick comments:
In @.plans/44-accuracy-and-diagnostic.md:
- Around line 216-217: Add an explicit in-game spellID verification step for any
AuraMap edits: when changing Data/AuraMap_*.lua (e.g., entries for Improved
Scorch and other stacking auras) run and record the `/dump
GetPlayerAuraBySpellID(id)` output for each modified spellID and include those
dumps in the PR description; update your audit checklist and the PR checklist
(referenced near the stack-scaling tests in tests/test_stack_scaling.lua and the
Improved Scorch entry) to require this verification before marking verification
complete.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ccd77ca4-cb9c-4953-bc17-74af1ccad5ef

📥 Commits

Reviewing files that changed from the base of the PR and between 79c53a2 and 4a9d70d.

📒 Files selected for processing (33)
  • .plans/44-accuracy-and-diagnostic.md
  • Core/Init.lua
  • Core/StateCollector.lua
  • Data/AuraMap_Mage.lua
  • Data/AuraMap_Priest.lua
  • Data/AuraMap_Shaman.lua
  • Data/AuraMap_Warlock.lua
  • Data/TalentMap_Druid.lua
  • Data/TalentMap_Hunter.lua
  • Data/TalentMap_Mage.lua
  • Data/TalentMap_Paladin.lua
  • Data/TalentMap_Priest.lua
  • Data/TalentMap_Rogue.lua
  • Data/TalentMap_Warlock.lua
  • Data/TalentMap_Warrior.lua
  • Engine/ModifierCalc.lua
  • Presentation/Diagnostics.lua
  • tests/test_crit_double_count.lua
  • tests/test_critcalc.lua
  • tests/test_druid_talents.lua
  • tests/test_hunter.lua
  • tests/test_mage_auras.lua
  • tests/test_mage_talents.lua
  • tests/test_modifiercalc.lua
  • tests/test_new_talents.lua
  • tests/test_paladin_talents.lua
  • tests/test_priest_auras.lua
  • tests/test_priest_talents.lua
  • tests/test_rogue_auras.lua
  • tests/test_rogue_talents.lua
  • tests/test_shaman_auras.lua
  • tests/test_stack_scaling.lua
  • tests/test_warrior_talents.lua

Comment thread .plans/44-accuracy-and-diagnostic.md Outdated
Comment on lines +48 to +73
**Audit (18 REMOVE, 17 KEEP, 0 INCONCLUSIVE):**

REMOVE rows:

| File | Talent | Per-rank | Rationale |
|---|---|---|---|
| `Data/TalentMap_Druid.lua` | Sharpened Claws | +2% | Form aura active in Cat/Bear; in `GetCritChance()` while in form |
| `Data/TalentMap_Druid.lua` | Natural Perfection | +1% | All spells universal; in `GetSpellCritChance()` for every school |
| `Data/TalentMap_Hunter.lua` | Lethal Shots | +1% | Ranged-wide; in `GetRangedCritChance()` |
| `Data/TalentMap_Hunter.lua` | Survival Instincts | +2% | Melee+ranged; in both `GetCritChance()` and `GetRangedCritChance()` |
| `Data/TalentMap_Mage.lua` | Arcane Instability | +1% | **CRIT_BONUS effect only.** DAMAGE_MULTIPLIER effect on the same talent stays. |
| `Data/TalentMap_Mage.lua` | Critical Mass | +2% | Whole Fire school; in `GetSpellCritChance(SCHOOL_FIRE)` |
| `Data/TalentMap_Mage.lua` | Pyromaniac | +1% | Whole Fire school |
| `Data/TalentMap_Paladin.lua` | Holy Power | +1% | Whole Holy school; in `GetSpellCritChance(SCHOOL_HOLY)` |
| `Data/TalentMap_Paladin.lua` | Combat Expertise | (none) | **Data bug.** TBC tooltip grants Expertise + Stamina, NOT crit. Crit was added in Wrath 3.1.0. Delete entry entirely. |
| `Data/TalentMap_Paladin.lua` | Sanctified Seals (3:21) | +1% | Universal +crit per TBC tooltip (Wowhead spell=35395/35396/35397). Reflected in every `GetSpellCritChance(school)` and `GetCritChance()`. **Coder must verify the comment "Retribution 3:21" against PhDamage's tab numbering convention** - Wowpedia places this talent in Holy Tier 7. The `tab:index` key stays as `"3:21"` per existing source; only the entry body is removed. |
| `Data/TalentMap_Priest.lua` | Holy Specialization | +1% | Whole Holy school |
| `Data/TalentMap_Priest.lua` | Force of Will | +1% | **CRIT_BONUS effect only.** DAMAGE_MULTIPLIER effect stays. |
| `Data/TalentMap_Rogue.lua` | Malice | +1% | Melee-wide; in `GetCritChance()` |
| `Data/TalentMap_Rogue.lua` | Dagger Specialization | +1% | Weapon aura while wielding daggers; in `GetCritChance()`. Delete the obsolete `weaponType filter` TODO comment. |
| `Data/TalentMap_Rogue.lua` | Fist Weapon Specialization | +1% | Same as Dagger Spec. Delete obsolete TODO. |
| `Data/TalentMap_Warlock.lua` | Demonic Tactics | +1% | All spells + melee universal |
| `Data/TalentMap_Warlock.lua` | Backlash | +1% | All spells universal per TBC tooltip ("with all spells"). Existing "no school filter" comment confirms. |
| `Data/TalentMap_Warrior.lua` | Poleaxe Specialization | +1% | Weapon aura while wielding axe/polearm; in `GetCritChance()`. Delete obsolete TODO. |
| `Data/TalentMap_Warrior.lua` | Cruelty | +1% | Melee-wide; in `GetCritChance()` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix contradictory execution guidance for Bug A before implementation.

The plan currently conflicts in two places:

  • Line 48 says 18 REMOVE, but the REMOVE table has 19 rows (Line 54-Line 73).
  • Line 58 and Line 65 mark partial-keep talents (remove only CRIT_BONUS), but Line 207 instructs deleting whole entry blocks for all REMOVE rows. That would drop intended DAMAGE_MULTIPLIER effects.
Proposed patch to make the plan unambiguous
-**Audit (18 REMOVE, 17 KEEP, 0 INCONCLUSIVE):**
+**Audit (17 FULL REMOVE, 2 CRIT_BONUS-only edits, 17 KEEP, 0 INCONCLUSIVE):**

-REMOVE rows:
+Rows requiring action (17 full removals + 2 partial CRIT_BONUS removals):

-### Phase 1: Bug A — crit double-count [PENDING]
+### Phase 1: Bug A — crit double-count [PENDING]

-- 1.1 Touch each TalentMap_*.lua file per the REMOVE table; delete the offending entry blocks (the table-assignment to `TalentMap["tab:index"]` and the comment line above it). Where a TODO comment for weapon-type filtering accompanied an entry (Rogue Dagger/Fist Spec, Warrior Poleaxe Spec), delete the TODO too. For Sanctified Seals (`TalentMap_Paladin.lua` `["3:21"]`), additionally verify and correct the tab-name comment on L121/L135 if PhDamage's tab numbering disagrees with Wowpedia (Holy = Tier 7 per Wowpedia; comment currently says "Retribution"). KEEP entries are not modified. ← CURRENT
+- 1.1 Touch each TalentMap_*.lua file per the action table.
+  - Full-remove rows: delete the offending talent entry blocks.
+  - Partial rows (Arcane Instability, Force of Will): remove only `MOD.CRIT_BONUS`; keep `DAMAGE_MULTIPLIER`.
+  - Remove obsolete weapon-type TODO comments where they belong to deleted rows.
+  - For Sanctified Seals (`TalentMap_Paladin.lua` `["3:21"]`), verify/correct tab-name comment vs PhDamage tab numbering.
+  KEEP rows are not modified.

Also applies to: 205-209

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.plans/44-accuracy-and-diagnostic.md around lines 48 - 73, The plan is
inconsistent: the summary "18 REMOVE" doesn't match the 19 rows in the REMOVE
table and the global instruction at the end ("delete whole entry blocks for all
REMOVE rows") would erase partially-kept effects like CRIT_BONUS vs
DAMAGE_MULTIPLIER; fix by updating the REMOVE summary count to match the table
(or remove one row from the table), and change the blanket-delete instruction so
it only removes the CRIT_BONUS effects listed for talents such as Arcane
Instability and Force of Will while preserving their DAMAGE_MULTIPLIER effects
and any other partial-keeps (e.g., Sanctified Seals should drop the entry body
but preserve non-crit effects per note); also ensure entries referencing
weapon-specific auras (Dagger Specialization, Fist Weapon Specialization,
Poleaxe Specialization) remove their obsolete "weaponType filter" TODO comments
rather than deleting the whole talent, and keep references to methods that
consume these effects (GetCritChance, GetRangedCritChance, GetSpellCritChance)
consistent with the adjusted removals.

Comment on lines +550 to 556
it("Cold Blood alone caps at 1.00", function()
local state = makeRogueState()
state.auras.player[14177] = true -- Cold Blood +100%
state.talents["1:3"] = 5 -- Malice 5/5 (+5%)
local result = Pipeline.Calculate(1752, state)
-- 0.25 + 1.00 + 0.05 = 1.30 → capped at 1.00
-- 0.25 + 1.00 = 1.25 -> capped at 1.00
assert.is_true(result.critChance >= 1.0,
"Expected critChance >= 1.0, got " .. tostring(result.critChance))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Tighten the cap assertion.

The new test name/comment says this verifies capping, but >= 1.0 still passes if critChance regresses above the clamp. Please assert the clamped value directly so this test catches the failure mode.

Proposed fix
-            assert.is_true(result.critChance >= 1.0,
-                "Expected critChance >= 1.0, got " .. tostring(result.critChance))
+            assert.is_near(1.0, result.critChance, 0.001,
+                "Expected critChance to clamp to 1.0, got " .. tostring(result.critChance))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
it("Cold Blood alone caps at 1.00", function()
local state = makeRogueState()
state.auras.player[14177] = true -- Cold Blood +100%
state.talents["1:3"] = 5 -- Malice 5/5 (+5%)
local result = Pipeline.Calculate(1752, state)
-- 0.25 + 1.00 + 0.05 = 1.30 → capped at 1.00
-- 0.25 + 1.00 = 1.25 -> capped at 1.00
assert.is_true(result.critChance >= 1.0,
"Expected critChance >= 1.0, got " .. tostring(result.critChance))
it("Cold Blood alone caps at 1.00", function()
local state = makeRogueState()
state.auras.player[14177] = true -- Cold Blood +100%
local result = Pipeline.Calculate(1752, state)
-- 0.25 + 1.00 = 1.25 -> capped at 1.00
assert.is_near(1.0, result.critChance, 0.001,
"Expected critChance to clamp to 1.0, got " .. tostring(result.critChance))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_rogue_auras.lua` around lines 550 - 556, The test currently uses a
loose check (assert.is_true(result.critChance >= 1.0)) which won't fail if
critChance exceeds the intended cap; change the assertion in the "Cold Blood
alone caps at 1.00" test to assert the clamped value exactly (e.g.
assert.are.equal(1.0, result.critChance) or assert.is_true(result.critChance ==
1.0)) so that Pipeline.Calculate's critChance result is verified to be exactly
1.0 for the given state (makeRogueState + aura 14177).

Xerrion added 3 commits April 29, 2026 13:48
…44)

Phase 5 of #44 - resolves the bug surfaced by /phd debug:
spellID 27213 (Hellfire R4 channel-cast) was unresolvable
because PhDamage stored 27214 (the per-tick effect spell)
in the rank.spellID slot.

TBC channel spells using "Aura #23 Periodic Trigger Spell"
have two IDs per rank: the channel-cast (returned by
GetActionInfo) and the per-tick effect (the proc damage
spell). The new optional rank.effectID field captures both.

Changes:
- New ns.SpellResolver consolidates three duplicated
  reverse-map builders (ActionBar, Tooltip, Diagnostics).
  Single lookup table maps any rank or effect ID to
  (baseID, rankIndex). Pure-Lua, no WoW APIs.
- Hellfire ranks 1-4: add effectID (5857/11681/11682/27214)
  and restore R4 spellID to 27213 (channel-cast).
- ActionBar/Tooltip/Diagnostics now delegate ID resolution
  to ns.SpellResolver.
- New tests/test_spell_resolver.lua covers Hellfire''s
  cast/effect/base ID resolution + type guards + Rebuild.

Engine and combat-log paths untouched (PhDamage has zero
combat-log listeners; only action-bar overlay and tooltip
needed updates).
Cross-class audit covered all TBC channeled AoE spells
using "Aura #23 Periodic Trigger Spell":

- Rain of Fire (Warlock 5740): 5 ranks
- Blizzard (Mage 10): 7 ranks
- Volley (Hunter 1510): 4 ranks
- Hurricane (Druid 16914): 4 ranks

All 20 ranks already had the channel-cast spellID stored
correctly (Hellfire R4 was uniquely MIXED). This commit
adds only the effectID field per rank (effect IDs in the
42xxx range, introduced patch 2.4.0). Now SpellResolver
covers any of these spells dragged from spellbook to
action bar.

Other channeled spells (Drain Life/Soul, Health Funnel,
Mind Flay, Arcane Missiles, Tranquility) use different
mechanics (Periodic Drain, Trigger Missile) - single ID,
no fix needed.

Source: TBC-Twinhead per-rank pages, cross-validated
against the verified Hellfire R1/R4 entries.

Also adds:
- ns.SpellResolver row in AGENTS.md namespace table
- Cross-class describe block in test_spell_resolver.lua
  (8 cases covering one rank cast+effect ID per audited
  spell)
Hellfire R1-R4 had incorrect totalDmg values (255/510/825/1395)
and R1-R3 had incorrect levels (12/28/44 vs the correct 30/42/54).
R4 level was already correct at 68.

Verified against TBC-Twinhead authoritative tooltips:
- R1 (1949):  per-tick 83,  level 30 -> totalDmg = 1245
- R2 (11683): per-tick 139, level 42 -> totalDmg = 2085
- R3 (11684): per-tick 208, level 54 -> totalDmg = 3120
- R4 (27213): per-tick 306, level 68 -> totalDmg = 4590

PhDamage convention for channel spells (confirmed by reading
Engine/SpellCalc.lua ComputeChannel/ComputeDot/ComputeHybrid):
totalDmg = perTickDamage * numTicks. Hellfire is a 15-tick
channel (numTicks=15, tickInterval=1, duration=15) - parent
fields unchanged.

Cross-checked Drain Life R8 (108/tick * 5 ticks = 540, matches
stored 540 exactly) confirming convention is correctly applied
elsewhere. Hellfire is an isolated data error, not class-wide.

Surfaced during /phd debug investigation of issue #44.
@Xerrion
Xerrion merged commit 9f728a5 into master Apr 29, 2026
3 checks passed
@Xerrion
Xerrion deleted the fix/44-accuracy-and-diagnostic branch April 29, 2026 12:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Expected damage overestimates actual; add diagnostic for missing-overlay spells

1 participant