Skip to content
build-evaluation-developer logo

build-evaluation-developer

Guidelines for safely understanding, modifying, and extending the weapon build optimization evaluator

SKILL.md

Full skill instructions

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

  1. 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.
  2. Thread Safety: processSlots runs in parallel worker pools. NEVER use global variables or mutate shared structures (like the CandidateTree items) during the search.
  3. Pre-Evaluation Setup: Before calling FindBestBuild, you MUST ensure the tree is initialized:
    weapon.UpdateAllowedItems()
    weapon.UpdateAllowedItemSlots()
    
  4. 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

  1. Item Selection: Before evaluating an item in a slot.
  2. 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):

  1. Update Models: Add the stat to Build, ItemEvaluation, and OptimalItem in evaluator.go.
  2. Implement Bounding: Create compute[Stat]LowerBound or compute[Stat]UpperBound.
  3. Update Comparison: Modify doesImproveStats to include the new stat in the priority chain.
  4. Integrate Pruning: Call your new bounding function in processSlots.
  5. Update Caching: Ensure the new stat is captured in CacheEntry and the Cache implementation.
  6. Verify:
    • Run go test ./​internal/​evaluator/ -run TestCache to check cache correctness.
    • Run go test ./​internal/​evaluator/ -bench=. -benchmem to check for performance regressions.