merge: 迭代29测试分层与门禁语义修正批入库(十提交, worktree g-plan2-iter29-testlayers)
This commit is contained in:
commit
fff4f120d3
28 files changed
+1436
-123
No files matched your search
@@ -141,8 +141,8 @@
|
||||
| 模块 | 载体 | 独占职责 | 对外暴露 | 判定口径 |
|
||||
|---|---|---|---|---|
|
||||
| **run_smoke**(系统级断言面) | `tools/run_smoke.py` | 冒烟总入口与判定权:headless 跑 `res://smoke/smoke_all.gd` 全量(轻闸首项,迭代 28 后在册 1125 项检查)+ 三闸判定 | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备、判定无洞:①退出码 = 0;②摘要行「冒烟完成:N 项检查,0 项失败」在案且失败数为 0;③输出扫 `SCRIPT ERROR` / `Parse Error` 标记行。外部 wall-clock 超时在位(超时 = FAIL) |
|
||||
| **run_all**(单元套件) | `game/tests/run_all.gd` | 27 套件单元测试统一入口:套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑 | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;**SCRIPT ERROR 扫描缺失**(脚本错误不改套件退出码时静默漏过);**断言数下限缺失**(套件零断言退出 0 照样绿) |
|
||||
| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排:默认段 ui_input_probe / ui_struct_probe / ui_contrast_probe,`--click` 加跑 ui_click_probe,`--full` 加跑 ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);**多数探针不查进程退出码**(仅个别段以 grep PASS 标记兜底)——退出码判定缺失 |
|
||||
| **run_all**(单元套件+L2 集成) | `game/tests/run_all.gd` | 33 套统一入口(32 单元/集成 + 见下):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸) | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;三硬闸齐备(入口内扫子套件输出 + 编排层扫 `unit.log`;`Kit.finish` 零断言返回专用码 2)。子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空 |
|
||||
| **probe_gate**(场景探针) | `tools/probe_gate.sh` | headless 场景探针段编排:默认段 ui_input_probe / ui_struct_probe / layout_probe / ui_contrast_probe / **screen_flow_probe**(L3 屏壳切换),`--click` 加跑 ui_click_probe,`--full` 加跑 ui_probe / ui_tune_probe / t17_defer_probe / fx_probe;孤儿 Godot 预清 + 批末残留清点(必须 0) | 段级 FAIL 输出 + 汇总退出码 | 每段 timeout 守卫在位(挂死 124/137 = FAIL,不静默);判定统一走 `verdict` 函数——退出码为唯一权威判据,PASS 串降级为附加判据,逐段跑 `tools/error_scan.py` |
|
||||
| **render_gate**(视觉 golden) | `tools/render_gate.sh` | 窗口化渲染闸:①shot_probe 七屏截图 → shot_checker A-D 语义断言;②fx_shot_probe / board_shot_probe 取证(超时/零字节/文件缺失任一 = FAIL);③棋盘 golden 逐像素容差比对(差 ≤16 / changed ≤2%,provisional)+ 菜单 golden;④批末孤儿残留清零 | 段级 FAIL 输出 + 汇总退出码 | 查退出码在位(每段 timeout 守卫 + 退出码/取证文件双检);**SCRIPT ERROR 扫描缺失**;断言数下限不适用(判定主体 = golden 容差与取证文件存在性,非断言计数) |
|
||||
| **static_scan**(静态扫描域) | `game/tests/static_scan.sh` | 静态扫描域 S1–S10 可执行载体(grep 规则 + `tools/audit_type_hints.py`〔S9〕+ `tools/audit_file_headers.py`〔S10〕) | fail 聚合退出码(任一硬失败规则命中即 exit 1) | 查退出码在位;SCRIPT ERROR 扫描与断言数下限不适用(逐规则命中即红,无运行时错误面与断言计数);S3 现为 warning 级、不参与 fail 判定(见下表) |
|
||||
|
||||
@@ -150,9 +150,9 @@
|
||||
|
||||
| 硬闸 | run_smoke | run_all | probe_gate | render_gate | static_scan |
|
||||
|---|---|---|---|---|---|
|
||||
| **退出码优先** | 已落实(闸①) | 已落实(套件 manifest expect 协议) | **缺失**(多数探针不查退出码) | 已落实(段级退出码 + 取证双检) | 已落实(fail 聚合退出) |
|
||||
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | **缺失** | **缺失** | **缺失** | 不适用(静态域无运行时错误面) |
|
||||
| **断言数下限** | 无独立下限(闸②摘要行必须在案,防「跑一半仍 PASS」;脚本错误吞断言路径由闸③兜住) | **缺失**(27 套件跑完不校验断言总数,零断言套件可绿) | 不适用(逐段 PASS 标记判定,无断言总数概念) | 不适用(golden 容差 + 取证存在性判定) | 不适用(逐规则命中判定) |
|
||||
| **退出码优先** | 已落实(闸①) | 已落实(套件 manifest expect 协议) | 已落实(`verdict` 函数逐段判退出码,PASS 串不作判据) | 已落实(段级退出码 + 取证双检) | 已落实(fail 聚合退出) |
|
||||
| **SCRIPT ERROR 扫描** | 已落实(闸③,外部扫描——GDScript 无脚本错误陷阱 API,闸设在外部包装) | 已落实(入口内扫子套件输出;子进程的 stdout/stderr 须先落盘——`OS.execute` 管道在本机恒空) | 已落实(逐探针段 `tools/error_scan.py`,标记与 run_smoke 同源) | **缺失**(本批未收——判定主体是 golden 容差与取证存在性;登记 06) | 不适用(静态域无运行时错误面) |
|
||||
| **断言数下限** | 无独立下限(闸②摘要行必须在案,防「跑一半仍 PASS」;脚本错误吞断言路径由闸③兜住) | 已落实(`Kit.finish` 零断言返回专用退出码 2——与「断言失败=1」区分,防致命类套件的 expect 协议放过) | 已落实(退出码 0 但无该探针签名标记亦判红——防静默退出) | 不适用(golden 容差 + 取证存在性判定) | 不适用(逐规则命中判定;另有扫描面为空即判红的零命中自检) |
|
||||
|
||||
背景事实(run_smoke 头注在案):GDScript 运行时脚本错误不改进程退出码、不计入冒烟失败数、且静默中断所在函数剩余断言——2026-09-19 状态记录 83 实证「309 项 0 失败」实为断言未跑全。这是 SCRIPT ERROR 扫描必须存在于判定层的原因。
|
||||
|
||||
@@ -164,15 +164,29 @@
|
||||
|---|---|---|
|
||||
| S1 | 玩法代码禁 `randomize()`、自建 `RandomNumberGenerator`(D2 Rule 5) | **在位**(硬失败;扫描面 game/core + game/presentation) |
|
||||
| S2 | 禁全局随机函数 `randi()` / `randf()` / `randf_range()`——全局 RNG 绕开 RngService(specialist 补登) | **在位**(与 S1 同段硬失败;实例方法 `.randi()` 排除、`rng-seed-exempt` 豁免注记在案) |
|
||||
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免)(D2 Rule 2) | **未落地**(脚本内仅 warning 级占位、不参与 fail 判定;且扫描路径 `core/` 自 2026-09-24 布局迁移后不存在——实际为 game/core,现行实跑恒零命中) |
|
||||
| S3 | core/ 禁 ConfigDB 外 data 表加载(`load(` / `preload(` / `ResourceLoader.load`;schema class_name 直引豁免)(D2 Rule 2) | **在位**(warning 级——class_name 直引误报需人工复核;路径已由裸 `core` 修正为 `game/core`,此前恒零命中静默空转;豁免 config_db.gd 自身=数值单一事实源的正主) |
|
||||
| S4 | core/ 禁 `extends Control/Node2D`、禁 `.tscn` preload(D2 Rule 7) | **在位**(硬失败) |
|
||||
| S5 | core/ 禁裸引用 autoload 全局标识符(`ConfigDB` / `RngService` 直接写 = --script 模式 parse error)——构造注入 only(specialist 升格) | **未落地**(脚本 TODO 注记在案:grep 近似误报多,需 AST 级或精确白名单;已有真实反面案例) |
|
||||
| S5 | core/ 禁裸引用 autoload 全局标识符(`ConfigDB` / `RngService` 直接写 = --script 模式 parse error)——构造注入 only(specialist 升格) | **在位**(硬失败;形态=标识符后紧跟 `.` 的成员访问,排除注释行与字符串字面量) |
|
||||
| S6 | core↔core 信号连接只许方法 Callable(禁 lambda 捕获 self);发射者禁持有订阅者反向引用 | **未落地**(脚本 TODO 注记在案) |
|
||||
| S7 | B1 编排唯一性:B2/B3/B4 API 符号命中文件 ⊆ B1 文件集;A2/B3/B4 零相互直调(B1 AC-43) | **未落地**(脚本 TODO 注记在案) |
|
||||
| S8 | 结算路径 `await` 零命中(B1 结算/编排文件集);字面量扫描 = warning 级 + 人工三要素复核(D2 AC-14 口径) | **未落地**(脚本 TODO 注记在案) |
|
||||
|
||||
域外扩充两件(均硬失败在位):**S9** 类型闸(`tools/audit_type_hints.py`,迭代 27 T5——产品码无类型容器/有值返回缺类型/参数缺类型);**S10** 文件头闸(`tools/audit_file_headers.py`——头注 ≤8 行 + 日期/变更叙事黑名单)。
|
||||
|
||||
### 三层承载与判定原则
|
||||
|
||||
测试分三层,各有在册承载与入闸方式(细目见 `game/tests/README.md`):
|
||||
|
||||
| 层 | 判什么 | 承载 | 入闸 |
|
||||
|---|---|---|---|
|
||||
| L1 单元 | 最单元的逻辑代码对不对 | `game/tests/unit/` | `run_all.gd`(闸 02) |
|
||||
| L2 集成 | 跨系统闭环对不对 | `game/tests/integration/` | 同上(同一入口同一协议) |
|
||||
| L3 系统/场景 | 跑起来之后 UI 与场景切换对不对 | `game/smoke/*.tscn` 探针 | `probe_gate.sh`(闸 03/04) |
|
||||
|
||||
**判定原则**:验收不能只是「跑一遍没崩」。三硬闸(退出码/脚本错误扫描/断言数下限)是判定的底线,
|
||||
缺一即判定有洞;「日志里写着 PASS」不作判据。另有两类静默失效必须机制性防范——规则扫描面为空
|
||||
(路径写错即恒零命中恒绿)与子进程输出丢失(本机 `OS.execute` 管道恒空),两者都以显式自检拦住。
|
||||
|
||||
### 依赖约束
|
||||
|
||||
测试域禁止反向依赖呈现层具体实现。五闸及其调用的探针场景、审计脚本只允许依赖两类边界:
|
||||
|
||||
@@ -696,3 +696,21 @@
|
||||
|
||||
---
|
||||
|
||||
|
||||
#### 测试域遗留(2026-10-16 迭代 29 测试分层与门禁语义修正批登记)
|
||||
|
||||
**现状**:迭代 29 把验收语义从「跑一遍没崩=通过」扭到「三层完备+判定可靠」,三硬闸(退出码/脚本错误扫描/断言数下限)已落地,L2/L3 从零建起承载。本批未收的余量集中登记在此,均不阻塞、不排期。
|
||||
|
||||
| # | 事项 | 归属与要点 |
|
||||
|---|---|---|
|
||||
| 1 | **余下 3 个 core 模块仍零指名单测**:`board_data`/`reward_economy`/`skill_lottery` | 测试基建批。本批补了 minesweeper_rules/board_generator/config_db_resolver/config_db_cross_validator 四件;余三件按同一手法补(判据=能否在 `--script` 模式不加载 autoload 验完) |
|
||||
| 2 | **render_gate 的 SCRIPT ERROR 扫描仍缺失** | 测试基建批。判定主体是 golden 容差与取证文件存在性,脚本错误不改退出码时无扫描面;本批只把矩阵状态如实记为「缺失」,收它的写法待裁 |
|
||||
| 3 | **静态扫描域 S7/S8 仍未落地** | 工程债批。S7=B1 编排唯一性(B2/B3/B4 API 符号命中文件 ⊆ B1 文件集);S8=结算路径 `await` 零命中。本批补的是 S3 路径与 S5,S6 亦仍在 TODO |
|
||||
| 4 | **E 级卫生件**(test_kit static var 串台风险、4 套件自建断言出口、157 处 untyped 容器、76 处 lambda) | 工程债批。本批只统一了**新写**断言的出口,存量未动;test_kit 的 static var 是「并发跑批」的前置障碍(并发分层方案的前提) |
|
||||
| 5 | **行覆盖率度量缺失** | 基建评估项。Godot 无内建、第三方属新基建;现行补洞靠「零指名清单 + 人工判定」,是否引入度量待裁 |
|
||||
| 6 | **`smoke_all.gd` 拆分**(单文件承载 1127 项检查) | 工程债批。本批按契约只做入闸与改名裁点,不拆不重构;拆分登记在册 |
|
||||
| 7 | **parity 逐位一致比对的触面判定为粗粒度路径匹配** | 测试基建批。触面表含 `battle_orchestrator.gd`,改该文件即要求批准发起 MC;若嫌过重可精化到「是否改动取数/结算数值」的语义面 |
|
||||
|
||||
**归属**:测试基建批/工程债批(不阻塞,不排期)
|
||||
|
||||
**状态**:**在册待裁**(七条均为本批未收的余量;迭代 29 已交付的部分见实现报告与 architecture.md 测试与质量域节)
|
||||
@@ -0,0 +1,167 @@
|
||||
# 迭代 29 实现报告 · 测试分层与门禁语义修正批(2026-10-16)
|
||||
|
||||
- 需求卡片:`design/research/需求卡片-测试分层与门禁语义修正批-2026-10-15.md`(**v2.0**,三轮复查定稿、零待裁点)
|
||||
- 开工门 U0:用户 `/iter 29` 发令(2026-10-16)。基线 master `752c9a0a`
|
||||
- worktree:`g-plan2-iter29-testlayers`,分支 `iter29-testlayers-20261016`,八提交(`698d864d` … `c3b64030`)
|
||||
- 用户原始诉求(本批要兑现的三条):①最单元的逻辑代码有它的单元测试;②整体的模块有它的接口测试;
|
||||
③整体的系统在执行之后,也有它 UI 方面的和场景切换相关的测试。**并且验收不能只是"冒烟"——
|
||||
可以是冒烟,但更重要的是逻辑正确性**
|
||||
|
||||
---
|
||||
|
||||
## 一、批次概览
|
||||
|
||||
| 卡 | 内容 | 状态 |
|
||||
|---|---|---|
|
||||
| T0 | 证据坐实与基线冻结(三反例实测 + 墙钟 + 行号复核 + 存量红留档) | ✅ |
|
||||
| T1 | 本地闸判定三硬闸(退出码/脚本错误扫描/断言数下限) | ✅ |
|
||||
| T2 | 术语订正 + CI 残留清除(机械面) | ✅ |
|
||||
| T3 | L2 接口层建承载(`integration/` + parity 条件闸件收编) | ✅ |
|
||||
| T4 | L3 屏壳切换探针 `screen_flow_probe`(入轻闸默认段) | ✅ |
|
||||
| T5 | L1 覆盖补洞(4 个 core + 呈现层两件) | ✅ |
|
||||
| T6 | 扫描域:S3 路径修正 + 零命中自检 + 补 S5 | ✅ |
|
||||
| T7 | 游离脚本台账 + golden 时效判定 | ✅ |
|
||||
| T8 | 文档同步闸(架构事实源回写 + 06 登记)与全闸 | ✅ |
|
||||
|
||||
**读数**:冒烟 **1127/0**(与开工日持平,本批零新增冒烟断言);单元套件 **27 → 33 套**;
|
||||
在闸探针 **9 → 10 件**(新增 `screen_flow_probe`);轻闸 `--quick` 墙钟 363s → 405s;
|
||||
全量重闸 **exit=0,存量红零新增**。
|
||||
|
||||
---
|
||||
|
||||
## 二、四件事的交付
|
||||
|
||||
### ① 本地闸三硬闸(本批改造成效最大的一卡)
|
||||
|
||||
判定语义从「超时 + 日志含 PASS 串」扭到三硬闸,**每一条都有反例实证**(不是推断,是跑出来的):
|
||||
|
||||
| 硬闸 | 反例构造 | 改前判定 | 改后判定 |
|
||||
|---|---|---|---|
|
||||
| 退出码为唯一权威判据 | 探针打印 PASS 后 `quit(1)` | `[probe_gate] 全绿` ❌ | `FAIL — 退出码=1(PASS 串不抵)` ✅ |
|
||||
| 脚本错误扫描命中即红 | 套件中途触发 `SCRIPT ERROR` 后仍退出 0 | `[run_all] PASS` ❌ | `FAIL — 输出含脚本错误标记` ✅ |
|
||||
| 断言数为零即红 | `Kit.finish()` 时 `checks==0` | `all green (0 checks)` ❌ | 返回专用退出码 **2** → `FAIL` ✅ |
|
||||
|
||||
实现要点:
|
||||
- `probe_gate.sh` 八件探针判定统一走 `verdict` 函数(退出码优先 → 脚本错误扫描 → 签名标记附加判据);
|
||||
`ui_click_probe` 本就走退出码语义、日志不写 PASS,签名标记传它自己的 `click_probe` 串,不强加。
|
||||
- 新闸件 `tools/error_scan.py`:错误标记 `import` 自 `run_smoke.py` 的 `ERROR_MARKERS`,**禁第二份清单**。
|
||||
- `run_all.gd` 侧遇到两个本机事实(都写进注释防复发):
|
||||
① `OS.execute` 的输出管道在本机恒空(实测 0 行)——子套件输出必须交给系统命令解释器重定向落盘再读;
|
||||
② `OS.get_executable_path()` 返回的是 GUI 子系统 exe,它起的子进程 stdout/stderr 句柄无效——
|
||||
必须换 `*_console.exe`(`GODOT_BIN` 优先)。
|
||||
- 断言下限用**专用退出码 2**(不是 1):致命类套件 manifest 期望 1,零断言若也返回 1 会被 expect 协议放过。
|
||||
|
||||
### ② L2 接口层从零建承载
|
||||
|
||||
- 建 `game/tests/integration/`,首个集成用例 `save_rng_roundtrip_test.gd`(17 条断言):存档 ⇄ RNG 快照往返,
|
||||
跨 SaveManager(落盘 + 版本迁移链)× AdventureRunner(断点消费)× RngService(续跑零跳号)× 停点态确定性。
|
||||
与 `unit/core/breakpoint_resume_test` 的分工:那件是单测只验首战开板一致,本件验跨系统闭环,
|
||||
含**「旧版本档经迁移链后断点仍可续玩」**这条真实玩家路径。
|
||||
- `tools/parity_anchor.py` 收编为重闸 **11 号条件闸件**:每次跑批都对 merge-base 做一遍触面判定
|
||||
(`data/tables`/`rng_service`/`save_manager`/`meta_progression`/`settlement`/`damage_math`/
|
||||
`damage_controller`/`battle_orchestrator`/`adventure_monte_carlo`),未触面 SKIP;**触面也不自动跑**——
|
||||
它调的是 MC 脚本,属「跑批发起前须用户批准」红线,闸只负责把触面判定做一遍并显式要求批准
|
||||
(批准后 `PARITY_APPROVED=1` 重跑)。**本批即判为触面(改了 `battle_orchestrator.gd`),故挂在 NEED-APPROVAL 待你批准**(见第六节)。
|
||||
|
||||
### ③ L3 屏壳切换探针
|
||||
|
||||
新建 `game/smoke/screen_flow_probe.gd/.tscn`:走 菜单→营地→菜单→冒险→(关卡屏⇄战斗屏)→放弃→终局→菜单
|
||||
六步主流程与两条回退路径,每步断言屏级互斥 + 流程状态位,入**轻闸默认段**。全库 `change_scene` 零命中、
|
||||
屏切换全靠 visible 互斥,这正是最容易被装配顺序改动悄悄破坏又最难被单测覆盖的面。
|
||||
|
||||
判据两处订正(首跑实测抓出):① 屏标记不能裸看 `visible`——战斗容器 `battle_root` 在局外也 visible,
|
||||
会把 boot 态误判成「菜单与冒险同时在场」;② 互斥不能用「可见屏计数 ≤1」——终局覆盖层合法叠在冒险内容之上
|
||||
(终局态 `is_adventure` 尚未收束,是设计如此),改为冲突对判定,合法叠加仅「终局+冒险」这一对。
|
||||
|
||||
### ④ L1 补洞 + 术语订正 + 台账清理
|
||||
|
||||
- 新增六套(全部进 manifest):`board_generator`(9 条)/`minesweeper_rules`(12 条)/
|
||||
`config_db_resolver`(8 条)/`config_db_cross_validator`(4 条)/`hud_presenter`(5 条)/`battle_narrator`(9 条)。
|
||||
三屏壳的断言面由 ③ 的探针承担,不另起第二载体。
|
||||
- 术语:「冒烟」回归本义=秒级存活检查,写进 README 术语小节,并显式点破两处历史遗留命名
|
||||
(`run_smoke.py` 与 `game/smoke/` 名带 smoke,实际承载的是系统级断言面与场景探针——判定看语义不看文件名)。
|
||||
- 台账:`game/smoke/` 15 个不带 `.tscn`、不入闸的手动入口件逐个定性成表(用途 + 触发方式 + 结果由谁判),
|
||||
存疑一律按在役登记并标注,禁「没看见=死件」式清理。
|
||||
- golden:**先判再动**——实测棋盘三图 changed 0.00%/0.00%/0.00%、菜单 0.15%,均远低于 2% 阈值 ⇒ 判有效,
|
||||
留在位不重制(禁「重制一下就绿了」掩盖真实视觉回归)。
|
||||
|
||||
---
|
||||
|
||||
## 三、代拍板追认单(超清单必呈报;本批共 12 项)
|
||||
|
||||
| # | 代拍板事项 | 处置与依据 |
|
||||
|---|---|---|
|
||||
| 1 | 三硬闸的具体实现写法 | `probe_gate` 抽 `verdict` 函数;错误扫描抽独立闸件 `tools/error_scan.py`(标记 import 自 `run_smoke.py`) |
|
||||
| 2 | 断言数下限的阈值 | 每套件 ≥1(防误伤小套件);零断言用专用退出码 2 与「断言失败=1」区分 |
|
||||
| 3 | `integration/` 内部目录分类 | 平铺,按系统命名(`save_rng_roundtrip_test.gd`)——首例而已,够用不加层 |
|
||||
| 4 | 新集成套并入 `run_all` manifest 的执行细节 | 零新机制,与单元套件同一入口同一协议 |
|
||||
| 5 | 新单测/新探针的文件命名与落点 | 沿用 `[system]_[feature]_test.gd` 与 `presentation/` 归位 |
|
||||
| 6 | 4 个 core 模块单测的断言面取舍 | 各 4–12 条真值断言,期望值一律由盘上/注入内容数出来(零玩法数值字面量) |
|
||||
| 7 | **断言本身写错且与现行设计冲突**时的修法(契约 1 唯一例外) | 见第四节「经你裁决的四项」,逐条列明 |
|
||||
| 8 | 游离脚本「在役/失效」的判定 | 存疑按在役登记并标注;`CLAUDE.md` 在册入口禁当死件 |
|
||||
| 9 | S5 规则的正反例构造 | 形态=标识符后紧跟 `.` 的成员访问,排除注释行与字符串字面量;core 实测零命中 |
|
||||
| 10 | 提交切分与分支命名 | 八提交按 T0–T8 一卡一笔;分支 `iter29-testlayers-20261016` |
|
||||
| 11 | **S3 零命中自检的实现形态** | 扫描路径收为单点变量 + 规则自己数扫描面文件数,为零即判红,且规则体包进自检成立分支 |
|
||||
| 12 | **条件闸件的 diff 触面路径表** | 写进 `run_all_gates.sh` 内(不自造第二份清单),见上文 ② |
|
||||
|
||||
---
|
||||
|
||||
## 四、经你裁决的四项(施工中冒出的分歧,未自答)
|
||||
|
||||
1. **`fx_grant_gp` 补进行来源断言豁免表**(你裁「本批补豁免」):`grant_segment.gd:221` 发「专长·GP 入账」
|
||||
效果行(source_class=`fx_grant_gp`、origin_cell=(-1,-1)),而 `SOURCE_EXEMPT_CLASSES` 只豁免了 `fx_reveal`
|
||||
⇒ 每局触发行来源断言失败 7 次,因脚本错误不改退出码被闸长期漏过。改动=一行常量加一个字符串,
|
||||
零玩法行为变更(迭代 21 同款修过 `fx_reveal`,本例是同一张表的漏网)。
|
||||
2. **manifest 增 `negative` 字段**(负面套件豁免错误扫描一闸):仅 `reward_dangling_treasure` 一件——
|
||||
坏表是它的测试目标,输出里的 Parse Error 属误伤。退出码与断言下限照判,不是开后门。
|
||||
3. **`config_db_test` 的 10 条被吞断言**(你裁「本批全修」):`rows` 是 `Array[Dictionary]` 定型属性,
|
||||
直接赋 untyped 数组字面量触发 `Invalid assignment` ⇒ **中断整个函数**,敌人表「行集构成三条纪律」
|
||||
的 10 条断言从未执行过(文件 31 处 check,实跑 21 条)。改 `rows.assign([...])` 后 26 条全跑,
|
||||
其中两条正向断言见红,根因是**夹具缺 depth-0 锚行**(`validate` 的 anchor 规则),补锚行后全绿。
|
||||
4. **`system_contracts_test` 的空桩换真实实例**(同上):`RngStub`/`UnitContainerStub` 赋给定型成员
|
||||
报 `Trying to assign value of type ''`,`RngStub` 类删除,`UnitContainer`/`StatusContainer` 改真实实例。
|
||||
|
||||
---
|
||||
|
||||
## 五、闸与读数(全量重闸 `tools/run_all_gates.sh`,commit `c3b64030`)
|
||||
|
||||
| 闸 | exit | 说明 |
|
||||
|---|---|---|
|
||||
| 01 冒烟全量 | 0 | **1127 项检查 0 项失败**(与开工日持平) |
|
||||
| 02 单元套件 | 0 | **33 套**(26 单元 + 1 集成 + 6 新单测);编排层另扫 `unit.log` 零命中 |
|
||||
| 03 探针 --click | 0 | 五件(含新增 screen_flow)全绿,残留 Godot=0 |
|
||||
| 04 探针 --full | 0 | 四件全绿 |
|
||||
| 05 渲染闸 | 0 | shot_checker 全过;棋盘 golden 三图 0.00%;菜单 0.15%(≤2%) |
|
||||
| 06 gdd_lint / 07 doc_link / 08 doc_term | 0 | doc_term 首跑抓到我新写的退役词「对拍」1 处,已改正 |
|
||||
| 08b active.md 体量 | 0 | — |
|
||||
| 09 静态扫描 | 1 | **存量红零新增**:S10 文件头存量 127 处,指纹与基线 `4354b2aa72a7` 一致 |
|
||||
| 10 熵增闸 | 0 | — |
|
||||
| 11 回放确定性对拍 | NEED-APPROVAL | 本批 diff 触及结算面,对拍需你批准发起(见第六节) |
|
||||
|
||||
**验收通过**(`all_green=true`,存量红零新增)。轻闸 `--quick` 墙钟 405s(开工日 363s + 新探针约 40s)。
|
||||
|
||||
---
|
||||
|
||||
## 六、待裁与遗留
|
||||
|
||||
0. **【合回状态:本批暂不合回 master,等两个非本批红处理完】** 同步 master(`60449941`)后复跑重闸冒出两个红,
|
||||
已取证均与本批零关系:①**渲染闸** `menu_boot.png` changed=4.61%(超 2%容差)——在主区(不含本批任何改动)
|
||||
跑同一道闸,结果一模一样 4.61%,棋盘三张 golden 仍 0.00%;②**gdd_lint** `01-核心循环.md` R5
|
||||
「一场战斗的流程」节无流程图——master 提交 `272cc1ad` 的说明已自陈为并行会话 `9846a29f` 的既有问题。
|
||||
按「禁带红合并」纪律,2026-10-16 用户裁「不合回,等你处理」:本批九提交留在分支 `iter29-testlayers-20261016`,
|
||||
待这两条在 master 上处理完(菜单 golden 需核热区后按 Q6 口径显式重制;01-核心循环 R5 归其引入批补流程图),
|
||||
我重跑重闸后立即合回。**不代为刷新基线**——那样会把 4.61% 的真实视觉差异固化成存量,热区原因不再有人追。
|
||||
|
||||
1. **11 号闸的 parity 对拍要不要跑一次**(需你批准,我不能自行发起):触面判定是机械的路径匹配,
|
||||
本批对 `battle_orchestrator.gd` 的改动实际只是加一个断言豁免常量(零玩法行为变更、零数值改动)。
|
||||
- 若按「MC 无数值改动零跑」红线 ⇒ 不跑,闸维持 NEED-APPROVAL(回执里留名,不判失败);
|
||||
- 若要验证一次 ⇒ 批准后 `PARITY_APPROVED=1 tools/run_all_gates.sh`(三变体约 10–15 分钟)。
|
||||
2. **GDD 反哺点三条**(设计权威文档不随批改,待你裁决后另批落文):
|
||||
- `design/dev/code-conventions.md` 测试纪律节:是否把「三硬闸」与「新写测试件全类型标注」写成硬纪律;
|
||||
- `design/dev/test-conventions.md`:是否补「判定可靠性优先于覆盖率」一节(本批的核心命题);
|
||||
- `design/gdd/07-体验验证清单.md`:`screen_flow_probe` 已转机检,是否把对应的人工走查项销号。
|
||||
3. **06 已登记本批未收的七项**(不阻塞不排期):余下 3 个 core 零指名模块、`render_gate` 的脚本错误扫描缺失、
|
||||
S7/S8(S6 亦仍在 TODO)、E 级卫生件、行覆盖率度量、`smoke_all` 拆分、parity 触面判定偏粗。
|
||||
4. **本批最有价值的副产品**(建议转设计侧裁决是否立规):脚本错误会**静默中断所在函数的剩余断言**而
|
||||
套件仍报「全绿」——本批至少三处实证(敌人表行集纪律 10 条零覆盖、`Callable` 形参类型写错让解析函数
|
||||
返回 null)。已由三硬闸之二机制性拦住,但是否把它写成 code-conventions 的硬纪律待裁。
|
||||
@@ -62,13 +62,27 @@
|
||||
|
||||
### 2.1 开工日实测读数区(T0-③ 填;本卡不预估值)
|
||||
|
||||
> **2026-10-16 开工日实采**(master `752c9a0a`——开工当日调试入口批刚合入,冒烟由 1126 升 1127;本批 worktree 与该提交同点实测,主区与 worktree 读数一致)。
|
||||
|
||||
| 项 | 开工日 master 读数 | 出处 |
|
||||
|---|---|---|
|
||||
| 冒烟项数/失败数 | T0 填(参照:iter28 收口 **1125/0**) | `tools/run_smoke.py` |
|
||||
| `run_all` 套件数 | T0 填(参照:**27** 套) | `run_all.gd` manifest |
|
||||
| 在闸探针数 | T0 填(参照:轻闸 4+`--click` 1+`--full` 4;`game/smoke/*.tscn` 共 12) | `tools/probe_gate.sh` |
|
||||
| 轻闸/重闸墙钟 | T0-② 填(参照:`--full` 四件实测 17.4/49.6/63.0/21.9s) | `tools/run_all_gates.sh` |
|
||||
| 各闸存量红清单 | T0 填(参照:`baseline.json` 十一闸现全绿) | `game/tests/.gates/baseline.json` |
|
||||
| 冒烟项数/失败数 | **1127 / 0** | `tools/run_smoke.py`(`game/tests/.gates/smoke.log`) |
|
||||
| `run_all` 套件数 | **27** 套(全绿) | `run_all.gd` manifest |
|
||||
| 在闸探针数 | **9**(轻闸默认 4 + `--click` 1 + `--full` 4);`game/smoke/*.tscn` 共 **12** | `tools/probe_gate.sh` |
|
||||
| 轻闸/重闸墙钟 | **run_all 全套 34s / smoke_all 71s / 轻闸 `--quick` 363s**(重闸待 T8 实测) | `tools/run_all_gates.sh` |
|
||||
| 各闸存量红清单 | **01/02/03/04/05/06/07/08/08b/10 全绿;09 静态扫描 exit=1(S10 文件头存量 127 处,指纹 `4354b2aa72a7`)** | `game/tests/.gates/baseline.json` |
|
||||
|
||||
### 2.1b T0-① 反例实测留档(缺陷坐实证据,2026-10-16)
|
||||
|
||||
三个靶子在 **未修的现行闸** 上实跑,全部**判绿**=三硬闸缺陷坐实(不是推断,是跑出来的):
|
||||
|
||||
| 靶子 | 构造 | 现行闸判定 | 结论 |
|
||||
|---|---|---|---|
|
||||
| a · 零断言套件 | `Kit.finish()` 时 `checks==0` → `quit(0)` | `run_all` 输出 `[run_all] PASS …(exit=0)`,汇总 `all green (29 suites)` | **断言数下限缺失**坐实(V3) |
|
||||
| b · 脚本错误套件 | 断言中途触发 `SCRIPT ERROR: Invalid call. Nonexistent function 'nonexistent_method' in base 'Nil'` 后继续退出 0 | 同上 → PASS | **SCRIPT ERROR 扫描缺失**坐实(V2);该错误类型**未**中断后续断言(`all green (2 checks)`),但退出码与汇总仍全绿——只有外部扫描能抓 |
|
||||
| c · PASS 后 `quit(1)` 探针 | 临时令 `ui_input_probe` 打印 PASS 后 `get_tree().quit(1)` | `probe_gate.sh` 输出 `[probe_gate] 全绿` | **退出码优先缺失**坐实(V1) |
|
||||
|
||||
> 三件靶子均为临时注入,验证后已删除/`git checkout` 还原(`game/tests/run_all.gd`、`smoke/ui_input_probe.gd` 回到 HEAD 态)。T1 落地后须用同三靶子复跑,判红才算修好。
|
||||
|
||||
### 2.2 本批专属增量事实(架构文档未载;A 类直读,2026-10-16 复核)
|
||||
|
||||
|
||||
Reference in new issue
Block a user