vosonandCursor d6f7c3f0f5 Keep shop/item meta in git and fix category icon filenames.
Drop the accidental itemcat_*.png.png downloads by stripping the
existing .png suffix before download_icons appends one. Leave daily
leaderboards/streamers and ignored OpenDota/STRATZ caches uncommitted.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-31 10:36:26 +08:00

上分帝(Climperor

Dota 2 天梯选将识别:从选人 / 决策时间截图中识别双方 10 个英雄。

本地、离线、秒级;模板来自 Steam CDN 官方头像。GSI 只负责触发时机,阵容靠视觉读顶栏。

架构与方案背景见 ARCHITECTURE.md;上分帝 Web 视觉设计规范见 DESIGN.md;协作约定见 AGENTS.md;变更记录见 CHANGELOG.md

原理

决策时间截图(PNG)
  → 按相对坐标裁出 10 个头像格(分辨率无关)
  → 缩放到统一尺寸;天梯局自动屏蔽段位条/勋章遮挡区
  → 与 CDN 模板做归一化相关匹配(TM_CCOEFF_NORMED
  → Top-1 分数 + Top1Top2 分差双阈值 → hero_key 或 null

皮肤差异靠会话策略处理(选人阶段多帧确认,决策阶段禁止改判),不堆皮肤模板。

安装

pip install -r pc/requirements.txt
python pc/fetch_cdn_templates.py   # 一次性:shared/data/heroes.json(含基础属性/血蓝)+ pc/templates/cdn/
# 可选:从选将笔记 xlsx 导入定性克制/搭档
# python shared/import_relations_xlsx.py path\to\dota2.xlsx

仓库为 monorepopc/(局内选将识别)、web/(上分帝 Web)、shared/(共享包)。脚本一律在仓库根执行,如 python pc/gsi_watch.pypython web/serve_relations.py

上分帝 Web(游戏外)

上分帝 Web 是独立子项目(web/):英雄克制/搭档、排行、主播、物品、版本等内容站点,与局内选将识别解耦。

python web/fetch_hero_portraits.py  # 官网竖版头像 → web/assets/hero_portraits/
python web/fetch_hero_items.py      # OpenDota 热度 + Valve 中文名 + Steam 图标 → web/data/hero_items.json
python web/fetch_hero_stats.py      # OpenDota 各段位胜率/场次 → web/data/hero_stats.jsonWeb 走势兜底)
python web/fetch_stratz_meta.py     # STRATZ 周胜率/分路/对位 Top → web/data/(需 token;对位默认全量刷新,--resume-matchups 仅续跑)
python web/fetch_hero_matches.py    # 同英雄近期比赛出装/加点(含购买时间)→ web/data/hero_matches.json
# 既有行补时间:python web/fetch_hero_matches.py --enrich-item-times [--heroes juggernaut]
python web/fetch_pro_matches.py    # 明星选手近期联赛/锦标赛 → web/data/pro_matches.json(默认 watchlist
# 含天梯:python web/fetch_pro_matches.py --include-pubs --limit 8
# 指定选手(会整文件覆盖):python web/fetch_pro_matches.py --players Ame,Yatoro --limit 12
python web/fetch_item_shop.py       # 官网商店 11 列目录 → web/data/item_shop.json
python web/fetch_items_meta.py      # 装备描述 + 机制标签 → web/data/items_meta.json
python web/fetch_hero_abilities.py  # 英雄技能 / 驱散汇总 → web/data/hero_abilities.json
python web/fetch_ability_videos.py  # 官网技能演示 webm(限速)→ web/assets/ability_videos/
python web/fetch_item_counter_stats.py # OpenDota 对阵购买率/胜率相对全局基线 → 可再生成缓存
python web/item_fears.py            # 规则推导「怕的装备」→ web/data/hero_item_fears.json
python web/fetch_patches.py        # 近一年版本日志 + 详情 → web/data/patches.json(版本页)
python web/fetch_leaderboards.py   # Valve Immortal 四区 Top100 → web/data/leaderboards.json(排行页)
python web/fetch_streamers.py      # 抖音主播主页补全 → web/data/streamers.json(主播页;手工名单)
python web/fetch_streamer_live.py  # 探测真实在播状态 → is_live/live_probed_at(主播页角标;--dry-run 只打印)
python web/serve_relations.py       # 本地开发服务 http://127.0.0.1:8765(改 JSON 后刷新)

Web 站点带 History 路径路由/heroes/axe/core/heroes/axe/trends/heroes/axe/matchups/heroes/axe/matches/trends/legend/rankings/matches(明星比赛,可 /matches/898754153/matches?origin=china/matches?page=2)、/items/black_king_bar/patches/7.41/patches = 最新,/rankings = 中国区);英雄页的定位标签筛选与搜索框也进 URL(?tags=核心&q=axe)。旧 #/... 书签会自动迁移到路径。刷新保状态、可分享 / 深度链接、浏览器前进后退还原。导出时预渲染英雄/机制等页并生成 sitemap.xmlSEO);Cloudflare Pages 用 _redirects 做 SPA fallback。

部署到 Cloudflare Pages

python web/deploy_relations.py
# 串联:导出 → 资产完整性预检 → wrangler 直传 → 绑定自定义域名
# 凭据经 keyzoo 注入(refining/cloudflare 的 Global API Key);
# 或手动设 $env:CLOUDFLARE_EMAIL / $env:CLOUDFLARE_API_KEY 后运行
# 默认项目 climperor-relations、域名 dota2.refining.dev(可用 --project-name / --domain 覆盖)
# 技能演示视频与静态图标默认走阿里云 OSS 桶 climperorPages 只部署 ~2MB HTML/JS/data
python web/_oss_static_assets.py upload   # patch/新英雄后同步图标到 OSS
python web/_cf_status.py    # 部署后只读核对(项目 / 部署 / 域名状态)

定时刷新(Gitea Actions

仓库 .gitea/workflows/self-hosted runner(与 site-traffic-notify 同一 Mac mini)上分层拉数;web/refresh_web.py 对 JSON 做忽略纯时间戳的业务摘要,有业务变更才 _oss_static_assets.py upload + deploy_relations.pyweb/refresh_cache.py 在发布锁内通过 runner 持久目录恢复/保存全部定时数据与补丁状态,Actions cache 另作冷启动备份;daily/weekly 串行,patch 与全量刷新冲突时延后到下一轮。数据-only 刷新 bump SITE_VERSIONdata.json / index.html 已不缓存)。

Workflow 时间(CST 档位 拉取内容
web-daily 每天 06:00 daily hero_statsleaderboardshero_matchesleague)、pro_matchesstreamers(抖音粉丝等,失败保留旧值);再做 patch check
web-weekly 周二 07:00 weekly stratz_metahero_itemsitems_meta、OpenDota 对阵装备证据、item_fears
web-patch 每 6 小时 patch fetch_patches.py --check;仅 has_new 时拉详情 + abilities/商店/items_meta/fears

版本检测: --check 只请求 Valve 版本列表,与本地 web/data/patches.jsondetails 比对,stdout 一行 JSONhas_new / new_versions / latest)。无新版本则跳过详情与部署。

可靠性与验收: 每次刷新都会把 REFRESH_SUMMARY 写入 web/.refresh/summary.json 并上传为 Actions artifact,记录步骤、耗时、变更、 部署和 stale 状态。飞书日报同时校验 Actions 摘要、生产 data.json 新鲜度及 /api/live-status;部署后会验证本次 refresh_run_id、英雄数据和 Pages Function。STRATZ / OpenDota 异常空响应不会覆盖上一份有效缓存,OSS 资产未同步时 也不会继续发布 Pages。

本机试跑:

pip install -r web/requirements.txt
python web/fetch_patches.py --check
python web/refresh_web.py --tier patch --dry-run
python web/refresh_web.py --tier patch --skip-deploy --skip-oss
# 真实拉数+部署(需 CF / OSS env 或 keyzoo):
# python web/refresh_web.py --tier daily

Runner 要求: python3(或 python)、node/npxwrangler)、出网访问 OpenDota / STRATZ / Valve / Cloudflare / 阿里云 OSS。

Gitea Secrets(仓库 Settings → Actions → Secrets):

Secret 用途
CLOUDFLARE_EMAIL / CLOUDFLARE_API_KEY 部署 Pages(与 site-traffic-notify 共用;可用 web/_gitea_actions_secrets.py 写入)
STRATZ_API_TOKEN web-weekly(及 daily 内若触发连带时不需要)
OSS_ACCESS_KEY_ID / OSS_ACCESS_KEY_SECRET 图标变更时上传 OSS

手工数据(shared/data/relations.json、overrides)与技能视频(GB 级)进定时;新英雄仍需本机 pc/fetch_cdn_templates.py + web/fetch_hero_portraits.py。冒烟:先对 web-daily、再对 web-patchRun workflow;第二次无业务变化时应显示 deployed=false,且生产探针无超时项。

仿局内选将四列网格(图标来自 dota2.com/heroes 竖版裁切):点英雄高亮克制 / 被克制 / 搭档,下方显示定位,并以标签页切换 技能(可点选魔晶/神杖升级与天赋树)、核心装备被克装备走势(STRATZ 优先:勋章条 + 最近 1 周三卡 + 分路 + 近 8 周列表;OpenDota 兜底)、对位STRATZ 克制/被克/搭档 Top)、近期比赛(终局出装/加点)、主播(常玩该英雄的收录主播)、版本变更(该英雄近一年各版本改动)。 英雄页上方网格固定高度;点选技能 / 核心装备 / 被克装备后,详情显示在下方(装备含合成,非弹窗)。

顶部另有 排行 页:Valve Immortal 四区榜各 Top 100(中国 / 欧洲 / 美洲 / 东南亚);数据来自 web/data/leaderboards.jsonpython web/fetch_leaderboards.py)。 顶部另有 主播 页:手工收录直播间/主页(抖音 + B 站);列表先按是否在播、再按粉丝数降序;抖音式卡片布局为头像行(头像 | 昵称 + 抖音号/获赞/粉丝或 B 站 UID/关注/粉丝 |「关注」)+ 签名独立全宽行(最长 3 行)+ 常用英雄 + 可选精选高光视频(视口分档:远处不拉、近处 metadata、滚入中部 canplay 静音自动播放,同时仅 1 路全量缓冲;同名 JPG 封面作占位;有 live_url 时点头像进直播间;在播时粉环 +「直播」角标叠在环底、无间距——线上由访客触发的 /api/live-status 边缘函数刷新(缓存 5 分钟合并请求),本地 serve_relations.py 同步真实探测(内存缓存 60s);角标只信本轮成功探测,stale/失败不沿用旧「直播中」;data.json 内 daily 探测值仅作接口返回前的首屏兜底);数据来自 web/data/streamers.jsonpython web/fetch_streamers.py 补全;亦由 web/refresh_web --tier daily 每日软失败刷新)。视频与封面放 web/assets/streamer_videos/gitignore),本地经 /streamer-video/ 提供,部署前用 web/_oss_static_assets.py upload 同步至 OSS streamer-video/。 顶部另有 比赛 页(/matches[/account_id][?origin=pro|china][&page=N],默认全部类型、每页 20 场):侧栏可筛职业/国服与选手;明星选手 watchlist 近期联赛/锦标赛与天梯对局(出装/加点;卡片标职业/天梯/国服);名单见 web/data/pro_player_watchlist.json,数据由 python web/fetch_pro_matches.py --include-pubs 拉取(亦进 daily)。 顶部另有 走势 页:近 8 周各段位高胜率 / 上场率榜(/trends[/bracket];STRATZ 周胜率数据)。 顶部另有 物品 页:对齐 官网商店物品11 列竖排(基础分类 5 列 + 合成分类 6 列),点选后详情显示在右侧(含合成组件)。目录来自 web/data/item_shop.jsonpython web/fetch_item_shop.py)。 顶部另有 版本 页:默认展示最新版本完整改动(综合 / 物品 / 中立物品 / 英雄),下拉切换近一年其它版本;数据来自 web/data/patches.jsonpython web/fetch_patches.py,近一年窗口可用 --days / --since 调)。 数据在 shared/data/relations.json(英雄关系,不用胜率);选将全网格「克/搭/补」推荐读同一份文件,并用 pc/draft_archetypes.py 规则识别推进/全球流与敌我缺口(不接 AI)。热门装备来自 OpenDota 统计。走势优先 STRATZweb/data/stratz_hero_meta.json,按勋章近 8 周 + 最近 1 周分路;python web/fetch_stratz_meta.py,需 token),OpenDota 各段位胜率/上场率/场次在 web/data/hero_stats.json(近约 7 天兜底;不进推荐)。对位数值 Top 仅用 web/data/stratz_matchup_tops.json(STRATZ 全局聚合相对优势,非走势页段位/周口径;与网格定性克/搭分开展示)。被克装备以描述标签 + 规则为主;web/fetch_item_counter_stats.py 另算敌方终局装备相对同装备全局基线的购买率提升/胜率差,对全部英雄的已有候选小幅调序;装备卡有数据时显示对阵该英雄的敌方队伍终局装备出现率。统计显著的新组合仍须确认机制成立后写入 web/data/hero_fear_overrides.json,避免“高相关但不克制”的误报。可改 web/data/item_tag_overrides.json 后重跑 web/fetch_items_meta.pyweb/item_fears.py尚无对局内出装推荐

快速开始(GSI 自动跟踪)

python pc/gsi_setup.py             # 写入 Dota GSI 配置
# Steam → Dota 2 → 属性 → 启动项加上 -gamestateintegration,重启游戏
python pc/gsi_watch.py

进入英雄选择后自动跟踪;敌方锁 ≥1 人后按关系与阵容画像在英雄网格标出 克 / 搭 / 补,并显示一句阵容分析(需 shared/data/relations.json;定位局按分路过滤)。凑齐 10 人或超时后写出 pc/results/draft_<时间戳>.json(该目录已 gitignore)。 会话截图按对局落在 pc/samples/raw/<matchid>/;默认同时把每包 GSI JSON 追加到同目录 gsi.jsonl--no-dump-gsi 可关)。

主菜单收不到 GSI 是正常的:客户端第一次载入对局后才开始推送。看到 [gsi] connected 才算链路通。

手动流程

截图

python pc/capture.py                # 单张 → pc/samples/raw/
python pc/capture.py --loop 300 3   # 每 3 秒一张,持续 300 秒

GSI 跟踪时自动写入 pc/samples/raw/<matchid>/draft_*.png(含 milestone / best)。 需无边框窗口或窗口模式(独占全屏可能黑帧)。

标定 ROI(一次性)

python pc/autocalibrate.py pc/samples/raw/<matchid>/<某帧>.png
# 失败时退路:python pc/calibrate.py pc/samples/raw/<matchid>/<某帧>.png

识别 / 评测

python pc/recognize.py pc/samples/raw/<matchid>/<>.png
python pc/recognize.py pc/samples/raw/<matchid>/<>.png --sheet
python pc/recognize.py pc/samples/raw/<matchid>/<>.png --truth hero1,...,hero10
python pc/evaluate.py              # 按 pc/samples/labels.json 批量评测

辅助脚本:pc/roles.py(位置字)、shared/grid.py(禁用名单)、pc/modes.py(模式字)。

截图要求

  • PNG、原始分辨率(勿经聊天工具压缩)
  • 顶栏 10 个英雄完整可见
  • 优先选人阶段默认脸帧(全员选完后顶栏才会换皮肤;GSI 无「皮肤已加载」信号)

已知限制

  • 宽高比差异大(如 21:9)时可能需重标定
  • 客户端改网格排序后 grid.pyok=False,不会瞎报禁用名单
  • 新英雄上线后重跑 fetch_cdn_templates.py(不在定时任务内);版本列表由 Gitea web-patch 自动检测,有更新才拉 fetch_patches.py 详情
  • 位置文字模板取自简体中文客户端
  • 决策界面 3D 模型挡住顶栏时单帧会很弱,依赖会话多帧回填
  • 普通玩家 GSI 只有自己的英雄 id,不能代替读顶栏

相关项目

成熟后可移植到 dota2-hex 或独立发布。合规边界:只使用屏幕可见信息,不读内存、不注入。

S
Description
Dota 2 天梯选将识别:GSI 触发 + 顶栏模板匹配 + 关系预览
Readme
16 MiB
Languages
Python 65.2%
JavaScript 25.8%
CSS 8%
HTML 0.9%