From 9f80609518c0f690ee48ae121b11eefefa7319f3 Mon Sep 17 00:00:00 2001 From: DKChan <524912269@qq.com> Date: Sat, 7 Nov 2026 01:59:49 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=B1=82=E5=90=88=E5=B9=B6?= =?UTF-8?q?=E6=89=B9(=E7=94=A8=E6=88=B7=E8=A3=81=202026-11-07):=20?= =?UTF-8?q?=E3=80=8C=E9=9B=86=E6=88=90=E6=B5=8B=E8=AF=95=E3=80=8D=E5=B1=82?= =?UTF-8?q?=E7=BA=A7=E9=80=80=E5=BD=B9=E5=BD=92=E5=B9=B6=E5=8D=95=E5=85=83?= =?UTF-8?q?=E5=B1=82=E2=80=94=E2=80=94tests/integration/=20=E4=B8=89?= =?UTF-8?q?=E5=A5=97=E4=BB=B6(=E5=AD=98=E6=A1=A3=E2=87=84RNG=20=E5=BE=80?= =?UTF-8?q?=E8=BF=94/=E8=8A=82=E7=82=B9=E5=9B=BE=E7=A1=AE=E5=AE=9A?= =?UTF-8?q?=E6=80=A7/=E8=B7=A8=E7=B3=BB=E7=BB=9F=E6=B5=81=C3=97=E8=90=BD?= =?UTF-8?q?=E7=9B=98)=E5=AE=9E=E4=BD=93=E8=BF=81=E7=A7=BB=20tests/unit/=20?= =?UTF-8?q?=E5=90=8C=E9=97=B8=E5=90=8C=E5=8D=8F=E8=AE=AE,=20run=5Fall=20ma?= =?UTF-8?q?nifest=2065=20=E5=A5=97=E5=85=A8=E9=87=8F=E5=8D=95=E5=85=83;=20?= =?UTF-8?q?=E9=97=B8=E5=90=8D=E3=80=8C=E5=8D=95=E5=85=83=E4=B8=8E=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95=E3=80=8D=E2=86=92=E3=80=8C=E5=8D=95?= =?UTF-8?q?=E5=85=83=E6=B5=8B=E8=AF=95=E3=80=8D(run=5Fall=5Fgates/?= =?UTF-8?q?=E5=9F=BA=E7=BA=BF=E9=94=AE/coverage=5Freceipts=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5);=20=E9=80=89=E9=97=B8=E8=AF=8D=20integration=20?= =?UTF-8?q?=E6=97=8F=E2=86=92save=5Frng/cross=5Fsystem/node=5Fmap=20?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=90=8D=E8=AF=8D;=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=B1=82=E8=AF=86=E5=88=AB=E6=8D=A2=E4=B8=89=E5=85=83=E7=BB=84?= =?UTF-8?q?(=E8=81=8C=E8=B4=A3=C3=97=E8=A7=A6=E5=8F=91=E6=97=B6=E9=97=B4?= =?UTF-8?q?=E7=82=B9=C3=97=E8=A6=86=E7=9B=96=E9=9D=A2=E7=A7=AF,=20?= =?UTF-8?q?=E6=9D=83=E5=A8=81=E8=A1=A8=3Dtest-conventions=20=C2=A71.1=20?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=B1=82=E4=B8=89=E5=85=83=E7=BB=84),=20?= =?UTF-8?q?=E5=B1=82=E7=BA=A7=E5=90=8D=E9=99=8D=E4=B8=BA=E4=BB=A3=E7=A7=B0?= =?UTF-8?q?;=20=E6=9C=AF=E8=AF=AD=E8=A1=A8=E9=80=80=E5=BD=B9=E8=AF=8D?= =?UTF-8?q?=E5=AF=B9=E7=85=A7+doc=5Fterm=20=E6=94=B6=E3=80=8C=E9=9B=86?= =?UTF-8?q?=E6=88=90=E6=B5=8B=E8=AF=95/=E7=94=A8=E4=BE=8B/=E5=B1=82/?= =?UTF-8?q?=E4=BE=A7/=E5=AE=BF=E4=B8=BB/=E6=97=8F=E3=80=8D,=20=E5=85=A8?= =?UTF-8?q?=E4=BB=93=E6=97=A7=E5=AD=97=E6=A0=B7=E6=B8=85=E9=9B=B6(gdd/?= =?UTF-8?q?=E8=A7=84=E7=BA=A6/README/=E5=A5=97=E4=BB=B6=E5=A4=B4=E6=B3=A8/?= =?UTF-8?q?=E6=96=AD=E8=A8=80=E6=A0=87=E7=AD=BE),=20=E8=B7=A8=E7=B3=BB?= =?UTF-8?q?=E7=BB=9F=E9=9D=A2=E6=80=A7=E8=B4=A8=E5=BD=92=E5=B1=9E=E7=94=B1?= =?UTF-8?q?=E5=90=84=E5=A5=97=E4=BB=B6=E5=A4=B4=E6=B3=A8=E6=89=BF=E8=BD=BD?= =?UTF-8?q?;=20extract=5Fchecks=20=E7=BC=BA=E7=9C=81=E9=81=8D=E5=8E=86?= =?UTF-8?q?=E9=9D=A2=E6=94=B6=20unit/**;=20test-conventions=20=C2=A78.2=20?= =?UTF-8?q?=E9=AA=8C=E6=94=B6=E5=9B=9B=E5=B1=82=E3=80=8C=E9=9B=86=E6=88=90?= =?UTF-8?q?=E3=80=8D=E6=94=B9=E3=80=8C=E8=B7=A8=E5=AE=BF=E4=B8=BB=E3=80=8D?= =?UTF-8?q?;=20architecture=20=E6=B5=8B=E8=AF=95=E5=B1=82=E7=BA=A7?= =?UTF-8?q?=E8=A1=A8=E4=B8=89=E7=BA=A7=E6=8D=A2=E4=BB=A3+65=20=E5=AE=9E?= =?UTF-8?q?=E6=95=B0;=20domain=5Froute/AGENTS=20=E5=AD=97=E6=A0=B7?= =?UTF-8?q?=E5=90=8C=E6=AD=A5=E3=80=82=E5=87=AD=E6=8D=AE:=20=E4=B8=89?= =?UTF-8?q?=E5=A5=97=E4=BB=B6=E5=8D=95=E8=B7=91=20exit=3D0;=20--code=20?= =?UTF-8?q?=E5=9F=9F=E9=97=B8=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87(?= =?UTF-8?q?=E5=86=92=E7=83=9F/=E5=8D=95=E5=85=83=E5=85=A8=E9=87=8F/?= =?UTF-8?q?=E7=B3=BB=E7=BB=9F/=E5=9C=BA=E6=99=AF=E6=8E=A2=E9=92=88/?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E9=97=B8/=E9=9D=99=E6=80=81=E6=89=AB?= =?UTF-8?q?=E6=8F=8F/=E7=86=B5=E5=A2=9E=E9=97=B8=E5=85=A8=E7=BB=BF);=20--d?= =?UTF-8?q?ocs=20=E5=9F=9F=E9=97=B8=E9=AA=8C=E6=94=B6=E9=80=9A=E8=BF=87(?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E5=9F=9F=E4=B8=83=E9=97=B8=E9=9B=B6=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E7=BA=A2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 8 +-- .../engineering/architecture/architecture.md | 9 ++- design/engineering/code-conventions.md | 2 +- design/engineering/process-conventions.md | 4 +- design/engineering/test-conventions.md | 18 +++++- design/gdd/systems/表现与支撑/数据配置.md | 2 +- design/gdd/术语表.md | 1 + game/sim/journey_player.gd.uid | 1 + game/tests/.gates/baseline.json | 6 +- game/tests/README.md | 63 +++++++++---------- game/tests/run_all.gd | 18 +++--- .../round_flow_skill_pool_test.gd | 2 +- .../battle_settlement/trigger_cond_test.gd | 4 +- game/tests/unit/core/adventure_feats_test.gd | 6 +- .../unit/core/save_manager_domain_test.gd | 10 +-- .../cross_system_flow_test.gd | 40 ++++++------ .../cross_system_flow_test.gd.uid | 0 .../unit/data_config/rng_stream_boot_test.gd | 2 +- .../node_map_determinism_test.gd | 6 +- .../node_map_determinism_test.gd.uid | 0 .../presentation/theme_projection_test.gd | 2 +- .../save_rng_roundtrip_test.gd | 6 +- .../save_rng_roundtrip_test.gd.uid | 0 tools/ci/domain_route.sh | 2 +- tools/coverage_receipts.py | 4 +- tools/doc_term_audit.py | 5 +- tools/extract_checks.py | 15 +++-- tools/run_all_gates.sh | 30 ++++----- 28 files changed, 139 insertions(+), 127 deletions(-) create mode 100644 game/sim/journey_player.gd.uid rename game/tests/{integration => unit}/cross_system_flow_test.gd (82%) rename game/tests/{integration => unit}/cross_system_flow_test.gd.uid (100%) rename game/tests/{integration => unit}/node_map_determinism_test.gd (96%) rename game/tests/{integration => unit}/node_map_determinism_test.gd.uid (100%) rename game/tests/{integration => unit}/save_rng_roundtrip_test.gd (96%) rename game/tests/{integration => unit}/save_rng_roundtrip_test.gd.uid (100%) diff --git a/AGENTS.md b/AGENTS.md index 36259288..6fe7ddbb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,8 +16,8 @@ ## 运行与验证(工作目录 = 含 project.godot 的切片目录) -- **合回验证 = 前置检查 + 远端 CI(前置检查 2026-11-06 用户裁)**:主区 `git merge --no-ff` 落合并提交前,pre-merge-commit 钩子(`tools/hooks/pre-merge-commit`)自动跑 `tools/run_all_gates.sh --premerge`——冒烟测试+单元与集成测试全套件(4 分片),热导入缓存约 2 分钟;红=合并提交不落(修红后 git commit 续合,或 git merge --abort 放弃),快进合并不触发钩子,`--no-verify` 可跳过且仅属用户。git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据=run 落定后的终局态,且绿须坐实跑闸步真跑过——零触发批呈报「未验」不作绿证,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 是开发期改完即测的选档入口(细则见 process-conventions §4),全量档不替代它。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 -- 单件直跑(=改完即测的最窄确认,其读数就是那一步的凭据;跨改动面的漏网由合回后远端全量档兜):冒烟测试 `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` +- **合回验证 = 前置检查 + 远端 CI(前置检查 2026-11-06 用户裁)**:主区 `git merge --no-ff` 落合并提交前,pre-merge-commit 钩子(`tools/hooks/pre-merge-commit`)自动跑 `tools/run_all_gates.sh --premerge`——冒烟测试+单元测试全套件(4 分片),热导入缓存约 2 分钟;红=合并提交不落(修红后 git commit 续合,或 git merge --abort 放弃),快进合并不触发钩子,`--no-verify` 可跳过且仅属用户。git merge 合回 master 落地后,post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式,环境自举件=`tools/ci/mac_setup.sh`),工作流(`.gitea/workflows/ci.yml`)按改动域分流(分流算法单点=`tools/ci/domain_route.sh`):游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发。远端结果**按需现查**:`tools/ci_check.sh <合并提交>` 读该提交任务的 run `status`/`conclusion`,只呈报、不落盘(2026-11-04 用户裁:合回不等待、不唤醒;判据=run 落定后的终局态,且绿须坐实跑闸步真跑过——零触发批呈报「未验」不作绿证,细则=process-conventions §1 第 3 条)。**合回落地即向用户呈报本批总结并止回合**,禁前台 sleep/轮询等 CI。CI 红态不回滚合并——红项在案、修复批合回即再推再验追绿;开工先跑现查件读 CI 状态,红未修不开新批。同一份字节禁止第二次人手跑。`tools/run_all_gates.sh` 仍是全部验证闸的**唯一入口与唯一清单,域划分与选闸算法只写在它这一个地方**,文档禁另列清单;`--code`/`--docs`/`--quick` 是开发期改完即测的选档入口(细则见 process-conventions §4),全量档不替代它。**runner 依赖**:Mac 需开机在线(关机=远端任务排队延后);换机/重装按 `tools/ci/mac_setup.sh` 自举 +- 单件直跑(=改完即测的最窄确认,其读数就是那一步的凭据;跨改动面的漏网由合回后远端全量档兜):冒烟测试 `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/`) - 熵增闸(新类别 = 同批修订 Project Layout + 白名单):`python tools/repo_structure_audit.py` @@ -30,7 +30,7 @@ - `design/content-archive/` — 退役内容留底(活文档,2026-10-20 用户裁新立:内容已退役、留底供日后重新过准入;落内文件头部写明所属线) - `design/research/` **已退役(2026-10-21 用户裁)**——`design/` 全目录只放持久化设计文档与规范,调研/提案/评估/历史验收一律落 `reports/`(熵增闸退役路径清单已收该目录,再现即判红);`design/qa/` 已退役,验证截图不进 `design/`(判据见 doc-conventions §11) - `artwork/` — 在途图像素材唯一工作区(文档归文档/在途图归一处/终稿归游戏);整目录不入 git(.gitignore 强制),收口归档 `archive/` -- `game/assets/art/` — 已验收终稿(入包被游戏加载);`game/probe/` — 场景探针的场景与宿主+探针共用面(probe_gate/render_gate 在闸+人工取证探针);`game/sim/` — 测量/诊断/模拟驱动件(autoplay 策略、MC 批量、数值验证采集端);`game/` — 游戏工程本体(代码 + tests/ 冒烟/单元/集成/系统测试面) +- `game/assets/art/` — 已验收终稿(入包被游戏加载);`game/probe/` — 场景探针的场景与宿主+探针共用面(probe_gate/render_gate 在闸+人工取证探针);`game/sim/` — 测量/诊断/模拟驱动件(autoplay 策略、MC 批量、数值验证采集端);`game/` — 游戏工程本体(代码 + tests/ 冒烟/单元/系统测试面) - `sim-data/` — 机器读数据产物;数据件不入 git(入库例外 = README+analyze.py+growth_model.py);MC 锚与读数常驻留盘,无数值调整禁重跑 - `tools/` — python 工具脚本(含 `hooks/`=版本化 git 钩子,经 `core.hooksPath` 挂载);`archive/` — 批次完结归档 zip(gitignored) - `reports/` — 过程件容器:**需求卡片**(写卡段产出,实现批的输入合同)+迭代批产出物(实现报告/GDD 反哺点清单)+设计讨论段过程稿(提案/审计/需求澄清/评估);**实现批唯一可写目录**,中间文档唯一容器(`design/research/` 已退役),不是事实源(细则见 `reports/README.md`) @@ -59,7 +59,7 @@ 2. **判断挂依据**:判断=「X 是/不是/造成了/应该」类陈述;每个判断后面必须跟依据。依据只认三种:仓库文件+行号或逐字引用(给行号就必须真读过那几行);本对话的消息号或原话;外部链接。三种都拿不出的判断必须当场标「这是推测」;说不出依据就答「不知道/没查过」,禁止编造。答复形状=结论行→依据清单→细节。 3. **简洁**:同一意思只说一遍;能用一句不说两句。硬约束、数字、前提条件不参与压缩;信息完整与简洁冲突时保信息。 4. **多角度**:介绍一个物体、一个概念、一个问题,多角度讲清楚;每角度信息点 ≤3 个,禁止违背第 3 条。 -5. **闸况呈报用闸名**:对话里报验证闸的跑批结果一律用闸名(冒烟测试/系统测试/单元与集成测试/场景探针/渲染闸等),不裸报编号。 +5. **闸况呈报用闸名**:对话里报验证闸的跑批结果一律用闸名(冒烟测试/系统测试/单元测试/场景探针/渲染闸等),不裸报编号。 ### 简洁细则 diff --git a/design/engineering/architecture/architecture.md b/design/engineering/architecture/architecture.md index 1a59f1ef..53b633a7 100644 --- a/design/engineering/architecture/architecture.md +++ b/design/engineering/architecture/architecture.md @@ -7,7 +7,7 @@ - 评审模式:solo(`review-mode.txt`,2026-09-12 用户拍板——TD/LP 门禁按 solo 跳过,用户授权架构产出自主推进、迭代期回调) - 覆盖 GDD:现行全集(根索引=`design/gdd/02-系统清单.md`,文档树构成以根索引为准) - 事实源地位:本文是架构事实的唯一来源——分层/所有权/数据流/API 边界/测试与质量域均以本文为准;历史决策沿革查 git commit log,不另设决策文档体系 -- 实现实证:`game/`(2026-09-10 起开发,**现为完整游戏开发版本,已非垂直切片**;测试层级=冒烟/单元/集成/系统四级+UI 探针与渲染 golden(2026-10-22 用户裁),闸机按改动域分跑在役)——本文档的分层/所有权/数据流均经实现验证,非纯纸面设计 +- 实现实证:`game/`(2026-09-10 起开发,**现为完整游戏开发版本,已非垂直切片**;测试层级=冒烟/单元/系统三级+UI 探针与渲染 golden(2026-10-22 用户裁立;2026-11-07 用户裁层级合并,跨系统闭环套件归单元层),闸机按改动域分跑在役)——本文档的分层/所有权/数据流均经实现验证,非纯纸面设计 - 主程序可行性:跳过 — solo 模式 --- @@ -245,7 +245,7 @@ |---|---|---|---|---| | **run_smoke**(冒烟测试运行器) | `tools/run_smoke.py` | 冒烟测试总入口与判定权:headless 以项目运行目标拉起 `res://tests/smoke_boot.tscn`(极小启动场景——autoload 在位、配置表全载、主屏实例化,零逻辑判定)+ 三闸判定 | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备、判定无洞:①退出码 = 0;②摘要行「[smoke] 摘要:N 项检查,0 项失败」在案且失败数为 0;③输出扫 `SCRIPT ERROR` / `Parse Error` 标记行。外部 wall-clock 超时在位(超时 = FAIL) | | **run_system_test**(系统测试运行器) | `tools/run_system_test.py` | 系统测试总入口:headless 跑真实玩法路径断言面(宿主=`res://tests/system_test.gd`)+ 三闸判定(口径同 run_smoke,摘要行兼容两种宿主格式) | 进程退出码(0=PASS / 1=FAIL) | 三闸齐备(同 run_smoke:退出码/摘要失败数/脚本错误扫描;wall-clock 超时在位) | -| **run_all**(单元与集成测试) | `game/tests/run_all.gd` | 64 套统一入口(61 单元+3 集成,2026-10-28 实数):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸);选跑态 `--only=<词[,词…]>`(直跑形态对套件路径子串匹配只跑命中套件,零命中=用法错 quit(2),仅供开发期最窄确认——不产闸回执、不作验收凭据,批末全量闸不经选跑分支) | 逐套件结果 + 汇总退出码(任一套件失败 quit(1)) | 套件级退出码协议在位;三硬闸齐备(入口内扫子套件输出 + 编排层扫 `unit.log`;`Kit.finish` 零断言返回专用码 2)。子进程输出须经系统命令解释器重定向落盘再读——`OS.execute` 的输出管道在本机恒空 | +| **run_all**(单元测试) | `game/tests/run_all.gd` | 65 套统一入口(65 单元,2026-11-07 实数——跨系统真实路径闭环 3 套件归并单元层):套件 manifest 逐条「套件 → 期望退出码」,致命类 expect=1 独立子进程实跑;`negative` 字段标负面套件(仅豁免错误扫描一闸);选跑态 `--only=<词[,词…]>`(直跑形态对套件路径子串匹配只跑命中套件,零命中=用法错 quit(2),仅供开发期最窄确认——不产闸回执、不作验收凭据,批末全量闸不经选跑分支) | 逐套件结果 + 汇总退出码(任一套件失败 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**(屏壳切换)/ ui_click_probe(行为走查)/ 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 扫描在位(逐段 `tools/error_scan.py`,标记与 run_smoke 同源);断言数下限不适用(判定主体 = 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 判定(见下表) | @@ -301,13 +301,12 @@ S11/S12 扫描面覆盖全域,故 sim/probe/tests 的术语与旧件面**在 ### 三层承载与判定原则 -测试分标准四级+UI 探针(2026-10-22 用户裁),各有在册承载与入闸方式(细目见 `game/tests/README.md`): +测试分标准三级+UI 探针(2026-10-22 用户裁立;2026-11-07 用户裁层级合并——跨系统真实路径闭环套件归单元层;识别三元组归 test-conventions §1.1),各有在册承载与入闸方式(细目见 `game/tests/README.md`): | 层 | 判什么 | 承载 | 入闸 | |---|---|---|---| | 冒烟测试 | 这版活着吗(零逻辑判定) | `game/tests/smoke_boot.tscn/.gd` | `run_smoke.py`(冒烟测试闸) | -| 单元测试 | 领域类的原子行为对不对(从对外方法进、钉对外可观测面——写法判据归 test-conventions §8.2) | `game/tests/unit/` | `run_all.gd`(单元与集成测试闸) | -| 集成测试 | 跨系统闭环对不对 | `game/tests/integration/` | 同上(同一入口同一协议) | +| 单元测试 | 受控裸环境断言面:领域类原子行为+跨系统真实路径闭环(从对外方法进、钉对外可观测面——写法判据归 test-conventions §8.2) | `game/tests/unit/` | `run_all.gd`(单元测试闸) | | 系统测试 | 真实玩法路径断言面 | `game/tests/system_test.gd` | `run_system_test.py`(系统测试闸) | | UI 探针+渲染 golden | 场景切换与观感 | `game/probe/*.tscn` 探针 | `probe_gate.sh`(场景探针闸)、`render_gate.sh`(渲染闸) | diff --git a/design/engineering/code-conventions.md b/design/engineering/code-conventions.md index 2efb9934..2bfbb738 100644 --- a/design/engineering/code-conventions.md +++ b/design/engineering/code-conventions.md @@ -78,7 +78,7 @@ 10. **`# rng-seed-exempt` 标记豁免通道**——S1/S2 随机函数扫描对该行豁免;限定用途=非玩法 RNG 消费面的引擎熵(现行例:组合根种子编辑框空态取 `randi()`——建种子不进玩法封闭流);非产品码三目录自建随机源的定种要求见第 13 条。 11. **`--script` 模式禁裸 autoload 标识符**——`ConfigDB`/`RngService` 等 autoload 名在 `--headless --script` 模式编译器不解析(parse error);产品码与测试夹具一律经节点路径取用(`get_node("/root/…")`/`AUTOLOAD_PATH_*` 常量);静态扫描域 S5 硬失败承载在位。 12. **Variant 参数化现行口径**——账本类字典/数组标注 `Dictionary[Variant, Variant]`/`Array[Variant]` 即合格(满足 R1);元素类型精化列后续事项、不入硬纪律,口径变更须用户裁决后续批次另行写入。 -13. **静态扫描适用域按来源分档(2026-11-03 架构审查 P1 余项裁)**——S 系静态扫描(S1–S13)的扫描面一律止于产品码三目录,`game/tests`/`game/probe`/`game/sim` 不进任何 S 系扫描面;这三目录的可靠性由运行期通道承接,各通道是:`game/tests`=冒烟·单元与集成·系统测试三闸、`game/probe`=场景探针与渲染 golden 两闸(同目录内 `type_glow_shot_probe` 是人工取证件、`probe_ui_util` 是探针共用面,两者不入闸)、`game/sim`=不入 CI,属用户手动发起的调参与数值测量批量。三目录**允许**自建 `RandomNumberGenerator`,条件=**必须显式定种**(改前改后读数要用同种子对拍,这是全项目测量口径);三目录真正禁止的形态是不定种随机——`randomize()` 与全局 `randi()`/`randf()`/`randf_range()`,一旦命中那次运行结果不可复现,读数与取证一并失效。这条对三目录的部分目前**无机器闸**(S1/S2 扫描面不含它们):扩面需给每处合法定种挂 `rng-seed-exempt` 注记、只拦得住新增,2026-11-03 用户裁不扩,改由本条文字+批末独立验收审计的人工评审承载。术语口径:`tools/run_all_gates.sh` 里的「测量面」指 `game/tests`+`game/sim`(影响面选闸用语,`game/probe` 不在其中——改探针必须跑场景探针与渲染闸),与本条分档无关;本文不再用「测量面」统称这三个目录。 +13. **静态扫描适用域按来源分档(2026-11-03 架构审查 P1 余项裁)**——S 系静态扫描(S1–S13)的扫描面一律止于产品码三目录,`game/tests`/`game/probe`/`game/sim` 不进任何 S 系扫描面;这三目录的可靠性由运行期通道承接,各通道是:`game/tests`=冒烟·单元·系统测试三闸、`game/probe`=场景探针与渲染 golden 两闸(同目录内 `type_glow_shot_probe` 是人工取证件、`probe_ui_util` 是探针共用面,两者不入闸)、`game/sim`=不入 CI,属用户手动发起的调参与数值测量批量。三目录**允许**自建 `RandomNumberGenerator`,条件=**必须显式定种**(改前改后读数要用同种子对拍,这是全项目测量口径);三目录真正禁止的形态是不定种随机——`randomize()` 与全局 `randi()`/`randf()`/`randf_range()`,一旦命中那次运行结果不可复现,读数与取证一并失效。这条对三目录的部分目前**无机器闸**(S1/S2 扫描面不含它们):扩面需给每处合法定种挂 `rng-seed-exempt` 注记、只拦得住新增,2026-11-03 用户裁不扩,改由本条文字+批末独立验收审计的人工评审承载。术语口径:`tools/run_all_gates.sh` 里的「测量面」指 `game/tests`+`game/sim`(影响面选闸用语,`game/probe` 不在其中——改探针必须跑场景探针与渲染闸),与本条分档无关;本文不再用「测量面」统称这三个目录。 --- diff --git a/design/engineering/process-conventions.md b/design/engineering/process-conventions.md index 25425c72..63e07139 100644 --- a/design/engineering/process-conventions.md +++ b/design/engineering/process-conventions.md @@ -10,9 +10,9 @@ 主工作区(master 检出)只作合并落点,一切开发/设计改动禁止直接发生在主工作区(2026-10-31 用户裁恢复;验证模型=批内按 §4 改完即测、合回后远端 CI 是唯一强制全量验证点,细则见本节第 3 条): 1. **worktree 车道(一切面)**:每回合先建独立 worktree(建区粒度=回合,会话单区细则见第 5 条):`git worktree add ../g-plan2-<任务名> -b <任务分支>`;建区后立刻跑 `tools/setup_worktree_userdir.sh`——给该区独立 Godot user 目录(测试夹具、试玩日志、截图产物不与其他区共用;主区不生成,真实档保持原位)。所有改动与提交都在 worktree 内自由进行——commit 点零钩子检查(`tools/hooks/pre-commit` 已删除)。豁免面仅 `.zcode/`(钩子与配置)与 `.qoder/`(本机 harness 配置)允许主工作区直改——两份实测均不入 git(走 worktree 反而带不进去,改动只对本机生效) -2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`(pre-merge-commit 钩子先跑前置检查——冒烟+单元与集成全套件约 2 分钟,红则停合并提交,细则见第 4 条 ③)——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §4 改完即测铁规履行——改完即测自己改过的那部分,不请示、不等点名;收口顺序本身不设跑批关口。**卡片复核前置判据(2026-11-05 用户裁)**:需求卡片的 grilling 复核先于卡片提交、卡片提交先于合回——复核未跑完、缺陷清单未裁完,禁把卡片落 commit(落了也不算成立),`master` 永不落未成立卡;本条与 §2 段三出口铁律、doc-conventions §12.1「已写卡」凭据同判。 +2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter 形)合回前执行 `python tools/reshape_cards.py`,把分支线性史按卡末态快照压成一卡一笔(可选追加一笔纯文档收尾),master 台账由此每卡一行——工具自带「重塑前后树逐字节一致」断言与 backup 分支,不改任何文件内容;非迭代批免重塑;然后主工作区 `git merge --no-ff <任务分支>`(pre-merge-commit 钩子先跑前置检查——冒烟+单元全套件约 2 分钟,红则停合并提交,细则见第 4 条 ③)——这是主区唯一合法写动作(合回守卫对迭代分支数卡校验,未归卡提交拦下)。**合回落地即 `git worktree remove`+删分支(合回即删,2026-09-19 原裁);post-merge 钩子已在合回落地时自动触发 CI,红态不回滚合并——新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §4 改完即测铁规履行——改完即测自己改过的那部分,不请示、不等点名;收口顺序本身不设跑批关口。**卡片复核前置判据(2026-11-05 用户裁)**:需求卡片的 grilling 复核先于卡片提交、卡片提交先于合回——复核未跑完、缺陷清单未裁完,禁把卡片落 commit(落了也不算成立),`master` 永不落未成立卡;本条与 §2 段三出口铁律、doc-conventions §12.1「已写卡」凭据同判。 3. **合回后验证(远端 CI,2026-11-03 用户裁)**:git merge 合回 master 落地后 post-merge 钩子(`tools/hooks/post-merge`)自动 `git push origin master` 触发远端 CI(Gitea Actions,runner=Mac 本机 host 模式),工作流按改动域分流(分流算法单点=`tools/ci/domain_route.sh`:游戏码批=两域全量含渲染闸,文档批=文档域秒级闸,纯闸件/管线批=零触发)——唯一强制全量验证点。远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读该提交任务的 run 自身 `status`/`conclusion`,只呈报、不落盘),agent 开工先读 CI 状态的纪律不变,红未修不开新批。**绿须坐实**:零触发批的 run 照样建、照样 conclusion=success,但跑闸步是 `skipped`(run 55 实证)——一条闸都没跑不构成验证,现查件对此呈报「未验」(退出码 4),禁当绿证用;job 列表为空、取不到步级读数或工作流里没有跑闸步时呈报「不可判」,同样禁当绿证。CI 红态不回滚合并——修复走新开修复 worktree(`fix-<任务名>` 分支,非 iter 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链)。导入缓存刷新(.godot 不入 git 的在册坑)由工作流「导入缓存刷新」步接管。**合回零等待纪律(2026-11-04 用户裁,取代 2026-11-02 回执等待纪律)**:合回落地即向用户呈报本批总结并止回合——不挂后台轮询、不等回执、不被唤醒;CI 结论什么时候要看就现查一次(`tools/ci_check.sh`,run 那时早已落定,秒级)。退役缘由=原轮询件把「commit status 离开 pending」当终局判据,会把 run 未落定期的尝试级失败写成终局红(run 37 实证:开跑 36 秒写红,该 run 三分半后 `conclusion=success`),且零触发批与 runner 排队必熬满超时再写红;同 commit 的多路轮询还重复覆写同一份判定。跑闸等一切分钟级跑批的等待纪律不变——后台运行,禁前台 sleep/轮询循环。 -4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层钩子=合回链两件:pre-merge-commit(合回前置检查,2026-11-06 用户裁:主区 merge 落合并提交前自动跑 `tools/run_all_gates.sh --premerge`=冒烟+单元与集成全套件 4 分片,热导入缓存约 2 分钟;exit≠0 拦合并提交不拦合并动作——红态仓库停 MERGE_HEAD 态,修红 git commit 续合或 git merge --abort 放弃;快进合并不触发;--no-verify 可跳过且仅属用户)+post-merge(远端 CI 触发,只推送,不挂等待);合回链之外 git 层不设车道钩子——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) +4. **执法分层(全机械,零自觉依赖)**:①`.zcode/hooks/worktree-guard.sh`(PreToolUse,本机件不入 git)——agent 侧:主工作区 Edit/Write 与主区非 merge `git commit` 拦截(豁免 `.zcode/`、`.qoder/`;worktree 内目标快速放行;合并收尾提交凭 MERGE_HEAD 豁免)、`git commit --no-verify` 拦截(该旗标仅属用户)、iter 分支合回的一卡一提交数卡校验、规则闸(写 design//game/ 前核对会话规约读取记录)。②post-merge 钩子——远端 CI 触发(只推送,不挂等待;仅主工作区,任务 worktree 内的合并不触发)。③git 层钩子=合回链两件:pre-merge-commit(合回前置检查,2026-11-06 用户裁:主区 merge 落合并提交前自动跑 `tools/run_all_gates.sh --premerge`=冒烟+单元全套件 4 分片,热导入缓存约 2 分钟;exit≠0 拦合并提交不拦合并动作——红态仓库停 MERGE_HEAD 态,修红 git commit 续合或 git merge --abort 放弃;快进合并不触发;--no-verify 可跳过且仅属用户)+post-merge(远端 CI 触发,只推送,不挂等待);合回链之外 git 层不设车道钩子——用户手工改动不受限;ZCode 层只约束 agent(子代理不经守卫层) 5. **会话单区(2026-11-04 用户裁)**:建区粒度=回合——同一时刻每会话至多一个 worktree 在役;合入删区之前,会话内用户追加的事项一律并入在役区推进,禁为追加事项另开新区。区内全部事项完成后只做一次合入(一次 `git merge --no-ff`、post-merge 一次推送、远端 CI 只跑一次),追加事项在合入提交信息里分列;无改动需求的事项不建区。合回删区后用户再发令的事项=新回合,另开新区(区名取该回合主任务名)。CI 红的 `fix-<任务名>` 修复区属同一回合的追绿延续,不另算新区。迭代实现批(iter 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。 ## 2. 五段流水线 diff --git a/design/engineering/test-conventions.md b/design/engineering/test-conventions.md index f8ef1ef2..63976c7a 100644 --- a/design/engineering/test-conventions.md +++ b/design/engineering/test-conventions.md @@ -13,6 +13,20 @@ 核心层验收标准通过不算有测试:GDScript 编译不查字典字段与时序,呈现层错误只在运行时暴露。 测试必须证明功能可达(用户真的能用到)且行为正确(结果符合验收标准)。 +### 1.1 测试层三元组(2026-11-07 用户裁——测试层识别权威表) + +识别一个测试层靠三元组,不靠名字——**层级名只是代称**;名称与定义的登记面=`design/gdd/术语表.md`,本表是识别判据的单点: + +| 层(代称) | 职责 | 触发时间点 | 覆盖面积 | +|---|---|---|---| +| 冒烟测试 | 真实开机路径存活检查——启动/加载表/进主屏,零逻辑判定 | 每次代码域跑批+合回前置检查+远端 CI(三件保险恒跑不降) | 最小:一条真实开机路径 | +| 单元测试 | 受控裸环境断言面:单机制契约+跨系统真实路径闭环——不加载开机装配、手工构造注入实跑;闭环套件=两以上真实系统手工装配×落盘往返,性质归属由各套件头注「跨系统面」句承载 | 同冒烟(恒跑;关联档选套件=需求卡片申报∪diff 推导并集,横切回落全量) | 领域类原子行为+跨系统接缝——不含开机装配与画面 | +| 系统测试 | 真实开机+真实玩法路径行为断言(推理→战斗→结算→推进) | 同冒烟(恒跑) | 完整装配下的玩法旅程 | +| 场景探针 | 真实实例化场景+真输入管线(合成输入事件走输入管线)的 UI 可达性/可见性/状态 | 代码域跑批(测量面批记 SKIP)+远端 CI | 逐 UI 区域交互动线 | +| 渲染闸 | 窗口化真实渲染截图 vs 冻结基线图逐像素容差比对 | 代码域跑批(测量面批与 --quick 记 SKIP)+远端 CI | 像素呈现(主题/素材/排版) | + +层级合并说明(2026-11-07 用户裁):跨系统真实路径闭环套件归单元层承载,三套件实体在 `game/tests/unit/`;「集成测试/集成用例/集成层/集成侧/集成宿主/集成族」为退役词(术语表「退役词对照」;S11 注释面+用语审计文档面双承接)。 + ## 2. 场景级探针标配 每新增一个可交互 UI 区域(视图/覆盖层/面板),必须配套场景级 headless 探针: @@ -29,7 +43,7 @@ ## 4. 验证时机(合回后远端 CI 制,2026-11-03 换代) -开发期义务=`process-conventions.md` §4 改完即测铁规:本批新增/改动的验收测试自己跑绿、读数写进该步提交信息才算完成(卡内只测自己改的东西)。合回 merge 提交落笔前另有 pre-merge-commit 前置检查(2026-11-06 用户裁:冒烟+单元与集成全套件 4 分片,约 2 分钟——当场拦截面,不落回执;细则见 process-conventions §1 第 4 条 ③)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、单元与集成测试全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 自身 `status`/`conclusion`,只呈报、不落盘——合回不等待、不唤醒,process-conventions §1 第 3 条);CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 +开发期义务=`process-conventions.md` §4 改完即测铁规:本批新增/改动的验收测试自己跑绿、读数写进该步提交信息才算完成(卡内只测自己改的东西)。合回 merge 提交落笔前另有 pre-merge-commit 前置检查(2026-11-06 用户裁:冒烟+单元全套件 4 分片,约 2 分钟——当场拦截面,不落回执;细则见 process-conventions §1 第 4 条 ③)。全量验证归合回后的远端 CI:git merge 合回 master 落地后 post-merge 钩子推送 origin master,远端按改动域分流跑 `tools/run_all_gates.sh`(游戏码批=不带参数档两域全量、单元测试全套件——跨卡触面与关联档选测的盲区由全量兜底;文档批=文档域秒级闸;分流算法单点=`tools/ci/domain_route.sh`),远端结果用 `tools/ci_check.sh <合并提交>` 按需现查(读 run 自身 `status`/`conclusion`,只呈报、不落盘——合回不等待、不唤醒,process-conventions §1 第 3 条);CI 红态下修复合回落地即再推再验追绿(修复走新开 `fix-<任务名>` 修复 worktree,process-conventions §1)。同一份字节禁止第二次人手跑。该脚本是全部验证闸的唯一入口与唯一清单,域划分与选闸算法只写在脚本里,本文禁另列闸件清单。 行为探针使用随机化玩家行为(封印/停手/刷新/无效点击)——自动玩家不做的动作就是测试盲区。 ## 5. 测试函数怎么写——验收清单条目的落码规范 @@ -81,7 +95,7 @@ - **单元**:不依赖场景树和真实宿主的纯逻辑;测试从领域类的对外方法进出,断言钉对外可观测面(返回值/信号/查询接口/落盘内容)。内部字段直查=受控例外:仅当该行为没有公开可观测面可钉时允许,对照表该行注明理由;存量不回头清扫,被批次触碰时顺带换代。探针直读视口承载子件结构(画布取视口首子件、渐隐带两片与平移提示子件按名取)属此类——断言对象即承载结构本身,无公开可观测面可钉,理由随对照表行申报。 - **接口**:模块边界的公开契约——数据表加载、存档与断点、事件载荷、命令接口;测试从公开入口进出。接口级断言随单元套件落(`unit/contracts/`),不另设承载目录。 -- **集成**:跨宿主的接线、持久、生命周期——例如 LevelRunner 把 level_counters 传给 BattleOrchestrator、两场战斗共享同一个桶、断点续玩;测试必须实例化真实宿主链,禁止用裸 EffectEngine 代替。 +- **跨宿主**:跨宿主的接线、持久、生命周期——例如 LevelRunner 把 level_counters 传给 BattleOrchestrator、两场战斗共享同一个桶、断点续玩;测试必须实例化真实宿主链,禁止用裸 EffectEngine 代替。断言载体归单元层跨系统闭环套件(§1.1 三元组表),验收入口不变。 - **探针/golden**:玩家可达性与观感;真实实例化场景、驱动真实输入。 - **golden 测试项扩项(2026-11-05 立)**:触碰画面呈现规格的需求批(新增画面要素/布局/配色/动效;判据=设计段出了预览图),实现段把该画面纳入渲染闸机器面:截图采集清单扩项+实跑截图冻结进 `game/tests/golden/`(基线属入库测试资产,render_gate.sh 在册例外)。黄金图=游戏真实渲染截屏冻结的逐像素基准,比对器与容差口径见 render_gate.sh 头注释(脚本即清单,本文不另列)。基线冻结(新建与换代同口径)=用户对照设计段预览图确认后执行,触发原因写进当批提交说明(`tools/golden_baseline_refresh.sh` 现役义务);此后渲染闸逐像素自动盯、零人工。预览图(HTML/SVG 效果图)与引擎渲染是两套管线,禁直接充当比对基线——预览图只作人审参照与验收判据句的出处(判据出处行锚指向 ux/ 内嵌预览节);缺陷修复以现行规格与在库基线为准,不触发新预览。 diff --git a/design/gdd/systems/表现与支撑/数据配置.md b/design/gdd/systems/表现与支撑/数据配置.md index e389308b..de722f5c 100644 --- a/design/gdd/systems/表现与支撑/数据配置.md +++ b/design/gdd/systems/表现与支撑/数据配置.md @@ -174,7 +174,7 @@ | 优先级 | 内容 | 后置原因 | |---|---|---| -| P1 | 战斗级可回放集成测试(同主种子同动作序列,步骤流逐行相同) | 需战斗域载体,随[战斗结算](../战斗循环/战斗结算.md)回放验收兑现 | +| P1 | 战斗级可回放闭环测试(同主种子同动作序列,步骤流逐行相同) | 需战斗域载体,随[战斗结算](../战斗循环/战斗结算.md)回放验收兑现 | | P1 | 难度曲线导出工具(无窗口导出行解析矩阵供比对) | 难度审计的离线载体,[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)期交付物 | | P2 | 正式包构建的致命表现验证与调试覆盖结构性不存在验证 | 挂首次正式导出检查清单 | | P2 | 进度键表的调试覆盖行寻址语法 | 调试覆盖现只支持单行全局表字段,行寻址登记为后续扩展 | diff --git a/design/gdd/术语表.md b/design/gdd/术语表.md index ffd8907d..f1ef39f8 100644 --- a/design/gdd/术语表.md +++ b/design/gdd/术语表.md @@ -179,6 +179,7 @@ | 罕见(档位名) | 稀有(2026-10-26 用户裁入表:旧三档档位名,现行四级第三档=稀有;散文形容词义不受限) | | 通关 | 关卡完成(单关)/冒险成功(整局)——2026-10-26 用户裁:主体明确短语族替代歧义单义词 | | 冒险终结 | 冒险落幕——2026-10-26 通关口径统一批 | +| 集成测试 / 集成用例 / 集成层 / 集成侧 / 集成宿主 / 集成族 | 单元测试(2026-11-07 用户裁层级合并——跨系统真实路径闭环套件归单元层,三套件实体迁移 tests/unit/;测试层识别靠三元组〔职责×触发时间点×覆盖面积,权威表=test-conventions「测试层三元组」节〕,层级名只是代称;闭环套件的性质归属由各套件头注承载) | ## 对外命名纪律(玩家可见命名,2026-10-11 用户令) diff --git a/game/sim/journey_player.gd.uid b/game/sim/journey_player.gd.uid new file mode 100644 index 00000000..276110b3 --- /dev/null +++ b/game/sim/journey_player.gd.uid @@ -0,0 +1 @@ +uid://c40h84iqkhwep diff --git a/game/tests/.gates/baseline.json b/game/tests/.gates/baseline.json index 1b5747bc..e37eeeaa 100644 --- a/game/tests/.gates/baseline.json +++ b/game/tests/.gates/baseline.json @@ -57,13 +57,13 @@ "exit": "0", "fp": "green" }, - "单元与集成测试": { + "闸壳自检": { "exit": "0", "fp": "green" }, - "闸壳自检": { + "单元测试": { "exit": "0", "fp": "green" } } -} +} \ No newline at end of file diff --git a/game/tests/README.md b/game/tests/README.md index 6b79c95a..bfb6ed59 100644 --- a/game/tests/README.md +++ b/game/tests/README.md @@ -1,7 +1,7 @@ # Test Manual — Dungeon Sweeper(测试工具手册) **Engine**: Godot 4.7.2-stable(GDScript 单语言) -**Test Framework**: 标准四级测试层(冒烟/单元/集成/系统)+ headless 场景探针与渲染 golden;gdUnit4 **deferred**(D2 OQ1 关闭形态——本仓库不引第三方测试框架,需求见下「门禁」节) +**Test Framework**: 标准测试层(冒烟/单元/系统)+ headless 场景探针与渲染 golden——层识别三元组(职责×触发时间点×覆盖面积)的权威表=test-conventions「测试层三元组」节,层级名只是代称;gdUnit4 **deferred**(D2 OQ1 关闭形态——本仓库不引第三方测试框架,需求见下「门禁」节) **门禁**: 本地闸——`tools/run_all_gates.sh`(收口唯一验收入口,按改动域选档 `--code`/`--docs`,跨两域或改闸件本身不带参数=全量;代码域 02 按关联档选套件,算法与套件族映射表只在脚本内定义)。本仓库**无远端、无 CI**(`.github/workflows/` 已删除,不建不留不参考);闸清单与域划分只在本脚本定义,文档不另列 **Setup date**: 2026-09-12 **参考实现**: `game/probe/`(场景探针族)· `game/sim/`(测量/诊断/模拟驱动件)(场景探针族——正式工程的活样板;系统测试断言面过渡宿主 `smoke_all.gd` 亦在此,迁移归断言迁移批) @@ -15,32 +15,28 @@ ``` tests/ - unit/ # 单元测试(公式/状态机/纯逻辑,隔离断言)——extends SceneTree + quit(code) - integration/ # 集成测试(跨系统闭环——与 unit/ 同一入口同一协议,见下「测试层级」节) + unit/ # 单元测试(公式/状态机/纯逻辑隔离断言+跨系统真实路径闭环,同一环境同一协议)——extends SceneTree + quit(code) smoke_boot.tscn/.gd # 冒烟测试——极小启动场景(真实开机路径存活检查) - system_test.gd # 系统测试宿主薄壳(SceneTree+构造注入;断言迁移批后承接 smoke_all.gd) + system_test.gd # 系统测试宿主薄壳(SceneTree+构造注入) smoke/ # critical-paths.md——人工 gate 清单(/smoke-check 读取) evidence/ # 截图与人工签核记录 - run_all.gd # 单元与集成测试统一入口(套件 manifest 协议,D2 AC-2b) + run_all.gd # 单元测试统一入口(套件 manifest 协议,D2 AC-2b) static_scan.sh # 静态扫描域 S1–S12 可执行子集 ``` -## 测试层级(标准四级+UI 探针) +## 测试层级(标准三级+UI 探针;识别三元组见 test-conventions「测试层三元组」节) | 层 | 判什么 | 承载 | 入闸方式 | |---|---|---|---| | **冒烟测试** | 这版活着吗——能否启动、能否加载表、能否进主屏。零逻辑判定 | `tests/smoke_boot.tscn/.gd`(以项目运行目标拉起=真实开机路径) | `run_smoke.py`(冒烟测试闸) | -| **单元测试** | 领域类的原子行为对不对(公式/状态机/纯逻辑)——从对外方法进、断言钉对外可观测面(写法判据见 test-conventions §8.2) | `tests/unit/` | `run_all.gd` manifest(单元与集成测试闸) | -| **集成测试** | 跨系统的闭环对不对 | `tests/integration/` | 同上(与单元测试同一入口、同一协议) | -| **系统测试** | 启动真实工程走真实玩法路径 | 过渡期 `game/probe/smoke_all.gd`(断言迁移批后=`tests/system_test.gd`) | `run_system_test.py`(系统测试闸) | +| **单元测试** | 受控裸环境断言面——单机制契约(公式/状态机/纯逻辑,从对外方法进、断言钉对外可观测面,写法判据见 test-conventions §8.2)+跨系统真实路径闭环(两以上真实系统手工装配×落盘/落库往返) | `tests/unit/` | `run_all.gd` manifest(单元测试闸) | +| **系统测试** | 启动真实工程走真实玩法路径 | `tests/system_test.gd` | `run_system_test.py`(系统测试闸) | | **UI 探针+渲染 golden** | 跑起来之后 UI 与场景切换对不对;画面基线 | `game/probe/*.tscn` 探针 | `probe_gate.sh`(场景探针闸)、`render_gate.sh`(渲染闸) | -> **integration/ 已建承载**。 -> 首个集成用例 = `save_rng_roundtrip_test.gd`(存档⇄RNG 快照往返)。章程登记的四类集成用例里, -> Core-Core 与 A1×A2 端到端仍随属主批增量登记;各 GDD 登记的 deferral 集成用例以对侧登记为生效条件。 -> -> **分层判据**:能不能在 `--script` 模式下不加载 autoload 就验完?能 = 单元测试; -> 需要两个以上真实系统协同并走落盘/落库 = 集成测试;需要完整项目引导与场景树 = 冒烟/系统/UI 探针。 +> **分层判据(2026-11-07 用户裁层级合并)**:能不能在 `--script` 模式下不加载开机装配(autoload)就验完? +> 能 = 单元测试——单机制契约(一个领域类+注入依赖)与跨系统真实路径闭环(两以上真实系统手工装配×落盘/落库 +> 往返,如存档⇄RNG 快照往返、旧档迁移续玩、同种子重开图)都在这层;需要完整项目引导与场景树 = 冒烟/系统/UI 探针。 +> 闭环套件的性质归属(跨系统面=哪几件协同)由各套件头注「跨系统面」句承载,不再靠层级名区分。 **能力清单**(能力义务清单:一行=一个玩家或系统可观测的能力,只宣布「要验证什么」,不记「是否已验证/是否跑过」;定位=实现指导+阅读导航+义务声明,代表用例只给指针、不复制断言细节。七段=核心四域+排雷循环+表现与支撑+数据配置与跨系统契约,行数随清单生长不在此记账;系统文档逐篇对照见下方「覆盖域表」): @@ -50,7 +46,7 @@ tests/ > > **维护规则(写卡段维护,实现批禁写本表)**:写需求卡片时当场入表——本批新增能力当场增行(真实入口取自设计文档已定的入口;代表用例=卡内定死的计划测试 `文件:函数`,实现照单落名,名实不符=卡片没写清=停卡退回);受影响存量行(入口/用例名变更)同批更新;能力退役删行。批末收尾只核对表上名字与实际落地一致(改名则回表),不刷任何批次字段(test-conventions §8.3/§8.9)。 > -> **旅程补录方法**:新旅程=加一行,「系统」列写涉及边界;源=architecture「API 边界」表+「数据流」存档/读档路径+各 GDD 登记的 deferral 集成用例。 +> **旅程补录方法**:新旅程=加一行,「系统」列写涉及边界;源=architecture「API 边界」表+「数据流」存档/读档路径+各 GDD 登记的 deferral 跨系统用例。 **旅程段一:开机与存档** @@ -60,11 +56,11 @@ tests/ | 三槽位隔离与激活失败回滚 | 存档序列化 | `SaveManagerCore.activate_slot()`/`create_slot()`/`delete_slot()` | `save_slot_isolation_test.gd:_test_three_slot_roundtrip_and_isolation`;`save_slot_isolation_test.gd:_test_activate_failure_restores_previous_slot` | | 槽位摘要窥视与坏槽三态 | 存档序列化 | `SaveManagerCore.peek_slot_summary()` | `save_slot_summary_test.gd:_test_peek_three_slots_distinct_values`;`save_slot_summary_test.gd:_test_peek_empty_and_unreadable_states`;`save_slot_summary_test.gd:_test_peek_zero_side_effect` | | 启动态无激活不写盘 | 存档序列化 | `SaveManagerCore.has_write_target()`/`write_to_disk()` | `save_boot_no_activate_test.gd:_test_write_rejected_without_target`;`save_boot_no_activate_test.gd:_test_out_of_range_slots_rejected` | -| 旧档迁移链与断点续玩零跳号(集成) | 存档序列化×冒险结构 | `SaveManagerCore.load_from_disk()`(内置迁移链)× `AdventureRunner.resume_adventure()` | `save_rng_roundtrip_test.gd:_test_old_version_save_migrates_and_resumes` | -| 存档⇄RNG 快照往返保真(集成) | 存档序列化×RNG 服务×冒险结构 | `AdventureRunner.start_adventure()` → `SaveManagerCore.write_to_disk()`/`load_from_disk()` | `save_rng_roundtrip_test.gd:_test_disk_roundtrip_preserves_breakpoint` | +| 旧档迁移链与断点续玩零跳号 | 存档序列化×冒险结构 | `SaveManagerCore.load_from_disk()`(内置迁移链)× `AdventureRunner.resume_adventure()` | `save_rng_roundtrip_test.gd:_test_old_version_save_migrates_and_resumes` | +| 存档⇄RNG 快照往返保真 | 存档序列化×RNG 服务×冒险结构 | `AdventureRunner.start_adventure()` → `SaveManagerCore.write_to_disk()`/`load_from_disk()` | `save_rng_roundtrip_test.gd:_test_disk_roundtrip_preserves_breakpoint` | | RNG 统一流派生与取号账本 | RNG 服务 | `RngServiceCore.derive_at()`/`open_stream()`/`counter()` | `rng_service_test.gd:_test_derive_determinism`;`rng_service_test.gd:_test_stream_draw_determinism`;`rng_stream_boot_test.gd:_smoke_initialize` | | 断点片段往返与续玩幂等 | 冒险结构×关卡节点地图 | `LevelRunner.breakpoint_fragment()`/`restore_fragment()`;`AdventureRunner.resume_adventure()` | `breakpoint_resume_test.gd:_init` | -| 跨系统附随面:事件日志落盘与断点附随件(集成) | RNG 服务×存档序列化×冒险结构 | `RngServiceCore.new_adventure()`(开事件日志会话);`SaveManagerCore.breakpoint_snapshot()`(断点附随键) | `cross_system_flow_test.gd:_rng_event_log_session`;`cross_system_flow_test.gd:_feats_breakpoint_roundtrip` | +| 跨系统附随面:事件日志落盘与断点附随件 | RNG 服务×存档序列化×冒险结构 | `RngServiceCore.new_adventure()`(开事件日志会话);`SaveManagerCore.breakpoint_snapshot()`(断点附随键) | `cross_system_flow_test.gd:_rng_event_log_session`;`cross_system_flow_test.gd:_feats_breakpoint_roundtrip` | **旅程段二:冒险开局与选路** @@ -75,7 +71,7 @@ tests/ | 节点图生成合法性与同种子复现 | 关卡节点图生成 | `NodeMapGenerator.generate()`/`validate_output()` | `node_map_generator_test.gd:_test_output_ok`;`node_map_generator_test.gd:_test_reproducible`;`node_map_generator_test.gd:_test_exit_structure` | | 节点图生长递增与无解 fail-fast | 关卡节点图生成 | `NodeMapGenerator.generate()`/`is_fatal()` | `node_map_growth_test.gd:_test_growth`;`node_map_growth_test.gd:_test_fatal_subprocess` | | 节点图参数读表与域单点(三参数驱动产物/生成器侧可行性保留) | 关卡节点图生成 | `NodeMapGenerator.generate()`/`LevelGenParams.validate()` | `node_map_generator_test.gd:_test_gen_params_table_driven`;`table_structure_guard_test.gd:_test_level_gen_new_fields`;`level_gen_params_test.gd:_init`;`encapsulation_p2_sweep_test.gd:_init` | -| 起关/选路/断点重生成同图(集成) | 关卡节点地图×关卡节点图生成×冒险结构 | `LevelRunner.start_level()`/`start_level_external()`/`pick_next_node()`;`AdventureRunner.resume_adventure()` | `node_map_determinism_test.gd:_smoke_generated_map_determinism` | +| 起关/选路/断点重生成同图 | 关卡节点地图×关卡节点图生成×冒险结构 | `LevelRunner.start_level()`/`start_level_external()`/`pick_next_node()`;`AdventureRunner.resume_adventure()` | `node_map_determinism_test.gd:_smoke_generated_map_determinism` | | 信息迷雾类型可见性 | 关卡节点地图 | `LevelRunner.type_visible_ids()` | `level_map_type_vis_test.gd:_test_layer_distance`;`level_map_type_vis_test.gd:_test_boss_exempt`;`level_map_type_vis_test.gd:_test_resume_rebuild`;`level_map_info_fog_test.gd:_initialize` | | 关卡表消费侧校验与关卡推进 | 关卡节点地图×数据配置 | `LevelTable.validate_graph()`;`LevelRunner.start_level()` | `level_table_validate_test.gd:_test_positive`;`level_table_validate_test.gd:_test_negative`;`level_shop_economy_test.gd:_smoke_level` | @@ -200,9 +196,9 @@ tests/ | 域 | 系统文档 | 能力行 | 状态 | 缺口 | |---|---|---|---|---| -| 冒险循环 | 冒险结构.md | 冒险生命周期:开局受理/三关序列/败北线;断点片段往返与续玩幂等;起关/选路/断点重生成同图(集成) | 已覆盖 | — | +| 冒险循环 | 冒险结构.md | 冒险生命周期:开局受理/三关序列/败北线;断点片段往返与续玩幂等;起关/选路/断点重生成同图 | 已覆盖 | — | | 冒险循环 | 关卡节点图生成.md | 节点图生成合法性与同种子复现;节点图生长递增与无解 fail-fast | 已覆盖 | — | -| 冒险循环 | 关卡节点地图.md | 起关/选路/断点重生成同图(集成);信息迷雾类型可见性;关卡表消费侧校验与关卡推进 | 已覆盖 | — | +| 冒险循环 | 关卡节点地图.md | 起关/选路/断点重生成同图;信息迷雾类型可见性;关卡表消费侧校验与关卡推进 | 已覆盖 | — | | 冒险循环 | 奇物.md | 专长(奇物)授予与凭证保底;奖励三选一候选/刷新付费/呈现 | 已覆盖 | — | | 冒险循环 | 难度曲线与Boss.md | Boss 半血解锁技门控;行集寻址、行序保序与难度乘数 clamp;难度修饰套乘:空表零行为/套乘生效/越域 clamp;Boss 出场门控(每区域恰一 Boss 终点/Boss 节点开战必选 Boss 行集) | 部分覆盖 | 机制面剩余缺口由「断言补齐批」承接;数值落带断言归 M4 数值批;见待办清单「能力清单覆盖缺口补齐」条 | | 战斗循环 | 地图战斗资源.md | 清图奖励双份与零路径;GP 钱包/escrow/商店收口/共享池;封印与极性路由/不可逆 | 已覆盖 | — | @@ -215,7 +211,7 @@ tests/ | 排雷循环 | 棋盘生成.md | 棋盘按参数行出图、数字恒真;同参数同种子逐位复现、异种子不退化;可推理率与清图可达(合法性先于质量分)、重试有界、降级诚实 | 已覆盖 | — | | 排雷循环 | 行动经济.md | AP 扣费、末点耗尽即收轮与战斗收束清零;停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励);下轮行动点修正排队与兑现 | 已覆盖 | — | | 表现与支撑 | HUD框架.md | 拒绝反馈分面可辨与未知兜底文案;结算明细带逐行呈现与排播节奏;模态覆盖层编排与输入锁;未开格类型微光(8 邻作用集/五组色相/零玩法写入);UI 全局主题与按钮五态(含对比度锚);中文字体随包与字形覆盖;地形瓦片套与格面数字可读;战斗叙述与终局文案(数值带入/胜负互异);玩家面文案与危险格格面字形;战斗屏三区几何与棋盘格边长(分辨率两档×四档板型);B 区舞台背景三关变体与连续地面层(按 board_type 选图/地面层高度读布局表/四柱光柱退役);模态句柄分域寻址面 | 已覆盖 | — | -| 表现与支撑 | 存档序列化.md | 存档写盘⇄读回往返保真;三槽位隔离与激活失败回滚;槽位摘要窥视与坏槽三态;启动态无激活不写盘;旧档迁移链与断点续玩零跳号(集成);存档⇄RNG 快照往返保真(集成) | 已覆盖 | — | +| 表现与支撑 | 存档序列化.md | 存档写盘⇄读回往返保真;三槽位隔离与激活失败回滚;槽位摘要窥视与坏槽三态;启动态无激活不写盘;旧档迁移链与断点续玩零跳号;存档⇄RNG 快照往返保真 | 已覆盖 | — | | 表现与支撑 | 教学引导.md | — | 未覆盖 | 教学形态与首遇时机待设计;见待办清单「教学形态未定」条 | | 表现与支撑 | 数据配置.md | 全表装载、就绪态与表清单目录对账;进度坐标查行:精确命中/向上取整/封顶/警告去重;运行期越域致命与进程非 0 退出;加载期违规表拒绝:跨表悬空/计数器前缀/真视窄域;表结构守卫:域校验/行序键/跨字段互锁/配额锚 | 已覆盖 | — | | 表现与支撑 | 设置与本地化.md | 减动效开关存储契约(持久化回读/缺省值/引擎动效档应用) | 已覆盖 | 存储契约三断言已落地;多语言切换面未建(P1,归设置与本地化待设计问题清单) | @@ -241,7 +237,7 @@ tests/ |---|---|---|---| | `tools/run_smoke.py`(冒烟测试) | 这版能否启动、能否加载表、能否进主屏——零逻辑判定 | 退出码+脚本错误扫描 | 开机路径/表加载改动后的快速自检;收口由闸编排 | | `tools/run_system_test.py`(系统测试) | 真实玩法路径的行为断言面(推理→战斗→结算→进度推进) | 退出码+脚本错误扫描 | 玩法链路改动的调试期确认 | -| `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,是验收跑批本体) | +| `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`(场景探针,单一形态十件恒跑) | UI 可达性、可见性、状态——真实实例化场景+真实输入路径 | 探针自检+退出码 | 新增 UI 区域的配套探针直跑与断言复核 | | `tools/render_gate.sh`(渲染 golden) | 画面基线——截图与基线图逐像素容差比对 | golden 比对脚本(依赖 PIL)+退出码/取证双检/三段探针日志脚本错误扫描(`error_scan.py`) | 主题/素材/排版类改动;窗口化运行,需开显示器 | | `game/tests/static_scan.sh`(S 域) | 项目纪律+第三方质量规则:RNG 单点/core 纯度/类型标注/文件头/注释禁词/旧符号残留;S13=gdstyle 质量规则(重复 load、循环内 await、循环内分配、不可达代码等真缺陷类;基线键=文件|规则|函数名(类/文件级=文件|规则,无数字条目带消息),数字漂移不假红,同键指标恶化超基线登记值即红) | 退出码(逐规则 FAIL 行) | 改产品码前后的快速自检;新增纪律先补扫描规则再修码 | @@ -255,8 +251,8 @@ tests/ # 冒烟测试——极小启动场景,秒级 python tools/run_smoke.py -# 单元与集成测试——Windows 须用 console 版 exe 取 stdout;工作目录 = game/(含 project.godot 那一层) -godot --headless --script tests/run_all.gd # 统一入口(本地单元与集成测试闸调用点) +# 单元测试——Windows 须用 console 版 exe 取 stdout;工作目录 = game/(含 project.godot 那一层) +godot --headless --script tests/run_all.gd # 统一入口(本地单元测试闸调用点) godot --headless --script tests/run_all.gd -- --only=battle_settlement # 选跑态——只跑路径含该词的套件(开发期最窄确认;零命中=用法错 exit 2,不产闸回执) godot --headless --script tests/unit/example_test.gd @@ -273,8 +269,8 @@ godot --headless --path . res://probe/.tscn # 不连续的超时照旧逐个回收;阈值是 run_all.gd 内可调常量 ``` -> 套件 manifest 现况:`_suites()` 登记 **65 条**(62 单元+3 集成;口径=manifest 条目数, -> 随套件增减浮动)——data_config(ConfigDB 加载/进度坐标 ceil 解析/fatal 退出码/统一随机流)、core(秘宝结构冻结/断点续玩/存档槽位/结算五段等)、battle_settlement(DamageMath 纯函数/DamageController 伤害单据五配方)、contracts(系统间接口契约断言)、presentation(节奏队列/奖励浮层/字体资产/地形瓦片集/模态编排等);集成 3 条存 `tests/integration/`。致命类独立进程 `expect=1`;负面套件 `negative=true` 只豁免错误扫描一闸。期望值一律由表读值推导(零玩法数值字面量)、随机只经 RngService 统一流。 +> 套件 manifest 现况:`_suites()` 登记 **65 条**(口径=manifest 条目数, +> 随套件增减浮动)——data_config(ConfigDB 加载/进度坐标 ceil 解析/fatal 退出码/统一随机流)、core(秘宝结构冻结/断点续玩/存档槽位/结算五段等)、battle_settlement(DamageMath 纯函数/DamageController 伤害单据五配方)、contracts(系统间接口契约断言)、presentation(节奏队列/奖励浮层/字体资产/地形瓦片集/模态编排等);跨系统真实路径闭环 3 条同住 `tests/unit/`(与单机制契约套件同环境同协议,见「测试层级」节分层判据)。致命类独立进程 `expect=1`;负面套件 `negative=true` 只豁免错误扫描一闸。期望值一律由表读值推导(零玩法数值字面量)、随机只经 RngService 统一流。 **协议要点**(D2 冒烟协议): - `--script` 载体 `extends SceneTree` 以获得 `quit(code)`;成功 `quit(0)`、失败 `quit(1)` @@ -295,7 +291,7 @@ godot --headless --path . res://probe/.tscn | Story Type | Required Evidence | Location | |---|---|---| | Logic | Automated unit test — must pass | `tests/unit/[system]/` | -| Integration | Integration test OR playtest doc | 落地批当场建 `tests/integration/[system]/`(章程见上) | +| Integration | 跨系统闭环套件 OR playtest doc | 落地批当场入 `tests/unit/`(跨系统真实路径闭环套件,章程见「测试层级」节) | | Visual/Feel | Screenshot + lead sign-off | `tests/evidence/` | | UI | Manual walkthrough OR interaction test(场景探针为首选) | `tests/evidence/` | | Config/Data | Smoke test pass | 跑批回执 `game/tests/.gates/receipt.json`(不入库)+结论随批记 `reports/` | @@ -363,20 +359,19 @@ S12 效果引擎无状态化旧件零残留:扫描 game/ 全域 `.gd` 与 `gam |---|---|---|---| | 00 冒烟 | O(1)——引擎启动一次+存活检查 | ~10–20 秒 | | | 01 系统测试 | O(屏壳装配数 × 断言面规模) | ~2–3 分钟 | 断言计数随面浮动(口径见「断言计数双口径」节) | -| 02 单元与集成 | O(套件 manifest 条目数 × 单套件冷启动) | ~3–6 分钟 | 每套件独立子进程;单套件挂死守卫 120 秒 | +| 02 单元 | O(套件 manifest 条目数 × 单套件冷启动) | ~3–6 分钟 | 每套件独立子进程;单套件挂死守卫 120 秒 | | 03 场景探针 | O(探针段数 × 引擎冷启动) | ~3–5 分钟(--full/--click 各计一次) | 耗时大头=每段一次引擎冷启动 | | 05 渲染 | O(截图段数 × 窗口化冷启动 + golden 比对) | ~8–15 分钟 | 五段串行,需显示器在位 | | 远端 CI 全量 | 上列各闸串联(两域全量) | ~30–35 分钟 | 2026-11-04 远端 run 实测 ≈33 分钟(单点样本) | ## 术语(本项目内的唯一语义) -标准四级测试层(2026-10-22 用户裁,权威对照=design/gdd/术语表.md「退役词对照」): +标准测试层(2026-10-22 用户裁立;2026-11-07 用户裁层级合并——跨系统真实路径闭环套件归单元层,识别三元组见 test-conventions「测试层三元组」节;权威对照=design/gdd/术语表.md「退役词对照」): | 词 | 语义 | 载体 | |---|---|---| | **冒烟测试** | 秒级存活检查——能否启动、能否加载表、能否进主屏。判断「这版还活着吗」,不判断「这版对不对」 | `tests/smoke_boot.tscn/.gd`、`tools/run_smoke.py` | -| **单元测试** | 隔离的公式/状态机/纯逻辑断言面,逐套件独立进程实跑 | `game/tests/unit/`、`run_all.gd` | -| **集成测试** | 跨系统闭环断言面(存档⇄RNG 往返、回放确定性等) | `game/tests/integration/`、`run_all.gd` | -| **系统测试** | 启动真实工程走真实玩法路径的断言面 | 过渡期 `game/probe/smoke_all.gd`+`tools/run_system_test.py`;迁移后 `tests/system_test.gd` | +| **单元测试** | 受控裸环境断言面(不加载开机装配、手工构造注入实跑,逐套件独立进程)——单机制契约与跨系统真实路径闭环(存档⇄RNG 往返、回放确定性等)都在内 | `game/tests/unit/`、`run_all.gd` | +| **系统测试** | 启动真实工程走真实玩法路径的断言面 | `tests/system_test.gd`+`tools/run_system_test.py` | | **场景探针** | 以探针场景为运行目标、走完整项目引导(autoload 在位)的 UI/场景断言 | `game/probe/*.tscn`、`probe_gate.sh` | | **渲染 golden** | 窗口化截图与基线图的逐像素容差比对 | `render_gate.sh`、`tests/golden/` | diff --git a/game/tests/run_all.gd b/game/tests/run_all.gd index 5c71ad25..4fb4788e 100644 --- a/game/tests/run_all.gd +++ b/game/tests/run_all.gd @@ -179,7 +179,7 @@ func _suites() -> Array: {"path": "res://tests/unit/core/run_time_fatal_test.gd", "expect": 1, "negative": true}, # 卡② wr5/wr6/wr7 运行期违约通道(fatal 封印传导/未知标签/未播种) {"path": "res://tests/unit/data_config/rng_service_test.gd", "expect": 0}, {"path": "res://tests/unit/data_config/table_structure_guard_test.gd", "expect": 0}, # 表结构守卫宿主(adventure 配额与同调目录/enemy 带键行序/AC 单轨与掷点参数/variation 门控互锁/difficulty 修饰/reward 九型/meta 行族) - {"path": "res://tests/unit/data_config/rng_stream_boot_test.gd", "expect": 0}, # 启动初始化与统一 RNG 流宿主(data/tables 加载链/统一流黄金向量 m=42/derive_at 恒等/open_stream 取号序/new_adventure 计数器重置——批 B 自 smoke_all 迁入;源 :70 事件日志落盘条按裁决一转办集成宿主) + {"path": "res://tests/unit/data_config/rng_stream_boot_test.gd", "expect": 0}, # 启动初始化与统一 RNG 流宿主(data/tables 加载链/统一流黄金向量 m=42/derive_at 恒等/open_stream 取号序/new_adventure 计数器重置——批 B 自 smoke_all 迁入;源 :70 事件日志落盘条按裁决一转办跨系统闭环宿主) {"path": "res://tests/unit/core/treasure_items_test.gd", "expect": 0}, # 秘宝结构冻结断言面 {"path": "res://tests/unit/core/node_map_generator_test.gd", "expect": 0}, # 节点图生成器产出合格/可复现/岔路取舍/恰一店/归属锁定/战斗保底/出口结构/RNG 纯流面(卡① V1·V3·V5–V7·V9·V10) {"path": "res://tests/unit/core/node_map_growth_test.gd", "expect": 0}, # 生成图生长递增+参数无解 fail-fast 子进程通道(卡① V8/V4) @@ -211,7 +211,7 @@ func _suites() -> Array: {"path": "res://tests/unit/battle_settlement/settlement_wiring_snapshot_test.gd", "expect": 0}, # 结算接线同种子逐项 golden 对拍主证〔封装5.1〕(架构审查 5.1 封装修复——段类束注入改造零行为变更基线) {"path": "res://tests/unit/battle_settlement/seal_polarity_test.gd", "expect": 0}, # 封印与极性宿主(末点 AP 封印/封印不可逆四情况/清图奖励双份与弱形式/刷新 provenance 守卫/极性路由与封印溢价/负向字典/格面资源资格——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/round_flow_skill_pool_test.gd", "expect": 0}, # 轮内流与技能池宿主(全战斗回放/轮内刷新与 V5 快照单源/遭遇区间骰/结算五段全序/技能池数据面·回放独立·EV 锚·死亡打断·校验拒绝/妨害附加/四桶计数——批 B 自 smoke_all 迁入) - {"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走关联档选跑,卡片退役后补登记由单元与集成测试全套件承接) + {"path": "res://tests/unit/battle_settlement/refresh_feats_test.gd", "expect": 0}, # 换图系专长七件宿主(迭代 46 卡A——基座产 RAP 与折价同源/成功事实恰一次/全局受伤四路径恰一次/石层缓冲/崩壁延迟/蓄力周转/坑道小憩/矿道红利 GP/七件数据与牌库/无专长基线;原经卡片申报走关联档选跑,卡片退役后补登记由单元测试全套件承接) {"path": "res://tests/unit/battle_settlement/variant_placement_test.gd", "expect": 0}, # 变异布置宿主(战内布置段门控与数量公式/全局变异兜底回落与资源面偏斜——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/battle_settlement/skill_unlock_test.gd", "expect": 0}, # Boss 半血解锁技·容器机制(门谓词/闩不可逆/双面过滤/保底不逼锁定/零扰动) {"path": "res://tests/unit/battle_settlement/extended_row_violation_test.gd", "expect": 1, "negative": true}, # 卡④ wr2-np 扩展行必载 effect_id 负路径(行协议运行期违约面首个负面套件——独立进程豁免错误扫描) @@ -235,24 +235,24 @@ func _suites() -> Array: {"path": "res://tests/unit/core/save_boot_no_activate_test.gd", "expect": 0}, # 卡① 启动态类级契约:无激活态/拒写盘面零触碰/越界拒绝(sv2/sv3) {"path": "res://tests/unit/core/save_slot_summary_test.gd", "expect": 0}, # 卡① 摘要三项按槽窥视:异值/空槽/坏槽三态/零副作用(sv7/sv8) {"path": "res://tests/unit/presentation/modal_flow_test.gd", "expect": 0}, # 模态编排纯逻辑(V6:直接 new/互斥域谓词/三例外锁/演播后排窗) - # ---- L2 集成层(跨系统用例,与 unit/ 的隔离断言面分工)---- - {"path": "res://tests/integration/save_rng_roundtrip_test.gd", "expect": 0}, # 存档⇄RNG 快照往返(SaveManager 迁移链 × 断点消费 × RNG 续跑 × 停点态确定性) - {"path": "res://tests/integration/node_map_determinism_test.gd", "expect": 0}, # 关卡节点图生成·确定性承载(生成图复现×首冒险置位落盘——批 B 自 smoke_all 迁入,裁决六整套集成不拆) - {"path": "res://tests/integration/cross_system_flow_test.gd", "expect": 0}, # 跨系统真实流×落盘(RNG 事件日志落盘/专长断点往返/存档摘要集成/试玩记录仪真实结算流——批 B 转办块宿主,裁决一/二/三/七) + # ---- 跨系统真实路径闭环(受控裸环境多系统协同×落盘往返;与单机制契约套件同闸同协议)---- + {"path": "res://tests/unit/save_rng_roundtrip_test.gd", "expect": 0}, # 存档⇄RNG 快照往返(SaveManager 迁移链 × 断点消费 × RNG 续跑 × 停点态确定性) + {"path": "res://tests/unit/node_map_determinism_test.gd", "expect": 0}, # 关卡节点图生成·确定性承载(生成图复现×首冒险置位落盘——批 B 自 smoke_all 迁入,裁决六整套不拆) + {"path": "res://tests/unit/cross_system_flow_test.gd", "expect": 0}, # 跨系统真实流×落盘(RNG 事件日志落盘/专长断点往返/存档摘要跨系统块/试玩记录仪真实结算流——批 B 转办块宿主,裁决一/二/三/七) # ---- core 四件与呈现层两件的直接单测 ---- {"path": "res://tests/unit/core/board_generator_test.gd", "expect": 0}, # 棋盘生成(尺寸合表/数字=八邻雷数/同种子逐位一致/可推理自洽) {"path": "res://tests/unit/core/board_gen_reward_test.gd", "expect": 0}, # 棋盘生成与地图战斗资源宿主(格边长公式/资源身份面/生成器三分出口与 r0 闸/伤害量值区间——批 B 自 smoke_all 迁入) - {"path": "res://tests/unit/core/adventure_feats_test.gd", "expect": 0}, # 冒险结构与专长宿主(状态机受理面/三关序列与账本封闭/败北线/feat_table 四级/专长凭证保底/V3 订阅面逐件——批 B 自 smoke_all 迁入;断点往返三条按裁决二转办集成宿主) + {"path": "res://tests/unit/core/adventure_feats_test.gd", "expect": 0}, # 冒险结构与专长宿主(状态机受理面/三关序列与账本封闭/败北线/feat_table 四级/专长凭证保底/V3 订阅面逐件——批 B 自 smoke_all 迁入;断点往返三条按裁决二转办跨系统闭环宿主) {"path": "res://tests/unit/core/level_shop_economy_test.gd", "expect": 0}, # 关卡图与 GP 商店宿主(level 图校验与推进/enemy 行集定点/钱包到账/escrow 钳制/商店三收口/共享池抽买——批 B 自 smoke_all 迁入) {"path": "res://tests/unit/core/meta_progression_forge_test.gd", "expect": 0}, # 元进度与锻造宿主(锻造两轴/树购买受理面/hero_override 注入/收束入账与工坊解锁/树六键合成——批 B 自 smoke_all 迁入) - {"path": "res://tests/unit/core/save_manager_domain_test.gd", "expect": 0}, # 存档域宿主(读写往返/迁移链/损坏兜底/域界干净/摘要 cap/断点域七字段/三槽位承载——批 B 自 smoke_all 迁入;摘要集成块按裁决三转办集成宿主) + {"path": "res://tests/unit/core/save_manager_domain_test.gd", "expect": 0}, # 存档域宿主(读写往返/迁移链/损坏兜底/域界干净/摘要 cap/断点域七字段/三槽位承载——批 B 自 smoke_all 迁入;摘要跨系统块按裁决三转办跨系统闭环宿主) {"path": "res://tests/unit/core/minesweeper_rules_test.gd", "expect": 0}, # 排雷规则(装板不变量/挖的 face 规则/揭示推进/封印/目录外拒) {"path": "res://tests/unit/core/config_db_resolver_test.gd", "expect": 0}, # 配置解析(ceil 偏难语义/行集行序/难度 clamp) {"path": "res://tests/unit/core/config_db_cross_validator_test.gd", "expect": 0}, # 跨表校验(正向零错/悬空 skill_id/side 反向) {"path": "res://tests/unit/presentation/hud_presenter_test.gd", "expect": 0}, # HUD 投影文案(逐拒绝原因/兜底不空) {"path": "res://tests/unit/presentation/battle_narrator_test.gd", "expect": 0}, # 战斗叙述(数值带入/胜负互异/收束态恒定) {"path": "res://tests/unit/presentation/type_glow_test.gd", "expect": 0}, # 未开格类型微光数据面(九型归组封闭/清单外显式失败/冻结色相只准动明度/α170·带宽cell÷8锚定/合成空间阶梯单调+模型CR≥1.20/probe API 形状) - {"path": "res://tests/unit/presentation/theme_projection_test.gd", "expect": 0}, # 呈现投影与主题宿主(微光数据面/战斗日志行协议/全局主题与按钮五态/纹理过滤白名单/对比度墨色锚/元素细节源码锚/诊断钮退役源码契约/状态面板与生涯页只读投影——批 B 自 smoke_all 迁入;试玩记录仪整套按裁决七转办集成宿主) + {"path": "res://tests/unit/presentation/theme_projection_test.gd", "expect": 0}, # 呈现投影与主题宿主(微光数据面/战斗日志行协议/全局主题与按钮五态/纹理过滤白名单/对比度墨色锚/元素细节源码锚/诊断钮退役源码契约/状态面板与生涯页只读投影——批 B 自 smoke_all 迁入;试玩记录仪整套按裁决七转办跨系统闭环宿主) {"path": "res://tests/unit/presentation/player_copy_sweep_test.gd", "expect": 0}, # 玩家面文案清扫宿主(迭代36 卡A——钉值表换代断言①–⑧⑩+源 token 扫描两处;直建断言族,禁字「雷」族与旧术语清零钉值) ] diff --git a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd index d3a7a1ef..de318317 100644 --- a/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd +++ b/game/tests/unit/battle_settlement/round_flow_skill_pool_test.gd @@ -659,7 +659,7 @@ func _smoke_skill_pool_data() -> void: if n_uncond < 1: uncond_ok = false Kit.check("每池至少一行无条件特殊技(触发条件批池构成纪律——逐池复查)〔触发器批 V7〕", uncond_ok) - # Boss 半血解锁技·编排集成断言(真实编排回路:半血 → 下轮开轮闩检查 → 解锁行恰一次) + # Boss 半血解锁技·编排回路断言(真实编排回路:半血 → 下轮开轮闩检查 → 解锁行恰一次) var st_unlock: Dictionary = {"rows": []} var sett_u = load("res://core/battle_orchestrator.gd").new(_db, _fresh_rng(4242)) sett_u.step_row.connect(func(row): st_unlock["rows"].append(row)) diff --git a/game/tests/unit/battle_settlement/trigger_cond_test.gd b/game/tests/unit/battle_settlement/trigger_cond_test.gd index 1852c966..154a8afa 100644 --- a/game/tests/unit/battle_settlement/trigger_cond_test.gd +++ b/game/tests/unit/battle_settlement/trigger_cond_test.gd @@ -1,7 +1,7 @@ # VERTICAL SLICE - NOT FOR PRODUCTION # 触发条件机制单测(技能池情境条件触发器批——V1 出域拒绝/V2 双面过滤/V3 保底不逼/V4 闩型语义/ # V6 事实面供给/V7 池构成纪律/V8 低血防御/V9 停手偷袭/V10 缺省零扰动;V5 对拍由 skill_unlock_test -# 迁移断言与 round_flow 编排集成断言承载)。 +# 迁移断言与 round_flow 编排回路断言承载)。 # 运行:godot --headless --path game --script res://tests/unit/battle_settlement/trigger_cond_test.gd(expect 0) extends SceneTree @@ -199,7 +199,7 @@ func _init() -> void: stray += 1 Kit.check_quiet(stray == 0, "V10 无 trigger 行池快照零新键(快照键策略钉死——防未来给普行塞键)〔触发器批 V10〕") - quit(Kit.finish("trigger_cond_test", "PASS — 触发条件 V1/V2/V4/V6/V7/V8/V9/V10 全绿(V5 对拍由 skill_unlock/round_flow 编排集成承载)")) + quit(Kit.finish("trigger_cond_test", "PASS — 触发条件 V1/V2/V4/V6/V7/V8/V9/V10 全绿(V5 对拍由 skill_unlock/round_flow 编排回路承载)")) func _rng(seed_val: int): diff --git a/game/tests/unit/core/adventure_feats_test.gd b/game/tests/unit/core/adventure_feats_test.gd index ee0eb005..a852361c 100644 --- a/game/tests/unit/core/adventure_feats_test.gd +++ b/game/tests/unit/core/adventure_feats_test.gd @@ -3,7 +3,7 @@ # 冒险结构 API 闸(状态机受理面——重复开始拒绝/入口停点/放弃两态) # 冒险验收断言(三关序列/大奖励三层兑付/账本封闭/败北线/恢复数学/加算区同账/共享池) # 专长重设计实装(feat_table 四级/案D 池接线/V3 订阅面逐件三列/专长凭证保底) -# 注:断点往返三条(基点 1420–1426)按主会话裁决②改判集成宿主,不在本宿主断言面内。 +# 注:断点往返三条(基点 1420–1426)按主会话裁决②改判跨系统闭环宿主,不在本宿主断言面内。 # 协议照单元侧惯例:extends SceneTree+Kit.check 主通道;--script 模式无 autoload, # ConfigDB 显式实例化构造注入(_fresh_rng 同种子隔离夹具——RngService 构造注入 runner); # 表加载失败不中止(沿被迁断言面的源语义——承 G2–G6 同款)。 @@ -579,8 +579,8 @@ func _smoke_feats() -> void: Kit.check("A6 概率面存在(chance=20% 200 展示内必出现——零触发=概率面失守)", offered) a_r.stop()._pending = {"candidates": [{"kind": "relic", "tier": "rare", "feat_id": "x3"}, {"kind": "gp", "tier": "common"}], "payload": {}} Kit.check("relic 兑付:入持有集 + 牌库摘牌(选中踢池——池库存 %d→%d)" % [deck_relic, deck_relic - 1], a_r.stop().pick_curio("relic", "rare", "x3") and (a_r.feats_held() as Array).has("x3") and a_r.deck().pool_stock("relic") == deck_relic - 1) - # 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判集成宿主—— - # 驱动代码(save_manager 装配+_on_level_finished+快照取读)随三条同去,集成批落位时重建。 + # 断点往返三条(基点 1420–1426:relics 快照/计数器键/余库逐枚还原)按主会话裁决②改判跨系统闭环宿主—— + # 驱动代码(save_manager 装配+_on_level_finished+快照取读)随三条同去,跨系统闭环套件落位时重建。 _db.crossrun.feats = live_rows # 还原现行表(共享 _db 防污染——后续冒烟段零感知) # ⑤ V3 订阅面逐件(附表 A 三列——命中面=EffectEngine dispatch 直测;账本面=counter 直读; # 落地行为=发射点施加面〔T1 施加段承载——本段按声明逐件钉命中与边界/反例〕) diff --git a/game/tests/unit/core/save_manager_domain_test.gd b/game/tests/unit/core/save_manager_domain_test.gd index 643d4c3a..c2c1a639 100644 --- a/game/tests/unit/core/save_manager_domain_test.gd +++ b/game/tests/unit/core/save_manager_domain_test.gd @@ -7,8 +7,8 @@ # ConfigDB 显式实例化构造注入(v2 断点续玩受理与收束链夹具消费 _db);表加载失败不中止 # (沿被迁断言面的源语义——诊断在此展开,红由具体断言落,承 G2–G5 同款)。 # 测试文件统一 user://smoke_save_*.json 隔离,段首清场+段尾清理——绝不碰 user://save.json(红线)。 -# 分宿主注记:源 `_smoke_save` ⑨ 摘要集成块(level_autoplay 真局驱动×落盘,基点 :6807–6831) -# 经主会话裁决三改判集成测试——不随本宿主,见迁移账本转办清单。 +# 分宿主注记:源 `_smoke_save` ⑨ 摘要跨系统块(level_autoplay 真局驱动×落盘,基点 :6807–6831) +# 经主会话裁决三改判跨系统闭环套件——不随本宿主,见迁移账本转办清单。 # 运行:godot --headless --path game --script res://tests/unit/core/save_manager_domain_test.gd extends SceneTree @@ -44,7 +44,7 @@ func _fresh_rng(seed_val: int): # ---- 存档序列化冒烟(SaveManager——读写往返/新档合法/余额拒花/锻造出域拒/高版本拒读禁写/ -# 损坏兜底/迁移链/域界干净/摘要 cap/摘要集成/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离, +# 损坏兜底/迁移链/域界干净/摘要 cap/摘要跨系统块/生涯终身计数域 v6)。测试文件统一 user://smoke_save_*.json 隔离, # 段首清场+段尾清理——绝不碰 user://save.json(红线)。 func _smoke_save() -> void: # oracle: design/gdd/systems/表现与支撑/存档序列化.md:176 (存档域读写往返/迁移链/损坏兜底/域界组) @@ -176,8 +176,8 @@ func _smoke_save() -> void: Kit.check("最新在前(头 seed 11 / 尾 seed 2)", int(arr[0].get("master_seed", -1)) == 11 and int(arr[9].get("master_seed", -1)) == 2) - # ⑨ 摘要集成块(level_autoplay 真局驱动×落盘,基点 :6807–6831)——经主会话裁决三 - # 改判集成测试,不随本宿主(迁移账本转办清单)。 + # ⑨ 摘要跨系统块(level_autoplay 真局驱动×落盘,基点 :6807–6831)——经主会话裁决三 + # 改判跨系统闭环套件,不随本宿主(迁移账本转办清单)。 # ⑪ 断点域:七字段写读往返逐位一致+附随件+值拷贝出户+清除+旧档迁移合法空态 var fbp = sm_script.new(paths[9]) diff --git a/game/tests/integration/cross_system_flow_test.gd b/game/tests/unit/cross_system_flow_test.gd similarity index 82% rename from game/tests/integration/cross_system_flow_test.gd rename to game/tests/unit/cross_system_flow_test.gd index 5fa4681b..5c0e8610 100644 --- a/game/tests/integration/cross_system_flow_test.gd +++ b/game/tests/unit/cross_system_flow_test.gd @@ -1,15 +1,15 @@ -# tests/integration/cross_system_flow_test.gd — L2 集成用例:跨系统真实流×落盘(批 B 转办块宿主) -# 断言面 = 四块 22 条(批 B 断言迁移自 smoke_all.gd,主会话裁决一/二/三/七改判集成,装配批落位; +# tests/unit/cross_system_flow_test.gd — 跨系统真实路径闭环套件:跨系统真实流×落盘(批 B 转办块宿主) +# 断言面 = 四块 22 条(批 B 断言迁移自 smoke_all.gd,主会话裁决一/二/三/七改判跨系统闭环承载,装配批落位; # 标签与 cond 逐字搬运,宿主通道换线 Kit.check——与单元侧同一对账口径): # RNG 事件日志会话落盘 1 条(源 `_initialize` :70,裁决一——RngService 会话日志×磁盘) # 专长断点往返 3 条(源 `_smoke_feats` 基点 :1420–1426,裁决二——AdventureRunner 兑付态×SaveManager 断点快照) -# 存档摘要集成 10 条(源 `_smoke_save` ⑨ 基点 :6807–6831,裁决三——level_autoplay 真局×SaveManager 入账×写盘读回) +# 存档摘要跨系统块 10 条(源 `_smoke_save` ⑨ 基点 :6807–6831,裁决三——level_autoplay 真局×SaveManager 入账×写盘读回) # 试玩记录仪 8 条(源 `_smoke_playtest_logger` 整套,裁决七——PlaytestLogger×BattleOrchestrator 真实结算流×JSONL 落盘) -# 协议照集成侧惯例(与单元同入口同协议,tests/README「测试层级」):extends SceneTree+Kit.check 主通道; +# 协议照单元侧惯例(与单元同入口同协议,tests/README「测试层级」):extends SceneTree+Kit.check 主通道; # --script 模式无 autoload,ConfigDB/RngService 显式实例化构造注入;表加载失败不中止(沿被迁断言面的 # 源语义——诊断在此展开,红由具体断言落,承 G2–G5 同款);冒烟库件迁移期按现路径 preload(账本 §五夹具前提)。 # 驱动代码随条自持、零新增断言(验证集不扩大为绝对约束——前置受理失败自然落到后续断言红)。 -# 运行:godot --headless --path game --script res://tests/integration/cross_system_flow_test.gd +# 运行:godot --headless --path game --script res://tests/unit/cross_system_flow_test.gd extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") @@ -44,7 +44,7 @@ func _fresh_rng(seed_val: int): return rng -# ---- ① RNG 事件日志会话落盘(源 `_initialize` :70,主会话裁决一改判集成—— +# ---- ① RNG 事件日志会话落盘(源 `_initialize` :70,主会话裁决一改判跨系统闭环承载—— # 驱动沿源 :60–70 取号序列自持:master 种子 42 → 前置取号 → new_adventure 开日志会话 → 会话内取号)---- func _rng_event_log_session() -> void: # oracle: design/engineering/architecture/architecture.md:90 (事件日志落盘会话组) @@ -57,7 +57,7 @@ func _rng_event_log_session() -> void: Kit.check("事件日志会话落盘(2026-09-27 统一流裁决)", FileAccess.file_exists("user://logs/rng_log_4242.jsonl")) -# ---- ② 专长断点往返(源 `_smoke_feats` 基点 :1420–1426 三条,主会话裁决二改判集成—— +# ---- ② 专长断点往返(源 `_smoke_feats` 基点 :1420–1426 三条,主会话裁决二改判跨系统闭环承载—— # 驱动沿源 :721–797 重建:5 行临时表注入+seed 72 起局+兑付 x3+save_manager 装配+ # _on_level_finished(victory)+breakpoint_snapshot 取读+clear_breakpoint; # 起局与兑付两步的受理断言留单元宿主(adventure_feats_test),此处纯驱动——前置失败自然落红)---- @@ -101,44 +101,44 @@ func _feats_breakpoint_roundtrip() -> void: sm_r.clear_breakpoint() if FileAccess.file_exists(bp_path): # 段尾清理(与段首清场对称) DirAccess.remove_absolute(bp_path) - _db.crossrun.feats = live_rows # 还原现行表(共享 _db 防污染——后续集成块零感知) + _db.crossrun.feats = live_rows # 还原现行表(共享 _db 防污染——后续跨系统块零感知) -# ---- ③ 存档摘要集成(源 `_smoke_save` ⑨ 基点 :6807–6831 十条,主会话裁决三改判集成—— +# ---- ③ 存档摘要跨系统块(源 `_smoke_save` ⑨ 基点 :6807–6831 十条,主会话裁决三改判跨系统闭环承载—— # level_autoplay 真局摘要 → apply 原语 → 域读数 → 写盘读回保持;临时档 user://smoke_save_integration.json # 段首清场+段尾清理(源 _smoke_save 隔离纪律),绝不碰 user://save.json(红线))---- func _save_summary_integration() -> void: -# oracle: design/gdd/systems/跨局循环/元进度.md:150 (摘要集成与钥石只增不减组) +# oracle: design/gdd/systems/跨局循环/元进度.md:150 (摘要跨系统块与钥石只增不减组) var sm_script = load("res://core/save_manager.gd") var path9 := "user://smoke_save_integration.json" # 源 paths[8] if FileAccess.file_exists(path9): DirAccess.remove_absolute(path9) - # ⑨ 摘要集成(冒险结构批产物驱动):run_adventure 真局摘要 → apply 原语 → 域读数 → 写盘读回保持 + # ⑨ 摘要跨系统块(冒险结构批产物驱动):run_adventure 真局摘要 → apply 原语 → 域读数 → 写盘读回保持 var st9: Dictionary = LevelAutoplay.run_adventure(_db, 42, 900, 400) var f9 = sm_script.new(path9) f9.meta_state.apply_adventure_summary(st9.get("summary", {})) var ks_run := 0 for li in range(1, int(_db.run.level.rows.size()) + 1): ks_run += int(_db.crossrun.meta.boss_keystone_row(li).get("first_kill", 0)) - Kit.check("摘要集成:冒险驱动满配冒险成功(seed 42)", String(st9.get("result", "")) == "completed") - Kit.check("集成钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" % f9.meta_state.keystone_balance(), f9.meta_state.keystone_balance() == ks_run) - Kit.check("集成关卡完成次数表全 1", f9.meta_state.career_state().clear_counts.get(str(1), 0) == 1 and f9.meta_state.career_state().clear_counts.get(str(2), 0) == 1 and f9.meta_state.career_state().clear_counts.get(str(3), 0) == 1) - Kit.check("集成三关首杀标记齐", f9.meta_state.career_state().boss_first_kill.has(str(1)) and f9.meta_state.career_state().boss_first_kill.has(str(2)) and f9.meta_state.career_state().boss_first_kill.has(str(3))) - Kit.check("集成 recent 1 条且 seed 42", f9.meta_state.career_state().recent_summaries.size() == 1 and int(f9.meta_state.career_state().recent_summaries[0].get("master_seed", -1)) == 42) + Kit.check("摘要跨系统块:冒险驱动满配冒险成功(seed 42)", String(st9.get("result", "")) == "completed") + Kit.check("跨系统钥石余额 = ΣBoss 首杀行值(%d——逐杀掉落全源)" % f9.meta_state.keystone_balance(), f9.meta_state.keystone_balance() == ks_run) + Kit.check("跨系统关卡完成次数表全 1", f9.meta_state.career_state().clear_counts.get(str(1), 0) == 1 and f9.meta_state.career_state().clear_counts.get(str(2), 0) == 1 and f9.meta_state.career_state().clear_counts.get(str(3), 0) == 1) + Kit.check("跨系统三关首杀标记齐", f9.meta_state.career_state().boss_first_kill.has(str(1)) and f9.meta_state.career_state().boss_first_kill.has(str(2)) and f9.meta_state.career_state().boss_first_kill.has(str(3))) + Kit.check("跨系统 recent 1 条且 seed 42", f9.meta_state.career_state().recent_summaries.size() == 1 and int(f9.meta_state.career_state().recent_summaries[0].get("master_seed", -1)) == 42) var st9b: Dictionary = LevelAutoplay.run_adventure(_db, 42, 900, 400) f9.meta_state.apply_adventure_summary(st9b.get("summary", {})) Kit.check("第二局后关卡完成次数全 2", f9.meta_state.career_state().clear_counts.get(str(1), 0) == 2 and f9.meta_state.career_state().clear_counts.get(str(2), 0) == 2 and f9.meta_state.career_state().clear_counts.get(str(3), 0) == 2) Kit.check("第二局后余额翻倍(2 × Σ首杀——模拟驱动恒首杀口径)", f9.meta_state.keystone_balance() == 2 * ks_run) - Kit.check("集成 recent 长度 2", f9.meta_state.career_state().recent_summaries.size() == 2) - Kit.check("集成写盘成功", f9.write_to_disk()) + Kit.check("跨系统 recent 长度 2", f9.meta_state.career_state().recent_summaries.size() == 2) + Kit.check("跨系统写盘成功", f9.write_to_disk()) var r9 = sm_script.new(path9) - Kit.check("集成写盘读回全量保持", r9.load_from_disk() and r9.meta_state.keystone_balance() == 2 * ks_run and r9.meta_state.career_state().clear_counts.get(str(3), 0) == 2 + Kit.check("跨系统写盘读回全量保持", r9.load_from_disk() and r9.meta_state.keystone_balance() == 2 * ks_run and r9.meta_state.career_state().clear_counts.get(str(3), 0) == 2 and r9.meta_state.career_state().recent_summaries.size() == 2 and r9.meta_state.career_state().boss_first_kill.has(str(3))) if FileAccess.file_exists(path9): # 段尾清理(与段首清场对称——源 _smoke_save 同款) DirAccess.remove_absolute(path9) -# ---- ④ 试玩记录仪(源 `_smoke_playtest_logger` 整套八条,主会话裁决七改判集成—— +# ---- ④ 试玩记录仪(源 `_smoke_playtest_logger` 整套八条,主会话裁决七改判跨系统闭环承载—— # PlaytestLogger 挂真实结算流:会话文件/逐结算快照/字段齐备/spawn_count 回归/诊断转储/JSONL 回读)---- func _smoke_playtest_logger() -> void: # oracle: design/engineering/code-conventions.md:40 (试玩日志器跨系统接线组) diff --git a/game/tests/integration/cross_system_flow_test.gd.uid b/game/tests/unit/cross_system_flow_test.gd.uid similarity index 100% rename from game/tests/integration/cross_system_flow_test.gd.uid rename to game/tests/unit/cross_system_flow_test.gd.uid diff --git a/game/tests/unit/data_config/rng_stream_boot_test.gd b/game/tests/unit/data_config/rng_stream_boot_test.gd index 3c9a4953..179d5591 100644 --- a/game/tests/unit/data_config/rng_stream_boot_test.gd +++ b/game/tests/unit/data_config/rng_stream_boot_test.gd @@ -20,7 +20,7 @@ func _init() -> void: # ---- 启动初始化断言面(宿主接线注记:源函数名 `_initialize` 是 MainLoop 虚拟入口, # 单元宿主改由 `_init` 显式调用,断言逐字未动;源 :70 事件日志落盘条经主会话裁决一 -# 改判集成测试——不随本宿主,见迁移账本转办清单)---- +# 改判跨系统闭环套件——不随本宿主,见迁移账本转办清单)---- func _smoke_initialize() -> void: # oracle: design/engineering/architecture/architecture.md:90 RngService autoload:open_stream 有状态门面与启动初始化(启动流组) print("=== Dungeon Sweeper 垂直切片冒烟 ===") diff --git a/game/tests/integration/node_map_determinism_test.gd b/game/tests/unit/node_map_determinism_test.gd similarity index 96% rename from game/tests/integration/node_map_determinism_test.gd rename to game/tests/unit/node_map_determinism_test.gd index d00e2215..80961da3 100644 --- a/game/tests/integration/node_map_determinism_test.gd +++ b/game/tests/unit/node_map_determinism_test.gd @@ -1,13 +1,13 @@ -# tests/integration/node_map_determinism_test.gd — L2 集成用例:关卡节点图生成·确定性承载 +# tests/unit/node_map_determinism_test.gd — 跨系统真实路径闭环套件:关卡节点图生成·确定性承载 # 断言面 = 生成图确定性套(批 B 断言迁移自 smoke_all.gd,G12 施工组; -# 主会话裁决六整套集成不拆——跨四系统且走落盘)。 +# 主会话裁决六整套不拆——跨四系统且走落盘)。 # 跨系统面 = RngService(纯派生流零计数器占用)× # LevelRunner(start_level/start_level_external 受理·生成图装配·单一图源)× # AdventureRunner(开局种子承载·断点写读后同图)× # SaveManager+MetaProgression(首次冒险布尔位收束置位落盘——真实档写盘)。 # 与单元侧同入口同协议:extends SceneTree+Kit.check 主通道;--script 模式无 autoload, # ConfigDB 显式实例化构造注入;表加载失败不中止(沿被迁断言面的源语义——诊断展开,红由具体断言落)。 -# 运行:godot --headless --path game --script res://tests/integration/node_map_determinism_test.gd +# 运行:godot --headless --path game --script res://tests/unit/node_map_determinism_test.gd extends SceneTree const Kit := preload("res://tests/unit/test_kit.gd") diff --git a/game/tests/integration/node_map_determinism_test.gd.uid b/game/tests/unit/node_map_determinism_test.gd.uid similarity index 100% rename from game/tests/integration/node_map_determinism_test.gd.uid rename to game/tests/unit/node_map_determinism_test.gd.uid diff --git a/game/tests/unit/presentation/theme_projection_test.gd b/game/tests/unit/presentation/theme_projection_test.gd index f5e2bdda..1935c026 100644 --- a/game/tests/unit/presentation/theme_projection_test.gd +++ b/game/tests/unit/presentation/theme_projection_test.gd @@ -15,7 +15,7 @@ # ConfigDB/RngService 显式实例化构造注入(统一流 master 种子 42 沿源);表加载失败不中止 # (沿被迁断言面的源语义——诊断在此展开,红由具体断言落,承 G2–G5 同款);全局 class_name # (UiKit)裸用(--script 模式全局类在册,G3 先例)。 -# 分宿主注记:源 `_smoke_playtest_logger`(挂真实结算流跨系统)经主会话裁决七改判集成测试, +# 分宿主注记:源 `_smoke_playtest_logger`(挂真实结算流跨系统)经主会话裁决七改判跨系统闭环套件, # 不随本宿主——见迁移账本转办清单;`_smoke_global_theme` 依赖 ThemeDB 项目主题在座, # 裁决四要求实跑验证(本批零测试运行,验证归装配验证轮)。 # 运行:godot --headless --path game --script res://tests/unit/presentation/theme_projection_test.gd diff --git a/game/tests/integration/save_rng_roundtrip_test.gd b/game/tests/unit/save_rng_roundtrip_test.gd similarity index 96% rename from game/tests/integration/save_rng_roundtrip_test.gd rename to game/tests/unit/save_rng_roundtrip_test.gd index d2bda306..756d4d64 100644 --- a/game/tests/integration/save_rng_roundtrip_test.gd +++ b/game/tests/unit/save_rng_roundtrip_test.gd @@ -1,10 +1,10 @@ -# tests/integration/save_rng_roundtrip_test.gd — L2 集成用例:存档 ⇄ RNG 快照往返 +# tests/unit/save_rng_roundtrip_test.gd — 跨系统真实路径闭环套件:存档 ⇄ RNG 快照往返 # 跨系统面 = SaveManager(落盘 + 版本迁移链)× AdventureRunner(断点消费)× # RngService(种子/取数号位续跑)× BoardGenerator(开板确定性)。 # 与 tests/unit/core/breakpoint_resume_test.gd 的分工:那件是单测,只验「断点写读 + 首战开板一致」; # 本件验的是**跨系统闭环**——盘上往返(含旧版本档经迁移链)之后,断点仍能被冒险层消费且 RNG 续跑 # 零跳号,是「玩家的旧存档还能不能接着打」这条真实路径。 -# 运行:godot --headless --path game --script res://tests/integration/save_rng_roundtrip_test.gd +# 运行:godot --headless --path game --script res://tests/unit/save_rng_roundtrip_test.gd extends SceneTree const SaveManagerScript := preload("res://core/save_manager.gd") @@ -65,7 +65,7 @@ func _test_disk_roundtrip_preserves_breakpoint() -> void: "往返用例:生命账本往返保真") -# ---- 用例二:旧版本档经迁移链后仍可续玩(单测不覆盖的跨系统集成面)---- +# ---- 用例二:旧版本档经迁移链后仍可续玩(单测不覆盖的跨系统闭环面)---- func _test_old_version_save_migrates_and_resumes() -> void: # oracle: design/gdd/systems/表现与支撑/存档序列化.md:179 (旧档迁移链读回续玩组) var rng := RngServiceScript.new() diff --git a/game/tests/integration/save_rng_roundtrip_test.gd.uid b/game/tests/unit/save_rng_roundtrip_test.gd.uid similarity index 100% rename from game/tests/integration/save_rng_roundtrip_test.gd.uid rename to game/tests/unit/save_rng_roundtrip_test.gd.uid diff --git a/tools/ci/domain_route.sh b/tools/ci/domain_route.sh index e2e02a89..013b32d9 100644 --- a/tools/ci/domain_route.sh +++ b/tools/ci/domain_route.sh @@ -11,7 +11,7 @@ # --gates),闸件改动不再零触发,也不在远端重复烧引擎。 # 3. diff 空 → skip。 # 判定面说明:game/tests/** 与 game/sim/** 属 game/ 行为载体(.md 除外)→ full, -# 与 run_all_gates.sh 关联档口径一致(测量面批单元与集成测试照跑全套件)。 +# 与 run_all_gates.sh 关联档口径一致(测量面批单元测试照跑全套件)。 set -u base="${1:-}" head="${2:-HEAD}" diff --git a/tools/coverage_receipts.py b/tools/coverage_receipts.py index f7265cd6..3523d0ad 100644 --- a/tools/coverage_receipts.py +++ b/tools/coverage_receipts.py @@ -8,7 +8,7 @@ tools/coverage_report.html 经