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
┌─ nix/apps/default.nix ─────────────────────────────────────────────────────┐
│ 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"; │
│ + }; │
│ }; │
│ }; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/apps/default.nix ───────────────┐
│ 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"; │
│ + }; │
│ }; │
│ }; │
│ } │
└──────────────────────────────────────────────┘
┌─ nix/encounter/default.nix ────────────────────────────────────────────────┐
│ 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; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/encounter/default.nix ──────────┐
│ 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; │
└──────────────────────────────────────────────┘
┌─ nix/lib/classes.nix ──────────────────────────────────────────────────────┐
│ 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; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/classes.nix ────────────────┐
│ 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; │
└──────────────────────────────────────────────┘
┌─ nix/lib/encounter.nix ────────────────────────────────────────────────────┐
│ 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; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/encounter.nix ──────────────┐
│ 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; │
└──────────────────────────────────────────────┘
┌─ nix/lib/player.nix ───────────────────────────────────────────────────────┐
│ 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, │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/player.nix ─────────────────┐
│ 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, │
└──────────────────────────────────────────────┘
┌─ nix/lib/raid.nix ─────────────────────────────────────────────────────────┐
│ 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) │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/raid.nix ───────────────────┐
│ 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) │
└──────────────────────────────────────────────┘
┌─ nix/lib/simulation.nix ───────────────────────────────────────────────────┐
│ 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"; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/simulation.nix ─────────────┐
│ 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"; │
└──────────────────────────────────────────────┘
┌─ nix/lib/target.nix ───────────────────────────────────────────────────────┐
│ 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;} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/lib/target.nix ─────────────────┐
│ 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;} │
└──────────────────────────────────────────────┘
┌─ nix/pkgs/default.nix ─────────────────────────────────────────────────────┐
│ 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; │
│ }; │
│ }; │
│ } │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/pkgs/default.nix ───────────────┐
│ 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; │
│ }; │
│ }; │
│ } │
└──────────────────────────────────────────────┘
┌─ nix/simulation/default.nix ───────────────────────────────────────────────┐
│ 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; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/simulation/default.nix ─────────┐
│ 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; │
└──────────────────────────────────────────────┘
┌─ nix/simulation/mkTestGroupSim.nix ────────────────────────────────────────┐
│ 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; │
│ +} │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ ...simulation/mkTestGroupSim.nix ───┐
│ 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; │
│ +} │
└──────────────────────────────────────────────┘
┌─ nix/target/default.nix ───────────────────────────────────────────────────┐
│ 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; │
└────────────────────────────────────────────────────────────────────────────────────┘
┌─ nix/target/default.nix ─────────────┐
│ 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