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
File diff suppressed because it is too large Load Diff
+68
View File
@@ -0,0 +1,68 @@
{
"meta": {
"note": "Manual hero→item fears merged in item_fears.py (add/remove). Use for iconic counters auto-rules miss."
},
"heroes": {
"juggernaut": {
"add": [
{ "item": "lotus_orb", "reason": "反射无敌斩/迅捷斩", "tags": ["spell_reflect"] },
{ "item": "abyssal_blade", "reason": "打断输出与无敌斩", "tags": [] },
{ "item": "butterfly", "reason": "闪避克制普攻与斩击", "tags": [] }
],
"remove": []
},
"phantom_assassin": {
"add": [
{ "item": "monkey_king_bar", "reason": "无视模糊闪避", "tags": ["true_strike"] }
],
"remove": []
},
"lina": {
"add": [
{ "item": "lotus_orb", "reason": "反射神灭斩", "tags": ["spell_reflect"] },
{ "item": "black_king_bar", "reason": "魔免克制爆发", "tags": ["magic_immune"] },
{ "item": "sphere", "reason": "格挡神灭斩", "tags": ["spell_block"] }
],
"remove": []
},
"lion": {
"add": [
{ "item": "lotus_orb", "reason": "反射死亡一指", "tags": ["spell_reflect"] }
],
"remove": []
},
"necrolyte": {
"add": [
{ "item": "lotus_orb", "reason": "反射死神镰刀", "tags": ["spell_reflect"] }
],
"remove": []
},
"axe": {
"add": [
{ "item": "lotus_orb", "reason": "反射淘汰之刃", "tags": ["spell_reflect"] },
{ "item": "eternal_shroud", "reason": "抵消旋转魔法伤害", "tags": [] }
],
"remove": []
},
"legion_commander": {
"add": [
{ "item": "lotus_orb", "reason": "反射决斗", "tags": ["spell_reflect"] },
{ "item": "ethereal_blade", "reason": "虚无无法被决斗普攻", "tags": [] },
{ "item": "ghost", "reason": "虚无无法被决斗普攻", "tags": [] }
],
"remove": []
},
"bane": {
"add": [
{ "item": "lotus_orb", "reason": "反射虚弱/噩梦/蚀脑", "tags": ["spell_reflect"] }
],
"remove": []
},
"doom_bringer": {
"add": [
{ "item": "lotus_orb", "reason": "反射末日", "tags": ["spell_reflect"] }
],
"remove": []
}
}
}
+138
View File
@@ -0,0 +1,138 @@
{
"_comment": "CN in-client order: Unicode code-point of name_loc within each attr.",
"str": [
"elder_titan",
"undying",
"shredder",
"omniknight",
"legion_commander",
"skeleton_king",
"phoenix",
"centaur",
"rattletrap",
"huskar",
"life_stealer",
"earth_spirit",
"abyssal_underlord",
"tiny",
"tusk",
"pudge",
"earthshaker",
"axe",
"slardar",
"sven",
"kunkka",
"night_stalker",
"largo",
"doom_bringer",
"treant",
"chaos_knight",
"tidehunter",
"alchemist",
"lycan",
"primal_beast",
"mars",
"dawnbreaker",
"spirit_breaker",
"bristleback",
"ogre_magi",
"dragon_knight"
],
"agi": [
"juggernaut",
"clinkz",
"viper",
"kez",
"riki",
"drow_ranger",
"morphling",
"templar_assassin",
"vengefulspirit",
"naga_siren",
"troll_warlord",
"phantom_assassin",
"phantom_lancer",
"spectre",
"nevermore",
"terrorblade",
"antimage",
"slark",
"hoodwink",
"ember_spirit",
"ursa",
"sniper",
"lone_druid",
"gyrocopter",
"mirana",
"meepo",
"weaver",
"medusa",
"broodmother",
"faceless_void",
"bloodseeker",
"bounty_hunter",
"razor",
"luna",
"monkey_king"
],
"int": [
"tinker",
"keeper_of_the_light",
"skywrath_mage",
"grimstroke",
"zuus",
"winter_wyvern",
"witch_doctor",
"lich",
"puck",
"pugna",
"disruptor",
"leshrac",
"rubick",
"shadow_demon",
"shadow_shaman",
"warlock",
"jakiro",
"obsidian_destroyer",
"crystal_maiden",
"silencer",
"muerta",
"queenofpain",
"necrolyte",
"ringmaster",
"invoker",
"oracle",
"lina",
"lion",
"ancient_apparition",
"dark_willow",
"chen",
"storm_spirit",
"enchantress",
"dark_seer"
],
"all": [
"abaddon",
"beastmaster",
"venomancer",
"nyx_assassin",
"arc_warden",
"techies",
"dazzle",
"death_prophet",
"sand_king",
"marci",
"snapfire",
"pangolier",
"bane",
"visage",
"furion",
"wisp",
"void_spirit",
"batrider",
"enigma",
"brewmaster",
"windrunner",
"magnataur"
]
}
File diff suppressed because it is too large Load Diff
+6801
View File
File diff suppressed because it is too large Load Diff
+5608
View File
File diff suppressed because it is too large Load Diff
+4608
View File
File diff suppressed because it is too large Load Diff
+42
View File
@@ -0,0 +1,42 @@
{
"meta": {
"note": "Manual add/remove tags merged after auto-tagging in fetch_items_meta.py. Prefer offensive mechanics for fear mapping."
},
"items": {
"diffusal_blade": { "add": ["basic_dispel", "mana_burn"], "remove": ["illusion_clear"] },
"disperser": { "add": ["basic_dispel", "mana_burn"], "remove": ["illusion_clear"] },
"nullifier": { "add": ["basic_dispel"], "remove": [] },
"cyclone": { "add": ["basic_dispel"], "remove": [] },
"wind_waker": { "add": ["basic_dispel"], "remove": [] },
"satanic": { "add": [], "remove": ["basic_dispel"] },
"lotus_orb": { "add": ["spell_reflect"], "remove": ["basic_dispel", "mana_burn"] },
"guardian_greaves": { "add": [], "remove": ["basic_dispel"] },
"manta": { "add": [], "remove": ["basic_dispel", "illusion_clear"] },
"black_king_bar": { "add": ["magic_immune"], "remove": ["basic_dispel", "strong_dispel"] },
"aeon_disk": { "add": [], "remove": ["strong_dispel", "basic_dispel"] },
"sphere": { "add": ["spell_block"], "remove": [] },
"monkey_king_bar": { "add": ["true_strike"], "remove": [] },
"silver_edge": { "add": ["break", "invis_break"], "remove": [] },
"spirit_vessel": { "add": ["heal_reduce"], "remove": [] },
"bfury": { "add": ["illusion_clear"], "remove": [] },
"radiance": { "add": ["illusion_clear"], "remove": [] },
"maelstrom": { "add": ["illusion_clear"], "remove": ["true_strike"] },
"mjollnir": { "add": ["illusion_clear"], "remove": ["true_strike"] },
"gungir": { "add": ["root", "illusion_clear"], "remove": [] },
"gem": { "add": ["invis_detect"], "remove": [] },
"dust": { "add": ["invis_detect"], "remove": [] },
"desolator": { "add": ["armor_reduce"], "remove": [] },
"solar_crest": { "add": ["armor_reduce"], "remove": [] },
"orchid": { "add": ["silence"], "remove": ["mana_burn"] },
"bloodthorn": { "add": ["silence", "true_strike"], "remove": [] },
"sheepstick": { "add": ["hex"], "remove": ["illusion_clear", "silence"] },
"rod_of_atos": { "add": ["root"], "remove": [] },
"mask_of_madness": { "add": [], "remove": ["silence"] },
"necronomicon": { "add": ["invis_detect"], "remove": ["basic_dispel", "mana_burn", "break"] },
"necronomicon_2": { "add": ["invis_detect"], "remove": ["basic_dispel", "mana_burn", "break"] },
"necronomicon_3": { "add": ["invis_detect"], "remove": ["basic_dispel", "mana_burn", "break"] },
"witch_blade": { "add": [], "remove": ["true_strike"] },
"devastator": { "add": ["armor_reduce"], "remove": ["true_strike"] },
"essence_distiller": { "add": [], "remove": ["invis_detect"] }
}
}
+2061
View File
File diff suppressed because it is too large Load Diff
+32219
View File
File diff suppressed because it is too large Load Diff
+845
View File
@@ -0,0 +1,845 @@
{
"version": 1,
"meta": {
"source": "dota2.xlsx",
"imported_at": "2026-07-26T07:15:47.770566+00:00",
"note": "qualitative counters/synergies; no winrate",
"patched_at": "2026-07-26T10:15:25.180740+00:00",
"patch_source": "data/relations_patch_draft.json",
"patch_note": "added cross-source counters; kept existing edges; no reverses",
"patch_added": 100,
"patch_skipped_dup": 0,
"cleaned_at": "2026-07-26T18:31:42.825338+00:00",
"cleaned_note": "removed numeric-reason counters (opendota_adv/stratz_adv winrate injected by relations_patch_draft.json; violated no-winrate contract)",
"cleaned_removed": 100
},
"counters": [
{
"a": "abaddon",
"b": "axe",
"reason": ""
},
{
"a": "abaddon",
"b": "legion_commander",
"reason": ""
},
{
"a": "abaddon",
"b": "life_stealer",
"reason": ""
},
{
"a": "abaddon",
"b": "slark",
"reason": ""
},
{
"a": "abyssal_underlord",
"b": "luna",
"reason": ""
},
{
"a": "abyssal_underlord",
"b": "riki",
"reason": ""
},
{
"a": "abyssal_underlord",
"b": "spirit_breaker",
"reason": ""
},
{
"a": "antimage",
"b": "lina",
"reason": ""
},
{
"a": "antimage",
"b": "medusa",
"reason": ""
},
{
"a": "antimage",
"b": "queenofpain",
"reason": ""
},
{
"a": "antimage",
"b": "skeleton_king",
"reason": ""
},
{
"a": "antimage",
"b": "sniper",
"reason": ""
},
{
"a": "antimage",
"b": "storm_spirit",
"reason": ""
},
{
"a": "axe",
"b": "antimage",
"reason": ""
},
{
"a": "axe",
"b": "drow_ranger",
"reason": "反击螺旋克敏核"
},
{
"a": "axe",
"b": "juggernaut",
"reason": "反击螺旋克敏核"
},
{
"a": "axe",
"b": "phantom_assassin",
"reason": "反击螺旋克敏核"
},
{
"a": "axe",
"b": "phantom_lancer",
"reason": ""
},
{
"a": "bloodseeker",
"b": "dark_seer",
"reason": ""
},
{
"a": "bloodseeker",
"b": "night_stalker",
"reason": ""
},
{
"a": "bloodseeker",
"b": "riki",
"reason": ""
},
{
"a": "bloodseeker",
"b": "shredder",
"reason": ""
},
{
"a": "bloodseeker",
"b": "slark",
"reason": ""
},
{
"a": "centaur",
"b": "phantom_lancer",
"reason": ""
},
{
"a": "dark_seer",
"b": "batrider",
"reason": ""
},
{
"a": "dark_seer",
"b": "doom_bringer",
"reason": ""
},
{
"a": "dark_seer",
"b": "luna",
"reason": ""
},
{
"a": "dark_seer",
"b": "templar_assassin",
"reason": ""
},
{
"a": "dark_willow",
"b": "spirit_breaker",
"reason": ""
},
{
"a": "dawnbreaker",
"b": "monkey_king",
"reason": ""
},
{
"a": "dawnbreaker",
"b": "ursa",
"reason": ""
},
{
"a": "death_prophet",
"b": "snapfire",
"reason": ""
},
{
"a": "dragon_knight",
"b": "phantom_assassin",
"reason": ""
},
{
"a": "dragon_knight",
"b": "slark",
"reason": ""
},
{
"a": "drow_ranger",
"b": "juggernaut",
"reason": ""
},
{
"a": "drow_ranger",
"b": "zuus",
"reason": ""
},
{
"a": "earth_spirit",
"b": "antimage",
"reason": ""
},
{
"a": "earth_spirit",
"b": "bloodseeker",
"reason": ""
},
{
"a": "earth_spirit",
"b": "faceless_void",
"reason": ""
},
{
"a": "elder_titan",
"b": "drow_ranger",
"reason": ""
},
{
"a": "ember_spirit",
"b": "centaur",
"reason": ""
},
{
"a": "ember_spirit",
"b": "phantom_assassin",
"reason": ""
},
{
"a": "ember_spirit",
"b": "queenofpain",
"reason": ""
},
{
"a": "faceless_void",
"b": "keeper_of_the_light",
"reason": ""
},
{
"a": "faceless_void",
"b": "kez",
"reason": ""
},
{
"a": "faceless_void",
"b": "lina",
"reason": ""
},
{
"a": "furion",
"b": "shredder",
"reason": ""
},
{
"a": "huskar",
"b": "keeper_of_the_light",
"reason": ""
},
{
"a": "huskar",
"b": "riki",
"reason": ""
},
{
"a": "huskar",
"b": "snapfire",
"reason": ""
},
{
"a": "keeper_of_the_light",
"b": "necrolyte",
"reason": ""
},
{
"a": "keeper_of_the_light",
"b": "skeleton_king",
"reason": ""
},
{
"a": "keeper_of_the_light",
"b": "spectre",
"reason": ""
},
{
"a": "keeper_of_the_light",
"b": "spirit_breaker",
"reason": ""
},
{
"a": "kez",
"b": "ancient_apparition",
"reason": ""
},
{
"a": "kunkka",
"b": "luna",
"reason": ""
},
{
"a": "legion_commander",
"b": "antimage",
"reason": ""
},
{
"a": "legion_commander",
"b": "juggernaut",
"reason": ""
},
{
"a": "legion_commander",
"b": "templar_assassin",
"reason": ""
},
{
"a": "leshrac",
"b": "magnataur",
"reason": ""
},
{
"a": "lich",
"b": "dark_seer",
"reason": ""
},
{
"a": "life_stealer",
"b": "axe",
"reason": ""
},
{
"a": "life_stealer",
"b": "pudge",
"reason": ""
},
{
"a": "life_stealer",
"b": "tiny",
"reason": ""
},
{
"a": "lina",
"b": "treant",
"reason": ""
},
{
"a": "luna",
"b": "necrolyte",
"reason": ""
},
{
"a": "magnataur",
"b": "dawnbreaker",
"reason": ""
},
{
"a": "marci",
"b": "lion",
"reason": ""
},
{
"a": "mars",
"b": "drow_ranger",
"reason": ""
},
{
"a": "medusa",
"b": "bloodseeker",
"reason": ""
},
{
"a": "medusa",
"b": "doom_bringer",
"reason": ""
},
{
"a": "medusa",
"b": "marci",
"reason": ""
},
{
"a": "meepo",
"b": "riki",
"reason": ""
},
{
"a": "monkey_king",
"b": "legion_commander",
"reason": ""
},
{
"a": "naga_siren",
"b": "riki",
"reason": ""
},
{
"a": "naga_siren",
"b": "viper",
"reason": ""
},
{
"a": "necrolyte",
"b": "huskar",
"reason": ""
},
{
"a": "necrolyte",
"b": "tiny",
"reason": ""
},
{
"a": "night_stalker",
"b": "antimage",
"reason": ""
},
{
"a": "night_stalker",
"b": "faceless_void",
"reason": ""
},
{
"a": "night_stalker",
"b": "slark",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "bristleback",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "ember_spirit",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "medusa",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "obsidian_destroyer",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "storm_spirit",
"reason": ""
},
{
"a": "nyx_assassin",
"b": "tidehunter",
"reason": ""
},
{
"a": "obsidian_destroyer",
"b": "faceless_void",
"reason": ""
},
{
"a": "obsidian_destroyer",
"b": "necrolyte",
"reason": ""
},
{
"a": "pangolier",
"b": "life_stealer",
"reason": ""
},
{
"a": "phantom_assassin",
"b": "huskar",
"reason": ""
},
{
"a": "phantom_assassin",
"b": "legion_commander",
"reason": ""
},
{
"a": "phantom_assassin",
"b": "ogre_magi",
"reason": ""
},
{
"a": "phantom_assassin",
"b": "sniper",
"reason": ""
},
{
"a": "phantom_lancer",
"b": "obsidian_destroyer",
"reason": ""
},
{
"a": "phantom_lancer",
"b": "viper",
"reason": ""
},
{
"a": "phantom_lancer",
"b": "windrunner",
"reason": ""
},
{
"a": "primal_beast",
"b": "drow_ranger",
"reason": ""
},
{
"a": "primal_beast",
"b": "riki",
"reason": ""
},
{
"a": "pudge",
"b": "drow_ranger",
"reason": ""
},
{
"a": "pudge",
"b": "riki",
"reason": ""
},
{
"a": "queenofpain",
"b": "phantom_assassin",
"reason": ""
},
{
"a": "rattletrap",
"b": "centaur",
"reason": ""
},
{
"a": "razor",
"b": "dragon_knight",
"reason": ""
},
{
"a": "razor",
"b": "life_stealer",
"reason": ""
},
{
"a": "razor",
"b": "troll_warlord",
"reason": ""
},
{
"a": "riki",
"b": "antimage",
"reason": ""
},
{
"a": "skeleton_king",
"b": "bloodseeker",
"reason": ""
},
{
"a": "skeleton_king",
"b": "doom_bringer",
"reason": ""
},
{
"a": "skeleton_king",
"b": "legion_commander",
"reason": ""
},
{
"a": "slardar",
"b": "antimage",
"reason": ""
},
{
"a": "slardar",
"b": "bounty_hunter",
"reason": ""
},
{
"a": "slark",
"b": "bristleback",
"reason": ""
},
{
"a": "slark",
"b": "ember_spirit",
"reason": ""
},
{
"a": "slark",
"b": "juggernaut",
"reason": ""
},
{
"a": "slark",
"b": "lion",
"reason": ""
},
{
"a": "slark",
"b": "rubick",
"reason": ""
},
{
"a": "slark",
"b": "skywrath_mage",
"reason": ""
},
{
"a": "slark",
"b": "tidehunter",
"reason": ""
},
{
"a": "slark",
"b": "tusk",
"reason": ""
},
{
"a": "slark",
"b": "zuus",
"reason": ""
},
{
"a": "snapfire",
"b": "antimage",
"reason": ""
},
{
"a": "snapfire",
"b": "meepo",
"reason": ""
},
{
"a": "snapfire",
"b": "necrolyte",
"reason": ""
},
{
"a": "snapfire",
"b": "phantom_lancer",
"reason": ""
},
{
"a": "sniper",
"b": "drow_ranger",
"reason": ""
},
{
"a": "sniper",
"b": "necrolyte",
"reason": ""
},
{
"a": "spectre",
"b": "snapfire",
"reason": ""
},
{
"a": "spectre",
"b": "sniper",
"reason": ""
},
{
"a": "storm_spirit",
"b": "keeper_of_the_light",
"reason": ""
},
{
"a": "templar_assassin",
"b": "huskar",
"reason": ""
},
{
"a": "templar_assassin",
"b": "legion_commander",
"reason": "折射克决斗斩杀线"
},
{
"a": "templar_assassin",
"b": "skywrath_mage",
"reason": ""
},
{
"a": "terrorblade",
"b": "ancient_apparition",
"reason": ""
},
{
"a": "terrorblade",
"b": "night_stalker",
"reason": ""
},
{
"a": "terrorblade",
"b": "viper",
"reason": ""
},
{
"a": "tidehunter",
"b": "phantom_lancer",
"reason": ""
},
{
"a": "tidehunter",
"b": "puck",
"reason": ""
},
{
"a": "tidehunter",
"b": "queenofpain",
"reason": ""
},
{
"a": "troll_warlord",
"b": "ursa",
"reason": ""
},
{
"a": "undying",
"b": "juggernaut",
"reason": "魔晶墓碑克剑圣大招"
},
{
"a": "ursa",
"b": "axe",
"reason": ""
},
{
"a": "ursa",
"b": "juggernaut",
"reason": ""
},
{
"a": "ursa",
"b": "pudge",
"reason": ""
},
{
"a": "ursa",
"b": "tidehunter",
"reason": ""
},
{
"a": "viper",
"b": "ember_spirit",
"reason": ""
},
{
"a": "viper",
"b": "huskar",
"reason": ""
},
{
"a": "viper",
"b": "night_stalker",
"reason": ""
},
{
"a": "weaver",
"b": "ancient_apparition",
"reason": ""
},
{
"a": "weaver",
"b": "keeper_of_the_light",
"reason": ""
},
{
"a": "weaver",
"b": "pudge",
"reason": ""
},
{
"a": "zuus",
"b": "snapfire",
"reason": ""
},
{
"a": "zuus",
"b": "treant",
"reason": ""
},
{
"a": "zuus",
"b": "ursa",
"reason": ""
}
],
"synergies": [
{
"a": "beastmaster",
"b": "keeper_of_the_light",
"reason": ""
},
{
"a": "centaur",
"b": "dark_seer",
"reason": ""
},
{
"a": "centaur",
"b": "snapfire",
"reason": ""
},
{
"a": "death_prophet",
"b": "mars",
"reason": ""
},
{
"a": "drow_ranger",
"b": "pudge",
"reason": ""
},
{
"a": "faceless_void",
"b": "invoker",
"reason": ""
},
{
"a": "faceless_void",
"b": "skywrath_mage",
"reason": ""
},
{
"a": "kunkka",
"b": "medusa",
"reason": ""
},
{
"a": "legion_commander",
"b": "skywrath_mage",
"reason": ""
},
{
"a": "magnataur",
"b": "snapfire",
"reason": ""
},
{
"a": "mars",
"b": "snapfire",
"reason": ""
}
]
}
+8
View File
@@ -0,0 +1,8 @@
{
"meta": {
"note": "Manual name_loc overrides for talents Valve omits bonus data for (auto-resolves to '?'). Applied in fetch_hero_abilities.py talent_rows when a talent still shows '?' after bonus lookup. Value is the full final name_loc string."
},
"talents": {
"special_bonus_unique_naga_siren_reel_in_speed": "+125 捕捞速度"
}
}