merge: 分支并入 master(推进 84 提交,2026-10-21)——research/ 治理批按现master重取四处冲突

解冲突取法=master 现文为准、本批新增语义打上去,不照抄分支旧文本:
- doc-conventions §11:活文档清单与「暂存地非档案库」行取本批新增全文(content-archive 退役内容留底/research/ 无常驻例外/跨会话交接文件禁入库/根 playnote 定性),清单称呼按 docmove 批现文写作「待办清单」,「落档必写接收方」「迭代实现报告」两行照收 master 版(旧日期注记不复活)。
- 08-Alpha版本标准:Alpha 出口裁决与营地五卡入 Alpha 的全部现行行照收 master,仅把 playnote 链接按新位置改指 ../../playnote.md。
- tools/repo_structure_audit.py:ROOT_FILES 取 master 全集(含待办清单/体验验证清单两件根文件)后追加 playnote.md。
- design/research/Alpha未完成事项梳理-2026-10-13.md:modify/delete 冲突按删除解——master 侧自分叉只改过两处指路句(f100e192/281d471e),其结论已由 08 v1.11-v1.12 与待办清单承载,吸收判据今日重核成立。

随批补两处 master 前进后暴露的同步面:AGENTS.md Project Layout 根目录段补 playnote.md 一行(白名单收了新根文件而目录说明未跟上);技能层 batch-closure 两份副本(.zcode 原件→整文件覆盖 .qoder)补「research/ 无常驻例外」的候选呈报口径与 playnote 不入退役候选。
This commit is contained in:
DKChan committed 2026-10-21 07:18:31 +08:00
commit 224a947559
132 files changed
+2162 -2565

No files matched your search

+2 -8
View File
@@ -3,9 +3,8 @@
"""audit_file_headers.py — 文件头机器闸(code-conventions §1.4.1)。
文件头 = .gd 文件顶部连续注释区:连续的空行或 # 注释行,首个非注释代码行止。
本脚本只实现机械可判子集(两类白名单的人判面见规范正文):
本脚本只实现机械可判子集(2026-10-20 缩窄:行数上限 R1 与类别白名单已停用):
R0 载体:头注非空时首行必须以 `##` 文档注释开头;
R1 行数上限:头注注释行(非空)> 8 行;
R2 日期:头注出现日期模式(2026-09-12 / 2026年9月 一类)——日期只属于 git log 与迭代报告;
R3 变更叙事词:「迭代N / 本迭代」、批次叙事复合词(本批/拆件批/改造批……)、「迁移到/改名为」
一类变更经过。清单从紧防误伤:「结算批」等玩法机制词不在黑名单;
@@ -20,8 +19,6 @@ from pathlib import Path
ROOT = Path(__file__).resolve().parent.parent
SCAN_DIRS = ["game/core", "game/data", "game/presentation"]
MAX_HEADER_LINES = 8
DATE_PAT = re.compile(r"\d{4}\s*[-/.年]\s*\d{1,2}")
NARRATIVE_PATS = [
(re.compile(r"迭代\s*\d|本迭代"), "变更叙事「迭代N」"),
@@ -52,9 +49,6 @@ def audit_path(path):
rows = header_lines(text)
if rows and not rows[0][1].lstrip().startswith("##"):
hits.append((rows[0][0], "R0 头注必须以 ## 文档注释开头"))
if len(rows) > MAX_HEADER_LINES:
hits.append((rows[0][0], "R1 头注 %d 行 > 上限 %d 行(区间 %d-%d 行)"
% (len(rows), MAX_HEADER_LINES, rows[0][0], rows[-1][0])))
for lineno, line in rows:
if DATE_PAT.search(line):
hits.append((lineno, "R2 头注日期"))
@@ -77,7 +71,7 @@ def main():
if violations:
print("[audit_file_headers] FAIL: 文件头存量 %d 处(存量按 code-conventions §1.4.1 白名单清偿)" % violations)
return 1
print("[audit_file_headers] ok: 头注 ≤8 行且零黑名单命中")
print("[audit_file_headers] ok: 头注零黑名单命中(R0 载体/R2 日期/R3 变更叙事)")
return 0
+219
View File
@@ -0,0 +1,219 @@
#!/usr/bin/env python3
r"""doc_ref_audit.py — 文档内代码引用可核检查(机检)
职责:现行文档里凡写「某文件:第几行」,就该指得动。本工具核两件事:
①路径可解析——目标文件在 git 受控文件清单里存在;
②行号在界内——引用的行号不超过目标文件的实际行数。
动机:条目与规约常写 `board_view.gd:737` 这类指针,代码前进后行号漂移、文件迁移改名,
账面就与现实脱节而无人知(2026-10-21 迭代 32 收尾时实测抓到两例)。
扫描面:`design/` 现行文档树 + 四份根文件(`待办清单.md`/`体验验证清单.md`/`roadmap.md`/`AGENTS.md`)。
排除面:`reports/` 与 `design/research/`——两处是中间文档暂存地(doc-conventions 第 11 节),
入内文件按「吸收即死」退役,历史件不回溯改,故不入本闸判据。
自检二道(防假绿):扫描文件数为零 ⇒ FAIL;抽取到的引用数为零 ⇒ FAIL。
(正则被改写失手或路径写错时,检查会静默空转并报绿——与 static_scan 各段同规。)
判据分两档:
红(判 FAIL)=路径不存在/行号超界;
建议(只报告)=同名文件有多条路径需要人工定位/`--anchors` 的内容锚没在目标行附近找到。
用法:
python tools/doc_ref_audit.py # 全扫,报告模式
python tools/doc_ref_audit.py --strict # 有红项时 exit 1(供门禁)
python tools/doc_ref_audit.py --anchors # 追加内容锚核对(只报告,不判红)
python tools/doc_ref_audit.py <file.md> ... # 只扫指定文件
exit:0=全绿 / 1=有红项(--strict)/ 2=扫描面异常(目录缺失)
"""
import argparse
import io
import os
import re
import subprocess
import sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
SCAN_DIRS = ["design"]
SCAN_EXCLUDE_DIRS = ["design/research"]
ROOT_FILES = ["待办清单.md", "体验验证清单.md", "roadmap.md", "AGENTS.md"]
# 引用形态:路径或文件名 + 扩展名 + 半/全角冒号 + 行号。反引号可包在中间或外面,
# 因此先抓到扩展名为止,冒号允许紧跟或在反引号之后。
REF_RE = re.compile(
r"([\w./一-鿿-]+\.(?:gd|tres|sh|py|json|md|cfg|yml|yaml|txt|png|html))"
r"[`”』」]?\s*[::]\s*(\d+)"
)
# 内容锚:同一行内的反引号片段或「」内文本,取长度 ≥4 的第一个非引用片段
ANCHOR_RE = re.compile(r"`([^`\n]{4,60})`|「([^」\n]{4,60})」")
ANCHOR_WINDOW = 12
# 留档信号(与 doc_term_audit 同口径):同句带日期或退役/注销类说明时,指向已不存在的路径
# 属历史叙述而非现行指针,降为建议级报出、不判红。
ARCHIVE_RE = re.compile(
r"\d{4}-\d{2}-\d{2}|退役|注销|已删|已废除|已废|废除|作废|历史|原名|改名|迁入|迁到|移到|迁至"
)
def tracked_files():
out = subprocess.run(
["git", "-c", "core.quotepath=false", "-C", str(REPO), "ls-files"],
capture_output=True, text=True, encoding="utf-8", errors="ignore",
)
if out.returncode != 0:
return None
return {l.strip().strip('"').replace("\\", "/") for l in out.stdout.splitlines() if l.strip()}
def collect_docs(explicit):
if explicit:
return [p for p in explicit if Path(p).is_file()]
paths = []
for d in SCAN_DIRS:
root = REPO / d
if not root.is_dir():
return None
for p in sorted(root.rglob("*.md")):
rel = p.relative_to(REPO).as_posix()
if any(rel.startswith(x + "/") for x in SCAN_EXCLUDE_DIRS):
continue
paths.append(rel)
for f in ROOT_FILES:
if (REPO / f).is_file():
paths.append(f)
return paths
def resolve(name, doc_rel, index, bybase):
"""返回 (命中路径列表, 解析方式)。方式=exact / ambiguous / none。"""
name = name.strip("./")
if "/" in name:
for cand in (
os.path.normpath(os.path.join(os.path.dirname(doc_rel), name)).replace("\\", "/"),
os.path.normpath(name).replace("\\", "/"),
):
if cand in index:
return [cand], "exact"
return [], "none"
cands = sorted(bybase.get(name, []))
if not cands:
return [], "none"
if len(cands) == 1:
return cands, "exact"
return cands, "ambiguous"
def line_count(path, cache):
if path not in cache:
try:
cache[path] = len(io.open(REPO / path, encoding="utf-8", errors="ignore").read().splitlines())
except OSError:
cache[path] = 0
return cache[path]
def anchor_of(line, name):
"""取同句里可当内容锚的片段:反引号或「」内、长度 ≥4、且自身不是文件路径。"""
for m in ANCHOR_RE.finditer(line):
frag = (m.group(1) or m.group(2)).strip()
if name.split("/")[-1] in frag:
continue
if re.search(r"\.(gd|tres|sh|py|json|md|yaml|yml|html|png)$", frag):
continue
if re.fullmatch(r"[\d:~\-]+", frag):
continue
return frag
return None
def check(doc_rel, index, bybase, cache, anchors=False):
reds, advises = [], []
text = io.open(REPO / doc_rel, encoding="utf-8", errors="ignore").read()
for ln, line in enumerate(text.splitlines(), 1):
for m in REF_RE.finditer(line):
name = m.group(1).lstrip("①②③④⑤⑥⑦⑧⑨⑩〔[[((「」·、。,,;;")
num = int(m.group(2))
hits, how = resolve(name, doc_rel, index, bybase)
if how == "none":
if ARCHIVE_RE.search(line):
advises.append("%s:%d 引用已退役件 指 `%s`——同句带日期或退役/注销类说明,按留档处理" % (doc_rel, ln, name))
else:
reds.append("%s:%d 引用可核/路径不存在 文档指 `%s`,git 受控文件清单里没有这件" % (doc_rel, ln, name))
continue
if how == "ambiguous":
advises.append("%s:%d 同名多路径 指 `%s`——命中 %d 件(%s),需写全路径" % (
doc_rel, ln, name, len(hits), ", ".join(hits[:3])))
target = hits[0]
total = line_count(target, cache)
if num > total:
if ARCHIVE_RE.search(line):
advises.append("%s:%d 行号已越界(留档句) 指 `%s:%d`,目标现 %d 行——同句带退役/日期说明" % (
doc_rel, ln, name, num, total))
else:
reds.append("%s:%d 引用可核/行号超界 指 `%s:%d`,目标文件现只有 %d 行" % (doc_rel, ln, name, num, total))
continue
if anchors:
frag = anchor_of(line, name)
if frag:
lines = io.open(REPO / target, encoding="utf-8", errors="ignore").read().splitlines()
lo, hi = max(0, num - 1 - ANCHOR_WINDOW), min(len(lines), num + ANCHOR_WINDOW)
window = "\n".join(lines[lo:hi])
if frag not in window:
advises.append("%s:%d 内容锚未命中 指 `%s:%d`,同句片段「%s」不在该线 ±%d 行内" % (
doc_rel, ln, os.path.basename(name), num, frag[:28], ANCHOR_WINDOW))
return reds, advises
def main():
ap = argparse.ArgumentParser(description="文档内代码引用可核检查")
ap.add_argument("files", nargs="*", help="指定文件;缺省扫 design/ 现行树+四份根文件")
ap.add_argument("--strict", action="store_true", help="有红项时 exit 1")
ap.add_argument("--anchors", action="store_true", help="追加内容锚核对(只报告)")
args = ap.parse_args()
index = tracked_files()
if index is None:
print("引用可核 FAIL:git ls-files 取不到受控文件清单(扫描面异常)")
return 2
docs = collect_docs(args.files)
if docs is None:
print("引用可核 FAIL:扫描目录缺失(design/ 不在位)")
return 2
if not docs:
print("引用可核 FAIL:扫描面为空——路径写错导致的静默假绿")
return 1
bybase = {}
for p in index:
bybase.setdefault(p.rsplit("/", 1)[-1], []).append(p)
cache, all_reds, all_adv = {}, [], []
total_refs = 0
for doc in docs:
reds, adv = check(doc, index, bybase, cache, anchors=args.anchors)
total_refs += doc_ref_count(doc)
all_reds += reds
all_adv += adv
if total_refs == 0:
print("引用可核 FAIL:抽取到的引用数为零——正则或扫描面失手,属假绿防线")
return 1
for r in all_reds:
print(r)
print("── 引用可核:扫描 %d 件文档 · 抽取引用 %d 处 · 红项 %d · 建议 %d" % (
len(docs), total_refs, len(all_reds), len(all_adv)))
if all_adv:
for a in all_adv[:25]:
print(" (建议)" + a)
if len(all_adv) > 25:
print(" (建议)…另有 %d 条" % (len(all_adv) - 25))
return 1 if (args.strict and all_reds) else 0
def doc_ref_count(doc_rel):
text = io.open(REPO / doc_rel, encoding="utf-8", errors="ignore").read()
return sum(len(REF_RE.findall(l)) for l in text.splitlines())
if __name__ == "__main__":
sys.exit(main())
+14 -4
View File
@@ -14,8 +14,10 @@ r"""doc_term_audit.py — GDD 退役术语扫描(半自动)
python tools/doc_term_audit.py <file.md> ... # 只扫指定文件
python tools/doc_term_audit.py --json # 机器可读
口径:只扫 design/gdd/——design/research
(历史提案)、docs/(样例)与 production/ 不在扫描域;词表扩充 = 改 RETIRED_TERMS。
口径:扫 design/gdd/ 与 design/art/ 两处文档域 + 四份根文件(`待办清单.md`/`体验验证清单.md`/
`roadmap.md`/`AGENTS.md`,2026-10-21 迭代 32 卡①反哺批扩面,用户裁;根文件同属现行文本)
——design/research(历史提案)与 reports/(中间文档)不在扫描域;
词表扩充 = 改 RETIRED_TERMS(受 doc-conventions §4 词表冻结约束:唯一入口=用户实读反馈并经用户裁决)。
"""
import argparse
import json
@@ -46,6 +48,8 @@ RETIRED_TERMS = [
("雾格", "2026-09-14", "(雾格移除)", "不做清单"),
("兄弟精", "2026-09-26", "L2 熊地精(bugbear 复用)", "变体名未经批准退役批——用户质问查证后全仓清除"),
# —— 用语规范(2026-10-15 用户令):权威=design/gdd/术语表.md「用语规范」A 表,机器闸吸收 ——
# 词表冻结:本节禁再新增词条;扩表唯一入口=用户实读反馈并经用户裁决,由非迭代批写入,agent 禁自行增词
# (doc-conventions §4「词表冻结」)。
# 注意:「随批」「台账」两词本身是上方行级白名单信号(行内出现即归留档类),对其扫描会自豁免,
# 故不列入;「收口」「在飞」等照常扫描。术语表「用语规范」节已加入节级豁免(词表自身不命中)。
("对标", "2026-10-15", "参照", "用语规范 A 表(STC 1.7)"),
@@ -64,9 +68,14 @@ RETIRED_TERMS = [
# (词面误报率高,靠术语表对照行与人工判读),game/ 扫描域归代码批后另加。
("金币", "2026-10-18", "GP(唯一正式命名)", "GP 命名统一批——「金币」全库禁用"),
("插旗|标旗", "2026-10-18", "封印(正式动作名,全库统一)", "术语刷新批——「旗」概念退役"),
("完美排雷", "2026-10-19", "完美封印(正式名见 design/gdd/术语表.md 退役词对照)", "迭代 32 卡①反哺批 2026-10-21 用户裁入表——代码注释面由 S11 承接,文档正文面由此条承接"),
]
DEFAULT_DIRS = ["design/gdd"]
DEFAULT_DIRS = ["design/gdd", "design/art"]
# 根规则与流程文件同属现行文本,2026-10-21 迭代 32 反哺批复测后纳入扫描面:
# 当日实测这批文件自身含 3 处用语表废止词(roadmap 的「对拍」「标旗」、AGENTS 的「收口」×2),
# 随同批订正后判红归零才扩面——先改现实、再让闸盯住现实,避免带红扩面。
DEFAULT_FILES = ["待办清单.md", "体验验证清单.md", "roadmap.md", "AGENTS.md"]
# 用语规范 A 表补充词专项补扫:随批/台账 两词本身是上方行级白名单信号(行内出现即归
# 留档类),主扫描对它们自豁免;本通道只做节外残留的专项报告,供后续触碰批清偿。
@@ -119,7 +128,7 @@ TERM_CONTEXT_EXEMPT = {
# 敌人 id 只能读作历史留档(真正的现行规格在 敌人系统.md「目标顺位与意图线索」节)。
"伏击": re.compile(r"ambusher|成文占位"),
"heavy_multiplier|重击倍率": re.compile(r"ambusher|heavy_multiplier|成文占位"),
# 拔旗(2026-09-19 退役「旗无移除手段」):06 待办清单「件派生动词白名单」条目说的是
# 拔旗(2026-09-19 退役「旗无移除手段」):待办清单「件派生动词白名单」条目说的是
# 「将来件池 ≥30 种后可能开放的第三盘面动作」,是待评估的设计意图而非在用机制。
"拔旗|撤旗": re.compile(r"件派生动词白名单|第三盘面动作|非信息类"),
}
@@ -187,6 +196,7 @@ def main():
for d in DEFAULT_DIRS:
paths += sorted(Path(d).rglob("*.md"))
paths += sorted(Path(d).rglob("*.yaml"))
paths += [Path(f) for f in DEFAULT_FILES]
paths = [p for p in paths if Path(p).exists()]
findings = scan(paths) + scan_supplement(paths)
+19 -55
View File
@@ -9,14 +9,12 @@ r"""gdd_lint.py — GDD lean 格式门禁
规则(口径权威 = design/engineering/doc-conventions.md 第 5–6 节文档骨架与结构纪律):
R1 散文段落 ≤ 240 字(代码块/表格/列表/引用不计)
R2 表格字符占比(词条表分域豁免,见 R2_EXEMPT 注)
R3 正文禁止修订注记:~~删除线~~、"作废"、括号内"日期+评审/修订/拍板/裁决/核销"
(登记处/清单按 RULE_EXEMPT 分域豁免)
R4 禁止变更记录节:出现「版本历史/修订记录/变更记录/修改记录/更新记录/更新日志」标题即红
(沿革只进 git commit log;扫描面 = 全 design/ + roadmap.md,见 --changelog-only)
R5 流程/状态类章节必须含代码块图(字符画或 Mermaid);清单节/词条节按 RULE_EXEMPT 分域豁免
R5 流程/状态类章节必须含代码块图(字符画或 Mermaid);词条节按 RULE_EXEMPT 分域豁免
R6 表格列数一致(含转义 \| 识别)
R7 括号嵌套跨度 ≤ 80 字(长括号 = 审计注记挤进正文的信号;登记处/清单按 RULE_EXEMPT 分域豁免)
R7 括号嵌套跨度 ≤ 80 字(长括号 = 审计注记挤进正文的信号)
R8 核心规则表:规则列必须是"谁·何时·做什么"陈述句所在的可读表——说明/要点列
禁止出现编号引用(AC-x/F-x/EC-x/E-x/OQ-x/Rule-x/义务),引用只允许在依据类列
R9 一页纸节点:任一标题节点的直接内容(到下一标题为止)≤ 80 行——装不下就拆子节点
@@ -31,42 +29,19 @@ import sys
from pathlib import Path
MAX_PARA = 240
# 2026-09-14 放宽 60→30:lean 改革后的新模板(参考 analysis/design 两件套)允许
# 短段落与编号人话规则——防铺开的职责改由 R1(段 ≤240 字)与 R9(节点 ≤80 行)承担,
# R2 降为兜底下限(防纯散文文档)。
MIN_TABLE_RATIO = 30
# R2 表格占比判据已停用(2026-10-20 规则审阅批):lean 体系退役后其解读对象已不存在,
# 分域豁免越加越多、只产存量噪音(af94ce97 指纹刷新为证),判据与 R2_EXEMPT 一并拆除。
MAX_PAREN_SPAN = 80
# 规则分域(2026-10-03 用户裁「测试要快/要准」优化批):R1/R2 的体裁前提是「规范文」
# (短段+表格化);登记处/清单/概念文的自然形态就是长条目与叙事散文,硬套=自 2026-09-20
# 上线起 8/8 常红零过滤价值(test-perf-report-2026-10-02 §6.2 实证)。R3/R7 不豁免——
# 「provenance 出正文」对一切体裁有效;R4 变更记录节禁令自 2026-10-19 起对一切体裁有效。
R1_EXEMPT = {
"06-待办清单.md", # 全项目待办登记处:现状/状态/方案长条目为体裁
"07-体验验证清单.md", # 体验验证读数清单:同上
}
R2_EXEMPT = {
"01-核心循环.md", # 循环概念文:叙事散文为体裁
"03-内容规模.md", # 规模框架文:多维档位靠散文交代,结构表只有一张
"04-美术音频方向.md", # 美术方向文:同上
"05-不做清单.md", # 清单文:条目散文为体裁
"06-待办清单.md",
"07-体验验证清单.md",
"术语表.md", # 词条表:表格为主体形态,表格占比高是体裁本身
}
# 规则分域(2026-10-03 用户裁「测试要快/要准」优化批):R1 的体裁前提是「规范文」
# (短段+表格化)。「provenance 出正文」对一切体裁有效;R4 变更记录节禁令自 2026-10-19
# 起对一切体裁有效。
# 规则粒度分域豁免(2026-10-15 用户令「清单类文件与术语表不做 GDD 规范性检查」)。
# 原则:**按规则豁免,不按文件拉黑**——体裁规则(R3/R5/R7)对登记处/清单/词条表
# 不适用,但机械可判的完整性规则(R4/R6/R8/R9/R11)一律不豁免,真缺陷不得藏起来。
# 各条理由:
# R3 正文禁修订注记——登记处/清单的条目「带日期+裁决来源」是设计目的本身
# (条目要能回答「这条谁在何时裁的」),禁它等于禁登记处的核心价值。
# R5 流程/状态类节须含代码块图——节名含「流程/生命周期」即触发,但 07 的
# 「㉖…全流程走查」「㉗…目检」是目检清单节、术语表的「开发流程」是词条节,
# 两者都不是流程描述,属节名误匹配。
# R7 括号跨度 ≤80 字——长括注在登记处/清单里是常态(现状+裁决+归属挤在一行)。
# 原则:**按规则豁免,不按文件拉黑**——体裁规则对词条表不适用,但机械可判的完整性规则
# (R4/R6/R8/R9/R11)一律不豁免,真缺陷不得藏起来。
# R5 流程/状态类节须含代码块图——节名含「流程/生命周期」即触发,术语表的「开发流程」
# 是词条节、不是流程描述,属节名误匹配。
RULE_EXEMPT = {
"06-待办清单.md": {"R3", "R7"},
"07-体验验证清单.md": {"R3", "R5", "R7"},
"术语表.md": {"R5"},
}
@@ -193,27 +168,16 @@ def lint_file(path):
issues.append(f"R9 节点「{node_name}」直接内容 {node_count} 行(>{MAX_NODE_LINES})——超一页,拆为子节点")
infos.append(f"最大节点直接内容 {max(node_count, 0)} 行")
# R1 段落长度(登记处/清单文分域豁免,见 R1_EXEMPT 注)
# R1 段落长度
fname = Path(path).name
ex = RULE_EXEMPT.get(fname, set()) # 规则粒度分域豁免(见 RULE_EXEMPT 注)
if fname not in R1_EXEMPT:
for p in paras:
if len(p) > MAX_PARA:
issues.append(f"R1 散文段 {len(p)} 字(>{MAX_PARA}): {p[:50]}…")
for p in paras:
if len(p) > MAX_PARA:
issues.append(f"R1 散文段 {len(p)} 字(>{MAX_PARA}): {p[:50]}…")
# R2 表格占比(概念/方向/清单文分域豁免,见 R2_EXEMPT 注)
code_chars = sum(len(l) for l in code_lines)
table_chars = sum(len(l) for l in text.splitlines() if l.strip().startswith("|"))
total = max(len(text) - code_chars, 1)
ratio = int(100 * table_chars / total)
if fname in R2_EXEMPT:
infos.append(f"表格占比 {ratio}%(R2 分域豁免)")
elif ratio < MIN_TABLE_RATIO:
issues.append(f"R2 表格占比 {ratio}%(<{MIN_TABLE_RATIO}%)")
else:
infos.append(f"表格占比 {ratio}%")
# R2 表格占比已停用(见文件头注释)。
# R3 修订注记(代码块豁免;登记处/清单按 RULE_EXEMPT 豁免——变更记录节已整体禁设,无豁免域)
# R3 修订注记(代码块豁免;变更记录节已整体禁设,无豁免域)
body_no_code = "\n".join(l for l in text.splitlines() if l not in code_lines)
for m in (() if "R3" in ex else re.finditer(r"~~.+?~~", body_no_code)):
issues.append(f"R3 删除线残留: …{m.group(0)[:40]}…")
@@ -307,7 +271,7 @@ def lint_file(path):
issues.append(f"R7 最长括号跨度 {longest_span} 字(>{MAX_PAREN_SPAN})——疑似审计注记挤进正文")
infos.append(f"最长括号跨度 {longest_span} 字")
return {"file": str(path), "issues": issues, "infos": infos, "table_ratio": ratio}
return {"file": str(path), "issues": issues, "infos": infos}
def main():
@@ -347,7 +311,7 @@ def main():
else:
for r in results:
mark = "❌ FAIL" if r["issues"] else "✅ PASS"
print(f"\n{mark} {r['file']} (表格占比 {r['table_ratio']}%)")
print(f"\n{mark} {r['file']}")
for i in r["infos"]:
print(f" · {i}")
for issue in r["issues"]:
+2 -2
View File
@@ -166,7 +166,7 @@ class RunAcc:
self.end_hp = int(e.get("hp", 0))
def on_action(self, e):
# 刷新经济监控(07 清单⑦):只计受理成功的弃图刷新——被拒尝试不构成弃图。
# 刷新经济监控(体验验证清单⑦):只计受理成功的弃图刷新——被拒尝试不构成弃图。
if e.get("op") == "refresh" and e.get("accepted"):
self.refresh_count += 1
@@ -514,7 +514,7 @@ def render(res_files, args, out):
print("== 关卡级统计 ==", file=out)
print(" 口径:局=run 行切分(无 run 行的旧日志整文件一局);通关=result=victory;", file=out)
print(" 进BossHP=Boss首结算快照HP+首轮承伤−首轮治疗(精确反推);平均轮数=每局settlement数。", file=out)
print(" 弃图率=每局受理刷新次数(07⑦ 基线读数,与 MC 刷新/局同口径);", file=out)
print(" 弃图率=每局受理刷新次数(体验验证清单⑦ 基线读数,与 MC 刷新/局同口径);", file=out)
print(" 每轮刷新率=刷新次数÷结算轮数(组内加权)。阈值数字归调参批,本表只出基线。", file=out)
hdr = (f" {'关卡':>6} {'档位':>8} {'局数':>5} {'通关率':>7} {'平均轮数':>8} {'进BossHP均':>10} "
f"{'通关剩余HP均':>12} {'整局承伤均':>10} {'弃图率':>7} {'每轮刷新率':>9}")
+68
View File
@@ -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())
+169 -50
View File
@@ -1,15 +1,17 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
r"""pipeline_gate.py — 工作流程流水线闸(06 条目状态机+roadmap 迭代总表+需求卡片口径+一次交叉核)。
r"""pipeline_gate.py — 工作流程流水线闸(待办清单条目状态机+roadmap 迭代总表+需求卡片口径+一次交叉核)。
职责:机检 06 条目(标题元数据/属性齐备/状态词白名单/条件凭据/状态段累积制在场约束/特批/
职责:机检待办清单条目(标题元数据/属性齐备/状态词白名单/条件凭据/状态段累积制在场约束/特批/
零命中)与 roadmap 迭代总表(六列/状态四形态/卡片与报告存在性/编入时间与预计周期的
历史行「—」豁免/周期≤2 周可特批豁免/单元格白名单/零命中);「已编迭代↔已写卡段↔迭代行
卡片列」交叉核只此一处。2026-10-18 卡片冲突修订批补两条卡片机检:C1 关联文档同步面的
文档义务口径(生效 2026-10-17)、C2 设计事实源声明(生效 2026-10-18)——两条都按文件名
日期只追新规生效日之后的卡,旧卡属 git 历史留档不回头改。
日期只追新规生效日之后的卡,旧卡属 git 历史留档不回头改。2026-10-21 验收闭合批补 C3:
改动—验收对照表的结构检查(生效 2026-10-21)——只查节/列/行/必填格非空,不判断覆盖真假;
覆盖真假由独立验收审计判。
约束:每红项一行「文件:行号 判据名 一句白话说明」,exit 0=全绿 / 1=有红 / 2=输入件缺失;
卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);06 与 roadmap 均已为新格式
卡片与报告存在性相对 --repo-root 解析(缺省=本文件上两级);待办清单与 roadmap 均已为新格式
(2026-10-18 数据整改批),本闸已注册进 tools/run_all_gates.sh 文档域(08c 流水线闸)。
用法:python tools/pipeline_gate.py [--todo PATH] [--roadmap PATH] [--repo-root PATH]
"""
@@ -18,7 +20,7 @@ import re
import sys
from pathlib import Path
DEFAULT_TODO = Path("design/gdd/06-待办清单.md")
DEFAULT_TODO = Path("待办清单.md")
DEFAULT_ROADMAP = Path("roadmap.md")
SOURCE_WORDS = ("playnote", "会话提及", "审计发现", "批次移交")
@@ -47,6 +49,12 @@ SOURCE_MARKS = ("设计事实源", "唯一事实源") # 两种合格写法,
SYNC_HEADING_RE = re.compile(r"^#{2,3}\s*\d*\.?\s*关联文档同步面")
BANNED_DOC_MARKS = ("design/", "../gdd/", "../registry/", "../research/", "../qa/",
"../engineering/", "../ux/", "../art/", "roadmap.md", "AGENTS.md")
# C3 改动—验收对照表(2026-10-21 验收闭合批,生效 2026-10-21)
LEDGER_RULE_DATE = (2026, 10, 21)
LEDGER_HEADING_RE = re.compile(r"^#{2,4}\s*.*(验收对照表|改动[—\-]验收)")
LEDGER_REQUIRED = ("需求点", "改动", "验收测试", "真实入口")
LEDGER_PROOF_MARKS = ("撤销", "变异", "证明")
AUDIT_MARK = "验收审计"
DATE_RE = re.compile(r"\d{4}-\d{2}-\d{2}")
VER_RE = re.compile(r"[vV]\d+(?:\.\d+)*")
@@ -119,8 +127,12 @@ def check_title(title, line, issues, where):
return face
def check_segment(word, creds, line, issues, where, root):
"""判据 3 的单段校验。返回 (合法状态词|None, 已写卡路径|None, 已编迭代号|None)。"""
def check_segment(word, line, issues, where):
"""判据 3 的单段校验。返回 (合法状态词|None, 已编迭代号|None)。
2026-10-20 状态栏纯化批:状态行只写状态词(§12.1),括号凭据已废止——
闭合证据改到现状关联文档清单核对(check_todo 内),本函数只认状态词本身。
"""
def red(name, msg):
issues.append((where, line, name, msg))
@@ -128,40 +140,24 @@ def check_segment(word, creds, line, issues, where, root):
base = "已编迭代" if word.startswith("已编迭代") else word
if im is None and base == "已编迭代":
red("状态词非法", "「%s」缺迭代号(须「已编迭代 N」)" % word)
return None, None, None
return None, None
if base not in STATUS_WORDS:
red("状态词非法", "「%s」不在合法状态词白名单" % word)
return None, None, None
cred_text = ";".join(creds)
if base in ("文档设计已闭合", "已写入"):
if not (VER_RE.search(cred_text) and ("/" in cred_text or ".md" in cred_text)):
red("凭据缺失", "「%s」须带括号落点凭据(文档路径+版本号形态)" % base)
elif base == "美术设计已闭合":
if not ("art-bible" in cred_text.lower() and VER_RE.search(cred_text)):
red("凭据缺失", "「美术设计已闭合」凭据须含 art-bible 路径+版本号")
elif base == "已写卡":
rp = None
for c in creds:
m = RESEARCH_RE.search(c)
if m:
rp = m.group(0)
break
if rp is None:
red("凭据缺失", "「已写卡」括号内须为 reports/ 下的需求卡片路径")
elif not (root / rp).is_file():
red("卡片路径不存在", "已写卡路径 %s 相对仓库根不存在" % rp)
return base, rp, None
red("状态词非法", "「%s」不在合法状态词白名单" % base)
return None, None
if im is not None:
return base, None, int(im.group(1))
return base, None, None
return base, int(im.group(1))
return base, None
def check_status(raw, line, face, issues, where, root):
"""判据 3+状态段累积制(§12.1)+特批/备选。返回 (状态词列表, 已写卡路径列表, 已编迭代号|None)。
def check_status(raw, line, face, issues, where):
"""判据 3+状态段累积制(§12.1)+特批/备选。返回 (状态词列表, 已编迭代号|None)。
累积制:按+切段后段数不限,逐段各自合法;再查两条结构约束——
下游段(待写卡/已写卡/已编迭代)在场时上游闭合段必须在场(带日期特批可豁免);
双轨设计阶段(无下游段)必须恰两段且一文档一美术。
状态行只写状态词(§12.1):去掉特批标记与(备选)后缀后,残留任何括号即红——
证据写现状关联文档清单,由 check_todo 到正文核对;此处点名病因后短路,
不再产出碎片「状态词非法」误导红。
"""
content = raw.strip()
m = re.match(r"^\*\*(.+)\*\*$", content)
@@ -175,28 +171,28 @@ def check_status(raw, line, face, issues, where, root):
issues.append((where, line, "特批标记", "〔用户特批〕缺日期(带 YYYY-MM-DD 才放行跳轨)"))
content = APPROVAL_RE.sub("", content)
content = content.replace("(备选)", "").replace("(备选)", "").strip()
if re.search(r"[(())]", content):
issues.append((where, line, "状态行含括号内容",
"括号里的信息(路径/版本/说明)写进现状关联文档清单,状态行只留状态词(§12.1)"))
return [], None
segs = [s.strip() for s in content.split("+") if s.strip()]
if not segs:
issues.append((where, line, "状态词非法", "状态行内容为空"))
return [], [], None
words, cards, iter_no = [], [], None
return [], None
words, iter_no = [], None
for seg in segs:
creds = CRED_RE.findall(seg)
word = CRED_RE.sub("", seg).strip()
w, rp, it = check_segment(word, creds, line, issues, where, root)
w, it = check_segment(seg, line, issues, where)
if w:
words.append(w)
if rp:
cards.append(rp)
if it is not None:
iter_no = it
downstream = [w for w in words if w in DOWNSTREAM_WORDS]
if downstream and face in PRESENCE_FACES and not dated_approval:
need = []
if face in ("文档轨", "双轨") and "文档设计已闭合" not in words:
need.append("文档设计已闭合(凭据)")
need.append("文档设计已闭合")
if face in ("美术轨", "双轨") and "美术设计已闭合" not in words:
need.append("美术设计已闭合(凭据)")
need.append("美术设计已闭合")
if need:
issues.append((where, line, "在场约束",
"下游段(%s)在场但缺上游闭合段 %s(带日期特批可豁免)"
@@ -208,11 +204,16 @@ def check_status(raw, line, face, issues, where, root):
issues.append((where, line, "双轨段数",
"设计面=双轨设计阶段须恰两段且一文档一美术(现 %d 段:文档轨 %d、美术轨 %d)"
% (len(segs), n_doc, n_art)))
return words, cards, iter_no
return words, iter_no
def check_todo(text, issues, where, root):
"""06 全部判据。返回交叉核链(已编迭代条目 → (行号, 迭代号, 卡片路径|None))。"""
"""待办清单全部判据。返回交叉核链(已编迭代条目 → (行号, 迭代号, 卡片路径|None))。
2026-10-20 状态栏纯化批:闭合类状态词的证据核对从状态栏括号改到现状关联文档
清单(§12.1「状态行只写状态词」)——已写卡对 reports/ 卡片路径+磁盘存在性,
文档轨闭合/已写入对 design/ 路径+版本号,美术轨闭合对 art-bible+版本号。
"""
entries = parse_todo(text)
links = []
if not entries:
@@ -233,8 +234,45 @@ def check_todo(text, issues, where, root):
issues.append((where, line, "属性缺失", "条目缺「**%s**」属性" % req))
if "状态" not in attrs:
continue
words, cards, iter_no = check_status(
attrs["状态"], attr_line["状态"], face, issues, where, root)
words, iter_no = check_status(
attrs["状态"], attr_line["状态"], face, issues, where)
# 现状属性全文(含清单续行):证据核对只看现状,其他属性不算数
xian_lines, in_xian = [], False
for ln, raw in e["body"]:
m = ATTR_LINE_RE.match(raw.strip())
if m:
in_xian = m.group(1) == "现状"
if in_xian:
xian_lines.append(m.group(2))
elif in_xian:
xian_lines.append(raw)
xian = "\n".join(xian_lines)
xline = attr_line.get("现状", line)
# 证据核对一:已写卡 → 现状清单须有卡片路径,逐条验磁盘存在
cards = []
if "已写卡" in words:
cards = RESEARCH_RE.findall(xian)
if not cards:
issues.append((where, xline, "正文缺卡片路径",
"状态段「已写卡」在场,但现状关联文档清单里没有 "
"reports/需求卡片-*.md 路径(§12.1:证据写正文)"))
else:
for rp in cards:
if not (root / rp).is_file():
issues.append((where, line, "卡片路径不存在",
"现状清单里的卡片路径 %s 相对仓库根不存在" % rp))
# 证据核对二:文档轨闭合/反哺已写入 → 现状清单须有文档路径+版本号
# (待办清单正文里的指针是相对路径,形如 systems/… 或 design/…,只认 .md 收尾+版本号)
if "文档设计已闭合" in words or "已写入" in words:
if not (re.search(r"[^\s ,,()()]+\.md", xian) and VER_RE.search(xian)):
issues.append((where, xline, "正文缺设计定稿指针",
"状态段含文档轨闭合词,但现状清单里没有带版本号的文档路径(§12.1:证据写正文)"))
# 证据核对三:美术轨闭合 → 现状清单须有 art-bible 版本凭据
if "美术设计已闭合" in words:
if not ("art-bible" in xian.lower() and VER_RE.search(xian)):
issues.append((where, xline, "正文缺美术凭据",
"状态段「美术设计已闭合」在场,但现状清单里没有 "
"art-bible 版本凭据(§12.1:证据写正文)"))
need_gap = any(w in GAP_REQUIRED for w in words)
if need_gap and "缺口" not in attrs:
issues.append((where, line, "缺口属性",
@@ -381,13 +419,88 @@ def card_date(path):
return tuple(int(x) for x in found[-1].split("-"))
def check_ledger(text, path, issues):
"""C3 改动—验收对照表结构检查(生效 2026-10-21)。
只查:对照表节在不在、表头有没有五个必需列、有没有数据行、必填格空不空。
不判断测试是否真的覆盖改动——那是独立验收审计的职责,机器闸不猜语义。
"""
lines = text.splitlines()
start = None
for i, raw in enumerate(lines):
if LEDGER_HEADING_RE.match(raw.strip()):
start = i
break
if start is None:
issues.append((str(path), 1, "验收对照表缺失",
"须含「改动—验收对照表」节(2026-10-21 起,req-card §8)"))
return
header = None
header_line = 0
for i in range(start + 1, len(lines)):
s = lines[i].strip()
if s.startswith("#") and not LEDGER_HEADING_RE.match(s):
break
if s.startswith("|") and not SEP_ROW_RE.match(s):
header = row_cells(s)
header_line = i + 1
break
if header is None:
issues.append((str(path), start + 1, "验收对照表缺表",
"节内未找到表格"))
return
idx = {}
for name in LEDGER_REQUIRED:
hit = next((k for k, cell in enumerate(header) if name in cell), None)
if hit is None:
issues.append((str(path), header_line, "验收对照表缺列",
"缺「%s」列" % name))
return
idx[name] = hit
proof_hit = next((k for k, cell in enumerate(header)
if any(mark in cell for mark in LEDGER_PROOF_MARKS)), None)
if proof_hit is None:
issues.append((str(path), header_line, "验收对照表缺列",
"缺「撤销或变异证明」列"))
return
idx["证明"] = proof_hit
rows = 0
for i in range(header_line, len(lines)):
s = lines[i].strip()
if not s:
continue
if not s.startswith("|"):
break
if SEP_ROW_RE.match(s):
continue
cells = row_cells(s)
if len(cells) < len(header):
issues.append((str(path), i + 1, "验收对照表缺格",
"列数少于表头(%d<%d)" % (len(cells), len(header))))
continue
rows += 1
for name, k in idx.items():
if k >= len(cells) or not cells[k].strip():
issues.append((str(path), i + 1, "验收对照表空格",
"「%s」列为空" % name))
if rows == 0:
issues.append((str(path), header_line, "验收对照表零行",
"表内没有数据行"))
def check_cards(root, issues):
"""C1 文档义务口径(生效 2026-10-17)+C2 设计事实源声明(生效 2026-10-18)。
"""C1 文档义务口径(生效 2026-10-17)+C2 设计事实源声明(生效 2026-10-18)
+C3 改动—验收对照表结构(生效 2026-10-21)。
只检文件名日期达生效日的卡——更早的卡属已实现批次的历史留档,不回头改。
C1 判据:「关联文档同步面」表内,目标格指向 design/ 或根规则文件、而义务格仍写
「随批」者红(2026-10-17 req-card §6 改口径后,这类点只能列 reports/ 反哺点清单)。
C2 判据:全文既无「设计事实源」也无「唯一事实源」者红(两种写法都与库内实态对齐)。
C3 判据:有「改动—验收对照表」节;表头含需求点/改动/验收测试/真实入口/证明列;
至少一行;必填格非空;全文含「验收审计」声明。语义覆盖不在此判。
"""
dirn = root / CARD_DIR
if not dirn.is_dir():
@@ -404,6 +517,12 @@ def check_cards(root, issues):
issues.append((str(path), 1, "设计事实源缺失",
"须含「设计事实源」或「唯一事实源」声明句(req-card §2 三选一,"
"GDD 节+版本号/样张+art-bible 版本/工程直入证据指认)"))
if when >= LEDGER_RULE_DATE:
check_ledger(text, path, issues)
if AUDIT_MARK not in text:
issues.append((str(path), 1, "独立验收审计未声明",
"须在卡内写明独立验收审计入口与报告文件(2026-10-21 起,"
"test-conventions §9.7)"))
if when < SYNC_RULE_DATE:
continue
in_sec = False
@@ -429,8 +548,8 @@ def check_cards(root, issues):
def main():
ap = argparse.ArgumentParser(
description="工作流程流水线闸:06 条目状态机+roadmap 迭代总表+一次交叉核")
ap.add_argument("--todo", help="06-待办清单路径(缺省=<仓库根>/design/gdd/06-待办清单.md)")
description="工作流程流水线闸:待办清单条目状态机+roadmap 迭代总表+一次交叉核")
ap.add_argument("--todo", help="待办清单路径(缺省=<仓库根>/待办清单.md)")
ap.add_argument("--roadmap", help="roadmap 路径(缺省=<仓库根>/roadmap.md)")
ap.add_argument("--repo-root", help="仓库根(卡片/报告存在性的相对基准;缺省=本文件上两级)")
args = ap.parse_args()
@@ -454,7 +573,7 @@ def main():
if issues:
print("流水线闸 FAIL:红项 %d" % len(issues))
return 1
print("流水线闸 PASS:06 条目 %d · 总表数据行 %d · 交叉核 %d 链 · 卡片口径 %d 张全过"
print("流水线闸 PASS:待办清单条目 %d · 总表数据行 %d · 交叉核 %d 链 · 卡片口径 %d 张全过"
% (len(parse_todo(todo_text)), len(index), len(links), n_cards))
return 0
+2 -2
View File
@@ -7,7 +7,7 @@
# 段:
# ① shot_probe 七屏窗口化截图(menu/settings/career/camp/level/battle/status)
# → tools/shot_checker.py A-D 语义断言自动判定(A 屏级骨架全屏/B 文字对比度/C 明度三档带/
# D 文字禁压纹理;E 焦点预算=语义面仍归人闸不机检)。首跑存量 FAIL 按 S1 停机点全量呈报登记 06。
# D 文字禁压纹理;E 焦点预算=语义面仍归人闸不机检)。首跑存量 FAIL 按 S1 停机点全量呈报登记待办清单。
# ② 取证段(Q7:超时/零字节/文件缺失任一即 FAIL——「渲染真实发生」必须成立,不做像素判定):
# fx_shot_probe 三时刻抓帧(行到达/结算排播/轮末连帧)+ board_shot_probe L1-L3 棋盘抓帧。
# ③ 棋盘 golden 逐像素:board 整屏帧按 board_meta.json 取样坐标裁剪棋盘区
@@ -64,7 +64,7 @@ fi
if [ "$_rc" -eq 0 ]; then
python tools/shot_checker.py "$SHOT" shot_menu.png shot_settings.png shot_career.png shot_camp.png shot_level.png shot_battle.png shot_status.png | sed 's/^/[render_gate] /'
_rc="${PIPESTATUS[0]}"
[ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记 06)"
[ "$_rc" -ne 0 ] && echo "[render_gate] FAIL — shot_checker A-D 断言存在红(S1 停机点:首跑存量 FAIL 全量呈报登记待办清单)"
fi
echo "======== fx_shot_probe 三时刻取证(Q7) ========"
+2 -1
View File
@@ -27,7 +27,8 @@ ROOT = Path(__file__).resolve().parent.parent
# 清单的家——迭代批禁写 design/,只准往这里写,design/ 全目录对它只读。只放 .md,不放图(图像白名单不含此目录)。
TOP_DIRS = {"archive", "artwork", "design", "game", "sim-data", "tools", "reports"}
# 2) 根目录文件白名单
ROOT_FILES = {"AGENTS.md", "README.md", "roadmap.md", "stage.txt", "review-mode.txt", "playnote.md",
ROOT_FILES = {"AGENTS.md", "README.md", "roadmap.md", "待办清单.md", "体验验证清单.md",
"stage.txt", "review-mode.txt", "playnote.md",
".gitignore", ".zcodeignore"}
# 3) 图像允许目录(相对路径前缀;archive/ 为归档 zip 不扫)
# game/tests/golden/(棋盘 golden 基线=机检测试资产非美术素材,素材齐盘态生成入库【基线换代权限 Q6】)
+5 -2
View File
@@ -49,7 +49,7 @@ for _a in "$@"; do
done
# 控制台编码:本仓库闸件输出中文与 ✅/✗ 符号。Windows GBK 控制台下 python 打印这些字符会抛
# UnicodeEncodeError——06/10 两闸直接崩(假红)、09 的 FAIL 行被写成乱码字节(指纹漂移,
# UnicodeEncodeError——待办清单/10 两闸直接崩(假红)、09 的 FAIL 行被写成乱码字节(指纹漂移,
# 与基线比必判「红项清单变化」假红)、末尾汇总自身崩掉打不出结论。
export PYTHONIOENCODING=utf-8
@@ -183,8 +183,11 @@ gate "06 gdd_lint" "gdd_lint.log" "$PY" "$REPO/tools/gdd_lint.py"
gate "06b 变更记录节" "changelog.log" "$PY" "$REPO/tools/gdd_lint.py" --changelog-only
gate "07 doc_link" "doc_link.log" "$PY" "$REPO/tools/doc_link_audit.py" --strict
gate "08 doc_term" "doc_term.log" "$PY" "$REPO/tools/doc_term_audit.py" --strict
# ── 8c. 流水线闸(06 条目状态机+roadmap 迭代总表+一次交叉核,判据=doc-conventions §12)──
# ── 8c. 流水线闸(待办清单条目状态机+roadmap 迭代总表+一次交叉核,判据=doc-conventions §12)──
gate "08c 流水线闸" "pipeline.log" "$PY" "$REPO/tools/pipeline_gate.py"
# 8d 引用可核(tools/doc_ref_audit.py,2026-10-21 交付)暂不注册:新闸件无基线记录必判「闸况变化」拦回执,
# 而建档要改 game/tests/.gates/baseline.json——触 game/ 即要求代码域全量测试(process-conventions §3.10 推域口径),
# 文档域批按测试两入口铁规零跑游戏测试。注册与建档随下一个触 game/ 的批次一并做(待办清单在册)。
fi
# ── 6c. 回放确定性对拍(条件闸件;属代码域)──
if in_domain code; then
+1 -1
View File
@@ -2,7 +2,7 @@
# -*- coding: utf-8 -*-
"""技能池出招节奏审计(消费 playtest_*.jsonl 的 enemy_action 行)。
对应 06 待办「单拍行为环的体验验证(改型:技能池节奏体验验证)」的机器半边。
对应待办清单「单拍行为环的体验验证(改型:技能池节奏体验验证)」的机器半边。
判读带:
· 过涝 = 敌群连续出特殊技、无普攻基底——读数 = 单敌群同场战斗内最长连续特殊技连击
(连击 ≥3 记过涝信号,连击阈值 3 为代拍板默认:保底 M=4 与冷却 2-4 语义下 2 连属正常波动)