Build Evaluation Developer Skill
Use this skill when modifying the weapon build evaluation logic in internal/evaluator/ .
The evaluator is a high-performance DFS engine. Incorrect modifications can lead to exponential search times or suboptimal builds.
Scope & Intent
Use this skill when:
-
Modifying the DFS algorithm in processSlots
-
Adding new optimization metrics (e.g. Weight, Price)
-
Adjusting pruning or bounding logic
-
Debugging why a "best build" isn't actually optimal
Safety & Performance Invariants
-
Limited Run Scope:
-
Full Runs: Do NOT trigger a full evaluator run (task evaluator:start ) unless explicitly requested.
-
Test Mode: You MAY use task evaluator:start:test-mode for smoke tests. It uses a restricted subset of weapons and trader levels.
-
Focused Testing: Prefer unit tests, benchmarks, or integration tests targeting specific weapons.
-
Thread Safety: processSlots runs in parallel worker pools. NEVER use global variables or mutate shared structures (like the CandidateTree items) during the search.
-
Pre-Evaluation Setup: Before calling FindBestBuild , you MUST ensure the tree is initialized: weapon.UpdateAllowedItems() weapon.UpdateAllowedItemSlots()
-
Branch-Local Exclusions: excludedItems must be cloned (helpers.CloneMap ) when descending into a branch where a new conflict is introduced.
Algorithm Logic: processSlots
Conflict Enforcement
Conflicts are enforced by the excludedItems map.
-
Rule: If an item is in excludedItems , it cannot be chosen for the current slot.
-
Rule: If an item is chosen, all its ConflictingItems must be added to the exclusion map for all subsequent descendants and siblings in that branch.
Tie-Breaking (doesImproveStats )
When comparing builds, we use primary and secondary stats:
-
Recoil Focus: Primary is RecoilSum (lower is better). Tie-breaker is ErgonomicsSum (higher is better).
-
Ergo Focus: Primary is ErgonomicsSum (higher is better). Tie-breaker is RecoilSum (lower is better).
Pruning & Bounding
Pruning skips branches that cannot mathematically beat the current best build.
Bounding Functions
-
computeRecoilLowerBound : Sum of current recoil + minimum possible recoil from all remaining slots.
-
computeErgoUpperBound : Sum of current ergonomics + maximum possible ergonomics from all remaining slots.
Pruning Locations
-
Item Selection: Before evaluating an item in a slot.
-
Empty Slot: Before evaluating the outcome of leaving a slot empty.
Caching Logic (Cache interface)
Caching uses memoization of "conflict-free" subtrees to avoid redundant DFS traversals.
The "Conflict-Free" Invariant
-
An item is only cacheable if it has zero ConflictingItems .
-
If an item has conflicts, its optimal subtree depends on which exclusions are active, making simple ID-based caching unsafe.
Clean Contribution
The cache stores the relative contribution of a subtree, not the absolute total. This allows the same cached result to be added to different parent builds.
How to Modify: Developer Checklist
When adding a new stat (e.g., Weight ):
-
Update Models: Add the stat to Build , ItemEvaluation , and OptimalItem in evaluator.go .
-
Implement Bounding: Create compute[Stat]LowerBound or compute[Stat]UpperBound .
-
Update Comparison: Modify doesImproveStats to include the new stat in the priority chain.
-
Integrate Pruning: Call your new bounding function in processSlots .
-
Update Caching: Ensure the new stat is captured in CacheEntry and the Cache implementation.
-
Verify:
-
Run go test ./internal/evaluator/ -run TestCache to check cache correctness.
-
Run go test ./internal/evaluator/ -bench=. -benchmem to check for performance regressions.