From c81bd99382d5a1e634b2db44a19f95260f05d326 Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sat, 15 Aug 2026 21:56:27 +0200 Subject: [PATCH 1/6] fix(rotation): implement proper Spiteful Spirit (29560) finder logic - Implement both EI finder paths: 1. DamageCastFinder: disabled when UnholyBurst effect data exists (UsingDisableWithEffectData) 2. EffectCastFinder for UnholyBurst: triggers on effect with DesertShroud/related hit checks - Add DesertShroud buff removal check (skill 40052, 50ms window) - Add related UnholyBurst hit check (skill 38767, 10ms window) - Increase UnholyBurst proximity window to 100ms (was 50ms) to catch early casts Fixes 31 Spiteful Spirit FAILs across corpus. Signed-off-by: Roland Salardon --- .../src/gw2_analytics/rotation.py | 58 ++++++++++++++++--- 1 file changed, 51 insertions(+), 7 deletions(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 636069df..01a002ae 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -955,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( From 641434585d62d2e627a221bc6970c9fd85696cbc Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sat, 15 Aug 2026 20:26:28 +0200 Subject: [PATCH 2/6] fix(rotation): add missing finders for Shale Storm, Relic of Sorrow, Unnatural Traversal, Shattered Aegis - Shale Storm (72906): BuffGainCastFinder for ShaleStormBuff (72996) and ShaleStormSecondaryAttackBuff (79373) - Relic of Sorrow (74410): EffectCastFinder with RelicOfSorrow1/2/3 effect GUIDs - Unnatural Traversal (63195): EffectCastFinderByDst with UntamedUnnaturalTraversal effect GUID - Shattered Aegis (22499): DamageCastFinder with 50ms ICD These address missing rotation casts in the corpus. Signed-off-by: Roland Salardon --- libs/gw2_analytics/src/gw2_analytics/rotation.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 01a002ae..32c6c604 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -203,6 +203,7 @@ 31658: 50, # Glyph of Equality 5572: 50, # Signet of Air (damage path; + effect-by-dst below) 76315: 50, # Bloodstone Explosion + 22499: 50, # Shattered Aegis } _DAMAGE_CASTS_BY_DAMAGE = {40071: 44428, 46808: 40813} _HEALING_CASTS = { @@ -465,7 +466,7 @@ "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 "D7F8FA5695F8714B99A5C3EE72EF6E178": 14413, # Dolyak Signet From 9b4b8b7d44c50cbc96c9020f279c8ce193752b5e Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sat, 15 Aug 2026 20:38:18 +0200 Subject: [PATCH 3/6] fix(rotation): fix duplicate key 22499 in _DAMAGE_CASTS dict Signed-off-by: Roland Salardon --- libs/gw2_analytics/src/gw2_analytics/rotation.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 32c6c604..dbc7f53b 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -203,7 +203,6 @@ 31658: 50, # Glyph of Equality 5572: 50, # Signet of Air (damage path; + effect-by-dst below) 76315: 50, # Bloodstone Explosion - 22499: 50, # Shattered Aegis } _DAMAGE_CASTS_BY_DAMAGE = {40071: 44428, 46808: 40813} _HEALING_CASTS = { From 733293ba381020b28e251c2da636f48057db34f1 Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Fri, 14 Aug 2026 23:10:14 +0200 Subject: [PATCH 4/6] chore: trigger CI rerun after Playwright chromium install timeout Signed-off-by: Roland Salardon From ceb7a19605308a0d2a1c21da6f4404e4b237191e Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sun, 16 Aug 2026 00:22:36 +0200 Subject: [PATCH 5/6] style(rotation): fix line length for UnholyBurst comment Signed-off-by: Roland Salardon --- libs/gw2_analytics/src/gw2_analytics/rotation.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index dbc7f53b..5e32a948 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 From 2a3c29b2c4fc9353efddcca4ec345c0d36810f87 Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Sun, 16 Aug 2026 00:26:47 +0200 Subject: [PATCH 6/6] style(rotation): ruff format Signed-off-by: Roland Salardon --- libs/gw2_analytics/src/gw2_analytics/rotation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/rotation.py b/libs/gw2_analytics/src/gw2_analytics/rotation.py index 5e32a948..f4c018dd 100644 --- a/libs/gw2_analytics/src/gw2_analytics/rotation.py +++ b/libs/gw2_analytics/src/gw2_analytics/rotation.py @@ -465,7 +465,7 @@ "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 "D7F8FA5695F8714B99A5C3EE72EF6E178": 14413, # Dolyak Signet