Skip to content

How the Build Generator Works

GidntSquia edited this page Oct 1, 2026 · 4 revisions

How the Build Generator Works

src/generator/ is a deterministic, pure function of (hero assets, item catalog, aggregate analytics). It never reads per-player data; npm run verify greps the module for any panel account id and checks two runs give identical output.

One build is produced per hero unless the analytics show two established ways to play it, in which case one build is produced per style, each scored from that style's own population of games (see Build Styles). The scoring archetype (stats.ts → ARCHETYPES) has neutral stat multipliers (1.0) and no slot bias, so the builds differ only because their populations differ.

All weights and limits live in one object, PARAMS in stats.ts. The current values were chosen by scripts/tune.ts to maximise the mean panel agreement across all 38 heroes (2026-09-06); see Held-out Validation and Judgment Calls.

Scoring function

For each candidate item i (shopable, cost > 0, relative usage ≥ 12 %):

score(i) = 2.0  · sqrt(pop)           pop     = matches_i / matches of the hero's most-bought item
         + 0.25 · winLift              winLift = (shrunkWR_i − heroMeanWR) × 10 × pop
         + 0    · eff / max(eff)       eff     = Σ_stat |value| · UNIT_VALUE[stat] · archetypeMult[stat] / cost
         + 0    · kit / max(kit)       kit     = Σ_stat |value| · UNIT_VALUE[stat] · kitMult[stat]       / cost
         + 0.3  · [is active item]
         , × slotBias[archetype][slot]
         + 0.25 · synergy              synergy = mean pair win-lift (×10) with items already chosen
                                                (permutation-stats, pairs with ≥ 200 matches only)
  • shrunkWR = (wins + K·mean) / (matches + K) with K = max(200, 20 % of the top item's matches) (Bayesian shrinkage; rare items regress to the hero mean instead of dominating on noisy win rates). The lift is then multiplied by pop: shrinkage handles small samples, not selection bias. An item bought in 5 % of games is bought in games that are already going well, and that bias does not shrink with more matches, so its win rate is only trusted in proportion to how widely it is bought.
  • UNIT_VALUE (souls per stat unit, stats.ts) converts stat lines from the assets properties into a soul-equivalent so value-per-soul is comparable across tiers. Values are hand-set from tier-1 item prices. Both stat terms currently have weight 0: the tuning sweep found they only lower agreement once the population is Phantom+. The table and the kit profile are kept in the code, and the sweep grid includes them, so they come back if a future panel favours them.
  • kitMult (kit.ts) comes from the hero assets: number of ability properties that scale with ETechPower/ETechDuration, bullet/weapon keywords in ability text, and standard_level_up_upgrades (bullet-damage vs tech-power growth). Infernus has a documented override: spirit ×1.6, spirit % ×1.5, fire rate ×1.5, ability duration ×1.4, bullet/ability lifesteal ×1.3, cooldown ×1.2, move speed ×1.2 — Afterburn is a bullet-applied burn scaling with spirit, so fire rate and spirit both feed it, and burn duration items extend Afterburn/Napalm.
  • Game phase: each item's avg_buy_time_s for the hero (from item-stats) drives buy order and the early (<10 min) / mid (<22 min) / late grouping. Tier / soul-investment thresholds: at least 4 tier-1 and 3 tier-2 items are forced in, at most 8 items per slot type (4 native slots + 4 flex), at most 3 active items, 16 final items (the game's slot count; minimum 14). An item and the item it upgrades into may both be picked, as in the in-game shop: the component is listed first, the upgrade shares its slot and pays only the cost difference (paidCost). Each component can be upgraded once; at most 6 such upgrade steps are added on top of the 16.
  • Selection is greedy: repeatedly take the highest-scoring admissible item (ties → lower item id), then sort the chosen set by average buy time (ties → cost, id) and accumulate the running soul total.

Ability order

From ability-order-stats: sequences restricted to the hero's 4 signature abilities, scored by shrunkWR × ln(1 + matches); the top sequence is used. Each entry is decoded as unlock (first appearance) then T1/T2/T3 upgrade tiers. Names and icons come from the abilities asset.

If the build has a passive item that adds ability charges (Extra Charge, Rapid Recharge, Omnicharge Signet), the sequences come from only the high-rank games where that item was bought (the first such item in buy order with a sequence of 200+ matches; a style's own games first, then the whole high-rank population). Those players level the charged abilities much earlier: on Infernus, 99% of Extra Charge buyers max Flame Dash by their 8th point, against 65% of all games by their 15th. The fetch stores these as item_ability_order_stats (npm run fetch-data -- --charge-orders-only refreshes just them).

Clone this wiki locally