Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
b664699
feat(ei-parity): normaliser le rapport de certification
Roddygithub Aug 14, 2026
e1f2de6
fix(ei-parity): resoudre les erreurs mypy des tests et la vulnerabili…
Roddygithub Aug 14, 2026
78ebdf6
feat(ei-parity): Story 3 - temporal identity resolution (CAP-4)
Roddygithub Aug 14, 2026
b3908b0
feat(ei-parity): Story 3 - temporal identity resolution (CAP-4)
Roddygithub Aug 14, 2026
ebe4826
chore(lint): exclude BMAD framework dirs from ruff check
Roddygithub Aug 14, 2026
12bc035
chore(format): ruff format fixes
Roddygithub Aug 14, 2026
8ade300
fix(mypy): fix type annotations for temporal_identity and rotation
Roddygithub Aug 14, 2026
73fddff
fix(rotation): build cast windows for Grand Finale to gate Flame Whee…
Roddygithub Aug 15, 2026
bbc2093
fix(rotation): gate 71356/13980 on EXT src_is_peer; apply 5780 ICD 900ms
Roddygithub Aug 15, 2026
0a322e8
fix(rotation): add missing finders for Shale Storm, Relic of Sorrow, …
Roddygithub Aug 15, 2026
7b77a56
Merge branch 'main' into fix/rotation-finders
Roddygithub Aug 15, 2026
00f8779
fix(rotation): fix duplicate key 22499 in _DAMAGE_CASTS dict
Roddygithub Aug 15, 2026
45d79d4
fix(rotation): add missing finders for Shale Storm, Relic of Sorrow, …
Roddygithub Aug 15, 2026
8319b1b
fix(rotation): fix duplicate key 22499 in _DAMAGE_CASTS dict
Roddygithub Aug 15, 2026
a405c3e
style(rotation): ruff format
Roddygithub Aug 15, 2026
2a014ba
fix(rotation): implement proper Spiteful Spirit (29560) finder logic
Roddygithub Aug 15, 2026
65e2125
Merge branch 'fix/rotation-finders-v3' into fix/rotation-finders
Roddygithub Aug 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 60 additions & 9 deletions libs/gw2_analytics/src/gw2_analytics/rotation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,),
Expand Down Expand Up @@ -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(
Expand Down
3 changes: 0 additions & 3 deletions libs/gw2_analytics/tests/test_ei_compare.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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",
Expand Down
Loading
Loading