Skip to content

Commit 5b99be0

Browse files
SnoopLawgclaude
andcommitted
M5: artifact set recommendations in the build recommender
Completes the per-hero build sheet (masteries + sets + blessing + stat readiness). recommend_sets() maps the hero's game-truth kit -> role-fit sets, with game-truth set bonuses from artifact_sets.json and internal->UI name mapping (CriticalDamage -> "Savage/Cruel"). Debuffers/DoT carries lead with Speed + Accuracy sets (cadence + landing), pure attackers with damage sets, support/tank with survivability. Wired into m5_recommender --builds. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent dd4ade8 commit 5b99be0

3 files changed

Lines changed: 67 additions & 2 deletions

File tree

docs/m5_recommender_guide.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@ downweighted vs CC-immune bosses; the game-truth ACC floor is surfaced.
5050
python3 tools/m5_build_recommender.py --hero Venomage --location cb
5151
```
5252
Maps the hero's game-truth kit → relevant masteries (filtered to the location),
53-
blessing, and stat focus — and checks the user's **actual ACC** against the
54-
floor (`[READY: you have 423]` / `[GAP: need +M]`).
53+
artifact sets, blessing, and stat focus — and checks the user's **actual ACC**
54+
against the floor (`[READY: you have 423]` / `[GAP: need +M]`). Set bonuses are
55+
game-truth (`artifact_sets.json`); the role→set mapping is heuristic. Set names
56+
are shown in UI form (internal `CriticalDamage` → "Savage/Cruel").
5557

5658
### What to pull / build next
5759
```bash

tools/m5_build_recommender.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def _load_blessing_relevance() -> list:
8080
return raw["blessings"]
8181

8282

83+
def _load_valid_sets() -> set:
84+
raw = json.loads((STATIC / "artifact_sets.json").read_text(encoding="utf-8"))
85+
rows = next((v for k, v in raw.items() if k != "_meta" and isinstance(v, list)), [])
86+
return {r["set"] for r in rows}
87+
88+
8389
_COMPUTED_COLS = ["base_computed", "artifact_bonus", "mastery_bonus",
8490
"affinity_bonus", "classic_arena_bonus", "blessing_bonus",
8591
"empower_bonus", "relic_bonus", "faction_guardians_bonus"]
@@ -195,6 +201,57 @@ def relevant(mid: int) -> bool:
195201
return by_tree
196202

197203

204+
# Artifact set recommendations by kit role. The set BONUSES are game-truth
205+
# (data/static/artifact_sets.json); the role->set mapping is standard
206+
# team-building heuristic. Internal set code -> UI name for readability
207+
# (set names differ from UI, like blessings — see artifact_relic_mechanics).
208+
SET_UI_NAME = {
209+
"CriticalDamage": "Savage/Cruel (C.DMG)", "CriticalChance": "Crit Rate",
210+
"AttackPower": "Offense (ATK%)", "IgnoreDefense": "Stalwart-ish / IgnoreDef",
211+
"LifeDrain": "Lifesteal", "AttackAndCritRate": "Atk+CR",
212+
"AccuracyAndSpeed": "Perception (ACC+SPD)", "Accuracy": "Accuracy",
213+
"AttackSpeed": "Speed", "SpeedAndResistance": "Speed+RES",
214+
"Hp": "Life (HP%)", "Defense": "Defense", "Shield": "Shield",
215+
"Resistance": "Resistance", "HpAndHeal": "Immortal",
216+
"Counterattack": "Retaliation", "Stamina": "Relentless (TM)",
217+
"DotRate": "Toxic", "BlockDebuff": "Immunity",
218+
"ResistanceAndBlockDebuff": "Resist+Immunity",
219+
"UnkillableAndSpdAndCrDmg": "Frenzy/Unkillable-ish",
220+
}
221+
222+
223+
def recommend_sets(hero: dict, location: str, valid_sets: set) -> list:
224+
provides = set(hero.get("provides", []))
225+
role = hero.get("synergy_role", "")
226+
game_role = hero.get("game_role", "")
227+
is_attacker = game_role == "Attack" or role in ("attacker", "dot")
228+
is_debuffer = any(p.startswith("enemy_debuff:") for p in provides) or "dot" in role
229+
is_support = role in ("support", "healer/support") or game_role == "Support"
230+
is_tank = game_role in ("Defense", "Health")
231+
232+
picks: list[str] = []
233+
# Debuffers/DoT carries lead with Speed + Accuracy (cadence + landing
234+
# matter most), then damage. Pure attackers lead with damage sets.
235+
if is_debuffer:
236+
picks += ["AttackSpeed", "AccuracyAndSpeed", "Accuracy"]
237+
if is_attacker:
238+
picks += ["CriticalDamage", "AttackPower", "CriticalChance",
239+
"IgnoreDefense", "LifeDrain"]
240+
if is_support or is_tank:
241+
picks += ["AttackSpeed", "SpeedAndResistance", "Hp", "Defense",
242+
"Resistance", "HpAndHeal", "Shield"]
243+
if location in ("arena", "tt"):
244+
picks = ["AttackSpeed", "CriticalDamage", "CriticalChance"] + picks
245+
# Dedup preserving order, keep only sets that exist in game data.
246+
seen, out = set(), []
247+
for s in picks:
248+
if s in seen or s not in valid_sets:
249+
continue
250+
seen.add(s)
251+
out.append(SET_UI_NAME.get(s, s))
252+
return out[:5]
253+
254+
198255
def recommend_blessing(hero: dict, location: str, brel: list) -> list:
199256
provides = set(hero.get("provides", []))
200257
role = hero.get("synergy_role", "")
@@ -277,6 +334,9 @@ def main() -> None:
277334
names = ", ".join(p["name"] for p in picks)
278335
print(f" {tree}: {names}")
279336
print()
337+
sets = recommend_sets(hero, args.location, _load_valid_sets())
338+
print(f" Artifact sets (role-fit): {', '.join(sets) if sets else '(role-standard)'}")
339+
print()
280340
bl = recommend_blessing(hero, args.location, brel)
281341
print(f" Blessing (relevant + role-fit): {', '.join(bl) if bl else '(role-standard)'}")
282342
print()

tools/m5_recommender.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,7 @@ def main() -> None:
343343
mrel = brmod._load_mastery_relevance()
344344
brel = brmod._load_blessing_relevance()
345345
computed = brmod._load_computed_stats()
346+
valid_sets = brmod._load_valid_sets()
346347
print()
347348
print("=== Per-hero builds ===")
348349
for name in res["team"]:
@@ -354,8 +355,10 @@ def main() -> None:
354355
masts = "; ".join(
355356
f"{t}: {', '.join(p['name'] for p in by_tree[t])}"
356357
for t in ("Attack", "Defence", "Support") if by_tree.get(t))
358+
sets = brmod.recommend_sets(hero, args.location, valid_sets)
357359
print(f"\n {name}:")
358360
print(f" masteries: {masts}")
361+
print(f" sets: {', '.join(sets) if sets else '(role-standard)'}")
359362
print(f" blessing: {', '.join(bl) if bl else '(role-standard)'}")
360363
for s in brmod.recommend_stats(hero, args.location, computed):
361364
print(f" stat: {s}")

0 commit comments

Comments
 (0)