工程(合回闸关联档+闸机编排快速失败): 收口跑批按改动关联面选测+红树快速失败——①tools/run_all_gates.sh 关联档选闸(推广 2026-10-28 影响面选闸):00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降(测量面批不再免 00);03/04 探针与 05 渲染闸维持影响面口径;02 单元与集成改「选套件跑」——套件集=需求卡片申报∪diff 推导并集(申报=本批 diff 触及卡片∪roadmap 进行中行登记卡片的对照表「验收测试」列测试文件名;推导=脚本内套件族映射表按 game/ 代码面文件归族,映射不到一律横切),横切(公共件前缀 config_db*/rng_service*/save_manager*/meta_progression*/main*、project.godot autoload 注册面、game/data/tables、PARITY 触面表)→ 02 回落全部套件;PARITY 触面表上移单点定义(gate 11 与横切判定共用同一清单);回执加 scope(full|related)与 scope_terms 字段,worktree-guard 校验逻辑不动——②game/tests/run_all.gd 快速失败:连续 3 个套件超时(可调常量 CONSECUTIVE_TIMEOUT_ABORT 初值 3)即中止整轮、汇总已见失败与超时清单、退出码非 0,不连续超时照旧逐个回收;_run_suite 改返 {fail, timed_out};新增 --only-source=gates 来源标记(关联档选跑横幅,区别于开发期选跑免责句),无选跑参数时行为与现状完全一致——③规则同批同步(禁只改闸不改规则):AGENTS.md「交付前验收」句与硬性红线改按关联档选测口径(所选域绿即可合回,全量回归由用户自发、合入后无自动兜底,2026-10-29 用户裁)+规范导航行同步;process-conventions §3 第 8 条合回闸细则同口径;test-conventions §4 改「提交前验证」、code-conventions §3 主题行随改;game/tests/README.md 工具速查 run_all.gd 行(快速失败+闸机关联档选跑+manifest 实数 65)与挂死防护注、门禁/本地闸回执字段句同步——④待办清单「合回闸关联档」「闸机编排快速失败」两条目完成整条删除。孤立验证:推导沙箱七场景(行为面归族/触面横切/测量面批/main* 横切/project.godot 横切/申报并集/横切优先)+run_all 闸源选跑 9/65 绿+2s 截止连续超时第 3 个中止(exit 1 带汇总)
This commit is contained in:
1 parent
6abaf67dd5
commit
9f94c9e100
8 files changed
+231
-77
No files matched your search
@@ -16,7 +16,7 @@
|
||||
|
||||
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
|
||||
|
||||
- **交付前验收 = 跑 `tools/run_all_gates.sh`,按本批改动域选档(`--code` 只跑代码域/`--docs` 只跑文档域/不带参数=两域全量),所选域全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,**域划分也只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单。域按**文件性质**判:代码域=`game/` 下非 `.md` 的行为载体(代码/测试脚本/探针/场景/数据表/素材),`game/` 内 `.md` 与 `game/` 外一切路径=文档域;同批两类都有时跑全量档。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回时由 worktree-guard 按 diff 文件性质推出该验哪个域再校验回执(六况拦截细则见 process-conventions §3.9);非代码面的直改批验收=commit 点 git 钩子自动跑 `--docs`,无 worktree 与回执流程
|
||||
- **交付前验收 = 跑 `tools/run_all_gates.sh`,按本批改动域选档(`--code` 只跑代码域/`--docs` 只跑文档域/不带参数=两域全量),所选域全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,**域划分与选闸算法也只写在它这一个地方**,闸件增减与分域调整都只改它,文档禁另列清单。域按**文件性质**判:代码域=`game/` 下非 `.md` 的行为载体(代码/测试脚本/探针/场景/数据表/素材),`game/` 内 `.md` 与 `game/` 外一切路径=文档域;同批两类都有时跑全量档。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件(关联档=只跑与本批改动关联的那部分测试),00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降,横切面改动(公共件/数据表/autoload/数值·存档·RNG·结算触面)回落全部套件;算法与套件族映射表只在闸脚本内定义。所选域全绿即可合回;全量回归由用户自发发起,合入后无自动兜底。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 与 `scope` 字段),合回时由 worktree-guard 按 diff 文件性质推出该验哪个域再校验回执(六况拦截细则见 process-conventions §3.9);非代码面的直改批验收=commit 点 git 钩子自动跑 `--docs`,无 worktree 与回执流程
|
||||
- 单件直跑(调试期最窄确认用,**不构成验收**):冒烟测试 `python tools/run_smoke.py`(`game/tests/smoke_boot.tscn` 极小启动场景)· 系统测试 `python tools/run_system_test.py`(宿主 `game/tests/system_test.gd`)· 场景探针 `…console.exe --headless --path . res://probe/ui_probe.tscn` · 单元与集成 `… --script res://tests/run_all.gd`
|
||||
- 调参批量:`--script res://sim/adventure_monte_carlo.gd`(全冒险期望)· `res://sim/reward_expectation.gd`(奖励期望);关卡完成率/难度类测量一律走全冒险
|
||||
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://sim/verifier_batch.gd`,报告与原始数据落 `sim-data/`)
|
||||
@@ -43,7 +43,7 @@
|
||||
| 批次开工 / 流程纪律 | `design/engineering/process-conventions.md`——车道铁律(代码面 worktree 批/其余主区直改)、五段流水线、批次清单、测试两入口与 MC/闸机细则、像素纪律 |
|
||||
| 流水线段入口 | 技能对照——doc-design=文档设计(兼任反哺点落文)/asset-ingest=美术录入/req-card=写卡/iter-plan=迭代编排/iter=实现(五者均装于项目 `.zcode/skills/`);登记不立技能;批后收尾与中间文档退役=batch-closure(同一目录);格式与状态机权威=`design/engineering/doc-conventions.md` §12 |
|
||||
| 定代码方案 / 写代码 / 改代码前 | `design/engineering/code-conventions.md`——结构/契约/语言/数值/流程五类硬纪律(反例库 = 同目录 code-conventions-cases.md) |
|
||||
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 全量验证 / 验收清单落码 |
|
||||
| 定测试方案 / 写测试 / 改测试前 | `design/engineering/test-conventions.md`——探针标配 / 回归断言 / 提交前验证 / 验收清单落码 |
|
||||
| 定文档方案 / 写文档 / 改文档前 | `design/engineering/doc-conventions.md`——白话写作 / 载体分工 / 用语 / 结构骨架 / 会话记录与生命周期 |
|
||||
| 概念切分与互链 | `design/engineering/concept-conventions.md`——概念封装 / 跨文档引用 / 归档与事实源 |
|
||||
| 系统设计 / 增删文档 | `design/gdd/02-系统清单.md`——根索引 + 中文规范名↔英文代号对照 |
|
||||
@@ -84,7 +84,7 @@
|
||||
- 车道铁律(判据与改动域划分同一,按文件性质切)——代码面(`game/` 下非 `.md` 的行为载体)唯一强制独立 worktree(迭代批全套仪式不变);其余一切面(含 `game/` 内 `.md` 与 `.zcode/`、`.qoder/` 等不入 git 面)主工作区直改+直提,commit 点由 git pre-commit 钩子(`tools/hooks/pre-commit`,`core.hooksPath` 挂载)现场跑文档域闸,绿落地红拦;主区直提代码面被该钩子拦;`--no-verify` 仅属用户,agent 禁用(细则见 process-conventions §1)
|
||||
- **仓库根禁止设会话状态文件**(`active.md` 已废除)——现状看 roadmap「当前位」,待办看待办清单,过程看 git 提交信息,产出物看 `reports/`;任何文档禁止另设「当前状态快照」节,两处记同一件事必然漂移(细则见 doc-conventions 第 10 节)
|
||||
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
|
||||
- 交付前验收 = 跑 `tools/run_all_gates.sh` **按改动域**全绿(唯一入口、唯一闸清单、唯一域划分,禁在文档另列);合回 master 由 worktree-guard 按 diff 文件性质推域校验回执强制(process-conventions §3);非代码面直改批的验收=commit 点 git 钩子自动跑 `--docs`(process-conventions §1)
|
||||
- 交付前验收 = 跑 `tools/run_all_gates.sh` **按改动域**全绿(唯一入口、唯一闸清单、唯一域划分与选闸算法,禁在文档另列);代码域按**关联档选测**——02 单元与集成跑「卡片申报 ∪ diff 推导」并集套件、三件保险(冒烟/系统测试/静态扫描)恒跑、横切面回落全量,所选域绿即可合回,全量回归由用户自发(2026-10-29 用户裁,合入后无自动兜底)(算法见闸脚本);合回 master 由 worktree-guard 按 diff 文件性质推域校验回执强制(process-conventions §3);非代码面直改批的验收=commit 点 git 钩子自动跑 `--docs`(process-conventions §1)
|
||||
- **改动四类**——**需求改动**(游戏代码,含随逻辑写/改的测试代码):唯一走「写需求卡片+编入迭代批次」通道;迭代实现批禁写 `design/` 整个目录、两份根流程清单(`待办清单.md`/`体验验证清单.md`)与 `game/tests/README.md`(能力清单,归写卡段维护)(含新增/修改/删除),设计文档对它只读、是写测试与实现代码的输入依据;两份流程清单的条目状态只由流水线各段推进,实现批禁止顺手改;产出物只准写 `reports/`;批末只跑代码域闸。**工程改动**(tools/、闸件、模拟测量工具、纯测试维护——不改变游戏行为的配套代码)、**文档设计改动**、**美术素材改动**:不写需求卡片、不编迭代批次,登记在案后由用户发令在主工作区直改+直提(commit 点 git 钩子自动跑文档域闸),完成即删条目;触及代码面的工程改动(如纯测试维护)仍走 worktree 批;文档设计类写入走 doc-design 技能(登记条目格式由流水线闸强制)
|
||||
- 五段流水线铁律——**需求改动**(游戏代码)按「登记 → 文档设计/美术设计 → 写需求卡片 → 迭代编排 → 实现」五段推进,段间不自动流转、每段转进由用户发令;编入迭代的卡片必须设计闭合+零待裁点——需求卡片任何工作量皆可写、皆可编入迭代,工作量限制只属迭代层(迭代估时总量 <5 个工作日=「冻结待凑批」、不可开工);流水线闸 `tools/pipeline_gate.py` 强制(已注册进 run_all_gates.sh 文档域);实现段零裁断——卡片没写清=上游缺陷,停该卡退回上游,禁内联求裁(process-conventions §2,格式与状态机=doc-conventions §12)。工程/文档设计/美术素材三类改动不走写卡与编排段:登记 →(如需设计讨论)→ 用户发令直改 → 主区直改+commit 点闸 → 完成删条目(车道细则=process-conventions §1)
|
||||
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
|
||||
## 3. 测试纪律
|
||||
|
||||
已独立成文:`design/engineering/test-conventions.md`(探针标配 / 回归断言 / 提交前全量验证 / 验收清单落码规范)。
|
||||
已独立成文:`design/engineering/test-conventions.md`(探针标配 / 回归断言 / 提交前验证 / 验收清单落码规范)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
7. **验收改动先行声明**——验收变化(基线换代/探针增改/旧断言改判)预先写进需求卡片,禁事后补
|
||||
|
||||
**完成**
|
||||
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。所选域全绿才合回 master。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底
|
||||
8. **批末按域验收才合并(worktree 批;直改车道无批末动作,验收在 commit 点由 git 钩子执行,见 §1 第 3 条)**——在任务 worktree 内跑 `tools/run_all_gates.sh`,**按本批改动域选档**:按文件性质判——代码面=`game/` 下非 `.md` 的行为载体(代码、测试脚本、探针、场景、数据表、素材)→ `--code`;文档件=`game/` 内 `.md`(README、生成清单)与 `game/` 外一切路径(`design/`、根规则文件、`tools/` 工具脚本、`.zcode/` 配置等)→ `--docs`;两类同批都有 → 全量档不带参数。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件——申报=在册需求卡片对照表「验收测试」列提到的测试文件名,推导=闸脚本内套件族映射表按改动文件归族;改动命中公共件/数据表/autoload/数值·存档·RNG·结算触面或映射不到 → 横切回落全部套件;00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降;03/04 探针与 05 渲染闸维持影响面口径(本批 game/ 改动全在测量面 game/tests、game/sim 时记 SKIP)。所选域全绿即可合回 master,全量回归由用户自发发起(合入后无自动兜底)。合回前独立验收审计通过(iter 技能第 5 步),未过不合回。该脚本是全部验证闸的**唯一入口、唯一清单与唯一域划分(含选闸算法与套件族映射表)**,闸件增减、分域调整都只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`(含 `domain`、`scope`(full|related,02 跑子集=related)与 `dirty_game` 字段),合回时由 worktree-guard 按 diff 文件性质推域强制校验(六况拦截:缺回执/quick 模式/回执未覆盖分支尖=回执 commit 之后代码面又有改动、或跑闸时代码面带着未提交改动(`dirty_game`)/域不覆盖本次改动/含新增红/master HEAD 非回执 commit 祖先=合并将产出未测内容;guard 只验域覆盖与全绿,不复核选套件清单)。回执与分支尖的对齐判据是**代码面**不是 commit 号:回执之后只追加文档件(`game/` 内 `.md`、`reports/`、`design/` 等)提交,回执照样有效,不构成复跑理由。闸件(`tools/` 下机检脚本与闸壳)自身的改动不占合回全量——其质量义务=落批时人工确认闸可跑通且判据口径未变,闸真崩溃时合回闸 fail-closed(跑不出回执即拦),不以合回全量为兜底
|
||||
9. **合并后主区零复跑**(2026-10-24 用户裁:合回重跑验的是与回执同一份字节,零信息)——合回落地后 code 批只做两件秒级事:`--editor --quit` 重刷类名缓存(在册坑:.godot 缓存不入 git,与合并后代码失配会假红假绿)+冒烟启动确认可开机(合计约 1 分钟);docs 批零动作。全量验证**每批跑一次**=worktree 批末按域闸,顺序是「先并入 master、再跑闸」(§1 第 2 条);只有两种情形允许把这次跑作废重来——① 闸红且修法动了 `game/`,② 闸已开跑后 master 又被推进(合并将产出未测内容,由合回闸祖先判定拦下,§3.9 第六况)。「跑都跑了再跑一遍保险」「闸后顺手改点 `game/` 再补一跑」都不构成复跑理由;master 上永不跑全量
|
||||
10. **批次完成后的登记义务**——合回 master 后按 doc-conventions §12.1 推进待办清单条目状态(做完整条删除),涉及迭代编排的在 roadmap 迭代总表与顶部「当前位」节换代——执行入口=项目技能 batch-closure(批后收尾与中间文档退役),跨会话教训写记忆库。批次过程与结论由 git 提交信息承载,禁止再写会话状态文件(见 doc-conventions 第 10 节)。`design/` 与根规则文件(`AGENTS.md`/`roadmap.md`/`待办清单.md`/`体验验证清单.md`)实现批禁写,见 §12
|
||||
11. **文档同步闸(实现批禁写文档)**——实现批**禁写 `design/` 整个目录与根规则文件**,**另禁写 `game/tests/README.md`(能力清单,归写卡段维护,test-conventions §8.9)**,要往外交代的东西一律落 `reports/`:实现报告 `reports/iter<N>-实现报告-<日期>.md`(各卡状态+实机目检总清单+遗留读数义务+实现决定记录——超白名单/超卡片明列范围事项逐条记「决了什么/为什么/代价/撤销方式」,全部按卡执行时只写一行「零拍板」)、GDD 反哺点清单 `reports/iter<N>-反哺点-<日期>.md`(每条写清哪个文件哪一节、改成什么、依据是什么)。设计权威文档(GDD/art-bible)**任何批都不直改**:用户在会话中逐点裁决后,由工程/文档设计改动批(直改类)写入 `design/`(执行入口=doc-design 技能),并把反哺条目从 `reports/` 清掉(吸收即死;退役执行=batch-closure 技能)。机械面文档(roadmap 行/术语表/README 工具注)同样归工程/文档设计改动批在合并前更新。反哺点未落地期间相关 GDD 节视为待反哺,读前先查 `reports/` 与待办清单;需求卡片必列「关联文档同步面」按两层标注
|
||||
|
||||
@@ -25,9 +25,9 @@
|
||||
每个缺陷修复必须在探针或冒烟中留下能抓住该缺陷的断言(例:封印动作耗尽 AP → 必须进入第 2 轮;
|
||||
选用道具 → 库存态必须变 Used)。没有回归断言的修复视为未完成。
|
||||
|
||||
## 4. 提交前全量验证
|
||||
## 4. 提交前验证
|
||||
|
||||
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`)全绿——该脚本是全部验证闸的唯一入口与唯一清单,本文禁另列闸件清单。闸件增减只改脚本。
|
||||
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`),按改动域选档、所选域全绿——该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。闸件增减只改脚本。代码域按**关联档选测**(2026-10-29 用户裁):02 单元与集成只跑「需求卡片申报 ∪ diff 推导」并集的套件,00 冒烟/01 系统测试/09 静态扫描三件保险恒跑不降,横切面改动(公共件/数据表/autoload/数值·存档·RNG·结算触面)回落全部套件;所选域全绿即可合回,全量回归由用户自发发起(合入后无自动兜底)。
|
||||
行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
|
||||
|
||||
## 5. 测试函数怎么写——验收清单条目的落码规范
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
**Engine**: Godot 4.7.2-stable(GDScript 单语言)
|
||||
**Test Framework**: 标准四级测试层(冒烟/单元/集成/系统)+ headless 场景探针与渲染 golden;gdUnit4 **deferred**(D2 OQ1 关闭形态——本仓库不引第三方测试框架,需求见下「门禁」节)
|
||||
**门禁**: 本地闸——`tools/run_all_gates.sh`(收口唯一验收入口,按改动域选档 `--code`/`--docs`,跨两域或改闸件本身不带参数=全量)。本仓库**无远端、无 CI**(`.github/workflows/` 已删除,不建不留不参考);闸清单与域划分只在本脚本定义,文档不另列
|
||||
**门禁**: 本地闸——`tools/run_all_gates.sh`(收口唯一验收入口,按改动域选档 `--code`/`--docs`,跨两域或改闸件本身不带参数=全量;代码域 02 按关联档选套件,算法与套件族映射表只在脚本内定义)。本仓库**无远端、无 CI**(`.github/workflows/` 已删除,不建不留不参考);闸清单与域划分只在本脚本定义,文档不另列
|
||||
**Setup date**: 2026-09-12(2026-09-27 `7f24b640` 三层收敛:根 tests/ 并入 `game/tests/`、docs 并入 `design/engineering/`、production 解散)
|
||||
**参考实现**: `game/probe/`(场景探针族)· `game/sim/`(测量/诊断/模拟驱动件)(场景探针族——正式工程的活样板;系统测试断言面过渡宿主 `smoke_all.gd` 亦在此,迁移归断言迁移批)
|
||||
|
||||
@@ -197,7 +197,7 @@ tests/
|
||||
|---|---|---|---|
|
||||
| `tools/run_smoke.py`(冒烟测试) | 这版能否启动、能否加载表、能否进主屏——零逻辑判定 | 退出码+脚本错误扫描 | 开机路径/表加载改动后的快速自检;收口由闸编排 |
|
||||
| `tools/run_system_test.py`(系统测试) | 真实玩法路径的行为断言面(推理→战斗→结算→进度推进) | 退出码+脚本错误扫描 | 玩法链路改动的调试期确认 |
|
||||
| `tests/run_all.gd`(单元与集成) | 公式/状态机/契约/跨系统闭环(64 套件 manifest) | 退出码+三硬闸(见下);每套件子进程带截止时间守护——超时先 taskkill /T 整树回收再判该套件 FAIL、批继续(默认 120s,环境变量 `RUN_ALL_SUITE_TIMEOUT_SEC` 可覆盖) | 新套件单跑调试;逻辑改动的最小单元复跑;开发期选跑——追加 `-- --only=<词[,词…]>` 只跑路径命中词的套件(零命中=用法错 exit 2;选跑态仅供开发期最窄确认,不产闸回执、不作验收凭据) |
|
||||
| `tests/run_all.gd`(单元与集成) | 公式/状态机/契约/跨系统闭环(65 套件 manifest) | 退出码+三硬闸(见下);每套件子进程带截止时间守护——超时先 taskkill /T 整树回收再判该套件 FAIL、批继续(默认 120s,环境变量 `RUN_ALL_SUITE_TIMEOUT_SEC` 可覆盖);连续 3 个套件超时=红树整批挂死,第 3 个即中止整轮、汇总已见失败与超时清单后非 0 退出(阈值=可调常量,不连续的超时照旧逐个回收) | 新套件单跑调试;逻辑改动的最小单元复跑;开发期选跑——追加 `-- --only=<词[,词…]>` 只跑路径命中词的套件(零命中=用法错 exit 2;此形态仅供开发期最窄确认,不产闸回执、不作验收凭据);闸机关联档选跑——同一过滤机制加 `-- --only-source=gates`,由 `tools/run_all_gates.sh` 在 02 闸传入(回执 scope=related,是验收跑批本体) |
|
||||
| `tools/probe_gate.sh`(场景探针,`--click`/`--full` 两模式) | UI 可达性、可见性、状态——真实实例化场景+真实输入路径 | 探针自检+退出码 | 新增 UI 区域的配套探针直跑与断言复核 |
|
||||
| `tools/render_gate.sh`(渲染 golden) | 画面基线——截图与基线图逐像素容差比对 | golden 比对脚本(依赖 PIL) | 主题/素材/排版类改动;窗口化运行,需开显示器 |
|
||||
| `game/tests/static_scan.sh`(S 域) | 项目纪律+第三方质量规则:RNG 单点/core 纯度/类型标注/文件头/注释禁词/旧符号残留;S13=gdstyle 质量规则(重复 load、循环内 await、循环内分配、不可达代码等真缺陷类;基线键=文件|规则|函数名(类/文件级=文件|规则,无数字条目带消息),数字漂移不假红,同键指标恶化超基线登记值即红) | 退出码(逐规则 FAIL 行) | 改产品码前后的快速自检;新增纪律先补扫描规则再修码 |
|
||||
@@ -225,7 +225,9 @@ godot --headless --path . res://probe/<probe>.tscn
|
||||
|
||||
# 挂死防护内建于 run_all.gd(引擎无死锁探测,超时靠外部判):每套件子进程带截止时间——
|
||||
# 默认 120s,环境变量 RUN_ALL_SUITE_TIMEOUT_SEC 可覆盖;超时先 taskkill /T 整树回收
|
||||
#(cmd wrapper 连引擎孙进程一起杀,不留孤儿),该套件判 FAIL、批继续——单套件挂死不拖死整闸
|
||||
#(cmd wrapper 连引擎孙进程一起杀,不留孤儿),该套件判 FAIL、批继续——单套件挂死不拖死整闸;
|
||||
# 连续 3 个套件超时=红树整批挂死,第 3 个即中止整轮(汇总已见失败与超时清单、退出码非 0),
|
||||
# 不连续的超时照旧逐个回收;阈值是 run_all.gd 内可调常量
|
||||
```
|
||||
|
||||
> 套件 manifest 现况:`_suites()` 登记 **65 条**(62 单元+3 集成,2026-10-29 实数;口径=manifest 条目数,
|
||||
@@ -297,9 +299,9 @@ S12 效果引擎无状态化旧件零残留:扫描 game/ 全域 `.gd` 与 `gam
|
||||
|
||||
## 本地闸(唯一验收门禁)
|
||||
|
||||
本仓库无远端、无 CI——**验收只在本地发生**,跑 `tools/run_all_gates.sh` 是收口唯一入口,按本批改动域选档(`--code`/`--docs`/跨两域或改闸件本身=不带参数全量),
|
||||
跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 字段),合回 master 由 worktree-guard 按 diff 文件性质推域校验回执(`game/` 内 `.md` 归文档域)。
|
||||
闸清单与域划分的唯一定义处就是那个脚本,本文档不另列清单(清单散落多份文档正是历史漏跑闸的成因)。
|
||||
本仓库无远端、无 CI——**验收只在本地发生**,跑 `tools/run_all_gates.sh` 是收口唯一入口,按本批改动域选档(`--code`/`--docs`/跨两域或改闸件本身=不带参数全量;代码域 02 按关联档选套件,回执 `scope` 字段记 full|related),
|
||||
跑完写回执 `game/tests/.gates/receipt.json`(含 `domain` 与 `scope` 字段),合回 master 由 worktree-guard 按 diff 文件性质推域校验回执(`game/` 内 `.md` 归文档域)。
|
||||
闸清单与域划分(含选闸算法与套件族映射表)的唯一定义处就是那个脚本,本文档不另列清单(清单散落多份文档正是历史漏跑闸的成因)。
|
||||
|
||||
**判定三硬闸**(各闸一律适用,缺一即判定有洞):
|
||||
①**退出码为唯一权威判据**——日志里的 PASS 串只是日志,不作判据;
|
||||
|
||||
+61
-18
@@ -1,8 +1,9 @@
|
||||
# tests/run_all.gd — 单元测试统一入口(双载体的逻辑测试调用点,套件 manifest 协议 D2 AC-2b)
|
||||
# 用法:godot --headless --path game --script res://tests/run_all.gd
|
||||
# 选跑态(开发期直跑专用):追加 `-- --only=<词[,词…]>` ——对套件 res:// 路径做子串匹配,只跑命中套件;
|
||||
# 零命中/空值=用法错 quit(2)。选跑态仅供开发期最窄确认,不产闸回执、不作验收凭据;
|
||||
# 批末全量闸不带参数不经选跑分支。
|
||||
# 选跑态:追加 `-- --only=<词[,词…]>` ——对套件 res:// 路径做子串匹配,只跑命中套件;
|
||||
# 零命中/空值=用法错 quit(2)。两个来源分开定性——开发期直跑(默认)仅供最窄确认,不产闸回执、
|
||||
# 不作验收凭据;闸机关联档选跑(--only-source=gates,由 tools/run_all_gates.sh 在 02 闸传入)
|
||||
# 是收口验收跑批本体,回执 scope=related。无任何选跑参数=全量态,行为与全量口径完全一致。
|
||||
# 协议:套件清单逐条"套件 → 期望退出码";致命类独立进程(expect=1)。
|
||||
# 执行形态:逐套件起独立 Godot 子进程实跑
|
||||
#(OS.get_executable_path() 解析本引擎二进制——--script 模式无 autoload/全局标识符,
|
||||
@@ -11,6 +12,9 @@
|
||||
# 套件挂死(_init 在 quit() 前中断,子进程永不退出)=超时先 taskkill /T 整树回收
|
||||
#(连 cmd wrapper 带引擎孙进程一起杀,只杀 wrapper 会留孤儿殃及后续全部 Godot 启动)
|
||||
# 再判该套件 FAIL 继续下一套件——单套件挂死不拖死整批;正常退出路径判定形态不变。
|
||||
# 快速失败:连续 N 个套件超时=红树正在整批挂死(如公共件解析错让每个套件都起不来),
|
||||
# 继续逐个回收只剩烧钟——第 N 个超时即中止整轮,汇总已见失败与超时清单后非 0 退出;
|
||||
# 不连续的超时仍逐个回收照旧。
|
||||
# 场景探针(.tscn 载体)由本地闸(probe_gate.sh)单独步骤调用,不经本入口。
|
||||
extends SceneTree
|
||||
|
||||
@@ -23,21 +27,27 @@ const ERROR_MARKERS: Array[String] = ["SCRIPT ERROR", "Parse Error"]
|
||||
const SUITE_TIMEOUT_ENV := "RUN_ALL_SUITE_TIMEOUT_SEC"
|
||||
const SUITE_TIMEOUT_DEFAULT_SEC := 120
|
||||
|
||||
# 快速失败阈值(可调常量,初值 3):连续 N 个套件超时即中止整轮。
|
||||
const CONSECUTIVE_TIMEOUT_ABORT := 3
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# 选跑态解析:--only=<词[,词…]>(经 -- 分隔的 user args)——对套件 res:// 路径做子串匹配。
|
||||
# 空值/零命中=用法错 quit(2)(fail-closed:禁把零套件当绿);选跑态仅供开发期最窄确认,
|
||||
# 不产闸回执、不作验收凭据;批末全量闸不带参数,不走此分支。
|
||||
# 选跑态解析:--only=<词[,词…]>(经 -- 分隔的 user args)——对套件 res:// 路径做子串匹配;
|
||||
# --only-source=gates=来源标记:由闸脚本传入的关联档选跑(验收跑批本体),缺省=开发期直跑。
|
||||
# 空值/零命中=用法错 quit(2)(fail-closed:禁把零套件当绿)。
|
||||
var only_terms := PackedStringArray()
|
||||
var has_only := false
|
||||
var only_from_gates := false
|
||||
for a in OS.get_cmdline_user_args():
|
||||
if a.begins_with("--only="):
|
||||
has_only = true
|
||||
var term := a.trim_prefix("--only=").strip_edges()
|
||||
if term != "":
|
||||
only_terms.append(term)
|
||||
elif a == "--only-source=gates":
|
||||
only_from_gates = true
|
||||
if has_only and only_terms.is_empty():
|
||||
push_error("[run_all] --only= 空值——用法错(选跑态禁把零套件当全量)")
|
||||
push_error("[run_all] --only= 空值——用法错(选跑禁把零套件当全量)")
|
||||
quit(2)
|
||||
return
|
||||
var manifest := _suites()
|
||||
@@ -50,15 +60,37 @@ func _init() -> void:
|
||||
quit(2)
|
||||
return
|
||||
filtered = true
|
||||
print("[run_all] FILTERED RUN — 选跑 %d/%d 套件(--only=%s)——仅供开发期最窄确认,不产闸回执、不作验收凭据" % [suites.size(), manifest.size(), ",".join(only_terms)])
|
||||
if only_from_gates:
|
||||
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)])
|
||||
print("[run_all] unit/contract suite entry — %d suites" % suites.size())
|
||||
if DisplayServer.get_name() != "headless":
|
||||
push_warning("[run_all] 建议以 --headless 运行(D2 冒烟协议)")
|
||||
var failures := 0
|
||||
var ran := 0
|
||||
var consecutive_timeouts := 0
|
||||
var failed_paths := PackedStringArray()
|
||||
var timed_out_paths := PackedStringArray()
|
||||
for suite in suites:
|
||||
failures += _run_suite(suite, ran, filtered)
|
||||
var outcome := _run_suite(suite, ran, filtered)
|
||||
var suite_fail: int = outcome["fail"]
|
||||
var suite_timed_out: bool = outcome["timed_out"]
|
||||
failures += suite_fail
|
||||
ran += 1
|
||||
var spath: String = suite.get("path", "")
|
||||
if suite_timed_out:
|
||||
consecutive_timeouts += 1
|
||||
timed_out_paths.append(spath)
|
||||
if consecutive_timeouts >= CONSECUTIVE_TIMEOUT_ABORT:
|
||||
push_error("[run_all] ABORT — 连续 %d 个套件超时(阈值 %d),中止整轮" % [consecutive_timeouts, CONSECUTIVE_TIMEOUT_ABORT])
|
||||
_report_abort(failed_paths, timed_out_paths, ran, suites.size())
|
||||
quit(1)
|
||||
return
|
||||
else:
|
||||
consecutive_timeouts = 0
|
||||
if suite_fail > 0:
|
||||
failed_paths.append(spath)
|
||||
# 实跑套件数校验:清单有条目却被静默跳过(早退/continue)必须判红,不能只凭 failures。
|
||||
# 选跑态对过滤后清单校验;全量态与原 manifest 条目数同一。
|
||||
if ran != suites.size():
|
||||
@@ -151,17 +183,19 @@ func _suites() -> Array:
|
||||
]
|
||||
|
||||
|
||||
func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> int:
|
||||
# 返回 {"fail": int(该套件失败计 1/过 0), "timed_out": bool(是否超时挂死)}——
|
||||
# timed_out 供编排层做连续超时快速失败判定,fail 照旧累计。
|
||||
func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> Dictionary:
|
||||
var path: String = suite.get("path", "")
|
||||
var expect: int = int(suite.get("expect", 0))
|
||||
if path.is_empty():
|
||||
push_error("[run_all] suite missing path")
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
var bin := _engine_bin()
|
||||
if bin == "":
|
||||
push_error("[run_all] 未找到 console 版引擎二进制(设 GODOT_BIN 指向 *_console.exe)——"
|
||||
+ "非 console 版是 GUI 子系统,子进程 stdout/stderr 句柄无效,脚本错误扫描无从落地")
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
# 子进程输出只能落盘再读:OS.execute 的 output 管道在本机 Godot/Windows 组合下恒为空
|
||||
#(实测 0 行),故整条命令交给 cmd 并重定向到日志。退出码同样走文件通道——
|
||||
# 阻塞式 execute 无超时能力(套件挂死时整批陪葬),换 create_process 非阻塞起进程
|
||||
@@ -180,7 +214,7 @@ func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> int:
|
||||
var pid: int = OS.create_process("cmd.exe", PackedStringArray(["/V:ON", "/C", cmd]))
|
||||
if pid < 0:
|
||||
push_error("[run_all] FAIL %s — cmd 子进程启动失败(pid=%d)" % [path, pid])
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
# 轮询先查存活再查截止:进程在轮询间隙自然退出时按正常路径判,截止临界不误杀。
|
||||
var timed_out := false
|
||||
var deadline_ms := Time.get_ticks_msec() + _suite_timeout_sec() * 1000
|
||||
@@ -213,7 +247,7 @@ func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> int:
|
||||
push_error("[run_all] FAIL %s — 超时挂死(%ds),进程树已整树回收(pid=%d)" % [path, _suite_timeout_sec(), pid])
|
||||
else:
|
||||
push_error("[run_all] FAIL %s — 超时挂死(%ds),进程树回收未确认(pid=%d,需手工清杀残留)" % [path, _suite_timeout_sec(), pid])
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": true}
|
||||
# 负面套件(坏表/违规输入是它的测试目标)全文折叠,只回显结论行——
|
||||
# 它输出里的 Parse Error 是预期产物,回显出去会让编排层的日志扫描误判成真实脚本错误。
|
||||
if negative:
|
||||
@@ -223,17 +257,26 @@ func _run_suite(suite: Dictionary, idx: int, filtered: bool = false) -> int:
|
||||
var code_txt := FileAccess.get_file_as_string(codefile).strip_edges() if FileAccess.file_exists(codefile) else ""
|
||||
if code_txt == "" or not code_txt.is_valid_int():
|
||||
push_error("[run_all] FAIL %s — 退出码文件缺失或非数字(%s)——子进程未走到退出码落盘即死,判红" % [path, codefile])
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
var code: int = int(code_txt)
|
||||
DirAccess.remove_absolute(codefile) # 用毕即删:退出码经文件通道但不落 residuum(.gates 只留 *.log)
|
||||
if not negative and _has_script_error(text):
|
||||
push_error("[run_all] FAIL %s — 输出含脚本错误标记:%s" % [path, _script_error_line(text)])
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
if code != expect:
|
||||
push_error("[run_all] FAIL %s — 退出码 %d ≠ 期望 %d" % [path, code, expect])
|
||||
return 1
|
||||
return {"fail": 1, "timed_out": false}
|
||||
print("[run_all] PASS %s (exit=%d)" % [path, code])
|
||||
return 0
|
||||
return {"fail": 0, "timed_out": false}
|
||||
|
||||
|
||||
# 快速失败中止时的汇总:已见失败与超时清单+中止时进度——红树几分钟出结论,不磨完整轮。
|
||||
func _report_abort(failed: PackedStringArray, timed_out: PackedStringArray, ran: int, total: int) -> void:
|
||||
var timed_out_txt := "、".join(timed_out) if timed_out.size() > 0 else "(无)"
|
||||
var failed_txt := "、".join(failed) if failed.size() > 0 else "(无)"
|
||||
push_error("[run_all] 已见超时 %d 件:%s" % [timed_out.size(), timed_out_txt])
|
||||
push_error("[run_all] 已见失败(非超时)%d 件:%s" % [failed.size(), failed_txt])
|
||||
push_error("[run_all] 中止时进度:%d/%d 套件(余下未跑)" % [ran, total])
|
||||
|
||||
|
||||
# 选跑过滤:对套件 res:// 路径做子串匹配,任一词命中即选入,词序无关。
|
||||
|
||||
+155
-30
@@ -14,8 +14,11 @@
|
||||
# --code 只跑代码域闸(00 冒烟测试/01 系统测试/02 单元与集成测试/03 探针 --click/
|
||||
# 04 探针 --full/05 渲染闸/09 静态扫描/11 回放确定性对拍+10 熵增闸)
|
||||
# ——本批改动含 game/ 下非 .md 的行为载体(代码/测试脚本/探针/场景/数据表/素材)时用。
|
||||
# 影响面选闸:本批 game/ 改动全在测量面(game/tests、game/sim)时,00/03/04/05 记
|
||||
# SKIP(测量面不触开机路径/场景树/渲染输出;映射定义在脚本中部,判定保守)。
|
||||
# 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸):00 冒烟/01 系统测试/
|
||||
# 09 静态扫描三件保险恒跑不降;03/04 探针与 05 渲染闸维持影响面口径(本批 game/ 改动
|
||||
# 全在测量面 game/tests、game/sim 时记 SKIP);02 单元与集成改「选套件跑」——套件集=
|
||||
# 需求卡片申报 ∪ diff 推导并集(算法与套件族映射表见脚本中部),横切面(公共件/数据表/
|
||||
# autoload/数值·存档·RNG·结算触面)回落全套件;回执 scope 字段记 full|related。
|
||||
# --docs 只跑文档域闸(06 gdd_lint/07 doc_link/08 doc_term/08c 流水线闸/08d 引用可核/
|
||||
# 08e 能力审计+10 熵增闸)
|
||||
# ——本批改动只有文档件时用:game/ 内的 .md(README、生成清单)、design/、根规则文件、
|
||||
@@ -154,12 +157,83 @@ fi
|
||||
declare -a NAMES=() EXITS=() LOGS=()
|
||||
FAILED=0
|
||||
|
||||
# ── 影响面选闸(2026-10-28 用户裁):本批 diff 相对 master 分叉点的 game/ 改动若全部落在
|
||||
# 测量面(game/tests/**、game/sim/**——测试与模拟驱动件,不触开机路径、场景树与渲染
|
||||
# 输出),则 00 冒烟/03/04 探针/05 渲染闸记 SKIP。判定保守取向:diff 含任一行为面
|
||||
# 文件即全部实跑;01/02/09 恒跑(测试宿主与静态扫描正是测量面改动的验证载体)。
|
||||
# 触发面映射只在本脚本一处定义;SKIP 与 --quick 同构,回执记 not_run,不作闸况变化。
|
||||
# PARITY 触面表——gate 11 数值·存档·RNG·结算触面判定与关联档横切判定共用同一清单(单点定义,勿在别处复制)。
|
||||
PARITY_TOUCH_FACES="
|
||||
game/data/tables/
|
||||
game/core/rng_service.gd
|
||||
game/core/save_manager.gd
|
||||
game/core/meta_progression.gd
|
||||
game/core/settlement/
|
||||
game/core/damage_math.gd
|
||||
game/core/damage_controller.gd
|
||||
game/core/battle_orchestrator.gd
|
||||
game/sim/adventure_monte_carlo.gd
|
||||
"
|
||||
|
||||
# ── 套件族映射表(关联档·推导半边的唯一事实源)──
|
||||
# game/ 代码面文件 → tests/ 套件族词(--only 子串词,词=套件目录名)。机械近似:同族内
|
||||
# 套件覆盖面仍可能宽于被测文件,保守方向=宁多勿漏。横切判定(公共件前缀/project.godot/
|
||||
# PARITY 触面表)先于本表执行,故表中不收 damage_math/damage_controller/battle_orchestrator/
|
||||
# settlement/** 等数值·存档·RNG·结算触面文件——它们恒走横切回落全量。映射不到的文件返回空=横切处理。
|
||||
suite_terms_for() { # suite_terms_for <仓库相对路径> → stdout: 套件族词(空格分隔,可空)
|
||||
case "$1" in
|
||||
# 战斗结算域
|
||||
game/core/action_economy.gd|game/core/battle_fx_host.gd|game/core/effect_context.gd|\
|
||||
game/core/effect_engine.gd|game/core/gain_journal.gd|game/core/skill_lottery.gd)
|
||||
echo "battle_settlement" ;;
|
||||
game/core/reward_economy.gd|game/core/reward_grant.gd)
|
||||
echo "battle_settlement core" ;;
|
||||
game/core/status_container.gd)
|
||||
echo "battle_settlement contracts" ;;
|
||||
game/core/unit_container.gd)
|
||||
echo "battle_settlement data_config" ;;
|
||||
# 排雷/棋盘域(封印与极性行为跨战斗结算族)
|
||||
game/core/board_data.gd|game/core/minesweeper_rules.gd)
|
||||
echo "core battle_settlement" ;;
|
||||
game/core/board_generator.gd|game/core/node_map_generator.gd|game/core/hero_stats.gd|\
|
||||
game/core/curio_deck.gd|game/core/curio_stop.gd|game/core/refresh_bank.gd|\
|
||||
game/core/treasure_items.gd|game/core/attune_loadout.gd|game/core/attunement.gd|\
|
||||
game/core/meta_state_store.gd|game/core/slot_store.gd|game/core/gp_wallet.gd|\
|
||||
game/core/shop_session.gd)
|
||||
echo "core" ;;
|
||||
# 冒险结构×存档断点×关卡推进(core 族+跨系统集成族)
|
||||
game/core/adventure_runner.gd|game/core/level_runner.gd)
|
||||
echo "core integration" ;;
|
||||
# 配置域表类(autoload 本体与解析器/跨表校验器走横切,不入本表)
|
||||
game/core/config_domain_*.gd)
|
||||
echo "data_config core" ;;
|
||||
# 数据 schema(data_config 族守卫+core 族消费侧校验与跨表校验)
|
||||
game/data/schemas/*)
|
||||
echo "data_config core" ;;
|
||||
# 呈现域
|
||||
game/presentation/*)
|
||||
echo "presentation" ;;
|
||||
# 素材(字体/地形/主题类断言在 presentation 族;探针与渲染闸另有行为面闸管)
|
||||
game/assets/*)
|
||||
echo "presentation" ;;
|
||||
*)
|
||||
echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ── 关联档选闸(2026-10-29 用户裁,推广自 2026-10-28 影响面选闸)──
|
||||
# 三件保险恒跑不降:00 冒烟/01 系统测试/09 静态扫描(10 熵增闸照旧两域都跑,11 数值·存档·RNG·结算触面判定照旧)。
|
||||
# 03/04 探针与 05 渲染闸维持影响面口径:本批 diff 相对 master 分叉点的 game/ 改动若全部落在
|
||||
# 测量面(game/tests/**、game/sim/**——测试与模拟驱动件,不触场景树与渲染输出)则记 SKIP。
|
||||
# 02 单元与集成改「选套件跑」:套件集=申报 ∪ 推导 取并集——
|
||||
# 申报:在册需求卡片(本批 diff 触及的 reports/需求卡片*.md ∪ roadmap「进行中」迭代行登记的
|
||||
# 卡片)对照表「验收测试」列提到的测试文件名,按文件名作为 --only 子串词归套件;
|
||||
# 找不到在册卡片则申报集为空并输出注明。
|
||||
# 推导:按下方套件族映射表把 game/ 代码面文件(非 .md、非测量面)归到套件族;
|
||||
# 映射不到的文件一律按横切处理。
|
||||
# 横切回落全量:diff 命中 game/data/tables/**、公共件前缀(config_db*/rng_service*/
|
||||
# save_manager*/meta_progression*/main*——main* 覆盖组合根拆件与主场景)、
|
||||
# autoload 注册面(game/project.godot)、或下方 PARITY 触面表任一 → 02 跑全部套件。
|
||||
# 测量面批(game/ 改动全在 tests/sim):02 照旧全套件跑(不加过滤)。
|
||||
# 回执 scope 字段:full|related(02 跑子集=related);映射表只在本脚本一处定义。
|
||||
NONBEHAVIOR=0
|
||||
SCOPE="full"
|
||||
ONLY_TERMS=""
|
||||
if in_domain code; then
|
||||
_base="$(git -C "$REPO" merge-base master HEAD 2>/dev/null || true)"
|
||||
_gfiles="$(git -C "$REPO" -c core.quotepath=false diff --name-only "${_base:-HEAD}" HEAD -- game/ 2>/dev/null || true)"
|
||||
@@ -167,7 +241,62 @@ if in_domain code; then
|
||||
_behavior="$(echo "$_gfiles" | grep -vE '^(game/tests/|game/sim/)' || true)"
|
||||
if [ -n "$_gfiles" ] && [ -z "$_behavior" ]; then
|
||||
NONBEHAVIOR=1
|
||||
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——00 冒烟/03/04 探针/05 渲染闸 → SKIP"
|
||||
echo "[gates] 影响面选闸:game/ 改动全在测量面(tests/sim)——03/04 探针/05 渲染闸 → SKIP(00/01/09 三件保险恒跑)"
|
||||
fi
|
||||
# ── 02 选套件推导(只对行为面代码文件做——测量面文件不进推导,其验证就是 02/09 自身)──
|
||||
_codefiles="$(echo "$_gfiles" | grep -viE '\.md$' | grep -vE '^(game/tests/|game/sim/)' || true)"
|
||||
if [ -n "$_codefiles" ]; then
|
||||
CROSSCUT=0
|
||||
_family_terms=""
|
||||
# 逐文件判定:横切命中即整批回落全量;否则按映射表归族。
|
||||
while IFS= read -r _f; do
|
||||
[ -z "$_f" ] && continue
|
||||
_b="$(basename "$_f")"
|
||||
case "$_b" in
|
||||
config_db*|rng_service*|save_manager*|meta_progression*|main*)
|
||||
CROSSCUT=1; break ;;
|
||||
esac
|
||||
[ "$_f" = "game/project.godot" ] && { CROSSCUT=1; break; } # autoload 注册面
|
||||
for _pat in $PARITY_TOUCH_FACES; do
|
||||
case "$_f" in
|
||||
"$_pat"*) CROSSCUT=1; break 2 ;;
|
||||
esac
|
||||
done
|
||||
_t="$(suite_terms_for "$_f")"
|
||||
[ -n "$_t" ] && _family_terms="$_family_terms $_t"
|
||||
done <<<"$_codefiles"
|
||||
# 申报半边:在册需求卡片对照表「验收测试」列的测试文件名(卡片=本批 diff 触及 ∪ roadmap 进行中行登记)。
|
||||
_cards="$(git -C "$REPO" -c core.quotepath=false diff --name-only "${_base:-HEAD}" HEAD -- 'reports/' 2>/dev/null | grep '需求卡片.*\.md$' || true)"
|
||||
_roadmap_cards="$(grep '| 进行中 |' "$REPO/roadmap.md" 2>/dev/null | grep -oE 'reports/需求卡片[^|]*\.md' || true)"
|
||||
_declared_names=""
|
||||
_card_seen=0
|
||||
for _c in $_cards $_roadmap_cards; do
|
||||
_card_seen=1
|
||||
_cf="$REPO/$_c"
|
||||
[ -f "$_cf" ] || continue
|
||||
_names="$(grep -ohE '[a-z0-9_]+_test\.gd' "$_cf" 2>/dev/null | sort -u || true)"
|
||||
[ -n "$_names" ] && _declared_names="$_declared_names $_names"
|
||||
done
|
||||
if [ "$CROSSCUT" -eq 1 ]; then
|
||||
SCOPE="full"
|
||||
echo "[gates] 关联档选闸:diff 命中横切面(公共件/数据表/autoload/数值·存档·RNG·结算触面)——02 回落全部套件"
|
||||
else
|
||||
# 并集去重:推导族词 ∪ 申报测试文件名;并集非空才过滤(映射表保证族词非空,此处双保险)。
|
||||
_union="$(printf '%s\n' $_family_terms $_declared_names | sed '/^$/d' | sort -u | tr '\n' ' ' || true)"
|
||||
_union="$(echo "$_union" | xargs 2>/dev/null || true)"
|
||||
if [ -n "$_union" ]; then
|
||||
SCOPE="related"
|
||||
ONLY_TERMS="$_union"
|
||||
else
|
||||
SCOPE="full"
|
||||
fi
|
||||
fi
|
||||
if [ "$_card_seen" -eq 0 ]; then
|
||||
echo "[gates] 关联档选闸·申报:未找到在册需求卡片(diff 与 roadmap 进行中行均无),申报集为空——仅按 diff 推导"
|
||||
fi
|
||||
if [ "$SCOPE" = related ]; then
|
||||
echo "[gates] 关联档选闸:02 选套件跑(--only=$ONLY_TERMS)"
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -195,11 +324,20 @@ gate() { # gate <名称> <日志名> <命令...>
|
||||
|
||||
gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
|
||||
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)
|
||||
fi
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════"
|
||||
echo " $name(--script 模式,退出码为唯一判据)"
|
||||
if [ "$SCOPE" = related ] && [ -n "$ONLY_TERMS" ]; then
|
||||
echo " 关联档选跑:--only=$ONLY_TERMS"
|
||||
fi
|
||||
echo "════════════════════════════════════════════"
|
||||
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd ) \
|
||||
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd "${_user_args[@]}" ) \
|
||||
>"$STAMP_DIR/unit.log" 2>&1
|
||||
local rc=$?
|
||||
# 三硬闸之二(脚本错误扫描):子套件的 SCRIPT ERROR/Parse Error 只写 stderr,入口内收不到,
|
||||
@@ -216,12 +354,8 @@ gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
|
||||
# ══ 代码域(0–5)══
|
||||
if in_domain code; then
|
||||
# ── 0. 冒烟测试(极小启动场景:真实开机路径存活检查,零逻辑判定,秒级)──
|
||||
if [ $NONBEHAVIOR -eq 1 ]; then
|
||||
NAMES+=("00 冒烟测试"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 00 冒烟测试 → SKIP(影响面选闸:测量面改动不触开机路径)"
|
||||
else
|
||||
gate "00 冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
|
||||
fi
|
||||
# 三件保险之一(00/01/09):恒跑不降——测量面批也不免(关联档选闸,2026-10-29 用户裁)。
|
||||
gate "00 冒烟测试" "smoke_boot.log" "$PY" "$REPO/tools/run_smoke.py"
|
||||
# ── 1. 系统测试(真实玩法路径断言面;宿主=game/tests/system_test.gd,缺省 --script 由 run_system_test.py 持有)──
|
||||
gate "01 系统测试" "system_test.log" "$PY" "$REPO/tools/run_system_test.py"
|
||||
# ── 2. 单元与集成测试 ──
|
||||
@@ -260,21 +394,10 @@ gate "08e 能力审计" "capability.log" "$PY" "$REPO/tools/capability_audit.py"
|
||||
fi
|
||||
# ── 6c. 回放确定性对拍(条件闸件;属代码域)──
|
||||
if in_domain code; then
|
||||
# 触面判定:本批 diff 相对 master 分叉点,命中下列路径面之一即「触面」。
|
||||
# 触面判定:本批 diff 相对 master 分叉点,命中 PARITY_TOUCH_FACES(脚本上部单点定义)之一即「触面」。
|
||||
# 触面也**不自动跑**——对拍调的是 MC 脚本,属「MC/统计类跑批发起前须用户批准」红线管辖;
|
||||
# 闸只负责每次跑批都把触面判定做一遍并显式要求批准,杜绝「该跑却没人记得」。
|
||||
# 用户批准后设 PARITY_APPROVED=1 重跑本脚本即实跑三变体对拍。
|
||||
PARITY_TOUCH_FACES="
|
||||
game/data/tables/
|
||||
game/core/rng_service.gd
|
||||
game/core/save_manager.gd
|
||||
game/core/meta_progression.gd
|
||||
game/core/settlement/
|
||||
game/core/damage_math.gd
|
||||
game/core/damage_controller.gd
|
||||
game/core/battle_orchestrator.gd
|
||||
game/sim/adventure_monte_carlo.gd
|
||||
"
|
||||
_base="$(git -C "$REPO" merge-base master HEAD 2>/dev/null || true)"
|
||||
_changed_files="$(git -C "$REPO" diff --name-only "${_base:-HEAD}" HEAD 2>/dev/null || true)"
|
||||
_touch_hit=0
|
||||
@@ -315,11 +438,11 @@ else
|
||||
fi
|
||||
|
||||
# ── 与存量基线比对 ──
|
||||
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "$DIRTY_GAME" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
|
||||
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$DOMAIN" "$REPO" "$DIRTY_GAME" "$SCOPE" "$ONLY_TERMS" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
|
||||
import hashlib, json, os, subprocess, sys
|
||||
|
||||
baseline_path, receipt_path, quick, domain, repo, dirty_game = sys.argv[1:7]
|
||||
rest = sys.argv[7:]
|
||||
baseline_path, receipt_path, quick, domain, repo, dirty_game, scope, only_terms = sys.argv[1:9]
|
||||
rest = sys.argv[9:]
|
||||
sep = rest.index("--")
|
||||
names = rest[:sep]
|
||||
tail = rest[sep + 1:]
|
||||
@@ -414,6 +537,8 @@ with open(receipt_path, "w", encoding="utf-8") as f:
|
||||
"timestamp": subprocess.run(["date", "-Iseconds"], capture_output=True, text=True).stdout.strip(),
|
||||
"quick_mode": quick == "1",
|
||||
"domain": domain,
|
||||
"scope": scope,
|
||||
"scope_terms": [t for t in only_terms.split() if t],
|
||||
"dirty_game": dirty_game == "1",
|
||||
"first_baseline_run": first_run,
|
||||
"gates": [{"name": n, "exit": e, "fingerprint": now[n]["fp"]} for n, e in zip(names, exits)],
|
||||
|
||||
@@ -2,22 +2,6 @@
|
||||
|
||||
本清单是流水线的唯一登记处:要做的事先在此登记一条,随流水线段推进改状态,做完整条删除;各系统文档的「待办」节只留一行指路存根,不再自建条目。条目格式(标题=登记元数据含可选优先级,正文=实况/缺口/改动面/状态四属性)与状态机以 [doc-conventions §12.1](design/engineering/doc-conventions.md) 为权威。
|
||||
|
||||
#### 合回闸关联档——收口跑批按改动关联面选测(2026-10-29 登记,来源=会话提及)
|
||||
|
||||
**实况**:起因=2026-10-29 迭代 41 收口跑批遇红树空磨(公共件解析错致每套件烧满 120 秒超时,单元段预计 2h+,会话全程静默)。用户裁:合入不再强制全量,改为「修改内容关联的测试都跑通即可」。设计要点(会话已闭合):①收口跑批范围=需求卡片对照表申报的测试族 ∪ 按改动文件机械推导的关联测试族(并集执行,防申报漏标);②三件廉价保险恒跑不降(冒烟/系统测试/静态扫描,合计约 1.5 分钟);③横切面改动(config_db、autoload、存档、RNG、数据表等公共件)映射回落全量;④回执加 scope 字段标注关联档,合回闸校验逻辑不动(树对齐/祖先/域覆盖照验);⑤绿树窄批收口约 3–4 分钟(现全量约 10 分钟)。现行「影响面选闸」(测量面批 SKIP 探针/渲染)是本条推广前的特例。规则同步义务:AGENTS.md「交付前验收」句与 process-conventions §3 合回闸细则同批改写,禁只改闸不改规则。关联条目:「闸机编排快速失败」(本条管跑多宽,彼条管红树磨多久,正交互补)。执行=用户发令;若选闸逻辑下沉 run_all.gd 则含游戏代码面、须走 worktree 批。未决点:「合入后自动补跑一次全量作兜底」待用户裁(折中案=合入前快查+合入后兜底;激进案=合入前只跑关联档)。证据=iter41 worktree 收口跑批现场日志(game/tests/.gates/unit.log 逐套件「超时挂死(120s),进程树已整树回收」记录,2026-10-29),与「迭代 41 测试债修复」条互证。
|
||||
|
||||
**改动面**:工具代码
|
||||
|
||||
**状态**:待执行
|
||||
|
||||
#### 闸机编排快速失败——连续套件超时中止整轮(2026-10-29 登记,来源=会话提及)
|
||||
|
||||
**实况**:单元与集成编排器现行逐套件 120 秒超时回收,红树(如公共件解析错致全部套件挂死)下单元段最长 64×2 分钟≈2h08m,整轮磨完才出红。用户裁加快速失败:连续 N 个套件超时(N 实现期定,3 起步)即中止整轮并汇总已见红因,红树几分钟出结论。run_all.gd 属代码面,走 worktree 批;执行=用户发令。证据=iter41 worktree 收口跑批现场日志(run_all_10→31 逐套件恰以 2 分钟间隔落盘=每套烧满超时,2026-10-29),与「迭代 41 测试债修复」条互证。
|
||||
|
||||
**改动面**:游戏代码
|
||||
|
||||
**状态**:待执行
|
||||
|
||||
#### 验证与清单体系整改——四方向分阶段推进(2026-10-28 登记,来源=会话提及)
|
||||
|
||||
**实况**:用户裁「验证及清单不合理」,经调研定四方向全做、分阶段推进:A 粒度整改(清单从断言级升到能力/行为级,逐断言账本退役)→ B 精简裁断(在新粒度上规则驱动删并)→ C 流程风险分级(P0/P1/P2 验收强度、审计抽样)→ D 文档减肥(待办条目限长、历史归 git);测试有效性度量暂不处理。阶段 0 盘点已毕:验证集清单零机器闸依赖(run_all_gates/hooks/五技能均不引用,仅 test-conventions §8.3 维护锚+2 份过程报告引用),退役无闸改动;但测试名尾缀仅覆盖 28/47 个有断言文件(19 个零尾缀,含 seal_polarity 69 条、variant_placement 42 条、roll_multiplier 35 条),故能力清单只能按「能力→代表用例」建,不要求逐断言尾缀。对照表列改动触 pipeline_gate+req-card/iter 两技能;待办正文压缩不触机检(无长度判据)。盘点明细见 reports/盘点-验证与清单体系-2026-10-28.md。**阶段 1 设计已闭合(2026-10-28 用户四裁点)**:能力清单 v1 落 `game/tests/README.md`——按系统归属与玩家旅程排序,首期核心四域(存档与随机、冒险循环、战斗结算、奖励与经济)29 行+跨域条件闸 1 行,共 30 行,每行真实入口+2–3 个代表用例(`文件:函数`),维护规则为每批收口核对本批受影响行并刷新「最后验证批次」;`design/engineering/test-conventions.md` §8.3 维护锚改写为「验证集清单整册冻结、能力清单承接覆盖索引、阶段 2 精简后删除」;`game/tests/验证集清单.md` 头注同步冻结声明,冻结快照(基点 `b9260138`/1587 条)留作阶段 2 对账基准;`tools/extract_checks.py` 保留为审计工具,不再承担常态同步。**阶段 2 已执行闭合(2026-10-28 C 档合回)**:存量断言精简 C 档 6 组手写断言族并为 7 个表驱动站点(33→7),冻结清单对账 1587→1561,验证集清单随阶段 2 完成退役(覆盖索引归能力清单,维护锚 test-conventions §8.3),删并申报与件核见 reports/工程改动报告-存量断言精简C档-2026-10-28.md;阶段 3–4 待各自设计推进:风险分级与审计抽样规则、条目限长是否入 pipeline_gate 均未定。**2026-10-29 阶段 1 落地定稿(用户三裁)**:①能力清单与覆盖域表落 `game/tests/README.md`,独立成表、不与 GDD 验收标准合并;②GDD 纯度判红——系统与概念文档全文禁写实现细节(代码路径/测试文件名/断言名),唯一例外=配置文件索引(`game/data/tables/*.tres` 表路径与表名);③能力清单维护归写卡段——写需求卡片时增删能力行并刷新「最后验证批次」,实现批禁写该文件(清单按规则滞后一个迭代,属设计后果)。三层模型(验收意图/覆盖索引/执行证据)与四法则落 test-conventions §8.9;落地分五批:①三份规约+AGENTS.md 红线+本条实况(本批);②README 覆盖域表+三域能力行补齐;③`tools/capability_audit.py` 文档域新闸(3a 普通工具入库/3b 接闸+基线,worktree);④pipeline_gate 对照表加「能力行」列+req-card/iter 两技能同步;⑤GDD 存量实现细节清理(28 篇约 98 处,逐处去向表过用户目后落笔)。**2026-10-29 阶段 1 五批全部落地**:①032d923c;②5377b2ce;③`tools/capability_audit.py` 入库 ae3c99e3+接闸上电 915222fa(文档域闸 08e 能力审计,纯度红清零后才上电);④f3263444;⑤GDD 清理 00312e12(30 篇去 124 处实现指证,纯度红 124→0)。阶段 1 收口;阶段 3–4 待各自设计推进。
|
||||
|
||||
Reference in new issue
Block a user