v0.2.0: relations preview, item shop, abilities, overlay recommend, GSI enhancements

- Add relations/item/abilities preview (serve_relations.py + web/relations/)
- Add fetch scripts: hero_items, item_shop, items_meta, hero_abilities,
  ability_videos, patches, stratz, matchups, portraits
- Add overlay.py (role tags + Top-3 cyan marks), recommend.py
- Add http_utils.py, loc_format.py, hero_tags.py, item_fears.py
- GSI: full payload JSONL dump, foreground window detection
- Drop real template library; CDN-only matching
- Update docs: CHANGELOG 0.2.0, DESIGN config table, AGENTS module table
- .gitignore: exclude large regenerable assets (icons/portraits/videos)
This commit is contained in:
voson
2026-07-27 11:56:51 +08:00
parent e567a5cdfc
commit a91789b72f
76 changed files with 109860 additions and 996 deletions
+13 -7
View File
@@ -22,15 +22,15 @@ import json
import cv2
import numpy as np
from common import ROOT
from common import HEROES_JSON
ATTR_ORDER = ("str", "agi", "int", "all")
def hero_table() -> list[dict]:
table = json.loads((ROOT / "heroes.json").read_text(encoding="utf-8"))
table = json.loads(HEROES_JSON.read_text(encoding="utf-8"))
if table and "attr" not in table[0]:
raise SystemExit("heroes.json predates grid support - rerun fetch_cdn_templates.py")
raise SystemExit(f"{HEROES_JSON.name} predates grid support - rerun fetch_cdn_templates.py")
return table
@@ -151,24 +151,30 @@ def read_grid(img: np.ndarray, cfg: dict | None = None) -> dict:
table = hero_table()
grid = detect_grid(img, cfg)
if grid is None:
return {"ok": False, "reason": "no grid detected", "unavailable": []}
return {"ok": False, "reason": "no grid detected", "unavailable": [], "cells": {}}
layout = build_layout(grid, table)
if layout is None:
return {"ok": False, "reason": "grid shape does not fit the roster", "unavailable": []}
return {"ok": False, "reason": "grid shape does not fit the roster", "unavailable": [], "cells": {}}
if len(layout) != len(table):
return {"ok": False,
"reason": f"placed {len(layout)} of {len(table)} heroes",
"unavailable": []}
"unavailable": [], "cells": {}}
cut = cfg.get("grid", {}).get("unavailable_std", 26.0)
scored = {key: cell_contrast(img, grid, r, c) for (r, c), key in layout.items()}
unavailable = sorted((k for k, s in scored.items() if s < cut), key=lambda k: scored[k])
live = [s for s in scored.values() if s >= cut]
cols, rows = grid["cols"], grid["rows"]
cells = {
key: {"x0": cols[c][0], "y0": rows[r][0], "x1": cols[c][1], "y1": rows[r][1]}
for (r, c), key in layout.items()
}
return {
"ok": True,
"unavailable": unavailable,
"grid": {"cols": len(grid["cols"]), "rows": len(grid["rows"])},
"cells": cells,
"grid": {"cols": len(cols), "rows": len(rows)},
"margin": round(min(live) - max((scored[k] for k in unavailable), default=0.0), 1) if live and unavailable else None,
}