feat(闸收敛批): 验收闸唯一入口 run_all_gates.sh + 合回闸回执强制 + t17 探针隔离修复 + 文档闸清单单一事实源
用户令「迭代完成前必须全量跑一遍所有测试」。起因是 iter27 台账只记 probe --click 未记 --full,t17_defer_probe 红了整个迭代无人发现——根因是闸清单散落 CLAUDE.md / process-conventions.md / test-conventions.md 三处、彼此不一致、且无任何机器校验。 - tools/run_all_gates.sh(新):全部验证闸的唯一入口与唯一清单,串跑冒烟/单元/探针 --click/--full/渲染/doc 三闸/静态扫描/熵增闸共十道,写回执 game/tests/.gates/receipt.json。 与 baseline.json 比对判定:基线红且红项清单未变=存量红零新增(不判失败), 基线绿转红或红项清单变化=新增红(判红)。python 解释器自动解析带 PIL 的解释器 并前置 PATH(渲染闸三个脚本依赖 PIL,用错解释器会假红)。 - .zcode/hooks/worktree-guard.sh:Bash 分支加合回闸——merge/rebase/cherry-pick 进 master 前按目标分支反查 worktree 回执,校验 commit 匹配/非 quick/无新增红三态, 任一不满足 exit 2 拦截(九态实测:匹配放行、commit 错/quick/含红/无回执/rebase 全拦)。 - game/smoke/t17_defer_probe.gd:隔离面从 main.save_manager = iso_c 改 ProbeUiUtil.isolate_save(iter27 已确立口径)。原写法对 getter-only 属性赋值无效 ——组合根 save_manager/meta 是 autoload 读视图,隔离必须落在单例上。修复后 --full 转绿。 - doc_link 三处死链归零:数据配置.md 已退役探针指路、元进度.md/天赋树.md 已改名文档链接。 - CLAUDE.md / process-conventions.md §3第10条 / test-conventions.md §4 三处 「交付前跑什么」统一为一句话=跑 run_all_gates.sh,删散落清单。 闸况:冒烟 0 / 单元 0 / --click 0 / --full 0 / 渲染 0 / doc_link 0 / doc_term 0 / 熵增 0;gdd_lint 与 static_scan 为存量红,已落 baseline.json 待判读清偿。
This commit is contained in:
1 parent
81c0ee2bb6
commit
1d4ac91e3b
10 files changed
+300
-19
No files matched your search
@@ -16,11 +16,11 @@
|
||||
|
||||
## 运行与验证(工作目录 = 含 project.godot 的切片目录)
|
||||
|
||||
- 冒烟:`python tools/run_smoke.py`(三闸:退出码+摘要失败数+SCRIPT ERROR 扫描;脚本错误不计失败数但静默中断剩余断言,读结果必核对)
|
||||
- 场景探针:`…console.exe --headless --path . res://smoke/ui_probe.tscn`(另有 ui_tune_probe / ui_click_probe)
|
||||
- **交付前验收 = 跑 `tools/run_all_gates.sh`(不带 `--quick`),全绿才合回 master**——它是全部验证闸的唯一入口与唯一清单,闸件增减只改它,文档禁另列清单。跑完写回执 `game/tests/.gates/receipt.json`,合回时由 worktree-guard 校验(缺回执/quick 模式/回执 commit ≠ HEAD/含红项四况一律拦截)
|
||||
- 单件直跑(调试期最窄确认用,**不构成验收**):冒烟 `python tools/run_smoke.py` · 场景探针 `…console.exe --headless --path . res://smoke/ui_probe.tscn` · 单元 `… --script res://tests/run_all.gd`
|
||||
- 调参批量:`--script res://smoke/adventure_monte_carlo.gd`(全冒险期望)· `res://smoke/reward_expectation.gd`(奖励期望);通关率/难度类测量一律走全冒险
|
||||
- 数值验证:`python tools/balance_verifier.py`(采集端 `res://smoke/verifier_batch.gd`,报告落 `design/qa/`)
|
||||
- 交付前验收 = smoke + 全部场景探针全绿;熵增闸 = `python tools/repo_structure_audit.py`(新类别 = 同批修订 Project Layout+白名单)
|
||||
- 熵增闸(新类别 = 同批修订 Project Layout + 白名单):`python tools/repo_structure_audit.py`
|
||||
|
||||
## Project Layout
|
||||
|
||||
@@ -53,6 +53,7 @@
|
||||
|
||||
- 主工作区禁止直接改动与提交——一切改动走独立 worktree(豁免:active.md 会话收口、.zcode 钩子配置)
|
||||
- 测试执行两入口铁规——只有迭代 loop 开发与用户点名两个触发入口;其余会话零测试,需测试支撑先请示并注明未跑测
|
||||
- 交付前验收 = 跑 `tools/run_all_gates.sh` 全绿(唯一入口与唯一闸清单,禁在文档另列清单);合回 master 由 worktree-guard 校验回执强制(process-conventions §3)
|
||||
- MC/模拟:无数值改动零跑;档位硬闸见 `sim-data/README.md`「MC 采集档位表」,一档只能由用户发起
|
||||
- 闸机轮次纪律:新码先孤立验证 → 出红只跑红件最小单元 → 长批先最小参数试车 → 开工扫坑册(process-conventions §6)
|
||||
- 变异验证——新增/改写验收函数必须变异变红才算验收成立;无变异红记录只写「已实现,拦截能力未证」(test-conventions §6)
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
9. **验收函数变异验证**——新增/改写的验收函数(冒烟断言/探针自检/闸机判据)必须配一次变异验证:故意把被测件改坏成该类缺陷形态 → 跑最窄那道闸确认变红 → `git checkout` 精确还原;变异点/跑的闸/红输出摘要/还原结论四件记入实现报告变异验证记录表;**无变异红记录的断言不写「已验收」,只写「已实现,拦截能力未证」**(细则见 test-conventions §6)
|
||||
|
||||
**收口**
|
||||
10. **批末四闸全绿才合并**——冒烟+单元测试+场景探针+改前改后读数对拍
|
||||
10. **批末全量验收才合并**——在任务 worktree 内跑 `tools/run_all_gates.sh`(不带 `--quick`),全绿才合回 master。该脚本是全部验证闸的**唯一入口与唯一清单**,闸件增减只改它;**本文与其他文档禁另列闸件清单**(清单散落多处正是 iter27 漏跑 `probe_gate --full` 的成因)。跑完写回执 `game/tests/.gates/receipt.json`,合回时由 worktree-guard 强制校验
|
||||
11. **合并后主区全量复跑**——合并可能引入冲突或环境差异,落回 master 后再跑全套
|
||||
12. **会话收口落记**——active.md 快照与状态记录更新(含删除已完结旧条目,doc-conventions 第 10 节);跨会话教训写入记忆库
|
||||
13. **文档同步闸**——机械面文档(roadmap 行/registry/active.md 记账/README 工具注)合并前随批更新完毕方算收口;设计权威文档批内不更新:实现批收口出迭代实现报告(`design/qa/iter<N>-实现报告-<日期>.md`:各卡状态+代拍板追认单+GDD 反哺点节),反哺点同步登记 `06-待办清单`,用户逐点裁决后另批落文;反哺点未落文期间相关 GDD 节视为待反哺,读前先查 06;需求卡片必列「关联文档同步面」按两层标注
|
||||
@@ -57,7 +57,7 @@
|
||||
|
||||
**MC/verifier 触发细则**:仅在数值验证场景触发(改数值前后取证);normal 档为主、perfect 按需;**无数值改动的会话禁跑 MC**;结构位移类「存档锚」须先向用户请示定性再跑;需求卡片预设步骤的前提消失时(如「带值裁定后重锚」实际未投值)停问勿照字面执行。
|
||||
|
||||
**分级周期口径**:轻闸(run_smoke 全量+ui_input_probe;触碰 presentation 加跑 ui_click_probe+ui_layout_probe)可随变更执行;重闸=轻闸+probe_gate --click+probe_gate --full(ui_probe/ui_tune_probe/t17_defer_probe/fx_probe 四探针)+render_gate(窗口化渲染闸:shot_probe 七屏样张 A-D 断言+fx_shot/board_shot 取证+棋盘 golden 逐像素)+run_all 单元全套+doc 三闸(gdd_lint/doc_link_audit/doc_term_audit)+熵增闸(repo_structure_audit);重闸属周期执行——统一在批次收口或固定周检跑,不进每次变更;真人实机目检归用户不进自动闸。
|
||||
**分级周期口径**:全部验证闸收编在 `tools/run_all_gates.sh` 一个入口下(**脚本即清单**),分两档——`--quick` 档跳过渲染闸(需开显示器)与静态扫描,供迭代中段自检;**收口出库一律跑全量档(不带 `--quick`)**,回执由合回闸强制校验。真人实机目检归用户不进自动闸。
|
||||
|
||||
## 5. MC 采集档位细则
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 测试规范(Dungeon Sweeper)
|
||||
|
||||
> 约束范围:`game/smoke/`、`game/tests/` 与一切场景探针的编写与验收。
|
||||
> 分工:测试**执行许可**(什么时候允许跑测试)归 CLAUDE.md「硬性红线·测试执行两入口铁规」;命令与工具清单归 CLAUDE.md「运行与验证」;本文管测试怎么写、怎么算合格。
|
||||
> 分工:测试**执行许可**(什么时候允许跑测试)归 CLAUDE.md「硬性红线·测试执行两入口铁规」;**跑什么闸、什么顺序**归 `tools/run_all_gates.sh`(脚本即清单,本文与 CLAUDE.md 均不另列);本文管测试怎么写、怎么算合格。
|
||||
|
||||
**写任何测试前必须通读本文。**
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
|
||||
## 4. 提交前全量验证
|
||||
|
||||
交付前必须全绿:`smoke_all.gd` + 全部场景探针(ui_probe / ui_click_probe / ui_tune_probe)。
|
||||
交付前跑 `tools/run_all_gates.sh`(不带 `--quick`)全绿——该脚本是全部验证闸的唯一入口与唯一清单,本文禁另列闸件清单。闸件增减只改脚本。
|
||||
行为探针使用随机化玩家行为(旗/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。
|
||||
|
||||
## 5. 测试函数怎么写——验收清单条目的落码规范
|
||||
|
||||
@@ -218,6 +218,6 @@
|
||||
| v0.1 | 2026-09-15 | 自归档 GDD 压缩改写 |
|
||||
| v0.2 | 2026-09-15 | 雾格残留清理:关闭「待拍板」的棋盘生成参数表雾字段清理登记(fog_region_count / fog_target_ratio 两字段、雾占比绝对上限与雾域跨字段校验)——字段与校验随雾格移除同批删除,registry `board_gen_table` 条目同步修订。表清单与校验纪律不变 |
|
||||
| v0.3 | 2026-09-17 | 代币→钥石统一拍板随批措辞清偿:表清单冒险表行「代币记账」改「钥石入账」;registry adventure_table 同批改键 token_per_level→keystone_per_level。表清单与校验纪律不变 |
|
||||
| v0.4 | 2026-09-18 | **进度坐标解析口径修订(用户拍板,场景三讨论产物)**:① 取整方向「就近向下」→「**向上取整**」——无精确行取不低于请求坐标的最近行,理由 = 保证偏难(宁高估难度让工具读数保守,不静默降难掩盖断档);② 越域致命补齐——低于合法域(关卡序号 <1、节点深度 <0)走运行期致命:Fatal 级诊断 + 进程退出码 1,与装载期致命纪律并轨;**同日追加拍板**:高于最大已有行不致命,封顶取最大已有行并留警告(用户裁定「可取范围内向上取整,超过了最大值就用最大值」);③ registry enemy_table 条目「进度键 floor」随批改 ceil;切片 config_db._resolve_progress 同批落地(ceil 选择器 + _fatal 通道 + `smoke/config_fatal_probe.gd` 探针 + 冒烟 ceil 断言) |
|
||||
| v0.4 | 2026-09-18 | **进度坐标解析口径修订(用户拍板,场景三讨论产物)**:① 取整方向「就近向下」→「**向上取整**」——无精确行取不低于请求坐标的最近行,理由 = 保证偏难(宁高估难度让工具读数保守,不静默降难掩盖断档);② 越域致命补齐——低于合法域(关卡序号 <1、节点深度 <0)走运行期致命:Fatal 级诊断 + 进程退出码 1,与装载期致命纪律并轨;**同日追加拍板**:高于最大已有行不致命,封顶取最大已有行并留警告(用户裁定「可取范围内向上取整,超过了最大值就用最大值」);③ registry enemy_table 条目「进度键 floor」随批改 ceil;切片 config_db._resolve_progress 同批落地(ceil 选择器 + _fatal 通道 + 独立进程致命类单测 + 冒烟 ceil 断言) |
|
||||
| v0.5 | 2026-09-27 | **统一随机流批(ADR-0007 裁丙——用户两轮复裁终选「统一用一个流」并拍「全面执行」)**:§「封闭随机流」整节改「统一随机流」口径——唯一主种子+单盐派生+唯一全局取数计数器,七流并一;取号参数降格为**用途标签**(只进事件日志不参与派生,七流名保留为封闭词表);新增**全事件日志**(冒险会话制 user://logs/rng_log_<主种子>.jsonl,逐次取数落 scope/标签/派生种子——号位全序化后单点定位的承载面);快照契约=主种子+全局计数器;扩签通道替代增流通道;表清单/依赖表/P0/验收行随批。代码实证:rng_service.gd 重写+七消费点迁移+armor_roll_stream 字段降格标签+behavior_roll 子流派生向量退役;新黄金向量 8 常量重登记(scope 0-3 与旧 board_gen 向量逐位连续)|
|
||||
| v0.6 | 2026-10-09 | **文档正确性审查批·独立复核(worktree docaudit-2026-10-09——四处表清单失同步,全部代码实证)**:①变异表行「现行六行」→七行(variation.tres 实测 7 行=region 6+global 贫矿脉,2026-10-04 首发批漏登);②奖励配置表 `RewardConfigTable` 行删除(表已随可选类删除下线——reward_config.tres 与类名全仓零命中);③秘宝表 `AuxItemTable`→`TreasureItemTable`(treasure_items.tres,词族对齐批换代漏登);④MetaTable「四行族」→现行五行族(+attune_refresh_upgrade,同调批扩列漏登);⑤主角表行补 armor_class 字段 |
|
||||
@@ -180,7 +180,7 @@ MVP 不要求本系统的闭环([不做清单](../../05-不做清单.md)整体
|
||||
| v1.9 | 2026-10-09 | 文档正确性审查批(定点):解锁双入口框架节「局内暂停层入口未实装(随 HUD 批 M-4)」过期订正——暂停层只读入口已实装(2026-10-06 迭代 17 卡③,实机目检待用户;06 登记条在册);裸 M-4 标签随订正消除 |
|
||||
| v1.10 | 2026-10-09 | **B2 裁决落文(用户令「删除旧的、直接写新的」)**:道具工坊行删旧写新——旧「现行六行」登记作废(旧目录已随秘宝 v1.5 全退役),改写=件的目录与效果现行事实源指路秘宝.md「目录(重设计 v1)」+解锁门槛谓词分级(普通 0 门槛/优秀中里程碑/稀有高里程碑,阈值归调参批)+通关次数渠道与钥石直购价归定价批后补 |
|
||||
| v1.11 | 2026-10-09 | **B1 裁决落文(用户令「元进度让他们都独立去写」——天赋树独立成文)**:①定名节冲突表述换代——「归档线索里的『天赋树』一词就此退役」与 v1.8 审查注(「本作不存在树形结构」失义登记)同步摘除,改写 = 退役的是归档线索旧概念的旧义、树形结构已随迭代 12 以锻造脉形态落地在役(系统现行事实源 = 天赋树.md,本批新建 v0.1);②三柜台节补指路句——属性锻造树形节点结构现行事实源 = 天赋树.md,旧三档柜台与树四线 flat 注入面叠加实态随句登记。机制零改动 |
|
||||
| v1.12 | 2026-10-09 | 武器锻造成文批随迁(用户令「完成文档编写、代码不实现、记录待办」):三柜台节补指路句——属性锻造攻击/护甲两档的路线化改造设计权威文档 = [武器锻造](武器锻造.md) v0.1(按 2026-10-07 提案推荐案成文、17 裁点开放在册、实装待办在册 06;改造落地前三档直购维持现役)。机制零改动 |
|
||||
| v1.12 | 2026-10-09 | 武器锻造成文批随迁(用户令「完成文档编写、代码不实现、记录待办」):三柜台节补指路句——属性锻造攻击/护甲两档的路线化改造设计权威文档 = [武器锻造](锻造工坊.md) v0.1(按 2026-10-07 提案推荐案成文、17 裁点开放在册、实装待办在册 06;改造落地前三档直购维持现役)。机制零改动 |
|
||||
| v1.14 | 2026-10-11 | 秘宝匣行换代随批(秘宝.md v2.7 目录修正批):13 件三档+解锁混合制口径;旧「正主/谓词分级」表述同步清除(拾取渠道指路同日追加又随终裁废止) |
|
||||
| v1.13 | 2026-10-10 | **「道具工坊」改名「秘宝匣」(2026-10-10 天赋树设计会话裁——树上解锁节点与营地柜台同名)**:现行正文六处扫改(三柜台行 / 柜台行 / 柜台纪律 / 后续商品行 / 调参表 P1 / 定价表行);版本历史行一律不动;别名「道具工坊」登记 registry 术语条保留检索。机制零改动 |
|
||||
| v1.14 | 2026-10-11 | **营地统一案甲随迁(2026-10-11 天赋树设计会话终裁)**:三柜台节补「营地统一」段——树=营地本体、属性升级唯一入口=树脊柱淬炼节点、本属性锻造柜台随 06「天赋树实装批」退役吸收进脊柱(落地前维持现役)、锻造柜台与秘宝匣转工作间钥匙模型、图鉴常开;设计权威文档指路 = 天赋树.md v0.2。机制零改动(退役与公式换代随实装批) |
|
||||
|
||||
@@ -206,7 +206,7 @@ A/B 态只放规则面:节点行持 a/b 双效果键(封闭规则键词表 R
|
||||
| v0.8 | 2026-10-11 | **Fork 一节点双态换代批(用户裁)**:①开局姿态/危险直觉/财富之道三天赋=三节点——现役 A/B 双行结构(迭代 12 沿袭,「付一次钱得双态开关」裁决未落进节点组织=落文错误已纠正)收编为单行双态,同调脉 9→6(同调位 3+Fork 3)、全树 15→12;②节点语义=购买解锁双态开关(cap1、零重复购买)、解锁默认 A 态、营地零币切换(互斥由单态承载);fork_sides 存档语义不变(组→激活态),tree_buy_fork=购买解锁/tree_toggle_fork=零币切态;schema=Fork 行持 a/b 双效果键、效果按激活态取值、Fork 组词表校验改每组恰一节点;③R1(「等级阈值前置」形态去留)不随本批变化,仍开放待裁(推荐裁撤)。实装归 06「天赋树实装批」(33→12 连带,需求卡片 v1.6 同批) |
|
||||
| v0.7 | 2026-10-11 | **一线一节点换代批(用户裁)**:①脊柱淬炼一线一节点——生命淬炼/命中淬炼各收编为单节点 4 级(原「一线 4 层×层内 3 购」层=级收编;用户明裁:一个内容一个节点、等级在节点内、不拆多节点),脊柱 8→2、全树 21→15;②行门槛平移——SPINE_ROW_GATE=4 公式不变,门槛主语从「层 N」改「节点第 N 级」(第 2~4 级需全树总投入 ≥4/8/12),row 字段随层行退役;③彩蛋条件随结构换代为「生命/命中两节点 4 级拉满 且 同调位 1/2/3 全部解锁」;④R1(「等级阈值前置」形态去留)不随本批变化,仍开放待裁(推荐裁撤)。实装归 06「天赋树实装批」(33→15 连带,需求卡片 v1.5 同批) |
|
||||
| v0.6 | 2026-10-11 | **攻防所有权切分落文批(锻造工坊裁决会话 A 组定案,天赋树会话交接落文)**:①脊柱换代 16→8——攻击淬炼×4 层与护甲淬炼×4 层删除,脊柱=生命/命中两线×4 层,全树 29→21 节点(每根数值轴全游戏单一来源:生命/命中→树脊柱、攻击→锻造工坊武器路线、AC→甲路线,树零攻防来源=红线);②锻造工坊钥匙节点前置改「无」(free、直接可点亮,原「攻击淬炼累计 ≥2 且 护甲淬炼累计 ≥2」等级阈值前置随两线砍除作废)——连带开放待裁点 R1:schema「等级阈值前置」新形态现无消费者,裁撤(推荐)/留作机制位待用户裁;③地脉共鸣换代——条件改「生命/命中两线全部买满(8 节点全上限)且 同调位 1/2/3 全部解锁」、效果改生命/命中两线 flat(攻/护分量摘除),上限 3 枚不变;④行门槛 SPINE_ROW_GATE=4 规则保留(层 N 需全树总等级 ≥4×(N−1)),两线换代后总等级供给结构变化=达成节奏重算注记在册、值归数值批;⑤叠加公式换代——开局属性=角色基础值+树生命/命中淬炼 flat+锻造路线加值(只按当前选中生效)+局内加算区 flat(命中在账未注入披露不变);⑥营地呈现措辞对齐四柜台并排(树淬炼柜台=树脊柱界面形态、锻造工坊柜台钥匙点亮前=锁定态,机制语义=树营地本体/钥匙模型/图鉴常开不变,A② 裁);⑦节点总表/一脊三脉/验收标准/调参表/依赖表连带换代。实装归 06「天赋树实装批」(结构目标 33→21 连带换代,需求卡片 v1.4 同批) |
|
||||
| v0.5 | 2026-10-11 | 锻造工坊更名批随迁(机械扫):五处 [武器锻造](武器锻造.md) 链接随系统更名「锻造工坊」换代为 [锻造工坊](锻造工坊.md)(v0.2 历史行内旧名按历史记载保留)。结构零改动 |
|
||||
| v0.5 | 2026-10-11 | 锻造工坊更名批随迁(机械扫):五处「武器锻造」链接随系统更名「锻造工坊」换代为 [锻造工坊](锻造工坊.md)(v0.2 历史行内旧名按历史记载保留)。结构零改动 |
|
||||
| v0.4 | 2026-10-11 | **歧义扫描清零批(/grilling 六裁落文)**:①Q1 术语回归正主——分叉效果废「陷阱/危险标记」自造词,改「踩中危险格伤害减半」「危险格标记错误返还行动点」,玩家文案沿「危险格/安全格」;②Q2 地脉共鸣效果改生命/攻击/护甲三线(命中分量待命中线注入随批加回,不留永久无效分量);③Q3 行门槛总等级口径写明=tree_nodes 全部节点等级之和(免费点亮与分叉激活计入,沿现役实态);④Q4 财富之道范围定界——聚=仅战斗掉落渠道、散=全部 GP 消费面(用户裁);⑤Q5 限次计数生命周期落句(随冒险持久/按关重置/新冒险清零/断点保留);⑥Q6 三处旧裁决换代——已建未接面槽位判定源句换 Q2 裁决(树点亮数+旧条件作废删除)、刷新升级条补 Q1 双入口并存、存档域节「迁移重做」整节换代为「旧档废弃」(Q3 裁决落文,禁迁移红线);顺带清「Y 升级行族」黑话为「刷新升级行族」。结构零改动 |
|
||||
| v0.3 | 2026-10-11 | **节点台账补全批(用户令:台账指引升格文首+29 节点总表落文)**:文档最前新增「节点台账指引」块(节点结构查本文总表 / 数值查配置文件 `game/data/tables/meta_tree.tres` 双源分工声明);核心规则节补「节点总表(29 节点)」——17 行折叠式全名单(脊柱四线按层折叠),逐节点名称/脉/类型/前置/效果方向,数值不进正文沿配置纪律。结构零改动(纯台账补全) |
|
||||
| v0.2 | 2026-10-11 | **天赋树设计会话裁决批(结构换代+分叉效果定案+营地统一)**:①结构换代 33→29——锻造枝三属性节点(武器锻造/护身锻造/甲胄锻造,旧柜台镜像重复件)删除换单个免费解锁节点「锻造工坊」(前置=攻击淬炼累计≥2 且护甲淬炼累计≥2,等级阈值前置新形态);克制专精 Fork 对整组砍除(类型表达归武器锻造);②分叉效果定案——开局姿态两侧收每关首轮窗(先手压制=每关首轮 AP+1 / 稳健拓荒=每关首轮内受击减伤)、雷感直觉改名危险直觉(锋=陷阱伤害减半 / 守=误标返 AP·均每关限次;标错机制存在性 T0 实查在册)、经济人格改名财富之道(聚=GP 获取增益 / 散=消费折价,避开两处在册旋钮);现役两键占位态随实装批终结;③营地统一案甲——树=营地本体,属性升级唯一入口=树淬炼节点,旧属性锻造柜台退役吸收进脊柱,工作间钥匙模型(锻造工坊/秘宝匣两钥匙+图鉴常开);④叠加公式换代=基础值+树淬炼+锻造路线加值+局内加算;⑤同调位 1 改无前置根节点、串行保留;地脉共鸣条件改四线全部买满;⑥「道具工坊」改名「秘宝匣」;⑦树上刷新升级节点定性缺陷(购买写 attune_refresh_level 同账修复随批);迁移映射重做(forge_levels→脊柱线)。全部实装归 06「天赋树实装批」(原「已建未接面两件」+「结构重构批」两条合并) |
|
||||
|
||||
@@ -3,9 +3,11 @@
|
||||
# 真实信号路径驱动一场战斗到击杀胜利(结算行在排播队列),断言两条:
|
||||
# ①胜利同帧:终局面未开 + 挂起态在案 + 队列非空(旧行为=同帧 flush+开窗,本断言即红——判别性);
|
||||
# ②排播放完:终局面开启(演播完边沿派发——_on_pump_ended)。
|
||||
# 存档/设置/元进度三面隔离(fx_shot_probe 同款),零真实档触碰。headless 可跑(不抓帧)。
|
||||
# 存档/设置/元进度三面隔离(isolate_save 正主,meta 经 autoload 链同源),零真实档触碰。headless 可跑(不抓帧)。
|
||||
extends Node
|
||||
|
||||
const ProbeUiUtil := preload("res://smoke/probe_ui_util.gd") # 隔离样板单点
|
||||
|
||||
var main
|
||||
var _problems: Array = []
|
||||
|
||||
@@ -15,14 +17,10 @@ func _ready() -> void:
|
||||
main = load("res://presentation/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
await get_tree().process_frame
|
||||
for p0 in ["user://t17_defer_probe.json", "user://t17_defer_probe_settings.cfg"]:
|
||||
if FileAccess.file_exists(p0):
|
||||
DirAccess.remove_absolute(p0)
|
||||
var iso_c = load("res://core/save_manager.gd").new("user://t17_defer_probe.json")
|
||||
iso_c.load_from_disk()
|
||||
main.save_manager = iso_c
|
||||
main.meta = load("res://core/meta_progression.gd").new(main.db, iso_c)
|
||||
main.settings_store = load("res://presentation/settings_store.gd").new("user://t17_defer_probe_settings.cfg")
|
||||
# 存档/设置两面隔离(ProbeUiUtil.isolate_save 正主,先删后建防陈旧档泄入)——
|
||||
# 隔离面走 attach_path 原地重置 autoload 单例:组合根 save_manager/meta 是getter-only
|
||||
# 读视图,直接赋值无效,隔离必须落在单例上。
|
||||
ProbeUiUtil.isolate_save(main, "t17_defer_probe", false, true, false)
|
||||
# 本探针验证完整动画档的演播后排窗——强制 motion_scale=1.0(隔离档替换在 boot 后,
|
||||
# 减动效已随真档 settings.cfg 应用于引擎态;减动效档=consume_now 快路、开窗即时,另属语义)
|
||||
UiKit.motion_scale = 1.0
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
# 本目录只入库 baseline.json(存量基线,需随代码走以保证各 worktree 判定口径一致)。
|
||||
# receipt.json 是每跑批覆写的当次回执、*.log 是跑批当场日志——两者都是中间产物,不入库。
|
||||
receipt.json
|
||||
*.log
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成",
|
||||
"gates": {
|
||||
"01 冒烟全量": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"02 单元套件": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"03 探针 --click": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"04 探针 --full": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"05 渲染闸": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"06 gdd_lint": {
|
||||
"exit": "1",
|
||||
"fp": "d09eed46271b"
|
||||
},
|
||||
"07 doc_link": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"08 doc_term": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"09 静态扫描": {
|
||||
"exit": "1",
|
||||
"fp": "3220614b09c4"
|
||||
},
|
||||
"10 熵增闸": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env bash
|
||||
# 重闸唯一入口(2026-10-15 用户令「迭代完成前必须全量跑一遍所有测试」)——
|
||||
# 闸清单的唯一定义处。文档只写「收口跑本脚本」,禁在文档里另列闸件清单(那正是 iter27
|
||||
# 漏跑 probe_gate --full 的成因:清单散落三份文档、彼此不一致、且无机器校验)。
|
||||
#
|
||||
# 跑完写回执 game/tests/.gates/receipt.json(commit + 各闸退出码 + 存量基线比对 + 时间戳);
|
||||
# .zcode/hooks/worktree-guard.sh 在主工作区 git merge 时校验本回执,缺或红即拦截。
|
||||
#
|
||||
# 用法:tools/run_all_gates.sh [--quick]
|
||||
# --quick 跳过渲染闸(窗口化,需开显示器)与静态扫描(纯文本扫描),供迭代中段自检;
|
||||
# 收口出库禁用,必须不带本参数跑全量。
|
||||
#
|
||||
# 判定口径:每道闸与 game/tests/.gates/baseline.json 里的存量基线比对——
|
||||
# 基线绿 + 现绿 → 绿
|
||||
# 基线红 + 现红且清单未变 → 「存量红·对基线零新增」,不判失败(但会在回执里留名)
|
||||
# 基线红 + 现红但清单新增 → 红(新增红项)
|
||||
# 任意闸 exit 与基线不符 → 红
|
||||
# 基线文件首次自动生成(把当次结果当基线),此后每次跑批与之比。
|
||||
#
|
||||
# 退出码:0=全绿或仅存量红零新增;1=有新增红/闸况变化;2=用法错
|
||||
set -u
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
GAME="$REPO/game"
|
||||
STAMP_DIR="$GAME/tests/.gates"
|
||||
RECEIPT="$STAMP_DIR/receipt.json"
|
||||
BASELINE="$STAMP_DIR/baseline.json"
|
||||
QUICK=0
|
||||
[ "${1:-}" = "--quick" ] && QUICK=1
|
||||
[ $# -gt 1 ] && { echo "用法: tools/run_all_gates.sh [--quick]"; exit 2; }
|
||||
|
||||
# ── python 解释器解析:优先带 PIL 的 gamedev venv(渲染闸的 shot_checker/golden_compare 依赖),
|
||||
# 回落 PATH 上的 python。渲染闸的三个脚本要 PIL,用错解释器会假红。
|
||||
PY=""
|
||||
for cand in "/d/dev/python/envs/gamedev/Scripts/python.exe" "$(command -v python 2>/dev/null)" "python"; do
|
||||
[ -z "$cand" ] && continue
|
||||
if "$cand" -c "import PIL" >/dev/null 2>&1; then PY="$cand"; break; fi
|
||||
[ -z "$PY" ] && PY="$cand"
|
||||
done
|
||||
GODOT="${GODOT_BIN:-/d/code/godot/Godot_v4.7.2-stable_win64_console.exe}"
|
||||
|
||||
# 把带 PIL 的解释器目录前置到 PATH——render_gate.sh / shot_checker.py / golden_compare.py /
|
||||
# golden_board_crop.py 内部各自调 `python`,继承本 PATH;用错解释器会因缺 PIL 假红。
|
||||
case "$PY" in
|
||||
*/Scripts/python.exe|*/Scripts/python) export PATH="$(dirname "$PY"):$PATH" ;;
|
||||
esac
|
||||
|
||||
mkdir -p "$STAMP_DIR"
|
||||
declare -a NAMES=() EXITS=() LOGS=()
|
||||
FAILED=0
|
||||
|
||||
echo "重闸全量跑批 · 仓库 $REPO"
|
||||
echo "起跑 commit: $(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '非git目录')"
|
||||
echo "python: $PY"
|
||||
echo "godot: $GODOT"
|
||||
|
||||
# ── 闸件执行:每道闸的输出落独立日志,供基线比对取指纹 ──
|
||||
gate() { # gate <名称> <日志名> <命令...>
|
||||
local name="$1" log="$2"; shift 2
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════"
|
||||
echo " $name"
|
||||
echo "════════════════════════════════════════════"
|
||||
"$@" >"$STAMP_DIR/$log" 2>&1
|
||||
local rc=$?
|
||||
# 失败时把关键行回显到终端(红必须看得见)
|
||||
if [ $rc -ne 0 ]; then
|
||||
grep -E "FAIL|红|Error|error" "$STAMP_DIR/$log" | tail -12 | sed 's/^/ | /'
|
||||
fi
|
||||
NAMES+=("$name"); EXITS+=("$rc"); LOGS+=("$log")
|
||||
return 0
|
||||
}
|
||||
|
||||
gate_unit() { # run_all 是 --script 模式,退出码为唯一判据
|
||||
local name="02 单元套件"
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════"
|
||||
echo " $name(--script 模式,退出码为唯一判据)"
|
||||
echo "════════════════════════════════════════════"
|
||||
( cd "$REPO" && "$GODOT" --headless --path "$(cygpath -m "$GAME")" --script res://tests/run_all.gd ) \
|
||||
>"$STAMP_DIR/unit.log" 2>&1
|
||||
local rc=$?
|
||||
[ $rc -ne 0 ] && tail -12 "$STAMP_DIR/unit.log" | sed 's/^/ | /'
|
||||
NAMES+=("$name"); EXITS+=("$rc"); LOGS+=("unit.log")
|
||||
return 0
|
||||
}
|
||||
|
||||
# ── 1. 系统级断言面(冒烟三闸:退出码+摘要失败数+SCRIPT ERROR 扫描,判定无洞)──
|
||||
gate "01 冒烟全量" "smoke.log" "$PY" "$REPO/tools/run_smoke.py"
|
||||
# ── 2. 单元套件 ──
|
||||
gate_unit
|
||||
# ── 3. 场景探针 · 行为走查段 ──
|
||||
gate "03 探针 --click" "probe_click.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --click
|
||||
# ── 4. 场景探针 · 断言段(iter27 漏跑的就是这一段)──
|
||||
gate "04 探针 --full" "probe_full.log" bash "$REPO/tools/probe_gate.sh" "$GAME" --full
|
||||
# ── 5. 渲染面(窗口化,依赖 PIL)──
|
||||
if [ $QUICK -eq 0 ]; then
|
||||
gate "05 渲染闸" "render.log" bash "$REPO/tools/render_gate.sh" "$GAME"
|
||||
else
|
||||
NAMES+=("05 渲染闸"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 05 渲染闸 → SKIP(--quick)"
|
||||
fi
|
||||
# ── 6. 文档三闸 ──
|
||||
gate "06 gdd_lint" "gdd_lint.log" "$PY" "$REPO/tools/gdd_lint.py"
|
||||
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"
|
||||
# ── 7. 静态扫描域 + 熵增闸 ──
|
||||
if [ $QUICK -eq 0 ]; then
|
||||
gate "09 静态扫描" "static_scan.log" bash "$GAME/tests/static_scan.sh"
|
||||
gate "10 熵增闸" "entropy.log" "$PY" "$REPO/tools/repo_structure_audit.py"
|
||||
else
|
||||
NAMES+=("09 静态扫描"); EXITS+=("SKIP"); LOGS+=("")
|
||||
NAMES+=("10 熵增闸"); EXITS+=("SKIP"); LOGS+=("")
|
||||
echo ""; echo "[gates] 09 静态扫描 / 10 熵增闸 → SKIP(--quick)"
|
||||
fi
|
||||
|
||||
# ── 与存量基线比对 ──
|
||||
"$PY" - "$BASELINE" "$RECEIPT" "$QUICK" "$REPO" "${NAMES[@]}" -- "${EXITS[@]}" -- "${LOGS[@]}" <<'PYEOF'
|
||||
import hashlib, json, os, subprocess, sys
|
||||
|
||||
baseline_path, receipt_path, quick, repo = sys.argv[1:5]
|
||||
rest = sys.argv[5:]
|
||||
sep = rest.index("--")
|
||||
names = rest[:sep]
|
||||
tail = rest[sep + 1:]
|
||||
sep2 = tail.index("--")
|
||||
exits = tail[:sep2]
|
||||
logs = tail[sep2 + 1:]
|
||||
|
||||
stamp_dir = os.path.dirname(receipt_path)
|
||||
|
||||
def head():
|
||||
try:
|
||||
return subprocess.run(["git", "-C", repo, "rev-parse", "HEAD"],
|
||||
capture_output=True, text=True, timeout=5).stdout.strip()
|
||||
except Exception:
|
||||
return "unknown"
|
||||
|
||||
def fingerprint(exit_code, log):
|
||||
"""红项指纹:只取 exit!=0 时的 FAIL 关键行集合,忽略时序/耗时噪声。
|
||||
绿闸一律记 'green'——绿闸日志里的「零红」「全绿」等字样不是红项,采进来只会造噪声。"""
|
||||
if exit_code in ("0", "SKIP"):
|
||||
return "green" if exit_code == "0" else "skip"
|
||||
if not log:
|
||||
return "red-nolog"
|
||||
p = os.path.join(stamp_dir, log)
|
||||
if not os.path.exists(p):
|
||||
return "red-nolog"
|
||||
keys = []
|
||||
with open(p, encoding="utf-8", errors="replace") as f:
|
||||
for line in f:
|
||||
s = line.strip()
|
||||
if not s or "FAIL" not in s:
|
||||
continue
|
||||
keys.append(s[:160])
|
||||
if not keys:
|
||||
return "red-nokeys"
|
||||
return hashlib.sha1("\n".join(sorted(set(keys))).encode("utf-8")).hexdigest()[:12]
|
||||
|
||||
now = {}
|
||||
for n, e, l in zip(names, exits, logs):
|
||||
now[n] = {"exit": e, "fp": fingerprint(e, l)}
|
||||
|
||||
first_run = not os.path.exists(baseline_path)
|
||||
if first_run:
|
||||
with open(baseline_path, "w", encoding="utf-8") as f:
|
||||
json.dump({"note": "存量基线·首次自动生成;闸况变化时本脚本会判红,需人工确认后重生成",
|
||||
"gates": now}, f, ensure_ascii=False, indent=1)
|
||||
base = dict(now)
|
||||
else:
|
||||
with open(baseline_path, encoding="utf-8") as f:
|
||||
base = json.load(f).get("gates", {})
|
||||
|
||||
changed, inherited_red = [], []
|
||||
for n in names:
|
||||
b, c = base.get(n), now[n]
|
||||
if b is None:
|
||||
changed.append("%s:无基线记录(新增闸件)" % n)
|
||||
continue
|
||||
if b["exit"] == c["exit"] and b["fp"] == c["fp"]:
|
||||
continue
|
||||
if b["exit"] != "0" and c["exit"] != "0" and b["fp"] == c["fp"]:
|
||||
continue # 同款存量红,清单未变
|
||||
if b["exit"] == "0" and c["exit"] != "0":
|
||||
changed.append("%s:基线绿 → 现红(exit=%s)" % (n, c["exit"]))
|
||||
elif b["exit"] != "0" and c["exit"] == "0":
|
||||
inherited_red.append("%s:基线红 → 现绿(改善)" % n)
|
||||
else:
|
||||
changed.append("%s:红项清单变化(基线 %s → 现 %s)" % (n, b["fp"], c["fp"]))
|
||||
|
||||
stock_red = [n for n, e in zip(names, exits) if e not in ("0", "SKIP")]
|
||||
all_green = not changed
|
||||
|
||||
with open(receipt_path, "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"commit": head(),
|
||||
"commit_short": head()[:8],
|
||||
"timestamp": subprocess.run(["date", "-Iseconds"], capture_output=True, text=True).stdout.strip(),
|
||||
"quick_mode": quick == "1",
|
||||
"first_baseline_run": first_run,
|
||||
"gates": [{"name": n, "exit": e, "fingerprint": now[n]["fp"]} for n, e in zip(names, exits)],
|
||||
"changed_vs_baseline": changed,
|
||||
"inherited_red": inherited_red,
|
||||
"all_green": all_green,
|
||||
}, f, ensure_ascii=False, indent=1)
|
||||
|
||||
print("")
|
||||
print("─" * 60)
|
||||
if first_run:
|
||||
print("首次跑批:已生成存量基线 %s" % baseline_path)
|
||||
print("本次结果作为基线,后续与之比对。")
|
||||
if changed:
|
||||
print("【闸况变化,需处理】")
|
||||
for c in changed:
|
||||
print(" ✗ " + c)
|
||||
if inherited_red:
|
||||
print("【改善】")
|
||||
for c in inherited_red:
|
||||
print(" ↓ " + c)
|
||||
stock = [n for n, e in zip(names, exits) if e not in ("0", "SKIP")]
|
||||
if stock:
|
||||
print("【存量红·对基线零新增】" + "、".join(stock))
|
||||
if all_green:
|
||||
print("【验收通过】闸况与基线一致,无新增红。")
|
||||
sys.exit(0)
|
||||
sys.exit(1)
|
||||
PYEOF
|
||||
RC=$?
|
||||
|
||||
echo ""
|
||||
echo "════════════════════════════════════════════"
|
||||
printf ' 回执:%s\n 基线:%s\n commit:%s\n' "$RECEIPT" "$BASELINE" "$(git -C "$REPO" rev-parse --short HEAD 2>/dev/null || echo '?')"
|
||||
echo "════════════════════════════════════════════"
|
||||
exit $RC
|
||||
Reference in new issue
Block a user