OOKNET                             [ /  search the index  ]  
──────────────────────────────────────────────────────────────────────────────────────
══════════════════════════════════════════════════════════════════════════════════════
OOKNET   [ /  search  ]  
────────────────────────────────────────────────
════════════════════════════════════════════════
 
 
master @ 448 LINES
 
[ HISTORY ]  [ UP ]
 

{
  lib,
  pkgs,
  classes,
  buffs,
  debuffs,
  inputs,
  trinket,
  ...
}: let
  inherit (lib.sim.simulation) mkSim;
  inherit (lib.sim) itemDatabase shellUtils;
  inherit (lib) hasAttr length listToAttrs imap0 replaceStrings concatMapStringsSe
p;

  # Import our trinket manipulation functions
  trinketLib = lib.sim.trinket;

  mkTrinketComparison = {
    class,
    spec,
    encounter,
    trinketCategory ? "meleeAgility", # Default to melee agility trinkets
    trinketIds ? null, # Optional: provide trinket IDs directly
    iterations ? 10000,
    phase ? "p1",
    encounterType ? "raid",
    targetCount ? "single",
    duration ? "long",
    template ? "singleTarget",
    wowsimsCommit ? inputs.wowsims-upstream.shortRev,
  }: let
    # Get the base spec configuration
    baseSpec = classes.${class}.${spec};
    defaultRace = baseSpec.defaultRace;

    # Get the base player configuration
    baseConfig =
      if
        hasAttr "template" baseSpec
        && hasAttr defaultRace baseSpec.template
        && hasAttr phase baseSpec.template.${defaultRace}
        && hasAttr encounterType baseSpec.template.${defaultRace}.${phase}
        && hasAttr template baseSpec.template.${defaultRace}.${phase}.${encounterT
ype}
      then baseSpec.template.${defaultRace}.${phase}.${encounterType}.${template}
      else throw "Template ${template} not found for ${class}/${spec}/${defaultRac
e} at ${phase}.${encounterType}";

    # Load trinket list - either from direct input or from trinket presets
    actualTrinketIds =
      if trinketIds != null
      then trinketIds
      else trinket.presets.p1.${trinketCategory};

    # Create baseline gearset (no trinkets)
    baselineGearset = trinketLib.removeTrinkets baseConfig.equipment;

    # Create baseline player config
    baselineConfig = baseConfig // {equipment = baselineGearset;};

    # Trinket profession requirements mapping
    trinketProfessionRequirements = {
      "75274" = "Alchemy"; # Zen Alchemist Stone
      # Add other profession-specific trinkets here as needed
    };

    # Generate trinket gearsets
    trinketGearsets = trinketLib.generateTrinketGearsets baselineGearset actualTri
nketIds;

    # Create trinket configs with gearsets and profession adjustments
    trinketConfigs =
      imap0 (index: gearset: let
        trinketId = builtins.elemAt actualTrinketIds index;
        trinketIdStr = toString trinketId;
        requiredProfession = trinketProfessionRequirements.${trinketIdStr} or null
;

        # Modify profession if trinket requires it
        configWithProfession =
          if requiredProfession != null
          then let
            # Determine which profession is being replaced
            droppedProfession =
              if baseConfig.profession2 != requiredProfession
              then baseConfig.profession2
              else if baseConfig.profession1 != requiredProfession
              then baseConfig.profession1
              else null;

            # Remove enchants from the dropped profession
            cleanedGearset =
              if droppedProfession != null
              then trinketLib.removeProfessionEnchants gearset droppedProfession
              else gearset;
          in
            baseConfig
            // {
              equipment = cleanedGearset;
              profession2 = requiredProfession;
            }
          else baseConfig // {equipment = gearset;};
      in {
        trinketId = trinketId;
        config = configWithProfession;
      })
      trinketGearsets;

    # Class-specific buff adjustments (copied from mkMassSim)
    classSpecificBuffs =
      if baseConfig.class == "ClassWarrior"
      then buffs.full // {skullBannerCount = 1;}
      else if baseConfig.class == "ClassShaman"
      then buffs.full // {stormlashTotemCount = 3;}
      else buffs.full;

    # Create baseline simulation
    baselineSim = let
      enrichedEquipment = itemDatabase.enrichEquipment baselineConfig.equipment;
      enrichedConsumables = itemDatabase.enrichConsumables baselineConfig.consumab
les;
      enrichedGlyphs = itemDatabase.enrichGlyphs baselineConfig.class baselineConf
ig.glyphs;
      enrichedTalents = itemDatabase.enrichTalents baselineConfig.class baselineCo
nfig.talentsString;

      simInput = mkSim {
        inherit iterations;
        player = baselineConfig;
        buffs = classSpecificBuffs;
        debuffs = debuffs.full;
        inherit encounter;
      };
    in
      pkgs.runCommand "trinket-baseline-${class}-${spec}" {
        buildInputs = [pkgs.jq];
        nativeBuildInputs = [inputs.wowsims.packages.${pkgs.system}.wowsimcli];
      } ''
        # Write enriched JSON data to files
        cat > enriched_equipment.json << 'EQUIPMENT_EOF'
        ${builtins.toJSON enrichedEquipment}
        EQUIPMENT_EOF

        cat > consumables.json << 'CONSUMABLES_EOF'
        ${builtins.toJSON enrichedConsumables}
        CONSUMABLES_EOF

        cat > glyphs.json << 'GLYPHS_EOF'
        ${builtins.toJSON enrichedGlyphs}
        GLYPHS_EOF

        cat > talents.json << 'TALENTS_EOF'
        ${builtins.toJSON enrichedTalents}
        TALENTS_EOF

        cat > input.json << 'INPUT_EOF'
        ${simInput}
        INPUT_EOF

        echo "Running baseline (no trinkets) simulation for ${class}/${spec}..."
        if wowsimcli sim --infile input.json --outfile output.json; then
          # Extract DPS statistics
          avgDps=$(jq -r '.raidMetrics.dps.avg // 0' output.json)
          maxDps=$(jq -r '.raidMetrics.dps.max // 0' output.json)
          minDps=$(jq -r '.raidMetrics.dps.min // 0' output.json)
          stdevDps=$(jq -r '.raidMetrics.dps.stdev // 0' output.json)

          # Generate wowsim link from input file
          echo "Generating wowsim link..."
          simLink=$(wowsimcli encodelink input.json || echo "")

          # Create final result
          jq -n \
            --arg avgDps "$avgDps" \
            --arg maxDps "$maxDps" \
            --arg minDps "$minDps" \
            --arg stdevDps "$stdevDps" \
            --arg simLink "$simLink" \
            --slurpfile equipment enriched_equipment.json \
            --slurpfile consumables consumables.json \
            --arg talentsString "${baselineConfig.talentsString}" \
            --slurpfile talents talents.json \
            --slurpfile glyphs glyphs.json \
            --arg race "${baselineConfig.race}" \
            --arg class "${baselineConfig.class}" \
            --arg profession1 "${baselineConfig.profession1}" \
            --arg profession2 "${baselineConfig.profession2}" \
            '{
              dps: ($avgDps | tonumber),
              max: ($maxDps | tonumber),
              min: ($minDps | tonumber),
              stdev: ($stdevDps | tonumber),
              loadout: {
                consumables: $consumables[0],
                talentsString: $talentsString,
                talents: $talents[0],
                glyphs: $glyphs[0],
                equipment: $equipment[0],
                race: $race,
                class: $class,
                profession1: $profession1,
                profession2: $profession2,
                simLink: $simLink
              }
            }' > $out
        else
          echo "Baseline simulation failed for ${class}/${spec}"
          exit 1
        fi
      '';

    # Create individual trinket simulations
    trinketSims = listToAttrs (map (trinketConfig: let
        trinketItem = itemDatabase.getItem trinketConfig.trinketId;
        trinketName =
          if trinketItem != null
          then trinketItem.name
          else "Unknown_${toString trinketConfig.trinketId}";
        trinketIlvl =
          if trinketItem != null && trinketItem ? scalingOptions && trinketItem.sc
alingOptions ? "0"
          then toString trinketItem.scalingOptions."0".ilvl
          else "0";
        # Clean name for use as attribute key
        cleanName = replaceStrings [" " "'" "," "(" ")"] ["_" "" "" "" ""] trinket
Name;
      in {
        name = "${cleanName}_${trinketIlvl}";
        value = let
          enrichedEquipment = itemDatabase.enrichEquipment trinketConfig.config.eq
uipment;
          enrichedConsumables = itemDatabase.enrichConsumables trinketConfig.confi
g.consumables;
          enrichedGlyphs = itemDatabase.enrichGlyphs trinketConfig.config.class tr
inketConfig.config.glyphs;
          enrichedTalents = itemDatabase.enrichTalents trinketConfig.config.class 
trinketConfig.config.talentsString;

          simInput = mkSim {
            inherit iterations;
            player = trinketConfig.config;
            buffs = classSpecificBuffs;
            debuffs = debuffs.full;
            inherit encounter;
          };
        in
          pkgs.runCommand "trinket-sim-${class}-${spec}-${toString trinketConfig.t
rinketId}" {
            buildInputs = [pkgs.jq];
            nativeBuildInputs = [inputs.wowsims.packages.${pkgs.system}.wowsimcli]
;
          } ''
            # Write enriched JSON data to files
            cat > enriched_equipment.json << 'EQUIPMENT_EOF'
            ${builtins.toJSON enrichedEquipment}
            EQUIPMENT_EOF

            cat > consumables.json << 'CONSUMABLES_EOF'
            ${builtins.toJSON enrichedConsumables}
            CONSUMABLES_EOF

            cat > glyphs.json << 'GLYPHS_EOF'
            ${builtins.toJSON enrichedGlyphs}
            GLYPHS_EOF

            cat > talents.json << 'TALENTS_EOF'
            ${builtins.toJSON enrichedTalents}
            TALENTS_EOF

            cat > input.json << 'INPUT_EOF'
            ${simInput}
            INPUT_EOF

            echo "Running trinket simulation for ${class}/${spec} with trinket ${t
oString trinketConfig.trinketId}..."
            if wowsimcli sim --infile input.json --outfile output.json; then
              # Extract DPS statistics
              avgDps=$(jq -r '.raidMetrics.dps.avg // 0' output.json)
              maxDps=$(jq -r '.raidMetrics.dps.max // 0' output.json)
              minDps=$(jq -r '.raidMetrics.dps.min // 0' output.json)
              stdevDps=$(jq -r '.raidMetrics.dps.stdev // 0' output.json)

              # Generate wowsim link from input file
              echo "Generating wowsim link..."
              simLink=$(wowsimcli encodelink input.json || echo "")

              # Get trinket metadata
              trinketName="${trinketName}"
              trinketId="${toString trinketConfig.trinketId}"

              # Create final result with trinket metadata
              jq -n \
                --arg avgDps "$avgDps" \
                --arg maxDps "$maxDps" \
                --arg minDps "$minDps" \
                --arg stdevDps "$stdevDps" \
                --arg simLink "$simLink" \
                --arg trinketName "$trinketName" \
                --arg trinketId "$trinketId" \
                --slurpfile equipment enriched_equipment.json \
                --slurpfile consumables consumables.json \
                --arg talentsString "${trinketConfig.config.talentsString}" \
                --slurpfile talents talents.json \
                --slurpfile glyphs glyphs.json \
                --arg race "${trinketConfig.config.race}" \
                --arg class "${trinketConfig.config.class}" \
                --arg profession1 "${trinketConfig.config.profession1}" \
                --arg profession2 "${trinketConfig.config.profession2}" \
                '{
                  dps: ($avgDps | tonumber),
                  max: ($maxDps | tonumber),
                  min: ($minDps | tonumber),
                  stdev: ($stdevDps | tonumber),
                  trinket: {
                    id: ($trinketId | tonumber),
                    name: $trinketName
                  },
                  loadout: {
                    consumables: $consumables[0],
                    talentsString: $talentsString,
                    talents: $talents[0],
                    glyphs: $glyphs[0],
                    equipment: $equipment[0],
                    race: $race,
                    class: $class,
                    profession1: $profession1,
                    profession2: $profession2,
                    simLink: $simLink
                  }
                }' > $out
            else
              echo "Trinket simulation failed for ${class}/${spec} with trinket ${
toString trinketConfig.trinketId}"
              exit 1
            fi
          '';
      })
      trinketConfigs);

    # Generate structured output filename
    structuredOutput = "${class}_${spec}_trinket_${phase}_${encounterType}_${targe
tCount}_${duration}";

    # Aggregation script that combines baseline + all trinket results
    aggregationScript = pkgs.writeShellApplication {
      name = "${structuredOutput}-aggregator";
      text = ''
        ${shellUtils.parseArgsAndEnv}

        echo "Aggregating trinket comparison results for: ${class}/${spec}"
        echo "Trinkets tested: ${toString (builtins.length actualTrinketIds)}"

        # Start with baseline result
        result=$(jq -n '{}')

        # Add baseline
        baselineData=$(cat ${baselineSim})
        result=$(echo "$result" | jq \
          --argjson baseline "$baselineData" \
          '.baseline = $baseline')

        # Add each trinket result
        ${concatMapStringsSep "\n" (trinketConfig: let
            trinketItem = itemDatabase.getItem trinketConfig.trinketId;
            trinketName =
              if trinketItem != null
              then trinketItem.name
              else "Unknown_${toString trinketConfig.trinketId}";
            trinketIlvl =
              if trinketItem != null && trinketItem ? scalingOptions && trinketIte
m.scalingOptions ? "0"
              then toString trinketItem.scalingOptions."0".ilvl
              else "0";
            cleanName = replaceStrings [" " "'" "," "(" ")"] ["_" "" "" "" ""] tri
nketName;
            keyName = "${cleanName}_${trinketIlvl}";
          in ''
            trinketData=$(cat ${trinketSims.${keyName}})
            result=$(echo "$result" | jq \
              --argjson trinket "$trinketData" \
              --arg keyName "${keyName}" \
              '.[$keyName] = $trinket')
          '')
          trinketConfigs}

        # Create final output with metadata
        finalResult=$(echo "$result" | jq \
          --arg class "${class}" \
          --arg spec "${spec}" \
          --arg comparison "trinket" \
          --arg baseline "no_trinkets" \
          --arg encounter "${phase}_${encounterType}_${targetCount}_${duration}" \
          --arg timestamp "$(date -Iseconds)" \
          --arg iterations "${toString iterations}" \
          --arg encounterDuration "${toString encounter.duration}" \
          --arg encounterVariation "${toString encounter.durationVariation}" \
          --arg targetCount "${toString (length encounter.targets)}" \
          --arg wowsimsCommit "${wowsimsCommit}" \
          --arg trinketCategory "${trinketCategory}" \
          --argjson raidBuffs '${builtins.toJSON classSpecificBuffs}' \
          '{
            metadata: {
              spec: $spec,
              class: $class,
              comparison: $comparison,
              baseline: $baseline,
              encounter: $encounter,
              timestamp: $timestamp,
              iterations: ($iterations | tonumber),
              encounterDuration: ($encounterDuration | tonumber),
              encounterVariation: ($encounterVariation | tonumber),
              targetCount: ($targetCount | tonumber),
              wowsimsCommit: $wowsimsCommit,
              trinketCategory: $trinketCategory,
              raidBuffs: $raidBuffs
            },
            results: .
          }')

        ${shellUtils.conditionalOutput {
          inherit structuredOutput;
          webSetupCode = shellUtils.setupComparisonDirs {
            comparisonType = "trinkets";
            class = "${class}";
            spec = "${spec}";
          };
          webPath = "$comparison_dir";
          webMessage = "Copied to";
        }}

        echo ""
        echo "Trinket DPS Rankings for ${class}/${spec}:"
        echo "======================================="

        # Show baseline DPS
        echo "Baseline (no trinkets): $(echo "$finalResult" | jq -r '.results.base
line.dps | floor') DPS"
        echo ""

        # Show trinket rankings sorted by DPS
        echo "$finalResult" | jq -r '
          .results | to_entries[] |
          select(.key != "baseline" and .value.dps != null and .value.dps > 0) |
          "\(.value.trinket.name): \(.value.dps | floor) DPS"
        ' | sort -k2 -nr

      '';
      runtimeInputs = [pkgs.jq pkgs.coreutils];
    };
  in {
    # Individual simulation derivations
    simulations = trinketSims // {baseline = baselineSim;};

    # Main aggregation script
    script = aggregationScript;

    # Metadata
    metadata = {
      inherit class spec trinketCategory;
      output = structuredOutput;
      inherit iterations phase encounterType targetCount duration;
      trinketCount = length actualTrinketIds;
      trinkets = actualTrinketIds;
    };
  };
in {
  inherit mkTrinketComparison;
}

{
  lib,
  pkgs,
  classes,
  buffs,
  debuffs,
  inputs,
  trinket,
  ...
}: let
  inherit (lib.sim.simulation) mkSim;
  inherit (lib.sim) itemDatabase shellUtils;
  inherit (lib) hasAttr length listToAttrs i
map0 replaceStrings concatMapStringsSep;

  # Import our trinket manipulation function
s
  trinketLib = lib.sim.trinket;

  mkTrinketComparison = {
    class,
    spec,
    encounter,
    trinketCategory ? "meleeAgility", # Defa
ult to melee agility trinkets
    trinketIds ? null, # Optional: provide t
rinket IDs directly
    iterations ? 10000,
    phase ? "p1",
    encounterType ? "raid",
    targetCount ? "single",
    duration ? "long",
    template ? "singleTarget",
    wowsimsCommit ? inputs.wowsims-upstream.
shortRev,
  }: let
    # Get the base spec configuration
    baseSpec = classes.${class}.${spec};
    defaultRace = baseSpec.defaultRace;

    # Get the base player configuration
    baseConfig =
      if
        hasAttr "template" baseSpec
        && hasAttr defaultRace baseSpec.temp
late
        && hasAttr phase baseSpec.template.$
{defaultRace}
        && hasAttr encounterType baseSpec.te
mplate.${defaultRace}.${phase}
        && hasAttr template baseSpec.templat
e.${defaultRace}.${phase}.${encounterType}
      then baseSpec.template.${defaultRace}.
${phase}.${encounterType}.${template}
      else throw "Template ${template} not f
ound for ${class}/${spec}/${defaultRace} at 
${phase}.${encounterType}";

    # Load trinket list - either from direct
 input or from trinket presets
    actualTrinketIds =
      if trinketIds != null
      then trinketIds
      else trinket.presets.p1.${trinketCateg
ory};

    # Create baseline gearset (no trinkets)
    baselineGearset = trinketLib.removeTrink
ets baseConfig.equipment;

    # Create baseline player config
    baselineConfig = baseConfig // {equipmen
t = baselineGearset;};

    # Trinket profession requirements mappin
g
    trinketProfessionRequirements = {
      "75274" = "Alchemy"; # Zen Alchemist S
tone
      # Add other profession-specific trinke
ts here as needed
    };

    # Generate trinket gearsets
    trinketGearsets = trinketLib.generateTri
nketGearsets baselineGearset actualTrinketId
s;

    # Create trinket configs with gearsets a
nd profession adjustments
    trinketConfigs =
      imap0 (index: gearset: let
        trinketId = builtins.elemAt actualTr
inketIds index;
        trinketIdStr = toString trinketId;
        requiredProfession = trinketProfessi
onRequirements.${trinketIdStr} or null;

        # Modify profession if trinket requi
res it
        configWithProfession =
          if requiredProfession != null
          then let
            # Determine which profession is 
being replaced
            droppedProfession =
              if baseConfig.profession2 != r
equiredProfession
              then baseConfig.profession2
              else if baseConfig.profession1
 != requiredProfession
              then baseConfig.profession1
              else null;

            # Remove enchants from the dropp
ed profession
            cleanedGearset =
              if droppedProfession != null
              then trinketLib.removeProfessi
onEnchants gearset droppedProfession
              else gearset;
          in
            baseConfig
            // {
              equipment = cleanedGearset;
              profession2 = requiredProfessi
on;
            }
          else baseConfig // {equipment = ge
arset;};
      in {
        trinketId = trinketId;
        config = configWithProfession;
      })
      trinketGearsets;

    # Class-specific buff adjustments (copie
d from mkMassSim)
    classSpecificBuffs =
      if baseConfig.class == "ClassWarrior"
      then buffs.full // {skullBannerCount =
 1;}
      else if baseConfig.class == "ClassSham
an"
      then buffs.full // {stormlashTotemCoun
t = 3;}
      else buffs.full;

    # Create baseline simulation
    baselineSim = let
      enrichedEquipment = itemDatabase.enric
hEquipment baselineConfig.equipment;
      enrichedConsumables = itemDatabase.enr
ichConsumables baselineConfig.consumables;
      enrichedGlyphs = itemDatabase.enrichGl
yphs baselineConfig.class baselineConfig.gly
phs;
      enrichedTalents = itemDatabase.enrichT
alents baselineConfig.class baselineConfig.t
alentsString;

      simInput = mkSim {
        inherit iterations;
        player = baselineConfig;
        buffs = classSpecificBuffs;
        debuffs = debuffs.full;
        inherit encounter;
      };
    in
      pkgs.runCommand "trinket-baseline-${cl
ass}-${spec}" {
        buildInputs = [pkgs.jq];
        nativeBuildInputs = [inputs.wowsims.
packages.${pkgs.system}.wowsimcli];
      } ''
        # Write enriched JSON data to files
        cat > enriched_equipment.json << 'EQ
UIPMENT_EOF'
        ${builtins.toJSON enrichedEquipment}
        EQUIPMENT_EOF

        cat > consumables.json << 'CONSUMABL
ES_EOF'
        ${builtins.toJSON enrichedConsumable
s}
        CONSUMABLES_EOF

        cat > glyphs.json << 'GLYPHS_EOF'
        ${builtins.toJSON enrichedGlyphs}
        GLYPHS_EOF

        cat > talents.json << 'TALENTS_EOF'
        ${builtins.toJSON enrichedTalents}
        TALENTS_EOF

        cat > input.json << 'INPUT_EOF'
        ${simInput}
        INPUT_EOF

        echo "Running baseline (no trinkets)
 simulation for ${class}/${spec}..."
        if wowsimcli sim --infile input.json
 --outfile output.json; then
          # Extract DPS statistics
          avgDps=$(jq -r '.raidMetrics.dps.a
vg // 0' output.json)
          maxDps=$(jq -r '.raidMetrics.dps.m
ax // 0' output.json)
          minDps=$(jq -r '.raidMetrics.dps.m
in // 0' output.json)
          stdevDps=$(jq -r '.raidMetrics.dps
.stdev // 0' output.json)

          # Generate wowsim link from input 
file
          echo "Generating wowsim link..."
          simLink=$(wowsimcli encodelink inp
ut.json || echo "")

          # Create final result
          jq -n \
            --arg avgDps "$avgDps" \
            --arg maxDps "$maxDps" \
            --arg minDps "$minDps" \
            --arg stdevDps "$stdevDps" \
            --arg simLink "$simLink" \
            --slurpfile equipment enriched_e
quipment.json \
            --slurpfile consumables consumab
les.json \
            --arg talentsString "${baselineC
onfig.talentsString}" \
            --slurpfile talents talents.json
 \
            --slurpfile glyphs glyphs.json \
            --arg race "${baselineConfig.rac
e}" \
            --arg class "${baselineConfig.cl
ass}" \
            --arg profession1 "${baselineCon
fig.profession1}" \
            --arg profession2 "${baselineCon
fig.profession2}" \
            '{
              dps: ($avgDps | tonumber),
              max: ($maxDps | tonumber),
              min: ($minDps | tonumber),
              stdev: ($stdevDps | tonumber),
              loadout: {
                consumables: $consumables[0]
,
                talentsString: $talentsStrin
g,
                talents: $talents[0],
                glyphs: $glyphs[0],
                equipment: $equipment[0],
                race: $race,
                class: $class,
                profession1: $profession1,
                profession2: $profession2,
                simLink: $simLink
              }
            }' > $out
        else
          echo "Baseline simulation failed f
or ${class}/${spec}"
          exit 1
        fi
      '';

    # Create individual trinket simulations
    trinketSims = listToAttrs (map (trinketC
onfig: let
        trinketItem = itemDatabase.getItem t
rinketConfig.trinketId;
        trinketName =
          if trinketItem != null
          then trinketItem.name
          else "Unknown_${toString trinketCo
nfig.trinketId}";
        trinketIlvl =
          if trinketItem != null && trinketI
tem ? scalingOptions && trinketItem.scalingO
ptions ? "0"
          then toString trinketItem.scalingO
ptions."0".ilvl
          else "0";
        # Clean name for use as attribute ke
y
        cleanName = replaceStrings [" " "'" 
"," "(" ")"] ["_" "" "" "" ""] trinketName;
      in {
        name = "${cleanName}_${trinketIlvl}"
;
        value = let
          enrichedEquipment = itemDatabase.e
nrichEquipment trinketConfig.config.equipmen
t;
          enrichedConsumables = itemDatabase
.enrichConsumables trinketConfig.config.cons
umables;
          enrichedGlyphs = itemDatabase.enri
chGlyphs trinketConfig.config.class trinketC
onfig.config.glyphs;
          enrichedTalents = itemDatabase.enr
ichTalents trinketConfig.config.class trinke
tConfig.config.talentsString;

          simInput = mkSim {
            inherit iterations;
            player = trinketConfig.config;
            buffs = classSpecificBuffs;
            debuffs = debuffs.full;
            inherit encounter;
          };
        in
          pkgs.runCommand "trinket-sim-${cla
ss}-${spec}-${toString trinketConfig.trinket
Id}" {
            buildInputs = [pkgs.jq];
            nativeBuildInputs = [inputs.wows
ims.packages.${pkgs.system}.wowsimcli];
          } ''
            # Write enriched JSON data to fi
les
            cat > enriched_equipment.json <<
 'EQUIPMENT_EOF'
            ${builtins.toJSON enrichedEquipm
ent}
            EQUIPMENT_EOF

            cat > consumables.json << 'CONSU
MABLES_EOF'
            ${builtins.toJSON enrichedConsum
ables}
            CONSUMABLES_EOF

            cat > glyphs.json << 'GLYPHS_EOF
'
            ${builtins.toJSON enrichedGlyphs
}
            GLYPHS_EOF

            cat > talents.json << 'TALENTS_E
OF'
            ${builtins.toJSON enrichedTalent
s}
            TALENTS_EOF

            cat > input.json << 'INPUT_EOF'
            ${simInput}
            INPUT_EOF

            echo "Running trinket simulation
 for ${class}/${spec} with trinket ${toStrin
g trinketConfig.trinketId}..."
            if wowsimcli sim --infile input.
json --outfile output.json; then
              # Extract DPS statistics
              avgDps=$(jq -r '.raidMetrics.d
ps.avg // 0' output.json)
              maxDps=$(jq -r '.raidMetrics.d
ps.max // 0' output.json)
              minDps=$(jq -r '.raidMetrics.d
ps.min // 0' output.json)
              stdevDps=$(jq -r '.raidMetrics
.dps.stdev // 0' output.json)

              # Generate wowsim link from in
put file
              echo "Generating wowsim link..
."
              simLink=$(wowsimcli encodelink
 input.json || echo "")

              # Get trinket metadata
              trinketName="${trinketName}"
              trinketId="${toString trinketC
onfig.trinketId}"

              # Create final result with tri
nket metadata
              jq -n \
                --arg avgDps "$avgDps" \
                --arg maxDps "$maxDps" \
                --arg minDps "$minDps" \
                --arg stdevDps "$stdevDps" \
                --arg simLink "$simLink" \
                --arg trinketName "$trinketN
ame" \
                --arg trinketId "$trinketId"
 \
                --slurpfile equipment enrich
ed_equipment.json \
                --slurpfile consumables cons
umables.json \
                --arg talentsString "${trink
etConfig.config.talentsString}" \
                --slurpfile talents talents.
json \
                --slurpfile glyphs glyphs.js
on \
                --arg race "${trinketConfig.
config.race}" \
                --arg class "${trinketConfig
.config.class}" \
                --arg profession1 "${trinket
Config.config.profession1}" \
                --arg profession2 "${trinket
Config.config.profession2}" \
                '{
                  dps: ($avgDps | tonumber),
                  max: ($maxDps | tonumber),
                  min: ($minDps | tonumber),
                  stdev: ($stdevDps | tonumb
er),
                  trinket: {
                    id: ($trinketId | tonumb
er),
                    name: $trinketName
                  },
                  loadout: {
                    consumables: $consumable
s[0],
                    talentsString: $talentsS
tring,
                    talents: $talents[0],
                    glyphs: $glyphs[0],
                    equipment: $equipment[0]
,
                    race: $race,
                    class: $class,
                    profession1: $profession
1,
                    profession2: $profession
2,
                    simLink: $simLink
                  }
                }' > $out
            else
              echo "Trinket simulation faile
d for ${class}/${spec} with trinket ${toStri
ng trinketConfig.trinketId}"
              exit 1
            fi
          '';
      })
      trinketConfigs);

    # Generate structured output filename
    structuredOutput = "${class}_${spec}_tri
nket_${phase}_${encounterType}_${targetCount
}_${duration}";

    # Aggregation script that combines basel
ine + all trinket results
    aggregationScript = pkgs.writeShellAppli
cation {
      name = "${structuredOutput}-aggregator
";
      text = ''
        ${shellUtils.parseArgsAndEnv}

        echo "Aggregating trinket comparison
 results for: ${class}/${spec}"
        echo "Trinkets tested: ${toString (b
uiltins.length actualTrinketIds)}"

        # Start with baseline result
        result=$(jq -n '{}')

        # Add baseline
        baselineData=$(cat ${baselineSim})
        result=$(echo "$result" | jq \
          --argjson baseline "$baselineData"
 \
          '.baseline = $baseline')

        # Add each trinket result
        ${concatMapStringsSep "\n" (trinketC
onfig: let
            trinketItem = itemDatabase.getIt
em trinketConfig.trinketId;
            trinketName =
              if trinketItem != null
              then trinketItem.name
              else "Unknown_${toString trink
etConfig.trinketId}";
            trinketIlvl =
              if trinketItem != null && trin
ketItem ? scalingOptions && trinketItem.scal
ingOptions ? "0"
              then toString trinketItem.scal
ingOptions."0".ilvl
              else "0";
            cleanName = replaceStrings [" " 
"'" "," "(" ")"] ["_" "" "" "" ""] trinketNa
me;
            keyName = "${cleanName}_${trinke
tIlvl}";
          in ''
            trinketData=$(cat ${trinketSims.
${keyName}})
            result=$(echo "$result" | jq \
              --argjson trinket "$trinketDat
a" \
              --arg keyName "${keyName}" \
              '.[$keyName] = $trinket')
          '')
          trinketConfigs}

        # Create final output with metadata
        finalResult=$(echo "$result" | jq \
          --arg class "${class}" \
          --arg spec "${spec}" \
          --arg comparison "trinket" \
          --arg baseline "no_trinkets" \
          --arg encounter "${phase}_${encoun
terType}_${targetCount}_${duration}" \
          --arg timestamp "$(date -Iseconds)
" \
          --arg iterations "${toString itera
tions}" \
          --arg encounterDuration "${toStrin
g encounter.duration}" \
          --arg encounterVariation "${toStri
ng encounter.durationVariation}" \
          --arg targetCount "${toString (len
gth encounter.targets)}" \
          --arg wowsimsCommit "${wowsimsComm
it}" \
          --arg trinketCategory "${trinketCa
tegory}" \
          --argjson raidBuffs '${builtins.to
JSON classSpecificBuffs}' \
          '{
            metadata: {
              spec: $spec,
              class: $class,
              comparison: $comparison,
              baseline: $baseline,
              encounter: $encounter,
              timestamp: $timestamp,
              iterations: ($iterations | ton
umber),
              encounterDuration: ($encounter
Duration | tonumber),
              encounterVariation: ($encounte
rVariation | tonumber),
              targetCount: ($targetCount | t
onumber),
              wowsimsCommit: $wowsimsCommit,
              trinketCategory: $trinketCateg
ory,
              raidBuffs: $raidBuffs
            },
            results: .
          }')

        ${shellUtils.conditionalOutput {
          inherit structuredOutput;
          webSetupCode = shellUtils.setupCom
parisonDirs {
            comparisonType = "trinkets";
            class = "${class}";
            spec = "${spec}";
          };
          webPath = "$comparison_dir";
          webMessage = "Copied to";
        }}

        echo ""
        echo "Trinket DPS Rankings for ${cla
ss}/${spec}:"
        echo "==============================
========="

        # Show baseline DPS
        echo "Baseline (no trinkets): $(echo
 "$finalResult" | jq -r '.results.baseline.d
ps | floor') DPS"
        echo ""

        # Show trinket rankings sorted by DP
S
        echo "$finalResult" | jq -r '
          .results | to_entries[] |
          select(.key != "baseline" and .val
ue.dps != null and .value.dps > 0) |
          "\(.value.trinket.name): \(.value.
dps | floor) DPS"
        ' | sort -k2 -nr

      '';
      runtimeInputs = [pkgs.jq pkgs.coreutil
s];
    };
  in {
    # Individual simulation derivations
    simulations = trinketSims // {baseline =
 baselineSim;};

    # Main aggregation script
    script = aggregationScript;

    # Metadata
    metadata = {
      inherit class spec trinketCategory;
      output = structuredOutput;
      inherit iterations phase encounterType
 targetCount duration;
      trinketCount = length actualTrinketIds
;
      trinkets = actualTrinketIds;
    };
  };
in {
  inherit mkTrinketComparison;
}
 
──────────────────────────────────────────────────────────────────────────────────────
OOKNET
────────────────────────────────────────────────
OOKNET