OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
HASH      4fb210957165
DATE      2025-08-06
SUBJECT   nix: group simulation testing
FILES     12 CHANGED
HASH      4fb210957165
DATE      2025-08-06
SUBJECT   nix: group simulation testing
FILES     12 CHANGED
 

diff --git a/nix/apps/default.nix b/nix/apps/default.nix
index 4d6703d..e4b1e03 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -58,6 +58,10 @@
           type = "app";
           program = "${parseCMs}/bin/cm-leaderboard-parser";
         };
+        testGroupSim = {
+          type = "app";
+          program = "${simulation.generateTestGroupSim pkgs}/bin/test-group-sim";
+        };
       };
   };
 }

diff --git a/nix/apps/default.nix b/nix/apps
/default.nix
index 4d6703d..e4b1e03 100644
--- a/nix/apps/default.nix
+++ b/nix/apps/default.nix
@@ -58,6 +58,10 @@
           type = "app";
           program = "${parseCMs}/bin/cm-lea
derboard-parser";
         };
+        testGroupSim = {
+          type = "app";
+          program = "${simulation.generateT
estGroupSim pkgs}/bin/test-group-sim";
+        };
       };
   };
 }
 

diff --git a/nix/encounter/default.nix b/nix/encounter/default.nix
index dfd8ccb..e4f8d14 100644
--- a/nix/encounter/default.nix
+++ b/nix/encounter/default.nix
@@ -41,6 +41,10 @@
 
   encounter = {
     inherit raid;
+    shaOfDoubt = mkEncounter {
+      targets = [target.shaOfDoubt];
+      useHealth = true;
+    };
   };
 in {
   flake.encounter = encounter;

diff --git a/nix/encounter/default.nix b/nix
/encounter/default.nix
index dfd8ccb..e4f8d14 100644
--- a/nix/encounter/default.nix
+++ b/nix/encounter/default.nix
@@ -41,6 +41,10 @@
 
   encounter = {
     inherit raid;
+    shaOfDoubt = mkEncounter {
+      targets = [target.shaOfDoubt];
+      useHealth = true;
+    };
   };
 in {
   flake.encounter = encounter;
 

diff --git a/nix/lib/classes.nix b/nix/lib/classes.nix
index 00d40ab..dc5afa6 100644
--- a/nix/lib/classes.nix
+++ b/nix/lib/classes.nix
@@ -40,7 +40,7 @@
                 challengeMode = true;
                 inherit race class spec options distanceFromTarget profession1 pr
ofession2 consumables;
                 inherit (singleTarget) apl;
-                inherit (challengeMode) gearset talents;
+                inherit (challengeMode) glyphs gearset talents;
               };
               multiTarget = mkPlayer {
                 challengeMode = true;

diff --git a/nix/lib/classes.nix b/nix/lib/c
lasses.nix
index 00d40ab..dc5afa6 100644
--- a/nix/lib/classes.nix
+++ b/nix/lib/classes.nix
@@ -40,7 +40,7 @@
                 challengeMode = true;
                 inherit race class spec opt
ions distanceFromTarget profession1 professi
on2 consumables;
                 inherit (singleTarget) apl;
-                inherit (challengeMode) gea
rset talents;
+                inherit (challengeMode) gly
phs gearset talents;
               };
               multiTarget = mkPlayer {
                 challengeMode = true;
 

diff --git a/nix/lib/encounter.nix b/nix/lib/encounter.nix
index f66d916..93222b3 100644
--- a/nix/lib/encounter.nix
+++ b/nix/lib/encounter.nix
@@ -3,9 +3,10 @@ let
     duration ? 300,
     durationVariation ? 60,
     targets ? [],
+    useHealth ? false,
   }: {
     apiVersion = 1;
-    inherit duration durationVariation targets;
+    inherit useHealth duration durationVariation targets;
 
     # default execute config
     executeProportion20 = 0.2;

diff --git a/nix/lib/encounter.nix b/nix/lib
/encounter.nix
index f66d916..93222b3 100644
--- a/nix/lib/encounter.nix
+++ b/nix/lib/encounter.nix
@@ -3,9 +3,10 @@ let
     duration ? 300,
     durationVariation ? 60,
     targets ? [],
+    useHealth ? false,
   }: {
     apiVersion = 1;
-    inherit duration durationVariation targ
ets;
+    inherit useHealth duration durationVari
ation targets;
 
     # default execute config
     executeProportion20 = 0.2;
 

diff --git a/nix/lib/player.nix b/nix/lib/player.nix
index 218e9cd..78b67b0 100644
--- a/nix/lib/player.nix
+++ b/nix/lib/player.nix
@@ -23,7 +23,7 @@
   toPascalCase = string: concatMapStrings toUpperCaseFirstLetter (splitWords stri
ng);
 
   mkPlayer = {
-    name ? "Player",
+    name ? "${spec} Player",
     race,
     class,
     spec,

diff --git a/nix/lib/player.nix b/nix/lib/pl
ayer.nix
index 218e9cd..78b67b0 100644
--- a/nix/lib/player.nix
+++ b/nix/lib/player.nix
@@ -23,7 +23,7 @@
   toPascalCase = string: concatMapStrings t
oUpperCaseFirstLetter (splitWords string);
 
   mkPlayer = {
-    name ? "Player",
+    name ? "${spec} Player",
     race,
     class,
     spec,
 

diff --git a/nix/lib/raid.nix b/nix/lib/raid.nix
index 90e4c20..dc6378f 100644
--- a/nix/lib/raid.nix
+++ b/nix/lib/raid.nix
@@ -1,22 +1,126 @@
 {lib, ...}: let
   inherit (lib.sim.party) mkParty;
-  # TODO, dynamically set buffs/debuffs based on specs present
+
+  # Extract class/spec from player
+  getPlayerClassSpec = player:
+    if builtins.isAttrs player && builtins.hasAttr "class" player
+    then let
+      # Extract class name from the enum-style class field
+      # e.g., "ClassShaman" -> "shaman"
+      classStr = player.class;
+      className =
+        if builtins.isString classStr && lib.hasPrefix "Class" classStr
+        then lib.toLower (lib.removePrefix "Class" classStr)
+        else null;
+      
+      # Extract spec from spec-specific fields like "elementalShaman", "brewmaste
rMonk", etc.
+      specName = 
+        if builtins.hasAttr "elementalShaman" player then "elemental"
+        else if builtins.hasAttr "enhancementShaman" player then "enhancement"
+        else if builtins.hasAttr "restorationShaman" player then "restoration"
+        else if builtins.hasAttr "brewmasterMonk" player then "brewmaster"
+        else if builtins.hasAttr "windwalkerMonk" player then "windwalker"
+        else if builtins.hasAttr "mistweaverMonk" player then "mistweaver"
+        else if builtins.hasAttr "survivalHunter" player then "survival"
+        else if builtins.hasAttr "beastMasteryHunter" player then "beast_mastery"
+        else if builtins.hasAttr "marksmanshipHunter" player then "marksmanship"
+        else if builtins.hasAttr "balanceDruid" player then "balance"
+        else if builtins.hasAttr "feralDruid" player then "feral"
+        else if builtins.hasAttr "guardianDruid" player then "guardian"
+        else if builtins.hasAttr "restorationDruid" player then "restoration"
+        else if builtins.hasAttr "holyPaladin" player then "holy"
+        else if builtins.hasAttr "protectionPaladin" player then "protection"
+        else if builtins.hasAttr "retributionPaladin" player then "retribution"
+        else null;
+    in {
+      class = className;
+      spec = specName;
+    }
+    else {
+      class = null;
+      spec = null;
+    };
+
+  # Get all players from all parties
+  getAllPlayers = parties: lib.flatten (builtins.filter (party: party != []) part
ies);
+
+  # Generate dynamic buffs based on raid composition
+  generateDynamicBuffs = players: let
+    classSpecs = builtins.filter (cs: cs.class != null) (map getPlayerClassSpec p
layers);
+    classes = lib.unique (map (cs: cs.class) classSpecs);
+    specs = lib.unique (builtins.filter (spec: spec != null) (map (cs: cs.spec) c
lassSpecs));
+
+    # Count classes for stacking buffs
+    classCount = class: builtins.length (builtins.filter (cs: cs.class == class) 
classSpecs);
+
+    # Check for class/spec presence
+    hasClass = class: builtins.elem class classes;
+    hasSpec = spec: builtins.elem spec specs;
+  in {
+    # Class-based buffs
+    trueshotAura = hasClass "hunter";
+    unholyAura = hasClass "death_knight";
+    darkIntent = hasClass "warlock";
+    moonkinAura = hasSpec "balance"; # Now properly detects balance druids
+    leaderOfThePack = hasClass "druid";
+    blessingOfMight = hasClass "paladin";
+    legacyOfTheEmperor = hasClass "monk";
+
+    # Universal buffs
+    bloodlust = true; # Assume bloodlust/heroism is always available
+
+    # Stacking buffs - count actual providers, capped at reasonable maximums
+    stormlashTotemCount = lib.min 4 (classCount "shaman");
+    skullBannerCount = lib.min 2 (classCount "warrior");
+
+    # Spec-specific buffs
+    legacyOfTheWhiteTiger = hasSpec "windwalker";
+  };
+
+  # Generate dynamic debuffs based on raid composition
+  generateDynamicDebuffs = players: let
+    classSpecs = builtins.filter (cs: cs.class != null) (map getPlayerClassSpec p
layers);
+    classes = lib.unique (map (cs: cs.class) classSpecs);
+
+    hasClass = class: builtins.elem class classes;
+  in {
+    curseOfElements = hasClass "warlock";
+    masterPoisoner = hasClass "rogue";
+    physicalVulnerability = hasClass "warrior" || hasClass "death_knight";
+    weakenedArmor = hasClass "warrior" || hasClass "rogue" || hasClass "druid";
+  };
+
   mkRaid = {
     party1 ? [],
     party2 ? [],
     party3 ? [],
     party4 ? [],
     party5 ? [],
-    buffs,
-    debuffs,
+    buffs ? null,
+    debuffs ? null,
+    dynamicBuffs ? false,
     targetDummies ? 1,
   }: let
     allParties = [party1 party2 party3 party4 party5];
+    allPlayers = getAllPlayers allParties;
+
     # Count non-empty parties
     # numActiveParties = builtins.length (builtins.filter (party: party != []) al
lParties);
     numActiveParties = 5;
+
+    # Use dynamic buffs/debuffs if requested, otherwise use provided ones
+    finalBuffs =
+      if dynamicBuffs
+      then generateDynamicBuffs allPlayers
+      else buffs;
+    finalDebuffs =
+      if dynamicBuffs
+      then generateDynamicDebuffs allPlayers
+      else debuffs;
   in {
-    inherit buffs debuffs targetDummies numActiveParties;
+    buffs = finalBuffs;
+    debuffs = finalDebuffs;
+    inherit targetDummies numActiveParties;
     parties = [
       (mkParty party1)
       (mkParty party2)

diff --git a/nix/lib/raid.nix b/nix/lib/raid
.nix
index 90e4c20..dc6378f 100644
--- a/nix/lib/raid.nix
+++ b/nix/lib/raid.nix
@@ -1,22 +1,126 @@
 {lib, ...}: let
   inherit (lib.sim.party) mkParty;
-  # TODO, dynamically set buffs/debuffs bas
ed on specs present
+
+  # Extract class/spec from player
+  getPlayerClassSpec = player:
+    if builtins.isAttrs player && builtins.
hasAttr "class" player
+    then let
+      # Extract class name from the enum-st
yle class field
+      # e.g., "ClassShaman" -> "shaman"
+      classStr = player.class;
+      className =
+        if builtins.isString classStr && li
b.hasPrefix "Class" classStr
+        then lib.toLower (lib.removePrefix 
"Class" classStr)
+        else null;
+      
+      # Extract spec from spec-specific fie
lds like "elementalShaman", "brewmasterMonk"
, etc.
+      specName = 
+        if builtins.hasAttr "elementalShama
n" player then "elemental"
+        else if builtins.hasAttr "enhanceme
ntShaman" player then "enhancement"
+        else if builtins.hasAttr "restorati
onShaman" player then "restoration"
+        else if builtins.hasAttr "brewmaste
rMonk" player then "brewmaster"
+        else if builtins.hasAttr "windwalke
rMonk" player then "windwalker"
+        else if builtins.hasAttr "mistweave
rMonk" player then "mistweaver"
+        else if builtins.hasAttr "survivalH
unter" player then "survival"
+        else if builtins.hasAttr "beastMast
eryHunter" player then "beast_mastery"
+        else if builtins.hasAttr "marksmans
hipHunter" player then "marksmanship"
+        else if builtins.hasAttr "balanceDr
uid" player then "balance"
+        else if builtins.hasAttr "feralDrui
d" player then "feral"
+        else if builtins.hasAttr "guardianD
ruid" player then "guardian"
+        else if builtins.hasAttr "restorati
onDruid" player then "restoration"
+        else if builtins.hasAttr "holyPalad
in" player then "holy"
+        else if builtins.hasAttr "protectio
nPaladin" player then "protection"
+        else if builtins.hasAttr "retributi
onPaladin" player then "retribution"
+        else null;
+    in {
+      class = className;
+      spec = specName;
+    }
+    else {
+      class = null;
+      spec = null;
+    };
+
+  # Get all players from all parties
+  getAllPlayers = parties: lib.flatten (bui
ltins.filter (party: party != []) parties);
+
+  # Generate dynamic buffs based on raid co
mposition
+  generateDynamicBuffs = players: let
+    classSpecs = builtins.filter (cs: cs.cl
ass != null) (map getPlayerClassSpec players
);
+    classes = lib.unique (map (cs: cs.class
) classSpecs);
+    specs = lib.unique (builtins.filter (sp
ec: spec != null) (map (cs: cs.spec) classSp
ecs));
+
+    # Count classes for stacking buffs
+    classCount = class: builtins.length (bu
iltins.filter (cs: cs.class == class) classS
pecs);
+
+    # Check for class/spec presence
+    hasClass = class: builtins.elem class c
lasses;
+    hasSpec = spec: builtins.elem spec spec
s;
+  in {
+    # Class-based buffs
+    trueshotAura = hasClass "hunter";
+    unholyAura = hasClass "death_knight";
+    darkIntent = hasClass "warlock";
+    moonkinAura = hasSpec "balance"; # Now 
properly detects balance druids
+    leaderOfThePack = hasClass "druid";
+    blessingOfMight = hasClass "paladin";
+    legacyOfTheEmperor = hasClass "monk";
+
+    # Universal buffs
+    bloodlust = true; # Assume bloodlust/he
roism is always available
+
+    # Stacking buffs - count actual provide
rs, capped at reasonable maximums
+    stormlashTotemCount = lib.min 4 (classC
ount "shaman");
+    skullBannerCount = lib.min 2 (classCoun
t "warrior");
+
+    # Spec-specific buffs
+    legacyOfTheWhiteTiger = hasSpec "windwa
lker";
+  };
+
+  # Generate dynamic debuffs based on raid 
composition
+  generateDynamicDebuffs = players: let
+    classSpecs = builtins.filter (cs: cs.cl
ass != null) (map getPlayerClassSpec players
);
+    classes = lib.unique (map (cs: cs.class
) classSpecs);
+
+    hasClass = class: builtins.elem class c
lasses;
+  in {
+    curseOfElements = hasClass "warlock";
+    masterPoisoner = hasClass "rogue";
+    physicalVulnerability = hasClass "warri
or" || hasClass "death_knight";
+    weakenedArmor = hasClass "warrior" || h
asClass "rogue" || hasClass "druid";
+  };
+
   mkRaid = {
     party1 ? [],
     party2 ? [],
     party3 ? [],
     party4 ? [],
     party5 ? [],
-    buffs,
-    debuffs,
+    buffs ? null,
+    debuffs ? null,
+    dynamicBuffs ? false,
     targetDummies ? 1,
   }: let
     allParties = [party1 party2 party3 part
y4 party5];
+    allPlayers = getAllPlayers allParties;
+
     # Count non-empty parties
     # numActiveParties = builtins.length (b
uiltins.filter (party: party != []) allParti
es);
     numActiveParties = 5;
+
+    # Use dynamic buffs/debuffs if requeste
d, otherwise use provided ones
+    finalBuffs =
+      if dynamicBuffs
+      then generateDynamicBuffs allPlayers
+      else buffs;
+    finalDebuffs =
+      if dynamicBuffs
+      then generateDynamicDebuffs allPlayer
s
+      else debuffs;
   in {
-    inherit buffs debuffs targetDummies num
ActiveParties;
+    buffs = finalBuffs;
+    debuffs = finalDebuffs;
+    inherit targetDummies numActiveParties;
     parties = [
       (mkParty party1)
       (mkParty party2)
 

diff --git a/nix/lib/simulation.nix b/nix/lib/simulation.nix
index 66c3d86..582f3d9 100644
--- a/nix/lib/simulation.nix
+++ b/nix/lib/simulation.nix
@@ -11,7 +11,6 @@
     buffs ? null,
     debuffs ? null,
   }:
-  # Validation assertions
     assert (player != null)
     != (raid != null)
     || throw "Must provide exactly one of 'player' or 'raid', not both";

diff --git a/nix/lib/simulation.nix b/nix/li
b/simulation.nix
index 66c3d86..582f3d9 100644
--- a/nix/lib/simulation.nix
+++ b/nix/lib/simulation.nix
@@ -11,7 +11,6 @@
     buffs ? null,
     debuffs ? null,
   }:
-  # Validation assertions
     assert (player != null)
     != (raid != null)
     || throw "Must provide exactly one of '
player' or 'raid', not both";
 

diff --git a/nix/lib/target.nix b/nix/lib/target.nix
index 7343e63..613d3de 100644
--- a/nix/lib/target.nix
+++ b/nix/lib/target.nix
@@ -1,39 +1,84 @@
 {lib, ...}: let
+  mobTypeMap = {
+    unknown = "MobTypeUnknown";
+    beast = "MobTypeBeast";
+    demon = "MobTypeDemon";
+    dragonkin = "MobTypeDragonkin";
+    elemental = "MobTypeElemental";
+    giant = "MobTypeGiant";
+    humanoid = "MobTypeHumanoid";
+    mechanical = "MobTypeMechanical";
+    undead = "MobTypeUndead";
+  };
   mkTarget = {
     id ? 31146,
     name ? "Raid Target",
     level ? 93,
-    type ? "mechanical",
+    mobType ? "mechanical",
     minBaseDamage ? 550000,
     damageSpread ? 0.4,
     swingSpeed ? 2,
-  }: {
-    inherit id name level damageSpread swingSpeed minBaseDamage;
-    mobType = "MobType${lib.strings.toSentenceCase type}";
-    # default stats
-    stats = [
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      650
-      0
-      0
-      0
-      0
-      24835
-      0
-      120016403
-      0
-      0
-    ];
-  };
+    tankIndex ? 0,
+    suppressDodge ? false,
+    parryHaste ? false,
+    dualWield ? false,
+    dualWieldPenalty ? false,
+    spellSchool ? "SpellSchoolPhysical",
+    attackPower ? 650,
+    armor ? 24835,
+    health ? 120016403,
+    targetInputs ? [],
+  }: let
+    base = {
+      inherit id name level minBaseDamage damageSpread swingSpeed spellSchool;
+      mobType = mobTypeMap.${mobType} or (throw "Invalid mobType: ${mobType}");
+      stats = [
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0 # Positions 0-11
+        attackPower # StatAttackPower (12)
+        0
+        0
+        0
+        0 # Positions 13-16
+        armor # StatArmor (17)
+        0 # StatBonusArmor (18)
+        health # StatHealth (19)
+        0
+        0 # StatMana, StatMP5 (20-21)
+      ];
+    };
+
+    # Filter out default values
+    optionalFields =
+      lib.filterAttrs (
+        _: v:
+          if builtins.isBool v
+          then v != false
+          else if builtins.isList v
+          then v != []
+          else if builtins.isInt v
+          then v != 0
+          else true
+      ) {
+        inherit
+          suppressDodge
+          parryHaste
+          dualWield
+          dualWieldPenalty
+          targetInputs
+          tankIndex
+          ;
+      };
+  in
+    base // optionalFields;
 in {inherit mkTarget;}

diff --git a/nix/lib/target.nix b/nix/lib/ta
rget.nix
index 7343e63..613d3de 100644
--- a/nix/lib/target.nix
+++ b/nix/lib/target.nix
@@ -1,39 +1,84 @@
 {lib, ...}: let
+  mobTypeMap = {
+    unknown = "MobTypeUnknown";
+    beast = "MobTypeBeast";
+    demon = "MobTypeDemon";
+    dragonkin = "MobTypeDragonkin";
+    elemental = "MobTypeElemental";
+    giant = "MobTypeGiant";
+    humanoid = "MobTypeHumanoid";
+    mechanical = "MobTypeMechanical";
+    undead = "MobTypeUndead";
+  };
   mkTarget = {
     id ? 31146,
     name ? "Raid Target",
     level ? 93,
-    type ? "mechanical",
+    mobType ? "mechanical",
     minBaseDamage ? 550000,
     damageSpread ? 0.4,
     swingSpeed ? 2,
-  }: {
-    inherit id name level damageSpread swin
gSpeed minBaseDamage;
-    mobType = "MobType${lib.strings.toSente
nceCase type}";
-    # default stats
-    stats = [
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      0
-      650
-      0
-      0
-      0
-      0
-      24835
-      0
-      120016403
-      0
-      0
-    ];
-  };
+    tankIndex ? 0,
+    suppressDodge ? false,
+    parryHaste ? false,
+    dualWield ? false,
+    dualWieldPenalty ? false,
+    spellSchool ? "SpellSchoolPhysical",
+    attackPower ? 650,
+    armor ? 24835,
+    health ? 120016403,
+    targetInputs ? [],
+  }: let
+    base = {
+      inherit id name level minBaseDamage d
amageSpread swingSpeed spellSchool;
+      mobType = mobTypeMap.${mobType} or (t
hrow "Invalid mobType: ${mobType}");
+      stats = [
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0
+        0 # Positions 0-11
+        attackPower # StatAttackPower (12)
+        0
+        0
+        0
+        0 # Positions 13-16
+        armor # StatArmor (17)
+        0 # StatBonusArmor (18)
+        health # StatHealth (19)
+        0
+        0 # StatMana, StatMP5 (20-21)
+      ];
+    };
+
+    # Filter out default values
+    optionalFields =
+      lib.filterAttrs (
+        _: v:
+          if builtins.isBool v
+          then v != false
+          else if builtins.isList v
+          then v != []
+          else if builtins.isInt v
+          then v != 0
+          else true
+      ) {
+        inherit
+          suppressDodge
+          parryHaste
+          dualWield
+          dualWieldPenalty
+          targetInputs
+          tankIndex
+          ;
+      };
+  in
+    base // optionalFields;
 in {inherit mkTarget;}
 

diff --git a/nix/pkgs/default.nix b/nix/pkgs/default.nix
index e15ec36..c6d9584 100644
--- a/nix/pkgs/default.nix
+++ b/nix/pkgs/default.nix
@@ -29,6 +29,7 @@
         testRaid = callPackage ./testRaid.nix {
           inherit lib classes encounter buffs debuffs wowsimcli;
         };
+        testGroupSim = simulation.generateTestGroupSim pkgs;
       };
   };
 }

diff --git a/nix/pkgs/default.nix b/nix/pkgs
/default.nix
index e15ec36..c6d9584 100644
--- a/nix/pkgs/default.nix
+++ b/nix/pkgs/default.nix
@@ -29,6 +29,7 @@
         testRaid = callPackage ./testRaid.n
ix {
           inherit lib classes encounter buf
fs debuffs wowsimcli;
         };
+        testGroupSim = simulation.generateT
estGroupSim pkgs;
       };
   };
 }
 

diff --git a/nix/simulation/default.nix b/nix/simulation/default.nix
index dce2d4f..865e0ed 100644
--- a/nix/simulation/default.nix
+++ b/nix/simulation/default.nix
@@ -86,6 +86,11 @@
       )
       config.trinketComparisonSpecs));
 
+  # Function to generate test group simulation with pkgs
+  generateTestGroupSim = pkgs: let
+    testGroupSim = import ./mkTestGroupSim.nix {inherit lib pkgs classes encounte
r buffs debuffs inputs;};
+  in testGroupSim.mkTestGroupScript;
+
   # Function to generate all simulations script with pkgs
   generateAllSimulationsScript = pkgs: let
     massSimulations = generateMassSimulations pkgs;
@@ -138,7 +143,7 @@
 
   simulation = {
     # Generation functions that take pkgs as parameter
-    inherit generateMassSimulations generateRaceComparisons generateTrinketCompar
isons generateAllSimulationsScript;
+    inherit generateMassSimulations generateRaceComparisons generateTrinketCompar
isons generateAllSimulationsScript generateTestGroupSim;
     
     # Configuration and utilities
     inherit config generateScenarios scenarios;

diff --git a/nix/simulation/default.nix b/ni
x/simulation/default.nix
index dce2d4f..865e0ed 100644
--- a/nix/simulation/default.nix
+++ b/nix/simulation/default.nix
@@ -86,6 +86,11 @@
       )
       config.trinketComparisonSpecs));
 
+  # Function to generate test group simulat
ion with pkgs
+  generateTestGroupSim = pkgs: let
+    testGroupSim = import ./mkTestGroupSim.
nix {inherit lib pkgs classes encounter buff
s debuffs inputs;};
+  in testGroupSim.mkTestGroupScript;
+
   # Function to generate all simulations sc
ript with pkgs
   generateAllSimulationsScript = pkgs: let
     massSimulations = generateMassSimulatio
ns pkgs;
@@ -138,7 +143,7 @@
 
   simulation = {
     # Generation functions that take pkgs a
s parameter
-    inherit generateMassSimulations generat
eRaceComparisons generateTrinketComparisons 
generateAllSimulationsScript;
+    inherit generateMassSimulations generat
eRaceComparisons generateTrinketComparisons 
generateAllSimulationsScript generateTestGro
upSim;
     
     # Configuration and utilities
     inherit config generateScenarios scenar
ios;
 

diff --git a/nix/simulation/mkTestGroupSim.nix b/nix/simulation/mkTestGroupSim.nix
new file mode 100644
index 0000000..b3d3374
--- /dev/null
+++ b/nix/simulation/mkTestGroupSim.nix
@@ -0,0 +1,71 @@
+{
+  lib,
+  pkgs,
+  classes,
+  encounter,
+  buffs,
+  debuffs,
+  inputs,
+  ...
+}: let
+  inherit (lib.sim.simulation) mkSim;
+  inherit (lib.sim.raid) mkRaid;
+  inherit (lib.sim.encounter) mkEncounter;
+  inherit (lib.sim.target) mkTarget;
+  inherit (classes) warlock monk shaman warrior hunter mage paladin;
+
+  testChallengeModeGroup = mkSim {
+    type = "SimTypeRaid";
+    iterations = 500;
+    encounter = mkEncounter {
+      targets = [
+        (mkTarget {
+          level = 93;
+          mobType = "elemental";
+          health = 19600000;
+          minBaseDamage = 150000;
+        })
+      ];
+      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
+      ];
+    };
+  };
+
+  mkTestGroupScript = pkgs.writeShellApplication {
+    name = "test-group-sim";
+    text = ''
+      echo "Generating test group simulation JSON..."
+      cat > test-group-sim.json << 'EOF'
+        ${testChallengeModeGroup}
+      EOF
+      echo "Running simulation with wowsimcli..."
+      wowsimcli sim --infile test-group-sim.json --outfile test-out.json
+
+      echo "=== Simulation Results ==="
+      echo "Avg Fight Duration: $(jq -r '.avgIterationDuration' test-out.json)s"
+      echo "Iterations: $(jq -r '.iterationsDone' test-out.json)"
+      echo ""
+      echo "=== Group Performance ==="
+      echo "Total Raid DPS: $(jq -r '.raidMetrics.dps.avg | floor' test-out.json)
"
+      echo "DPS Range: $(jq -r '.raidMetrics.dps.min | floor' test-out.json) - $(
jq -r '.raidMetrics.dps.max | floor' test-out.json)"
+      echo ""
+      echo "=== Individual Players ==="
+      jq -r '.raidMetrics.parties[0].players[] | select(.name and .name != "Targe
t Dummy 1") | "Player: \(.name) - DPS: \(.dps.avg | floor)"' test-out.json
+      echo ""
+      echo "Files created: test-group-sim.json (input), test-out.json (output)"
+    '';
+    runtimeInputs = [pkgs.jq inputs.wowsims.packages.${pkgs.system}.wowsimcli];
+  };
+in {
+  inherit testChallengeModeGroup mkTestGroupScript;
+}

diff --git a/nix/simulation/mkTestGroupSim.n
ix b/nix/simulation/mkTestGroupSim.nix
new file mode 100644
index 0000000..b3d3374
--- /dev/null
+++ b/nix/simulation/mkTestGroupSim.nix
@@ -0,0 +1,71 @@
+{
+  lib,
+  pkgs,
+  classes,
+  encounter,
+  buffs,
+  debuffs,
+  inputs,
+  ...
+}: let
+  inherit (lib.sim.simulation) mkSim;
+  inherit (lib.sim.raid) mkRaid;
+  inherit (lib.sim.encounter) mkEncounter;
+  inherit (lib.sim.target) mkTarget;
+  inherit (classes) warlock monk shaman war
rior hunter mage paladin;
+
+  testChallengeModeGroup = mkSim {
+    type = "SimTypeRaid";
+    iterations = 500;
+    encounter = mkEncounter {
+      targets = [
+        (mkTarget {
+          level = 93;
+          mobType = "elemental";
+          health = 19600000;
+          minBaseDamage = 150000;
+        })
+      ];
+      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
+      ];
+    };
+  };
+
+  mkTestGroupScript = pkgs.writeShellApplic
ation {
+    name = "test-group-sim";
+    text = ''
+      echo "Generating test group simulatio
n JSON..."
+      cat > test-group-sim.json << 'EOF'
+        ${testChallengeModeGroup}
+      EOF
+      echo "Running simulation with wowsimc
li..."
+      wowsimcli sim --infile test-group-sim
.json --outfile test-out.json
+
+      echo "=== Simulation Results ==="
+      echo "Avg Fight Duration: $(jq -r '.a
vgIterationDuration' test-out.json)s"
+      echo "Iterations: $(jq -r '.iteration
sDone' test-out.json)"
+      echo ""
+      echo "=== Group Performance ==="
+      echo "Total Raid DPS: $(jq -r '.raidM
etrics.dps.avg | floor' test-out.json)"
+      echo "DPS Range: $(jq -r '.raidMetric
s.dps.min | floor' test-out.json) - $(jq -r 
'.raidMetrics.dps.max | floor' test-out.json
)"
+      echo ""
+      echo "=== Individual Players ==="
+      jq -r '.raidMetrics.parties[0].player
s[] | select(.name and .name != "Target Dumm
y 1") | "Player: \(.name) - DPS: \(.dps.avg 
| floor)"' test-out.json
+      echo ""
+      echo "Files created: test-group-sim.j
son (input), test-out.json (output)"
+    '';
+    runtimeInputs = [pkgs.jq inputs.wowsims
.packages.${pkgs.system}.wowsimcli];
+  };
+in {
+  inherit testChallengeModeGroup mkTestGrou
pScript;
+}
 

diff --git a/nix/target/default.nix b/nix/target/default.nix
index b8d1d35..fc3f40d 100644
--- a/nix/target/default.nix
+++ b/nix/target/default.nix
@@ -19,6 +19,12 @@
       level = 90;
       minBaseDamage = 40000;
     };
+    shaOfDoubt = {
+      level = 93;
+      mobType = "elemental";
+      health = 19600000;
+      minBaseDamage = 150000;
+    };
   };
 in {
   flake.target = target;

diff --git a/nix/target/default.nix b/nix/ta
rget/default.nix
index b8d1d35..fc3f40d 100644
--- a/nix/target/default.nix
+++ b/nix/target/default.nix
@@ -19,6 +19,12 @@
       level = 90;
       minBaseDamage = 40000;
     };
+    shaOfDoubt = {
+      level = 93;
+      mobType = "elemental";
+      health = 19600000;
+      minBaseDamage = 150000;
+    };
   };
 in {
   flake.target = target;
 
[ BACK TO LOG ]
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET