v0.5.59: item counter evidence for fears, catch up Web features to site version.

Ship OpenDota counter-stats reordering for feared items, finalize SITE_VERSION/docs for rankings/streamers/trends/matches/mechanics and draft archetypes, and ignore regenerable Web data caches.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-29 02:11:49 +08:00
co-authored by Cursor
parent 37769580f5
commit 3ec8007077
72 changed files with 20669 additions and 2105 deletions
+73 -15
View File
@@ -38,7 +38,7 @@ from capture import grab_frame, is_dota_foreground, raw_dir_for_match, save_fram
from grid import bans, hero_table, read_grid
from modes import detect_mode, load_mode_templates
from recognize import recognize_image
from recommend import ally_keys, enemy_keys, load_relations, suggest_top
from recommend import ally_keys, enemy_keys, load_relations, suggest_marks
from roles import ROLES, detect_roles, load_role_templates
HERO_SELECTION = "DOTA_GAMERULES_STATE_HERO_SELECTION"
@@ -84,11 +84,16 @@ class DraftSession:
self.frame_dir = raw_dir_for_match(None)
rec = cfg.get("recommend") or {}
self.recommend_enabled = bool(rec.get("enabled", True))
self.recommend_top_n = int(rec.get("top_n", 3))
self.recommend_top_n = int(rec.get("top_n", 0))
self.recommend_min_enemies = int(rec.get("min_enemies", 1))
self.recommend_min_heroes_for_gaps = int(rec.get("min_heroes_for_gaps", 2))
self.recommend_archetypes = bool(rec.get("archetypes", True))
self.recommend_role_tags = rec.get("role_tags")
self.relations = load_relations(rec.get("relations_path")) if self.recommend_enabled else None
self._rec_warned = False
self._last_rec_sig: tuple | None = None
self._last_enemy_profile: dict = {}
self._last_rec_meta: dict = {}
def _push_overlay(self, confirmed: dict[int, str]) -> None:
if self.overlay is None:
@@ -98,12 +103,19 @@ class DraftSession:
except Exception as e: # noqa: BLE001
self.log(f"[draft] overlay update failed: {e}")
def _push_rec_overlay(self, cells: dict | None, picks: list[dict]) -> None:
def _push_rec_overlay(
self,
cells: dict | None,
picks: list[dict],
analysis: str = "",
) -> None:
if self.overlay is None:
return
try:
marks = {p["key"]: p["rank"] for p in picks}
marks = {p["key"]: list(p.get("labels") or []) for p in picks if p.get("key")}
self.overlay.set_grid_marks(cells or {}, marks)
if hasattr(self.overlay, "set_analysis"):
self.overlay.set_analysis(analysis or "")
except Exception as e: # noqa: BLE001
self.log(f"[draft] rec overlay update failed: {e}")
@@ -115,6 +127,8 @@ class DraftSession:
try:
self.overlay.set_roster({})
self.overlay.set_grid_marks({}, {})
if hasattr(self.overlay, "set_analysis"):
self.overlay.set_analysis("")
self.overlay.show()
except Exception as e: # noqa: BLE001
self.log(f"[draft] overlay show failed: {e}")
@@ -255,6 +269,8 @@ class DraftSession:
if self.overlay is not None:
try:
self.overlay.set_grid_marks({}, {})
if hasattr(self.overlay, "set_analysis"):
self.overlay.set_analysis("")
self.overlay.hide()
except Exception as e: # noqa: BLE001
self.log(f"[draft] overlay hide failed: {e}")
@@ -365,7 +381,7 @@ class DraftSession:
def _refresh_recommendations(self, confirmed: dict, info: dict, gsi_fn, *, force: bool = False) -> None:
if not self.recommend_enabled:
return
if not self.relations:
if not self.relations and not self.recommend_archetypes:
return
gsi = (gsi_fn() or {}) if gsi_fn else {}
self_slot = gsi_slot(gsi) or info.get("self_slot")
@@ -376,8 +392,10 @@ class DraftSession:
if self_slot and confirmed.get(self_slot):
if info.get("recommendations") or self._last_rec_sig is not None:
info["recommendations"] = []
self._last_enemy_profile = {}
self._last_rec_meta = {}
self._last_rec_sig = ("locked",)
self._push_rec_overlay(info.get("cells"), [])
self._push_rec_overlay(info.get("cells"), [], "")
return
enemies = enemy_keys(confirmed, self_team)
allies = ally_keys(confirmed, self_team, self_slot)
@@ -388,11 +406,13 @@ class DraftSession:
if not force and sig == self._last_rec_sig:
return
self._last_rec_sig = sig
if position is None or (not enemies and not allies):
if len(enemies) < self.recommend_min_enemies:
info["recommendations"] = []
self._push_rec_overlay(info.get("cells"), [])
self._last_enemy_profile = {}
self._last_rec_meta = {}
self._push_rec_overlay(info.get("cells"), [], "")
return
picks = suggest_top(
result = suggest_marks(
position=position,
enemies=enemies,
allies=allies,
@@ -400,14 +420,38 @@ class DraftSession:
relations=self.relations,
top_n=self.recommend_top_n,
role_tags=self.recommend_role_tags,
min_enemies=self.recommend_min_enemies,
min_heroes_for_gaps=self.recommend_min_heroes_for_gaps,
archetypes_enabled=self.recommend_archetypes,
)
picks = result.get("marks") or []
profile = result.get("enemy_profile") or {}
analysis = result.get("analysis") or ""
info["recommendations"] = picks
self._push_rec_overlay(info.get("cells"), picks)
if picks:
names = ", ".join(f"{p['rank']}.{p['name_loc']}({p['score']:+d})" for p in picks)
self._last_enemy_profile = profile
self._last_rec_meta = {
"analysis": analysis,
"enemy_archetypes": list(result.get("enemy_archetypes") or []),
"enemy_gaps": list(result.get("enemy_gaps") or []),
"ally_gaps": list(result.get("ally_gaps") or []),
"ally_profile": dict(result.get("ally_profile") or {}),
}
self._push_rec_overlay(info.get("cells"), picks, analysis)
if picks or analysis:
bits = []
for p in picks[:12]:
labs = "".join(p.get("labels") or [])
why = "".join(p.get("reasons") or [])
extra = f":{why}" if why else ""
bits.append(f"{p['name_loc']}[{labs}]{extra}")
pos_s = f"pos{position}" if position is not None else "all"
ally_n = [self.hero_names.get(a, a) for a in allies]
enemy_n = [self.hero_names.get(e, e) for e in enemies]
self.log(f"[rec] pos{position} with {ally_n} vs {enemy_n}: {names}")
analysis_s = analysis or "-"
self.log(
f"[rec] {pos_s} {analysis_s} | with {ally_n} vs {enemy_n}: "
f"{len(picks)} marks — {', '.join(bits)}"
)
def _absorb_picks(self, result: dict, pending: dict, confirmed: dict,
scores: dict, *, allow_revise: bool = True,
@@ -541,6 +585,12 @@ class DraftSession:
"position": role["position"] if role else None,
"enemies": enemies,
"allies": allies,
"enemy_profile": dict(self._last_enemy_profile or {}),
"ally_profile": dict((self._last_rec_meta or {}).get("ally_profile") or {}),
"enemy_archetypes": list((self._last_rec_meta or {}).get("enemy_archetypes") or []),
"enemy_gaps": list((self._last_rec_meta or {}).get("enemy_gaps") or []),
"ally_gaps": list((self._last_rec_meta or {}).get("ally_gaps") or []),
"analysis": (self._last_rec_meta or {}).get("analysis") or "",
"picks": info.get("recommendations") or [],
},
}
@@ -613,10 +663,18 @@ def describe(summary: dict) -> list[str]:
if summary.get("bans_loc"):
lines.append(f"bans : {len(summary['bans_loc'])} - {', '.join(summary['bans_loc'])}")
rec = summary.get("recommendations") or {}
analysis = rec.get("analysis") or ""
if analysis:
lines.append(f"draft : {analysis}")
picks = rec.get("picks") or []
if picks:
bits = ", ".join(f"{p['rank']}.{p.get('name_loc') or loc(p['key'], names)}" for p in picks)
lines.append(f"rec : {bits}")
bits = []
for p in picks[:15]:
labs = "".join(p.get("labels") or []) or "?"
why = "".join(p.get("reasons") or [])
name = p.get("name_loc") or loc(p["key"], names)
bits.append(f"{name}[{labs}]" + (f"({why})" if why else ""))
lines.append(f"rec : {len(picks)}{', '.join(bits)}")
return lines