From 1ee65d854bc3939849325fd95479bad4da2cf39b Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 30 Oct 2026 02:17:05 +0800 Subject: [PATCH 1/2] =?UTF-8?q?=E5=B7=A5=E7=A8=8B(=E9=97=B8=E6=9C=BA?= =?UTF-8?q?=E6=8F=90=E9=80=9F=E6=89=B9):=2002=E5=88=86=E7=89=87=E5=B9=B6?= =?UTF-8?q?=E8=A1=8C=EF=BC=8B=E4=BB=A3=E7=A0=81=E5=9F=9F=E8=B5=B7=E6=89=8B?= =?UTF-8?q?=E8=87=AA=E6=A3=80=EF=BC=8Bworktree=E7=B4=A0=E6=9D=90=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E2=80=94=E2=80=94run=5Fall.gd=E5=8A=A0--shard/--shard?= =?UTF-8?q?-count=E5=88=86=E7=89=87=EF=BC=88=E4=B8=B2=E8=A1=8C=E9=81=93=3D?= =?UTF-8?q?RNG=E6=97=A5=E5=BF=97=E4=BC=9A=E8=AF=9D=E5=85=AD=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=E9=92=89=E7=AC=AC1=E7=89=87=E9=98=B2=E5=90=8C?= =?UTF-8?q?=E7=A7=8D=E5=AD=90=E6=97=A5=E5=BF=97=E4=BA=92=E8=B8=A9=EF=BC=9B?= =?UTF-8?q?=E7=A9=BA=E7=89=87=E6=94=BE=E8=A1=8C=EF=BC=9B=E9=80=90=E5=A5=97?= =?UTF-8?q?=E4=BB=B6=E6=97=A5=E5=BF=97=5Fs=E6=AE=B5=E9=98=B2=E8=B7=A8?= =?UTF-8?q?=E7=89=87=E8=A6=86=E5=86=99=EF=BC=89=EF=BC=8Bgate=5Funit?= =?UTF-8?q?=E5=A4=9A=E8=BF=9B=E7=A8=8B=E5=B9=B6=E8=A1=8C=EF=BC=88GATE=5FSH?= =?UTF-8?q?ARDS=E9=BB=98=E8=AE=A44=EF=BC=8C=3D1=E5=9B=9E=E8=90=BD=E5=8D=95?= =?UTF-8?q?=E8=BF=9B=E7=A8=8B=E5=8E=9F=E8=B7=AF=E5=BE=84=EF=BC=9B=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=8C=89=E7=89=87=E5=BA=8F=E5=90=88=E5=B9=B6=E4=BE=9B?= =?UTF-8?q?=E9=94=99=E8=AF=AF=E6=89=AB=E6=8F=8F=E4=B8=8E=E6=8C=87=E7=BA=B9?= =?UTF-8?q?=E5=8F=96=E6=9D=90=E2=80=94=E2=80=94=E6=8C=87=E7=BA=B9=E7=BB=BF?= =?UTF-8?q?=E9=97=B8=E8=AE=B0green=E7=BA=A2=E9=97=B8=E5=8F=96FAIL=E8=A1=8C?= =?UTF-8?q?=E6=8E=92=E5=BA=8F=E9=9B=86=E5=90=88=EF=BC=8C=E4=B8=8D=E5=8F=97?= =?UTF-8?q?=E7=89=87=E5=BA=8F=E5=BD=B1=E5=93=8D=EF=BC=89=EF=BC=8Brun=5Fall?= =?UTF-8?q?=5Fgates.sh=20code=E5=9F=9F=E8=B5=B7=E6=89=8B=E8=87=AA=E6=A3=80?= =?UTF-8?q?=EF=BC=88=E7=86=B5=E5=A2=9E=E9=A2=84=E6=A3=80=E7=BA=A2=E7=81=AF?= =?UTF-8?q?=E5=8D=B3=E6=AD=A2/master=E6=8E=A8=E8=BF=9B=E9=BB=84=E7=89=8C/w?= =?UTF-8?q?orktree=E7=B4=A0=E6=9D=90=E8=87=AA=E5=8A=A8=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=EF=BC=89=EF=BC=8Btools/sync=5Fworktree=5Fassets.sh=EF=BC=88?= =?UTF-8?q?=E4=B8=BB=E5=8C=BA=E2=86=92worktree=E5=A2=9E=E9=87=8F=E6=8B=B7a?= =?UTF-8?q?ssets=20art=E4=B8=8E.import=E8=BE=B9=E8=BD=A6=E3=80=81=E7=BC=BA?= =?UTF-8?q?=E4=BB=B6=E6=88=96=E7=BC=BA.godot=E6=97=B6=E7=BC=96=E8=BE=91?= =?UTF-8?q?=E5=99=A8=E5=AF=BC=E5=85=A5=E5=88=B7=E6=96=B0=EF=BC=9B=E7=9B=B8?= =?UTF-8?q?=E5=AF=B9=E8=B7=AF=E5=BE=84=E6=8B=B7=E8=B4=9D=E9=98=B2=E6=9D=82?= =?UTF-8?q?=E6=95=A3=E9=A1=B6=E5=B1=82=E7=9B=AE=E5=BD=95=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/tests/run_all.gd | 79 ++++++++++++++++++++++++++++++++++- tools/run_all_gates.sh | 62 ++++++++++++++++++++++++--- tools/sync_worktree_assets.sh | 54 ++++++++++++++++++++++++ 3 files changed, 188 insertions(+), 7 deletions(-) create mode 100644 tools/sync_worktree_assets.sh diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 489f219b..93e05bd1 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -4,6 +4,14 @@ # 零命中/空值=用法错 quit(2)。两个来源分开定性——开发期直跑(默认)仅供最窄确认,不产闸回执、 # 不作验收凭据;闸机关联档选跑(--only-source=gates,由 tools/run_all_gates.sh 在 02 闸传入) # 是收口验收跑批本体,回执 scope=related。无任何选跑参数=全量态,行为与全量口径完全一致。 +# 分片态:追加 `-- --shard=k --shard-count=n`(须成对,1≤k≤n,n≥2 才有意义)——把选套集 +# (--only 过滤后)切成 n 片互斥子集,本进程只跑第 k 片;闸机据此多进程并行缩短 02 闸墙钟。 +# 分片分配走「串行道优先」:开 RNG 事件日志会话的套件(SERIAL_PATTERNS,new_adventure 调用方) +# 全部钉第 1 片——事件日志按种子命名同文件追加(user://logs/rng_log_.jsonl),跨片并行 +# 写同一文件互踩内容,断言日志纯净序的套件(cross_system_flow)必读错行。新增日志会话套件必须 +# 同步登记 SERIAL_PATTERNS,漏登记=并行期日志互踩假红。空片合法(--only 小集 × 多片):空片 +# 直接 quit(0) 放行,判定职责由其余片承担。各片独立退出码,由闸机聚合;片内三硬闸与 +# 快速失败语义不变,逐套件日志名加 _s 段防跨片覆写。 # 协议:套件清单逐条"套件 → 期望退出码";致命类独立进程(expect=1)。 # 执行形态:逐套件起独立 Godot 子进程实跑 #(OS.get_executable_path() 解析本引擎二进制——--script 模式无 autoload/全局标识符, @@ -30,6 +38,23 @@ const SUITE_TIMEOUT_DEFAULT_SEC := 120 # 快速失败阈值(可调常量,初值 3):连续 N 个套件超时即中止整轮。 const CONSECUTIVE_TIMEOUT_ABORT := 3 +# 串行道套件(路径子串匹配):开 RNG 事件日志会话(new_adventure)的调用方——事件日志按种子 +# 命名同文件追加(user://logs/rng_log_.jsonl),跨分片并行写同一文件互踩;断言日志内容 +# 的套件(cross_system_flow)必须读到本会话纯净追加序。分片分配时全部钉第 1 片(见头注分片态)。 +# 新增日志会话套件时同步登记此处,漏登记=并行期假红。 +const SERIAL_PATTERNS: Array[String] = [ + "board_generator_test", + "node_map_generator_test", + "minesweeper_rules_test", + "rng_stream_boot_test", + "cross_system_flow_test", + "node_map_determinism_test", +] + +# 分片态成员:0=未分片(单进程原路径);>0=第 _shard_k/_shard_n 片(_run_suite 取名用)。 +var _shard_k := 0 +var _shard_n := 0 + func _init() -> void: # 选跑态解析:--only=<词[,词…]>(经 -- 分隔的 user args)——对套件 res:// 路径做子串匹配; @@ -46,6 +71,22 @@ func _init() -> void: only_terms.append(term) elif a == "--only-source=gates": only_from_gates = true + elif a.begins_with("--shard="): + var sv := a.trim_prefix("--shard=").strip_edges() + if sv.is_valid_int(): + _shard_k = int(sv) + elif a.begins_with("--shard-count="): + var sn := a.trim_prefix("--shard-count=").strip_edges() + if sn.is_valid_int(): + _shard_n = int(sn) + if (_shard_k == 0) != (_shard_n == 0): + push_error("[run_all] --shard 与 --shard-count 必须成对出现") + quit(2) + return + if _shard_n < 0 or (_shard_n > 0 and (_shard_k < 1 or _shard_k > _shard_n)): + push_error("[run_all] 分片参数非法(须 1≤k≤n,n≥1):k=%d n=%d" % [_shard_k, _shard_n]) + quit(2) + return if has_only and only_terms.is_empty(): push_error("[run_all] --only= 空值——用法错(选跑禁把零套件当全量)") quit(2) @@ -64,6 +105,14 @@ func _init() -> void: print("[run_all] GATE FILTERED RUN — 闸机关联档选跑 %d/%d 套件(--only=%s)" % [suites.size(), manifest.size(), ",".join(only_terms)]) else: print("[run_all] FILTERED RUN — 选跑 %d/%d 套件(--only=%s)——仅供开发期最窄确认,不产闸回执、不作验收凭据" % [suites.size(), manifest.size(), ",".join(only_terms)]) + var sharded := _shard_n > 1 + if sharded: + suites = _shard_suites(suites, _shard_k, _shard_n) + if suites.is_empty(): + print("[run_all] SHARD %d/%d — 本片零套件,空片放行(判定职责由其余片承担)" % [_shard_k, _shard_n]) + quit(0) + return + print("[run_all] SHARD %d/%d — 本片 %d 套件" % [_shard_k, _shard_n, suites.size()]) print("[run_all] unit/contract suite entry — %d suites" % suites.size()) if DisplayServer.get_name() != "headless": push_warning("[run_all] 建议以 --headless 运行(D2 冒烟协议)") @@ -101,7 +150,10 @@ func _init() -> void: push_error("[run_all] %d 个套件失败" % failures) quit(1) return - print("[run_all] all green (%d suites%s)" % [suites.size(), ",filtered" if filtered else ""]) + var tag := ",filtered" if filtered else "" + if sharded: + tag += ",shard %d/%d" % [_shard_k, _shard_n] + print("[run_all] all green (%d suites%s)" % [suites.size(), tag]) quit(0) @@ -203,7 +255,8 @@ func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> Dictiona # 后又没有退出码读取 API,故由 cmd /V:ON 在套件退出后把 !ERRORLEVEL! 回显落盘, # 值与阻塞式 execute 经 cmd 回传的同一。 var proj := ProjectSettings.globalize_path("res://") - var log := proj + "/tests/.gates/run_all%s_%02d.log" % ["_filter" if filtered else "", idx] + var shard_tag := "" if _shard_n == 0 else "_s%d" % _shard_k + var log := proj + "/tests/.gates/run_all%s%s_%02d.log" % ["_filter" if filtered else "", shard_tag, idx] var codefile := log + ".code" if FileAccess.file_exists(codefile): DirAccess.remove_absolute(codefile) # 陈值防护:上轮临界残件不许冒充本轮退出码 @@ -293,6 +346,28 @@ func _filter_suites(manifest: Array, terms: PackedStringArray) -> Array: return out +# 分片分配:串行道套件(SERIAL_PATTERNS 命中)全钉第 1 片;其余按片轮转(片内相对序 j%n+1)。 +# 分配对每个 k 确定性、互斥且完备——各片并起来=传入全集零重复零遗漏。 +func _shard_suites(suites: Array, k: int, n: int) -> Array: + var out: Array = [] + var j := 0 + for suite in suites: + var path: String = suite.get("path", "") + var is_serial := false + for pat in SERIAL_PATTERNS: + if path.contains(pat): + is_serial = true + break + if is_serial: + if k == 1: + out.append(suite) + else: + if j % n + 1 == k: + out.append(suite) + j += 1 + return out + + # 引擎二进制必须取 console 版:OS.get_executable_path() 返回的是 GUI 子系统 exe #(Godot_v4.7.2-stable_win64.exe),它起的子进程 stdout/stderr 句柄无效——输出全丢, # 脚本错误扫描就成了扫空气。GODOT_BIN 优先,其次按命名推导 *_console.exe。 diff --git a/tools/run_all_gates.sh b/tools/run_all_gates.sh index 943d227a..d70f7fa5 100644 --- a/tools/run_all_gates.sh +++ b/tools/run_all_gates.sh @@ -300,6 +300,24 @@ if in_domain code; then fi fi +# ── 代码域起手自检(闸承载:只省烧闸,不做合回判定——真判定在 00–11 与合回 guard)── +if in_domain code; then + # worktree 素材同步:素材走 gitignore 不入 git,worktree 冷检出缺件=呈现断言红的环境成因; + # 主工作区直跑=脚本内自动无操作。 + bash "$REPO/tools/sync_worktree_assets.sh" + # 熵增预检:垃圾目录/越界文件先于长跑批暴露(熵增闸在批末,撞上才红=整轮白烧)。 + if ! "$PY" "$REPO/tools/repo_structure_audit.py" >"$STAMP_DIR/entropy_preflight.log" 2>&1; then + echo "[gates] 起手自检红:熵增预检不过——顶层目录越界/根文件异动,先清理再跑闸(日志 $STAMP_DIR/entropy_preflight.log)" + grep -aE "越界|✗|违例" "$STAMP_DIR/entropy_preflight.log" | tail -5 | sed 's/^/ | /' + exit 1 + fi + # master 推进预警(worktree 才会触发):master 已越过本分支=合回 guard 第 6 况必拦, + # 先并 master 再出闸,省一轮白烧;本闸回执照出(开发期跑批不受阻)。 + if [ -f "$RECEIPT" ] && ! git -C "$REPO" merge-base --is-ancestor master HEAD 2>/dev/null; then + echo "[gates] 警告:master 已推进至本分支之外——本闸回执照出,但合回前须并 master 重出闸。" + fi +fi + echo "闸机跑批 · 域=$DOMAIN · 仓库 $REPO" echo "起跑 commit: $(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '非git目录')" echo "python: $PY" @@ -322,24 +340,58 @@ gate() { # gate <名称> <日志名> <命令...> return 0 } -gate_unit() { # run_all 是 --script 模式,退出码为唯一判据 +gate_unit() { # run_all 是 --script 模式,退出码为唯一判据;分片态=多 Godot 进程各跑一片 local name="02 单元与集成测试" # 关联档选跑:把套件词列表经 --only= 传给 run_all(闸机来源标记 --only-source=gates, # 入口据此打关联档横幅、不落「仅供开发期」免责句);全量态不传任何选跑参数。 local -a _user_args=() if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then - _user_args=(-- "--only=${ONLY_TERMS// /,}" --only-source=gates) + _user_args+=("--only=${ONLY_TERMS// /,}" "--only-source=gates") fi + local _shards="${GATE_SHARDS:-4}" echo "" echo "════════════════════════════════════════════" echo " $name(--script 模式,退出码为唯一判据)" if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then echo " 关联档选跑:--only=$ONLY_TERMS" fi + if [ "$_shards" -gt 1 ]; then + echo " 分片并行:$_shards 进程(GATE_SHARDS 可调,=1 回落单进程原路径)" + fi echo "════════════════════════════════════════════" - ( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd "${_user_args[@]}" ) \ - >"$STAMP_DIR/unit.log" 2>&1 - local rc=$? + local rc=0 + if [ "$_shards" -le 1 ]; then + local -a _cmd=(--headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd) + [ ${#_user_args[@]} -gt 0 ] && _cmd+=(-- "${_user_args[@]}") + ( cd "$REPO" && "$GODOT" "${_cmd[@]}" ) \ + >"$STAMP_DIR/unit.log" 2>&1 + rc=$? + else + # 分片并行:串行道套件由 run_all 内部分配钉第 1 片(RNG 日志会话跨片互踩,见其头注); + # 各片独立日志,退出码聚合(任一片非 0=闸红);日志按片序合并供错误扫描与指纹取材 + #(指纹只取 FAIL 行排序集合、绿闸记 green,片序与合并不改变指纹)。 + local -a _pids=() + local k + for k in $(seq 1 "$_shards"); do + ( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd \ + -- "${_user_args[@]}" "--shard=$k" "--shard-count=$_shards" ) \ + >"$STAMP_DIR/unit_shard_$k.log" 2>&1 & + _pids+=($!) + done + local _shard_rc _any=0 + for k in $(seq 1 "$_shards"); do + wait "${_pids[$((k - 1))]}" + _shard_rc=$? + echo "[run_all] 分片 $k/$_shards 退出码 $_shard_rc" + [ "$_shard_rc" -ne 0 ] && _any=1 + done + [ "$_any" -ne 0 ] && rc=1 + : >"$STAMP_DIR/unit.log" + for k in $(seq 1 "$_shards"); do + echo "── unit 分片 $k/$_shards ──" >>"$STAMP_DIR/unit.log" + cat "$STAMP_DIR/unit_shard_$k.log" >>"$STAMP_DIR/unit.log" + done + fi # 三硬闸之二(脚本错误扫描):子套件的 SCRIPT ERROR/Parse Error 只写 stderr,入口内收不到, # 只能在编排层扫本闸日志。命中即红——此类错误不改退出码、且会中断所在函数的剩余断言。 if [ "$rc" -eq 0 ] && ! "$PY" "$REPO/tools/error_scan.py" "$STAMP_DIR/unit.log" >>"$STAMP_DIR/unit.log" 2>&1; then diff --git a/tools/sync_worktree_assets.sh b/tools/sync_worktree_assets.sh new file mode 100644 index 00000000..0e82c10e --- /dev/null +++ b/tools/sync_worktree_assets.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# worktree 素材同步:主工作区 → 本 worktree 增量拷贝 gitignore 面(game/assets/art/ 终稿与 .import 边车)。 +# 素材不入 git,worktree 冷检出与主区新收素材之间必然缺件——呈现/样张断言红的环境成因。 +# 只在 worktree 生效(主工作区直跑=无操作);有新拷件或缺 .godot 时跑一次编辑器导入刷新 +#(headless --script 模式不触发 import,素材在盘而 .ctex 缺位照样加载失败)。 +# 用法:tools/sync_worktree_assets.sh(闸机 code 域起手自动调用;可单独手跑) +# 纪律:拷贝一律相对路径定位(在源根 cd 后按相对路径落盘)——绝对路径 --parents 会在 +# 目标仓顶层误产盘符目录树(杂散目录即此误产,熵增闸判红)。 +set -u +REPO="$(cd "$(dirname "$0")/.." && pwd)" +GITDIR="$(git -C "$REPO" rev-parse --git-dir 2>/dev/null)" +COMMON="$(git -C "$REPO" rev-parse --git-common-dir 2>/dev/null)" +if [ -z "$GITDIR" ] || [ -z "$COMMON" ] || [ "$GITDIR" = "$COMMON" ]; then + echo "[assets] 主工作区:无同步面" + exit 0 +fi +MAIN="$(git -C "$REPO" worktree list --porcelain 2>/dev/null | sed -n 's/^worktree //p' | head -1)" +if [ -z "$MAIN" ]; then + echo "[assets] 取不到主工作区路径(git worktree list 为空),跳过同步" + exit 0 +fi +MAIN="$(cd "$MAIN" && pwd)" +[ "$MAIN" = "$REPO" ] && exit 0 + +_copied=0 +if [ -d "$MAIN/game/assets/art" ]; then + _new="$(comm -13 <(cd "$REPO/game/assets/art" 2>/dev/null && find . -type f | sort) \ + <(cd "$MAIN/game/assets/art" && find . -type f | sort) | wc -l)" + if [ "$_new" -gt 0 ]; then + mkdir -p "$REPO/game/assets/art" + cp -ru "$MAIN/game/assets/art/." "$REPO/game/assets/art/" + _copied=$((_copied + _new)) + fi +fi +_imports="$(comm -13 <(cd "$REPO/game" 2>/dev/null && find . -name '*.import' -type f | sort) \ + <(cd "$MAIN/game" && find . -name '*.import' -type f | sort) | wc -l)" +if [ "$_imports" -gt 0 ]; then + (cd "$MAIN/game" && find . -name '*.import' -type f -print0) | while IFS= read -r -d '' f; do + if [ ! -f "$REPO/game/$f" ]; then + mkdir -p "$REPO/game/$(dirname "$f")" + cp "$MAIN/game/$f" "$REPO/game/$f" + fi + done + _copied=$((_copied + _imports)) +fi +if [ "$_copied" -gt 0 ] || [ ! -d "$REPO/game/.godot" ]; then + echo "[assets] worktree 素材同步:补拷 $_copied 件(含 .import 边车),刷新导入缓存" + "${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}" \ + --headless --editor --quit --path "$REPO/game" >/dev/null 2>&1 + echo "[assets] 导入刷新完成" +else + echo "[assets] worktree 素材同步:无缺件" +fi +exit 0 From db5abb8527847de0f9c4219cdfb1b93e6b113c3c Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Fri, 30 Oct 2026 02:23:57 +0800 Subject: [PATCH 2/2] =?UTF-8?q?=E6=94=B6=E5=B0=BE(=E9=97=B8=E6=9C=BA?= =?UTF-8?q?=E6=8F=90=E9=80=9F=E4=B8=8E=E9=98=B2=E4=BD=9C=E5=BA=9F=E6=89=B9?= =?UTF-8?q?):=20=E5=BE=85=E5=8A=9E=E6=9D=A1=E7=9B=AE=E5=AE=8C=E6=88=90?= =?UTF-8?q?=E6=95=B4=E6=9D=A1=E5=88=A0=E9=99=A4=E2=80=94=E2=80=94=E5=AE=9E?= =?UTF-8?q?=E6=B5=8B=E5=85=A8=E9=87=8F=E9=97=B83=E5=88=8629=E7=A7=92?= =?UTF-8?q?=E5=85=A8=E7=BB=BF66=E5=A5=97=E4=BB=B6(=E5=AF=B9=E7=85=A7?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E7=BA=A624=E5=88=86);=E7=AA=84=E8=AF=95?= =?UTF-8?q?=E8=BD=A66=E6=80=81;=E7=B4=A0=E6=9D=90=E5=90=8C=E6=AD=A5?= =?UTF-8?q?=E5=AE=9E=E6=88=98=E8=A1=A5=E6=8B=B7247=E4=BB=B6;iter=E6=8A=80?= =?UTF-8?q?=E8=83=BD=E5=90=88=E5=9B=9E=E6=AE=B5=E4=B8=89=E8=BF=9E=E6=88=90?= =?UTF-8?q?=E6=96=87;C=E6=89=B9=E6=8E=A2=E9=92=88=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=B9=B6=E8=A1=8C=E6=9C=AA=E7=AB=8B=E9=A1=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 待办清单.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/待办清单.md b/待办清单.md index d37ed442..0ef086eb 100644 --- a/待办清单.md +++ b/待办清单.md @@ -2,14 +2,6 @@ 本清单是流水线的唯一登记处:要做的事先在此登记一条,随流水线段推进改状态,做完整条删除;各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。 -#### 闸机提速与防作废批——02 分片并行+代码域起手自检+worktree 素材同步(2026-10-30 登记,来源=会话提及) - -**实况**:迭代 42 合回六轮被拦复盘:单轮代码域闸约 24 分钟(02 串行 66 套件约 5.5 分钟+探针渲染约 3.5 分钟+对拍实跑约 14 分钟),六轮累计约 2 小时;被拦主因=master 中途被另一会话推进(回执作废整轮重跑)、worktree 缺 gitignore 素材(迭代 43 新套件断言的 PNG 不入 git)、闸进程被会话取消连带杀掉、操作失误产杂散目录到批末熵增闸才红。证据=`tools/run_all_gates.sh:325`(gate_unit 单 Godot 进程串行编排)、`game/tests/run_all.gd:206`(逐套件独立子进程实跑、编排层串行为唯一瓶颈)、`tools/run_all_gates.sh:470`(回执指纹绿闸记 green、红闸取 FAIL 行排序集合——输出顺序不影响指纹,并行化免记账换代)、`game/core/rng_service.gd:134`(事件日志按种子命名同文件追加——开日志会话的套件跨分片并行互踩,须钉同一分片串行)。本批内容:B=`run_all_gates.sh` code 域起手自检(熵增预检红灯即止、master 推进黄牌、worktree 素材自动同步 `tools/sync_worktree_assets.sh`)+iter 技能合回段补「并 master → 自检 → 出闸 → 立即合并」次序;A=`run_all.gd` 加 `--shard/--shard-count` 分片(串行道套件钉第 1 片)+`gate_unit` 多进程并行(`GATE_SHARDS` 可调,=1 回落单进程原路径)。C 批(探针/渲染三段并行)本轮缓做不立项。 - -**改动面**:工具代码 - -**状态**:待执行 - #### 架构审查优化讨论——全库架构审计读数的逐条裁决与整改分批(2026-10-29 登记,来源=审计发现) **实况**:2026-10-29 全库架构静态审计完成,读数见 reports/审计-全库架构审查-2026-10-29.md(七节:总评/健康面/P0/P1/P2/待裁点/取证口径;零测试执行零引擎运行,全部结论带 文件:行号)。健康面已逐条核干净:Core 零 UI 八项判据全零命中、core 内 RNG 单点 100% 成立、字符串式信号连接全库 0 处、core 层零超长函数、19 表 ↔ 19 schema 一一对应、无循环 preload。问题集中在三条「写了没闸守」的纪律被实际破坏:①数值单一事实源被 18 处硬编码击穿,最危险两处是 `presentation/main.gd:787` 与 `:790` 把 `rap_refresh_cost=8` 与 `refresh_cap_per_round=2` 写死进提示文案(紧邻 783 行是正确读表的),表改文案不改、玩家直接受错误数字;另有 4 组代码自标「机制结构常量/产出约束」的字面量(`node_map_generator.gd:12` `MAX_WIDTH`、`action_economy.gd:14` `CHARGE_LAYER_CAP`、`board_data.gd:9` `SPAWN_AREA_CELLS`、`node_map_generator.gd:83-94` 域字面量)是否入表待裁。②`game/sim/` 与 `game/core/` 四组平行实现——扫雷推理引擎双份(`board_generator.gd` `measure_inferability` 族 vs `auto_player.gd` `_deduce` 族,输入集口径不同:前者含 safe_set oracle、后者 no-peek)、伤害掷点期望重算(`damage_probe.gd:196-201`)、档位排序与驱动循环双份(`level_autoplay.gd` vs `treasure_item_table.gd:44` 与 `battle_driver.gd:17`)、FakeDB 伪造表喂生成器——直接威胁「模拟读数 = 游戏真实数值」这一数值验证前提。③RNG 单点纪律在 core 外失守(sim 4 处/probe 1 处/tests 3 处自建 `RandomNumberGenerator`,均显式定种、确定性尚存),而 `static_scan.sh` 的 S1/S2 扫描面只含 `DIR_CORE` + `DIR_PRES`。守卫面另有三缺口:S8 是 9 文件显式清单(新增结算文件忘登记=静默不扫,缺位自检只防删改不防增)、S9 类型闸与 S13 质量闸扫描面止步于 core+data+presentation(sim/probe/tests 三层除 S11/S12 外零闸)、S13 三处豁免常态化到最大户文件(`battle_orchestrator`/`main.gd`/`tune_sim_controller`);另有四个 core 文件零测试点名(`attune_loadout`/`battle_fx_host`/`meta_state_store`/`slot_store`)。P2 级:`core/settlement/` 五段类直读写编排器私有成员 20+ 处(`oc._damage.*`/`oc._enemy_system.*`,GDScript 无编译期检查、改名静默断裂);架构文档三处漂移(`architecture.md:110` 登记的 `core/hero_ledger.gd` 盘上不存在且 `hero_ledger_test.gd:171` 显式断言它必须不存在、`:135` 行数过期、EC8 热重载门后 `ConfigDb.reload()` 零调用方且走 `load()` 拿不到磁盘新值);class_name 级双向依赖(`core/config_db` → `data/schemas/board_gen_table` → `core/board_data.SPAWN_AREA_CELLS`)与 `NODE_TYPES` 词表双份;`MetaProgression` 是唯一用 `get_node("/root/...")` 取依赖、唯一跨 Autoload 直调的单例。