Files
gplan-2/tools/clear_dominance_audit.py
T

304 lines
17 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""清图支配性对账重算工具(行动经济 条款⑤ · 挖穿残余池计价)。
判式权威 = design/gdd/systems/排雷循环/行动经济.md 条款⑤(公式本体同时内联于本 docstring):
主判式 Δ_clear = [R_res + C_eff] − [X_res + Δr × s_ref] ≥ 0
保守地板 C_eff(min) ≥ X_res + Δr × s_ref
E[踩雷] = |M| × n_s ÷ (n_s + 1) (均匀交错模型)
n_s = (1 − ι) × |S| (域守卫 n_s < 1 ⇒ 不评估)
R_res = n_s × E_dig;X_res = E[踩雷] × V_mine;Δr = ⌈(n_s + E[踩雷]) ÷ AP_r⌉
s_ref = D_ref;停手侧 RAP 机会收益按零计价(显式假设)。
本工具是难度曲线调参的常设重跑入口:
所有输入可经命令行覆盖(--d-ref --c-clear --ap-r --iota ...),
重跑时按当期 .tres 行值更新默认值即可,公式本体不动。
单雷代价 / 单格收益的当量换算严格走 战斗结算「效果档位对照表」单一口径:
flat 类 E = 表值;percent 类 E = |层数×行值|÷100 × D_ref(每结算每层);
AC 轴换算 = AC 点数 × 5pp(d20 线性)× D_ref(假设登记,见审计报告)。
【现行判式口径】
- 池权重合计 77(含 action 6 / gp 2;clear_blast 系清图槽对象不入格池)。
- 直伤雷负向三行 6/8/8(backlash/snare/recoil);
正向 pierce_strike/magic_bolt 维持 flat 2;heal_minor 2(Package B 第 2 轮)。
- 清图槽 C_MIN/C_MAX = 24/40(中点 32);AP_r = 8。
- off_guard 走 ac 轴 −1(0.2u 模型:点数×5pp×D_ref=0.2 当量/每敌拍)。
- V_mine 为九型负向字典口径(enrage/off_guard/警觉/backlash/sapped/snare/recoil/
ap_drain/gp_lose——ap_drain 按 A6 flat 1、gp_lose 按 A5 代理 2 计)。
- β 比值带判读与逐型净期望输出:
β(u)=V_mine/E_dig ∈ [1.2, 2.5];条件一 (1−ρ̂)E_dig−ρ̂V_mine ≥ 0(下限 0,上限归 β_hi);
条件二 E[标中兑付] ≥ (1+δ)·E_dig(δ=1.25 provisional,兑付溢价形态 a)。
"""
import argparse
import math
# ============ 现行值链(出处见各注释;重跑按当期 .tres 更新) ============
D_REF = 4 # settlement.tres equivalence_ref_damage
P_PERFECT = 200 # settlement.tres(percent,÷100 = ×2 双份交付)
C_MIN, C_MAX = 24, 40 # reward.tres clear_blast power_min/max;期望口径 = 中点 32
AP_R = 8 # action_economy.tres ap_per_round
DIG_COST = 1 # action_economy.tres dig_ap_cost
IOTA = 0.88 # board_gen.tres inferability_target(判式 ι 代理)
# 效果行值(status_effects.tres)
ATTACK_UP_PCT = 50 # 增伤 +50%,duration 1,hero 侧
AC_UP_POINT = 1 # 护甲强化 +1 AC,duration 1
WEAKEN_PCT = 65 # 削弱 −65%(有效拍 1:duration 2 先兑付后衰减)
ENRAGE_PCT = 25 # 暴走 +25%,enemy 侧 side-wide
SAPPED_PCT = 25 # 乏力 −25%,hero 侧
OFF_GUARD_AC = 1 # 失衡 AC −1(0.2u 模型:1 点 × 5pp × D_ref = 0.2 当量/每敌拍)
ALERT_EQUIV = 0.2 # 警觉(敌 AC+1×1拍):主角每拍恰 1 攻 × 1 拍寿命 ⇒ 0.2 当量,u 无关(假设 A4)
# 直伤/群伤 flat 值(reward.tres)
SPARK_AOE = 1 # 爆裂粉尘(全体存活敌)
HEAL_MINOR = 2 # 急救敷料(Package B 第 2 轮)
HEAL_BACKLASH = 6 # 反噬震荡(主角直伤)
PIERCE_STRIKE = 2 # 穿刺弹(单目标克制直伤,必中全额)
MAGIC_BOLT = 2 # 魔法矢(同上)
PIERCE_SNARE = 8 # 穿刺陷阱(主角直伤)
MAGIC_RECOIL = 8 # 魔法反噬(主角直伤)
AP_UP = 1 # 行动点 +1(当量 = flat 表值 1,机会成本口径更高仅登记——假设 A6)
GP_COIN = 3 # GP 袋 +3 GP(gp 当量 = flat power 代理——假设 A5,口径空白登记)
GP_LOSE = 2 # 破财 −2GP(同上代理)
# 甲效矩阵(settlement.tres,加法核点数):基础值 + 点数,damage_floor=1
ARMOR_EFFECT = { # attack_type → {armor_type: 点数};切片 L1 全轻甲 / L2 除 Boss 全轻甲
"pierce": {"light": -1, "ward": 1, "heavy": 0},
"magic": {"light": 0, "ward": -1, "heavy": 1},
"normal": {"light": 0, "ward": 1, "heavy": -1},
}
# 格面资源池正向权重(reward.tres,合计 77)
# 类型 → (权重);负向行 weight=0 不参与摇号,类型分布 = 正向权重
# clear_blast pool_tag=clear_default 系清图槽对象,不入格池(地图战斗资源.md §清图槽)。
POOL_WEIGHTS = {"attack": 14, "defense": 11, "blast": 16, "heal": 4,
"weaken": 8, "pierce": 8, "magic": 8, "action": 6, "gp": 2}
# β 比值带 [1.2, 2.5](u=3 登记态 1.17 微破下限=点名不强制回滚)
BETA_LO, BETA_HI = 1.2, 2.5
# δ 旗溢价(形态 a 兑付溢价,δ=1.25 provisional ⇒ 兑付按同型正向 ×(1+δ))
DELTA = 0.25
# 板形(|M| 幸存雷 / |S| 安全格)双口径
# 名义 = density×(W×H−9) 初布值(参考板形口径,连续性参照)
# 实测 = sim/board_stat_probe.gd 200 样本/行均值("先布后删"落地值,主流读数)
# (实测值沿用 board_stat_probe 读数;板形行值未变,不强制复跑)
BOARDS = {
"L1前段·名义(10x10)": {"M": 23, "S": 77},
"L1后段·名义(12x12)": {"M": 34, "S": 110},
"L2·名义(12x12)": {"M": 34, "S": 110},
"L1前段·实测(10x10)": {"M": 12.91, "S": 87.09},
"L1后段·实测(12x12)": {"M": 22.61, "S": 121.39},
"L2·实测(12x12)": {"M": 22.39, "S": 121.61},
}
def e_dig(u: float, d_ref: float, aoe_per_unit: bool = True) -> float:
"""单安全格期望收益(当量),格子池 77 权重加权。
u = 敌方单位数。hero 侧单目标效果不随 u 缩放(主角每轮出手一次);
enemy 侧 side-wide 效果(ac_up 承伤面 / spark_aoe 群伤 / weaken 全体减伤)随 u 缩放。
"""
w = POOL_WEIGHTS
total = sum(w.values())
attack = w["attack"] * ATTACK_UP_PCT / 100 * d_ref # 主角下一拍增伤
ac_up = w["defense"] * AC_UP_POINT * 0.05 * d_ref * u # 每个来敌拍 −5pp 命中
spark = w["blast"] * (SPARK_AOE * u if aoe_per_unit else SPARK_AOE) # 全体存活敌
heal = w["heal"] * HEAL_MINOR
weaken = w["weaken"] * WEAKEN_PCT / 100 * d_ref * u # 全体敌拍 ×0.35
pierce = w["pierce"] * max(1, PIERCE_STRIKE + ARMOR_EFFECT["pierce"]["light"]) # 轻甲口径
magic = w["magic"] * max(1, MAGIC_BOLT + ARMOR_EFFECT["magic"]["light"])
action = w["action"] * AP_UP
gp = w["gp"] * GP_COIN
return (attack + ac_up + spark + heal + weaken + pierce + magic + action + gp) / total
def v_mine(u: float, d_ref: float) -> float:
"""现行极性矩阵加权口径(九型负向字典)——踩雷兑付 = 该格类型负向行,按 77 权重池加权。
hero 侧固定(直伤三类 heal_backlash/snare/recoil + 乏力 sapped + ap_drain + gp_lose);
enemy 侧 side-wide(enrage 每敌拍 / off_guard 每敌拍命中上浮)随 u 缩放;
警觉按 0.2 当量固定(假设 A4)。
"""
w = POOL_WEIGHTS
total = sum(w.values())
enrage = w["attack"] * ENRAGE_PCT / 100 * d_ref * u
off_guard = w["defense"] * OFF_GUARD_AC * 0.05 * d_ref * u # AC−1 → 每敌拍 +5pp 命中
alert = w["blast"] * ALERT_EQUIV
backlash = w["heal"] * HEAL_BACKLASH
sapped = w["weaken"] * SAPPED_PCT / 100 * d_ref # 主角下一拍 −25%
snare = w["pierce"] * PIERCE_SNARE
recoil = w["magic"] * MAGIC_RECOIL
ap_drain = w["action"] * AP_UP
gp_lose = w["gp"] * GP_LOSE
return (enrage + off_guard + alert + backlash + sapped + snare + recoil
+ ap_drain + gp_lose) / total
def w_flag_hit(u: float, d_ref: float, prem_pct: int = 125) -> float:
"""标中旗兑付聚合当量 W(u)——整数量级实现口径(与机制同式:各型四舍五入取整后折算)。
条件二常设判读式:条件二 = W(u) ≥ (1+δ)·E_dig(u)。
取整跟随机制(GDScript round=半分远离零),非 Python banker's rounding。
1 点量纲行(ac_up/action/spark)取整地板=×1.0——u=3 聚合微欠的成因(登记面)。
"""
w = POOL_WEIGHTS
total = sum(w.values())
def sc(x: float) -> int:
return int(math.floor(x * prem_pct / 100.0 + 0.5))
attack = w["attack"] * sc(ATTACK_UP_PCT) / 100 * d_ref
ac_up = w["defense"] * sc(AC_UP_POINT) * 0.05 * d_ref * u
spark = w["blast"] * (sc(SPARK_AOE) * u)
heal = w["heal"] * sc(HEAL_MINOR)
weaken = w["weaken"] * sc(WEAKEN_PCT) / 100 * d_ref * u
pierce = w["pierce"] * max(1, sc(PIERCE_STRIKE) + ARMOR_EFFECT["pierce"]["light"])
magic = w["magic"] * max(1, sc(MAGIC_BOLT) + ARMOR_EFFECT["magic"]["light"])
action = w["action"] * sc(AP_UP)
gp = w["gp"] * sc(GP_COIN)
return (attack + ac_up + spark + heal + weaken + pierce + magic + action + gp) / total
def per_type_table(u: float, d_ref: float) -> list:
"""逐型 P_t/N_t 明细(当量),供条件净期望计算。返回 [(型, P_t, N_t)]。"""
return [
("attack", ATTACK_UP_PCT / 100 * d_ref, ENRAGE_PCT / 100 * d_ref * u),
("defense", AC_UP_POINT * 0.05 * d_ref * u, OFF_GUARD_AC * 0.05 * d_ref * u),
("blast", float(SPARK_AOE * u), ALERT_EQUIV),
("heal", float(HEAL_MINOR), float(HEAL_BACKLASH)),
("weaken", WEAKEN_PCT / 100 * d_ref * u, SAPPED_PCT / 100 * d_ref),
("pierce", float(max(1, PIERCE_STRIKE + ARMOR_EFFECT["pierce"]["light"])), float(PIERCE_SNARE)),
("magic", float(max(1, MAGIC_BOLT + ARMOR_EFFECT["magic"]["light"])), float(MAGIC_RECOIL)),
("action", float(AP_UP), float(AP_UP)),
("gp", float(GP_COIN), float(GP_LOSE)),
]
def audit_shape(name: str, m: float, s: float, u: float, c_clear: float,
d_ref: float, ap_r: int, iota: float, v_mine_v: float,
e_dig_v: float, dig_cost: int) -> dict:
"""单板形单语境全量计算,返回判式各腿与判定。"""
n_s = (1 - iota) * s
if n_s < 1:
return {"name": name, "skip": True}
e_mine = m * n_s / (n_s + 1)
r_res = n_s * e_dig_v
x_res = e_mine * v_mine_v
dr = math.ceil((n_s + e_mine * dig_cost) / ap_r)
c_eff_normal = c_clear
c_eff_perfect = c_clear * P_PERFECT / 100
d_normal = (r_res + c_eff_normal) - (x_res + dr * d_ref)
d_perfect = (r_res + c_eff_perfect) - (x_res + dr * d_ref)
floor_req = x_res + dr * d_ref # 保守地板需求(对 C_eff)
return {
"name": name, "skip": False, "n_s": n_s, "e_mine": e_mine,
"r_res": r_res, "x_res": x_res, "dr": dr, "dr_cost": dr * d_ref,
"floor_req": floor_req, "gap_normal": floor_req - c_eff_normal,
"gap_perfect": floor_req - c_eff_perfect,
"d_normal": d_normal, "d_perfect": d_perfect,
}
def main() -> None:
ap = argparse.ArgumentParser(description="清图支配性(条款⑤)对账重算 + 判式换代面板(条款3)")
ap.add_argument("--d-ref", type=float, default=D_REF)
ap.add_argument("--c-clear", type=float, default=None,
help="清图奖励当量 C(默认 = (C_MIN+C_MAX)/2 = 32;传 24 得保守地板口径)")
ap.add_argument("--ap-r", type=int, default=AP_R)
ap.add_argument("--iota", type=float, default=IOTA)
ap.add_argument("--rho-nominal", type=float, default=0.25, help="名义雷率(判式主口径)")
ap.add_argument("--rho-measured", type=float, default=0.129, help="实测幸存雷率(10×10 包络口径)")
ap.add_argument("--delta", type=float, default=DELTA, help="旗溢价系数 δ(兑付溢价形态 a)")
ap.add_argument("--e-dig-discount", type=float, default=1.0,
help="E_dig 实现率折减(legacy 敏感性口径传 0.6)")
args = ap.parse_args()
c_clear = args.c_clear if args.c_clear is not None else (C_MIN + C_MAX) / 2
print(f"现行值链快照:D_ref={args.d_ref:g} C={c_clear:g}(min {C_MIN}/max {C_MAX}) "
f"AP_r={args.ap_r} ι={args.iota} p_perfect={P_PERFECT} RAP零价假设照旧")
print(f"E_dig(77权重池加权当量): u=1 {e_dig(1, args.d_ref):.3f} · u=2 {e_dig(2, args.d_ref):.3f} · "
f"u=3 {e_dig(3, args.d_ref):.3f}(折减 ×{args.e_dig_discount:g} 后同乘)")
print(f"V_mine(九型矩阵加权): u=1 {v_mine(1, args.d_ref):.3f} · "
f"u=2 {v_mine(2, args.d_ref):.3f} · u=3 {v_mine(3, args.d_ref):.3f}")
print()
print("### 条款3 判式换代面板(β 比值带 + 条件一聚合健康式 + 条件二旗溢价)")
print(f"β 带 = [{BETA_LO:g}, {BETA_HI:g}](2026-10-04 用户裁 P4) 兑付溢价 ×(1+δ) = ×{1 + args.delta:g}(形态 a)")
print("| u | E_dig | V_mine | β=V/E | 带判定 | 条件一 盲挖净期望(名义ρ) | 条件一 盲挖净期望(实测ρ̂) | 条件二 W(u) | 条件二 阈值(1+δ)E_dig | 条件二判定 |")
print("|---|---|---|---|---|---|---|---|---|---|")
for u in (1, 2, 3):
ed = e_dig(u, args.d_ref)
vm = v_mine(u, args.d_ref)
beta = vm / ed
verdict = "✓带内" if BETA_LO <= beta <= BETA_HI else (
"✗破下限(登记态)" if beta < BETA_LO else "✗破上限")
blind_nom = (1 - args.rho_nominal) * ed - args.rho_nominal * vm
blind_meas = (1 - args.rho_measured) * ed - args.rho_measured * vm
# 条件二(机制在位):W=整数量级标中兑付聚合(取整跟随机制),阈值 =(1+δ)·E_dig
wfh = w_flag_hit(u, args.d_ref, int(round((1 + args.delta) * 100)))
thresh = (1 + args.delta) * ed
cond2 = "✓转绿" if wfh >= thresh else "✗微欠(整数量级取整地板,登记面)"
print(f"| {u} | {ed:.3f} | {vm:.3f} | {beta:.2f} | {verdict} | "
f"{blind_nom:+.2f} | {blind_meas:+.2f} | {wfh:.3f} | {thresh:.3f} | {cond2} |")
print()
print("### 逐型条件净期望 (1−ρ)P_t − ρN_t(当量/格,u=1,名义 ρ 与实测 ρ̂ 双列)")
print("| 型 | P_t | N_t | 净期望(名义ρ) | 净期望(实测ρ̂) | 判(名义) |")
print("|---|---|---|---|---|---|")
for t, p, n in per_type_table(1, args.d_ref):
net = (1 - args.rho_nominal) * p - args.rho_nominal * n
net_m = (1 - args.rho_measured) * p - args.rho_measured * n
verdict = "✓" if net > 1e-9 else ("踩线" if abs(net) <= 1e-9 else "✗破线")
print(f"| {t} | {p:g} | {n:g} | {net:+.2f} | {net_m:+.2f} | {verdict} |")
for u in (2, 3):
cells = " · ".join(f"{t}({(1 - args.rho_nominal) * p - args.rho_nominal * n:+.2f})"
for t, p, n in per_type_table(u, args.d_ref))
print(f"u={u} 净期望: {cells}")
print()
print(f"### 主判式(条款⑤)· 九型矩阵加权 V_mine · C={c_clear:g}")
print("| 板形 | u | n_s | E[踩雷] | R_res | X_res | Δr(代价) | Δ_clear 非完美 | Δ_clear 完美 | 判定 |")
print("|---|---|---|---|---|---|---|---|---|---|")
for bname, b in BOARDS.items():
for u in (1, 2, 3):
ed = e_dig(u, args.d_ref) * args.e_dig_discount
r = audit_shape(bname, b["M"], b["S"], u, c_clear, args.d_ref,
args.ap_r, args.iota, v_mine(u, args.d_ref), ed, DIG_COST)
if r["skip"]:
continue
verdict = "✓" if r["d_perfect"] >= 0 and r["d_normal"] >= 0 else (
"完美✓" if r["d_perfect"] >= 0 else "✗")
print(f"| {bname} | {u} | {r['n_s']:.2f} | {r['e_mine']:.2f} | {r['r_res']:.1f} | "
f"{r['x_res']:.1f} | {r['dr']}({r['dr_cost']:.0f}) | {r['d_normal']:+.1f} | "
f"{r['d_perfect']:+.1f} | {verdict} |")
print()
print("### 保守地板(C_eff(min)=C_MIN)需求与缺口")
print("| 板形 | u | 地板需求 X_res+Δr×s_ref | 缺口(对 min) | 缺口(对完美 ×2) |")
print("|---|---|---|---|---|")
for bname, b in BOARDS.items():
for u in (1, 3):
ed = e_dig(u, args.d_ref) * args.e_dig_discount
r = audit_shape(bname, b["M"], b["S"], u, float(C_MIN), args.d_ref,
args.ap_r, args.iota, v_mine(u, args.d_ref), ed, DIG_COST)
if r["skip"]:
continue
print(f"| {bname} | {u} | {r['floor_req']:.1f} | +{r['gap_normal']:.1f} | +{r['gap_perfect']:.1f} |")
print()
# 护栏 B(防钓鱼——退化板刷清图奖励上界):⌈(|S|min − |R0|max) ÷ AP_r⌉ × s_ref ÷ 2
# |S|min = min_safe_count 当期行集(50/60);|R0|max = 20 经验值(无登记供给方)
print("### 护栏 B 复核(防钓鱼上界,D_ref 口径)")
for s_min in (50, 60):
ub = math.ceil((s_min - 20) / args.ap_r) * args.d_ref / 2
print(f"|S|min={s_min}: 上界 = ⌈({s_min}−20)/{args.ap_r}⌉×{args.d_ref:g}÷2 = {ub:g}"
f" → C={c_clear:g} {'✓' if c_clear <= ub else '越 %.1f×' % (c_clear / ub)}"
f" / 完美 C_eff={c_clear * P_PERFECT / 100:g} "
f"{'✓' if c_clear * P_PERFECT / 100 <= ub else '越 %.1f×' % (c_clear * P_PERFECT / 100 / ub)}")
if __name__ == "__main__":
main()