diff --git a/eval/role_topology/absjudge.py b/eval/role_topology/absjudge.py index e84ebc3b..362c9a1e 100644 --- a/eval/role_topology/absjudge.py +++ b/eval/role_topology/absjudge.py @@ -169,6 +169,7 @@ def build_unit(u: dict) -> tuple[str, dict]: kinds: dict[str, str] = {a: "боевой" for a in slots} clean = DECOY_HOOK(slots) if DECOY_HOOK is not None else (slots.get("A") or slots["A_law"]) + decoy_from = next((a for a, t in slots.items() if t == clean), "") planted, n = _plant(clean) if n >= 2: slots["CTRLdecoy"], kinds["CTRLdecoy"] = planted, "контроль-декой" @@ -206,8 +207,12 @@ def build_unit(u: dict) -> tuple[str, dict]: "", "=" * 60, "ИСХОДНИК:", u["source"]] for n in order: body += ["", "=" * 60, f"{label[n]}:", slots[n]] + # ⚠ `decoy_from` пишется В КЛЮЧ. Без него контроль декоя считался ПРОТИВ ЖЁСТКО ЗАШИТОГО арма + # `A`, которого у другой панели нет, и обязательный контроль молча не считался вовсе + # (поймано эксп-22 чтением контролей перед боевыми числами). key = {label[n]: dict(arm=n, kind=kinds[n], uid=u["uid"], - sig=hashlib.sha1(slots[n].encode()).hexdigest()[:12]) # noqa: S324 + sig=hashlib.sha1(slots[n].encode()).hexdigest()[:12], # noqa: S324 + **({"decoy_from": decoy_from} if n == "CTRLdecoy" else {})) for n in order} return "\n".join(body), key @@ -617,8 +622,10 @@ def controls() -> None: print("голосов нет — сначала --ingest") return print("КОНТРОЛИ САМОГО СУДЬИ — читать ДО боевых чисел\n") - dec = [d["A"]["errors"] - d["CTRLdecoy"]["errors"] - for d in data.values() if "CTRLdecoy" in d and "A" in d] + # База декоя берётся ИЗ КЛЮЧА (`decoy_from`), а не из имени арма: панель у каждого пака своя. + dec = [d[d["CTRLdecoy"].get("decoy_from", "A")]["errors"] - d["CTRLdecoy"]["errors"] + for d in data.values() + if "CTRLdecoy" in d and d["CTRLdecoy"].get("decoy_from", "A") in d] if dec: print(f" ДЕКОЙ посаженная деградация против чистого: {st.mean(dec):+.2f} " f"· поймано {sum(1 for x in dec if x < 0)}/{len(dec)} (нужен отрицательный знак)")