diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 7aa91c85..dbc7f53b 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -126,6 +126,8 @@ 9123: 9082, # Shield of Wrath 10582: 10583, # Spectral Armor 62919: 62749, # Legendary Alliance Stance + 72996: 72906, # Shale Storm buff -> Shale Storm skill + 79373: 72906, # Shale Storm Secondary Attack buff -> Shale Storm skill } _BUFF_LOSS_CASTS = { 29446: 30961, @@ -335,6 +337,10 @@ "13D0B65D73B5334D80824EE17B5C257E": 13677, "FB78801BB31CAF488B55F2F57EF9B070": 78837, "842F977C318FDC4F96C99C385C1D0672": 76613, # Symbiotic Shielding + "3D981397D9C6A44B888212CE4E3F6F9A": 74410, # Relic of Sorrow + "DB02EB39ABBEE241859DC9662AD49FA5": 74410, # Relic of Sorrow + "FEEA4632BAF0B1438D83F6C8B71AEA15": 74410, # Relic of Sorrow + "8D36806A690A5442A983308EDCECB018": 63195, # Unnatural Traversal "4A83F0B627B75C47894941C4D35BA89F": 78604, "03850757F14FD44A9998D4CAD71CC589": 78358, "611D90C69ECF8142BEEE84139F333388": 30101, @@ -459,11 +465,12 @@ "9242D10B4F04274EB6E9EBCDB2262181": 77213, "B02D3D0FF0A4FC47B23B1478D8E770AE": -29, "30A96C0E559DBD489FEE36DA96CC374A": 5572, # Signet of Air - "23284B87C26C9A41A887F410F930E1A2": 13064, # Infiltrator's Signet +"23284B87C26C9A41A887F410F930E1A2": 13064, # Infiltrator's Signet "BB5488951B60B546BB1BD5626DAE83E1": 13062, # Signet of Agility "418A090D719AB44AAF1C4AD1473068C4": 43176, # Flash Spark - "D7F8FA5695F8714B99A51EE72EF6E178": 14413, # Dolyak Signet + "D7F8FA5695F8714B99A5C3EE72EF6E178": 14413, # Dolyak Signet "68F2C378E6C80548B5A3C89870C5DD86": 9085, # "Save Yourselves!" + "8D36806A690A5442A983308EDCECB018": 63195, # Unnatural Traversal } _SECONDARY_EFFECTS = { _MECHANIST_SHIFT_SIGNET_EFFECT: (_MECHANIST_SHIFT_SIGNET_SELF_EFFECT,), @@ -948,17 +955,61 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None owner = spawn_owner_by_target.get(event.target_agent_id) or event.source_agent_id if owner: add_instant(owner, 12658, event.time_ms) + # Spiteful Spirit (29560) - EI has two finders: + # 1. DamageCastFinder: disabled when effect data exists (UsingDisableWithEffectData) + # 2. EffectCastFinder for UnholyBurst: triggers on effect with DesertShroud/related hit checks elif isinstance(event, DamageEvent) and event.skill_id == 29560: source_is_necro = ( not professions or professions.get(event.source_agent_id) is Profession.NECROMANCER ) - if source_is_necro and not any( - isinstance(other, EffectEvent) - and other.guid == "C4E8DD3234E0C647993857940ED79AC1" - and other.source_agent_id == event.source_agent_id - and abs(other.time_ms - event.time_ms) < 50 - for other in nearby_events(event.time_ms, 49) - ): + if not source_is_necro: + continue + + # Check if there's any UnholyBurst effect in the fight (global check) + has_unholy_burst_in_fight = any( + isinstance(other, EffectEvent) and other.guid == "C4E8DD3234E0C647993857940ED79AC1" + for other in event_list + ) + + if has_unholy_burst_in_fight: + # EI uses EffectCastFinder: only emit when UnholyBurst effect is present + # with DesertShroud and related hit checks + unholy_burst_nearby = any( + isinstance(other, EffectEvent) + and other.guid == "C4E8DD3234E0C647993857940ED79AC1" + and other.source_agent_id == event.source_agent_id + and abs(other.time_ms - event.time_ms) < 100 + for other in nearby_events(event.time_ms, 100) + ) + if not unholy_burst_nearby: + continue + + # DesertShroud check: no DesertShroud buff removal within 50ms + desert_shroud_removal = any( + isinstance(other, BoonApplyEvent) + and other.kind == "remove_all" + and other.skill_id == 40052 # DesertShroudBuff + and other.source_agent_id == event.source_agent_id + and abs(other.time_ms - event.time_ms) < 50 + for other in nearby_events(event.time_ms, 50) + ) + if desert_shroud_removal: + continue + + # Related hit check: no UnholyBurst hit from same caster within 10ms + related_hit = any( + isinstance(other, DamageEvent) + and other.source_agent_id == event.source_agent_id + and other.skill_id == 38767 # UnholyBurst + and abs(other.time_ms - event.time_ms) < 10 + for other in nearby_events(event.time_ms, 10) + ) + if related_hit: + continue + + add_instant(event.source_agent_id, event.skill_id, event.time_ms) + else: + # No UnholyBurst effect data in fight: use DamageCastFinder add_instant(event.source_agent_id, event.skill_id, event.time_ms) elif isinstance(event, DamageEvent) and event.skill_id in _DAMAGE_CASTS: add_instant( diff --git a/libs/gw2_analytics/tests/test_ei_compare.py b/libs/gw2_analytics/tests/test_ei_compare.py index 502e23fe..539d1ee1 100644 --- a/libs/gw2_analytics/tests/test_ei_compare.py +++ b/libs/gw2_analytics/tests/test_ei_compare.py @@ -2,8 +2,6 @@ from typing import Any, cast -import pytest - from gw2_analytics.ei_compare import _skill_stats, compare_elite_insights from gw2_core import ( Agent, @@ -23,7 +21,6 @@ def _rows(result: dict[str, object]) -> dict[str, dict[str, Any]]: return {str(row["key"]): row for row in cast("list[dict[str, Any]]", result["results"])} -@pytest.mark.skip(reason="Pre-existing flaky test - deadCount mismatch for Non Squad Player 5") def test_compare_elite_insights_keeps_first_anonymous_agent_for_shared_instance() -> None: fight = Fight( id="fight", diff --git a/scripts/ei-parity/known-deltas.json b/scripts/ei-parity/known-deltas.json index 29277218..f69920aa 100644 --- a/scripts/ei-parity/known-deltas.json +++ b/scripts/ei-parity/known-deltas.json @@ -1,406 +1,4 @@ { "schema_version": 1, - "rules": [ - { - "id": "71356-first-heal-skip", - "selector": { - "skill_id": 71356, - "bucket": "players.rotation" - }, - "constraint": { - "max_abs_delta": 0.001 - }, - "reason": "EI skips first heal of 71356 per caster (EXTHealingCastFinder edge case)", - "remove_when": "fixed" - }, - { - "id": "13980-first-heal-skip", - "selector": { - "skill_id": 13980, - "bucket": "players.rotation" - }, - "constraint": { - "max_abs_delta": 0.001 - }, - "reason": "EI skips first heal of 13980 per caster (EXTHealingCastFinder edge case)", - "remove_when": "fixed" - }, - { - "id": "krill-group-slice-9617", - "selector": { - "account": "krill le faucheur.1679", - "slice": 9617, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "coligroup-slice-10523", - "selector": { - "account": "Coli.7136", - "slice": 10523, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "mikey-group-slice-3731", - "selector": { - "account": "Mikey.4982", - "slice": 3731, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "sharpsteel-group-slice-214", - "selector": { - "account": "SharpSteel.3051", - "slice": 214, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "empiria-group-slice-11322", - "selector": { - "account": "empiria.8961", - "slice": 11322, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "krill-group-slice-5561", - "selector": { - "account": "krill le faucheur.1679", - "slice": 5561, - "bucket": "players.group" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "EI group=1 for first slice of multi-slice WvW player", - "remove_when": "fixed" - }, - { - "id": "SagerKing.3907-slice-downContribution", - "selector": { - "account": "SagerKing.3907", - "slice": 1, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 449 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "EstaticFear.7692-slice-downContribution", - "selector": { - "account": "EstaticFear.7692", - "slice": 3, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 592 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-slice-appliedCrowdControlDownContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsAll.appliedCrowdControlDownContribution" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-slice-appliedCrowdControlDurationDownContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsAll.appliedCrowdControlDurationDownContribution" - }, - "constraint": { - "max_abs_delta": 1000 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-slice-downContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 2657 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "LuiStheGamers.5132-slice-downContribution", - "selector": { - "account": "LuiStheGamers.5132", - "slice": 1, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 40 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "creative.1094-slice-appliedCrowdControl", - "selector": { - "account": "creative.1094", - "slice": 0, - "bucket": "players.statsAll.appliedCrowdControl" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "creative.1094-slice-appliedCrowdControlDuration", - "selector": { - "account": "creative.1094", - "slice": 0, - "bucket": "players.statsAll.appliedCrowdControlDuration" - }, - "constraint": { - "max_abs_delta": 2000 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Schoko.1536-slice-downContribution", - "selector": { - "account": "Schoko.1536", - "slice": 2, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 2436 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-slice-appliedCrowdControlDownContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsAll.appliedCrowdControlDownContribution" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-slice-appliedCrowdControlDurationDownContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsAll.appliedCrowdControlDurationDownContribution" - }, - "constraint": { - "max_abs_delta": 1500 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-slice-downContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsAll.downContribution" - }, - "constraint": { - "max_abs_delta": 1177 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats", - "remove_when": "fixed" - }, - { - "id": "EstaticFear.7692-targets-downContribution", - "selector": { - "account": "EstaticFear.7692", - "slice": 3, - "bucket": "players.statsTargets.downContribution" - }, - "constraint": { - "max_abs_delta": 592 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-targets-appliedCrowdControlDownContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsTargets.appliedCrowdControlDownContribution" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-targets-appliedCrowdControlDurationDownContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsTargets.appliedCrowdControlDurationDownContribution" - }, - "constraint": { - "max_abs_delta": 1000 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Kurupt.6378-targets-downContribution", - "selector": { - "account": "Kurupt.6378", - "slice": 0, - "bucket": "players.statsTargets.downContribution" - }, - "constraint": { - "max_abs_delta": 2657 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "LuiStheGamers.5132-targets-downContribution", - "selector": { - "account": "LuiStheGamers.5132", - "slice": 1, - "bucket": "players.statsTargets.downContribution" - }, - "constraint": { - "max_abs_delta": 40 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "creative.1094-targets-appliedCrowdControl", - "selector": { - "account": "creative.1094", - "slice": 0, - "bucket": "players.statsTargets.appliedCrowdControl" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "creative.1094-targets-appliedCrowdControlDuration", - "selector": { - "account": "creative.1094", - "slice": 0, - "bucket": "players.statsTargets.appliedCrowdControlDuration" - }, - "constraint": { - "max_abs_delta": 2000 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Schoko.1536-targets-downContribution", - "selector": { - "account": "Schoko.1536", - "slice": 2, - "bucket": "players.statsTargets.downContribution" - }, - "constraint": { - "max_abs_delta": 2436 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-targets-appliedCrowdControlDownContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsTargets.appliedCrowdControlDownContribution" - }, - "constraint": { - "max_abs_delta": 1 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-targets-appliedCrowdControlDurationDownContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsTargets.appliedCrowdControlDurationDownContribution" - }, - "constraint": { - "max_abs_delta": 1500 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - }, - { - "id": "Nikola.8512-targets-downContribution", - "selector": { - "account": "Nikola.8512", - "slice": 1, - "bucket": "players.statsTargets.downContribution" - }, - "constraint": { - "max_abs_delta": 1177 - }, - "reason": "Slice-matching divergence for down/CC contribution in stats (targets)", - "remove_when": "fixed" - } - ] -} \ No newline at end of file + "rules": [] +}