From 14aaa5a163aa0ff2971f1e1817d0d1a747cc0274 Mon Sep 17 00:00:00 2001 From: Roland Salardon Date: Wed, 12 Aug 2026 02:01:56 +0200 Subject: [PATCH] fix(ei-compare): reach full parity on ranger pet spawns (-28) Elite Insights credits the -28 "Ranger Pet Spawned" rotation entry only for minions whose species is in its static JuvenilePetIDs list (65 species) AND whose master relationship was resolved (activeRangerPets != null). Two fixes bring parity to 0 missing / 0 extra across the 35-log corpus (was 2 missing / 11 extra): - Replace the name-based juvenile-pet filter (which wrongly included the phoenix 25131, absent from EI's list) with the exact _JUVENILE_PET_SPECIES whitelist mirrored from RangerHelper.cs. - Gate -28 casts on player.get("activeRangerPets") being non-null, mirroring EI's MinionSpawnCastFinder which requires m.Master != null. - Keep -28 casts that land within 2000ms before slice_lo, matching EI's time-slice tolerance. Signed-off-by: Roland Salardon --- .../src/gw2_analytics/ei_compare.py | 91 ++++++++++++++++++- 1 file changed, 86 insertions(+), 5 deletions(-) diff --git a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py index b9658568..7b690b68 100644 --- a/libs/gw2_analytics/src/gw2_analytics/ei_compare.py +++ b/libs/gw2_analytics/src/gw2_analytics/ei_compare.py @@ -35,6 +35,84 @@ spec_display_name, ) +#: Exact mirror of Elite Insights' ``RangerHelper.JuvenilePetIDs`` +#: (union of the 11 juvenile-pet family lists + the base list). A spawn +#: of one of these agents becomes the ``-28`` "Ranger Pet Spawned" +#: rotation entry on its master. ``JuvenilePhoenix`` (25131) is +#: deliberately absent -- EI credits it only via +#: ``MinionCommandCastFinder(GaleBreath)``, so a phoenix spawn must NOT +#: emit ``-28``. +# ponytail:EIJuvenilePetIDs -- from EI RangerHelper.cs / SpeciesIDs.cs +_JUVENILE_PET_SPECIES: frozenset[int] = frozenset( + { + 3827, + 4425, + 4426, + 5581, + 5582, + 6043, + 6044, + 6045, + 6849, + 6850, + 6883, + 6884, + 6885, + 6886, + 6887, + 6888, + 6889, + 6898, + 6968, + 7336, + 7926, + 7927, + 7928, + 7932, + 7948, + 7949, + 7975, + 7976, + 8002, + 8003, + 8004, + 8005, + 8006, + 8007, + 8008, + 8013, + 8014, + 8015, + 8016, + 8035, + 8041, + 8042, + 9458, + 10022, + 11491, + 15380, + 15399, + 15402, + 15418, + 15436, + 18119, + 18688, + 19005, + 19104, + 19166, + 24203, + 24298, + 24796, + 25652, + 26147, + 26220, + 26628, + 26851, + 27259, + 27687, + } +) + _STAT_FIELDS = ( "totalDamageCount", "totalDmg", @@ -533,11 +611,7 @@ def player_agent_ids(agent: Agent) -> set[int]: and agent.elite in {EliteSpec.UNKNOWN, EliteSpec.MIRAGE, EliteSpec.CHRONOMANCER} }, {agent.id for agent in fight.agents if agent.species_id == 8111}, - { - agent.id - for agent in fight.agents - if agent.name.startswith(("Juvenile ", "Jeune ")) or agent.name.endswith(" juvénile") - }, + {agent.id for agent in fight.agents if agent.species_id in _JUVENILE_PET_SPECIES}, {agent.id for agent in fight.agents if agent.species_id == 24796}, {agent.id for agent in fight.agents if agent.species_id == 15402}, {agent.id for agent in fight.agents if agent.species_id == 7336}, @@ -833,17 +907,24 @@ def in_slice(event: Event, _lo: int = slice_lo, _hi: int = slice_hi) -> bool: ), key=lambda item: (item[1], item[0]), ) + has_active_ranger_pets = player.get("activeRangerPets") is not None actual_casts = sorted( ( (cast.skill_id, cast.time_ms, cast.duration_ms) for cast in rotation if cast.source_agent_id in agent_ids + and (cast.skill_id != -28 or has_active_ranger_pets) and ( (cast.time_ms + origin >= slice_lo and cast.time_ms + origin <= slice_hi) or ( cast.time_ms < 0 and slice_lo <= cast.time_ms + cast.duration_ms + origin <= slice_hi ) + or ( + cast.skill_id == -28 + and cast.time_ms + origin <= slice_hi + and slice_lo - (cast.time_ms + origin) <= 2000 + ) ) ), key=lambda item: (item[1], item[0]),