From a9d5ac13dbad4642cb384c174b91977a05e09e4f Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Tue, 20 Oct 2026 23:38:33 +0800 Subject: [PATCH] =?UTF-8?q?feat(=E5=8D=A1=E2=91=A1=20T7-V12):=20=E5=90=8C?= =?UTF-8?q?=E7=A7=8D=E5=AD=90=E5=A4=8D=E6=94=BE=E6=AF=94=E5=AF=B9=E5=99=A8?= =?UTF-8?q?=20parity=5Fcompare.py=20+=20=E4=B8=89=E7=BB=84=E5=88=A4?= =?UTF-8?q?=E8=AF=BB=E8=AE=B0=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --mode parity(off 组整条逐位相等)/--mode input-parity(formed/rules 输入面八字段+顶层字段集恒等+局数种子集一致); 退出码 0/1/2,字段集从基线推导。判读结论:off 组 base==post 逐字节一致(bf084ef3…,确定性零漂移); formed/rules input-parity PASS(输出面差=已定性预期行为变更);rules≠off 0 局相同(分组非空转)。 基线=master 码+采集器修复重采(卡片 f75acc15 基线因卡① 改 game/ 作废,据 T0 重采,哈希 2d3d4247/0fcbcb26 复现卡片在册值)。 --- tools/parity_compare.py | 68 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 tools/parity_compare.py diff --git a/tools/parity_compare.py b/tools/parity_compare.py new file mode 100644 index 00000000..25d80b08 --- /dev/null +++ b/tools/parity_compare.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""V12 同种子复放比对器(卡② 效果引擎无状态化批):解析两次 MC 采集的 RUNSUM 行按 seed 配对判读。 +两判据模式——parity:整条记录逐位相等(off 组,未受影响路径须透明); +input-parity:输入面八字段 + 顶层字段集恒等、种子集与局数一致(formed/rules 组,输出面允许 RNG 级联位移)。 +退出码 0 绿 / 1 红 / 2 用法或解析错。字段集从基线文件推导,禁硬编码。""" +import sys, json, argparse + +INPUT_KEYS = ["policy", "seed", "atk_pct", "hp_pct", "route", "attune", "equipped", "feat_mode"] + + +def load(path): + recs = {} + with open(path, encoding="utf-8") as f: + for line in f: + line = line.strip() + if not line.startswith("RUNSUM "): + continue + try: + d = json.loads(line[len("RUNSUM "):]) + except json.JSONDecodeError: + print("解析失败行:", line[:80]) + sys.exit(2) + recs[d.get("seed")] = d + return recs + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("--mode", choices=["parity", "input-parity"], required=True) + ap.add_argument("--base", required=True) + ap.add_argument("--post", required=True) + ap.add_argument("--expect", type=int, required=True) + a = ap.parse_args() + base, post = load(a.base), load(a.post) + if len(base) != a.expect or len(post) != a.expect: + print("局数不符:base=%d post=%d expect=%d" % (len(base), len(post), a.expect)) + return 1 + if set(base) != set(post): + print("种子集不一致") + return 1 + red = False + if a.mode == "parity": + diff = [s for s in base if base[s] != post[s]] + if diff: + red = True + print("off 组逐位差异 %d 局: %s" % (len(diff), diff[:10])) + for s in diff[:3]: + k = [kk for kk in base[s] if base[s].get(kk) != post[s].get(kk)] + print(" seed", s, "差异字段:", k) + else: + bkeys = set(next(iter(base.values())).keys()) + pkeys = set(next(iter(post.values())).keys()) + if bkeys != pkeys: + red = True + print("顶层字段集不一致 base-p=%s post-b=%s" % (bkeys - pkeys, pkeys - bkeys)) + for s in base: + for kk in INPUT_KEYS: + if base[s].get(kk) != post[s].get(kk): + red = True + print("输入面字段 %s 在 seed %s 不一致: %r vs %r" % (kk, s, base[s].get(kk), post[s].get(kk))) + # levels_cleared 直方图仅报告(方向不作判据) + lc = post[s].get("levels_cleared") if post else None + print("判读:%s" % ("FAIL" if red else "PASS")) + return 1 if red else 0 + + +if __name__ == "__main__": + sys.exit(main())