Add 7.41e patch notes with a hand-authored reading sidebar.

Ship Climperor Web 0.6.13: latest-patch summary panel, icon/innate fallbacks, and layout/scroll fixes; keep shared ability badges in git.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
voson
2026-07-31 10:06:09 +08:00
co-authored by Cursor
parent a257f96d94
commit 30218790ce
18 changed files with 2169 additions and 63 deletions
+35 -1
View File
@@ -147,6 +147,7 @@ HERO_ABILITIES_PATH = DATA / "hero_abilities.json"
ITEM_SHOP_PATH = DATA / "item_shop.json"
ITEMS_META_PATH = DATA / "items_meta.json"
PATCHES_PATH = DATA / "patches.json"
PATCH_SUMMARIES_PATH = DATA / "patch_summaries.json"
LEADERBOARDS_PATH = DATA / "leaderboards.json"
PRO_MATCHES_PATH = DATA / "pro_matches.json"
STREAMERS_PATH = DATA / "streamers.json"
@@ -346,6 +347,32 @@ def load_patches() -> dict:
}
def load_patch_summaries() -> dict:
"""Hand-authored patch readings (see web/data/patch_summaries.json).
Returns {by_version: {version: summary}}; empty when missing/invalid.
Only the latest version is expected to have an entry.
"""
empty: dict = {"by_version": {}}
if not PATCH_SUMMARIES_PATH.is_file():
return empty
try:
raw = json.loads(PATCH_SUMMARIES_PATH.read_text(encoding="utf-8"))
except (OSError, json.JSONDecodeError):
return empty
if not isinstance(raw, dict):
return empty
by_version = raw.get("by_version")
if not isinstance(by_version, dict):
return empty
out: dict[str, dict] = {}
for ver, cell in by_version.items():
if not isinstance(ver, str) or not isinstance(cell, dict):
continue
out[ver] = cell
return {"by_version": out}
def load_leaderboards() -> dict:
"""Valve Immortal division Top 100 (see fetch_leaderboards.py). Preview only."""
empty: dict = {
@@ -627,10 +654,14 @@ def ensure_ability_icon(key: str) -> Path | None:
try:
data = http_bytes(ABILITY_ICON_URL.format(key=key), timeout=30)
if not data or len(data) < 32:
return None
raise OSError("empty ability icon")
dest.write_bytes(data)
return dest
except (urllib.error.URLError, TimeoutError, OSError):
# CDN 404s are common for innates; serve the bundled badge when present.
innate = ABILITY_ICONS / f"{INNATE_ICON_NAME}.png"
if innate.is_file() and innate.stat().st_size >= 32:
return innate
return None
@@ -724,6 +755,7 @@ def build_payload() -> dict:
shop = load_item_shop()
items_meta = load_items_meta_index()
patches_data = load_patches()
patch_summaries = load_patch_summaries()
leaderboards = load_leaderboards()
pro_matches = load_pro_matches()
streamers = load_streamers()
@@ -759,6 +791,7 @@ def build_payload() -> dict:
"patches": patches_data.get("patches") or [],
"patch_lookup": patches_data.get("lookup") or {},
"patch_details": patches_data.get("details") or {},
"patch_summaries": patch_summaries,
"leaderboards": leaderboards,
"pro_matches": pro_matches,
"streamers": streamers,
@@ -801,6 +834,7 @@ def build_payload() -> dict:
"shop_items": len(shop.get("items") or {}),
"patches": len(patches_data.get("patches") or []),
"patch_details": len(patches_data.get("details") or {}),
"patch_summaries": len(patch_summaries.get("by_version") or {}),
"leaderboard_regions": len(leaderboards.get("regions") or {}),
"pro_match_players": len(pro_matches.get("by_pro") or {}),
"pro_match_heroes": len(pro_matches.get("by_hero") or {}),