Skip to content

Commit a6517aa

Browse files
SnoopLawgclaude
andcommitted
M5: farm-source for recommended sets (close the gear loop)
The build recommender now lists WHERE to farm each recommended artifact set, joining the set->drop-region tables (drops.json) with the set id<->name map (artifact_sets.json). "needs Speed set" -> "Dragon's Lair"; "Offense" -> "Ice Golem Peak". recommend_sets now returns internal codes (mapped to UI names + drop lookup at display); only sets dropping in a named dungeon are listed as farmable. Wired into m5_recommender --builds. Closes the recommender loop: team -> per-hero build (masteries/sets/blessing/ stats/readiness) -> where to farm the gear. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5b99be0 commit a6517aa

3 files changed

Lines changed: 61 additions & 4 deletions

File tree

docs/m5_recommender_guide.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,9 @@ Maps the hero's game-truth kit → relevant masteries (filtered to the location)
5353
artifact sets, blessing, and stat focus — and checks the user's **actual ACC**
5454
against the floor (`[READY: you have 423]` / `[GAP: need +M]`). Set bonuses are
5555
game-truth (`artifact_sets.json`); the role→set mapping is heuristic. Set names
56-
are shown in UI form (internal `CriticalDamage` → "Savage/Cruel").
56+
are shown in UI form (internal `CriticalDamage` → "Savage/Cruel"). It also lists
57+
**where to farm** each recommended set, joining the set→drop-region tables
58+
(`drops.json`) so "needs Speed set" → "farm Dragon's Lair".
5759

5860
### What to pull / build next
5961
```bash

tools/m5_build_recommender.py

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,63 @@ def recommend_sets(hero: dict, location: str, valid_sets: set) -> list:
243243
if location in ("arena", "tt"):
244244
picks = ["AttackSpeed", "CriticalDamage", "CriticalChance"] + picks
245245
# Dedup preserving order, keep only sets that exist in game data.
246+
# Returns INTERNAL codes (callers map to UI via SET_UI_NAME for display
247+
# and pass codes to recommend_farm for the drop-location lookup).
246248
seen, out = set(), []
247249
for s in picks:
248250
if s in seen or s not in valid_sets:
249251
continue
250252
seen.add(s)
251-
out.append(SET_UI_NAME.get(s, s))
253+
out.append(s)
252254
return out[:5]
253255

254256

257+
# Region code -> readable farm location (named dungeons; RegionN = campaign).
258+
FARM_REGION_NAME = {
259+
"DragonsLair": "Dragon's Lair", "IceGolemCave": "Ice Golem Peak",
260+
"FireGolemCave": "Fire Knight Castle", "SpiderCave": "Spider's Den",
261+
"MinotaurLabyrinth": "Minotaur", "EventDungeon": "Event Dungeon",
262+
}
263+
264+
265+
def _set_drop_regions() -> dict:
266+
"""internal set name -> sorted list of readable drop locations (named
267+
dungeons first; campaign RegionN bucketed as 'Campaign')."""
268+
af = json.loads((STATIC / "artifact_sets.json").read_text(encoding="utf-8"))
269+
arows = next((v for k, v in af.items() if k != "_meta" and isinstance(v, list)), [])
270+
id_to_name = {r["id"]: r["set"] for r in arows}
271+
name_to_id = {r["set"]: r["id"] for r in arows}
272+
drops = json.loads((STATIC / "drops.json").read_text(encoding="utf-8")).get("regions", {})
273+
from collections import defaultdict
274+
id_to_regions = defaultdict(set)
275+
for rname, info in drops.items():
276+
for diff in info.get("by_difficulty", []):
277+
for sid in (diff.get("set_drops") or {}).keys():
278+
id_to_regions[int(sid)].add(rname)
279+
out = {}
280+
for sname, sid in name_to_id.items():
281+
locs = []
282+
for r in sorted(id_to_regions.get(sid, [])):
283+
if r in FARM_REGION_NAME:
284+
locs.append(FARM_REGION_NAME[r])
285+
elif "Campaign" not in locs:
286+
locs.append("Campaign")
287+
out[sname] = locs
288+
return out
289+
290+
291+
def recommend_farm(rec_set_internal: list, drop_map: dict) -> list:
292+
"""Given recommended internal set codes, return 'Set -> location(s)' lines.
293+
Only sets that drop in a named dungeon are actionable to farm."""
294+
lines = []
295+
for code in rec_set_internal:
296+
locs = [l for l in drop_map.get(code, []) if l != "Campaign"]
297+
if locs:
298+
ui = SET_UI_NAME.get(code, code)
299+
lines.append(f"{ui}: {', '.join(locs)}")
300+
return lines
301+
302+
255303
def recommend_blessing(hero: dict, location: str, brel: list) -> list:
256304
provides = set(hero.get("provides", []))
257305
role = hero.get("synergy_role", "")
@@ -335,7 +383,13 @@ def main() -> None:
335383
print(f" {tree}: {names}")
336384
print()
337385
sets = recommend_sets(hero, args.location, _load_valid_sets())
338-
print(f" Artifact sets (role-fit): {', '.join(sets) if sets else '(role-standard)'}")
386+
sets_ui = [SET_UI_NAME.get(s, s) for s in sets]
387+
print(f" Artifact sets (role-fit): {', '.join(sets_ui) if sets_ui else '(role-standard)'}")
388+
farm = recommend_farm(sets, _set_drop_regions())
389+
if farm:
390+
print(" Farm sets from:")
391+
for f in farm:
392+
print(f" - {f}")
339393
print()
340394
bl = recommend_blessing(hero, args.location, brel)
341395
print(f" Blessing (relevant + role-fit): {', '.join(bl) if bl else '(role-standard)'}")

tools/m5_recommender.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -356,9 +356,10 @@ def main() -> None:
356356
f"{t}: {', '.join(p['name'] for p in by_tree[t])}"
357357
for t in ("Attack", "Defence", "Support") if by_tree.get(t))
358358
sets = brmod.recommend_sets(hero, args.location, valid_sets)
359+
sets_ui = [brmod.SET_UI_NAME.get(s, s) for s in sets]
359360
print(f"\n {name}:")
360361
print(f" masteries: {masts}")
361-
print(f" sets: {', '.join(sets) if sets else '(role-standard)'}")
362+
print(f" sets: {', '.join(sets_ui) if sets_ui else '(role-standard)'}")
362363
print(f" blessing: {', '.join(bl) if bl else '(role-standard)'}")
363364
for s in brmod.recommend_stats(hero, args.location, computed):
364365
print(f" stat: {s}")

0 commit comments

Comments
 (0)