OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      938573c1323d
DATE      2025-08-11
SUBJECT   challenge-mode: fix eu merge logic
FILES     2 CHANGED
HASH      938573c1323d
DATE      2025-08-11
SUBJECT   challenge-mode: fix eu merge logic
FILES     2 CHANGED
 

diff --git a/nix/apps/challenge-mode-leaderboard.nix b/nix/apps/challenge-mode-lea
derboard.nix
index a1da435..05844fd 100644
--- a/nix/apps/challenge-mode-leaderboard.nix
+++ b/nix/apps/challenge-mode-leaderboard.nix
@@ -102,7 +102,7 @@
 
 
       def merge_leaderboard_data(existing_path, new_data):
-          """Merge new leaderboard data with existing data, preserving historical
 entries."""
+        #FIX ME: merge new leaderboard data with existing data, preserving histor
ical entries (https://github.com/ClassicWoWCommunity/mop-classic-bugs/issues/2208)
           if not os.path.exists(existing_path):
               return new_data
 
@@ -110,18 +110,39 @@
               with open(existing_path, 'r', encoding='utf-8') as f:
                   existing_data = json.load(f)
 
-              # Get existing and new leading groups
+              # get existing and new leading groups
               existing_groups = existing_data.get('leading_groups', [])
               new_groups = new_data.get('leading_groups', [])
 
-              # Merge the leading groups (append new to existing)
-              merged_groups = existing_groups + new_groups
+              # combine all groups for deduplication
+              all_groups = existing_groups + new_groups
 
-              # Use new data as base (has current metadata) but with merged group
s
+              # deduplicate runs using the same logic as the parser
+              seen = set()
+              deduplicated_groups = []
+
+              for run in all_groups:
+                  # create a unique identifier for each run using timestamp, dura
tion, and sorted player IDs
+                  player_ids = sorted([member["profile"]["id"] for member in run[
"members"]])
+                  unique_key = (run["completed_timestamp"], run["duration"], tupl
e(player_ids))
+
+                  if unique_key not in seen:
+                      seen.add(unique_key)
+                      deduplicated_groups.append(run)
+
+              # sort by duration and re-rank (same logic as parser)
+              sort_key = lambda run: run["duration"]
+              deduplicated_groups.sort(key=sort_key)
+
+              # re-rank runs from 1 to N based on sorted order
+              for i, run in enumerate(deduplicated_groups):
+                  run["ranking"] = i + 1
+
+              # use new data as base (has current metadata) but with properly sor
ted and ranked groups
               merged_data = new_data.copy()
-              merged_data['leading_groups'] = merged_groups
+              merged_data['leading_groups'] = deduplicated_groups
 
-              print(f"    Merged {len(existing_groups)} existing + {len(new_group
s)} new = {len(merged_groups)} total entries")
+              print(f"    Merged {len(existing_groups)} existing + {len(new_group
s)} new = {len(deduplicated_groups)} sorted and ranked entries")
 
               return merged_data
 
@@ -172,7 +193,7 @@
                   )
                   os.makedirs(os.path.dirname(output_path), exist_ok=True)
 
-                  # For EU realms, merge with existing data to preserve historica
l records
+                  # for EU realms, merge with existing data to preserve historica
l records
                   if realm_info['region'] == 'eu':
                       final_data = merge_leaderboard_data(output_path, leaderboar
d)
                   else:

diff --git a/nix/apps/challenge-mode-leaderb
oard.nix b/nix/apps/challenge-mode-leaderboa
rd.nix
index a1da435..05844fd 100644
--- a/nix/apps/challenge-mode-leaderboard.ni
x
+++ b/nix/apps/challenge-mode-leaderboard.ni
x
@@ -102,7 +102,7 @@
 
 
       def merge_leaderboard_data(existing_p
ath, new_data):
-          """Merge new leaderboard data wit
h existing data, preserving historical entri
es."""
+        #FIX ME: merge new leaderboard data
 with existing data, preserving historical e
ntries (https://github.com/ClassicWoWCommuni
ty/mop-classic-bugs/issues/2208)
           if not os.path.exists(existing_pa
th):
               return new_data
 
@@ -110,18 +110,39 @@
               with open(existing_path, 'r',
 encoding='utf-8') as f:
                   existing_data = json.load
(f)
 
-              # Get existing and new leadin
g groups
+              # get existing and new leadin
g groups
               existing_groups = existing_da
ta.get('leading_groups', [])
               new_groups = new_data.get('le
ading_groups', [])
 
-              # Merge the leading groups (a
ppend new to existing)
-              merged_groups = existing_grou
ps + new_groups
+              # combine all groups for dedu
plication
+              all_groups = existing_groups 
+ new_groups
 
-              # Use new data as base (has c
urrent metadata) but with merged groups
+              # deduplicate runs using the 
same logic as the parser
+              seen = set()
+              deduplicated_groups = []
+
+              for run in all_groups:
+                  # create a unique identif
ier for each run using timestamp, duration, 
and sorted player IDs
+                  player_ids = sorted([memb
er["profile"]["id"] for member in run["membe
rs"]])
+                  unique_key = (run["comple
ted_timestamp"], run["duration"], tuple(play
er_ids))
+
+                  if unique_key not in seen
:
+                      seen.add(unique_key)
+                      deduplicated_groups.a
ppend(run)
+
+              # sort by duration and re-ran
k (same logic as parser)
+              sort_key = lambda run: run["d
uration"]
+              deduplicated_groups.sort(key=
sort_key)
+
+              # re-rank runs from 1 to N ba
sed on sorted order
+              for i, run in enumerate(dedup
licated_groups):
+                  run["ranking"] = i + 1
+
+              # use new data as base (has c
urrent metadata) but with properly sorted an
d ranked groups
               merged_data = new_data.copy()
-              merged_data['leading_groups']
 = merged_groups
+              merged_data['leading_groups']
 = deduplicated_groups
 
-              print(f"    Merged {len(exist
ing_groups)} existing + {len(new_groups)} ne
w = {len(merged_groups)} total entries")
+              print(f"    Merged {len(exist
ing_groups)} existing + {len(new_groups)} ne
w = {len(deduplicated_groups)} sorted and ra
nked entries")
 
               return merged_data
 
@@ -172,7 +193,7 @@
                   )
                   os.makedirs(os.path.dirna
me(output_path), exist_ok=True)
 
-                  # For EU realms, merge wi
th existing data to preserve historical reco
rds
+                  # for EU realms, merge wi
th existing data to preserve historical reco
rds
                   if realm_info['region'] =
= 'eu':
                       final_data = merge_le
aderboard_data(output_path, leaderboard)
                   else:
 

diff --git a/nix/simulation/mkTestGroupSim.nix b/nix/simulation/mkTestGroupSim.nix
index b3d3374..2ff5589 100644
--- a/nix/simulation/mkTestGroupSim.nix
+++ b/nix/simulation/mkTestGroupSim.nix
@@ -19,6 +19,7 @@
     iterations = 500;
     encounter = mkEncounter {
       targets = [
+        # based off Sha of Doubt challenge mode logs
         (mkTarget {
           level = 93;
           mobType = "elemental";
@@ -26,16 +27,18 @@
           minBaseDamage = 150000;
         })
       ];
+      # irrelevant if useHealth true
       duration = 60;
       durationVariation = 20;
+
       useHealth = true;
     };
     raid = mkRaid {
       dynamicBuffs = true;
       party1 = [
         monk.brewmaster.template.orc.p1.challengeMode.multiTarget
-        warlock.destruction.template.troll.p1.challengeMode.singleTarget
-        hunter.survival.template.troll.p1.challengeMode.singleTarget
+        shaman.elemental.template.troll.p1.challengeMode.singleTarget
+        paladin.retribution.template.blood_elf.p1.challengeMode.singleTarget
         shaman.elemental.template.troll.p1.challengeMode.singleTarget
       ];
     };
@@ -51,6 +54,9 @@
       echo "Running simulation with wowsimcli..."
       wowsimcli sim --infile test-group-sim.json --outfile test-out.json
 
+      echo "=== Sha of Doubt - Challenge Mode - 19.6M HP ==="
+      echo "=== Buffs ==="
+      echo $(jq -r '.)
       echo "=== Simulation Results ==="
       echo "Avg Fight Duration: $(jq -r '.avgIterationDuration' test-out.json)s"
       echo "Iterations: $(jq -r '.iterationsDone' test-out.json)"

diff --git a/nix/simulation/mkTestGroupSim.n
ix b/nix/simulation/mkTestGroupSim.nix
index b3d3374..2ff5589 100644
--- a/nix/simulation/mkTestGroupSim.nix
+++ b/nix/simulation/mkTestGroupSim.nix
@@ -19,6 +19,7 @@
     iterations = 500;
     encounter = mkEncounter {
       targets = [
+        # based off Sha of Doubt challenge 
mode logs
         (mkTarget {
           level = 93;
           mobType = "elemental";
@@ -26,16 +27,18 @@
           minBaseDamage = 150000;
         })
       ];
+      # irrelevant if useHealth true
       duration = 60;
       durationVariation = 20;
+
       useHealth = true;
     };
     raid = mkRaid {
       dynamicBuffs = true;
       party1 = [
         monk.brewmaster.template.orc.p1.cha
llengeMode.multiTarget
-        warlock.destruction.template.troll.
p1.challengeMode.singleTarget
-        hunter.survival.template.troll.p1.c
hallengeMode.singleTarget
+        shaman.elemental.template.troll.p1.
challengeMode.singleTarget
+        paladin.retribution.template.blood_
elf.p1.challengeMode.singleTarget
         shaman.elemental.template.troll.p1.
challengeMode.singleTarget
       ];
     };
@@ -51,6 +54,9 @@
       echo "Running simulation with wowsimc
li..."
       wowsimcli sim --infile test-group-sim
.json --outfile test-out.json
 
+      echo "=== Sha of Doubt - Challenge Mo
de - 19.6M HP ==="
+      echo "=== Buffs ==="
+      echo $(jq -r '.)
       echo "=== Simulation Results ==="
       echo "Avg Fight Duration: $(jq -r '.a
vgIterationDuration' test-out.json)s"
       echo "Iterations: $(jq -r '.iteration
sDone' test-out.json)"
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET