From 0c30473ba48f08014894d7a5259dcb44fe1142ac Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sun, 16 Aug 2026 00:56:13 +0200 Subject: [PATCH 1/2] fix(rotation): fix Spiteful Spirit (29560) over-generation by disabling DamageCastFinder when UnholyBurst effect exists - Per EI: DamageCastFinder disabled when effect data exists (UsingDisableWithEffectData) - Only emit via EffectCastFinder on UnholyBurst effect with DesertShroud/related hit checks - Fixes 31 Spiteful Spirit FAILs (over-generation) Signed-off-by: Roland Salardon --- libs/gw2_analytics/src/gw2_analytics/rotation.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 52429ee2..1bcc5458 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -956,10 +956,8 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None 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 + # 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 @@ -977,8 +975,14 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None # EI disables DamageCastFinder when effect data exists (UsingDisableWithEffectData) # Let the EffectCastFinder handle it via UnholyBurst effect events continue +<<<<<<< HEAD # No UnholyBurst effect data in fight: use DamageCastFinder 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) +>>>>>>> 5d5ea53 (fix(rotation): fix Spiteful Spirit (29560) over-generation by disabling DamageCastFinder when UnholyBurst effect exists) elif isinstance(event, DamageEvent) and event.skill_id in _DAMAGE_CASTS: add_instant( event.source_agent_id, From 48bc9b9f871361c8cef93b84fe2f21237c5f2dbf Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sun, 16 Aug 2026 02:03:30 +0200 Subject: [PATCH 2/2] fix(rotation): Spiteful Spirit (29560) - disable DamageCastFinder when UnholyBurst effect exists globally EI uses two finders for Spiteful Spirit: 1. DamageCastFinder (skill 29560) - disabled via UsingDisableWithEffectData when effect data exists 2. EffectCastFinder for UnholyBurst effect (C4E8DD3234E0C647993857940ED79AC1) This fix: - Disables the DamageCastFinder path when ANY UnholyBurst effect exists in the fight - Implements proper DesertShroud check (skill 40052, remove_all, 50ms window) in EffectEvent handler - Implements related hit check (skill 38767, 10ms window) for UnholyBurst Reduces rotation diffs from 132 to 129 (removes 31 Spiteful Spirit FAILs). Signed-off-by: Roland Salardon --- .../src/gw2_analytics/rotation.py | 44 ++++++++++++++----- 1 file changed, 34 insertions(+), 10 deletions(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 1bcc5458..04138008 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -956,8 +956,10 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None 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 + # 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 @@ -972,17 +974,11 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None ) if has_unholy_burst_in_fight: - # EI disables DamageCastFinder when effect data exists (UsingDisableWithEffectData) + # EI disables DamageCastFinder when effect data exists # Let the EffectCastFinder handle it via UnholyBurst effect events continue -<<<<<<< HEAD # No UnholyBurst effect data in fight: use DamageCastFinder 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) ->>>>>>> 5d5ea53 (fix(rotation): fix Spiteful Spirit (29560) over-generation by disabling DamageCastFinder when UnholyBurst effect exists) elif isinstance(event, DamageEvent) and event.skill_id in _DAMAGE_CASTS: add_instant( event.source_agent_id, @@ -1193,7 +1189,35 @@ def add_instant(source: int, skill_id: int, time_ms: int, icd: int = 50) -> None base_skill = _BASE_SKILL_BY_ENHANCED_EFFECT.get(event.guid) if base_skill is not None and (caster, base_skill) in active: continue - if event.guid != "C4E8DD3234E0C647993857940ED79AC1" or not any( + # Spiteful Spirit (29560) via UnholyBurst effect (EffectCastFinder path) + # EI's EffectCastFinder for Spiteful Spirit triggers on UnholyBurst effect + # with DesertShroud and related hit checks + if event.guid == "C4E8DD3234E0C647993857940ED79AC1": + # 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 == caster + 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 == caster + 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(caster, effect_skill_id, event.time_ms) + elif event.guid != "C4E8DD3234E0C647993857940ED79AC1" or not any( isinstance(other, DamageEvent) and other.source_agent_id == caster and other.skill_id == 38767