Mechanisches Boardstärke-Modell: Statsheet aus cdragon-Exaktdaten, sqrt(eHP×DPS), Tier-Anker, Tau-Fit
A/B auf 38 Holdout-Matches: mechanical 0.724 vs legacy 0.695. Promotion durchgeführt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -3,8 +3,67 @@
|
||||
Fails loudly on missing keys — never guess through structure drift.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
CDRAGON_GAME = "https://raw.communitydragon.org/latest/game"
|
||||
|
||||
DAMAGE_TAG_RE = re.compile(
|
||||
r"<(magicDamage|physicalDamage|trueDamage)>(.*?)</\1>", re.S
|
||||
)
|
||||
VAR_RE = re.compile(r"@([A-Za-z0-9_]+?)(?:\*[\d.]+)?@")
|
||||
# Reihenfolge = Priorität; ADDamage/APDamage werden gesondert addiert.
|
||||
DAMAGE_FALLBACKS = (
|
||||
"Damage", "DamageAP", "DamageAD", "SpellDamage",
|
||||
"DamagePerSecond", "TrueDamagePerSecond", "BonusDamageOnAttack",
|
||||
)
|
||||
TAG_TO_TYPE = {"magicDamage": "magic", "physicalDamage": "physical", "trueDamage": "true"}
|
||||
|
||||
|
||||
def resolve_spell(ability: dict) -> dict:
|
||||
"""Spell-Schaden pro Stern aus Desc-Markup + Variablen auflösen.
|
||||
|
||||
Stern-Konvention der Arrays: value[1..3] = 1-3 Sterne (verifiziert).
|
||||
"""
|
||||
desc = ability.get("desc") or ""
|
||||
variables = {v["name"]: v["value"] for v in ability.get("variables", [])
|
||||
if v.get("value")}
|
||||
|
||||
def get(name: str):
|
||||
val = variables.get(name)
|
||||
return val if val and any(val) else None
|
||||
|
||||
dmg_type = None
|
||||
array = None
|
||||
for m in DAMAGE_TAG_RE.finditer(desc):
|
||||
for vm in VAR_RE.finditer(m.group(2)):
|
||||
name = vm.group(1)
|
||||
array = get(name) or get(name.removeprefix("Modified"))
|
||||
if array:
|
||||
dmg_type = TAG_TO_TYPE[m.group(1)]
|
||||
break
|
||||
if array:
|
||||
break
|
||||
|
||||
if array is None:
|
||||
ad, ap = get("ADDamage"), get("APDamage")
|
||||
if ad and ap:
|
||||
array = [a + b for a, b in zip(ad, ap)]
|
||||
else:
|
||||
for name in DAMAGE_FALLBACKS:
|
||||
array = get(name)
|
||||
if array:
|
||||
break
|
||||
|
||||
has_ap = "%i:scaleAP%" in desc
|
||||
has_ad = "%i:scaleAD%" in desc
|
||||
scaling = "both" if has_ap and has_ad else "ap" if has_ap else "ad" if has_ad else None
|
||||
|
||||
return {
|
||||
"spell_damage": array,
|
||||
"spell_damage_type": dmg_type or ("magic" if array else None),
|
||||
"spell_scaling": scaling,
|
||||
}
|
||||
|
||||
|
||||
def icon_url(asset_path: str) -> str:
|
||||
p = asset_path.lower()
|
||||
@@ -39,10 +98,8 @@ def parse(raw: dict, set_override: int | None = None) -> dict:
|
||||
"role": c.get("role"),
|
||||
"stats": c["stats"],
|
||||
"ability_name": c["ability"]["name"],
|
||||
"ability_variables": {
|
||||
v["name"]: v["value"] for v in c["ability"]["variables"]
|
||||
},
|
||||
"icon": icon_url(c["squareIcon"]),
|
||||
**resolve_spell(c["ability"]),
|
||||
}
|
||||
|
||||
traits = {}
|
||||
@@ -52,7 +109,8 @@ def parse(raw: dict, set_override: int | None = None) -> dict:
|
||||
"api_name": t["apiName"],
|
||||
"name": t["name"],
|
||||
"breakpoints": [
|
||||
{"min_units": e["minUnits"], "style": e["style"]}
|
||||
{"min_units": e["minUnits"], "style": e["style"],
|
||||
"variables": e.get("variables") or {}}
|
||||
for e in t["effects"]
|
||||
],
|
||||
"icon": icon_url(t["icon"]),
|
||||
@@ -74,6 +132,7 @@ def parse(raw: dict, set_override: int | None = None) -> dict:
|
||||
"api_name": api,
|
||||
"name": i["name"],
|
||||
"composition": i["composition"],
|
||||
"effects": i.get("effects") or {},
|
||||
"icon": icon_url(i["icon"]) if i["icon"] else None,
|
||||
}
|
||||
if "Augment" in api:
|
||||
|
||||
Reference in New Issue
Block a user