iter56-卡1:结算期文字列表退役与日志零文案(迭代56卡1): 撤B区逐行文字列表(row_pacer文字入带三调用/行格式化域删净——FX排播链零diff保留)+日志step行零文案(text/display_name不落盘)+reward_ineffective六发射点补reason结构化(full_layers/full_hp/no_enemy_alive/hero_down); 分析工具改读结构化字段(target==all_enemies/reason优先text兜底双分支)+analyze_parity_check对拍载体。读数: R1零逐行文字/R2 FX链行型分布独立推导逐位一致/R3零文案+全字段落盘/R4判读等价三读数全一致/R5四类reason逐一命中——各T提交信息载; 能力行:137/:138名实一致(_smoke_settle_zero_rows/_assert_fx_chain/_assert_no_text_and_full_payload/_assert_four_reasons); 测试削弱全部申报内(§10清单+§5.5同生死波及面11处登记实现报告); 中档审计通过
This commit is contained in:
1 parent
9251d35650
commit
908e518306
40 files changed
+1258
-1298
No files matched your search
@@ -0,0 +1,92 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""判读等价对拍工具(结算期文字列表退役批 R4 载体——日志零文案改后分析判读逐位一致)。
|
||||
|
||||
用法:
|
||||
python tools/analyze_parity_check.py <输入A> <输入B> [<输入C> ...] [--runs N]
|
||||
|
||||
输入 = 日志(.jsonl,各跑一次 tools/analyze_playtest.py 完整判读)或基线快照
|
||||
(.md,analyze_playtest.py 输出重定向落盘的静态文本,直读不重跑)。全部剥去文件名
|
||||
标题行后相邻逐位 diff——任一对不一致即 exit 1 并打印第一条分歧行。
|
||||
|
||||
三读数跑法(改前改后配对,卡 R4):
|
||||
① 历史锚日志 vs 改前 T0 基线快照(兼容分支等价)
|
||||
② 改前同种子日志 vs 改前 T0 基线快照(复现等价)
|
||||
③ 改前同种子日志 vs 改后同种子新日志(同种子+玩法零改动=事件流逐位同)
|
||||
|
||||
退出码: 0=全部逐位一致 1=判读分歧 2=参数/执行错误
|
||||
"""
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
ANALYZE = HERE / "analyze_playtest.py"
|
||||
|
||||
|
||||
def run_analyze(log_path: str, runs: str) -> str:
|
||||
cmd = [sys.executable, str(ANALYZE), log_path]
|
||||
if runs:
|
||||
cmd += [runs]
|
||||
proc = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8")
|
||||
if proc.returncode != 0:
|
||||
print("[parity] 判读执行失败(%s)exit=%d" % (log_path, proc.returncode))
|
||||
print(proc.stdout[-2000:])
|
||||
print(proc.stderr[-2000:])
|
||||
sys.exit(2)
|
||||
# 剥文件名标题行(playtest_*.jsonl 文件名随输入变,不参与逐位比对)
|
||||
return strip_name_lines(proc.stdout)
|
||||
|
||||
|
||||
def strip_name_lines(text: str) -> str:
|
||||
out_lines = []
|
||||
for ln in text.splitlines():
|
||||
if "playtest_" in ln and ".jsonl" in ln:
|
||||
continue
|
||||
out_lines.append(ln)
|
||||
return "\n".join(out_lines)
|
||||
|
||||
|
||||
def first_diff(a: str, b: str):
|
||||
la, lb = a.splitlines(), b.splitlines()
|
||||
for i in range(max(len(la), len(lb))):
|
||||
xa = la[i] if i < len(la) else "<缺行>"
|
||||
xb = lb[i] if i < len(lb) else "<缺行>"
|
||||
if xa != xb:
|
||||
return i + 1, xa, xb
|
||||
return None
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv[1:]
|
||||
runs = ""
|
||||
if "--runs" in args:
|
||||
i = args.index("--runs")
|
||||
runs = args[i + 1]
|
||||
args = args[:i] + args[i + 2:]
|
||||
if len(args) < 2:
|
||||
print(__doc__)
|
||||
sys.exit(2)
|
||||
outputs = []
|
||||
for p in args:
|
||||
if p.lower().endswith(".jsonl"):
|
||||
outputs.append(run_analyze(p, runs))
|
||||
else: # 基线快照(.md)——静态文本直读,剥名规则同判读输出
|
||||
outputs.append(strip_name_lines(Path(p).read_text(encoding="utf-8")))
|
||||
ok = True
|
||||
for i in range(1, len(outputs)):
|
||||
d = first_diff(outputs[i - 1], outputs[i])
|
||||
pair = "%s <-> %s" % (Path(args[i - 1]).name, Path(args[i]).name)
|
||||
if d is None:
|
||||
print("[parity] 一致 | %s | 判读逐位一致" % pair)
|
||||
else:
|
||||
ln, xa, xb = d
|
||||
print("[parity] 分歧 | %s | 第 %d 行" % (pair, ln))
|
||||
print(" A: %s" % xa)
|
||||
print(" B: %s" % xb)
|
||||
ok = False
|
||||
sys.exit(0 if ok else 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -186,7 +186,7 @@ def analyze_run_core(rows, meta):
|
||||
if cur_b is not None:
|
||||
cur_b["hero_dmg"] += v
|
||||
elif src in ("safe_cell", "clear_reward", "flag_hit") or (
|
||||
src == "mine_hit" and "全体敌人" in r.get("text", "")):
|
||||
src == "mine_hit" and r.get("target") == "all_enemies"):
|
||||
out["reward_dmg"] += v
|
||||
out["rewards_n"] += 1
|
||||
name = "清障炸药" if src == "clear_reward" else "群伤"
|
||||
@@ -431,7 +431,7 @@ def analyze(rows, label):
|
||||
cf_n = int(round(n * 100.0 / (100 + up)))
|
||||
rowout["cf_hero"] += cf_n
|
||||
elif src in ("safe_cell", "clear_reward", "flag_hit") or (
|
||||
src == "mine_hit" and "全体敌人" in s.get("text", "")):
|
||||
src == "mine_hit" and s.get("target") == "all_enemies"):
|
||||
# 奖励伤害 = 打敌人的棋盘来源伤害;踩雷负向打主角的(反噬震荡)归承伤侧
|
||||
rowout["rw"] += int(s["value"])
|
||||
rowout["pos_n"] += 1
|
||||
@@ -468,17 +468,32 @@ def analyze(rows, label):
|
||||
rowout["cf_tr"] += cf_norw
|
||||
for s in steps:
|
||||
if s["type"] == "reward_ineffective":
|
||||
txt = s.get("text", "")
|
||||
if "满层" in txt:
|
||||
# 结构化 reason 优先(日志零文案批——四类枚举 full_layers/full_hp/
|
||||
# no_enemy_alive/hero_down);text 子串兜底 = 旧日志兼容分支(改前日志
|
||||
# 无 reason 键——判读等价两腿各走各分支,结论逐位一致)
|
||||
reason = s.get("reason", "")
|
||||
if reason == "full_layers":
|
||||
key = "增益满层"
|
||||
elif "满血" in txt:
|
||||
elif reason == "full_hp":
|
||||
key = "回血满血"
|
||||
elif "无存活敌人" in txt:
|
||||
elif reason == "no_enemy_alive":
|
||||
key = "无存活敌人"
|
||||
elif "主角已倒下" in txt:
|
||||
elif reason == "hero_down":
|
||||
key = "主角已倒下"
|
||||
else:
|
||||
elif reason:
|
||||
key = "其他"
|
||||
else:
|
||||
txt = s.get("text", "")
|
||||
if "满层" in txt:
|
||||
key = "增益满层"
|
||||
elif "满血" in txt:
|
||||
key = "回血满血"
|
||||
elif "无存活敌人" in txt:
|
||||
key = "无存活敌人"
|
||||
elif "主角已倒下" in txt:
|
||||
key = "主角已倒下"
|
||||
else:
|
||||
key = "其他"
|
||||
rowout["ineff_counts"][key] = rowout["ineff_counts"].get(key, 0) + 1
|
||||
bstat["hero_dmg"] += rowout["hero"]
|
||||
bstat["reward_dmg"] += rowout["rw"]
|
||||
|
||||
+9
-21
@@ -20,8 +20,6 @@ gate_game_path() {
|
||||
|
||||
# 杀整棵进程树(<pid>)——递归杀子进程后杀本进程。macOS/Linux 走 pgrep -P;
|
||||
# Windows 侧 MSYS kill 对其可见进程有效,带 wrapper 树的场景仍由调用方原 taskkill /T 逻辑负责。
|
||||
# 注意:Windows Git Bash 实测无 pgrep/pkill(2026-11-08 修复批)——本函数在 Windows 退化成只杀
|
||||
# 顶格进程,引擎子进程残留由调用方段末 kill_orphans 清场兜底(probe_gate/render_gate 均有段后清点)。
|
||||
gate_kill_tree() {
|
||||
local p c
|
||||
kill -9 "$1" 2>/dev/null
|
||||
@@ -31,36 +29,26 @@ gate_kill_tree() {
|
||||
# 带超时执行(coreutils timeout 的纯 bash 替代,双平台同语义):
|
||||
# gate_run_timeout <秒> <日志路径> <命令...>
|
||||
# 命令输出重定向到日志;退出码=命令退出码,超时=124(与 coreutils 语义一致,判据与既有闸一致)。
|
||||
# 实现=后台启动+守卫轮询(守卫每 0.2 秒醒一次,看两样:停止文件在不在/到点没;
|
||||
# 命令先完时父流程放停止文件,守卫看见即自己退场——没有嵌套长 sleep,从机制上无孤儿可留;
|
||||
# 2026-11-08 修复批:旧版 mktemp 建标志文件「建时即真」致退出码恒 124+守卫被杀留孤儿 sleep
|
||||
# 占输出管道至满档);到点未见停止文件=真超时:落标志文件+杀整树+回 124。
|
||||
# 实现=后台启动+守卫落标志文件(probe_gate run_suite 同款手法:taskkill 强杀后 wait 报 0 的
|
||||
# 假绿以标志文件纠偏);超时先杀整树再回 124。
|
||||
gate_run_timeout() {
|
||||
local t="$1" log="$2"; shift 2
|
||||
local kf stop
|
||||
kf="${TMPDIR:-/tmp}/gatetime.$$.$RANDOM.kf" # 只赋路径、不建文件——建时即真=旧缺陷病根(probe_gate run_suite 同款)
|
||||
stop="${TMPDIR:-/tmp}/gatetime.$$.$RANDOM.stop"
|
||||
rm -f "$kf" "$stop"
|
||||
local kf
|
||||
kf="$(mktemp "${TMPDIR:-/tmp}/gatetime_XXXXXX")"
|
||||
"$@" >"$log" 2>&1 &
|
||||
local pid=$!
|
||||
( end=$((SECONDS+t))
|
||||
until [ -f "$stop" ] || [ "$SECONDS" -ge "$end" ]; do
|
||||
sleep 0.2 2>/dev/null || sleep 1
|
||||
done
|
||||
if [ ! -f "$stop" ]; then
|
||||
touch "$kf"
|
||||
gate_kill_tree "$pid"
|
||||
fi
|
||||
( sleep "$t" 2>/dev/null
|
||||
touch "$kf"
|
||||
gate_kill_tree "$pid"
|
||||
true ) &
|
||||
local guard=$!
|
||||
wait "$pid"; local st=$?
|
||||
touch "$stop" # 放停止文件=通知守卫收工,0.2 秒内自退,无需 kill
|
||||
wait "$guard" 2>/dev/null
|
||||
kill "$guard" 2>/dev/null; wait "$guard" 2>/dev/null
|
||||
if [ -f "$kf" ]; then
|
||||
gate_kill_tree "$pid"
|
||||
st=124
|
||||
fi
|
||||
rm -f "$kf" "$stop"
|
||||
rm -f "$kf"
|
||||
return "$st"
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# 合回前置检查钩子(git pre-merge-commit,2026-11-06 用户裁)——主工作区 git merge 落合并提交前
|
||||
# 跑最小验证面:tools/run_all_gates.sh --premerge(冒烟测试+单元与集成全套件 4 分片,热导入缓存约 2 分钟)。
|
||||
# 语义(git 2.45.2.windows.1 本机实测):本钩子拦的是「合并提交」不是「合并动作」——exit≠0 时合并结果
|
||||
# 已进暂存区、仓库停 MERGE_HEAD 待提交态;处置唯一:git merge --abort 放弃 → 回 worktree 处理 → 重新 git merge --no-ff(禁 git commit 续合:该路径 pre-merge-commit/post-merge 两钩子零触发)。
|
||||
# 已进暂存区、仓库停 MERGE_HEAD 待提交态;处置二选一:修红后 git commit 续合,或 git merge --abort 放弃。
|
||||
# 快进合并不触发本钩子(git 语义)——本仓合回铁规恒 --no-ff,漏写时远端 CI 兜底仍在。
|
||||
# --no-verify 可整体跳过(实测)——仅属用户,agent 禁用(worktree-guard 在案)。
|
||||
# 仅主工作区触发(worktree 内合并不触发,与 post-merge 同款 git-dir 判别)。
|
||||
@@ -18,7 +18,7 @@ RC=$?
|
||||
if [ "$RC" -ne 0 ]; then
|
||||
echo "" >&2
|
||||
echo "[pre-merge-commit] ✗ 前置检查红(读数如上)——合并提交未落,仓库停 MERGE_HEAD 待提交态。" >&2
|
||||
echo " 处置唯一:git merge --abort 放弃 → 回 worktree 处理 → 重新 git merge --no-ff(禁 git commit 续合:两合回钩子零触发)。" >&2
|
||||
echo " 处置二选一:① 修红后 git commit 续合本合并;② 放弃整次合并=git merge --abort。" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "[pre-merge-commit] ✓ 前置检查绿——合并提交继续。"
|
||||
|
||||
Reference in new issue
Block a user