A genetic algorithm solver for finding optimal gear combinations in Grim Dawn. This project models gear optimization as a variation of the knapsack problem, maximizing a "benefit score" based on gear stats across 11 equipment slots with components and augments.
Highlights:
- Fast convergence - Typically finds optimal solutions in 6-8 seconds
- Parallel execution - 7-8x speedup on multi-core systems (enabled by default)
- Massive search space - Efficiently handles combinatorial explosions across gear/component/augment possibilities
- Lock specific items - Build around items with unique bonuses (+skills, set bonuses)
- Live progress - Real-time updates every 2 seconds with Ctrl+C support
# Run with defaults (parallel mode, auto-stop)
python3 solver.py
# Output:
# [2.0s] Best: 2148 pts | Gen 37 | 45k evals/s
# [4.0s] Best: 2159 pts | Gen 80 | 48k evals/s
# [6.0s] Best: 2159 pts | Gen 128 | 51k evals/s
# No improvement after 2s plateau. Terminating search.
# Single-core mode (slower, lower CPU usage)
python3 solver.py --single-core
# Customize time limit (default: unlimited, stops on 2s plateau)
python3 solver.py --time-limit 10
# Verbose output (show generation progress and item counts)
python3 solver.py -v
# Show only gear slots with changes (default shows all gear)
python3 solver.py --swaps-only
# Press Ctrl+C anytime to stop and see best results found so farThe solver finds the combination of gear that maximizes a "benefit score" by:
- Selecting one base item for each of 11 gear slots (helm, chest, shoulders, gloves, pants, boots, belt, amulet, medal, ring 1, ring 2)
- Attaching one component and one augment to each base item (when compatible options exist)
- Calculating total benefit points based on weighted stat contributions with configurable caps
A spreadsheet contains all available gear, components, and augments with the following structure:
- Header Row: Defines the stats available from gear (e.g.,
rFirefor Fire Resistance) points_per_unitRow: Specifies the benefit score awarded for each point of a given statbaselineRow: Starting stat values from skills and difficulty (e.g., -50 for resistances)maxRow: Defines the cap for benefit scoring, NOT a hard limit on gear stats- Example: If
rFirehasmaxof 80 andpoints_per_unitof 2:- 30
rFirefrom gear scores 60 points (30 × 2) - 50
rFirefrom gear scores 80 points (benefit capped at 80)
- 30
- Gear stats can exceed the
maxvalue; excess doesn't contribute to score
- Example: If
typeColumn: Specifies the item category (e.g.,Helm,Chest,Component,Augment)tfColumn: A '1' indicates the gear is currently equippednameColumn: Item name (prefix with*to lock, see Locking Gear section)
The game allows one "Component" and one "Augment" to be attached to each base gear piece:
- Represented in the spreadsheet with
typeof "Component" or "Augment" applies_toColumn: Specifies which gear types they can attach to (e.g., "Helm,Chest" or "armor" for all armor pieces)- The solver creates virtual items combining base + component + augment stats
- Goal: Find the best combination of all three for every slot
Some gear types can be equipped in multiple slots:
- Rings use separate types (
Ring 1andRing 2) for two independent slots - The same ring cannot be equipped in both slots
The solver uses a Multi-Gene Genetic Algorithm as its only optimization method:
Performance:
- Adaptive termination (runs until plateau), typically 6-8 seconds
- 100% optimal on current dataset, evaluates millions of combinations per second
- Island model parallelization enabled by default (7-8x speedup on 16 cores)
Why Genetic Algorithm?
- The search space is astronomically large: each of 11 gear slots can be filled with (base × component × augment) combinations
- With typical datasets, total combinations range from billions to trillions, making brute force completely infeasible
- Multi-gene representation allows evolution of building blocks (base/component/augment) independently
- Efficiently explores trillion-scale search spaces through intelligent sampling
- Finds optimal or near-optimal solutions in seconds (typically 6-8s to convergence)
- Scales well as dataset grows
Key Features:
- Adaptive termination stops after 2s without improvement (no arbitrary timeout)
- Live progress updates every 2 seconds (score, generation, eval rate)
- Ctrl+C support for graceful interruption at any time
- Multi-gene chromosome: Each slot has 3 independent genes (base item, component, augment)
- Operators: Tournament selection, multi-gene crossover, granular mutation
- Scales population by CPU cores for better exploration (150 per core)
- Island model parallelization (enabled by default,
-sfor single-core): True multi-core execution with migration- 7-8x speedup on 16 cores (16k → 128k comparisons/sec)
- Ring topology migration every 2 seconds (adaptive to search progress)
- Each island evolves independently, sharing best solutions periodically
Parameters:
- Population: 150 per core (auto-calculated based on CPU count)
- Mutation rate: 15% (applies to each gene independently)
- Crossover rate: 70% (can mix genes from different parents)
- Elitism: Top 10% preserved each generation
Lock specific items to force them into all optimal builds (useful for items with unique bonuses like +skills that aren't captured in stat columns):
- Open
grim_items.csv(or editgrim_items.odsand reconvert) - Find the item you want to lock
- Add a
*prefix to its name (e.g.,thread of mortality→*thread of mortality) - Save and run the solver
Use cases:
- Items with unique skill bonuses (e.g., +3 to all skills) not in stat columns
- Set items you want to keep for set bonuses
- Items you like aesthetically or for specific build requirements
- Testing "what's the best build around this specific item?"
How it works:
- Locked items are forced into every individual during population initialization
- The genetic algorithm never mutates locked slots
- The solver optimizes everything else around the locked items
- Components and augments can still be optimized for locked gear pieces
- The
*prefix remains visible in all output displays - At startup, the solver displays a message listing all locked items detected
Example output:
Locked items detected (1 items will be forced into all builds):
amulet: *thread of mortality
AMULET
Base: *thread of mortality
Component: dread skull
Augment: boneback spine → breath of life (rov)
Note: You can lock base items, components, AND augments independently. For example, you could lock a base helm while allowing the solver to optimize its component and augment.
The solver displays the optimal gear configuration with:
- Gear recommendations showing all slots with stats for each item (sorted by benefit value)
- Full stat comparison table (current vs recommended with benefit points)
- Execution summary (evaluations, time, benefit improvement)
Example Output:
========================================================================================================================
RECOMMENDED GEAR CONFIGURATION
========================================================================================================================
HELM
Base: overseers vitality rStun(52), aAbsorb(25), hlth(891), da(78)
Component: runestone → leathery hide (hs) rStun(25)
Augment: (none) → outcasts warding pow. rAeth(12)
CHEST
Base: shrewd menhirs → impenetrable vitality rPrc(91), hlth(947), rChs(22)
Component: hallowed ground → chains of oleron oa(60), move(6)
Augment: (none) → outcasts warding pow. rAeth(12)
========================================================================================================================
BASELINE STATS (from skills + difficulty)
========================================================================================================================
rPhys: -50 | rPrc: -50 | rVit: -50 | rAeth: -50 | rChs: -50 | rEle: -50 | rBld: -50 | rStun: -50
========================================================================================================================
STAT COMPARISON
========================================================================================================================
Stat Wgt Cur → Opt Points Sources (optimal)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
dam 0.02 3805 → 3805 76 ring 1(3805)
dAll 0.33 108 → 49 16 gloves(49)
LS 7.00 11 → 13 91 belt(8), comp(5)
hlth 0.05 3940 → 4579 247 chest(947), helm(891), medal(619), gloves(344), boots(372), +2
%hlth 3.22 14 → 32 103 comp(12), boots(5), medal(5), pants(5), ring 2(5)
move 3.00 16 → 35 / 66 105 boots(19), comp(16)
+as% 5.00 15 → 24 120 gloves(17), ring 2(7)
rPhys 3.00 27 → 77 / 105 231 chest(44), amulet(33), ring 1(25), ring 2(21), helm(4)
rPrc 1.00 27 → 84 / 105 84 chest(49), amulet(33), ring 1(25), ring 2(21), comp(6)
rVit 1.00 15 → 27 / 80 27 comp(35), medal(17), aug(15)
rAeth 1.00 -2 → 20 / 80 20 aug(27), amulet(25), shoulders(18)
rChs 1.00 62 → 86 / 80 80 chest(22), ring 1(22), medal(21), ring 2(21)
rBld 1.00 59 → 74 / 80 74 pants(22), medal(20), ring 2(17), comp(15)
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
TOTAL: 1501 → 1666 (+165 points, +11.0%)
Gear display format:
- Shows all gear slots by default (use
--swaps-onlyto show only changes) - Each item displays its stats sorted by benefit contribution (highest first)
- Format:
item name stat(value), stat(value), ... - Changed items show:
old → new stat(value), ... - Unchanged items show:
name stat(value), ...(for verification) - Stats truncate with
+Nif too many to fit on one line
Stat table column descriptions:
- Stat: Stat name from CSV header
- Wgt: Benefit points awarded per unit (shows 2 decimal places)
- Cur → Opt: Current value → Optimal value / Cap (cap shown only when present)
- Points: Total benefit points contributed by this stat in optimal build
- Sources: Items contributing to this stat (with values in parentheses)
User Features:
- Display top-N alternative builds (diversity selection code exists but output not shown to user)
- Gear culling recommendations (identify items safe to discard based on top solutions)
- Minimum stat constraints (e.g., "must have at least 80% rAether")
Optimization Ideas:
- Seed initial population with current gear (guarantees never worse than equipped)
- Local search / hill climbing post-processing (try all single-item swaps after GA convergence)
- Smart mutation strategy (prioritize swaps that meet minimum thresholds)
- Better migration strategy (synchronous barriers, track migration success rate)
- Adaptive population sizing based on search space size