Merge branch 'master' into camp-noon-20261106
This commit is contained in:
commit
ee0b1d4081
116 files changed
+1530
-1064
No files matched your search
@@ -16,21 +16,22 @@
|
||||
|
||||
## 运行与验证(工作目录 = 含 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`
|
||||
|
||||
## Project Layout
|
||||
|
||||
- `design/gdd/` — 现行设计文档树(根索引 `02-系统清单.md`,文档增删必须同步;`gdd/ux/` 战斗画面与交互规格)
|
||||
- `design/gdd/` — 系统与概念文档树(根索引 `02-系统清单.md`,文档增删必须同步)
|
||||
- `design/ux/` — 呈现实现设计规格层(屏级布局、交互规格与预览图;与 gdd/ 同级)
|
||||
- `design/art/` — 美术文档(圣经/角色设计/两份清单/路径图/提示词库与生图操作单);只放 .md 与 `references/` 参考图,审计报告与调研归 `reports/`
|
||||
- `design/engineering/` — 工程规范(五份规约 + architecture + engine-reference)
|
||||
- `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 +60,7 @@
|
||||
2. **判断挂依据**:判断=「X 是/不是/造成了/应该」类陈述;每个判断后面必须跟依据。依据只认三种:仓库文件+行号或逐字引用(给行号就必须真读过那几行);本对话的消息号或原话;外部链接。三种都拿不出的判断必须当场标「这是推测」;说不出依据就答「不知道/没查过」,禁止编造。答复形状=结论行→依据清单→细节。
|
||||
3. **简洁**:同一意思只说一遍;能用一句不说两句。硬约束、数字、前提条件不参与压缩;信息完整与简洁冲突时保信息。
|
||||
4. **多角度**:介绍一个物体、一个概念、一个问题,多角度讲清楚;每角度信息点 ≤3 个,禁止违背第 3 条。
|
||||
5. **闸况呈报用闸名**:对话里报验证闸的跑批结果一律用闸名(冒烟测试/系统测试/单元与集成测试/场景探针/渲染闸等),不裸报编号。
|
||||
5. **闸况呈报用闸名**:对话里报验证闸的跑批结果一律用闸名(冒烟测试/系统测试/单元测试/场景探针/渲染闸等),不裸报编号。
|
||||
|
||||
### 简洁细则
|
||||
|
||||
|
||||
@@ -27,11 +27,11 @@
|
||||
|---|---|
|
||||
| 一句话视觉规则 | 明快的地牢,轻快的扫荡,仔细的推理——**明亮像素幻想,全年龄,非暗黑**,角色 2~3 头身 |
|
||||
| 原则一「预告上格」 | 格内绘制优先级 **数字(含色牌)> 小人 > 封印印记 > 变异纹带 > 装饰变体 > 底色**;任何元素禁止遮挡数字字形区;类型微光为底效泛光层,不入遮挡栈 |
|
||||
| 原则二「明亮为皮,明度分档为骨」 | 暖倾向;数字亮墨直画已开格底(亮墨 L≥0.75 / 已开格底 L 0.08–0.14,art-bible §4.1),对比 ≥4.5:1;色牌 `#F5EAD3` 为复杂格底后备(牌上深墨 L≤0.027);未开格底 L≥0.30、HSV 饱和 ≤0.55 |
|
||||
| 原则二「明亮为皮,明度分档为骨」 | 暖倾向;数字亮墨直画已开格底(对比过 WCAG AA,art-bible §4.1);色牌为复杂格底后备;未开格底亮度地板与饱和上限同 §4.1(门值定值面=互辨抽测件) |
|
||||
| 原则三「一砖一世界」 | 变体优先换色板不加新资产;危险状态必须**形状+颜色双通道**(色弱灰度可辨) |
|
||||
| 类型预读单通道(§4.6.3) | 未开格 = **类型常显微光**(五组色带 §4.2:芥末黄/黄绿/青/紫罗兰/绯红,色相+明度双码,避 300°–345° 禁入带,样张冻结);不呈现极性、不泄危险格与安全格(角嵌形状通道已不存在——微光格只留微光) |
|
||||
| 剪影哲学(§5.1) | **所有角色自带轮廓通道:亮色描边+落影底盘**,识别不依赖背景;四类原型见各条目 |
|
||||
| 语义色规则(§4.2) | 红只说危险(危险红 `#A8321F` 单档)、金只说收益(`#F0A828`)、蓝只说己方(`#2E86DE`);封印印记现行色 = 封印黑 `#000000`(色史归 art-bible §4.1);**紫赤 `#8F2F6A` 为 Boss 独占,HSV 色相 300°–345° 全场禁入带**(微光群伤组绯红为类型先验色带域,例外见 §4.2) |
|
||||
| 语义色规则(§4.2) | 红只说危险、金只说收益、蓝只说己方(各语义色定值面=ui_kit,art-bible §4.1);封印印记现行色 = 封印黑(色史归 art-bible §4.1);**紫赤为 Boss 独占,高饱和紫赤域全场禁入带**(带域定值面=互辨抽测件;微光群伤组绯红为类型先验色带域,例外见 §4.2) |
|
||||
| UI 形状语法(§7.1,§3.3 存方向指导) | 半拟物:面板=石板圆角矩形(与地砖同圆角率),按钮=可按的砖,仪式确认=圆形蜡封;数据区直角细线无纹理 |
|
||||
|
||||
**参考图**:`design/art/references/` 三款×2 张(Into the Breach / Moonlighter / Peglin,与圣经点名池一一对应;gungeon 已退场,反面条款见 art-bible §9.2;正面取/弃与反面条款已成文 art-bible §9,反面图证待素材生成后增补)。
|
||||
@@ -43,7 +43,7 @@
|
||||
### 1.1 格子样张(砖面形态定稿闸)⚙
|
||||
|
||||
- **要什么**:一张按 art-bible §1.4 参数画成的格子风格试验图。**内容清单与验收四项以 art-bible §1.4 为唯一定义处**(含类型微光呈现组),本条只登记生产视角差异:图类型色板变体组至少两套、样张含至少一处砖缘/岛缘厚度与一格装饰立牌示例(§3.2 呈现基准)。
|
||||
- **风格/规格**:色板 token 直接取 §4.1(掩尘 `#C9AF83`、裸土 `#B39A78`、数字墨三档、封印黑、危险红、奖励金、小人蓝、数字色牌)。
|
||||
- **风格/规格**:色板 token 直接取 ui_kit 常量组(语义岗清单见 art-bible §4.1:掩尘、裸土、数字墨三档、封印黑、危险红、奖励金、小人蓝、数字色牌)。
|
||||
- **验收**:art-bible §1.4 四项按序判定(数字可读性 WCAG ≥4.5:1 硬指标 → 格态三形与微光互辨灰度可辨 → 焦点预算 → 图类型换皮抽测)。**通过 = 砖面形态与色板冻结**。
|
||||
- **来源**:⚙ 脚本管线自产——样张是验收闸本身,必须按圣经参数画,无复用意义。
|
||||
- **现状**:验收标准已成文,**测试未跑**。
|
||||
@@ -53,7 +53,7 @@
|
||||
- **要什么**:按双舞台布局(战斗画面与扫雷图同屏常驻)出的整屏试验图。要锁的四件事:**角色剪影方向 / 动画方案 / 战斗空间 / 敌人层绘制优先级插入位置**;**敌种呈现形态已裁决:全站桩式(主角同站桩、状态小图标挂血条下),卡片式已弃,正式样张按站桩形态单版出样**。
|
||||
- **风格/规格**:敌人剪影按 art-bible §5.1 四类原型出(见 2.2);战斗舞台背景=明亮地窟,紫赤仅 Boss;像素尺寸按已定案两档换算(见 §0 通用规格纪律)。
|
||||
- **验收**:战斗表现「一屏样张的验收标准」(已成文)。
|
||||
- **联动**:样张未过,**不启动任何终稿资产与动画设计**(文档明文的排期硬前置);格边长的计算口径不在本清单——现行唯一出处是 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」,HUD框架只判装板可行性(固定两档分辨率、窗口不可调、格子量增加则格边长相应变小;**档位值已定案 1280×800 + 1920×1080**)。
|
||||
- **联动**:样张未过,**不启动任何终稿资产与动画设计**(文档明文的排期硬前置);格边长的计算口径不在本清单——现行唯一出处是 [battle.md](../ux/battle.md)「棋盘格尺寸公式」,HUD框架只判装板可行性(固定两档分辨率、窗口不可调、格子量增加则格边长相应变小;**档位值已定案 1280×800 + 1920×1080**)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -395,7 +395,7 @@
|
||||
- **交付形态**:**背景层 1 张(不透明全幅)+ 围墙层 1 张 + 八个物件层 = 10 张**,其中 9 张透明底、1 张不透明全幅 + 一份坐标表(贴图矩形/命中矩形/z 序)。背景层由**远景片 `bg_far`(2560×734)与地面片 `bg_floor`(2560×866)两片出图、按 y734 拼成一张**——整张一次出图时模型把远山挤到画面下半部,被围墙挡住后完全消失(实测三张里两张山体上缘落在墙顶以下)。接缝整条落在围墙背后,画面无接缝。**篝火例外**:命中矩形只取下半(火堆),上半(火焰)只作特效展示。围墙层落盘按细条裁,不做全幅(全幅约 3.9 MB 显存,细条约 0.35 MB);**实装落盘矩形 `(0, 203, 1280, 164)`、底锚 y367**(桩身 150px、门柱 164px 终稿锁定值,落带内〔围墙带 (0,203,1280,164)〕)。
|
||||
- **色彩与氛围依据**:营地的暖由晒白的地面、木色物件与烛光黄承载,安全温馨,与冒险中的地下城形成对比;远景=正午强日照——天空淡晴蓝(高明度低饱和的清蓝)、远山一整片连续的淡蓝灰山体(禁层叠轮廓带与阴影台阶)、草地与土路晒白,整体淡而退后,近景暖调为主。现行口径 = 本文件 §2.8「全作最暖」(管菜单容器与元进度,不约束背景天空)与本节「情绪与色域」条。本节不复述色值;新增环境色按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门)。
|
||||
- **交互四态(零新素材,全部程序化)**:悬停=**轮廓亮边 + 浮动**(浮动须整像素吸附);按下=位移 1px;**禁用/未解锁=降明度 + 去饱和**(§2 已缩窄至情绪域,状态表达不受限),**仅锻炉(锻造工坊)与宝箱(秘宝匣)两件会有此态**;已解锁=常态。
|
||||
- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。栏体底=`CAMP_HEADER_BAND` `#6E4526`(§7.2 五 token 对齐表在册色)× 不透明度 0.80;栏内只钥石徽章一件、右位顶格,左位不放件。呈现定值的唯一事实源=[camp](../gdd/ux/camp.md) §顶栏,本节不复述判据。验收:目检〈顶栏成一条可辨窄带,徽章落在带内〉
|
||||
- **钥石余额**:常驻画面顶部的一行小 HUD 栏(高 48px,**浮层覆盖,不挤压画面**)。栏体底=`CAMP_HEADER_BAND` `#6E4526`(§7.2 五 token 对齐表在册色)× 不透明度 0.80;栏内只钥石徽章一件、右位顶格,左位不放件。呈现定值的唯一事实源=[camp](../ux/camp.md) §顶栏,本节不复述判据。验收:目检〈顶栏成一条可辨窄带,徽章落在带内〉
|
||||
- **事实源边界**:解锁条件/价格/存档写入归元进度 GDD 与 `MetaProgression`;素材命名与路径归 `asset-path-map`;清单登记归 `art-assets-checklist` §3.5。
|
||||
- **已裁(2026-11-01 用户裁)**:① **营地环境子板色值**——不前置拍板:量化场景实际用色后按 §4.1 场景环境子板条款登记(每屏一套、≤32 色、过 §4.2 语义检查与 RGB 三通道差 ≥8/255 两道门);② **门扇与门洞的高宽比例**——门扇贴图 240×138(锁定 240×147),随门洞 y283–367 压扁重画,坐标按门洞 x520–760 对齐;③ **帐篷的落地尺寸**——251×170 落位 (982, 335),上端遮住围墙下部。
|
||||
|
||||
@@ -508,7 +508,7 @@
|
||||
### 8.1 分辨率档位与格边长
|
||||
|
||||
- **两档定案**:1280×800(基座)+ 1920×1080,窗口不可调;后续调整走修订。验收:机检〈工程分辨率配置与两档一致〉
|
||||
- **格边长取值**:按档位与板形计算——固定档位下格子量增加则格边长相应变小;公式、单位与现行算例的唯一出处 = [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」,本节不复述数值。资产侧锁两条比例:**瓦片整格铺满**(32×32 源随格边长缩放,允许非整数倍——瓦片不适用 §8.2 甲类整数倍纪律)、**棋盘小人显示占位 ≤ 0.75× 格边长**。(1920 档已裁决全局等比缩放)验收:机检〈瓦片显示尺寸随格边长、小人占位 ≤0.75×〉
|
||||
- **格边长取值**:按档位与板形计算——固定档位下格子量增加则格边长相应变小;公式、单位与现行算例的唯一出处 = [battle.md](../ux/battle.md)「棋盘格尺寸公式」,本节不复述数值。资产侧锁两条比例:**瓦片整格铺满**(32×32 源随格边长缩放,允许非整数倍——瓦片不适用 §8.2 甲类整数倍纪律)、**棋盘小人显示占位 ≤ 0.75× 格边长**。(1920 档已裁决全局等比缩放)验收:机检〈瓦片显示尺寸随格边长、小人占位 ≤0.75×〉
|
||||
|
||||
### 8.2 像素基准与画布
|
||||
|
||||
@@ -521,7 +521,7 @@
|
||||
- **1920×1080 档策略**:全局等比(stretch=canvas_items+aspect **keep**)——16:10 设计在 16:9 窗口=等比 1.35×+左右黑边各 ~96px,构图与 1280×800 基准档完全一致;甲类 1.35 非整数缩放例外注记:轻度宽窄不均系本档位接受代价(像素游戏 shipped 常态),实机目检可感不适→回乙案(1920 独立 2× 基准)重议;乙类同前款。验收:目检〈1920 档构图与 1280 档一致〉
|
||||
- **画布基准(暂定值)**:
|
||||
- **主角人设原图 = 48×72**(1× 基准,暂定;与站姿降采样可读性验证一致);
|
||||
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../gdd/ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销;
|
||||
- **棋盘小人 = 32×32 二头身派生版(暂定,允许更小)**——独立资产,自人设定稿压缩派生(character-design §5.4),不吞数字位、占位 ≤ 0.75× 格边长;板越小占比摩擦越大(13×13 / 14×14 更小,算例见 [battle.md](../ux/battle.md)「棋盘格尺寸公式」),32px 源档的去留已定终值 = **保留 32×32 源档**(沿「独立小源档」路线;N=12 板下显示占位上限 27px = 0.84 倍,正落本节乙类非整数缩放许可范围,**连豁免都不用再办**),待决标注注销;
|
||||
- **封印印记参数表(绘制件,非贴图件)**:形与画法见 §3.2。定值——**环外径 = 0.82 × 格边**(26 格 21.3px/46 格 37.7px);**环壁厚 = 0.11 × 环径**(≈2.3/4.1px);**十字臂宽 = 0.156 × 环径**(≈3.3/5.9px);**环外光圈宽 = 0.09 × 格边**(≈2.3/4.1px),向外衰减指数 1.6、峰值 alpha 0.50;圆心 = 格心。色 = 封印黑单色(光圈只改 alpha)。本件随格边连续缩放,**不适用甲类整数倍纪律**(非贴图件,见上条清单)。参数单点 = `ui_kit.gd` 六枚常量 `SEAL_RING_FRAC`/`SEAL_WALL_FRAC`/`SEAL_ARM_FRAC`/`SEAL_HALO_ALPHA`/`SEAL_HALO_GAMMA`/`SEAL_HALO_FRAC`,本节为条款侧对照值。改值须本节与 §3.2 同批修订。验收:机检〈ui_kit 六常量与本表逐值一致〉+目检〈渲染帧量环径落 82%±1px、光圈不越格边〉
|
||||
- **站桩立绘显示档 = 三档绝对高**:小型 96 / 中型 144 / 大型 188。逐件归档——小:地精、巨蜘蛛、灰矮人;中:主角、钩爪恐魔、熊地精;大:食人魔酋长、蛛化卓尔、灰矮人石君王。**源 = 原稿像素网格**(八敌 `pixel-1`、主角手修 2× 三态稿),紧裁后原样入包,**禁再降采样**;显示倍率 = 档高 ÷ 源高,NEAREST,宽度随源图比例(高定宽随不变,见上条)。定档依据 = 生物体型(体量=物种体型,非强度线性映射,见 character-design §5.2),**顺序由显示档承载、不由源高承载**。已接受项:中型三件与地精/巨蜘蛛的源高(92/94/123/90/81)低于档高,属放大显示;食人魔酋长显示宽 157 超桩盒 150。验收:机检〈standee_asset_check 逐件显示档高与归档一致、纵横倍率恒等〉+目检〈同屏体量顺序成立〉
|
||||
- **站桩立绘排版**:① 区高 B 区 ≈1/3 屏 / C 区 ≈2/3 屏,**扫雷盘原尺寸不缩、格子不变**(区高事实源 = 布局表 `battle_layout.tres`;压缩由 C 区面板空档吸收);② 站桩内部重排——立绘双脚落石台上缘(弃盒内居中)、**血条+状态/名字行落底排**(立绘→血条→状态 顺序,全场血条对齐一线;血条嵌石板位、台缘线留作地面线、落影保留接地感),状态空时该行退化为名字行;③ 大体档(显示 >150px 级:食人魔酋长/蛛化卓尔/灰矮人石君王)**允许向上溢出盒顶 N px**(N 按实测尺寸算定,**与背景/光柱无关**——现行溢出区恰为临时光柱氛围层〔§6.1 方向稿四柱,x 基准 110/900/1055/1210@1280,`battle_stage_view.gd:47-56`〕,正式舞台背景插画(三关变体)到位后该层直接退役(背景不画光柱),**溢出判定不随之变更**;横向禁止压到顶部中央轮次·阶段指示,溢出上限用真图复算 x 区间定),血条仍落底(落底排的第二个收益=头部探顶不带出血条);意图徽标悬浮头顶上方(探顶档压胸);④ 「血条悬浮头顶+立绘盒内居中」排式已弃——盒顶固定 y 与盒内居中在高盒下拉开血条-立绘空洞。验收:目检〈血条一线对齐、大体档溢出不压轮次指示〉
|
||||
|
||||
@@ -1,7 +1,28 @@
|
||||
# 美术后处理与校验工具清单
|
||||
|
||||
> **性质**:本文件是 `tools/` 下美术资产处理与校验脚本的用法清单。回答三件事:这类资产该跑哪件工具、命令怎么敲、判据与坑在哪。
|
||||
> **边界**:只管工具用法,不立美术规格。色值、画布、体量、命名与验收条款归 [art-bible](art-bible.md)(§4.1 主色板、§8 资产与技术规范)和 [art-assets-checklist](art-assets-checklist.md);命令与源码冲突时以源码现行为准,美术规格冲突时以 art-bible 为准。素材入库与代码接线归 [asset-path-map](asset-path-map.md),不在本文件。
|
||||
> **性质**:本文件是 `tools/` 下美术资产处理与校验脚本的用法清单,兼**执行件门值登记面**——美术数值定值(色值、阈值、档位、帧数、区间)的落点速查索引。回答三件事:这类资产该跑哪件工具、命令怎么敲、判据与坑在哪。
|
||||
> **边界**:只管工具用法与定值落点登记,不立美术规格。概念、规则与方向归 [art-bible](art-bible.md);命令与源码冲突时以源码现行为准,美术规格冲突时以 art-bible 为准。素材入库与代码接线归 [asset-path-map](asset-path-map.md),不在本文件。
|
||||
|
||||
## 执行件门值登记(定值落点速查索引,2026-11-07 立)
|
||||
|
||||
美术数值定值的落点速查——每行=一个定值面与其承载件;改值=改对应执行件并跑其守门断言,文档侧对照值同批核对:
|
||||
|
||||
| 定值面 | 承载件 |
|
||||
|---|---|
|
||||
| 色值 token 全板(语义色/墨族/材质色/呈现态) | `game/presentation/views/ui_kit.gd` 常量组(色板唯一来源)+ theme README 溯源表 |
|
||||
| token 存量近色互查与新增色第二道门 | `tools/palette_check.py`(素材侧)+ `palette_token_pairwise_test.gd`(常量面守门,含在册豁免账) |
|
||||
| 微光五组色值与呈现参数 | `ui_kit.gd` 微光常量组+`type_glow_test.gd` 守门断言 |
|
||||
| 未开格底/图类型色板准入门(亮度地板/饱和上限/禁入带/近色容差)与**世界底层两子带** | `tools/glow_discrimination_sheet.py` 门值常量(本件「判据或口径」节) |
|
||||
| 已开格底渲染面亮度 | `game/presentation/views/tile_palette.gd` 归一化目标常量;对比判据=`game/probe/ui_contrast_probe.gd` |
|
||||
| 文字对比门槛(WCAG AA)与整屏有效色数上限 | `tools/shot_checker.py` 常量(`ui_contrast_probe` 同源) |
|
||||
| 封印印记六参数 | `ui_kit.gd` `SEAL_*` 六常量+`seal_mark_params_test.gd` |
|
||||
| 站桩显示档三档与源档逐件 | `standee_present_values_test.gd` 钉值+`tools/standee_asset_check.py` |
|
||||
| 动画帧数档(待机/攻击/受击) | `animation_frame_spec_test.gd` 规格表(序列帧资产入包后逐件判) |
|
||||
| 字号六档与下限 | `ui_kit.gd` `FS_*` 常量组 |
|
||||
| 屏级分区几何/带高 | `game/data/tables/battle_layout.tres`(数据索引,art-bible 直指) |
|
||||
| 营地场景坐标群(落盘矩形/命中矩形/z 序/桩柱高) | `camp-scene-prompts.md` 坐标表+落盘件 |
|
||||
| 棋盘结构件 px(墨线/暗带/投影/悬崖) | `board_world.gd`/`board_view.gd` 常量+渲染闸 golden 基线 |
|
||||
| 视野迷雾半径/提灯加成/记忆态常量 | `board_view.gd` `FOG_*` 常量+`ui_kit.gd` 迷雾常量组 |
|
||||
> **分工**:[art-prompts](art-prompts.md) 管角色级提示词与五步出图流程,[camp-scene-prompts](camp-scene-prompts.md) 管营地场景分层提示词与四步流程;两卡只把「后处理」一步指到本文件。提示词怎么写出图、出完图怎么处理,是两件事。
|
||||
> **命令前提**:以下命令均在仓库根执行。素材不入 git,worktree 缺件时部分脚本回退主检出 `D:/code/g-plan2`,部分直接返回错误,见各节「已知坑与适用边界」。
|
||||
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
|
||||
| 素材 | 状态 | 需求文档 |
|
||||
|---|---|---|
|
||||
| 主角战斗小人(待机/攻击/受击三态) | ✅ 已交付:B 区站桩三态按原稿分辨率入包、显示按档缩放落位(显示档=中档 144,源=手修 2× 三态稿,art-bible §8.2),机检全绿;挂事件切换、结算回 idle 短驻自回;棋盘 1.5×/脚锚25%/椭圆影在役 | [character-design](character-design.md) + [art-prompts](art-prompts.md) §2 |
|
||||
| 八敌种形象(L2 熊地精=bugbear 复用,不单列) | ✅ 已交付:B 区站桩八敌真图服役——八件源=原稿不降采样入包、显示按档缩放落位(三档绝对高 96/144/188,art-bible §8.2),闸件全绿;character_enemy_<键>.png 按常量入位,字形回退退役为缺位兜底;素材不入 git 本地在盘 | [art-bible](art-bible.md) §5 + [art-prompts](art-prompts.md) §3 |
|
||||
| 主角战斗小人(待机/攻击/受击三态) | ✅ 已交付:B 区站桩三态按原稿分辨率入包、显示按档缩放落位(显示档=中档,源=手修 2× 三态稿,art-bible §8.2),机检全绿;挂事件切换、结算回 idle 短驻自回;棋盘 1.5×/脚锚25%/椭圆影在役 | [character-design](character-design.md) + [art-prompts](art-prompts.md) §2 |
|
||||
| 八敌种形象(L2 熊地精=bugbear 复用,不单列) | ✅ 已交付:B 区站桩八敌真图服役——八件源=原稿不降采样入包、显示按档缩放落位(三档绝对高,art-bible §8.2),闸件全绿;character_enemy_<键>.png 按常量入位,字形回退退役为缺位兜底;素材不入 git 本地在盘 | [art-bible](art-bible.md) §5 + [art-prompts](art-prompts.md) §3 |
|
||||
| 意图准备动画三档 | ◐ | [art-bible](art-bible.md) §4.6.4 |
|
||||
|
||||
## P2 图标与格面(可与样张并行)
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
- **主色域 = 物种本色**,不受圣经 §4.2 语义色禁则约束(该禁则管格面与 UI 的逻辑色,角色身体主色豁免);**两例外**:禁入带 300°–345° 全角色禁入(仅 drider/duergar_king 两 Boss 独占)、蓝族禁入敌方主色(§5.4 同款)。
|
||||
- **L2 熊地精 = bugbear 复用**(第二关三行,显示名熊地精系,非独立键):同剪影**换色板不换形**(色板替换走脚本管线——原则三"先换色板"的角色侧应用);变体主色避小人蓝族与禁入带。
|
||||
- 每敌种出 2–3 版候选(清单 §2.2);受击反馈/死亡表现/顺位标记三类呈现下限见圣经 §4.6.4 资产需求行。
|
||||
- **立绘显示档(定值单点 = 圣经 §8.2 站桩立绘显示档条,本档指路不复述)**:三档绝对高 96/144/188,逐件归档、源档与缩放规则一律见圣经 §8.2——**数值权威 = 圣经 §8.2**(定值单点纪律,圣经 §8 头注);归档经三体型约束校验(§5.2)确认;派生管线与排版规范同归圣经 §8.2。
|
||||
- **立绘显示档(定值单点 = 圣经 §8.2 站桩立绘显示档条,本档指路不复述)**:三档绝对高,逐件归档、源档与缩放规则一律见圣经 §8.2——**数值权威 = 圣经 §8.2 指向的执行件**(定值单点纪律,圣经头注);归档经三体型约束校验(§5.2)确认;派生管线与排版规范同归圣经 §8.2。
|
||||
- **钩爪恐魔「宽≥高」豁免登记(待样张终审)**:定档源 58 高窄条束状剪影(长臂圆钩),宽高比 <1,与 §5.1 普通怪「近圆墩形宽≥高」冲突——按体量=生物天性(§5.2)登记豁免:以长臂束状轮廓为识别锚,不强行压成墩形;样张(圣经 §1.4)终审复验灰度剪影可辨性。
|
||||
|
||||
## 5.6 意图准备动画三档(规格承载节)
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
## 文档状态
|
||||
|
||||
- 版本:1.36
|
||||
- 版本:1.37
|
||||
- 引擎:Godot 4.7.2-stable(GDScript 单语言;2026-09-07 锁定)
|
||||
- 评审模式: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 模式
|
||||
|
||||
---
|
||||
@@ -172,7 +172,7 @@
|
||||
| **CampSceneView**(`views/camp_scene_view.gd`,营地场景根视图,已建件) | 营地场景图唯一呈现件:十层贴图铺底(层序按 layout 表 z 序;`camp_scene_layout.tres` fail-fast 校验+七命中区几何红线——camp_scene_layout_test 承载)、七物件命中区命中即发 object_pressed(组合根路由大门/篝火/笔记本/四界面)、反参照纪律(场景根零文字按钮)、登记模态初始隐(boot 态不盖菜单) | 构造注入(贴图经 `UiKit.TEX_CAMP_*`、layout 表经 ConfigDB 表现域容器);object_pressed 信号外发;open/close 经营地域子容器寻址 | camp_screen(宿主挂载);ConfigDB;组合根(路由消费) | Control+TextureRect 树 |
|
||||
| **营地四子界面视图**(`views/camp_sub_view_base.gd`+forge_view/tree_view/treasure_view/codex_view,功能窗口公共壳+四件,已建件) | 营地五功能窗口的四个承载视图(锻造工坊/天赋树/秘宝匣/图鉴)+公共壳(窗口壳/行板/关闭钮;生涯页挂笔记本路由不经此族);模态栈单口 open/close、五窗互斥单开;presenter 承接面零回表(树购买/锻造三意图经既有写口族) | 各 view open/close+意图信号外发;关闭钮 pressed | camp_shop_presenter/MetaProgression 写口(经组合根);CampRefreshBus(刷新广播消费) | Control |
|
||||
| **camp_esc_overlay+MainEscFlow**(`views/camp_esc_overlay.gd`+`presentation/main_esc_flow.gd`,Esc 浮窗视图+四态路由件,已建件) | Esc 浮窗三钮视图(返回主菜单/设置/退出)+Esc 四态路由静态件(根态唤起/再按关窗/功能窗开着先关最上层窗〔camp.md v2.3 定稿〕/设置开态不误关;菜单语境不唤起);**main._unhandled_input=产品码首个键盘输入入口**(契约 9——事件驱动、禁 Input 轮询、不挂 ui_cancel、pressed 非 echo) | flow.open/close 路由口;三钮 pressed 意图信号(经组合根接线);入口方法公开化(main 转发调用的入口去下划线——on_esc_input/is_camp_context 先前已公开) | 组合根(_unhandled_input 前置与意图消费);模态栈(关窗单口);screen_flow(设置/返回动线) | Control+静态件 |
|
||||
| **CampHudBand**(`views/camp_hud_band.gd`,营地顶栏 HUD 件,已建件) | 顶栏 48px 全宽窄条:钥石余额 badge 唯一呈现(单口取现值、显式刷新才变);set_hud_band 预留位落座 | badge 文本投影;refresh 时取现值 | CampRefreshBus(挂册广播);MetaProgression 钥石读 | Control |
|
||||
| **CampHudBand**(`views/camp_hud_band.gd`,营地顶栏 HUD 件,已建件) | 顶栏 48px 全宽窄条两呈现物:栏体色面(UiKit 出口 camp_hud_band_surface 落装——CAMP_HUD_BAND×α0.80 全宽铺满、不吃点击)+钥石徽章(右位顶格:徽章右缘=栏右缘−12);badge 文本单口取现值、显式刷新才变;set_hud_band 预留位落座 | badge 文本投影;refresh 时取现值 | CampRefreshBus(挂册广播);MetaProgression 钥石读;UiKit(栏体色面出口 camp_hud_band_surface) | Control |
|
||||
| **CampRefreshBus**(`views/camp_refresh_bus.gd`,营地刷新单口件,已建件) | 营地刷新单口唯一承载:进档尾与营地写口尾触发 refresh 广播(八写口尾改路由),现开界面与 HUD 同步取现值 | 挂册/refresh 广播 | 组合根(触发点接线);四子界面与 CampHudBand(消费) | 无(RefCounted 信号件) |
|
||||
| **board_hover**(`views/board_hover.gd`,悬停预承诺呈现域件,已建件) | 悬停反馈域唯一承载:三语义光标、目标格边框、BFS 路径预览、chord 双击首遇锚;_r0 出生展开区成员留宿主 | 构造=引用束注入 7 项(cells/minesweeper/diggable 引用+格命中换算/光标写入/hover_forbidden/chord_attempted 四 Callable);三入站口留宿主委托;hover_probe 读面留宿主;公开方法面 7(七口公开化——rebind/reset/refresh_after_paint/hover_at/detect_chord/on_shell_mouse_exited/hover_pos) | board_view(宿主);输入事件经宿主入站口 | 无(RefCounted) |
|
||||
| **cell_base_layer/cell_mark_overlay**(`views/`,单格两绘制域件,已建件) | 单格绘制域拆分(入参与该层绘制命令同件):基座层(底面/描边/立面/投影/接触带/倒角,12 成员)与标识叠加层(标记/字形/徽章/点迹/高亮/退色,14 成员);徽章遮罩走构造交付 Callable(`_mask_set`);宿主 _draw 退为六步时序器,宿主实测类变量 10 | 构造注入;绘制命令由宿主 _draw 按层序调用 | board_cell_unit(宿主);gdstyle 键各文件独立计数(12/13 均 ≤15) | 无(RefCounted) |
|
||||
@@ -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`(渲染闸) |
|
||||
|
||||
|
||||
@@ -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` 不在其中——改探针必须跑场景探针与渲染闸),与本条分档无关;本文不再用「测量面」统称这三个目录。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -68,12 +68,12 @@
|
||||
| 一系统一文档 | 不建 analysis 推演件;两三句能说清的"为什么"直接写进设计目的或规则正文 |
|
||||
| 正文只写现行规则 | 禁删除线、"(作废)"、"(日期+拍板)"式注记;审计信息入对应文档附录 |
|
||||
| 禁实现内容 | 系统文档与概念文档全文禁写实现细节——代码路径(`.gd`/`.py`/`.sh` 与运行时文件路径)、测试文件名、断言名、类名/方法名/信号名/常量名/调试与遥测入口名、提交号一律不出现;唯一例外=数据索引(`game/data/tables/*.tres` 与 `game/data/schemas/` 的表路径与表名、表字段名与表内枚值、存档键、封闭词表标签)。实现事实归 `architecture.md`;验收标准只写玩家或系统可观测面。本行由文档域闸的能力审计机检两个可判死的面(带后缀路径、反引号调用形态),其余靠写卡段人工核对;闸件清单与域划分的唯一出处=`tools/run_all_gates.sh` 头部 |
|
||||
| 正文单点维护 | 一条规则句只写在定义它的那份文档(owner 文档);其他文档要引用时写指针或语义概括,禁复制色值、尺寸、路径等可核对的具体值——上一句管不住数值之外的双写,本句补上。豁免面 = 执行侧文件必须带字面值才能工作时(生图提示词里的十六进制色、脚本参数),转写不算双写,但该文件头部必须写明"冲突时以 owner 文档为准" |
|
||||
| 正文单点维护 | 一条规则句只写在定义它的那份文档(owner 文档)——此前半句全域适用;后半句(其他文档要引用时写指针或语义概括,禁复制色值、尺寸、路径等可核对的具体值)适用域=`design/gdd/**`(GDD 设计文档;2026-11-07 用户裁收窄——美术圣经与 UX 呈现规格层为可带值参考层,不在后半句约束内)。豁免面 = 执行侧文件必须带字面值才能工作时(生图提示词里的十六进制色、脚本参数),转写不算双写,但该文件头部必须写明"冲突时以 owner 文档为准" |
|
||||
| 禁写变更记录 | 文档与代码文件内一律禁设「版本历史/修订记录/变更记录/修改记录/更新记录/更新日志」节或表格,禁逐版本叙述「改了哪些条款」;沿革与改动明细只进 git commit log。版本号可作为文档现行身份标记出现在文首状态行与正文引用中,但不得再挂变更表 |
|
||||
| 后置 ≠ 删除 | P1/P2 后置内容逐条列出;复活后置项 = 修订文档,禁静默加回 |
|
||||
| 活体清单不冻结计数 | 对会生长的清单(文档树构成、UI 层项、RNG 流词表等)不写死计数与枚举——写指针到 owner(根索引/常量表/实现文件),现状值由 owner 承载;历史计数只进 git 提交信息 |
|
||||
|
||||
**数值纪律(概念/实现分界,2026-11-05 用户立)**:GDD 概念文档只定概念、机制与规则——是什么、有什么、为什么;数量面(多大、多少、多大概率)与呈现实现细节不进概念文档正文。分工三处:取值域与字段契约归 `game/data/schemas/*.gd`(表结构定义+域校验执法,provisional 随值标注);现行值归 `game/data/tables/*.tres`;呈现实现规格(冻结公式、像素值、动效参数)居 `design/gdd/ux/`=呈现实现设计规格层,豁免概念纯度约束。存量系统文档的旧调参表节由独立清扫批消化,消化期不作机检对象(现行闸无此项判据)。
|
||||
**数值纪律(概念/实现分界,2026-11-05 用户立)**:GDD 概念文档只定概念、机制与规则——是什么、有什么、为什么;数量面(多大、多少、多大概率)与呈现实现细节不进概念文档正文。分工三处:取值域与字段契约归 `game/data/schemas/*.gd`(表结构定义+域校验执法,provisional 随值标注);现行值归 `game/data/tables/*.tres`;呈现实现规格(冻结公式、像素值、动效参数)居 `design/ux/`=呈现实现设计规格层,豁免概念纯度约束。存量系统文档的旧调参表节由独立清扫批消化,消化期不作机检对象(现行闸无此项判据)。
|
||||
|
||||
**登记处唯一性**:`design/registry/`(`entities.yaml`/`architecture.yaml`)已注销删除,禁再建第二处事实登记——术语进 术语表、数值进 `.tres`、架构进 架构文档、影响面靠全仓搜索概念规范名得出。强制收口条款:
|
||||
|
||||
@@ -181,7 +181,7 @@
|
||||
|
||||
设计线段与下游段同条并存是合法形态:状态行里出现过的**每条设计线各自留最新一段**,跨线依赖不阻塞本条推进(文档线闭合后写卡、美术线仍未出图,就把 `文档设计已闭合` 与 `待美术设计` 并写)。「写卡前本条要落的那条设计线必须已闭合」是实现侧的执行义务,由编排段查卡片凭据核对,不靠标题声明——闸只查同线并现多段。
|
||||
|
||||
状态行只写状态词:去掉特批标记与「(备选)」后缀后,残留任何括号即违规。闭合类状态词(文档设计已闭合/美术设计已闭合/已写卡/已写入)的证据——设计定稿路径+版本、art-bible 条款版本、需求卡片路径、复核报告路径(「已写卡」段另须报这一项,2026-11-05 用户立规;对文件名日期 ≥2026-11-06 的卡生效,旧卡不回头改,核对由 `tools/pipeline_gate.py` 的卡片复核判据到卡正文做)、落点文档+版本——一律写在实况的关联文档清单里,由流水线闸到正文核对;已编迭代 N 段的交叉核依据=实况清单里的需求卡片路径。
|
||||
状态行只写状态词:去掉特批标记与「(备选)」后缀后,残留任何括号即违规。闭合类状态词(文档设计已闭合/美术设计已闭合/已写卡/已写入)的证据——设计定稿路径+版本、art-bible 条款版本、需求卡片路径(「已写卡」段的复核凭据=卡内「复核记录」节,2026-11-06 用户裁:不另设复核报告文件;对文件名日期 ≥2026-11-06 的卡生效,旧卡不回头改,核对由 `tools/pipeline_gate.py` 的卡片复核判据到卡正文做)、落点文档+版本——一律写在实况的关联文档清单里,由流水线闸到正文核对;已编迭代 N 段的交叉核依据=实况清单里的需求卡片路径。
|
||||
|
||||
三种条目形态与一种后缀:
|
||||
|
||||
|
||||
@@ -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<N> 形)合回前执行 `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<N> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §4 改完即测铁规履行——改完即测自己改过的那部分,不请示、不等点名;收口顺序本身不设跑批关口。**卡片复核前置判据(2026-11-05 用户裁)**:需求卡片的 grilling 复核先于卡片提交、卡片提交先于合回——复核未跑完、缺陷清单未裁完,禁把卡片落 commit(落了也不算成立),`master` 永不落未成立卡;本条与 §2 段三出口铁律、doc-conventions §12.1「已写卡」凭据同判。
|
||||
2. **收口顺序(固定)**:**冻结 → 件核 → 并 master → 重塑 → 主区合回 → 删区**——代码与断言改完即冻结,全仓件核 grep 查残留;master 已被别的批次推进时先在 worktree 内 `git merge master` 解冲突(必须在合回之前,合回后才并=CI 验的是中间态);**重塑**=迭代分支(iter<N> 形)合回前执行 `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<N> 形免重塑免数卡)修复提交再合,落地复验追绿后删修复区**。批内的验证义务按 §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<N> 形免重塑免数卡),修复合回落地即再推再验追绿(追绿同链)。导入缓存刷新(.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<N> 分支合回的一卡一提交数卡校验、规则闸(写 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<N> 分支合回的一卡一提交数卡校验、规则闸(写 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<N> 形分支)独占回合——其他类事项不得并入迭代区(合回守卫数卡校验本就拦未归卡提交),待其合回后按新回合另开。
|
||||
|
||||
## 2. 五段流水线
|
||||
@@ -23,8 +23,8 @@
|
||||
|---|---|---|---|
|
||||
| 一·登记 | 无(闸管格式) | 待办清单条目 | 条目=标题登记元数据(日期/来源/可选优先级)+正文四属性(实况/缺口/改动面/状态);标题登记后不再变,正文随进程演进。格式与状态机权威=doc-conventions 第 12 节 |
|
||||
| 二·文档设计/美术设计 | doc-design(文档线,兼任反哺点写入)/asset-ingest(美术录入;样张素材由用户主导产出) | GDD 文档或 art-bible/过审样张素材;画面类需求另出预览图(文档线,2026-11-05 用户裁) | 用户提起或从待办清单取出;会话内进行、用户主导、禁无监管自跑;经用户点头确认心智一致才许写入文档;完成判据=待裁点清零,带未裁点的是讨论稿;设计线闭合凭据记入待办清单实况清单(文档线=文档路径+版本号,美术线=art-bible 条款版本);缺哪条线写在缺口属性,闭合与否写在状态段。画面类需求(新增画面要素/布局/配色/动效等触碰画面呈现规格的改动)设计段先出预览图=目标画面的 HTML/SVG 效果图(diagram-design 通道,AGENTS.md 图优先的同款载体),经用户人审通过才许进写卡段;预览图内嵌对应 ux/ 规格文档并随附像素级视觉判据句(§7 第 2 条同款写法);缺陷修复以现行规格与在库基线为准,不触发新预览 |
|
||||
| 三·写需求卡片 | req-card | 需求卡片 | 设计线闭合后用户发令;新玩法/新系统/新内容必须有设计定稿前置,例外=需求性工程条目(缺陷修复/重构等改游戏行为)凭实况里的证据指认跳过设计线;配套维护类(工程债/测试基建/工具维护,不动游戏行为)=工程改动直改类,不走本段,由用户发令直改;卡片必经 grilling 多轮复核、零待裁点才成立;复核发现按四类去向处置(2026-11-05 用户立规,定义在 req-card 技能「复核发现四类分流」:自判=查权威文档或查代码现状有唯一答案,复核者自己下结论改卡;补写=卡里漏写,回炉补全;待裁=几种做法都不违规且代价不同、影响玩法取向,才交用户;退回上游=文档设计线未闭合、美术设计线未闭合、或架构与代码规范本身冲突,停笔退对应段);成立凭据=卡内「复核记录」节+该节申报的复核报告路径,顺序未走完禁对用户称完成、禁落 commit(同 §1 第 2 条卡片复核前置判据);写完在待办清单状态记「已写卡」,卡片路径与复核报告路径记入实况清单(防遗忘);画面类需求卡片必引设计段已人审的预览图(验收判据的判据出处行锚指向 ux/ 内嵌预览节,test-conventions §8.3 锚口径不变) |
|
||||
| 四·迭代编排 | iter-plan | roadmap 迭代行 | 编排五查:①每卡卡片文件存在 ②每卡设计闭合凭据齐(或需求性工程条目的证据指认)③估时与冻结线——所选卡估时按区间**中值**求和(单位工作日、写精确值不取整):**卡片任何工作量均可编入**(2026-10-25 用户立:工作量限制属迭代层、非卡片层);编入后迭代总量 <5 个工作日=迭代行状态「冻结待凑批」、不可开工,凑批 ≥5 后由编排段改「待开工」(迭代体量偏好,2026-10-21 用户立、2026-10-25 改冻结制);5–10 个工作日为常规区间;超过 10 个工作日须经用户确认(确认前呈报整批/拆批两案等裁,禁自行拆分或塞入),确认后迭代行随带日期特批标记;超过 20 个工作日禁止编(硬上限,用户确认也不放行,必须拆批)④每卡复核记录在位(2026-11-05 用户立规)——卡内有「复核记录」节、该节申报的复核报告文件在 `reports/` 盘上真实存在;缺则呈报退回写卡段,禁编入,本段不代跑复核也不代补记录(生效=卡片文件名日期 ≥2026-11-06,旧卡不回头改)⑤写完 roadmap 行后跑流水线闸确认绿;编入未完成迭代,新开迭代行经用户确认 |
|
||||
| 三·写需求卡片 | req-card | 需求卡片 | 设计线闭合后用户发令;新玩法/新系统/新内容必须有设计定稿前置,例外=需求性工程条目(缺陷修复/重构等改游戏行为)凭实况里的证据指认跳过设计线;配套维护类(工程债/测试基建/工具维护,不动游戏行为)=工程改动直改类,不走本段,由用户发令直改;卡片必经 grilling 多轮复核、零待裁点才成立;复核发现按四类去向处置(2026-11-05 用户立规,定义在 req-card 技能「复核发现四类分流」:自判=查权威文档或查代码现状有唯一答案,复核者自己下结论改卡;补写=卡里漏写,回炉补全;待裁=几种做法都不违规且代价不同、影响玩法取向,才交用户;退回上游=文档设计线未闭合、美术设计线未闭合、或架构与代码规范本身冲突,停笔退对应段);成立凭据=卡内「复核记录」节(2026-11-06 用户裁:不另设复核报告文件),顺序未走完禁对用户称完成、禁落 commit(同 §1 第 2 条卡片复核前置判据);写完在待办清单状态记「已写卡」,卡片路径记入实况清单(防遗忘);画面类需求卡片必引设计段已人审的预览图(验收判据的判据出处行锚指向 ux/ 内嵌预览节,test-conventions §8.3 锚口径不变) |
|
||||
| 四·迭代编排 | iter-plan | roadmap 迭代行 | 编排五查:①每卡卡片文件存在 ②每卡设计闭合凭据齐(或需求性工程条目的证据指认)③估时与冻结线——所选卡估时按区间**中值**求和(单位工作日、写精确值不取整):**卡片任何工作量均可编入**(2026-10-25 用户立:工作量限制属迭代层、非卡片层);编入后迭代总量 <5 个工作日=迭代行状态「冻结待凑批」、不可开工,凑批 ≥5 后由编排段改「待开工」(迭代体量偏好,2026-10-21 用户立、2026-10-25 改冻结制);5–10 个工作日为常规区间;超过 10 个工作日须经用户确认(确认前呈报整批/拆批两案等裁,禁自行拆分或塞入),确认后迭代行随带日期特批标记;超过 20 个工作日禁止编(硬上限,用户确认也不放行,必须拆批)④每卡复核记录在位(2026-11-05 用户立规;2026-11-06 用户裁凭据换代——复核凭据=「复核记录」节自身,不另查报告文件)——卡内有「复核记录」节;缺节或节内任一条发现没有去向标记则呈报退回写卡段,禁编入,本段不代跑复核也不代补记录(生效=卡片文件名日期 ≥2026-11-06,旧卡不回头改)⑤写完 roadmap 行后跑流水线闸确认绿;编入未完成迭代,新开迭代行经用户确认 |
|
||||
| 五·实现 | iter | 代码与配置,可运行的游戏 | 需求卡片永不单独施工,统一由用户指定迭代号触发(=U0 开工令),按 iter 技能与本文 §3 执行;**本段是代码实现批:禁写 `design/` 与根规则文件,产出物只落 `reports/`(见 §3 第 12 条)**;设计权威文档(GDD/art-bible)批内一律不更新;完成人闸=实机目检清单+实现决定记录写齐即完成批内义务;画面类需求实现批另按 test-conventions §8.2 黄金图基线冻结口径执行(实跑截图入基线前用户对照预览图确认一眼,此后渲染闸自动盯) |
|
||||
|
||||
**实现段零裁断**:实现段的输入在编排段出口已锁死(卡片零待裁点+设计闭合凭据齐+编排五查过)。实现中冒出卡片没写清的分歧=上游缺陷——停该卡、待办清单状态回退+缺口补记,退回上游;禁自答、禁内联提问求裁断;该卡停摆不阻塞同迭代其他卡。环境故障与测试红走异常分级(自救重试/记录继续/停机呈报)。代拍板白名单保留:授权发生在卡片审问阶段,实现只执行预授权;超白名单/超卡片明列范围事项记入实现报告「实现决定记录」节备案,不另立追认单。
|
||||
|
||||
@@ -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/ 内嵌预览节);缺陷修复以现行规格与在库基线为准,不触发新预览。
|
||||
|
||||
@@ -112,7 +126,7 @@
|
||||
|
||||
**新增 core 件的覆盖登记义务(2026-11-03 架构审查 P1 余项裁)**:拆件或重构产出的新 core 文件不触发 process-conventions §3 第 6 条「新功能必配新测试」(那条只管新增玩法/机制),因此单由本条承接——新 core 件要么被 `game/tests/` 某套件按文件名或 `class_name` 点名,要么在承接它的能力行「代表用例」里申报「经宿主链覆盖(宿主=<件>)」;两者都无=批末收尾核对时补登。本条**不是覆盖率要求**,与 §7「覆盖率不是目标」同轨:登记的是能力义务的归属,不做文件计数——「每个 core 件必须被点名一次」的机器闸已裁否(它逼出为点名而点名的空套件,且与 §7 正面冲突)。落笔面沿用现行分工:申报行由写卡段入能力清单(实现批禁写该文件),核对归批末收尾提交。
|
||||
|
||||
**退役对账义务(2026-11-05 立)**:需求卡片退役(吸收即死)时,其对照表每行的验收意图必须已被承接——承接形态=能力清单行(能力+真实入口+代表用例)或已落地的断言;退役后无承接=该能力的规格判据断链,收尾核对判红。机检=`tools/capability_audit.py`(套件→能力行反向覆盖+断言链核验)。先例教训:营地图批卡片退役后,大门判据(`design/gdd/ux/camp.md:16`)无任何活面承接,探针 emit 断言悬空假绿——规格在、链接断。
|
||||
**退役对账义务(2026-11-05 立)**:需求卡片退役(吸收即死)时,其对照表每行的验收意图必须已被承接——承接形态=能力清单行(能力+真实入口+代表用例)或已落地的断言;退役后无承接=该能力的规格判据断链,收尾核对判红。机检=`tools/capability_audit.py`(套件→能力行反向覆盖+断言链核验)。先例教训:营地图批卡片退役后,大门判据(`design/ux/camp.md:16`)无任何活面承接,探针 emit 断言悬空假绿——规格在、链接断。
|
||||
|
||||
### 8.4 真实入口断言(验收闭合的主判据)
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@
|
||||
| `教学引导.md` | 新手教学与提示 | 范围草案·待设计 |
|
||||
| `设置与本地化.md` | 设置项与多语言 | 范围草案·待设计 |
|
||||
|
||||
## ux/(战斗画面与交互规格,`ux/`)
|
||||
## ux/(战斗画面与交互规格,`design/ux/`——与 gdd/ 同级的呈现实现设计规格层)
|
||||
|
||||
| 文件 | 一句话概要 | 状态 |
|
||||
|---|---|---|
|
||||
|
||||
@@ -63,7 +63,7 @@ MVP 不加新状态;扩容走 `status_effects.tres` 加行 + [增益状态](sy
|
||||
垂直切片阶段明确砍掉或推迟的内容,写在这里防止回潮:
|
||||
|
||||
- 更多关卡(N>3)——第三关已按扩展批流程落地(行集);三关正式序列已生效,唯一试玩入口 = 完整冒险
|
||||
- 精英节点与词缀系统——只预留了字段位,不定义语义(精英怪**设定**已登记预留——同立绘+附加特征标记+数值更强、钥石掉 1 个预留;实装随怪物批)
|
||||
- 精英节点与词缀系统——精英怪本体已落敌人表(行类 elite+池继承基础池加附加技,首只=地精精英),精英节点与投放仍未接线;词缀系统未设计
|
||||
- 地形变异扩展内容——首批四类型已落地,反制道具与全局作用域排 Alpha
|
||||
- 道具/增益之间的组合协同(遗物式联动)
|
||||
- 战后自动回复——回复只有道具与恢复点两个来源
|
||||
|
||||
+10
-10
@@ -103,15 +103,15 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
|
||||
| 主菜单 | —— | 在役(三入口:进入存档/设置/退出游戏——入口拓扑=进入存档→选档界面→进档/新档→营地覆盖层→营地场景图与生涯笔记本) |
|
||||
| 开始覆盖层 | —— | 在役 |
|
||||
| 选档界面(3 存档位) | —— | 在役(空槽新档/有档进档/删档二次确认+摘要三项) |
|
||||
| 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](ux/camp.md) | 在役(场景图+七物件点选+Esc 浮窗与顶栏;五功能页呈现=居中定尺寸功能窗口、场景根留底、窗内滚动) |
|
||||
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](ux/camp.md) | 在役(五卡全部落地,2026-10-21 用户裁整体列入 Alpha;沿革查 git 提交史。出口=五卡各自验收标准通过+营地屏用户目检〔登记体验验证清单 ㊺〕) |
|
||||
| 营地(天赋树 / 锻造工坊 / 秘宝匣 / 图鉴) | [camp](../ux/camp.md) | 在役(场景图+七物件点选+Esc 浮窗与顶栏;五功能页呈现=居中定尺寸功能窗口、场景根留底、窗内滚动) |
|
||||
| 营地交互重做(五卡:存档制数据层/主菜单三入口/选档界面/营地场景图/Esc 浮窗与营地顶栏) | [camp](../ux/camp.md) | 在役(五卡全部落地,2026-10-21 用户裁整体列入 Alpha;沿革查 git 提交史。出口=五卡各自验收标准通过+营地屏用户目检〔登记体验验证清单 ㊺〕) |
|
||||
| 生涯页 | [生涯](systems/跨局循环/生涯.md) | 在役(期一) |
|
||||
| 关卡地图屏 | [level-map](ux/level-map.md) | 在役 |
|
||||
| 战斗屏 | [battle](ux/battle.md) | 在役 |
|
||||
| 战斗结算总结框 | [settlement-summary](ux/settlement-summary.md) | 在役 |
|
||||
| 关卡结算屏 | [level-clear](ux/level-clear.md) | 在役 |
|
||||
| 三选一(奖励节点 / 关卡完成) | [node-reward](ux/node-reward.md) | 在役(两渠道均三选一停点:关卡完成与奖励节点各停一次,奖励节点经兑付拉起开三选一) |
|
||||
| 商店 | [shop](ux/shop.md) | 在役 |
|
||||
| 关卡地图屏 | [level-map](../ux/level-map.md) | 在役 |
|
||||
| 战斗屏 | [battle](../ux/battle.md) | 在役 |
|
||||
| 战斗结算总结框 | [settlement-summary](../ux/settlement-summary.md) | 在役 |
|
||||
| 关卡结算屏 | [level-clear](../ux/level-clear.md) | 在役 |
|
||||
| 三选一(奖励节点 / 关卡完成) | [node-reward](../ux/node-reward.md) | 在役(两渠道均三选一停点:关卡完成与奖励节点各停一次,奖励节点经兑付拉起开三选一) |
|
||||
| 商店 | [shop](../ux/shop.md) | 在役 |
|
||||
| 状态面板(局内) | [元进度](systems/跨局循环/元进度.md)(「解锁界面:双入口框架」节) | 在役 |
|
||||
| 冒险终局面板 | —— | 在役 |
|
||||
| 图例面板 | —— | 在役 |
|
||||
@@ -147,12 +147,12 @@ Alpha 内以玩法循环本身的内容与表现为主(棋盘、关卡、战
|
||||
| 棋盘瓦片与格面资源图标 | [美术圣经](../art/art-bible.md) | 在役 |
|
||||
| 未开格类型微光(五组色相+明度档) | [HUD框架](systems/表现与支撑/HUD框架.md) | 在役(明度档 provisional 值登记见 art-bible 的 git 提交史;观感复核已随战斗一屏样张过闸完成——用户实机目检核销,沿革查 git 提交史) |
|
||||
| 棋盘观感(第二 / 三关亮度与对比度) | [待办清单](../../待办清单.md) | 待做(开工前置=先做素材轴/UI 轴对照截图定性,再按定轴修复;旧卡 v1.4 存 reports/ 作素材、非已写卡状态,重启须重新立卡;出口=用户实机目检第二、三关不再判「看着费劲」) |
|
||||
| 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 在役(原稿分辨率入包禁降采样、显示按三档绝对高 96/144/188 缩放、现有形象即终稿;定值单点=art-bible §8.2;实机目检清晰度与同屏体量顺序登记体验验证清单 ㊻) |
|
||||
| 立绘显示清晰度(原稿分辨率+三档显示) | [角色设计](../art/character-design.md) | 在役(原稿分辨率入包禁降采样、显示按三档绝对高缩放、现有形象即终稿;定值单点=art-bible §8.2(档值定值面=站桩守门测试);实机目检清晰度与同屏体量顺序登记体验验证清单 ㊻) |
|
||||
| 敌人立绘(全敌种) | [角色设计](../art/character-design.md) | 在役 |
|
||||
| 主角立绘三态 | [角色设计](../art/character-design.md) | 在役 |
|
||||
| 状态图标 | [美术圣经](../art/art-bible.md) | 在役 |
|
||||
| 战斗演出链(弹道 / 受击 / 血条 / 灰化) | [战斗表现](systems/战斗循环/战斗表现.md) | 在役 |
|
||||
| 逐行结算排播 | [settlement-summary](ux/settlement-summary.md) | 在役 |
|
||||
| 逐行结算排播 | [settlement-summary](../ux/settlement-summary.md) | 在役 |
|
||||
| 减动效开关 | [设置与本地化](systems/表现与支撑/设置与本地化.md) | 在役 |
|
||||
| 全局纹理过滤设为邻近采样 | [待办清单](../../待办清单.md) | 在役(default_texture_filter=0+平滑渐变件三处显式 LINEAR 白名单,翻转面全枚举闭合) |
|
||||
| UI 字体(全局像素字,Fusion Pixel 单套单字重) | [美术圣经](../art/art-bible.md) §7.4 | 在役(现状即终稿——拉丁沿用自带字形不另接专件、不上第二字重;信息层级手段=字号档差+墨色深浅) |
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
|
||||
每关一套参数档(L1/L2/L3),生成器只吃参数档、不认关卡号。生长曲线:层高、宽度、节点总数、路径长度逐关递增且增幅变大;决策点密度逐关递增;第一关即含岔口——第一关也要有选择。单链是分支密度取下限的合法退化形态,不作为三关参数档的取值。
|
||||
|
||||
图规模无呈现侧上限:关卡画面为卷轴视口呈现,画布随图生长、屏为窗口(呈现规格归 [level-map](../../ux/level-map.md));规模由参数档与生长曲线约束,参数档给不出合法图即致命中止。
|
||||
图规模无呈现侧上限:关卡画面为卷轴视口呈现,画布随图生长、屏为窗口(呈现规格归 [level-map](../../../ux/level-map.md));规模由参数档与生长曲线约束,参数档给不出合法图即致命中止。
|
||||
|
||||
### 节点类型配比:类型分布怎么掷、内容归谁
|
||||
|
||||
|
||||
@@ -64,9 +64,9 @@
|
||||
|
||||
每个节点在图上有**节点深度**(本文含义:自入口沿路走到该节点的最长路径步数;多入口时取距任一入口的最长路径),用来当战斗的定位地址——遭遇坐标=关卡序号+节点深度,敌人表按**深度带**(深度区间)配池,寻址语义见[敌人系统](../战斗循环/敌人系统.md)。不用最短路径:合流节点或多入口图上取最短,会让某些出边指向深度不增甚至更浅的节点,破坏「连线只朝前进方向」的无环判据。"最深处是关底"的直感由深度承载:终点恒在各路径最深处。单链(每步只有一条连线)是合法退化形态,节点深度等于它前面走过的战斗场数;三关参数档都含岔口,第一关不再是单链。图的来源见[关卡节点图生成](关卡节点图生成.md)。
|
||||
|
||||
呈现侧口径(2026-09-17 用户拍板):节点图的画面表达走**方块地牢观感**——节点画成地牢房间的方块、连线画成通道——但逻辑拓扑就是本文的节点图,不产生任何额外选路或探索语义。关卡画面本体即节点链进度的呈现载体(选路覆盖层的常驻形态,节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 紫赤描边(紫赤+深墨环,视觉定稿见 [level-map](../../ux/level-map.md))),不另设进度条组件;视觉规格归 [HUD 框架](../表现与支撑/HUD框架.md)。
|
||||
呈现侧口径(2026-09-17 用户拍板):节点图的画面表达走**方块地牢观感**——节点画成地牢房间的方块、连线画成通道——但逻辑拓扑就是本文的节点图,不产生任何额外选路或探索语义。关卡画面本体即节点链进度的呈现载体(选路覆盖层的常驻形态,节点态直接读图:已过暗金 / 当前金 / 候选下一跳通道亮金 / Boss 紫赤描边(紫赤+深墨环,视觉定稿见 [level-map](../../../ux/level-map.md))),不另设进度条组件;视觉规格归 [HUD 框架](../表现与支撑/HUD框架.md)。
|
||||
|
||||
**商店覆盖层呈现**(呈现 spec:[design/gdd/ux/shop.md](../../ux/shop.md) v1.0;pool 模式货架面=共享池三选一同构卡〔进店抽 3/买 1 全重抽/空架售罄态〕,与三选一奖励同一交互语言;legacy 固定三件面随对照模式保留。**库存余量不呈现**(2026-10-04 用户裁)——货架卡不带「库存余 N/∞」字样,库存语义仍在共享池令牌库、只是不呈现):兑付 = 打开商店覆盖层(模态)——货架卡陈列、GP 余额读数、购买/离开;离开后兑付确认回选路。本节锁编排面(何时开/何时关/回选路),呈现细节归 spec;定价数值归[难度曲线与Boss](难度曲线与Boss.md)(shop.tres)。
|
||||
**商店覆盖层呈现**(呈现 spec:[design/ux/shop.md](../../../ux/shop.md) v1.0;pool 模式货架面=共享池三选一同构卡〔进店抽 3/买 1 全重抽/空架售罄态〕,与三选一奖励同一交互语言;legacy 固定三件面随对照模式保留。**库存余量不呈现**(2026-10-04 用户裁)——货架卡不带「库存余 N/∞」字样,库存语义仍在共享池令牌库、只是不呈现):兑付 = 打开商店覆盖层(模态)——货架卡陈列、GP 余额读数、购买/离开;离开后兑付确认回选路。本节锁编排面(何时开/何时关/回选路),呈现细节归 spec;定价数值归[难度曲线与Boss](难度曲线与Boss.md)(shop.tres)。
|
||||
|
||||
### 行进与选路:兑付后确认,选路就是行进
|
||||
|
||||
@@ -85,7 +85,7 @@
|
||||
|
||||
| 纪律 | 内容 |
|
||||
|---|---|
|
||||
| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才回图。战斗节点的确认挂在战斗总结结算上(呈现 spec:design/gdd/ux/settlement-summary.md),奖励/恢复节点挂在兑付反馈覆盖层上,商店节点挂在商店覆盖层的离开动作上——复用既有呈现位,零新增交互范式 |
|
||||
| 兑付后确认 | 到达节点立即兑付,不设"进入前确认"步;兑付反馈呈现完,玩家确认才回图。战斗节点的确认挂在战斗总结结算上(呈现 spec:design/ux/settlement-summary.md),奖励/恢复节点挂在兑付反馈覆盖层上,商店节点挂在商店覆盖层的离开动作上——复用既有呈现位,零新增交互范式 |
|
||||
| 零自动推进 | 确认之后回到关卡画面的选路口:岔路口玩家选一条连线,单链路口确认"继续"。选了才走;不选就停在图上 |
|
||||
|
||||
Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选路——关卡已定局,后续流程归冒险结构。
|
||||
@@ -123,7 +123,7 @@ Boss 兑付(胜利或英雄死亡)触发关卡收束点,此后不再有选
|
||||
|
||||
第二关的"新内容 = 既有池新技能"(技能池构成已定稿入[敌人系统](../战斗循环/敌人系统.md)技能池节)投放时,改的是战斗侧行里引用的技能池内容——节点图结构原样不动,本文无需为投放留任何新结构位。
|
||||
|
||||
**节点奖励 = 提升三选一(用户 2026-10-01 拍板:与通关三选一共享同池)**:奖励节点到达即开三选一(呈现 = 提升三选一覆盖层共用面,spec:[node-reward.md](../../ux/node-reward.md) v2.0)——从提升池七种(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件)按同一令牌库洗牌不放回抽 3(同 kind 去重、同一封闭随机流),玩家点选一件兑付(选中踢池、未选回池;秘宝件已出池,满位替换子交互随之不再触发)。件获取次数计数与令牌库为冒险级单例——两渠道同一令牌库合计消耗。「池空直通」为合法态——池耗即枯竭,空库 = 零候选合法态直通(见[冒险结构](冒险结构.md)令牌池节)。
|
||||
**节点奖励 = 提升三选一(用户 2026-10-01 拍板:与通关三选一共享同池)**:奖励节点到达即开三选一(呈现 = 提升三选一覆盖层共用面,spec:[node-reward.md](../../../ux/node-reward.md) v2.0)——从提升池七种(攻击/生命上限/护甲/GP 袋/行动点件/恢复件/刷新件)按同一令牌库洗牌不放回抽 3(同 kind 去重、同一封闭随机流),玩家点选一件兑付(选中踢池、未选回池;秘宝件已出池,满位替换子交互随之不再触发)。件获取次数计数与令牌库为冒险级单例——两渠道同一令牌库合计消耗。「池空直通」为合法态——池耗即枯竭,空库 = 零候选合法态直通(见[冒险结构](冒险结构.md)令牌池节)。
|
||||
|
||||
### 关卡收束点:先清库、后宣布
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
| 节点奖励件的分类与凭证定义、池结构与抽取机制(案D——共享池库存仍归本文) | [奇物](奇物.md) |
|
||||
| 关卡完成率带、敌我数值与 Boss 设计 | [难度曲线与Boss](难度曲线与Boss.md) |
|
||||
| 钥石与跨局成长 | [元进度](../跨局循环/元进度.md) |
|
||||
| 冒险总结结算(呈现摘要五件事;呈现 spec:design/gdd/ux/settlement-summary.md)与关卡推进指示 | [HUD框架](../表现与支撑/HUD框架.md) |
|
||||
| 冒险总结结算(呈现摘要五件事;呈现 spec:design/ux/settlement-summary.md)与关卡推进指示 | [HUD框架](../表现与支撑/HUD框架.md) |
|
||||
|
||||
## 在核心循环中的位置
|
||||
|
||||
|
||||
@@ -281,7 +281,7 @@
|
||||
**两层是两件事,别混**:
|
||||
|
||||
- **命名是本节的规矩**——三个分类各有一个专名,文档、代码词族、帮百科一律用它。指代某个分类时直接说「专长」,不说「机制类」「专长那一类」。
|
||||
- **呈现是另一件事**(归 [battle](../../ux/battle.md) 与 [node-reward](../../ux/node-reward.md),本节不裁决):玩家看到某件奇物时,卡面只写**这件东西的名字与效果**,不附带它的分类。玩家可以从效果反推它属于哪一类,但界面不替他说破。
|
||||
- **呈现是另一件事**(归 [battle](../../../ux/battle.md) 与 [node-reward](../../../ux/node-reward.md),本节不裁决):玩家看到某件奇物时,卡面只写**这件东西的名字与效果**,不附带它的分类。玩家可以从效果反推它属于哪一类,但界面不替他说破。
|
||||
|
||||
**为什么两层都要有**:分类名在卡面上不出现,不等于玩家对它没有概念——帮助手册、百科条目、成就名称三处一定会用到它(「首次集齐 4 件专长」「三件属性值提升」),效果描述里出现指代时也需要名字(如「本项属专长」类归类指代)。所以它必须是一个**玩家读得懂、读一遍就懂**的正式名词,不能是内部描述或自造词。
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 难度曲线与Boss
|
||||
|
||||
> 一句话:难度曲线、期望值带与 Boss 设计
|
||||
> 状态:已定稿;现行为 v1.5
|
||||
> 状态:已定稿;现行为 v1.6
|
||||
|
||||
## 设计目的
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
|---|---|---|
|
||||
| 关间台阶(关卡序号) | 每关至少投放一项新内容(新敌种或既有池新技能),加基准数值跳升 | 台阶必须能归因到看得见的变化,不是纯数字膨胀;投放断档是内容生产缺口,调参修补不了 |
|
||||
| 关内坡度(节点深度) | 同敌种数值平缓上移,深度也可表达为敌人成群 | 数量是比单体质变更优先的深度表达:保持技能池 EV 读数不变(权重即旋钮、EV 重锚顺带登记)、总量受控 |
|
||||
| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统,[棋盘生成](../排雷循环/棋盘生成.md)只按表生成。精英怪设定已登记预留(与其他普通小怪同一立绘 + 附加特征标记 + 数值更强;视觉特征三件 = 名称前后缀「精英·」+ 底座光环 + 描边变色,色板合规与样张闸义务随怪物设定批实装) |
|
||||
| Boss 与精英修饰(预留第三轴) | Boss 走最深深度直填行;精英怪本体走敌人表精英行(独立行+池继承附加,语义见[敌人系统](../战斗循环/敌人系统.md)「精英行」节),精英节点修饰只有字段命名空间预留 | Boss 不走修饰表;修饰乘数表 MVP 为空表、零行为,首个使用场景 = 精英节点(随[关卡节点地图](关卡节点地图.md)定稿联动);数值所有权在本系统(含精英行数值与附加技权重),[棋盘生成](../排雷循环/棋盘生成.md)只按表生成。精英怪设定:与其他普通小怪同一立绘 + 附加特征标记 + 数值更强;视觉特征三件 = 名称前缀「精英·」(由精英行名字行值承载)+ 底座光环 + 描边变色,后两件的色板合规与样张闸义务随怪物设定批实装 |
|
||||
|
||||
**结论**:关内坡度 = 每深一档同敌种基准上移,或按"深度也可表达为敌人成群"改由数量承载——单体生命随深度的具体走向以 `enemy.tres` 行值为准(数量优先于单体;s_hp 线性式为建模工具,现行行值不承诺随深度单调上移);关间台阶 = 相邻关同敌种基准乘 1.3–1.6(域语义如上;s_hp / k_L 现行行值读 `enemy.tres`,取值域归敌人表字段定义)。
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
## 系统范围
|
||||
|
||||
边界这样划:战斗屏上"发生什么、摆在哪里、是什么意思"已经由两份现行文档定稿——屏级呈现形态归 [战斗屏 UX 规格 battle.md](../../ux/battle.md),视觉基调与剪影色彩归 [美术圣经 art-bible](../../../art/art-bible.md)。本文只负责"这些东西怎么动",而且是排期靠后的打磨项(见 [不做清单](../../05-不做清单.md):战斗动画演出是 MVP 内的打磨项,一律排后)。
|
||||
边界这样划:战斗屏上"发生什么、摆在哪里、是什么意思"已经由两份现行文档定稿——屏级呈现形态归 [战斗屏 UX 规格 battle.md](../../../ux/battle.md),视觉基调与剪影色彩归 [美术圣经 art-bible](../../../art/art-bible.md)。本文只负责"这些东西怎么动",而且是排期靠后的打磨项(见 [不做清单](../../05-不做清单.md):战斗动画演出是 MVP 内的打磨项,一律排后)。
|
||||
|
||||
负责:
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
|
||||
| 不负责 | 归谁 |
|
||||
|---|---|
|
||||
| 屏级呈现形态与呈现语义(四区布局、敌人呈现〔站桩形态〕、明细带、高光行) | [battle.md](../../ux/battle.md) 与 [HUD 框架](../表现与支撑/HUD框架.md),本文只引用 |
|
||||
| 屏级呈现形态与呈现语义(四区布局、敌人呈现〔站桩形态〕、明细带、高光行) | [battle.md](../../../ux/battle.md) 与 [HUD 框架](../表现与支撑/HUD框架.md),本文只引用 |
|
||||
| 视觉基调、剪影哲学、色彩与图标键契约 | [art-bible](../../../art/art-bible.md),本文只引用 |
|
||||
| 结算逻辑与逐行流水本身 | [战斗结算](战斗结算.md) |
|
||||
| 敌种视觉状态的数据面(哪个敌种有几态、什么姿态) | [敌人系统](敌人系统.md) |
|
||||
@@ -48,7 +48,7 @@
|
||||
|
||||
### 不另起场景:在既有呈现上叠加动画密度
|
||||
|
||||
战斗屏是挖掘与结算两种形态共用的同一张屏:常驻双舞台(白话:战斗画面与扫雷图同屏常驻,像同一块屏上的两个舞台),结算期不切换场景,裁决与理由见 [battle.md 布局决策 1](../../ux/battle.md)。所以战斗表现的工作不是另做一个战斗场景,而是在既有呈现上叠加动画密度与演出——这是本系统范围边界的第一条。
|
||||
战斗屏是挖掘与结算两种形态共用的同一张屏:常驻双舞台(白话:战斗画面与扫雷图同屏常驻,像同一块屏上的两个舞台),结算期不切换场景,裁决与理由见 [battle.md 布局决策 1](../../../ux/battle.md)。所以战斗表现的工作不是另做一个战斗场景,而是在既有呈现上叠加动画密度与演出——这是本系统范围边界的第一条。
|
||||
|
||||
"发生什么"与"怎么动"因此分了工:敌人死亡是血条归零后灰化加碎裂退场、禁止瞬间消失,结算明细带逐行滚动,完美封印与封印标中有高光行强调——这些呈现语义已由 battle.md 与 HUD 框架定稿,本文不复述。本文将来承接的只是其上的动画密度:受击反馈、弹道、退场动画等类别。
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
|
||||
样张是前置美术测试的判定物;本节把"怎么算过"在测试组织前写成文,避免临场现定标准。成文做法参照 [art-bible](../../../art/art-bible.md) §1.4 格子样张测试(内容清单、按序验收项、判定与回退),判据全部落到已冻结的现行口径上——本节不定任何新的视觉参数。
|
||||
|
||||
样张内容清单(一组齐备,缺项即无效):按 [battle.md 布局决策 1](../../ux/battle.md) 的常驻双舞台四区布局整屏出样;角色剪影按棋盘格、结算明细带、战斗舞台三个语境同图摆放;~~敌人卡两候选形态对照(卡片式与站桩式各一版,其余内容不变)~~——**本项已完成(全站桩定稿、卡片式退役),正式样张按站桩形态单版出样即可**;受击、退场、高光兑现三组动画关键帧条(每组至少起、中、末三帧);敌人姿态精灵与数字、封印印记、小人同格叠放的遮挡对照组至少一组。
|
||||
样张内容清单(一组齐备,缺项即无效):按 [battle.md 布局决策 1](../../../ux/battle.md) 的常驻双舞台四区布局整屏出样;角色剪影按棋盘格、结算明细带、战斗舞台三个语境同图摆放;~~敌人卡两候选形态对照(卡片式与站桩式各一版,其余内容不变)~~——**本项已完成(全站桩定稿、卡片式退役),正式样张按站桩形态单版出样即可**;受击、退场、高光兑现三组动画关键帧条(每组至少起、中、末三帧);敌人姿态精灵与数字、封印印记、小人同格叠放的遮挡对照组至少一组。
|
||||
|
||||
验收项按序判定,第一项不过 = 整张否决(继承格子样张"数字可读性定风格"的次序):
|
||||
|
||||
@@ -118,7 +118,7 @@
|
||||
| 上游 | [HUD 框架](../表现与支撑/HUD框架.md) | 四区布局、战斗画面区(四区中的 B 区)演出区结构、敌人呈现(站桩形态)与明细带呈现语义——本文在其上叠加密度 |
|
||||
| 上游 | [敌人系统](敌人系统.md) | 资产需求下限三类与敌种姿态数据面——数据已锁,本文出终稿;另登记一项随样张一并裁决的事项:敌人层绘制优先级的插入位置 |
|
||||
| 上游 | [art-bible](../../../art/art-bible.md)(现行权威) | 剪影、色彩、图标键契约、各阶段氛围——表现侧约束 |
|
||||
| 上游 | [battle.md](../../ux/battle.md)(现行权威) | 屏级呈现形态与呈现语义 |
|
||||
| 上游 | [battle.md](../../../ux/battle.md)(现行权威) | 屏级呈现形态与呈现语义 |
|
||||
| 协作 | [音频](../表现与支撑/音频.md) | 坍塌显形动画与音效方向配对登记,将来对位设计(挂点未定) |
|
||||
|
||||
## P0 最小闭环
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# 敌人系统
|
||||
|
||||
> 一句话:敌人数据、技能池出招、意图呈现
|
||||
> 状态:已定稿;现行为 v1.8
|
||||
> 状态:已定稿;现行为 v1.9
|
||||
|
||||
## 设计目的
|
||||
|
||||
@@ -138,6 +138,20 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
|
||||
|
||||
举证:**摇定结果 = 可摇集合按权重份额归一的一次抽签;当前可摇集合 = 实例池排除冷却中与触发条件不满足的技能(保底命中时 = 条件满足的全部特殊技)。**连带可判别:把权重当固定百分比、把冷却当只锁高伤技、把蓄力当重击前奏,都是与本规约相悖的旧口径。
|
||||
|
||||
### 精英行:现役敌种的强化版,池=继承基础池+附加技
|
||||
|
||||
精英怪是某个现役敌种的强化版本:玩家看到的是同一种怪(同立绘、名字带「精英·」前缀),数值更强、出招多几样。它在敌人表里是独立的一行——自己的 enemy_id、自己的数值、自己的专属池,和基础敌种的行互不覆盖;外显与内部定义因此可以是一对多(一个外观对应基础行与精英行两份定义)。精英身份由行类承载:敌人表行类新增 elite 值。
|
||||
|
||||
| 规则 | 含义 |
|
||||
|---|---|
|
||||
| 独立行 | 精英=敌人表行类为 elite 的一行,enemy_id 独立、不与基础敌种同名;名字前缀「精英·」由该行名字行值承载。数值字段(生命、攻击、护甲两轴、数量)与战斗行同一套定义与取值域 |
|
||||
| 基础敌种引用 | 精英行用基础敌种引用字段指向一个基础敌种的 enemy_id;被指敌种必须在同关战斗行类里存在,指向不存在的敌种或另一条精英行,加载期拒绝 |
|
||||
| 专属池=继承+附加 | 精英行不写自己的池模板,只写附加技清单({skill_id, weight} 数组);它的池模板=基础池+附加技合并。基础池取自同关、战斗行类、该基础敌种深度带最小的那一行的池模板——继承不复制:基础池调参自动传到精英。附加技的 skill_id 与基础池重复,加载期拒绝 |
|
||||
| 合并池过同一纪律 | 池构成纪律(恰一普攻、增益 ≤1、妨害 ≤1、至少一行无条件特殊技、悬空引用致命、权重域)校验的对象是合并后的池;开战快照、实例池、冷却、保底、触发条件语义全部不变——精英实例拿到的就是合并后池的快照 |
|
||||
| 暂不接线 | 精英行落表即合法;开战寻址只按节点类型取战斗或 Boss 行集,精英行在接线前不会被任何节点读到,静置合法、不返回错误。精英遭遇怎么投放到节点,归待办清单「战斗节点遭遇编排方式」条目 |
|
||||
|
||||
举证:**精英池模板 = 基础池条目 ∪ 附加技条目(两者 skill_id 不相交)**;中签率公式不变,可摇权重合计含附加技权重——加附加技会摊薄基础池各技能的中签率,EV 重锚归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)。各精英行的数值与附加技行值读敌人表(`enemy.tres`),本文不登记。
|
||||
|
||||
### 触发条件:情境技与闩型解锁共用一套门槛
|
||||
|
||||
技能行可以带一个触发条件:条件不满足的拍,这行技能不进可摇集合——与冷却中的排除同一待遇;条件满足时,按正常权重照摇。它服务两类出招:情境技(自身低血才用的防御、上轮玩家停手才会出的偷袭等)让怪物像在"看情况出招",停手与压血因此有了代价;闩型解锁(半血解锁)让大招只在血量跌破阈值后才可被摇出。两类用例、一套机制——条件只管"现在能不能被摇到",不改权重、不强制出手。
|
||||
@@ -236,7 +250,8 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
|
||||
| 优先级 | 内容 | 后置原因 |
|
||||
|---|---|---|
|
||||
| P1 | 情境条件触发器(低血触发防御、玩家停手时偷袭技等) | 已实装(三 Boss 闩型+两示范投放,见「触发条件」节与「首批落地」段) |
|
||||
| P1 | 个体化变体池(A 型好斗 / B 型守势、精英专属池) | 共识点 A 的 A2 案;登记[待办清单](../../../../待办清单.md) |
|
||||
| P1 | 个体化变体池(同种个体分型:A 型好斗 / B 型守势) | 共识点 A 的 A2 案;排期 Beta,登记[待办清单](../../../../待办清单.md) |
|
||||
| P1 | 精英遭遇接线、精英底座光环与描边变色、精英钥石掉落 | 精英行已落表静置(见「精英行」节);接线归[待办清单](../../../../待办清单.md)「战斗节点遭遇编排方式」条目,视觉二件随怪物设定批,钥石随接线批 |
|
||||
| P1 | 全局特殊间隔旋钮 | 特殊技背靠背实测仍嫌时的备用闸;登记[待办清单](../../../../待办清单.md) |
|
||||
| P1 | 新效应词汇(妨害技 + 必中附加分量已实装——实装面 = L2 Boss 卓尔;多段命中经用户拍板退役) | 妨害类型 + rider_mult 字段已落地;登记[待办清单](../../../../待办清单.md)(已解决移除,去向见状态记录) |
|
||||
| P1 | 敌种扩容与投放节奏(含第二关池与投放) | 投放节奏是关卡命脉,归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
|
||||
@@ -253,7 +268,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
|
||||
| 参数 | 取值域 | 说明 |
|
||||
|---|---|---|
|
||||
| level_index(关卡序号) | int ≥ 1 | 行集坐标第一维:第几关;每关必有战斗类、深度带 0 的一行(锚行)与至少一行 Boss 类行——加载校验强制;关间台阶的坐标轴,行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
|
||||
| row_kind | {battle, boss} | 行类:battle = 战斗节点配池(非 Boss 的战斗节点一律取此类),boss = 终点行集——Boss 战按终点配置,多终点各配一行(走哪条路决定打谁);开战按节点类型取对应类的行集 |
|
||||
| row_kind | {battle, boss, elite} | 行类:battle = 战斗节点配池(非 Boss 的战斗节点一律取此类),boss = 终点行集——Boss 战按终点配置,多终点各配一行(走哪条路决定打谁);elite = 精英行(见核心规则「精英行」节,接线前不被任何节点读到);开战按节点类型取对应类的行集 |
|
||||
| depth_band | int ≥ 0 | 深度带 = 节点深度所在的区间(定义见核心规则「行集构成纪律」):配池按带不按点,难度坡随带递增;节点深度落在带与带之间时取首个 ≥ 深度的带键(偏难一侧);越顶带加载校验判为调参事故;带键投放归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
|
||||
| unit_count | [1, 3] | 遭遇数量区间下界;有上界的行开战时在区间内掷定,教学位节点同样掷骰 |
|
||||
| unit_count_max | [unit_count, 3],可省略 | 数量区间上界;开战时在区间内掷定本战实际数量;缺省 = 定值 |
|
||||
@@ -263,7 +278,9 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
|
||||
| armor_class(护甲等级) | int [0, 999],缺省 0 | 防守方护甲等级——基础攻击掷点的折扣判定读数,掷点结算归[战斗结算](战斗结算.md);与 armor_type(克制属性轴)两概念禁止混用;行值投值归[难度曲线与Boss](../冒险循环/难度曲线与Boss.md) |
|
||||
| hp | [1, 999] | 单位生命基线(难度修饰前) |
|
||||
| atk | [0, 999] | 攻击基线 |
|
||||
| pool(池模板) | {skill_id, weight} 数组,weight ∈ (0, 9999] | 技能行引用在技能表;池构成纪律与悬空引用加载期拒绝(校验在数据配置跨表通道) |
|
||||
| pool(池模板) | {skill_id, weight} 数组,weight ∈ (0, 9999] | battle / boss 行必填、elite 行禁填;技能行引用在技能表;池构成纪律与悬空引用加载期拒绝(校验在数据配置跨表通道) |
|
||||
| base_ref(基础敌种引用) | enemy_id 字符串 | 仅 elite 行、必填(其余行类禁填);须指向同关 battle 行类存在的敌种,悬空或指向 elite 行加载期拒绝 |
|
||||
| pool_extra(附加技清单) | {skill_id, weight} 数组,weight ∈ (0, 9999],非空 | 仅 elite 行、必填(其余行类禁填);skill_id 与基础池不相交;合并后池过池构成纪律 |
|
||||
|
||||
技能表(全局单表,技能行主键 skill_id):
|
||||
|
||||
@@ -305,6 +322,7 @@ Boss 专属身份 = 专属池承载:独占技能 + 权重轮廓(**技能错
|
||||
- 表校验:越域值、池权重 ≤ 0、空池、普攻配倍率/冷却、池增益 > 1、skill_id 悬空、行集构成违例(行集内同敌种第二行 / 同带键多行不相邻)均在加载期返回错误并止动;域内合法极端(攻击基线 0)不拒;主角表恰一行、攻击配 0 拒绝。
|
||||
- 零随机回放:同一动作与受击序列驱动两个独立实例,存活投影、逐实例摇定轨迹、顺位返回序列逐项相同。
|
||||
- 表实一致:敌人表、主角表、技能表三张配置表的字段与各自字段定义逐字段一致。
|
||||
- 精英行:elite 行的池模板 = 基础池(同关、战斗行类、基础敌种深度带最小行)+附加技清单,开战快照拿到合并后池;基础池改表后精英合并池随之改变;elite 行落表时任何战斗节点与 Boss 节点开战都不读到它;elite 行缺基础敌种引用或附加技清单、基础敌种引用悬空或指向 elite 行、精英 enemy_id 与基础敌种引用同名、附加技与基础池 skill_id 重复、合并池违反池构成纪律、elite 行写自己的池模板、非 elite 行写基础敌种引用或附加技清单——均在加载期返回错误并止动。
|
||||
|
||||
## 待办
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ HUD框架把各玩法系统产生的事实变成玩家看得见、点得动、
|
||||
|
||||
## 系统范围
|
||||
|
||||
边界这样划:本系统管"怎么呈现、怎么翻译输入、弹层怎么排队"。一个动作合不合法、结果算什么意思,归排雷玩法、行动经济、战斗结算等上游裁定;画面具体长什么样、组件落在哪一像素,屏级权威按屏分治——战斗屏是 [battle.md](../../ux/battle.md)、关卡画面是 [level-map.md](../../ux/level-map.md)——本文只锁系统级契约。
|
||||
边界这样划:本系统管"怎么呈现、怎么翻译输入、弹层怎么排队"。一个动作合不合法、结果算什么意思,归排雷玩法、行动经济、战斗结算等上游裁定;画面具体长什么样、组件落在哪一像素,屏级权威按屏分治——战斗屏是 [battle.md](../../../ux/battle.md)、关卡画面是 [level-map.md](../../../ux/level-map.md)——本文只锁系统级契约。
|
||||
|
||||
负责:
|
||||
|
||||
@@ -32,7 +32,7 @@ HUD框架把各玩法系统产生的事实变成玩家看得见、点得动、
|
||||
|---|---|
|
||||
| 任何玩法状态与玩法数值 | 各玩法系统;数值表本体归[数据配置](数据配置.md) |
|
||||
| 合法性判定与机制语义裁定 | [排雷玩法](../排雷循环/排雷玩法.md)、[行动经济](../排雷循环/行动经济.md)、[战斗结算](../战斗循环/战斗结算.md) |
|
||||
| 屏级布局与动画时长的像素级裁决 | [battle.md](../../ux/battle.md)(战斗屏)与 [level-map.md](../../ux/level-map.md)(关卡画面) |
|
||||
| 屏级布局与动画时长的像素级裁决 | [battle.md](../../../ux/battle.md)(战斗屏)与 [level-map.md](../../../ux/level-map.md)(关卡画面) |
|
||||
| 美术终稿与动画密度 | [art-bible](../../../art/art-bible.md)与[战斗表现](../战斗循环/战斗表现.md) |
|
||||
| 音频、教学内容、本地化文本生产 | [音频](音频.md)、[教学引导](教学引导.md)、[设置与本地化](设置与本地化.md) |
|
||||
| 任何随机消费 | 无——呈现层零随机 |
|
||||
@@ -61,9 +61,9 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| C 角色信息 | 左下,宽读 `info_panel_w`,高 = 基准屏高 − B 区高 | 生命 / 攻击 / 储备数据框、状态效果槽、小人立绘 |
|
||||
| D 扫雷图 | 中下,宽 = 基准屏宽 − C 区宽 − 列间距,高同 C 区 | 行动点条与按钮组、棋盘本体、toast 槽 |
|
||||
|
||||
分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`(经 `ConfigDb.presentation.battle_layout` 读取);分区尺寸与组件落位的像素级裁决归 [battle.md](../../ux/battle.md),两份文档只写结构关系、不复写数值。三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。
|
||||
分区几何的唯一事实源 = `game/data/tables/battle_layout.tres`(经 `ConfigDb.presentation.battle_layout` 读取);分区尺寸与组件落位的像素级裁决归 [battle.md](../../../ux/battle.md),两份文档只写结构关系、不复写数值。三区结构与双舞台常驻是结构冻结项,修订走文档修订而非调参。节点链进度不在战斗屏呈现——它由关卡画面(选路覆盖层常驻形态)的节点态承载,见[关卡节点地图](../冒险循环/关卡节点地图.md)。
|
||||
|
||||
**结论**:棋盘格边长由 [battle.md](../../ux/battle.md)「棋盘格尺寸公式」按棋盘区实测面积与板型算出;本文只判"塞不塞得进",不复制算式与算例。**分辨率档位**:两档=1280×800 基准档(本表全部定值的口径)+1920×1080 档=**全局等比**(stretch canvas_items+aspect keep——16:10 设计在 16:9 窗口为等比 1.35×+左右黑边各 ~96px);本节与各屏定值一律按 1280 基准档书写,1920 档由引擎等比承载、零重排。
|
||||
**结论**:棋盘格边长由 [battle.md](../../../ux/battle.md)「棋盘格尺寸公式」按棋盘区实测面积与板型算出;本文只判"塞不塞得进",不复制算式与算例。**分辨率档位**:两档=1280×800 基准档(本表全部定值的口径)+1920×1080 档=**全局等比**(stretch canvas_items+aspect keep——16:10 设计在 16:9 窗口为等比 1.35×+左右黑边各 ~96px);本节与各屏定值一律按 1280 基准档书写,1920 档由引擎等比承载、零重排。
|
||||
|
||||
```
|
||||
装板可行性(网格实高 ≤ 棋盘区可用高):
|
||||
@@ -76,7 +76,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| N | 棋盘边长格数 | 读[棋盘生成](../排雷循环/棋盘生成.md)(现行四档;事实源 board_gen.tres) |
|
||||
| 棋盘区宽 / 高 | 扫雷图区基准尺寸 | 由 D 区尺寸扣行动点条高(读 `ap_bar_h`)得出,本表不复制现算值 |
|
||||
|
||||
装板可行性由上面的不等式保证;格边长的可读下限见 [battle.md](../../ux/battle.md),算例由样张渲染脚本按布局表现算、不入文档。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
|
||||
装板可行性由上面的不等式保证;格边长的可读下限见 [battle.md](../../../ux/battle.md),算例由样张渲染脚本按布局表现算、不入文档。棋盘数字直接显示排雷玩法给的真值——呈现层禁止自己算数字,这是"数据层从不说谎"在呈现侧的镜像。
|
||||
|
||||
### 提示与弹层:轻提示随起随消、重弹层排队进出
|
||||
|
||||
@@ -169,7 +169,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
|
||||
### 营地:可点击场景图
|
||||
|
||||
营地形态**已换代为一张可点击的场景图 + 七物件点选**(现行权威=[camp](../../ux/camp.md));全部程序占位视觉(占位纪律延续,2026-10-11 用户令),**视觉与布局终稿归 HUD 营地视觉批**(未立项)。结构现行事实源 = [天赋树](../跨局循环/天赋树.md)「营地=树:统一模型」节。
|
||||
营地形态**已换代为一张可点击的场景图 + 七物件点选**(现行权威=[camp](../../../ux/camp.md));全部程序占位视觉(占位纪律延续,2026-10-11 用户令),**视觉与布局终稿归 HUD 营地视觉批**(未立项)。结构现行事实源 = [天赋树](../跨局循环/天赋树.md)「营地=树:统一模型」节。
|
||||
|
||||
### 可访问性基线:看得清、不闪、纯鼠标玩得通
|
||||
|
||||
@@ -212,7 +212,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| [排雷玩法](../排雷循环/排雷玩法.md) | 已开 / 未开格态一眼可辨;悬停预承诺与可挖域常驻指示;移动路径预览即合法性指示;每次拒绝可感知且棋盘面细因可区分;停手被改判可感知 |
|
||||
| [行动经济](../排雷循环/行动经济.md) | 行动点与储备常显;储备清零反馈;棋盘面与资源面拒绝可区分;刷新入口与代价可读 |
|
||||
| [战斗结算](../战斗循环/战斗结算.md) | 未生效类明细行(奖励未生效)的呈现;高光行强调 |
|
||||
| [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链进度呈现(关卡画面选路覆盖层节点态承载:暗金已过、金当前、候选下一跳通道亮金、Boss 紫赤描边);选路覆盖层只读呈现(零自动推进停点语义)——屏级 spec:[level-map](../../ux/level-map.md) |
|
||||
| [关卡节点地图](../冒险循环/关卡节点地图.md) | 节点链进度呈现(关卡画面选路覆盖层节点态承载:暗金已过、金当前、候选下一跳通道亮金、Boss 紫赤描边);选路覆盖层只读呈现(零自动推进停点语义)——屏级 spec:[level-map](../../../ux/level-map.md) |
|
||||
| [敌人系统](../战斗循环/敌人系统.md) | 意图线索映射成文且全局恒定;单位顺位与血条可读;敌方共享层数各卡同值 |
|
||||
| [增益状态](../战斗循环/增益状态.md) | 状态栏常显;无状态时整排槽隐藏;有利 / 有害极性呈现不看数值符号 |
|
||||
| [地图战斗资源](../战斗循环/地图战斗资源.md) | 已开格的奖励身份常读;未开格类型微光(英雄周围 8 格内的未开格显示资源类型——概念定义见本篇「未开格类型微光」节) |
|
||||
@@ -236,7 +236,7 @@ HUD框架站在玩家与全部玩法系统之间:挖掘期它是输入的翻
|
||||
| P1 | 远格点击自动"先移动再挖"复合意图 | 移动免费高频,两击摩擦先经实测验证再裁 |
|
||||
| P1 | Gamepad 焦点序 | 纯鼠标已覆盖 MVP 全部操作 |
|
||||
| P1 | 可访问性正式分层(文本缩放 / 色盲模式 / 按键重映射) | 归[设置与本地化](设置与本地化.md)设置项全集裁决(2026-09-27 拍板不另立专项文档) |
|
||||
| P1 | 相邻域屏(节点奖励 / 关卡结算)完整屏形态 | 归后续 UX 文档(关卡结算屏 spec 已建 [level-clear](../../ux/level-clear.md);节点奖励屏 spec 已在役:[node-reward](../../ux/node-reward.md) v2.5 令牌池口径);MVP 以覆盖层反馈承载 |
|
||||
| P1 | 相邻域屏(节点奖励 / 关卡结算)完整屏形态 | 归后续 UX 文档(关卡结算屏 spec 已建 [level-clear](../../../ux/level-clear.md);节点奖励屏 spec 已在役:[node-reward](../../../ux/node-reward.md) v2.5 令牌池口径);MVP 以覆盖层反馈承载 |
|
||||
| P2 | 提示一次性作用域升级为存档级"一生一次" | 归[存档序列化](存档序列化.md)与[元进度](../跨局循环/元进度.md)后重议 |
|
||||
| P2 | 调试面板退役 | 正式侧归元进度;诊断按钮保留为调试设施 |
|
||||
| P2 | 探测类秘宝的显影呈现面 | 接口归[秘宝](../跨局循环/秘宝.md)定稿批 |
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
| 关卡表 | 逐关节点图与层名 | [冒险结构](../冒险循环/冒险结构.md) |
|
||||
| 冒险表 | 关卡大奖励与钥石入账(单行全局) | [冒险结构](../冒险循环/冒险结构.md) |
|
||||
| 元进度表 | 元进度字段(现行五行族:属性锻造 / Boss 钥石 / 工坊阈值 / 换算率 / 同调刷新升级行族) | [元进度](../跨局循环/元进度.md) |
|
||||
| 战斗屏布局表(`battle_layout.tres`) | 分区几何、B 区连续地面层高与明细带两态高(单行;呈现层布局配置,非玩法数值——供呈现层与样张工具同源读取) | [战斗屏规格](../../ux/battle.md)(结构)/[HUD框架](HUD框架.md)(呈现常量) |
|
||||
| 战斗屏布局表(`battle_layout.tres`) | 分区几何、B 区连续地面层高与明细带两态高(单行;呈现层布局配置,非玩法数值——供呈现层与样张工具同源读取) | [战斗屏规格](../../../ux/battle.md)(结构)/[HUD框架](HUD框架.md)(呈现常量) |
|
||||
|
||||
举证:本节为数据纪律约定,无计算。违规检测 = 静态扫描加人工复核,随各表回填定稿与里程碑检查执行。
|
||||
|
||||
@@ -174,7 +174,7 @@
|
||||
|
||||
| 优先级 | 内容 | 后置原因 |
|
||||
|---|---|---|
|
||||
| P1 | 战斗级可回放集成测试(同主种子同动作序列,步骤流逐行相同) | 需战斗域载体,随[战斗结算](../战斗循环/战斗结算.md)回放验收兑现 |
|
||||
| P1 | 战斗级可回放闭环测试(同主种子同动作序列,步骤流逐行相同) | 需战斗域载体,随[战斗结算](../战斗循环/战斗结算.md)回放验收兑现 |
|
||||
| P1 | 难度曲线导出工具(无窗口导出行解析矩阵供比对) | 难度审计的离线载体,[难度曲线与Boss](../冒险循环/难度曲线与Boss.md)期交付物 |
|
||||
| P2 | 正式包构建的致命表现验证与调试覆盖结构性不存在验证 | 挂首次正式导出检查清单 |
|
||||
| P2 | 进度键表的调试覆盖行寻址语法 | 调试覆盖现只支持单行全局表字段,行寻址登记为后续扩展 |
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
| 不负责 | 归谁 |
|
||||
|---|---|
|
||||
| 呈现语义与可访问性基线本体(字号下限、闪烁上限、纯鼠标可玩等) | [HUD 框架](HUD框架.md) |
|
||||
| 逐行结算的呈现规则 | [battle.md](../../ux/battle.md) 与 [HUD 框架](HUD框架.md),本文只引用 |
|
||||
| 逐行结算的呈现规则 | [battle.md](../../../ux/battle.md) 与 [HUD 框架](HUD框架.md),本文只引用 |
|
||||
| 减动效开启后动画怎么演 | [战斗表现](../战斗循环/战斗表现.md)——本文提供开关,动画侧兑现归对方 |
|
||||
| 音量调节对声音怎么生效 | [音频](音频.md)——生效侧归对方,口径已对齐 |
|
||||
| 文案内容本体(教学条目、界面用语等写了什么) | 各系统文档——本文只做翻译生产,不改含义 |
|
||||
@@ -60,7 +60,7 @@
|
||||
|
||||
| 输入 | 登记处 | 将来要做的 |
|
||||
|---|---|---|
|
||||
| 减动效开关 | [HUD 框架](HUD框架.md)的可访问性基线与 [battle.md](../../ux/battle.md)的呈现承诺;[战斗表现](../战斗循环/战斗表现.md)已登记承接其动画侧 | 提供开关:开启后全部动画有时长减半或直达终态的通路。呈现侧的约束以 battle.md 与 HUD 框架为权威,本文不复述 |
|
||||
| 减动效开关 | [HUD 框架](HUD框架.md)的可访问性基线与 [battle.md](../../../ux/battle.md)的呈现承诺;[战斗表现](../战斗循环/战斗表现.md)已登记承接其动画侧 | 提供开关:开启后全部动画有时长减半或直达终态的通路。呈现侧的约束以 battle.md 与 HUD 框架为权威,本文不复述 |
|
||||
| 音量控制入口 | 归档对本系统的常规分工登记;[音频](音频.md)已登记同款分工 | 在设置里提供音量入口;生效侧归音频(口径已对齐) |
|
||||
| 键位映射表 | [HUD 框架](HUD框架.md)定稿登记的交付物输入(全键族) | 如何转化成设置项(比如是否允许改键)待设计 |
|
||||
| 结算风味词 | 归档概念层增补批随巢穴隐喻换轴登记("封印 / 镇 / 反制"一类类型风味词),只登记了存在、各归各的时机 | 内容与形态未定,本文只登记这条输入在 |
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
|
||||
### 解锁界面:双入口框架
|
||||
|
||||
(2026-09-18 拍板;入口拓扑=三入口化)营地消费面(消费钥石、查看与管理永久强化)经主菜单「进入存档」→选档界面→进档/新档进入,主菜单不再设营地独立钮;局内状态面板(游戏非实时无暂停语义)也可查看钥石余额与本局冒险信息(只读)。钥石余额在 HUD 常驻展示。呈现 spec:[ux/camp.md](../../ux/camp.md) v1.0(退役/封存件「已退役/已解锁·能力封存中」呈现语义随 spec 落位);视觉终稿已落码。本节锁框架与需求,呈现细节归 spec;局内状态面板入口已实装(实机目检待用户,待办清单登记条在册)。
|
||||
(2026-09-18 拍板;入口拓扑=三入口化)营地消费面(消费钥石、查看与管理永久强化)经主菜单「进入存档」→选档界面→进档/新档进入,主菜单不再设营地独立钮;局内状态面板(游戏非实时无暂停语义)也可查看钥石余额与本局冒险信息(只读)。钥石余额在 HUD 常驻展示。呈现 spec:[ux/camp.md](../../../ux/camp.md) v1.0(退役/封存件「已退役/已解锁·能力封存中」呈现语义随 spec 落位);视觉终稿已落码。本节锁框架与需求,呈现细节归 spec;局内状态面板入口已实装(实机目检待用户,待办清单登记条在册)。
|
||||
|
||||
### 单局循环零依赖:排期边界的推论
|
||||
|
||||
|
||||
@@ -69,9 +69,9 @@
|
||||
|
||||
| 界面 | 管什么(现状指证) | 与生涯的边界 |
|
||||
|---|---|---|
|
||||
| 营地([camp UX spec](../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树——营地场景图+五功能窗口形态);原底栏「最近冒险」条带已撤下(P6 甲),逐局历史回看唯一归生涯 | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带已撤(P6 甲) |
|
||||
| 营地([camp UX spec](../../../ux/camp.md)) | 跨局成长**操作**面(花钥石买强化 / 装配 / 点树——营地场景图+五功能窗口形态);原底栏「最近冒险」条带已撤下(P6 甲),逐局历史回看唯一归生涯 | 营地不改生涯只读定位;生涯不做任何购买入口,也不放「去买」按钮。营地条带已撤(P6 甲) |
|
||||
| 状态面板 | **局内**只读查看这一局进行中的钥石与本局冒险进度 | 状态面板看当局当前值,生涯看跨局累计与历史;局内无生涯入口(P4 甲) |
|
||||
| 结算屏([settlement-summary UX spec](../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
|
||||
| 结算屏([settlement-summary UX spec](../../../ux/settlement-summary.md)) | 冒险收束当下的入账明细——冒险收束时由结算屏呈现入账明细与钥石明细(复用终局面账行的呈现形态) | 收束当下的呈现保留不动;生涯承接的是收束之后的任意时刻回看 |
|
||||
| 帮助手册(Beta 批,未实装——教学内容归 Beta 帮助手册) | 讲「怎么玩」 | 生涯讲「我玩过什么」。图鉴条目文案与手册是否共用内容源,接口登记归 Beta 批一并裁 |
|
||||
| 图例面板 | 局内符号/徽标即时解码 | 不合并——图例是局内解码面,图鉴是跨局收集面 |
|
||||
|
||||
|
||||
+2
-1
@@ -111,7 +111,7 @@
|
||||
| 图类型 | 棋盘生成产物 `board_type` 的中文名——三关换皮的单位(同款地砖换色板) |
|
||||
| 方向稿 | 冻结前的方向性候选稿,供气质目检;过目检后才进机检冻结 |
|
||||
| 站桩 | 战斗画面区(B 区)里角色直立立绘的显示形态(配血条与状态行) |
|
||||
| 显示档 | 站桩立绘的显示尺寸规则——源用原稿分辨率入包(禁降采样),显示按三档绝对高(小型 96/中型 144/大型 188)缩放、宽随源比例;定值单点见[美术圣经](../art/art-bible.md) §8.2 站桩立绘显示档条 |
|
||||
| 显示档 | 站桩立绘的显示尺寸规则——源用原稿分辨率入包(禁降采样),显示按三档绝对高缩放、宽随源比例;定值单点见[美术圣经](../art/art-bible.md) §8.2 站桩立绘显示档条(档值定值面=站桩呈现守门测试) |
|
||||
| 徽章 | 库存条与结算明细用的小图标(与格面类型微光预读相区分) |
|
||||
|
||||
## 开发流程
|
||||
@@ -179,6 +179,7 @@
|
||||
| 罕见(档位名) | 稀有(2026-10-26 用户裁入表:旧三档档位名,现行四级第三档=稀有;散文形容词义不受限) |
|
||||
| 通关 | 关卡完成(单关)/冒险成功(整局)——2026-10-26 用户裁:主体明确短语族替代歧义单义词 |
|
||||
| 冒险终结 | 冒险落幕——2026-10-26 通关口径统一批 |
|
||||
| 集成测试 / 集成用例 / 集成层 / 集成侧 / 集成宿主 / 集成族 | 单元测试(2026-11-07 用户裁层级合并——跨系统真实路径闭环套件归单元层,三套件实体迁移 tests/unit/;测试层识别靠三元组〔职责×触发时间点×覆盖面积,权威表=test-conventions「测试层三元组」节〕,层级名只是代称;闭环套件的性质归属由各套件头注承载) |
|
||||
|
||||
## 对外命名纪律(玩家可见命名,2026-10-11 用户令)
|
||||
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
|
||||
- N = 棋盘长宽两边的较大者;格间距 2px;垂直预留 24px(上 12 / 下 12);悬崖厚度 8px;结构钳制区间 [26, 46]px。
|
||||
- 格边长可读性以用户验收的现行画面为锚点(2026-11-01 用户裁——现行渲染值即基准,基座档 14×14=28px 达标);全域不设人造的格边长可读阈值。结构钳制 [26, 46] 只管算式结果不越界;格边长接线守卫=生产写入值与上式单点算式的返回值严格相等,最大板型的推定读数仅留痕观察、不作判红依据。
|
||||
- N 的取值与每关板型的对应关系读[棋盘生成](../systems/排雷循环/棋盘生成.md),现行值的事实源是 `data/tables/board_gen.tres`。
|
||||
- N 的取值与每关板型的对应关系读[棋盘生成](../gdd/systems/排雷循环/棋盘生成.md),现行值的事实源是 `data/tables/board_gen.tres`。
|
||||
- 数字字号 = max(18, ceil(格边长 × 0.5)),像素字体整数倍缩放(canvas_items stretch 保证清晰)。
|
||||
|
||||
算例不入文档:棋盘区可用面积随布局表与板型变化,不是契约值——现算并绘在整屏样张上,本文件不复写。
|
||||
@@ -154,7 +154,7 @@
|
||||
| B | 主角站桩立绘 | 站桩(无框立绘+落影) | HP 条落底盘(2026-10-06 方案二拍板:站桩排式 = **立绘→血条→状态**,全场血条对齐一线;血条嵌石板位、台缘线留作地面线;不悬浮头顶——高盒下血条-立绘空洞,用户点名否决)、状态效果小图标一排挂血条正下方(形状+色相双通道、层数角标;空态不占位退化为空行)——**HP 与状态效果的唯一展示面(2026-09-26 信息归属拍板;悬停 tooltip〔来源+语义+剩余结算次数〕职责在本组件)**;攻击值等规划数据仍归 C 区数据框(2026-09-20 全站桩拍板,英雄卡退役) | 悬停状态小图标看 tooltip | HUD 视图拆分改造 |
|
||||
| B | 敌人站桩立绘 ×1–3 | 站桩(无框立绘+落影) | 血条+顺位徽标落底盘(同主角排式,2026-10-06 方案二;**大体档〔显示 >150px 级:drider/duergar_king〕允许向上溢出盒顶 N px(N 由实装实算,溢出判定与背景无关),血条仍落底**)、状态小图标挂血条正下方(敌方增益按个体呈现——各立绘只显示它自己名下的状态实例〔蓄力/硬化/削弱/暴走/警觉,2026-11-02 个体化裁〕;数据源 = 战斗结算流水经增益状态投影,敌人系统×4④;无行为环点〔proposal §2.4〕,意图占位 = 本拍摇定结果的动画占位——普通攻击零表示 / 特殊技类型级提示,**意图徽标悬浮头顶上方,探顶档压胸**,敌方增益〔蓄力/硬化〕入状态图标行);死亡灰化加碎裂退场直接演在立绘上(2026-09-20 全站桩拍板,敌人卡退役);血条下名字行 = 纯名字 + 护甲类型图标(形状+色相双通道,与状态图标同行共存,状态空时为纯名字),AC 数值不展示——2026-09-26 终裁(用户口径:HP 信息已足够,攻防数值不直接写上;护甲类型图标保留克制对策可读性——类型可读、数值隐藏) | 只读 | 新建 |
|
||||
| B | 轮次·阶段指示 | 标签 | "第 N 轮 · 挖掘中/结算中"(终局定格「战斗结束」;Boss 变体附 Boss 名) | 只读 | 新建 |
|
||||
| B | 结算明细带 | 滚动列表 | 结算期逐行流水([战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 9 类行协议);挖掘期收起为上轮摘要 1 行 | 无明细回看——带保留单行摘要,冒险中回看归「生涯」 | 改造 |
|
||||
| B | 结算明细带 | 滚动列表 | 结算期逐行流水([战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 9 类行协议);挖掘期收起为上轮摘要 1 行 | 无明细回看——带保留单行摘要,冒险中回看归「生涯」 | 改造 |
|
||||
| B | 结算明细回看(已退役) | —— | 冒险中回看归「生涯」;回看覆盖层为未接线遗留件(待办清单登记) | —— | 未接线 |
|
||||
| C | 攻击数据框 | 直角细线数据框 | 攻击值 + 修正来源角标 + 护甲 AC(AC 二轮拍板 2026-09-26 补入——我方 AC 唯一展示面;HP 与状态归 B 区站桩、RAP 归 D 区 AP 条,不在本组件) | 只读 | HUD 视图拆分 |
|
||||
| C | 秘宝槽 | 图标槽 ×4 | 四槽壳在役、空槽占位(标题「秘宝」,悬停「秘宝槽(空)」);局内两步使用与关卡域库存随秘宝过渡形态退役——现行秘宝 = 营地同调装配件(秘宝.md),本区展示重接归装配面 | 悬停 tooltip | 已实装 |
|
||||
@@ -282,7 +282,7 @@
|
||||
| 弃图刷新(挖掘期,非轮出口) · 刷新尝试遥测 | 刷新使用(难度曲线与Boss OQ6)与行动经济 OQ3 预警阈值观测;每轮 0–2 次(布局决策 4 cap=2) | {受理结果: 受理 / 拒因 RAP 不足 / 拒因触上限, 本轮第几次刷新: 1..2} |
|
||||
| 拒绝/voided · 拒绝遥测 | 拒绝来源(板面/资源/相位三源)与细分原因 | {拒绝来源: 板面/资源/相位, 细分原因} |
|
||||
| 提示触发/关闭 · 提示展示遥测 | 提示类型 | {提示类型: chord 提示 / 无相邻可挖格} |
|
||||
| 呈现订阅(不改核心) | 排雷玩法三事件 / [战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行协议,含终局判定行) / 战斗结束 / 关卡完成 | 载荷 schema 成文义务见 §Data Requirements(HUD框架硬性要求) |
|
||||
| 呈现订阅(不改核心) | 排雷玩法三事件 / [战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行协议,含终局判定行) / 战斗结束 / 关卡完成 | 载荷 schema 成文义务见 §Data Requirements(HUD框架硬性要求) |
|
||||
|
||||
**持久状态写入**全部经组合根唯一路径(意图批次提交 / 刷新意图提交 / 关卡推进)——UI 层禁直调核心写(架构七原则"依赖单向组合根")。
|
||||
|
||||
@@ -313,7 +313,7 @@
|
||||
| BFS 移动路径 | 排雷玩法 路径规划只读面 | Read | 布局决策 3 合法性指示消费 |
|
||||
| 拒绝原因 | 排雷玩法 细分原因封闭枚举 + 行动经济 资源面 | Read | 三源(板面/资源/相位)映射文案——呈现层契约成文;资源面原因无上游载荷(行动经济仅回受理与否,细分原因仅调试诊断可见),客户端按动作上下文推断 |
|
||||
| AP / RAP / 本轮刷新计数 | 行动经济 | Read | 常显;刷新入口可用性 = RAP 读数客户端预判(RAP≥8)+ 刷新意图提交的受理回执(行动经济 无刷新可用性预查询——仅受理与否的回执 + 只读观测量;行动经济 EC13 拒绝合法可见) |
|
||||
| 结算流水行(9 类封闭协议) | [战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 流水协议 | Read | 逐行来源标注(P2);未生效类行(奖励未生效)聚合计数呈现 |
|
||||
| 结算流水行(9 类封闭协议) | [战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋) 流水协议 | Read | 逐行来源标注(P2);未生效类行(奖励未生效)聚合计数呈现 |
|
||||
| 判定胜负 | 战斗结算 判定段 | Read | 终局画面数据锚 |
|
||||
| 敌单位 生命/顺位/摇定动作(技能标识·类型·名称)/敌方增益/敌单位标识/存活 | 敌人系统 只读面(经战斗结算呈现快照) | Read | 意图线索映射全局恒定(敌人系统×4②,技能池批三档口径);**销毁前捕获**(缺陷②教训) |
|
||||
| 状态效果投影(含空态/极性/溢出) | 增益状态 投影口径 | Read | 极性映射数据源 = 战斗结算效果表语义,非幅度符号(增益状态×③) |
|
||||
@@ -332,7 +332,7 @@
|
||||
|
||||
## Accessibility
|
||||
|
||||
无障碍基线权威 = [HUD框架](../systems/表现与支撑/HUD框架.md)「可访问性基线」节;不再另立专项文档(2026-09-27 拍板,见 OQ2)。本 spec 内建基线承诺(呈现侧落地):
|
||||
无障碍基线权威 = [HUD框架](../gdd/systems/表现与支撑/HUD框架.md)「可访问性基线」节;不再另立专项文档(2026-09-27 拍板,见 OQ2)。本 spec 内建基线承诺(呈现侧落地):
|
||||
|
||||
- **对比**:全部格底/面板底对数字墨 ≥4.5:1(WCAG AA)——art-bible §4.1 硬指标已可测(#33261B/#C9AF83 = 6.9:1 实测);UI 数据一律墨族素面
|
||||
- **色弱冗余**:无任何信息仅靠色相承载——格态靠完整/碎裂形;奖励金 vs 危险红靠剪影+运动;UI 红/金强调对加图标+文案冗余;Boss 靠形状独占(art-bible §4.5 清单逐项成立)
|
||||
@@ -397,8 +397,8 @@
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Player journey map——已完成(2026-09-27 拍板:不建)**:实现前补建的前提随战斗屏实装失效;情绪曲线事实源 = art-bible §2 能量档,真人体验观测与结论回流 = [体验验证清单](../../../体验验证清单.md)
|
||||
2. **无障碍专项文档——已完成(2026-09-27 拍板:不另立 design/accessibility-requirements.md)**:基线(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体)权威 = [HUD框架](../systems/表现与支撑/HUD框架.md)「可访问性基线」节;正式分层(文本缩放/色盲模式/按键重映射)归 [设置与本地化](../systems/表现与支撑/设置与本地化.md) 待设计问题清单 #1(设置项全集裁决时定);本 spec §Accessibility 为呈现侧落地承诺
|
||||
1. **Player journey map——已完成(2026-09-27 拍板:不建)**:实现前补建的前提随战斗屏实装失效;情绪曲线事实源 = art-bible §2 能量档,真人体验观测与结论回流 = [体验验证清单](../../体验验证清单.md)
|
||||
2. **无障碍专项文档——已完成(2026-09-27 拍板:不另立 design/accessibility-requirements.md)**:基线(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体)权威 = [HUD框架](../gdd/systems/表现与支撑/HUD框架.md)「可访问性基线」节;正式分层(文本缩放/色盲模式/按键重映射)归 [设置与本地化](../gdd/systems/表现与支撑/设置与本地化.md) 待设计问题清单 #1(设置项全集裁决时定);本 spec §Accessibility 为呈现侧落地承诺
|
||||
3. **interaction-patterns.md——已建成**:本 spec 引入的四 pattern(双源拒绝反馈/逐行兑现明细带/悬停预承诺光标/点阵资源显示)均已入库成文(pattern 1/3/4/5)
|
||||
4. **战斗表现美术测试**(game-concept 已登记,战斗表现前强制):本 spec 的常驻双舞台分屏(布局决策 1)是测试的布局输入——**敌人卡形态已裁决(2026-09-20 用户看对照样张拍板):全站桩式(无框立绘+底座),主角同样站桩、状态小图标挂血条下,卡片式整体退役**;站桩式不改变分区结构。对照样张已存档(敌人卡与站桩式对照样张)
|
||||
5. **移动动画形态**(布局决策 3):≤150ms 短滑为推荐值,playtest 观测「移动太瞬/太拖」反馈后调
|
||||
@@ -1,8 +1,8 @@
|
||||
# 营地场景 UX spec(camp)
|
||||
|
||||
> 状态:v2.8(现行)
|
||||
> 事实源:本 spec(呈现)+ [元进度](../systems/跨局循环/元进度.md)(机制)
|
||||
> 美术画法:[art-bible](../../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../../art/asset-path-map.md)
|
||||
> 事实源:本 spec(呈现)+ [元进度](../gdd/systems/跨局循环/元进度.md)(机制)
|
||||
> 美术画法:[art-bible](../art/art-bible.md) §6.2/§7.5;素材命名与路径:[asset-path-map](../art/asset-path-map.md)
|
||||
> 相邻:[level-map.md](level-map.md)(主菜单语境)
|
||||
|
||||
## 定位
|
||||
@@ -26,9 +26,9 @@
|
||||
|
||||
**场景按要素分层(10 张)**:远景天空/远山/整片地面/门洞内的小路合成 `scene_camp_bg` 背景层(z0,不透明全幅);中景木围墙独立成 `scene_camp_wall` 围墙层(z1,透明底,门洞处留透明缺口);其余八个物件层各一张、由上往下叠。门洞是围墙层的透明缺口,透出背景层画的**一条通向远处的小路**,不可填死。
|
||||
|
||||
**地平线在终稿 y367**(自画面底部往上约 46%),其上是天空与远山、其下整片地面;**中景围墙带 y203–367**,桩身 150、门柱 164;近景物件允许压住围墙(围墙不可点,z 序在近景物件之下),**唯一例外=中间门洞必须留空、不被任何物件遮挡**。围墙形态、门扇比例与帐篷尺寸的现行口径归 [art-bible](../../art/art-bible.md) §6.2,本 spec 不再复述。
|
||||
**地平线在终稿 y367**(自画面底部往上约 46%),其上是天空与远山、其下整片地面;**中景围墙带 y203–367**,桩身 150、门柱 164;近景物件允许压住围墙(围墙不可点,z 序在近景物件之下),**唯一例外=中间门洞必须留空、不被任何物件遮挡**。围墙形态、门扇比例与帐篷尺寸的现行口径归 [art-bible](../art/art-bible.md) §6.2,本 spec 不再复述。
|
||||
|
||||
坐标草案与分层验收判据见 [camp-scene-prompts](../../art/camp-scene-prompts.md) §2.3/§4(10 张口径现行事实源);2026-10-22 美术方案讨论沿革查 git 提交史。
|
||||
坐标草案与分层验收判据见 [camp-scene-prompts](../art/camp-scene-prompts.md) §2.3/§4(10 张口径现行事实源);2026-10-22 美术方案讨论沿革查 git 提交史。
|
||||
|
||||
## 点选反馈(根态)
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
|
||||
## 顶栏
|
||||
|
||||
钥石余额常驻画面顶部一行小 HUD 栏:高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**。栏体底=营地头带深棕 `#6E4526`(token `CAMP_HEADER_BAND`,色值出处 [art-bible](../../art/art-bible.md) §7.2 五 token 对齐表)× **不透明度 0.80**——半透明系定值,底下画面透出 20%。栏内**只放钥石徽章一件**,右位、顶格落座(徽章自高 48px=栏高,无顶距可言),左位不放件。验收:目检〈顶栏成一条可辨窄带、徽章落在带内〉+机检〈栏体呈色对其下方落位带底色对比 ≥3:1〉。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。
|
||||
钥石余额常驻画面顶部一行小 HUD 栏:高 48px、全宽窄条、**浮层覆盖在场景图上层,不挤压画面**。栏体底=营地头带深棕 `#6E4526`(token `CAMP_HEADER_BAND`,色值出处 [art-bible](../art/art-bible.md) §7.2 五 token 对齐表)× **不透明度 0.80**——半透明系定值,底下画面透出 20%。栏内**只放钥石徽章一件**,右位、顶格落座(徽章自高 48px=栏高,无顶距可言),左位不放件。验收:目检〈顶栏成一条可辨窄带、徽章落在带内〉+机检〈栏体呈色对其下方落位带底色对比 ≥3:1〉。刷新入口已实装=营地刷新单口(进档与营地内写口尾触发,现开界面与顶栏余额同步取现值)。
|
||||
|
||||
## 叠层与开合
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
> **Author**: user + ux-designer
|
||||
> **Last Updated**: 2026-10-29
|
||||
> **Template**: Interaction Pattern Library
|
||||
> **来源**: `design/gdd/ux/battle.md` 已拍板内容的成文固化——本库不引入新设计决策
|
||||
> **来源**: `design/ux/battle.md` 已拍板内容的成文固化——本库不引入新设计决策
|
||||
|
||||
---
|
||||
|
||||
@@ -84,7 +84,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
|
||||
**Description**: 轮末结算逐行流水实时呈现——「扫雷之因逐行兑现为战果」(P2 归因)的承载面。挖掘期收起为 1 行上轮摘要;结算期展开为实时流(带高读 `log_band_h_settle`)。
|
||||
|
||||
**Specification**:
|
||||
- 数据源:[战斗结算](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败**
|
||||
- 数据源:[战斗结算](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)步骤流(9 类行封闭协议(现行);公共字段:来源类别 / 来源格 / 轮次 / 显示名 / 数值 / 可空高光标记);**无来源行 = 断言失败**
|
||||
- 行模板:「{行为} {值} → {目标}(来源)」——来源标注(第 N 轮 · 安全格式);行文案最长 token = 来源标签(4 汉字封闭枚举),模板预留 40% 膨胀宽度
|
||||
- 聚合纪律:未生效类行(奖励未生效——现行 9 类行协议唯一未生效型)按聚合计数呈现(防逐行惩罚轰炸);衰减行聚合计数;效果行数值 = 单次贡献值、禁与池合成值相加呈现;零值行可见不吞(免伤轮归因不断点)
|
||||
- 高亮:高亮标记(封印标中、清图奖励、完美破除) → 金框强调;完美封印 = 独立高光行 + 全屏喝彩级反馈。「完美破除」的判据句读作「本板首次标错受检」,不等于完美资格已失——专长「完美保险」可在本场首次标错时豁免,实际资格按有效误标(误标总数 − 豁免数)判定
|
||||
@@ -92,7 +92,7 @@ MVP 未使用标准控件(不入库):toggle / dropdown / input field / tab
|
||||
- 击杀标记呈现(H3 数据源 = 战斗结算流水击杀结果字段)
|
||||
|
||||
**When to Use**: 任何「因果可归因」的逐行流水呈现
|
||||
**When NOT to Use**: 自造行类型/字段(9 类封闭枚举(现行)——增删 = 接口漂移,须走[《战斗结算》](../systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)修订)
|
||||
**When NOT to Use**: 自造行类型/字段(9 类封闭枚举(现行)——增删 = 接口漂移,须走[《战斗结算》](../gdd/systems/战斗循环/战斗结算.md#明细流水每一行都能指回你刚才的哪一步棋)修订)
|
||||
|
||||
**Reference**: battle.md §Component Inventory 结算明细带、§AC 信息与归因;归档《战斗结算》(b1-settlement.md)Core Rule 14 / Visual Req
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
## 现行载体与目标形态(重要边界)
|
||||
|
||||
本屏**没有独立新屏**:现行与 MVP 目标载体都是关卡收束时刻的覆盖层反馈([HUD框架](../systems/表现与支撑/HUD框架.md) P1 行「相邻域屏完整屏形态:MVP 以覆盖层反馈承载」)。本 spec 锁义务与数据锚,不锁视觉形态;完整屏形态待 P1 屏实装时定。
|
||||
本屏**没有独立新屏**:现行与 MVP 目标载体都是关卡收束时刻的覆盖层反馈([HUD框架](../gdd/systems/表现与支撑/HUD框架.md) P1 行「相邻域屏完整屏形态:MVP 以覆盖层反馈承载」)。本 spec 锁义务与数据锚,不锁视觉形态;完整屏形态待 P1 屏实装时定。
|
||||
|
||||
| 时刻 | 载体(目标形态) | 呈现内容 |
|
||||
|---|---|---|
|
||||
@@ -93,7 +93,7 @@
|
||||
|
||||
## Accessibility
|
||||
|
||||
沿用 [HUD框架](../systems/表现与支撑/HUD框架.md)「可访问性基线」节全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体)——本屏无新增承诺。结算数字是本屏信息主载体,遵守同一字号下限与对比硬指标。
|
||||
沿用 [HUD框架](../gdd/systems/表现与支撑/HUD框架.md)「可访问性基线」节全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体)——本屏无新增承诺。结算数字是本屏信息主载体,遵守同一字号下限与对比硬指标。
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
> **Template**: UX Spec
|
||||
> **覆盖范围**: 独立屏形态(关卡停点全景)· 入口停点 · 岔路停点 · 单链停点 · 放弃冒险入口
|
||||
> **不覆盖**: 节点覆盖层(NodeOverlay——奖励/恢复兑付反馈,spec:[node-reward.md](node-reward.md))、商店覆盖层(spec:[shop.md](shop.md))、关卡结算屏([level-clear.md](level-clear.md))、战斗屏([battle.md](battle.md))
|
||||
> **机制权威**: [关卡节点地图](../systems/冒险循环/关卡节点地图.md)(选路、兑付、停点语义);图的来源 = [关卡节点图生成](../systems/冒险循环/关卡节点图生成.md)(参数化长图——只换图的来源与形态,不换本屏的呈现语义;多终点时各终点均带 Boss 标记,标记语义不变、数量随图;图形态随参数变化,布局自适应,形态不进呈现代码);**系统契约**: [HUD框架](../systems/表现与支撑/HUD框架.md)(订阅纪律、模态层级、可访问性基线)
|
||||
> **机制权威**: [关卡节点地图](../gdd/systems/冒险循环/关卡节点地图.md)(选路、兑付、停点语义);图的来源 = [关卡节点图生成](../gdd/systems/冒险循环/关卡节点图生成.md)(参数化长图——只换图的来源与形态,不换本屏的呈现语义;多终点时各终点均带 Boss 标记,标记语义不变、数量随图;图形态随参数变化,布局自适应,形态不进呈现代码);**系统契约**: [HUD框架](../gdd/systems/表现与支撑/HUD框架.md)(订阅纪律、模态层级、可访问性基线)
|
||||
> **生成图随动登记**:入口停点候选数=入口个数(1 或多,多入口并列呈现,首跳即选从哪个入口进场);节点数字=图内句柄 id(非节点深度、非数组序);生成图深度域 L3 上探至 0–13——卷轴视口范式(布局决策 5)下画布随图生长、屏为窗口,深度不再受屏预算约束(2026-11-05 用户裁:滚动视口案落地、等比缩放案否决;原「超预算」挂账闭合)。
|
||||
|
||||
---
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
- **到达时机**:关卡开始即出图(停点前移——入口不自动进入,第一次选路就是「出发」);或每次兑付确认后返回(战后回图、奖励/恢复反馈确认后回图、商店覆盖层离开后回图)
|
||||
- **到达前刚在做**:上一场战斗的结算画面,或奖励/恢复/商店覆盖层的确认动作
|
||||
- **情绪状态**:谋定后动——[art-bible](../../art/art-bible.md) §2.7 下探过渡的「纵深的期待」(能量 4/10);画面应安静可读,不给选路决策添噪
|
||||
- **情绪状态**:谋定后动——[art-bible](../art/art-bible.md) §2.7 下探过渡的「纵深的期待」(能量 4/10);画面应安静可读,不给选路决策添噪
|
||||
- **到达方式**:游戏推进送入(屏级切换——独立屏激活期战斗屏整体隐藏)
|
||||
|
||||
## Navigation Position
|
||||
@@ -52,7 +52,7 @@
|
||||
|
||||
本屏必须传达的全部信息,按注视优先级分六层:
|
||||
|
||||
1. **节点图本体**(第一注视点):节点方块(五类型字面:战/赏/疗/王/商)、通道连线、当前位置、可选去路;类型可见性=邻近 N 层内显示类型,范围外只见结构(机制见[关卡节点地图](../systems/冒险循环/关卡节点地图.md))
|
||||
1. **节点图本体**(第一注视点):节点方块(五类型字面:战/赏/疗/王/商)、通道连线、当前位置、可选去路;类型可见性=邻近 N 层内显示类型,范围外只见结构(机制见[关卡节点地图](../gdd/systems/冒险循环/关卡节点地图.md))
|
||||
2. **候选选路口**:亮金候选通道 + 候选节点亮金描边——「下一步能去哪」的图上双冗余(2026-09-27 拍板:候选按钮退役,图上点击为唯一提交通道)
|
||||
3. **常驻语境**(顶栏):关卡名(第 N 层·名称)、GP 常驻总量(2026-09-26 二轮拍板:GP = 关卡画面常驻总量)
|
||||
4. **状态展示**(底栏,2026-09-27 拍板):生命(当前/上限)、属性(攻击 / 护甲 AC 有效值)、本关已选加成(统一冒险加算区增量)——选路规划原料常读
|
||||
@@ -233,7 +233,7 @@
|
||||
|
||||
## Accessibility
|
||||
|
||||
无障碍基线权威 = [HUD框架](../systems/表现与支撑/HUD框架.md)「可访问性基线」节,本屏无新增豁免。呈现侧内建承诺:
|
||||
无障碍基线权威 = [HUD框架](../gdd/systems/表现与支撑/HUD框架.md)「可访问性基线」节,本屏无新增豁免。呈现侧内建承诺:
|
||||
|
||||
- **节点态形状冗余**(全局语义红线「无任何信息仅靠色相承载」的合规项):已过 = 暗金无描边、当前 = 金填充 + 唯一焦点方块、候选 = 亮金粗描边、未来 = 暗底无描边;Boss = 紫赤描边 + 外缘 1px 深墨环(**灰度冗余通道**——紫赤灰度对石纹噪底不可辨)+ 图标冗余。四态在灰度截图下可分类(AC,样张 L2 断言承载)
|
||||
- **对比**:节点图标对方块底(双色墨制:暗底态暖白/当前金底深墨,各态 ≥4.5:1)、顶栏数字对底 ≥4.5:1(样张 L3 断言承载);色值取 art-bible §4.1 冻结色板(已过暗金 = GOLD_DONE #68562C 冻结新值)
|
||||
@@ -295,12 +295,12 @@
|
||||
|
||||
| 义务源 | 义务 | 本 spec 承载落点 |
|
||||
|---|---|---|
|
||||
| [关卡节点地图](../systems/冒险循环/关卡节点地图.md) | 呈现侧口径:方块地牢观感、节点态四色、不另设进度条 | Information Hierarchy 第 5 层 + 图区组件行 |
|
||||
| [关卡节点地图](../gdd/systems/冒险循环/关卡节点地图.md) | 呈现侧口径:方块地牢观感、节点态四色、不另设进度条 | Information Hierarchy 第 5 层 + 图区组件行 |
|
||||
| 关卡节点地图 | 零自动推进、兑付后确认、选路只读 | Interaction Map + Data Requirements + AC |
|
||||
| 关卡节点地图 | 呈现只读(节点图/当前位置/可选下一跳对呈现层只读可见) | Data Requirements 只读出口清单 |
|
||||
| [HUD框架](../systems/表现与支撑/HUD框架.md) | 模态 overlay 层级纪律、订阅纪律(推进信号两线之一) | Navigation Position + Data Requirements 架构注记 |
|
||||
| [HUD框架](../gdd/systems/表现与支撑/HUD框架.md) | 模态 overlay 层级纪律、订阅纪律(推进信号两线之一) | Navigation Position + Data Requirements 架构注记 |
|
||||
| HUD框架 | 可访问性基线全量(对比/色弱/字号/闪烁/减动效/纯鼠标/中文字体) | Accessibility |
|
||||
| [battle.md](battle.md) OQ14 二轮拍板 | GP = 关卡画面常驻总量 | 顶栏 GP 常驻徽章 |
|
||||
| [art-bible](../../art/art-bible.md) | 方块地牢观感(§3.2 圆角方形世界侧)、色板冻结(§4.1)、半拟物(§7.1)、明度分层(§2) | 图区组件 + Accessibility 对比条 |
|
||||
| [art-bible](../art/art-bible.md) | 方块地牢观感(§3.2 圆角方形世界侧)、色板冻结(§4.1)、半拟物(§7.1)、明度分层(§2) | 图区组件 + Accessibility 对比条 |
|
||||
|
||||
---
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 状态:v3.0(现行)
|
||||
> 事实源:本 spec(呈现)+ 三选一覆盖层(共用呈现件)+ 头部行文案(旁白条目)
|
||||
> 相邻:[level-clear](level-clear.md)(关卡完成三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../systems/跨局循环/秘宝.md)(道具件内容权威——局外同调件,不再入三选一牌库)/ [冒险结构](../systems/冒险循环/冒险结构.md)(共享池令牌库账本)
|
||||
> 相邻:[level-clear](level-clear.md)(关卡完成三选一面——同一覆盖层的另一上下文)/ [level-map](level-map.md)(关卡画面)/ [秘宝](../gdd/systems/跨局循环/秘宝.md)(道具件内容权威——局外同调件,不再入三选一牌库)/ [冒险结构](../gdd/systems/冒险循环/冒险结构.md)(共享池令牌库账本)
|
||||
|
||||
## 定位
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
- 载体 = 提升三选一覆盖层(与 [level-clear](level-clear.md) 共用组件——零新增交互原语)。
|
||||
- 头部行:「关卡奖励点 / 从冒险牌库抽三选一(属性值提升与补给;与通关专长三选一同库分流):选择一件带走:」(旁白条目)。
|
||||
- 三卡点选(✓ 高亮,确认前可改选)→「确认选择」→ 兑付反馈(选中件效果 + 丢弃提示)→「继续」→ 回图选路(零自动推进同款)。
|
||||
- **三选一刷新**(X/Y 双源;机制现行事实源=[秘宝](../systems/跨局循环/秘宝.md)「三选一刷新双源」节,X 源承载件=现行目录「刷新练习」):装配「三选一刷新件」给 X 次(每次碰三选一重给、优先消耗、不结转)+ 营地「刷新升级」给 Y 次(每冒险开局注入冒险周期池、用完即无);刷新钮余量 >0 时现形,点按重抽三候选(奖励抽取既有洗牌流,池规则全保留:不放回/达上限剔牌/掷定前置直显/停点可中断)。自动演示默认不刷(钓鱼归玩家)。
|
||||
- **三选一刷新**(X/Y 双源;机制现行事实源=[秘宝](../gdd/systems/跨局循环/秘宝.md)「三选一刷新双源」节,X 源承载件=现行目录「刷新练习」):装配「三选一刷新件」给 X 次(每次碰三选一重给、优先消耗、不结转)+ 营地「刷新升级」给 Y 次(每冒险开局注入冒险周期池、用完即无);刷新钮余量 >0 时现形,点按重抽三候选(奖励抽取既有洗牌流,池规则全保留:不放回/达上限剔牌/掷定前置直显/停点可中断)。自动演示默认不刷(钓鱼归玩家)。
|
||||
|
||||
## 口径
|
||||
|
||||
- **同库同池耗**:池=固定配额令牌库(档位=四级框架 普通/优秀/稀有/传说,传说=头奖位留空〔稀有档现投放为行动点件〕;各 kind 现行配额读 `game/data/tables/adventure.tres`);洗牌不放回抽 3(**同 kind 去重每 kind 至多一枚**——「三种不同选项」体验延续,kind 出现率∝剩余令牌数)、选中踢池未选回池、池耗即枯竭(余库不足出齐剩余,空库=零候选合法态直通);**种类次数上限退役**(池消耗即上限)。数值对:小GP 20/大GP 50、小增伤 +1/大 +2、小护甲 +1/大 +2、小生命 +5/大 +10、恢复 +6 当即回血(封顶生效上限)、AP +1。**MC 策略 316-f**:1 级优先度=最稀有(legendary>rare>excellent>common,头奖位现行留空)、2 级=最左(并列同档取最左)。配额结构锚与档位构成读冒险表(结构校验归实现侧)。商店货架=同库付费渠道(令牌级货架,买一件=奖励面少一件),库存=剩余令牌数,账本语义与切边注记归[冒险结构](../systems/冒险循环/冒险结构.md)共享池账本节。
|
||||
- **同库同池耗**:池=固定配额令牌库(档位=四级框架 普通/优秀/稀有/传说,传说=头奖位留空〔稀有档现投放为行动点件〕;各 kind 现行配额读 `game/data/tables/adventure.tres`);洗牌不放回抽 3(**同 kind 去重每 kind 至多一枚**——「三种不同选项」体验延续,kind 出现率∝剩余令牌数)、选中踢池未选回池、池耗即枯竭(余库不足出齐剩余,空库=零候选合法态直通);**种类次数上限退役**(池消耗即上限)。数值对:小GP 20/大GP 50、小增伤 +1/大 +2、小护甲 +1/大 +2、小生命 +5/大 +10、恢复 +6 当即回血(封顶生效上限)、AP +1。**MC 策略 316-f**:1 级优先度=最稀有(legendary>rare>excellent>common,头奖位现行留空)、2 级=最左(并列同档取最左)。配额结构锚与档位构成读冒险表(结构校验归实现侧)。商店货架=同库付费渠道(令牌级货架,买一件=奖励面少一件),库存=剩余令牌数,账本语义与切边注记归[冒险结构](../gdd/systems/冒险循环/冒险结构.md)共享池账本节。
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
- 触发:主角倒下(判负)/ 末关完成 / 玩家放弃——三结局同面(由冒险收束信号统一触发)。
|
||||
- 载体:与战斗面同一个战斗结束覆盖层——末关关卡完成走胜利态开屏(金蜡封 +「本局 GP 入账 · 合计 N」行,GP 收束清零前的呈现侧影子账);倒下 / 放弃走失败态开屏(暮蓝)。无独立「冒险终局面板」实体。
|
||||
- 呈现内容:冒险摘要五件事(定义归[冒险结构](../systems/冒险循环/冒险结构.md))+ 钥石写入明细(= 本局逐杀 Boss 掉落合计;消费归[元进度](../systems/跨局循环/元进度.md) 的冒险结算入账——大奖励面不产钥石)+ GP 合计行。
|
||||
- 呈现内容:冒险摘要五件事(定义归[冒险结构](../gdd/systems/冒险循环/冒险结构.md))+ 钥石写入明细(= 本局逐杀 Boss 掉落合计;消费归[元进度](../gdd/systems/跨局循环/元进度.md) 的冒险结算入账——大奖励面不产钥石)+ GP 合计行。
|
||||
- 出口:确认 → 回存档首页。
|
||||
|
||||
## 口径
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 状态:v1.2(现行)
|
||||
> 事实源:本 spec(呈现)+ 商店覆盖层(呈现件)
|
||||
> 机制权威:[关卡节点地图](../systems/冒险循环/关卡节点地图.md)(商店房编排:每关恰 1/Boss 前一位必经/兑付触发);定价数值归[难度曲线与Boss](../systems/冒险循环/难度曲线与Boss.md)(shop.tres,provisional)
|
||||
> 机制权威:[关卡节点地图](../gdd/systems/冒险循环/关卡节点地图.md)(商店房编排:每关恰 1/Boss 前一位必经/兑付触发);定价数值归[难度曲线与Boss](../gdd/systems/冒险循环/难度曲线与Boss.md)(shop.tres,provisional)
|
||||
|
||||
## 定位
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
|
||||
## 口径
|
||||
|
||||
- GP 余额读数 = runner 钱包投影(GP 归[冒险结构](../systems/冒险循环/冒险结构.md) GP 节);本层不作计算。
|
||||
- GP 余额读数 = runner 钱包投影(GP 归[冒险结构](../gdd/systems/冒险循环/冒险结构.md) GP 节);本层不作计算。
|
||||
- 与关卡画面 GP 常驻徽章的关系:进店语境余额与选路常驻总量不同义(本层带购买语境),不构成重复展示——钥石「写入/存量」拆名逻辑同款。
|
||||
- 购买确认交互:现行直接购买(无二次确认步);是否需破坏性确认异位(pattern 7)未裁决,本 spec 只登记现状不立新裁。
|
||||
@@ -191,7 +191,7 @@ func validate_skill_pool_links(skill_table: SkillTable, enemy_table: EnemyTable,
|
||||
for row in enemy_table.rows:
|
||||
var eid: String = String(row.get("enemy_id", ""))
|
||||
var pool_n := {"basic": 0, "buff": 0, "debuff": 0, "uncond_special": 0}
|
||||
for entry in row.get("pool", []):
|
||||
for entry in enemy_table.effective_pool(row): # 合并池(elite=基础池+附加技;battle/boss=自身 pool——池构成纪律校验合并后的池)
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if not skill_types.has(sid):
|
||||
errs.append({"category": "dangling_skill", "message": "EnemyTable %s 池引用不存在的 skill_id: %s(悬空致命)" % [eid, sid]})
|
||||
|
||||
@@ -38,7 +38,7 @@ func resolve_enemy_rowset(L: int, D: int, row_kind: String) -> Array[Dictionary]
|
||||
if L < 1 or D < 0:
|
||||
_diag.call("fatal", "request_oob", "enemy 请求越域 (%d,%d)——进度坐标低于合法域(关卡序号≥1、节点深度≥0)" % [L, D])
|
||||
return []
|
||||
if not EnemyTable.ROW_KINDS.has(row_kind):
|
||||
if not EnemyTable.ENCOUNTER_ROW_KINDS.has(row_kind): # 开战寻址行类门——elite 行静置合法、任何寻址拿不到它(敌人系统.md:151)
|
||||
_diag.call("fatal", "request_oob", "enemy 请求行类非法 (%d,%d,\"%s\")——row_kind 域 {battle, boss}" % [L, D, row_kind])
|
||||
return []
|
||||
var rows: Array[Dictionary] = []
|
||||
|
||||
@@ -8,8 +8,10 @@ extends Resource
|
||||
|
||||
@export var rows: Array[Dictionary] = [] # Array[Dictionary]
|
||||
|
||||
# 行类封闭枚举(对齐关卡节点 type 词表):battle = 战斗节点配池,boss = 终点行集
|
||||
const ROW_KINDS := ["battle", "boss"]
|
||||
# 行类封闭枚举:表内允许出现的行类(battle/boss 对齐关卡节点 type 词表,elite=精英行)
|
||||
const ROW_KINDS := ["battle", "boss", "elite"]
|
||||
# 开战寻址可读的行类——elite 接线前不在此列(敌人系统 v1.9 暂不接线;resolver 行类门单点消费)
|
||||
const ENCOUNTER_ROW_KINDS := ["battle", "boss"]
|
||||
|
||||
# 护甲类型封闭枚举(克制矩阵本体在 SettlementTable;
|
||||
# 语义键 {light, ward, heavy} = 轻甲 / 魔法护甲 / 重甲)
|
||||
@@ -49,6 +51,7 @@ func validate() -> Array:
|
||||
errs.append_array(_validate_row_numbers(row, eid2))
|
||||
errs.append_array(_validate_row_atk_range(row, eid2))
|
||||
errs.append_array(_validate_row_pool(row, eid2))
|
||||
errs.append_array(_validate_elite_fields(row, eid2))
|
||||
errs.append_array(_validate_row_types(row, eid2))
|
||||
# 行集构成纪律②(行序即契约,敌人系统 v0.15):同带键多行必须
|
||||
# 文件内相邻。迭代中记录上一行行集键;当前行集键曾出现过但 ≠ 上一行行集键 = 同带键行被隔断,报错。
|
||||
@@ -67,6 +70,7 @@ func validate() -> Array:
|
||||
boss_ok[L2] = true
|
||||
if not anchor_ok.has(L2):
|
||||
anchor_ok[L2] = false
|
||||
errs.append_array(_validate_elite_refs())
|
||||
errs.append_array(_validate_anchor_rows(anchor_ok, boss_ok))
|
||||
return errs
|
||||
|
||||
@@ -85,7 +89,7 @@ func _validate_row_base(row: Dictionary, seen: Dictionary) -> Array[Dictionary]:
|
||||
if L < 1 or band < 0:
|
||||
errs.append({"category": "domain", "message": "进度键越域 (%d,band %d)" % [L, band]})
|
||||
if not ROW_KINDS.has(rk):
|
||||
errs.append({"category": "domain", "message": "%s row_kind 未知枚举: \"%s\"(域 {battle, boss})" % [eid, rk]})
|
||||
errs.append({"category": "domain", "message": "%s row_kind 未知枚举: \"%s\"(域 {battle, boss, elite})" % [eid, rk]})
|
||||
if eid == "":
|
||||
errs.append({"category": "domain", "message": "enemy_id 空"})
|
||||
if String(row.get("display_name", "")) == "":
|
||||
@@ -136,25 +140,99 @@ func _validate_row_atk_range(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
## 非空池 + 条目 {skill_id 非空, weight > 0} + 池内 skill_id 不重复。
|
||||
## 普攻冷却恒 0(SkillTable schema 锁死)→ 可摇集合恒非空,"空池不可能"由此结构保证。
|
||||
func _validate_row_pool(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var no_errs: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
return no_errs # elite 行不带 pool 键(pool_extra 承载附加技——条目域校验随 _validate_elite_fields)
|
||||
var errs: Array[Dictionary] = []
|
||||
var pool: Array[Variant] = row.get("pool", [])
|
||||
if pool.is_empty():
|
||||
errs.append({"category": "domain", "message": "%s pool 空池(禁——技能池行必带池模板)" % eid})
|
||||
errs.append_array(_validate_pool_entries(pool, eid, "pool"))
|
||||
return errs
|
||||
|
||||
|
||||
## 池条目域共用校验(pool 与 pool_extra 共用——权重上限 9999 不出现第二份,契约 3 单点);
|
||||
## field 承载报错定位段("pool"=既有文案逐字不变,"pool_extra"=附加技清单定位)。
|
||||
func _validate_pool_entries(entries: Array, eid: String, field: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
var pool_ids := {}
|
||||
for entry_v in pool:
|
||||
for entry_v in entries:
|
||||
if not (entry_v is Dictionary):
|
||||
errs.append({"category": "domain", "message": "%s pool 条目非字典: %s" % [eid, str(entry_v)]})
|
||||
errs.append({"category": "domain", "message": "%s %s 条目非字典: %s" % [eid, field, str(entry_v)]})
|
||||
continue
|
||||
var entry: Dictionary[Variant, Variant] = entry_v
|
||||
var sid: String = String(entry.get("skill_id", ""))
|
||||
if sid == "":
|
||||
errs.append({"category": "domain", "message": "%s pool 条目 skill_id 空" % eid})
|
||||
errs.append({"category": "domain", "message": "%s %s 条目 skill_id 空" % [eid, field]})
|
||||
elif pool_ids.has(sid):
|
||||
errs.append({"category": "dup_key", "message": "%s pool 内 skill_id 重复: %s" % [eid, sid]})
|
||||
errs.append({"category": "dup_key", "message": "%s %s 内 skill_id 重复: %s" % [eid, field, sid]})
|
||||
pool_ids[sid] = true
|
||||
var w: int = int(entry.get("weight", 0))
|
||||
if w <= 0 or w > 9999:
|
||||
errs.append({"category": "domain", "message": "%s pool 权重越域 (0,9999]: %d" % [eid, w]})
|
||||
errs.append({"category": "domain", "message": "%s %s 权重越域 (0,9999]: %d" % [eid, field, w]})
|
||||
return errs
|
||||
|
||||
|
||||
## 精英行字段互锁(敌人系统 v1.9 :281/:282/:283):elite 行必备 base_ref/pool_extra、禁 pool、
|
||||
## enemy_id 不与 base_ref 同名;非 elite 行禁带 base_ref/pool_extra。base_ref 引用存在性归 _validate_elite_refs。
|
||||
func _validate_elite_fields(row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
var base_ref := String(row.get("base_ref", ""))
|
||||
if base_ref == "":
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 base_ref 缺或空(基础敌种引用必带)" % eid})
|
||||
if base_ref == eid:
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 base_ref 与自身 enemy_id 同名(禁)" % eid})
|
||||
if not row.has("pool_extra") or (row.get("pool_extra") as Array).is_empty():
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行 pool_extra 缺键或空(附加技清单非空必带)" % eid})
|
||||
if row.has("pool"):
|
||||
errs.append({"category": "cross_field", "message": "%s elite 行带 pool 键(禁——池走继承+附加)" % eid})
|
||||
if row.has("pool_extra") and (row.get("pool_extra") as Array).size() > 0:
|
||||
errs.append_array(_validate_pool_entries(row.get("pool_extra"), eid, "pool_extra"))
|
||||
else:
|
||||
if row.has("base_ref"):
|
||||
errs.append({"category": "cross_field", "message": "%s 非 elite 行带 base_ref 键(禁——基础敌种引用为 elite 行专属)" % eid})
|
||||
if row.has("pool_extra"):
|
||||
errs.append({"category": "cross_field", "message": "%s 非 elite 行带 pool_extra 键(禁——附加技清单为 elite 行专属)" % eid})
|
||||
return errs
|
||||
|
||||
|
||||
## 精英行跨行引用校验(敌人系统 v1.9 :148/:282):base_ref 指向同关 battle 行类敌种(缺/空已由
|
||||
## _validate_elite_fields 报——此处跳过);指向 elite 行判别同门 _min_band_row;附加技与基础池
|
||||
## skill_id 交集非空报 dup_key(敌人系统.md:149 附加技与基础池重复拒绝)。
|
||||
func _validate_elite_refs() -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
for row in rows:
|
||||
if String(row.get("row_kind", "")) != "elite":
|
||||
continue
|
||||
var base_ref := String(row.get("base_ref", ""))
|
||||
if base_ref == "":
|
||||
continue
|
||||
var level := int(row.get("level_index", 0))
|
||||
var eid := String(row.get("enemy_id", ""))
|
||||
var base := _min_band_row(level, base_ref, "battle")
|
||||
if base.is_empty():
|
||||
if not _min_band_row(level, base_ref, "elite").is_empty():
|
||||
errs.append({"category": "dangling_base", "message": "%s base_ref 指向 elite 行: %s(基础敌种须为 battle 行类)" % [eid, base_ref]})
|
||||
else:
|
||||
errs.append({"category": "dangling_base", "message": "%s base_ref 同关 battle 行类无此敌种: %s" % [eid, base_ref]})
|
||||
continue
|
||||
errs.append_array(_extra_overlap_errs(row, base, eid))
|
||||
return errs
|
||||
|
||||
|
||||
## 附加技与基础池重叠判(_validate_elite_refs 交集段提出——分支预算内聚)
|
||||
func _extra_overlap_errs(row: Dictionary, base_row: Dictionary, eid: String) -> Array[Dictionary]:
|
||||
var errs: Array[Dictionary] = []
|
||||
var base_ids := {}
|
||||
for entry_v in base_row.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
base_ids[String(entry_v.get("skill_id", ""))] = true
|
||||
for entry_v in row.get("pool_extra", []):
|
||||
if entry_v is Dictionary:
|
||||
var sid := String(entry_v.get("skill_id", ""))
|
||||
if sid != "" and base_ids.has(sid):
|
||||
errs.append({"category": "dup_key", "message": "%s pool_extra 与基础池 skill_id 重复: %s" % [eid, sid]})
|
||||
return errs
|
||||
|
||||
|
||||
@@ -193,6 +271,43 @@ func _validate_anchor_rows(anchor_ok: Dictionary, boss_ok: Dictionary) -> Array[
|
||||
return errs
|
||||
|
||||
|
||||
# 基础行查找单点(按 (关, 敌种, 行类) 取深度带最小行——精英有效池与「指向 elite 判别」共用,
|
||||
# 禁第二份查找算法):无匹配返回空字典。行字段一律 row.get 读取(夹具行可缺键)。
|
||||
func _min_band_row(level: int, eid: String, kind: String) -> Dictionary:
|
||||
var best := {}
|
||||
var best_band := -1
|
||||
for row in rows:
|
||||
if int(row.get("level_index", -1)) != level or String(row.get("row_kind", "")) != kind \
|
||||
or String(row.get("enemy_id", "")) != eid:
|
||||
continue
|
||||
var band := int(row.get("depth_band", -1))
|
||||
if best.is_empty() or band < best_band:
|
||||
best = row
|
||||
best_band = band
|
||||
return best
|
||||
|
||||
|
||||
## 精英有效池(按需现算、零缓存零写回——敌人系统.md:149 继承不复制):非 elite 行 = 自身 pool
|
||||
## 字典条目副本;elite 行 = 基础行(同关 battle 行类基础敌种深度带最小行)pool 条目 + 本行
|
||||
## pool_extra 条目(均只取 is Dictionary 条目并 duplicate——返回值改写不回灌表内行)。
|
||||
func effective_pool(row: Dictionary) -> Array[Dictionary]:
|
||||
var out: Array[Dictionary] = []
|
||||
if String(row.get("row_kind", "")) != "elite":
|
||||
for entry_v in row.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
return out
|
||||
var base := _min_band_row(int(row.get("level_index", 0)), String(row.get("base_ref", "")), "battle")
|
||||
if not base.is_empty():
|
||||
for entry_v in base.get("pool", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
for entry_v in row.get("pool_extra", []):
|
||||
if entry_v is Dictionary:
|
||||
out.append((entry_v as Dictionary).duplicate())
|
||||
return out
|
||||
|
||||
|
||||
# 深度带切法单点(唯一实现,禁第二份算法):band_keys = 该 (level_index, row_kind) 的
|
||||
# depth_band 键升序数组;返回首个 ≥ D 的键(请求落带取偏难一侧)。
|
||||
# D 高于全部键 → 返回 -1——resolver 侧 clamp 顶带+warning,跨表校验侧判「顶 clamp 消解」违例;
|
||||
|
||||
@@ -207,6 +207,25 @@ rows = [{
|
||||
"armor_type": "light",
|
||||
"armor_class": 8
|
||||
}, {
|
||||
"level_index": 1,
|
||||
"row_kind": "elite",
|
||||
"depth_band": 0,
|
||||
"enemy_id": "goblin_elite",
|
||||
"display_name": "精英·地精",
|
||||
"unit_count": 1,
|
||||
"hp": 20,
|
||||
"atk": 2,
|
||||
"atk_min": 1,
|
||||
"atk_max": 3,
|
||||
"base_ref": "goblin",
|
||||
"pool_extra": [{
|
||||
"skill_id": "heavy",
|
||||
"weight": 2
|
||||
}],
|
||||
"attack_type": "normal",
|
||||
"armor_type": "light",
|
||||
"armor_class": 2
|
||||
}, {
|
||||
"level_index": 2,
|
||||
"row_kind": "battle",
|
||||
"depth_band": 0,
|
||||
@@ -943,6 +962,7 @@ rows = [{
|
||||
; 不做差异化(pierce/magic 属新设计另立项)。全局平移效应=主角穿重甲受伤 −1 / 魔法护甲 +1 / 轻甲 0,
|
||||
; 锻造甲路线首次具备战斗意义;克制矩阵数值零触碰(settlement.tres armor_effectiveness 不改)。
|
||||
;
|
||||
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss}——battle=战斗节点配池,boss=终点行集;
|
||||
; 行键 = (level_index, row_kind, depth_band, enemy_id);row_kind ∈ {battle, boss, elite}——battle=战斗节点配池,boss=终点行集,elite=精英行(独立 enemy_id、池走继承+附加,暂不接线任何节点寻址);
|
||||
; depth_band=节点深度所在的带键,请求落带取首个 ≥ 深度的键(偏难一侧);越顶带由 resolver clamp、跨表校验判为调参事故。
|
||||
; (1,3) 大地精行 2026-11-04 用户裁退役,改地精+熊地精混编(行序熊地精在前、行值整行复用 (1,2)/(1,0)、静态骰和 3);沿革见 git 提交史
|
||||
; 精英专属池批首只精英=地精精英(1,elite,0,goblin_elite),行值 provisional(投值所有权归难度曲线与Boss 统一修正),来源=2026-11-06 用户点头草案 v0
|
||||
@@ -190,8 +190,8 @@ func _start_adventure_from_menu() -> void:
|
||||
# 主菜单「开始冒险」钮已随三入口化撤出(卡② mn6——主菜单侧断点互斥确认链同步摘除,
|
||||
# 弹窗本体与两分支逻辑归卡④大门承接)。本函数为探针直调中间态保留(mn10——冒险段探针
|
||||
# 直调位点逐个登记实现报告「已知中间态」;卡④到位后由大门两分支换代)。无激活态写盘拒绝
|
||||
# 由卡① sv3 承接(结算不落盘)。
|
||||
ui.screens.start_overlay.visible = false
|
||||
# 由卡① sv3 承接(结算不落盘)。开始层隐藏已收进 _start_adventure()(营地交互修复批契约 1
|
||||
# ——开局路径隐藏点唯一出口)。
|
||||
_start_adventure()
|
||||
|
||||
|
||||
@@ -290,6 +290,7 @@ static func build_adventure_params(save_manager_ref: SaveManagerCore, meta_ref:
|
||||
# 冒险模式(三关连探——AdventureRunner 编排;转发信号签名与 runner 一致,接线照原样对接;
|
||||
# 信号连接只在组合根)
|
||||
func _start_adventure() -> void:
|
||||
ui.screens.start_overlay.visible = false # 开局路径开始层隐藏唯一出口(营地交互修复批契约 1——三处调用方经此收口;失败分支开始层终态=隐藏,致命屏接管)
|
||||
ui.flow.close(ui.modals.menu.debug_overlay) # 开冒险路径显式关浮窗(调试浮窗默认开态下开局不带窗;对局中经角标再开)
|
||||
begin_session(true)
|
||||
_wire_adventure()
|
||||
|
||||
@@ -189,20 +189,10 @@ func is_refresh_ready() -> bool:
|
||||
return not _btn_refresh.disabled
|
||||
|
||||
|
||||
# 探针接缝(合成输入走真实翻译出口口径):刷新按钮点击 → 确认 chips 全链路
|
||||
func press_refresh_for_probe() -> void:
|
||||
_on_refresh_pressed()
|
||||
|
||||
|
||||
func confirm_visible() -> bool:
|
||||
return _confirm_row.visible
|
||||
|
||||
|
||||
func press_confirm_for_probe() -> void:
|
||||
if _arm_timer.is_armed():
|
||||
_disarm_and_fire()
|
||||
|
||||
|
||||
func refresh_remaining_text() -> String:
|
||||
return _refresh_sub.text
|
||||
|
||||
|
||||
@@ -191,10 +191,11 @@ func close() -> void:
|
||||
st.seal.visible = false
|
||||
st.seal.modulate = Color.WHITE
|
||||
st.cele.visible = false
|
||||
var tw: Tween = get_meta(&"celebrate_tw", null) # 循环句柄随节点存(跨 open/close,close 必杀)
|
||||
if tw != null and tw.is_valid():
|
||||
tw.kill()
|
||||
remove_meta(&"celebrate_tw")
|
||||
if has_meta(&"celebrate_tw"): # 循环句柄随节点存(跨 open/close:有则必杀、无则无事——缺 meta 跳过)
|
||||
var tw: Tween = get_meta(&"celebrate_tw")
|
||||
if tw.is_valid():
|
||||
tw.kill()
|
||||
remove_meta(&"celebrate_tw")
|
||||
_clear_crest_bricks()
|
||||
|
||||
|
||||
@@ -328,9 +329,10 @@ func _spawn_dust_mote(i: int, base: Vector2) -> void:
|
||||
func _play_celebrate() -> void:
|
||||
if UiKit.motion_scale < 1.0:
|
||||
return
|
||||
var prev: Tween = get_meta(&"celebrate_tw", null)
|
||||
if prev != null and prev.is_valid():
|
||||
prev.kill()
|
||||
if has_meta(&"celebrate_tw"): # 有句柄才取并 kill(缺 meta 静默跳过——败走后首次胜利同款容错)
|
||||
var prev: Tween = get_meta(&"celebrate_tw")
|
||||
if prev.is_valid():
|
||||
prev.kill()
|
||||
var tw := st.cele.create_tween()
|
||||
set_meta(&"celebrate_tw", tw)
|
||||
tw.set_loops()
|
||||
|
||||
@@ -11,10 +11,15 @@ func _init() -> void:
|
||||
set_anchors_preset(Control.PRESET_TOP_WIDE)
|
||||
custom_minimum_size = Vector2(0, 48) # 高 48px 浮层覆盖、不挤压画面(camp.md §顶栏)
|
||||
mouse_filter = Control.MOUSE_FILTER_IGNORE # HUD 栏不挡场景命中区(浮层覆盖语义)
|
||||
UiKit.camp_hud_band_surface(self) # 栏体色面——整件外观单一出口(色值与 α、铺满、不吃点击、挂载全在出口内);先入=绘制序底,徽章后入不被盖
|
||||
_badge = UiKit.keystone_badge("钥石 0")
|
||||
_badge.set_anchors_preset(Control.PRESET_CENTER_RIGHT)
|
||||
_badge.position = Vector2(-160, 8) # 顶栏右位次(UiKit 现行件域内——代拍板 3)
|
||||
add_child(_badge)
|
||||
var dock := HBoxContainer.new() # 右对齐容器承载徽章(Q2 已裁乙——位数增宽自然向左展;摆位机制先例=confirm_dialog 钮行同款容器 alignment)
|
||||
dock.set_anchors_preset(Control.PRESET_FULL_RECT)
|
||||
dock.offset_right = -12.0 # 右缘内缩 12px(UiKit 钮间距同档——白名单 3)
|
||||
dock.alignment = BoxContainer.ALIGNMENT_END
|
||||
dock.mouse_filter = Control.MOUSE_FILTER_IGNORE # 容器自身默认 STOP——与出口内 IGNORE 是两个点,必须同批落(漏一处顶排物件点不动)
|
||||
dock.add_child(_badge)
|
||||
add_child(dock)
|
||||
|
||||
|
||||
## 余额写入(格式串=既有消费面同款「钥石 %d」——badge 工厂约定单点,禁第二格式串)
|
||||
|
||||
@@ -30,13 +30,15 @@ func _init() -> void:
|
||||
box.add_child(btn_row)
|
||||
_cancel_btn = UiKit.button("取消")
|
||||
_cancel_btn.pressed.connect(_emit_cancelled)
|
||||
btn_row.add_child(_cancel_btn)
|
||||
_secondary_btn = UiKit.button("次选项")
|
||||
_secondary_btn.pressed.connect(_emit_secondary)
|
||||
btn_row.add_child(_secondary_btn)
|
||||
_confirm_btn = UiKit.button("确认")
|
||||
_confirm_btn.pressed.connect(_emit_confirmed)
|
||||
# 位次定值(HUD框架.md:90 三条纪律·其三——推进钮在左、次推进居中、维持在右;四处消费方
|
||||
# 共用本件同一序。信号按按钮对象路由、不按位次——换序不改路由〔营地交互修复批契约 3〕)
|
||||
btn_row.add_child(_confirm_btn)
|
||||
btn_row.add_child(_secondary_btn)
|
||||
btn_row.add_child(_cancel_btn)
|
||||
visible = false
|
||||
|
||||
|
||||
|
||||
@@ -153,26 +153,6 @@ func _on_confirm_pressed() -> void:
|
||||
pick_confirmed.emit(_selected)
|
||||
|
||||
|
||||
# 探针/测试复用:以真实按钮路径触发点选、确认与继续(树序遮挡纪律——断言走真实信号路径)
|
||||
func press_card(index: int) -> void:
|
||||
# M7:卡载体 Button→bcard(gui_input 点选)——真实路径=同一 _select_card 处理器,探针语义不变
|
||||
if index >= 0 and index < _card_meta.size():
|
||||
_select_card(String(_card_meta[index].get("id", "")))
|
||||
|
||||
|
||||
func press_confirm() -> void:
|
||||
_confirm_btn.pressed.emit()
|
||||
|
||||
|
||||
func press_continue() -> void:
|
||||
_continue_btn.pressed.emit()
|
||||
|
||||
|
||||
# 探针/测试复用:以真实按钮路径触发刷新意图(信号外发——受理归组合根)
|
||||
func press_refresh() -> void:
|
||||
_refresh_btn.pressed.emit()
|
||||
|
||||
|
||||
# 兑付反馈态(curio_resolved 到达——结果行追加在头部之后;唯一出口 = 继续)
|
||||
func show_result(text: String) -> void:
|
||||
_label.text = _header + "\n" + text
|
||||
|
||||
@@ -123,20 +123,6 @@ func output_text() -> String:
|
||||
return st.sim_out.get_parsed_text()
|
||||
|
||||
|
||||
# ---- 探针接缝(真实翻译出口——按钮 pressed 信号路径,非直调组合根)----
|
||||
|
||||
func press_apply_for_probe() -> void:
|
||||
_apply_btn_pressed()
|
||||
|
||||
|
||||
func press_reset_for_probe() -> void:
|
||||
_reset_btn_pressed()
|
||||
|
||||
|
||||
func press_simulate_for_probe() -> void:
|
||||
_sim_btn.pressed.emit()
|
||||
|
||||
|
||||
# ---- 内部(信号 → 外发)----
|
||||
|
||||
func _apply_btn_pressed() -> void:
|
||||
|
||||
@@ -465,18 +465,20 @@ func _submit_choice(nid: int) -> void:
|
||||
route_chosen.emit(nid)
|
||||
|
||||
|
||||
# 探针复用:按候选序号走图上命中同路径(候选节点中心坐标 → 命中检测 → 提交——布局决策 2 探针面改造)
|
||||
func press_candidate(index: int) -> void:
|
||||
# 探针面命中矩形读口:候选节点的图上命中矩形(全局坐标——真输入 push_click_at 落点)。
|
||||
# 命中面与 _candidate_at 同源(节点方块+外扩容差);index 越界或候选未就绪返回空 Rect2(调用方跳过)。
|
||||
func candidate_rect(index: int) -> Rect2:
|
||||
if index < 0 or index >= _cand_ids.size() or _map.runner == null:
|
||||
return
|
||||
return Rect2()
|
||||
var nodes: Array[Variant] = _map.runner.nodes()
|
||||
var node := _node_by_id(int(_cand_ids[index]), nodes)
|
||||
if node.is_empty():
|
||||
return
|
||||
return Rect2()
|
||||
var pos: Vector2 = _layout(nodes, _map.canvas)[node]
|
||||
var nid := _candidate_at(pos) # 节点中心必命中自身(命中面含节点方块)
|
||||
if nid >= 0:
|
||||
_submit_choice(nid)
|
||||
var half := NODE_HALF + Vector2(HIT_PAD, HIT_PAD)
|
||||
var local := Rect2(pos - half, half * 2.0)
|
||||
var xform := _map.canvas.get_global_transform()
|
||||
return Rect2(xform * local.position, local.size)
|
||||
|
||||
|
||||
# ---- 视口锚点(布局决策 5:当前节点居中;入口停点尚无当前节点=锚入口组包围盒居中) ----
|
||||
|
||||
@@ -29,10 +29,6 @@ func show_rest(healed: int, hp_after: int, max_hp: int) -> void:
|
||||
_btn.text = "休息完毕,继续"
|
||||
visible = true
|
||||
|
||||
# 探针复用:以真实按钮路径触发继续
|
||||
func press_continue() -> void:
|
||||
_btn.pressed.emit()
|
||||
|
||||
|
||||
func _emit_continue_pressed() -> void:
|
||||
continue_pressed.emit()
|
||||
@@ -2,7 +2,7 @@
|
||||
// 封印印记(卡④ 法阵化批)——环+十字+环外压暗光圈,canvas_item 距离场实时绘制(无贴图缩放,
|
||||
// 格边 26–46px 连续可变下任意格边清晰)。
|
||||
// 事实源:art-bible v3.41 §3.2「封印印记」(形/画法/光圈/层序纪律)+ §8.2「封印印记参数表」;
|
||||
// gdd/ux/battle.md「棋盘格尺寸公式」(格边 clampi 26–46)。
|
||||
// design/ux/battle.md「棋盘格尺寸公式」(格边 clampi 26–46)。
|
||||
// 纪律:参数五枚+色全由 gd 侧逐次注入(参数单点 = ui_kit SEAL_* 五常量——禁在此改值,
|
||||
// 下方默认值仅为兜底且与 §8.2 同值);光圈不出格(半径夹 cell_px/2);
|
||||
// 零动效(不呼吸不脉冲——§2.1 挖掘期唯一在动 = 接触带微光);印记与光圈同用一色(光圈只改 alpha)。
|
||||
|
||||
@@ -193,6 +193,7 @@ const TIP_BG := Color(0.16, 0.12, 0.09, 0.96) # tooltip 深底
|
||||
const STAND_STONE := Color8(150, 130, 100) # 站桩石板地台·板身(standee_body 方向稿取色)
|
||||
const PLAZA_EDGE := Color8(76, 64, 56) # 小石台描边(art-bible §4.1 换色合并②合并目标——贴图已落此值,缺位回退同值)
|
||||
const CAMP_HEADER_BAND := Color8(110, 69, 38) # 营地柜台头带深棕 #6E4526(4-camp.png 方向稿取色;对齐 demo --wood-deep)
|
||||
const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80) # 营地顶栏栏体=头带深棕×α0.80(camp.md §顶栏定值——派生收口单点,非色板新增;同文件 TOAST_BG/TIP_BG 带 α 先例)
|
||||
const CAMP_ROW_PLATE := Color8(240, 230, 206) # 营地行板暖米(同上)
|
||||
const CAMP_BADGE_PANEL := Color8(40, 32, 24) # 营地钥石徽暗底(同上)
|
||||
const HP_BAR_BACK := Color8(40, 34, 32) # 迷你血条深底 #282220(兼血数垫牌;站桩名字行素底同源——§6.1/§7.2 文字禁压地台)
|
||||
@@ -656,6 +657,19 @@ static func keystone_badge(text: String, icon_path := TEX_ICON_KEYSTONE, icon_px
|
||||
return KeystoneBadge.new(text, icon_path, icon_px)
|
||||
|
||||
|
||||
## 营地顶栏栏体面(整件外观单一出口——营地交互修复批契约 4/5):色值与 α、铺满、不吃点击、
|
||||
## 挂上宿主五件事全写死在体内;形参只有 host——调用方没有可以忘记的形参。返回句柄供调用方
|
||||
## 读取与断言;徽章摆位不由本出口负责(归顶栏件内右对齐容器)。须在宿主入树前调用
|
||||
## (set_anchors_preset 是入树前动作——_modal_dim/overlay_bg 同形)。
|
||||
static func camp_hud_band_surface(host: Control) -> ColorRect:
|
||||
var rect := ColorRect.new()
|
||||
rect.color = CAMP_HUD_BAND
|
||||
rect.set_anchors_preset(Control.PRESET_FULL_RECT) # 四边 offset 归零铺满宿主
|
||||
rect.mouse_filter = Control.MOUSE_FILTER_IGNORE # 栏体不吃点击(顶排物件命中区不受阻——契约 5)
|
||||
host.add_child(rect)
|
||||
return rect
|
||||
|
||||
|
||||
# 按钮皮·五态矩阵(逐实例 override 不动主题层)
|
||||
# 悬停:常态图+modulate 提亮(0.06 档与 primary_button 现行 lightened(0.06) 同纪律,零新素材);
|
||||
# 禁用:本族底+α0.45 幽灵化(与 font_disabled_color 同纪律);
|
||||
|
||||
@@ -45,7 +45,7 @@ func _ready() -> void:
|
||||
return
|
||||
for i in 5:
|
||||
await get_tree().process_frame
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
for i in 20:
|
||||
await get_tree().process_frame
|
||||
var st = main.run_scope.settlement
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# VERTICAL SLICE - NOT FOR PRODUCTION
|
||||
# 布局几何断言探针(现行事实源=tools/probe_gate.sh 默认段;呈现层三区布局的分辨率维度验收)
|
||||
# 用途:battle.md §验收「1280×800 与 16:9 全屏下三区无遮挡/无重叠/无裁切」的可执行化
|
||||
# GDD 锚:design/gdd/ux/battle.md §Layout Zones(B/C/D 三区几何)
|
||||
# GDD 锚:design/ux/battle.md §Layout Zones(B/C/D 三区几何)
|
||||
# 硬约束:档位表是数据、断言是一份函数据——加档只改表不动断言(禁每档建一个探针)
|
||||
extends Node
|
||||
|
||||
@@ -30,7 +30,7 @@ func _ready() -> void:
|
||||
for i in range(8):
|
||||
await get_tree().process_frame
|
||||
# 进战斗屏——三区(B 战斗画面/C 角色信息/D 扫雷图)同屏常驻,菜单屏不构成断言面
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
for i in range(20):
|
||||
await get_tree().process_frame
|
||||
for vp in VIEWPORTS:
|
||||
|
||||
@@ -22,14 +22,14 @@ var _runner # 当前停点的单关 runner(open_choices 驱动随记——
|
||||
|
||||
|
||||
func _check(desc: String, ok: bool) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
_checks += 1
|
||||
if not ok:
|
||||
_problems.append(desc)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (关卡画面视口跟随——卷轴视口组)
|
||||
# oracle: design/ux/level-map.md:3 (关卡画面视口跟随——卷轴视口组)
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var host := Control.new()
|
||||
@@ -62,7 +62,7 @@ func _ready() -> void:
|
||||
|
||||
## R1 浅图整幅居中无裁切+画布入视口件承载(画布=视口首子件——setup 注入序)
|
||||
func _probe_shallow_no_crop(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (浅层不裁切组)
|
||||
# oracle: design/ux/level-map.md:3 (浅层不裁切组)
|
||||
_runner = _make_runner_entry(1, SEED_L1)
|
||||
screen.open_choices(_cand_nodes(_runner), _runner)
|
||||
await get_tree().process_frame
|
||||
@@ -81,7 +81,7 @@ func _probe_shallow_no_crop(screen, vp) -> void:
|
||||
|
||||
## R5 前置+深图入口停点:锚点=入口组包围盒中心夹紧(spec :101——多入口并列取包围盒居中)
|
||||
func _probe_deep_entry_clamp(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (深层入口钳制组)
|
||||
# oracle: design/ux/level-map.md:3 (深层入口钳制组)
|
||||
_runner = _make_runner_entry(3, SEED_L3)
|
||||
screen.open_choices(_cand_nodes(_runner), _runner)
|
||||
await get_tree().process_frame
|
||||
@@ -175,7 +175,7 @@ func _probe_clamp_bounds_and_columns(screen, vp) -> void:
|
||||
|
||||
## R6 拖拽不触发选路提交;阈值另一侧完整点击仍提交(提交语义零改动)
|
||||
func _probe_drag_no_submit(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# 重建到中段可居中停点(候选=停点出边——候选节点居屏中可拖可达)
|
||||
var stops := _centerable_stops(screen, vp)
|
||||
if stops.size() < 3:
|
||||
@@ -214,7 +214,7 @@ func _probe_drag_no_submit(screen, vp) -> void:
|
||||
|
||||
## R2 停点切换后当前节点居中(视口中线投影 x=640±2——视口满屏宽即屏中线)
|
||||
func _probe_follow_anchor(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
var stops := _centerable_stops(screen, vp)
|
||||
if stops.size() < 3:
|
||||
_problems.append("R2 可居中停点不足(=%d)" % stops.size())
|
||||
@@ -230,7 +230,7 @@ func _probe_follow_anchor(screen, vp) -> void:
|
||||
|
||||
## R7 平滑滚动有 tween 中间帧;减动效开=位置即达(画布坐标域判定)
|
||||
func _probe_smooth_scroll_reduced_motion(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
var saved_scale: float = UiKit.motion_scale
|
||||
UiKit.motion_scale = 1.0
|
||||
var stops := _centerable_stops(screen, vp)
|
||||
@@ -270,7 +270,7 @@ func _probe_smooth_scroll_reduced_motion(screen, vp) -> void:
|
||||
|
||||
## R8 指引件可见性与几何(深图超窗——像素级判据②③)
|
||||
func _probe_guidance_chrome(screen, vp) -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
var band_l: Control = vp.get_node_or_null(NodePath("FadeL"))
|
||||
var band_r: Control = vp.get_node_or_null(NodePath("FadeR"))
|
||||
var chip: Control = vp.get_node_or_null(NodePath("PanHint"))
|
||||
@@ -302,7 +302,7 @@ func _probe_shallow_guidance_hidden(screen, vp) -> void:
|
||||
|
||||
## 入口停点 runner(defer_entry——选路态挂起、候选=入口节点)
|
||||
func _make_runner_entry(level_index: int, seed_val: int):
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.new_adventure(seed_val)
|
||||
var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng)
|
||||
@@ -313,7 +313,7 @@ func _make_runner_entry(level_index: int, seed_val: int):
|
||||
|
||||
## 中段停点 runner(断点续玩重建——图由种子+参数档重生成同图,零 RNG 消耗)
|
||||
func _make_runner_at_stop(level_index: int, seed_val: int, node_id: int):
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
var rng = load("res://core/rng_service.gd").new()
|
||||
rng.new_adventure(seed_val)
|
||||
var runner = load("res://core/level_runner.gd").new(get_node("/root/ConfigDB"), rng)
|
||||
@@ -388,7 +388,7 @@ func _extreme_node(pos: Dictionary, nodes: Array, first: bool) -> Dictionary:
|
||||
|
||||
|
||||
func _finish() -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
# oracle: design/ux/level-map.md:3 (判定上报通道——验证函数组)
|
||||
if _checks == 0:
|
||||
_problems.append("零断言执行(断言数下限闸——三硬闸③)")
|
||||
if _problems.is_empty():
|
||||
|
||||
@@ -199,6 +199,7 @@ func _seen_into(node: Node, keys: Array[String]) -> void:
|
||||
# ---- 落盘(JSONP 式 .js——file:// 页面 <script src> 直载)----
|
||||
|
||||
func dump_js() -> void:
|
||||
# oracle: design/engineering/test-conventions.md:30 (覆盖登记落盘守卫——判定上报通道组)
|
||||
var dir := DirAccess.open("res://")
|
||||
if dir == null:
|
||||
push_error("[coverage] res:// 打不开,登记未落盘")
|
||||
|
||||
@@ -119,7 +119,7 @@ func _step_boot_menu() -> void:
|
||||
|
||||
# ---- ② 菜单 → 营地(真实按钮信号路径)----
|
||||
func _step_menu_to_camp() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:10 (菜单→营地组)
|
||||
# oracle: design/ux/camp.md:10 (菜单→营地组)
|
||||
# 营地经进档开层(卡② mn9 新路径——campbtn 已撤;槽模板注入隔离命名空间)
|
||||
if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_camp"):
|
||||
_problems.append("②菜单→营地:选档层新档钮缺失(新路径驱动中断)")
|
||||
@@ -130,7 +130,7 @@ func _step_menu_to_camp() -> void:
|
||||
|
||||
# ---- ③ 营地 → 菜单(回退路径:篝火二次确认动线——卡④ 营地唯一回菜单出口;选档层随关层收闭同收)----
|
||||
func _step_camp_back_to_menu() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:22 (营地→菜单组)
|
||||
# oracle: design/ux/camp.md:22 (营地→菜单组)
|
||||
if main.ui.modals.camp.scene == null:
|
||||
_problems.append("③营地→菜单:营地场景根句柄缺失〔句柄子容器批 V1〕")
|
||||
return
|
||||
@@ -194,7 +194,7 @@ func seals_check_single(f: Dictionary) -> bool:
|
||||
|
||||
|
||||
func _step_menu_to_adventure() -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:11 (菜单→冒险组)
|
||||
# oracle: design/ux/level-map.md:11 (菜单→冒险组)
|
||||
# 开始冒险钮已随三入口化撤出——函数直调中间态(卡② mn10;卡④ 大门两分支换代)
|
||||
main._start_adventure_from_menu()
|
||||
await _frames(3)
|
||||
@@ -208,7 +208,7 @@ func _step_menu_to_adventure() -> void:
|
||||
|
||||
# ---- ⑤ 冒险内屏切换:关卡地图屏 ⇄ 战斗屏(屏内两态,屏级仍独占)----
|
||||
func _step_level_to_battle_and_back() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:57 (冒险内切换组)
|
||||
# oracle: design/ux/battle.md:57 (冒险内切换组)
|
||||
if not main.ui.modals.run.level_screen.visible:
|
||||
_problems.append("⑤冒险内切换:前置不在关卡地图屏")
|
||||
return
|
||||
@@ -228,7 +228,7 @@ func _step_level_to_battle_and_back() -> void:
|
||||
|
||||
# ---- ⑥ 放弃 → 终局(放弃路径=回退路径的非正常出口)----
|
||||
func _step_abandon_to_end() -> void:
|
||||
# oracle: design/gdd/ux/level-map.md:42 (放弃→终局组)
|
||||
# oracle: design/ux/level-map.md:42 (放弃→终局组)
|
||||
if main.run_scope == null or main.run_scope.adventure == null:
|
||||
_problems.append("⑥放弃→终局:前置无冒险实例")
|
||||
return
|
||||
@@ -241,7 +241,7 @@ func _step_abandon_to_end() -> void:
|
||||
|
||||
# ---- ⑦ 终局 → 菜单(终局出口返回开始界面)----
|
||||
func _step_end_back_to_menu() -> void:
|
||||
# oracle: design/gdd/ux/settlement-summary.md:24 (终局→菜单组)
|
||||
# oracle: design/ux/settlement-summary.md:24 (终局→菜单组)
|
||||
if main.ui.modals.run.end_overlay == null or not main.ui.modals.run.end_overlay.visible:
|
||||
_problems.append("⑦终局→菜单:前置不在终局")
|
||||
return
|
||||
@@ -254,7 +254,7 @@ func _step_end_back_to_menu() -> void:
|
||||
|
||||
# ---- ③' 营地 Esc 浮窗+HUD(卡⑤——真实键盘合成路径 push_input;四态路由+三出口+语境分支+余额单口)----
|
||||
func _step_camp_esc_and_hud() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:45 (Esc 段组)
|
||||
# oracle: design/ux/camp.md:45 (Esc 段组)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_screen_flow_esc", false, false, true)
|
||||
if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_esc"):
|
||||
_problems.append("[esc] 营地入口缺失(选档层新档钮驱动中断)〔ESC批 V1〕")
|
||||
@@ -320,7 +320,7 @@ func _step_camp_esc_and_hud() -> void:
|
||||
# 判的是呈现形态换壳(近全屏面板→居中定尺寸窗壳 720×640):窗几何/场景根留底/遮罩 0.6/关闭钮/
|
||||
# 互斥单开/ESC 优先序/窗内滚动/生涯同壳——行为面(开合路由)零改,只钉窗壳可观测提交面。
|
||||
func _step_camp_window_shell() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:45 (窗壳 V1–V8 组)
|
||||
# oracle: design/ux/camp.md:45 (窗壳 V1–V8 组)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_screen_flow_winshell", false, false, true)
|
||||
if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_screen_flow_winshell"):
|
||||
_problems.append("[winshell] 营地入口缺失(选档层新档钮驱动中断)〔窗口化批 V1〕")
|
||||
@@ -478,7 +478,7 @@ func _step_camp_window_shell() -> void:
|
||||
|
||||
# 清窗经真实钮(逐窗巡检 visible→钮 pressed——栈 close 恢复账随层清空)
|
||||
func _close_all_windows(views: Dictionary) -> void:
|
||||
# oracle: design/gdd/ux/camp.md:45 (清窗组)
|
||||
# oracle: design/ux/camp.md:45 (清窗组)
|
||||
for k: StringName in [&"forge", &"chest", &"dummy", &"board"]:
|
||||
var w: Control = views[k]
|
||||
if not w.visible:
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
# 流程 = 真实按钮信号路径驱动(ui_click_probe 同款;存档/设置/元进度三面换注隔离档——禁碰真实档,
|
||||
# 坑册「断点写盘随 save_manager 引用走」教训同款覆盖):菜单(启动态)→ 设置面(settingsbtn
|
||||
# 真实信号路径)→ 生涯页(函数直调中间态——卡② mn10)→ 营地(进档开层新路径——mn9)→ 关卡画面(函数直调中间态→停点出图)→
|
||||
# 战斗(level_screen.press_candidate(0) 真实选路提交)→ 状态面板(地图屏状态角标真实信号路径)。
|
||||
# 战斗(level_screen candidate_rect 命中矩形真点选路提交)→ 状态面板(地图屏状态角标真实信号路径)。
|
||||
# 每屏双帧 + frame_post_draw 同步后抓 get_viewport().get_texture(),PNG 落输出目录;
|
||||
# 最小自检 = 七件存在且非零字节(像素方差断言归 tools/shot_checker.py——B 档骨架分工)。
|
||||
# 商店房不纳屏(真实路径口径):真实路径必经 L1 打穿→L2 商店节点
|
||||
@@ -103,7 +103,7 @@ func _ready() -> void:
|
||||
ProbeUiUtil.push_click(main, set_close)
|
||||
await get_tree().process_frame
|
||||
# ①c 生涯页(函数直调中间态——卡② mn10:careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径)
|
||||
# oracle: design/gdd/ux/camp.md:45 生涯页与四子界面同壳同形态(生涯组)
|
||||
# oracle: design/ux/camp.md:45 生涯页与四子界面同壳同形态(生涯组)
|
||||
if true:
|
||||
main._open_career()
|
||||
for i in 3:
|
||||
@@ -123,7 +123,7 @@ func _ready() -> void:
|
||||
if main.ui.modals.menu.career_overlay.visible or not main.ui.screens.start_overlay.visible:
|
||||
_problems.append("生涯页关闭后页未隐藏或开始层未恢复")
|
||||
# ② 营地场景图(进档开层新路径——卡② mn9;截图=可点击场景图形态——卡④ 契约 9 V1 验收载体)
|
||||
# oracle: design/gdd/ux/camp.md:16 营地=可点击场景图(七物件命中区表——场景图形态组)
|
||||
# oracle: design/ux/camp.md:16 营地=可点击场景图(七物件命中区表——场景图形态组)
|
||||
if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_shot_probe_camp"):
|
||||
_problems.append("营地入口缺失(选档层新档钮——新路径驱动中断)〔句柄子容器批 V1〕")
|
||||
else:
|
||||
@@ -132,16 +132,16 @@ func _ready() -> void:
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"campfire").get_center()) # 收闭改接篝火动线(营地根态无关闭钮——卡④ T5)
|
||||
await get_tree().process_frame
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 二次确认离场(关层收闭——选档层随收)
|
||||
# oracle: design/gdd/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(篝心动线组)
|
||||
# oracle: design/ux/camp.md:22 篝火=离开营地回主菜单、必须二次确认(篝心动线组)
|
||||
await get_tree().process_frame
|
||||
# ③ 关卡画面(开始冒险→停点出图——入口停点语义;函数直调中间态 mn10)
|
||||
# oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(关卡画面出图组)
|
||||
# oracle: design/ux/level-map.md:11 入口停点候选数=入口个数(关卡画面出图组)
|
||||
main._start_adventure_from_menu()
|
||||
for i in 4:
|
||||
await get_tree().process_frame
|
||||
await _snap("level")
|
||||
# GP 常驻徽章断言(读数=runner.gp 现值,刷新=写路径回执后重查〔open_choices〕)
|
||||
# oracle: design/gdd/ux/level-map.md:77 GP 常驻徽章=GP 图标+数字(冒险钱包读数;写路径回执后重查)
|
||||
# oracle: design/ux/level-map.md:77 GP 常驻徽章=GP 图标+数字(冒险钱包读数;写路径回执后重查)
|
||||
var gp_label: Label = ProbeUiUtil.find_label(main.ui.modals.run.level_screen, "◈ ")
|
||||
if gp_label == null or not gp_label.is_visible_in_tree():
|
||||
_problems.append("GP 徽章不可见(关卡画面顶栏)")
|
||||
@@ -149,10 +149,10 @@ func _ready() -> void:
|
||||
var expect := "◈ %d" % int(main.ui.modals.run.level_screen.get("_runner").gp)
|
||||
if gp_label.text != expect:
|
||||
_problems.append("GP 徽章读数不符: %s != %s" % [gp_label.text, expect])
|
||||
# ④ 战斗(节点一——真实选路提交路径 press_candidate,ui_click_probe :199 同款)
|
||||
# oracle: design/gdd/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组)
|
||||
# ④ 战斗(节点一——candidate_rect 命中矩形真点选路提交,ui_click_probe 同款)
|
||||
# oracle: design/ux/battle.md:122 B 区构建:站桩/明细带/轮次指示(战斗出图与几何锚组)
|
||||
if main.ui.modals.run.level_screen != null and main.ui.modals.run.level_screen.visible:
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
for i in 10:
|
||||
await get_tree().process_frame
|
||||
await _snap("battle")
|
||||
|
||||
@@ -13,7 +13,7 @@ var _problems: Array = []
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (明细带挖掘期收起——T17 视觉扩展时序组)
|
||||
# oracle: design/ux/battle.md:122 (明细带挖掘期收起——T17 视觉扩展时序组)
|
||||
await get_tree().process_frame
|
||||
main = load("res://presentation/main.tscn").instantiate()
|
||||
add_child(main)
|
||||
@@ -37,7 +37,7 @@ func _ready() -> void:
|
||||
return
|
||||
for i in 5:
|
||||
await get_tree().process_frame
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
for i in 20:
|
||||
await get_tree().process_frame
|
||||
if main.run_scope.settlement == null:
|
||||
|
||||
+114
-23
@@ -40,6 +40,8 @@ var _route_clicks := 0 # 选路覆盖层点击计数(岔路使用
|
||||
var _locktest_state := 0 # T1 D1 锁期/重放断言状态机(0=待注入 1=等重放 2=完结)——每 run 一次
|
||||
var _locktest_cell := Vector2i(-9, -9)
|
||||
var _locktest_frames := 0
|
||||
var _stoptest_state := 0 # 停手钮定型化覆盖段状态机(0=待命 1=已点击待核效 2=完结)——每 run 一次
|
||||
var _stoptest_frames := 0
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -383,7 +385,7 @@ func _ready() -> void:
|
||||
if ProbeUiUtil.find_button(main.ui.modals.menu.settings_overlay, "退出游戏", "prefix") != null:
|
||||
_problems.append("设置面仍含退出游戏按钮(二轮 D1——只在一级菜单出现)")
|
||||
# ---- 生涯页段(入口点击链+关闭回返,真实按钮信号路径;本页只读零写,
|
||||
# oracle: design/gdd/ux/camp.md:45 (生涯页段——同壳同形态组)
|
||||
# oracle: design/ux/camp.md:45 (生涯页段——同壳同形态组)
|
||||
# 值断言归冒烟投影段与 ui_struct 结构族,此处只验开合路径与零跳转)----
|
||||
# 生涯页驱动=函数直调中间态(卡② mn10——careerbtn 已撤,卡④ 笔记本改挂前无 UI 路径)
|
||||
if true:
|
||||
@@ -421,13 +423,13 @@ func _ready() -> void:
|
||||
var wc3_txt: String = main.ui.battle.log_view._log.get_parsed_text()
|
||||
if not wc3_txt.contains("种子 42") or not wc3_txt.contains("(含面板加成)"):
|
||||
_problems.append("开场行回归失守:局外加成(pct>0)局的开场行应带「(含面板加成)」(wc3)")
|
||||
# 关卡开始 = 入口选路停点——press_candidate 图上命中路径选入口候选进首战
|
||||
# 关卡开始 = 入口选路停点——candidate_rect 命中矩形中心真点选入口候选进首战
|
||||
#(首格可点 AC 口径随之含入口选路一步:菜单点击 → 停点呈现 → pick → 棋盘安装)
|
||||
if main.ui.modals.run.level_screen.visible:
|
||||
# 独立性断言:独立屏激活期战斗屏宿主整体隐藏
|
||||
if main.ui.screens.battle_root.visible:
|
||||
_problems.append("独立屏激活期战斗屏宿主未隐藏(零透出契约失守)")
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var t_entry := Time.get_ticks_msec() - t1
|
||||
@@ -449,7 +451,7 @@ func _ready() -> void:
|
||||
action_budget -= 1
|
||||
await get_tree().process_frame
|
||||
if main.ui.modals.run.node_overlay.visible:
|
||||
main.ui.modals.run.node_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
if main.ui.modals.run.curio_overlay.visible:
|
||||
@@ -474,15 +476,17 @@ func _ready() -> void:
|
||||
if not main.ui.flow.locks_input():
|
||||
_problems.append("三选一停点打开时输入未锁定")
|
||||
if main.ui.modals.run.curio_overlay._continue_btn.visible:
|
||||
main.ui.modals.run.curio_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty():
|
||||
main.ui.modals.run.curio_overlay.press_card(0) # 奖励节点待选态:点首件+确认(真实按钮信号路径——与关卡完成停点同形)
|
||||
main.ui.modals.run.curio_overlay.press_confirm()
|
||||
ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center()) # 奖励节点待选态:真点首卡+确认(gui_input 点选——与关卡完成停点同形)
|
||||
await get_tree().process_frame # 选卡当帧确认钮才现形、容器重排在途——排版帧后矩形成形再真点
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
|
||||
main.ui.modals.run.curio_overlay.press_card(0)
|
||||
main.ui.modals.run.curio_overlay.press_confirm()
|
||||
ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center())
|
||||
await get_tree().process_frame # 同上:确认钮排版帧
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
|
||||
else:
|
||||
main.ui.modals.run.curio_overlay.press_continue() # 停点已消费的残面——继续关屏
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn) # 停点已消费的残面——继续关屏
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
if main.ui.modals.menu.shop_overlay.visible:
|
||||
@@ -537,7 +541,7 @@ func _ready() -> void:
|
||||
if String(nd.get("type", "")) == "reward" and main.ui.modals.run.level_screen._cand_ids.has(nd_id):
|
||||
pick = main.ui.modals.run.level_screen._cand_ids.find(nd_id)
|
||||
break
|
||||
main.ui.modals.run.level_screen.press_candidate(pick)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(pick).get_center())
|
||||
_route_clicks += 1
|
||||
_seals_placed.clear()
|
||||
continue
|
||||
@@ -626,15 +630,17 @@ func _ready() -> void:
|
||||
_ap_spent_this_round = 0
|
||||
_last_state = st
|
||||
_locktest_tick()
|
||||
_stoptest_tick()
|
||||
if main.run_scope.settlement.battle_result == "" and main.run_scope.settlement.action_economy.phase == "Active":
|
||||
# T1 D1——锁测在飞期(state 1)冻结探针动作面:排空帧 autoplay 的免费 move 会抢在
|
||||
# 帧末 deferred 重放之前改变英雄位(seal 拒收 not_adjacent 实证)——「注入→重放」
|
||||
# 窗口内局面零变化正是 v3.1 被测语义(演播期冻结),动作面恢复待断言收口。
|
||||
if _locktest_state != 1:
|
||||
# 停手定型化段同款冻结:核效窗口内零挖掘提交,排除 AP/盘面漂移对收轮判据的混淆。
|
||||
if _locktest_state != 1 and _stoptest_state != 1:
|
||||
_run_modal_probes_once()
|
||||
_play_one_action()
|
||||
# ---- 预算耗尽收口(T17 硬锁批适配):选件变强后 probe 可能活过预算不再战败——断点段前提
|
||||
# oracle: design/gdd/ux/level-map.md:11 (主循环选路段——入口候选组)
|
||||
# oracle: design/ux/level-map.md:11 (主循环选路段——入口候选组)
|
||||
# (冒险终局面在案)须主动满足。收口驱动:任何停点层逐个推进(复用主循环分支语义)至
|
||||
# level_screen 选路停点→真实放弃二击→终局面。战斗中则继续 autoplay 打完。
|
||||
if not _done and not main.ui.modals.run.end_overlay.is_open():
|
||||
@@ -651,14 +657,15 @@ func _ready() -> void:
|
||||
break
|
||||
elif main.ui.modals.run.curio_overlay.visible:
|
||||
if main.ui.modals.run.curio_overlay._continue_btn.visible:
|
||||
main.ui.modals.run.curio_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
|
||||
main.ui.modals.run.curio_overlay.press_card(0)
|
||||
main.ui.modals.run.curio_overlay.press_confirm()
|
||||
ProbeUiUtil.push_click_at(main, (main.ui.modals.run.curio_overlay._bcards[0] as Control).get_global_rect().get_center())
|
||||
await get_tree().process_frame # 选卡当帧确认钮才现形、容器重排在途——排版帧后矩形成形再真点
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._confirm_btn)
|
||||
else:
|
||||
main.ui.modals.run.curio_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.curio_overlay._continue_btn)
|
||||
elif main.ui.modals.run.node_overlay.visible:
|
||||
main.ui.modals.run.node_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
|
||||
elif main.ui.modals.menu.shop_overlay.visible:
|
||||
var shop_leave2: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")
|
||||
if shop_leave2 != null:
|
||||
@@ -714,7 +721,7 @@ func _ready() -> void:
|
||||
# ① 战斗收口断点写盘断言保留(上方——冒险流直调中间态 mn10)。
|
||||
|
||||
# ---- 大门两分支断言段(卡④ T5 接收——卡② 移交落点;V3 断言面)----
|
||||
# oracle: design/gdd/ux/camp.md:16 (大门段——点大门先出确认组)
|
||||
# oracle: design/ux/camp.md:16 (大门段——点大门先出确认组)
|
||||
# 驱动=营地场景根点选信号(真实路由)→组合根 _on_camp_object_pressed→确认弹窗。
|
||||
# 无断点=两出口(是/否);有断点=三出口(set_breakpoint 注入选路停点形——同下方开场行③先例)。
|
||||
# 判据绑可观测提交面:点大门不直接进冒险(is_adventure 翻转只在 confirmed 后);cancelled 恒=
|
||||
@@ -737,10 +744,30 @@ func _ready() -> void:
|
||||
_problems.append("大门取消:未无变更退回营地(写口零调用/营地根 visible 失守)〔营地图批 V3〕")
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"gate").get_center())
|
||||
await get_tree().process_frame
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.menu.confirm_dialog._confirm_btn) # 真点「确认」→开始新冒险
|
||||
await get_tree().process_frame
|
||||
# R4 位次真点(营地交互修复批——位次与语义绑定钉):真点前消费方前置读(gate_new 在册)→
|
||||
# 按类型定位按钮行 child(0) 全局矩形中心真点——位次若错接,此点命中「否」,下方主证(翻转)即红。
|
||||
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 确认弹窗位次定值:推进在左、维持居右(R4 位次真点)
|
||||
if String(main._confirm_consumer) != "gate_new":
|
||||
_problems.append("R4 前置:确认消费方非 gate_new(分派面漂移)〔营地交互批 V3〕")
|
||||
var gate_row := _first_hbox(main.ui.modals.menu.confirm_dialog)
|
||||
if gate_row == null or gate_row.get_child_count() != 3:
|
||||
_problems.append("R4:按钮行缺失或子节点数≠3(位次判据面塌)〔营地交互批 V3〕")
|
||||
else:
|
||||
ProbeUiUtil.push_click_at(main, (gate_row.get_child(0) as Control).get_global_rect().get_center()) # 真点推进钮(左位)
|
||||
for i in 3:
|
||||
await get_tree().process_frame # 停点成形帧口径(先例 3 帧——screen_flow_probe 选路停点成形窗)
|
||||
if not main.run_scope.is_adventure or main.ui.modals.camp.scene.visible:
|
||||
_problems.append("大门确认:冒险未发起或营地场景屏未隐藏〔营地图批 V3〕〔营地图批 B1〕")
|
||||
if String(main._confirm_consumer) != "":
|
||||
_problems.append("R4 主证:确认后消费方未清零〔营地交互批 V3〕")
|
||||
# 开局终态两钉(营地交互修复批——开始层隐藏收一处后的落点断言):
|
||||
# oracle: design/gdd/systems/冒险循环/关卡节点地图.md:78 关卡开始 → 呈现节点图选路停点(开局终态两钉)
|
||||
# R1=确认后开始层不可见(被测改动特有提交面——修复前该句恒红=「开局被开始层盖住」缺陷本体);
|
||||
# R2=关卡画面(选路停点)在位(开局终态呈现面,与既有 is_adventure 断言同批缺一即红)
|
||||
if main.ui.screens.start_overlay.visible:
|
||||
_problems.append("大门无断点确认后:开始层滞留可见(开局被开始层盖住缺陷回潮)〔营地交互批 V1〕")
|
||||
if not main.ui.modals.run.level_screen.visible:
|
||||
_problems.append("大门无断点确认后:关卡画面(选路停点)未在位〔营地交互批 V2〕")
|
||||
# 有断点三出口(选路停点形注入——同开场行③ set_breakpoint 先例)
|
||||
main.save_manager.set_breakpoint({
|
||||
"master_seed": 42, "rng_counter": 0, "hero_hp": 30, "gp": 10, "keystone": 0,
|
||||
@@ -978,10 +1005,13 @@ func _run_modal_probes_once() -> void:
|
||||
_tested_refresh_chain = true
|
||||
var rap_before: int = int(main.run_scope.settlement.action_economy.rap)
|
||||
var board_before = main.run_scope.settlement.minesweeper.current_board()
|
||||
main.ui.battle.board_zone.ap_bar.press_refresh_for_probe()
|
||||
ProbeUiUtil.push_click(main, main.ui.battle.board_zone.ap_bar._btn_refresh) # 真点弃图刷新(disabled 守卫=真实拒绝路径)
|
||||
if not main.ui.battle.board_zone.ap_bar.confirm_visible():
|
||||
_problems.append("弃图刷新确认 chips 未出现(异位确认缺失)")
|
||||
main.ui.battle.board_zone.ap_bar.press_confirm_for_probe()
|
||||
for c in main.ui.battle.board_zone.ap_bar._confirm_row.get_children():
|
||||
if c is Button:
|
||||
ProbeUiUtil.push_click(main, c) # 真点确认 chips 真钮(异位确认——_confirm_row 内 Button 子件)
|
||||
break
|
||||
if int(main.run_scope.settlement.action_economy.rap) != rap_before - 8:
|
||||
_problems.append("弃图刷新未扣 RAP8(%d→%d)" % [rap_before, int(main.run_scope.settlement.action_economy.rap)])
|
||||
if main.run_scope.settlement.action_economy.phase != "Active":
|
||||
@@ -1155,6 +1185,67 @@ func _locktest_pick_cell(minesweeper) -> Vector2i:
|
||||
return Vector2i(-1, -1)
|
||||
|
||||
|
||||
# ---- 停手钮定型化覆盖段(B1——每 run 一次:使能窗内真点停手钮,钉命中登记与收轮落效) ----
|
||||
# 触发条件=战局挖掘期(相位 Active+停手钮使能+盘面有挖可走+本 run 未测过);触发帧冻结
|
||||
# 自动玩家其余随机行为,仅提交停手(驱动与 _click_stop 同型——AP 条真钮 push_input)。
|
||||
# 断言两钉:①覆盖登记件停手键在场(零命中=驱动空转假绿,即红);②相位离开 Active(停手
|
||||
# 收轮)。前提消失守卫:点击后战局收场(终结演播竞态)→ 静默弃测不计 problem(正常局不触发)。
|
||||
func _stoptest_tick() -> void:
|
||||
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手=玩家主动收轮——判定上报通道)
|
||||
if _stoptest_state == 2:
|
||||
return
|
||||
var settlement = main.run_scope.settlement
|
||||
if settlement == null or settlement.minesweeper == null or settlement.minesweeper.current_board() == null:
|
||||
return
|
||||
if _stoptest_state == 0:
|
||||
if String(settlement.battle_result) != "" or main.ui.modals.run.end_overlay.is_open():
|
||||
return # 战局已收场——本 run 无停手窗(静置)
|
||||
if not main.ui.screens.row_pacer.pending_rows().is_empty():
|
||||
return # 演播期不出手:点击会进锁期缓存被吞(同 _locktest_tick 前提)
|
||||
var ae = settlement.action_economy
|
||||
if String(ae.phase) != "Active" or int(ae.ap) < 1:
|
||||
return
|
||||
var stop_btn: Button = main.ui.battle.board_zone.ap_bar._btn_stop
|
||||
if stop_btn == null or stop_btn.disabled:
|
||||
return # 停手钮未使能——下帧重试
|
||||
if AutoPlayer.choose_dig(settlement.minesweeper).is_empty():
|
||||
return # 盘面已无可挖——不算挖掘期触发窗(防清图混淆)
|
||||
_stoptest_state = 1
|
||||
_stoptest_frames = 0
|
||||
_click_stop()
|
||||
elif _stoptest_state == 1:
|
||||
_stoptest_frames += 1
|
||||
if _stoptest_frames > 120:
|
||||
_problems.append("停手定型化段:点击后 120 帧相位仍 Active(收轮落效失守)〔QA收口 V2〕")
|
||||
_stoptest_state = 2
|
||||
return
|
||||
if String(settlement.battle_result) != "" or main.ui.modals.run.end_overlay.is_open():
|
||||
_stoptest_state = 2 # 前提消失守卫:终结演播竞态——静默弃测
|
||||
return
|
||||
if String(settlement.action_economy.phase) != "Active":
|
||||
_stoptest_assert_hit()
|
||||
_stoptest_state = 2
|
||||
|
||||
|
||||
func _stoptest_assert_hit() -> void:
|
||||
# oracle: design/gdd/systems/排雷循环/排雷玩法.md:167 (停手钮真输入命中登记——判定上报通道)
|
||||
var stop_btn: Button = main.ui.battle.board_zone.ap_bar._btn_stop
|
||||
var stop_key := String(_recorder._node_keys.get(stop_btn.get_instance_id(), ""))
|
||||
if stop_key == "" or not _recorder._hits.has(stop_key):
|
||||
_problems.append("停手定型化段:停手钮真点零命中登记(驱动空转假绿)〔QA收口 V2〕")
|
||||
|
||||
|
||||
func _first_hbox(node: Node) -> HBoxContainer:
|
||||
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 (位次真点定位——R4 判据面辅助)
|
||||
if node is HBoxContainer:
|
||||
return node
|
||||
for ch in node.get_children():
|
||||
var found := _first_hbox(ch)
|
||||
if found != null:
|
||||
return found
|
||||
return null
|
||||
|
||||
|
||||
func _report_stall(st: Dictionary) -> void:
|
||||
# oracle: design/engineering/test-conventions.md:30 (判定上报通道——验证函数组)
|
||||
var why := ""
|
||||
|
||||
@@ -301,7 +301,7 @@ func _ready() -> void:
|
||||
if not ms.is_revealed(dig_t):
|
||||
_problems.append("case⑥ 核心揭格未落位(目标=%s)" % dig_t)
|
||||
# ---- case⑦ 英雄精灵世界落位(回归闸):精灵在 SubViewport 世界内,
|
||||
# oracle: design/gdd/ux/battle.md:154 主角站桩立绘带落影、贴脚与缩放档(case⑦-⑩ 呈现落位)
|
||||
# oracle: design/ux/battle.md:154 主角站桩立绘带落影、贴脚与缩放档(case⑦-⑩ 呈现落位)
|
||||
# 落位 = 格×步距 + 脚底锚(横=格中线/纵=格底往上 25% 线=HERO_GROUND_FRAC 自底边口径——「脚踩
|
||||
# 格子 1/4 处·从下往上数」;禁带壳层 _svp_container.position 居中偏移——上批误用壳层
|
||||
# cell_origin 实测右偏 ~7 格+偏离格心,实机截图用户报缺陷)。素材缺位树(hero==null,字形
|
||||
|
||||
+11
-11
@@ -16,7 +16,7 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
var problems: Array = []
|
||||
# ---- 放弃入口(台账 119):默认隐藏(非冒险模式无冒险可放弃——开始层/调试两层)----
|
||||
# oracle: design/gdd/ux/level-map.md:59 放弃冒险入口=边缘动作、任意选路停点承载——非冒险模式无入口(默认隐藏)
|
||||
# oracle: design/ux/level-map.md:59 放弃冒险入口=边缘动作、任意选路停点承载——非冒险模式无入口(默认隐藏)
|
||||
if main.ui.modals.run.level_screen.abandon_btn == null or main.ui.modals.run.level_screen.abandon_btn.visible:
|
||||
print("[ui_probe] FAIL — 放弃按钮默认应隐藏(非冒险模式关)")
|
||||
get_tree().quit(1)
|
||||
@@ -24,7 +24,7 @@ func _ready() -> void:
|
||||
# ---- 营地覆盖层(主菜单入口/开合/余额文案/购买禁用态;
|
||||
# 隔离存档换注 main.meta 保确定性(禁碰真实档 user://save.json——SaveManager 路径注入纪律;
|
||||
# 营地先行段只换 meta 不换 save_manager——冒险段换注零档在前)----
|
||||
# oracle: design/gdd/ux/camp.md:45 五功能窗统一窗壳、五窗互斥单开、各窗自带关闭钮(营地开合组)
|
||||
# oracle: design/ux/camp.md:45 五功能窗统一窗壳、五窗互斥单开、各窗自带关闭钮(营地开合组)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_ui_probe_camp", false, false, false)
|
||||
if not await ProbeUiUtil.open_camp_via_savesel(main, "smoke_ui_probe_camp"):
|
||||
problems.append("营地入口缺失(选档层新档钮——mn9 新路径驱动中断)〔句柄子容器批 V1〕")
|
||||
@@ -36,12 +36,12 @@ func _ready() -> void:
|
||||
problems.append("营地层打开时开始层未隐藏(规范 2.3 遮挡纪律)〔句柄子容器批 V1〕")
|
||||
else:
|
||||
# 天赋树子界面(场景点选真实路由——木桩命中区进树界面;卡④ T5 深员改指)
|
||||
# oracle: design/gdd/ux/camp.md:20 木桩命中区→天赋树界面入口(场景点选路由)
|
||||
# oracle: design/ux/camp.md:20 木桩命中区→天赋树界面入口(场景点选路由)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"dummy").get_center())
|
||||
await get_tree().process_frame
|
||||
if not main.ui.modals.camp.tree.visible:
|
||||
problems.append("天赋树子界面未打开(场景点选路由中断)〔营地图批 V2〕")
|
||||
# oracle: design/gdd/ux/camp.md:41 钥石余额常驻画面顶部 HUD 栏(余额文案组)
|
||||
# oracle: design/ux/camp.md:41 钥石余额常驻画面顶部 HUD 栏(余额文案组)
|
||||
if not String(main.ui.modals.camp.tree._balance_label.text).begins_with("钥石"):
|
||||
problems.append("营地余额文案缺失(%s)〔句柄子容器批 V1〕" % main.ui.modals.camp.tree._balance_label.text)
|
||||
if main.ui.modals.camp.tree._tree_buy_btns.is_empty():
|
||||
@@ -57,7 +57,7 @@ func _ready() -> void:
|
||||
ProbeUiUtil.push_click(main, ProbeUiUtil.find_button(main.ui.modals.camp.tree, "返回营地")) # 真点底栏返回钮→_emit_closed(camp_sub_view_base.gd:66-84)
|
||||
await get_tree().process_frame
|
||||
# 秘宝匣子界面(场景点选真实路由——宝箱命中区)
|
||||
# oracle: design/gdd/ux/camp.md:18 宝箱命中区→秘宝匣界面入口
|
||||
# oracle: design/ux/camp.md:18 宝箱命中区→秘宝匣界面入口
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.camp.scene.get_hotspot_rect(&"chest").get_center())
|
||||
await get_tree().process_frame
|
||||
if main.ui.modals.camp.treasure._workshop_rows.size() != 12:
|
||||
@@ -75,8 +75,8 @@ func _ready() -> void:
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
# ---- 入口选路停点(关卡开始即出节点图——level_started 后、首战前弹选路层;
|
||||
# press_candidate 图上命中路径 pick 入口单候选进首战)----
|
||||
# oracle: design/gdd/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言)
|
||||
# candidate_rect 命中矩形真点 pick 入口单候选进首战)----
|
||||
# oracle: design/ux/level-map.md:11 入口停点候选数=入口个数(入口单候选断言)
|
||||
# ---- 独立性断言(独立屏激活期战斗屏宿主整体隐藏,零透出)----
|
||||
if not main.ui.modals.run.level_screen.visible:
|
||||
problems.append("关卡开始未弹入口选路停点(level_screen 应可见)")
|
||||
@@ -86,13 +86,13 @@ func _ready() -> void:
|
||||
or main.run_scope.runner.next_candidate_ids().size() != 1:
|
||||
problems.append("入口停点状态失守(选路态 + 入口单候选)")
|
||||
else:
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
if not main.ui.screens.battle_root.visible or main.ui.modals.run.level_screen.visible:
|
||||
problems.append("选路提交战斗节点后未切回战斗屏(屏级互斥恢复失守)")
|
||||
# ---- 放弃入口·冒险模式可见 + 战斗中按下被拒(真实按钮路径——入口挂选路停点,战斗中拒绝为防御分支)----
|
||||
# oracle: design/gdd/ux/level-map.md:42 放弃冒险(任意选路停点)→ 冒险落幕——战斗域单事务在飞时拒绝
|
||||
# oracle: design/ux/level-map.md:42 放弃冒险(任意选路停点)→ 冒险落幕——战斗域单事务在飞时拒绝
|
||||
if main.ui.modals.run.level_screen.abandon_btn == null or not main.ui.modals.run.level_screen.abandon_btn.visible:
|
||||
problems.append("冒险模式放弃按钮未开放(选路停点入口在案)")
|
||||
else:
|
||||
@@ -101,7 +101,7 @@ func _ready() -> void:
|
||||
if main.run_scope.adventure == null or String(main.run_scope.adventure.state) != "running":
|
||||
problems.append("首战中放弃未被拒(战斗域单事务在飞——state 应保持 running)")
|
||||
# ---- 既有断言(回归基线;A 区无进度条——节点链进度由关卡画面独立屏承载)----
|
||||
# oracle: design/gdd/ux/battle.md:122 B 区构建组:主角与敌人站桩、明细带挖掘期收起、轮次指示、棋盘与 C 区/秘宝槽、首战进行中
|
||||
# oracle: design/ux/battle.md:122 B 区构建组:主角与敌人站桩、明细带挖掘期收起、轮次指示、棋盘与 C 区/秘宝槽、首战进行中
|
||||
if main.ui.battle.board_zone.board_view.cells_probe().is_empty():
|
||||
problems.append("棋盘格未生成(cells=0)")
|
||||
if main.ui.battle.board_zone.board_view.board() == null:
|
||||
@@ -237,7 +237,7 @@ func _ready() -> void:
|
||||
# ---- GP 终局行:summary gp_final 键取数+胜利面板 GP 行呈现(C5:GP 行只上冒险终局面板;
|
||||
# 断言面 = summary 键,无影子账清零转存)----
|
||||
# (置于战斗态断言之后——合成收束会灰化站桩并清 settlement 引用,具污染性)
|
||||
# oracle: design/gdd/ux/settlement-summary.md:20 战利品行=GP 增量+总量(冒险终局面板 GP 行组)
|
||||
# oracle: design/ux/settlement-summary.md:20 战利品行=GP 增量+总量(冒险终局面板 GP 行组)
|
||||
var meta_saved = main.meta.save
|
||||
for p2 in ["user://smoke_ui_probe_settle_scratch.json", "user://smoke_ui_probe_settle_scratch.json.tmp"]:
|
||||
if FileAccess.file_exists(p2):
|
||||
|
||||
@@ -89,7 +89,7 @@ func _ready() -> void:
|
||||
# ---- 生涯页:入口=帐篷旁笔记本(卡④ T3 改挂——场景点选信号真实路由→组合根 _open_career)→
|
||||
# 结构断言族→只读巡检(契约 3)+零动效契约+零跳转契约(提纲 P4 甲)→组合根真实路径关闭。
|
||||
#(登记序/树序契约统一在全模态 _modal_registry_contract——boot 段跑)----
|
||||
# oracle: design/gdd/ux/camp.md:19 帐篷旁笔记本→生涯记录;生涯页返回钮口径由结构校验兜底(生涯段)
|
||||
# oracle: design/ux/camp.md:19 帐篷旁笔记本→生涯记录;生涯页返回钮口径由结构校验兜底(生涯段)
|
||||
# 营地语境:开始层全程隐藏(笔记本入口在营地场景根——关生涯回营地不回开始层,卡④ T3 语境分支)
|
||||
main._camp_pres.open_camp() # 重开营地场景根(笔记本入口语境)
|
||||
_settle()
|
||||
@@ -106,7 +106,7 @@ func _ready() -> void:
|
||||
if main.ui.modals.menu.career_overlay.visible or main.ui.screens.start_overlay.visible:
|
||||
problems.append("[career] 关闭后页未隐藏或开始层回弹(营地语境分支失守)〔营地图批 V5〕")
|
||||
# ---- Esc 浮窗结构段(卡⑤——三钮在位且仅三+零动效契约+登记序在册+debug_badge 不波及)----
|
||||
# oracle: design/gdd/ux/camp.md:45 ESC 优先序:有窗先关最上层窗、无窗才进浮窗;各窗自带关闭钮(Esc 段)
|
||||
# oracle: design/ux/camp.md:45 ESC 优先序:有窗先关最上层窗、无窗才进浮窗;各窗自带关闭钮(Esc 段)
|
||||
main._camp_pres.open_camp()
|
||||
_settle()
|
||||
var gpos_gate: Vector2 = main.ui.modals.camp.scene.get_hotspot_rect(&"gate").get_center()
|
||||
@@ -213,7 +213,7 @@ func _ready() -> void:
|
||||
_settle()
|
||||
|
||||
# ---- 冒险簇(真流推进:开冒险→地图选路态→选战斗节点→战斗 HUD)----
|
||||
# oracle: design/gdd/ux/level-map.md:3 图上点击选路·候选按钮退役(冒险簇选路真点组)
|
||||
# oracle: design/ux/level-map.md:3 图上点击选路·候选按钮退役(冒险簇选路真点组)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_ui_struct_probe_adv", true, false, true)
|
||||
main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(种子框默认为空=非首局随机,结构采样确定性禁依赖默认分支)
|
||||
main._start_adventure_from_menu()
|
||||
@@ -272,7 +272,7 @@ func _ready() -> void:
|
||||
if not main.ui.modals.run.node_overlay.visible:
|
||||
problems.append("[node] 节点结算层未打开(rest 兑付路径中断)")
|
||||
await _audit("node_overlay", [main.ui.modals.run.node_overlay])
|
||||
main.ui.modals.run.node_overlay.press_continue()
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
|
||||
_settle()
|
||||
if main.ui.modals.run.node_overlay.visible:
|
||||
problems.append("[node] 关闭后层未隐藏(continue_pressed 路径中断)")
|
||||
@@ -648,7 +648,7 @@ func _modal_registry_contract(main: Node) -> void:
|
||||
|
||||
# ⑬career 零跳转契约(提纲 P4 甲——页内零「前往他处」入口:除「返回」外任何导航钮=红)
|
||||
func _career_nojump_contract(main: Node) -> void:
|
||||
# oracle: design/gdd/ux/camp.md:19 生涯记录入口与返回钮口径由结构校验兜底——页内零导航入口(零跳转契约组)
|
||||
# oracle: design/ux/camp.md:19 生涯记录入口与返回钮口径由结构校验兜底——页内零导航入口(零跳转契约组)
|
||||
for nav in ["营地", "商店", "设置", "冒险", "继续上局", "退出", "前往"]:
|
||||
if ProbeUiUtil.find_button(main.ui.modals.menu.career_overlay, nav, "contains") != null:
|
||||
problems.append("[career][nojump] 页内出现导航入口「%s」(零跳转违约——生涯纯只读)" % nav)
|
||||
@@ -658,7 +658,7 @@ func _career_nojump_contract(main: Node) -> void:
|
||||
# (名行基线离盒底 1px 同型高危写法返修回归闸;
|
||||
# 内容矩形=StandeeBody.content_rect(),最低绘制元素=底座下缘)
|
||||
func _standeefit_contract(main: Node) -> void:
|
||||
# oracle: design/gdd/ux/battle.md:154 主角站桩立绘(无框立绘+落影)——站桩内容矩形⊆舞台(站桩适配组)
|
||||
# oracle: design/ux/battle.md:154 主角站桩立绘(无框立绘+落影)——站桩内容矩形⊆舞台(站桩适配组)
|
||||
var st: Node = main.ui.battle.stage
|
||||
if st == null or st.hero_standee == null:
|
||||
problems.append("[standee] 舞台/主角站桩缺失(断言中断)")
|
||||
|
||||
@@ -58,16 +58,47 @@ func _ready() -> void:
|
||||
problems.append("生效预览未联动(%s)" % main.ui.modals.menu.debug_overlay.effect_text())
|
||||
# ③ 应用(真实信号路径:apply_requested → 组合根)——E4 后加成零表写,生效断言移至 ⑧ 战斗快照
|
||||
# oracle: design/engineering/test-conventions.md:49 调试面加成链路:滑条联动/应用/重置、hero 表运行期只读(②③④⑦组)
|
||||
main.ui.modals.menu.debug_overlay.press_apply_for_probe()
|
||||
var apply_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "应用并关闭")
|
||||
if apply_btn == null:
|
||||
problems.append("调试浮窗「应用并关闭」钮缺失")
|
||||
else:
|
||||
ProbeUiUtil.push_click(main, apply_btn) # 真点应用(组合根应用=关浮窗——落点断言由 ④ 重开承载)
|
||||
# ④ 重置(重开浮窗 → 重置按钮真实路径;面板百分比归零——下一场战斗生效值回到基线)
|
||||
main._show_debug_overlay()
|
||||
main.ui.modals.menu.debug_overlay.press_reset_for_probe()
|
||||
var reset_btn: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "重置为 0")
|
||||
if reset_btn == null:
|
||||
problems.append("调试浮窗「重置为 0」钮缺失")
|
||||
else:
|
||||
ProbeUiUtil.push_click(main, reset_btn)
|
||||
# ⑤ 批量模拟全链路(N=10——走 level_autoplay 与 headless 扫描同口径;表基线由组合根复位;
|
||||
# 菜单态上下文未锁定=按钮可点,is_running 语义=模拟在跑〔18卡⑦ 与对局禁用态拆分〕)
|
||||
# oracle: design/engineering/test-conventions.md:49 调试面批量模拟链路:N=10 输出关卡完成率/剩余 HP/败北节点(⑤组)
|
||||
main.ui.modals.menu.debug_overlay.set_sliders(100, 0)
|
||||
main.ui.modals.menu.debug_overlay.set_sim_count(10)
|
||||
main.ui.modals.menu.debug_overlay.press_simulate_for_probe()
|
||||
# 折叠区控件真输入通道(test-conventions §2——先滚轮滚入可视区再点):sim 钮在滚动列表 boot
|
||||
# 滚位下跨出视口底边(rect y=639 对 scroll 底 641、钮高 40——hit 面被裁),真点必落空;
|
||||
# 滚轮下探滚入可视区后真点(实证一档入视口、同帧 is_running 翻真)
|
||||
var sim_btn: Button = main.ui.modals.menu.debug_overlay._sim_btn
|
||||
var sim_scroll: ScrollContainer = null
|
||||
var sim_stack: Array = [main.ui.modals.menu.debug_overlay]
|
||||
while not sim_stack.is_empty():
|
||||
var sn: Node = sim_stack.pop_back()
|
||||
if sn is ScrollContainer:
|
||||
sim_scroll = sn
|
||||
break
|
||||
for sch in sn.get_children():
|
||||
sim_stack.append(sch)
|
||||
if sim_scroll == null:
|
||||
problems.append("调试浮窗滚动列表缺失(sim 钮滚入通道无载体)")
|
||||
else:
|
||||
for i in 12:
|
||||
if sim_scroll.get_global_rect().encloses(sim_btn.get_global_rect()):
|
||||
break
|
||||
ProbeUiUtil.push_wheel(main, sim_scroll.get_global_rect().get_center(), 1)
|
||||
await get_tree().process_frame
|
||||
if not sim_scroll.get_global_rect().encloses(sim_btn.get_global_rect()):
|
||||
problems.append("sim 钮滚轮滚入可视区失败(12 档未入——折叠区真输入通道断)")
|
||||
ProbeUiUtil.push_click(main, sim_btn) # 真点「运行模拟」(菜单态未锁可点——disabled 守卫=真实拒绝路径)
|
||||
var guard := SIM_GUARD_FRAMES
|
||||
while main.ui.modals.menu.debug_overlay.is_running() and guard > 0:
|
||||
guard -= 1
|
||||
@@ -97,7 +128,12 @@ func _ready() -> void:
|
||||
# isolate_save 先删后建——上次运行的残留断点会触发互斥确认早退)
|
||||
ProbeUiUtil.isolate_save(main, "smoke_tune_probe_save", true, false, true) # 末参=swap_save(卡②签名换代——断点写盘经 autoload 落隔离档)
|
||||
main.ui.modals.menu.debug_overlay.set_sliders(100, 50)
|
||||
main.ui.modals.menu.debug_overlay.press_apply_for_probe()
|
||||
main._show_debug_overlay() # ⑥ 关闭后重开——⑧ 应用真点(应用=组合根关窗,⑧b 显式关浮窗断言照旧承载)
|
||||
var apply_btn2: Button = ProbeUiUtil.find_button(main.ui.modals.menu.debug_overlay, "应用并关闭")
|
||||
if apply_btn2 == null:
|
||||
problems.append("调试浮窗「应用并关闭」钮缺失(⑧)")
|
||||
else:
|
||||
ProbeUiUtil.push_click(main, apply_btn2)
|
||||
main.ui.modals.menu.debug_overlay.seed_edit.text = "42" # 种子钉死(18卡⑦审计🔴——空框=非首局随机,显式优先经真实路径断言 master_seed)
|
||||
main._start_adventure_from_menu() # 完整冒险路径(唯一试玩入口;_start_single 按语义清零滑条 = 恒基线,不承载加成断言)
|
||||
await get_tree().process_frame
|
||||
@@ -107,9 +143,9 @@ func _ready() -> void:
|
||||
problems.append("开冒险路径未显式关浮窗(T2 防护失守)")
|
||||
if int(main.run_scope.adventure.master_seed) != 42:
|
||||
problems.append("显式种子未达冒险(master_seed=%s ≠ 42——真实路径显式优先失守)" % str(main.run_scope.adventure.master_seed))
|
||||
# 关卡开始 = 入口选路停点——真实按钮路径选入口候选进首战再读呈现快照
|
||||
# 关卡开始 = 入口选路停点——candidate_rect 命中矩形真点选入口候选进首战再读呈现快照
|
||||
if main.ui.modals.run.level_screen.visible:
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
await get_tree().process_frame
|
||||
await get_tree().process_frame
|
||||
var hero: Dictionary = main.run_scope.settlement.presentation_snapshot().hero
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
## 运行:Godot_console.exe --headless --path . --script res://sim/compendium_dump.gd -- [out=<绝对路径>] [tables_root=<res:// 路径>]
|
||||
## out 缺省 = 仓库根 sim-data/compendium/dump.json(res:// 的上一级);目录不存在则创建。
|
||||
## tables_root 缺省 = res://data/tables;fail-fast 路径验证时可指向临时表目录。
|
||||
## 导出形态:顶层 counts 带两表行数计数 + enemy_rows/skill_rows 行字典原样(零新增字段、零改名;
|
||||
## 表内可省略字段缺席即缺席)。本件零随机、零数值硬编码、零判定逻辑。
|
||||
## 导出形态:顶层 counts 带两表行数计数 + enemy_rows/skill_rows 行字典原样,elite 行另补合并后的
|
||||
## pool 供展示(表内行零改写;battle/boss 行=自身 pool 原样;表内可省略字段缺席即缺席)。
|
||||
## 本件零随机、零数值硬编码、零判定逻辑。
|
||||
## fail-fast:表加载失败(缺表/行域校验/跨表悬空引用)、行缺关键字段、pool.skill_id 在技能表无对应行
|
||||
## → 逐条报错并以退出码 1 中止,禁软回退。
|
||||
extends SceneTree
|
||||
@@ -29,15 +30,23 @@ func _initialize() -> void:
|
||||
return
|
||||
var enemy_rows: Array = db.combat.enemy.rows
|
||||
var skill_rows: Array = db.combat.skill.rows
|
||||
var errs := _validate_rows(enemy_rows, skill_rows)
|
||||
# 导出行数组:逐行 duplicate——elite 行副本写合并后的 pool(按需现算,表内行不被改写);
|
||||
# battle/boss 行=自身 pool 原样(读法等价面)
|
||||
var export_enemy_rows: Array = []
|
||||
for row_v in enemy_rows:
|
||||
var row: Dictionary = (row_v as Dictionary).duplicate()
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
row["pool"] = db.combat.enemy.effective_pool(row_v)
|
||||
export_enemy_rows.append(row)
|
||||
var errs := _validate_rows(export_enemy_rows, skill_rows)
|
||||
if not errs.is_empty():
|
||||
for e in errs:
|
||||
push_error("compendium_dump: %s" % e)
|
||||
quit(1)
|
||||
return
|
||||
var payload := {
|
||||
"counts": {"enemy_rows": enemy_rows.size(), "skill_rows": skill_rows.size()},
|
||||
"enemy_rows": enemy_rows,
|
||||
"counts": {"enemy_rows": export_enemy_rows.size(), "skill_rows": skill_rows.size()},
|
||||
"enemy_rows": export_enemy_rows,
|
||||
"skill_rows": skill_rows,
|
||||
}
|
||||
var out_path := String(opt.out)
|
||||
|
||||
+42
-16
@@ -10,9 +10,9 @@
|
||||
# 真实输入-场景热点/真实输入-键盘/方法-地图命中口/方法-棋盘口)。原归因制度残骸=本结构内字段。
|
||||
# 误报收敛:误报指纹(类+段+归因摘要)落 .gates/coverage/journey_misfire_ledger.json——同一指纹
|
||||
# 第 2 次起自动登记静默(计数照记、不再进上报清单);连 3 批零真缺陷判据收紧归用户复核轮。
|
||||
# 驱动纪律:按钮/场景物件全走真输入(push_input 管线,同探针判据——ProbeUiUtil 单点通道)。
|
||||
# 两处方法口如实标注通道:地图候选=press_candidate(自绘布局命中路径,探针族既定入口);
|
||||
# 棋盘格=click_cell(BoardView 公共点击口,ui_input_probe 先例——棋盘管线面由该探针承载)。
|
||||
# 驱动纪律:按钮/场景物件/地图候选全走真输入(push_input 管线,同探针判据——ProbeUiUtil 单点通道;
|
||||
# 地图候选=LevelMapScreen.candidate_rect 命中矩形中心 push_click_at——方法口已清零)。
|
||||
# 棋盘格=click_cell(BoardView 公共点击口,ui_input_probe 先例——棋盘管线面由该探针承载)=余下唯一方法口。
|
||||
# 停点分发的视图内部读数(_continue_btn/_node_pending 等)沿用探针族直读受控例外(iter53 反哺点 9 同款在册)。
|
||||
# 运行:Godot_console.exe --headless --path . res://sim/journey_player.tscn
|
||||
extends Node
|
||||
@@ -41,6 +41,10 @@ var _map_status_done := false # 地图屏状态角标一次性
|
||||
var _legend_done := false # 图例开合一次性(每战斗面一次)
|
||||
var _status_done := false # 战斗屏状态面板一次性
|
||||
var _refresh_done := false # 刷新确认链一次性
|
||||
var _stop_done := false # 停手行为一次性(每战至多一次——挖掘期确定性触发)
|
||||
var _stop_settlement: Variant = null # 停手基线战局(换战局重置一次性)
|
||||
var _stop_round := -1 # 停手轮次基线(轮换重置已花计数)
|
||||
var _digs_this_round := 0 # 本轮已提交挖掘数(停手条件——AP 已花 ≥1)
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
@@ -376,11 +380,11 @@ func _phase_adventure() -> void:
|
||||
_record_screens()
|
||||
var st := _screen_state()
|
||||
if bool(st["节点"]):
|
||||
_last_action = "节点层「继续」(方法-视图口)"
|
||||
main.ui.modals.run.node_overlay.press_continue()
|
||||
_last_action = "真点节点层「继续」钮"
|
||||
ProbeUiUtil.push_click(main, main.ui.modals.run.node_overlay._btn)
|
||||
continue
|
||||
if bool(st["三选一"]):
|
||||
_drive_curio()
|
||||
await _drive_curio()
|
||||
continue
|
||||
if bool(st["商店"]):
|
||||
var lv: Button = ProbeUiUtil.find_button(main.ui.modals.menu.shop_overlay, "离开商店")
|
||||
@@ -437,30 +441,42 @@ func _drive_map_once() -> void:
|
||||
var resumed: Button = ProbeUiUtil.find_button(main.ui.modals.run.status_panel, "继续", "prefix")
|
||||
if resumed != null and main.ui.modals.run.status_panel.visible:
|
||||
ProbeUiUtil.push_click(main, resumed)
|
||||
_last_action = "地图候选命中(方法-地图命中口)"
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
_last_action = "地图候选命中(真实输入-命中矩形)"
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
|
||||
|
||||
func _drive_curio() -> void:
|
||||
# 三选一停点:待选态点首件+确认;兑付/已消费态点继续(方法-视图口——curio 停点既定驱动面)
|
||||
# 三选一停点:待选态真点首卡+确认(gui_input 点选——确认钮现形当帧容器重排在途,排版帧后再点);
|
||||
# 兑付/已消费态真点继续
|
||||
var co: Control = main.ui.modals.run.curio_overlay
|
||||
_last_action = "三选一停点分发(方法-视图口)"
|
||||
_last_action = "三选一停点分发(真输入)"
|
||||
if co._continue_btn.visible:
|
||||
co.press_continue()
|
||||
ProbeUiUtil.push_click(main, co._continue_btn)
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._node_pending.is_empty():
|
||||
co.press_card(0)
|
||||
co.press_confirm()
|
||||
ProbeUiUtil.push_click_at(main, (co._bcards[0] as Control).get_global_rect().get_center())
|
||||
await _frames(2)
|
||||
ProbeUiUtil.push_click(main, co._confirm_btn)
|
||||
elif main.run_scope.adventure != null and not main.run_scope.adventure.stop()._pending.is_empty():
|
||||
co.press_card(0)
|
||||
co.press_confirm()
|
||||
ProbeUiUtil.push_click_at(main, (co._bcards[0] as Control).get_global_rect().get_center())
|
||||
await _frames(2)
|
||||
ProbeUiUtil.push_click(main, co._confirm_btn)
|
||||
else:
|
||||
co.press_continue()
|
||||
ProbeUiUtil.push_click(main, co._continue_btn)
|
||||
|
||||
|
||||
func _drive_battle_once() -> void:
|
||||
# 战斗单步:挂死检测+功能钮真点(图例/状态/刷新确认链/停手)+棋盘挖格(方法-棋盘口)
|
||||
var settlement = main.run_scope.settlement
|
||||
var diag: Dictionary = settlement.diagnostic_snapshot()
|
||||
if settlement != _stop_settlement: # 换战局——停手一次性与轮内计数重置
|
||||
_stop_settlement = settlement
|
||||
_stop_done = false
|
||||
_stop_round = -1
|
||||
_digs_this_round = 0
|
||||
var round_now := int(diag.round)
|
||||
if round_now != _stop_round:
|
||||
_stop_round = round_now
|
||||
_digs_this_round = 0
|
||||
var st := {
|
||||
"round": diag.round, "phase": diag.action_economy.phase, "ap": diag.action_economy.ap,
|
||||
"revealed": diag.minesweeper_revealed,
|
||||
@@ -514,6 +530,15 @@ func _drive_battle_once() -> void:
|
||||
# 棋盘动作(方法-棋盘口——BoardView 公共点击口,ui_input_probe 先例;C 的按钮面全走真输入)
|
||||
var minesweeper = settlement.minesweeper
|
||||
var plan: Dictionary = AutoPlayer.choose_dig(minesweeper)
|
||||
# 停手行为(每战至多一次——挖掘期确定性触发,无随机占比;触发时点微调在代拍板白名单):
|
||||
# 相位 Active+停手钮使能+AP 已花 ≥1+盘面有挖可走 → 真点停手钮收轮(真输入行为之一)
|
||||
if not _stop_done and String(diag.action_economy.phase) == "Active" \
|
||||
and not ap_bar._btn_stop.disabled and _digs_this_round >= 1 and not plan.is_empty():
|
||||
_stop_done = true
|
||||
_last_action = "真点「停手」钮(挖掘期定型化触发)"
|
||||
ProbeUiUtil.push_click(main, ap_bar._btn_stop)
|
||||
await _frames(2)
|
||||
return
|
||||
if plan.is_empty():
|
||||
plan = AutoPlayer.gamble_pick(minesweeper)
|
||||
if plan.is_empty():
|
||||
@@ -525,6 +550,7 @@ func _drive_battle_once() -> void:
|
||||
if plan.hero_cell != minesweeper.hero_pos():
|
||||
main.ui.battle.board_zone.board_view.click_cell(plan.hero_cell, MOUSE_BUTTON_LEFT)
|
||||
main.ui.battle.board_zone.board_view.click_cell(plan.dig_cell, MOUSE_BUTTON_LEFT)
|
||||
_digs_this_round += 1
|
||||
await _frames(1)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
uid://c40h84iqkhwep
|
||||
@@ -57,13 +57,13 @@
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"单元与集成测试": {
|
||||
"闸壳自检": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
},
|
||||
"闸壳自检": {
|
||||
"单元测试": {
|
||||
"exit": "0",
|
||||
"fp": "green"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+37
-37
@@ -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` |
|
||||
|
||||
@@ -139,13 +135,14 @@ tests/
|
||||
| 模态句柄分域寻址面 | HUD框架 | 局内/菜单/营地三子容器句柄经注册面合同寻址(`UiRegistry.node()` 逐键取层)+模态编排口开合(`ModalFlow.open()`/`ModalFlow.close()`) | `ui_struct_probe.gd:_modal_registry_contract`(registry 合同序段+分域句柄段) |
|
||||
| 未开格类型微光(8 邻作用集/五组色相/零玩法写入) | HUD框架 | `BoardView.type_glow_probe()`/`UiKit.glow_group_for_cell_type()` | `theme_projection_test.gd:_smoke_type_glow`;`type_glow_test.gd:_test_grouping_closed`;`type_glow_test.gd:_test_frozen_hue_face` |
|
||||
| 棋盘悬停反馈(三语义光标/目标格边框/路径预览点迹) | HUD框架 | `ui_input_probe` 合成鼠标事件→`BoardView._gui_input()`;读面 `BoardView.hover_probe()` | `ui_input_probe.gd:_ready`(case①到达②禁入③路径预览三语义) |
|
||||
| UI 全局主题与按钮五态(含对比度锚) | HUD框架 | `UiKit.button()`/`UiKit.primary_button()` | `theme_projection_test.gd:_smoke_global_theme`;`theme_projection_test.gd:_smoke_button_five_states`;`theme_projection_test.gd:_smoke_contrast_inks` |
|
||||
| UI 全局主题与按钮五态(含对比度锚) | HUD框架 | `UiKit.button()`/`UiKit.primary_button()` | `theme_projection_test.gd:_smoke_global_theme`;`theme_projection_test.gd:_smoke_button_five_states`;`theme_projection_test.gd:_smoke_contrast_inks`;`palette_token_pairwise_test.gd:_test_pairwise`(色板近色纪律存量面) |
|
||||
| 中文字体随包与字形覆盖 | HUD框架 | `UiKit.load_font()` | `ui_font_asset_test.gd:_init`;`theme_projection_test.gd:_smoke_global_theme` |
|
||||
| 地形瓦片套与格面数字可读 | HUD框架 | `TilePalette.digit_ink_mode()`/`TilePalette.normalize_floor_luma()` | `terrain_tile_sets_test.gd:_test_set_completeness`;`terrain_tile_sets_test.gd:_test_hue_domains`;`terrain_tile_sets_test.gd:_test_digit_ink_modes` |
|
||||
| 站桩敌人呈现与结算演出落点 | 战斗表现 | `BattleStageView.enemy_standee_center()`/`BattleEndOverlay.open_victory()` | `standee_fx_sweep_test.gd:_init`;`pacing_queue_test.gd:_init` |
|
||||
| 站桩呈现定值与素材绑定 | 战斗表现 | `UiKit.load_tex()`/`StandeeBody.name_line_probe()` | `standee_present_values_test.gd:_init` |
|
||||
| 站桩显示档与素材分辨率绑定 | 战斗表现 | `StandeeBody.portrait_probe()`/`StandeeMotion.PORTRAIT_DISPLAY_H` | `standee_present_values_test.gd:_check_display_tier` |
|
||||
| B 区舞台背景三关变体与连续地面层(按 board_type 选图/地面层高度读布局表/四柱光柱退役) | HUD框架 | `BattleStageView.set_board_type()` | `stage_background_test.gd:_test_background_selection`;`stage_background_test.gd:_test_ground_layer_geometry`;`stage_background_test.gd:_test_beams_retired` |
|
||||
| 动画帧数规格(待机/攻击/受击;现役单帧形制锚定) | 战斗表现 | `UiKit.load_tex()`(`TEX_HERO_*` 四态件装载链——棋盘站立/行走与舞台三态同源) | `animation_frame_spec_test.gd:_test_spec_table`;`animation_frame_spec_test.gd:_test_current_form` |
|
||||
| 战斗屏三区几何与棋盘格边长(分辨率两档×四档板型) | HUD框架 | `BoardView.set_cell_area()`(D 区可用面积 → 格边长);读面 `BoardView.cell_size` | `layout_probe.gd:_ready`;`board_gen_reward_test.gd:_smoke_layout_cell_formula` |
|
||||
| 调参测速批量模拟(逐帧步进与统计读数) | 调参测量 | `TuneSimController.run()`(调试浮窗批量模拟入口) | `tune_sim_batch_parity_test.gd:_init`(配对对拍套件) |
|
||||
| sim 驱动循环单点(缺省零变化与策略/日志回调形态) | 调参测量 | `BattleDriver.drive_round()`(两参缺省与五参回调两形态) | `battle_driver_drive_test.gd:_smoke_drive_round_contract` |
|
||||
@@ -160,7 +157,7 @@ tests/
|
||||
| 四柜台拆解后五界面入口改挂与写口零回归 | 营地五界面 | 五界面意图信号 → `CampShopPresenter` → `MetaProgression.tree_buy()` 写口 | `forge_workbench_test.gd:_run`(承载类同迁断言十二项);`ui_click_probe.gd:_ready`(写口链段) |
|
||||
| 营地篝火回主菜单与笔记本改挂动线 | 营地交互流 | 笔记本命中区真点(`push_click_at`)→ `CareerOverlay.open()`;篝火命中区真点 → ConfirmDialog 真钮二次确认 → 主菜单(2026-11-06 真输入化) | `ui_struct_probe.gd:_ready`(career 段);`screen_flow_probe.gd:_ready`(篝火/开合段——真输入驱动) |
|
||||
| 确认弹窗按钮位次与语义同钉 | HUD框架 | `ConfirmDialog.open()`/`ConfirmDialog.open_three()` 开态读按钮行子节点序(定值=推进钮在左/次推进居中/维持钮在右,四处共用件同一序);探针在弹窗开态取按钮行 child(0) 的全局矩形中心经 `ProbeUiUtil.push_click_at` 真点 | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order`;`ui_click_probe.gd:_ready`(大门无断点段推进钮坐标真点) |
|
||||
| 营地顶栏栏体定值与落座 | 营地场景视图 | 真实工程 boot 后经装配链 `MainUiBuild`→`CampSceneView.set_hud_band()` 读 `ui.modals.camp.scene.hud_band`(定值=`CAMP_HEADER_BAND`×α0.80、48px 全宽浮层、徽章右位顶格);单元经 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口读背景贴图落位带像素算对比 ≥3:1 | `system_test.gd:_smoke_camp_hud_band`;`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` |
|
||||
| 营地顶栏栏体定值与落座 | 营地场景视图 | 真实工程 boot 后经装配链 `MainUiBuild`→`CampSceneView.set_hud_band()` 读 `ui.modals.camp.scene.hud_band`(定值=`CAMP_HUD_BAND`×α0.80、48px 全宽浮层、徽章右位顶格);单元经 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口读背景贴图落位带像素算对比 ≥3:1 | `camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg`(对比度算式,几何五钉定值承载=系统测试宿主 _smoke_camp_hud_band) |
|
||||
| 关卡画面视口跟随 | 关卡画面(选路枢纽) | 停点切换与兑付回图经真实屏流程;读面 `LevelMapViewport.viewport_center_canvas_pos()` | `level_map_viewport_probe.gd:_probe_follow_anchor`;`level_map_viewport_probe.gd:_probe_smooth_scroll_reduced_motion` |
|
||||
| 关卡画面视口平移 | 关卡画面(选路枢纽) | 合成拖拽沿与滚轮沿经 `push_input`(`ProbeUiUtil.push_drag_at()`/`push_wheel_at()`) | `level_map_viewport_probe.gd:_probe_drag_pan`;`level_map_viewport_probe.gd:_probe_wheel_pan`;`level_map_viewport_probe.gd:_probe_clamp_bounds_and_columns`;`level_map_viewport_probe.gd:_probe_drag_no_submit` |
|
||||
| 关卡画面视口指引 | 关卡画面(选路枢纽) | 深图经 `LevelMapScreen.open_choices()` 真实停点 | `level_map_viewport_probe.gd:_probe_guidance_chrome` |
|
||||
@@ -170,6 +167,9 @@ tests/
|
||||
| 减动效开关存储契约(持久化回读/缺省值/引擎动效档应用) | 设置与本地化 | `SettingsStore.load_settings()`/`save_settings()`/`apply()` | `settings_store_test.gd:_test_settings_roundtrip`;`settings_store_test.gd:_test_settings_default`;`settings_store_test.gd:_test_settings_apply_chain` |
|
||||
| 营地顶栏 HUD 钥石读数与刷新单口同步 | 营地 HUD 栏 | 隔离存档实例 `meta_state._add_keystone()`→ 显式触发 `CampRefreshBus.refresh(camp_data)` → `camp_hud_band.refresh()` → `keystone_badge` 读数 | `screen_flow_probe.gd:_ready`(HUD 余额读数/刷新段);`ui_struct_probe.gd:_ready`(顶栏位结构段) |
|
||||
| 营地五窗统一窗壳形态 | 营地五界面 | 七物件命中区/笔记本真点(`push_click_at`,2026-11-06 真输入化)→ `CampSceneView._gui_input()`(`object_pressed` 信号)→ 组合根分派开窗;窗板几何(居中定尺寸 720×640 起点)/遮罩/窗内滚动面为呈现提交面 | `screen_flow_probe.gd:_step_camp_window_shell`(窗壳形态段) |
|
||||
| 终局结算框关闭动线零引擎报错 | 战斗表现 | fx_probe/screen_flow_probe 真实败走关闭动线(`BattleEndOverlay.open_defeat()` → `BattleEndOverlay.close()`)孤立直跑,日志零报错行读数 | `fx_probe.gd:_ready`(计划测试——需求卡片「QA harness 缺陷与覆盖遗漏收口批」2026-11-06 锚定) |
|
||||
| 停手钮真输入命中与停手收轮落效 | 行动经济 | ui_click_probe 使能窗真输入点击(AP 条停手钮经 `ProbeUiUtil.push_click()`)+journey_player 全线跑批(sim) | `ui_click_probe.gd:_stoptest_tick`(计划测试——需求卡片「QA harness 缺陷与覆盖遗漏收口批」2026-11-06 锚定) |
|
||||
| 停点链与控件级真输入命中 | HUD框架 | 探针与测试宿主真输入驱动(节点继续/三选一选卡·确认/弃图刷新确认/奖励刷新/地图候选矩形中心/调试浮窗钮,一律经 `ProbeUiUtil.push_click()`/`push_click_at()` 进 `push_input`) | `ui_click_probe.gd:_ready`(停点链段——计划测试,需求卡片「QA harness 缺陷与覆盖遗漏收口批」2026-11-06 锚定) |
|
||||
|
||||
**旅程段七:数据配置与跨系统契约**
|
||||
|
||||
@@ -191,27 +191,28 @@ tests/
|
||||
| 状态实例按个体归属:个体隔离/全体授予逐体发/死亡即清/同体合并照旧 | 增益状态 | `StatusContainer.apply_effect()`(个体键)/`purge_owner()` | `system_contracts_test.gd:_test_status_individual_isolation`;`system_contracts_test.gd:_test_status_grant_all_per_unit`;`system_contracts_test.gd:_test_status_death_clears`;`system_contracts_test.gd:_test_status_merge_per_unit` |
|
||||
| 敌方增益自体落点与乘区池按个体 | 敌人系统×战斗结算×增益状态 | 技能兑付编排链/`DamageController.clamped_pool()` | `round_flow_skill_pool_test.gd:_smoke_enemy_buff_targets_self`;`damage_controller_test.gd:_test_pool_consumption_per_unit`;`damage_controller_test.gd:_test_defender_ac_per_unit`;`battle_narrator_test.gd:_test_snapshot_effects_per_unit` |
|
||||
| 敌人技能池跨表链接校验 | 数据配置×敌人系统 | `ConfigDbCrossValidator.validate_skill_pool_links()` | `config_db_cross_validator_test.gd:_test_all_links_valid`;`config_db_cross_validator_test.gd:_test_dangling_skill_id`;`config_db_cross_validator_test.gd:_test_cast_side_mismatch` |
|
||||
| 精英行:池继承合并、加载期校验与暂不接线 | 敌人系统×数据配置 | `ConfigDB.load_tables()`/`EnemyTable.validate()`/`EnemyTable.effective_pool()`/`ConfigDbCrossValidator.validate_skill_pool_links()`/`EnemyTable.resolve_rowset()` | `elite_row_test.gd:_test_elite_validation_rejects`;`elite_row_test.gd:_test_effective_pool_merge`;`elite_row_test.gd:_test_base_pool_change_propagates`;`elite_row_test.gd:_test_merged_pool_composition`;`elite_row_test.gd:_test_elite_unreachable_by_encounter`;`elite_row_test.gd:_test_production_goblin_elite` |
|
||||
|
||||
**覆盖域表**(`design/gdd/systems/` 逐篇对照——状态三档:已覆盖/部分覆盖/未覆盖;部分与未覆盖行的缺口指待办清单条目,覆盖面提升时同批改本表):
|
||||
|
||||
| 域 | 系统文档 | 能力行 | 状态 | 缺口 |
|
||||
|---|---|---|---|---|
|
||||
| 冒险循环 | 冒险结构.md | 冒险生命周期:开局受理/三关序列/败北线;断点片段往返与续玩幂等;起关/选路/断点重生成同图(集成) | 已覆盖 | — |
|
||||
| 冒险循环 | 冒险结构.md | 冒险生命周期:开局受理/三关序列/败北线;断点片段往返与续玩幂等;起关/选路/断点重生成同图 | 已覆盖 | — |
|
||||
| 冒险循环 | 关卡节点图生成.md | 节点图生成合法性与同种子复现;节点图生长递增与无解 fail-fast | 已覆盖 | — |
|
||||
| 冒险循环 | 关卡节点地图.md | 起关/选路/断点重生成同图(集成);信息迷雾类型可见性;关卡表消费侧校验与关卡推进 | 已覆盖 | — |
|
||||
| 冒险循环 | 关卡节点地图.md | 起关/选路/断点重生成同图;信息迷雾类型可见性;关卡表消费侧校验与关卡推进 | 已覆盖 | — |
|
||||
| 冒险循环 | 奇物.md | 专长(奇物)授予与凭证保底;奖励三选一候选/刷新付费/呈现 | 已覆盖 | — |
|
||||
| 冒险循环 | 难度曲线与Boss.md | Boss 半血解锁技门控;行集寻址、行序保序与难度乘数 clamp;难度修饰套乘:空表零行为/套乘生效/越域 clamp;Boss 出场门控(每区域恰一 Boss 终点/Boss 节点开战必选 Boss 行集) | 部分覆盖 | 机制面剩余缺口由「断言补齐批」承接;数值落带断言归 M4 数值批;见待办清单「能力清单覆盖缺口补齐」条 |
|
||||
| 战斗循环 | 地图战斗资源.md | 清图奖励双份与零路径;GP 钱包/escrow/商店收口/共享池;封印与极性路由/不可逆 | 已覆盖 | — |
|
||||
| 战斗循环 | 增益状态.md | 系统间契约词表与受理口径:五配方互异/掷点标签成员性/增益布尔受理;同名状态合并策略(叠加/累加/并存)与剩余次数语义;状态效果结算链(逐值衰减/到期移除/战斗结束全清/满层拒绝/独立并存/首授快照);状态实例按个体归属:个体隔离/全体授予逐体发/死亡即清/同体合并照旧;敌方增益自体落点与乘区池按个体;封印与极性路由/不可逆 | 部分覆盖 | 结算链剩余缺口由「断言补齐批」承接;见待办清单「能力清单覆盖缺口补齐」条 |
|
||||
| 战斗循环 | 战斗结算.md | 伤害公式与掷点乘区;伤害单据编排与回放;轮内流与结算五段全序 | 已覆盖 | — |
|
||||
| 战斗循环 | 战斗表现.md | 站桩敌人呈现与结算演出落点;站桩呈现定值与素材绑定;站桩显示档与素材分辨率绑定 | 已覆盖 | 死亡灰化与减动效演出档以探针+人工目检为终态(2026-11-02 用户裁除名,不再补自动断言) |
|
||||
| 战斗循环 | 敌人系统.md | 技能池数据面与回放独立;Boss 半血解锁技门控;敌人技能池跨表链接校验 | 已覆盖 | — |
|
||||
| 战斗循环 | 敌人系统.md | 技能池数据面与回放独立;Boss 半血解锁技门控;敌人技能池跨表链接校验;精英行:池继承合并、加载期校验与暂不接线 | 部分覆盖 | 精英行「开战快照拿到合并后池」随接线批验收;见待办清单「战斗节点遭遇编排方式」条 |
|
||||
| 排雷循环 | 地形变异.md | 变异布置门控;表结构守卫:域校验/行序键/跨字段互锁/配额锚 | 已覆盖 | — |
|
||||
| 排雷循环 | 排雷玩法.md | 装板与开局免费展开(封锁格无资源、计入清图);挖/封印合法性与可辨拒绝(拒绝零副作用);封印不可逆与挖开封印格放行(兑付改判四情况) | 已覆盖 | — |
|
||||
| 排雷循环 | 棋盘生成.md | 棋盘按参数行出图、数字恒真;同参数同种子逐位复现、异种子不退化;可推理率与清图可达(合法性先于质量分)、重试有界、降级诚实 | 已覆盖 | — |
|
||||
| 排雷循环 | 行动经济.md | AP 扣费、末点耗尽即收轮与战斗收束清零;停手转存储备与轮内刷新(上限/AP 不重置/刷新图零清图奖励);下轮行动点修正排队与兑现 | 已覆盖 | — |
|
||||
| 表现与支撑 | HUD框架.md | 拒绝反馈分面可辨与未知兜底文案;结算明细带逐行呈现与排播节奏;模态覆盖层编排与输入锁;未开格类型微光(8 邻作用集/五组色相/零玩法写入);UI 全局主题与按钮五态(含对比度锚);中文字体随包与字形覆盖;地形瓦片套与格面数字可读;战斗叙述与终局文案(数值带入/胜负互异);玩家面文案与危险格格面字形;战斗屏三区几何与棋盘格边长(分辨率两档×四档板型);B 区舞台背景三关变体与连续地面层(按 board_type 选图/地面层高度读布局表/四柱光柱退役);模态句柄分域寻址面 | 已覆盖 | — |
|
||||
| 表现与支撑 | 存档序列化.md | 存档写盘⇄读回往返保真;三槽位隔离与激活失败回滚;槽位摘要窥视与坏槽三态;启动态无激活不写盘;旧档迁移链与断点续玩零跳号(集成);存档⇄RNG 快照往返保真(集成) | 已覆盖 | — |
|
||||
| 表现与支撑 | 存档序列化.md | 存档写盘⇄读回往返保真;三槽位隔离与激活失败回滚;槽位摘要窥视与坏槽三态;启动态无激活不写盘;旧档迁移链与断点续玩零跳号;存档⇄RNG 快照往返保真 | 已覆盖 | — |
|
||||
| 表现与支撑 | 教学引导.md | — | 未覆盖 | 教学形态与首遇时机待设计;见待办清单「教学形态未定」条 |
|
||||
| 表现与支撑 | 数据配置.md | 全表装载、就绪态与表清单目录对账;进度坐标查行:精确命中/向上取整/封顶/警告去重;运行期越域致命与进程非 0 退出;加载期违规表拒绝:跨表悬空/计数器前缀/真视窄域;表结构守卫:域校验/行序键/跨字段互锁/配额锚 | 已覆盖 | — |
|
||||
| 表现与支撑 | 设置与本地化.md | 减动效开关存储契约(持久化回读/缺省值/引擎动效档应用) | 已覆盖 | 存储契约三断言已落地;多语言切换面未建(P1,归设置与本地化待设计问题清单) |
|
||||
@@ -237,7 +238,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 行) | 改产品码前后的快速自检;新增纪律先补扫描规则再修码 |
|
||||
@@ -251,8 +252,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
|
||||
|
||||
@@ -269,8 +270,8 @@ godot --headless --path . res://probe/<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)`
|
||||
@@ -291,7 +292,7 @@ godot --headless --path . res://probe/<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/` |
|
||||
@@ -359,20 +360,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/` |
|
||||
+13
-9
@@ -179,7 +179,8 @@ 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/elite_row_test.gd", "expect": 0}, # 精英行校验/有效池合并/继承不复制/合并池纪律/暂不接线(精英专属池批)
|
||||
{"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)
|
||||
@@ -200,10 +201,13 @@ func _suites() -> Array:
|
||||
{"path": "res://tests/unit/presentation/tune_sim_batch_parity_test.gd", "expect": 0}, # 测速循环批双实现对拍(帧索引/运行态/进度串/stats/报告五面——信号步进 vs await 基线)
|
||||
{"path": "res://tests/unit/presentation/standee_present_values_test.gd", "expect": 0}, # 战斗一屏样张批 V5–V12(血条危险红/名字行素底不压地台与提档 FS_HINT/徽标/护甲 PNG 绑定/素材入包/地台色合并退役)
|
||||
{"path": "res://tests/unit/presentation/reward_overlay_test.gd", "expect": 0}, # 奖励节点 UI 直验(盲区补漏)
|
||||
{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}, # 确认弹窗按钮位次与语义同钉(推进/次推/维持子节点序——营地交互修复批 R3)
|
||||
{"path": "res://tests/unit/presentation/level_map_info_fog_test.gd", "expect": 0}, # 信息迷雾——呈现显隐/Boss 豁免/详情零显示/大图拟合
|
||||
{"path": "res://tests/unit/presentation/forge_workbench_test.gd", "expect": 0}, # 工作间两区六路线 UI 直验(V13 自动化前置面)
|
||||
{"path": "res://tests/unit/presentation/camp_scene_layout_test.gd", "expect": 0}, # 营地场景布局数据件校验(V14——fail-fast 判据+七命中区几何红线)
|
||||
{"path": "res://tests/unit/presentation/stage_background_test.gd", "expect": 0}, # B 区舞台背景三关变体与连续地面层(V1–V5/V8——ground_h 表单源/board_type 选图/缺图回退/地面几何/光柱退役)
|
||||
{"path": "res://tests/unit/presentation/animation_frame_spec_test.gd", "expect": 0}, # 动画帧数规格守门(帧数档定值面=本套件——规格表冻结+现役单帧形制锚定)
|
||||
{"path": "res://tests/unit/presentation/palette_token_pairwise_test.gd", "expect": 0}, # token 板两两互查守门(色板增量纪律存量面——豁免账外零近色对,六对欠账在册)
|
||||
{"path": "res://tests/unit/battle_settlement/damage_math_test.gd", "expect": 0},
|
||||
{"path": "res://tests/unit/battle_settlement/damage_controller_test.gd", "expect": 0},
|
||||
{"path": "res://tests/unit/battle_settlement/effect_port_test.gd", "expect": 0}, # 效果端口件(效果端口与掷格分件批 V1–V3——端口单实例/业务类零引擎直连源码锚/事实面闭集两对照)
|
||||
@@ -211,7 +215,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 +239,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 扫描两处;直建断言族,禁字「雷」族与旧术语清零钉值)
|
||||
]
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
extends SceneTree
|
||||
|
||||
const AutoPlayer := preload("res://sim/auto_player.gd") # 自动玩家策略单一实现(lifecycle 活战斗驱动;smoke 库件迁移期按现路径 preload)
|
||||
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 真输入合成单点(tests 面复用共用件——禁复制第二份合成输入件)
|
||||
|
||||
var _failures: int = 0
|
||||
var _checks: int = 0
|
||||
@@ -43,6 +44,7 @@ func _split_tail() -> void:
|
||||
await _smoke_run_lifecycle()
|
||||
await _smoke_sim_guards()
|
||||
await _smoke_lifecycle_matrix()
|
||||
await _smoke_camp_hud_band()
|
||||
else:
|
||||
check(false, "V2/V3 前置:autoload 链未就绪(ConfigDB/SaveManager)")
|
||||
print("[system_test] 摘要:%d 项检查,%d 项失败" % [_checks, _failures])
|
||||
@@ -436,8 +438,8 @@ func _smoke_lifecycle_matrix() -> void:
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3b 开局停点", "alive", "alive", "null",
|
||||
{"mf_start": false, "level_vis": true})
|
||||
# 阶段③地图选路→战斗中(真实选路提交 press_candidate)
|
||||
main.ui.modals.run.level_screen.press_candidate(0)
|
||||
# 阶段③地图选路→战斗中(真实选路提交——candidate_rect 命中矩形真点)
|
||||
ProbeUiUtil.push_click_at(main, main.ui.modals.run.level_screen.candidate_rect(0).get_center())
|
||||
for i in range(10):
|
||||
await process_frame
|
||||
await _stage_faces(main, "V3c 战斗中", "alive", "alive", "alive", {"mf_start": false})
|
||||
@@ -602,4 +604,49 @@ func _smoke_lifecycle_matrix() -> void:
|
||||
for p in ["user://smoke_lifecycle.json", "user://smoke_lifecycle.json.tmp",
|
||||
"user://smoke_lifecycle_resume.json", "user://smoke_lifecycle_resume.json.tmp"]:
|
||||
if FileAccess.file_exists(p):
|
||||
DirAccess.remove_absolute(p)
|
||||
DirAccess.remove_absolute(p)
|
||||
|
||||
|
||||
func _smoke_camp_hud_band() -> void:
|
||||
# oracle: design/ux/camp.md:41 (营地顶栏栏体定值与落座——几何/色值/吃点击五钉组)
|
||||
print("--- 营地交互修复批 T3 顶栏栏体(R5:色值/铺满/不吃点击/首子件/徽章落座)---")
|
||||
var main: Node = load("res://presentation/main.tscn").instantiate()
|
||||
root.add_child(main)
|
||||
await process_frame
|
||||
await process_frame
|
||||
main._camp_pres.open_camp() # 营地屏 boot 态为隐——先开营地(装配链 main_ui_build→camp_scene_view 落座)
|
||||
for i in range(3):
|
||||
await process_frame # 排版帧(容器重排在途读数不可信)
|
||||
var band: Control = main.ui.modals.camp.scene.hud_band
|
||||
check(band != null and band.is_inside_tree(), "R5 前置:顶栏件经装配链在树(camp_scene_view.hud_band 公开句柄)")
|
||||
if band == null:
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
return
|
||||
var badge_min: float = band.get_child(band.get_child_count() - 1).get_child(0).get_combined_minimum_size().y if band.get_child_count() > 0 else -1.0
|
||||
print("[system_test] 徽章 get_combined_minimum_size().y 实测 = %s(T3.0 前置读数——48=顶格落座前提)" % str(badge_min))
|
||||
check(band.get_child_count() >= 2 and band.get_child(0) is ColorRect,
|
||||
"R5④ 栏体色面是顶栏件第一个子节点(子节点序=绘制序——色面先入不盖徽章)")
|
||||
if band.get_child_count() >= 2 and band.get_child(0) is ColorRect:
|
||||
var surface: ColorRect = band.get_child(0)
|
||||
check(String(surface.color.to_html(true)) == "6e4526cc",
|
||||
"R5① 栏体色=#6E4526×α0.80(to_html 字面编码——禁浮点分量直等)〔营地交互批 V4〕")
|
||||
var band_rect: Rect2 = band.get_global_rect()
|
||||
var surf_rect: Rect2 = surface.get_global_rect()
|
||||
print("[system_test] 顶栏件实测 get_global_rect=%s、栏体=%s(R5② 承重=ui_registry FULL_RECT 注册)" % [band_rect, surf_rect])
|
||||
check(absf(surf_rect.size.x - band_rect.size.x) < 0.51 and absf(surf_rect.size.y - band_rect.size.y) < 0.51
|
||||
and absf(surf_rect.size.y - 48.0) < 0.51,
|
||||
"R5② 栏体铺满宿主且高 48(全局矩形等值)〔营地交互批 V4〕")
|
||||
check(surface.mouse_filter == Control.MOUSE_FILTER_IGNORE,
|
||||
"R5③ 栏体不吃点击(MOUSE_FILTER_IGNORE——顶排物件命中区不受阻)〔营地交互批 V4〕")
|
||||
var dock: Control = band.get_child(band.get_child_count() - 1)
|
||||
var badge: Control = dock.get_child(0) if dock.get_child_count() > 0 else null
|
||||
check(badge != null,
|
||||
"R5⑤ 前置:徽章经右对齐容器在座(dock 尾子件)")
|
||||
if badge != null:
|
||||
var brect: Rect2 = badge.get_global_rect()
|
||||
check(absf(brect.position.y - band_rect.position.y) < 0.51
|
||||
and absf(brect.end.x - (band_rect.end.x - 12.0)) < 0.51,
|
||||
"R5⑤ 徽章顶格右位(y=栏顶格、end.x=栏右缘−12——全局矩形不与局部 position 混写)〔营地交互批 V4〕")
|
||||
main.queue_free()
|
||||
await process_frame
|
||||
@@ -527,7 +527,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
and is_equal_approx(float(venom.rider_mult), 0.75))
|
||||
var venom_sole_ok := true
|
||||
for row in _db.combat.enemy.rows:
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
if String(entry.skill_id) == "venom_burst" and String(row.enemy_id) != "drider":
|
||||
venom_sole_ok = false
|
||||
Kit.check("毒爆独占性(仅蛛化卓尔精灵池引用——Boss 专属池独占技纪律)", venom_sole_ok)
|
||||
@@ -538,7 +538,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
and int(snare.buff_duration) == 2 and int(snare.cooldown_beats) == 4)
|
||||
var snare_sole_ok := true
|
||||
for row in _db.combat.enemy.rows:
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
if String(entry.skill_id) == "web_snare" and String(row.enemy_id) != "drider":
|
||||
snare_sole_ok = false
|
||||
Kit.check("蛛网束缚独占性(仅蛛化卓尔精灵池引用——实装面收窄,其余敌种不动)", snare_sole_ok)
|
||||
@@ -557,7 +557,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
var spit_sole_ok := true # 毒吐只挂巨蜘蛛族且首遇带不投(Q6——教学位池零新招)
|
||||
var bash_sole_ok := true # 盾击只挂灰矮人族三行
|
||||
for row in _db.combat.enemy.rows:
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
var sid := String(entry.skill_id)
|
||||
if sid == "venom_spit" and not (String(row.enemy_id) == "giant_spider" and int(row.level_index) == 2 \
|
||||
and int(row.depth_band) > spider_lead_band):
|
||||
@@ -608,6 +608,8 @@ func _smoke_skill_pool_data() -> void:
|
||||
"3_boss_11_duergar_king": {"attack": 6, "heavy": 2, "earth_pulse": 2, "rockslide": 2},
|
||||
"3_boss_12_duergar_king": {"attack": 6, "heavy": 2, "earth_pulse": 2, "rockslide": 2},
|
||||
"3_boss_13_duergar_king": {"attack": 6, "heavy": 2, "earth_pulse": 2, "rockslide": 2},
|
||||
# 精英专属池批:首只精英=地精精英(有效池=基础池 goblin 9/1/1+附加技 heavy 2——expect_pools 比对读 effective_pool)
|
||||
"1_elite_0_goblin_elite": {"attack": 9, "lunge": 1, "desperate_guard": 1, "heavy": 2},
|
||||
}
|
||||
var pools_ok := true
|
||||
for row in _db.combat.enemy.rows:
|
||||
@@ -616,15 +618,15 @@ func _smoke_skill_pool_data() -> void:
|
||||
pools_ok = false
|
||||
continue
|
||||
var got := {}
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
got[String(entry.skill_id)] = int(entry.weight)
|
||||
if got != expect_pools[rk]:
|
||||
pools_ok = false
|
||||
Kit.check("二十九行十种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1/1〔触发条件批+孤注守势 w1〕 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1/2〔触发条件批+趁隙偷袭 w2〕 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2——半血解锁收编技能行 trigger 闩型——迭代14④ T5/触发条件批迁移〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok)
|
||||
Kit.check("三十行十一种池模板 = 建议池行值((L,row_kind,depth_band,enemy_id) 四元键逐行精确比对;L1 地精 9/1/1〔触发条件批+孤注守势 w1〕 · 熊地精 6/2/2 · 酋长 6/2/2;L2 熊地精 6/1/2 · 蜘蛛 6/2/1+毒吐2〔首遇带不投——迭代 24 N2〕 · 重击手 5/2/2 · 蛛化卓尔精灵 6/2/2/2〔Boss〕;L3 灰矮人 6/2/2+盾击2 · 钩爪恐魔 6/2/1/2〔触发条件批+趁隙偷袭 w2〕 · 重装手 5/2/2+盾击2 · 石君王 6/2/2〔earth_pulse〕——全部 provisional;三 Boss 池尾 +1 门控条目〔quake_smash/venom_curb/rockslide 权重 2——半血解锁收编技能行 trigger 闩型——迭代14④ T5/触发条件批迁移〕;混编行集同带多行各一池合法——首批投放 (2,battle,5) 双行〔迭代 18 卡⑤:重击手+巢群〕;迭代 35 覆盖批八行为顶档池克隆,逐行同检)", pools_ok)
|
||||
var basic_count_ok := true
|
||||
for row in _db.combat.enemy.rows:
|
||||
var n := 0
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
var srow: Dictionary = _db.combat.skill.row_by_id(String(entry.skill_id))
|
||||
if not srow.is_empty() and String(srow.type) == "basic":
|
||||
n += 1
|
||||
@@ -637,7 +639,7 @@ func _smoke_skill_pool_data() -> void:
|
||||
for row in _db.combat.enemy.rows:
|
||||
var n_buff := 0
|
||||
var n_debuff := 0
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
var srow: Dictionary = _db.combat.skill.row_by_id(String(entry.skill_id))
|
||||
if srow.is_empty():
|
||||
continue
|
||||
@@ -652,14 +654,14 @@ func _smoke_skill_pool_data() -> void:
|
||||
var uncond_ok := true
|
||||
for row in _db.combat.enemy.rows:
|
||||
var n_uncond := 0
|
||||
for entry in row.pool:
|
||||
for entry in _db.combat.enemy.effective_pool(row):
|
||||
var srow_u: Dictionary = _db.combat.skill.row_by_id(String(entry.skill_id))
|
||||
if not srow_u.is_empty() and String(srow_u.type) != "basic" and not srow_u.has("trigger"):
|
||||
n_uncond += 1
|
||||
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))
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 施加段承载——本段按声明逐件钉命中与边界/反例〕)
|
||||
|
||||
@@ -711,6 +711,7 @@ func _smoke_level_graph() -> void:
|
||||
# ⑫ 混编行集运行面正反断言:注入带键 (1,battle,5)/(1,battle,6) =
|
||||
# L1 节点图与全部既有扫描面之外的新带键(带命中精确取注入行集——新 L 会被 ceil 封顶到 L3 不可用),
|
||||
# 断言后立即还原表,后续断言面零污染。R3 fail-fast 正反验证 + R6 行序运行面验证。
|
||||
var base_n: int = _db.combat.enemy.rows.size() # 注入前基线行数(生产表行数不写死——精英行随批入表)
|
||||
var mix_row_a := {"level_index": 1, "row_kind": "battle", "depth_band": 5, "enemy_id": "goblin", "display_name": "地精", "unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]}
|
||||
var mix_row_b := {"level_index": 1, "row_kind": "battle", "depth_band": 5, "enemy_id": "bugbear", "display_name": "熊地精", "unit_count": 2, "hp": 20, "atk": 3, "pool": [{"skill_id": "attack", "weight": 9}]}
|
||||
_db.combat.enemy.rows.append(mix_row_a)
|
||||
@@ -729,7 +730,7 @@ func _smoke_level_graph() -> void:
|
||||
_db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1)
|
||||
_db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1)
|
||||
_db.combat.enemy.rows.remove_at(_db.combat.enemy.rows.size() - 1)
|
||||
Kit.check("混编断言段注入行全量还原(表回 29 行——L1 混编批后的基线行数;首批混编投放基线 21 行+覆盖 8 行)", int(_db.combat.enemy.rows.size()) == 29)
|
||||
Kit.check("混编断言段注入行全量还原(表回注入前基线行数 %d——相对基数断言,生产表行数不写死)" % base_n, int(_db.combat.enemy.rows.size()) == base_n)
|
||||
|
||||
# ⑬ 真图类型可见性(信息迷雾 v2.1):L2 入口停点纯核心面读数——候选 ⊆ 可见集 /
|
||||
# 终点恒可见 / 入口 BFS 层距 > N 的非豁免节点入雾。N 取数据表(代码零硬编码)。
|
||||
@@ -1575,7 +1576,7 @@ func _has_err_category(errs: Array, category: String) -> bool:
|
||||
# ---- 货架位数读表(架构整改——shelf_size 表头单点:进店与重抽同源)----
|
||||
|
||||
func _test_shelf_size_from_table() -> void:
|
||||
# oracle: design/gdd/ux/shop.md:13 (货架位数读表组)
|
||||
# oracle: design/ux/shop.md:13 (货架位数读表组)
|
||||
var autoplay = load("res://sim/level_autoplay.gd")
|
||||
var st = _db.run.shop
|
||||
var saved: int = int(st.shelf_size)
|
||||
|
||||
@@ -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])
|
||||
|
||||
+20
-20
@@ -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 (试玩日志器跨系统接线组)
|
||||
File renamed without changes.
@@ -256,16 +256,16 @@ func _test_rowset_discipline() -> void:
|
||||
if String(e.get("category", "")) == "domain" and String(e.get("message", "")).contains("unit_count"):
|
||||
dom_hit = true
|
||||
Kit.check("单行数量域 [1,3] 域闸不回归(UNIT_COUNT_MAX 语义 = 单种数)", dom_hit)
|
||||
# 负向:row_kind 封闭枚举(非法值拒——键面换代后新增判据)
|
||||
# 负向:row_kind 封闭枚举(非法值拒——键面换代后新增判据;elite 随精英专属池批转合法,非法样本换 miniboss)
|
||||
var t_kind = EnemyTableScript.new()
|
||||
t_kind.rows.assign([mk_row.call(1, "elite", 5, "goblin"), mk_row.call(1, "battle", 0, "goblin"),
|
||||
t_kind.rows.assign([mk_row.call(1, "miniboss", 5, "goblin"), mk_row.call(1, "battle", 0, "goblin"),
|
||||
mk_row.call(1, "boss", 9, "ogre_chief")])
|
||||
errs = t_kind.validate()
|
||||
var kind_hit := false
|
||||
for e in errs:
|
||||
if String(e.get("category", "")) == "domain" and String(e.get("message", "")).contains("row_kind"):
|
||||
kind_hit = true
|
||||
Kit.check("非法 row_kind 被拒(封闭枚举 {battle, boss})〔fv7〕", kind_hit)
|
||||
Kit.check("非法 row_kind 被拒(封闭枚举 {battle, boss, elite})〔fv7〕", kind_hit)
|
||||
# 负向:anchor 两半——缺 battle band 0 锚行、缺 Boss 行集,各自报 anchor
|
||||
var t_no_anchor = EnemyTableScript.new()
|
||||
t_no_anchor.rows.assign([mk_row.call(1, "battle", 5, "goblin"), mk_row.call(1, "boss", 9, "ogre_chief")])
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
# tests/unit/data_config/elite_row_test.gd — 精英行单元套(精英专属池批)
|
||||
# 断言面 = 精英行加载期校验(字段互锁/引用校验/错误类别)·有效池合并(基础池取深度带最小行
|
||||
# +附加技)·继承不复制(改基础池传到精英、改写返回不回灌)·合并池过池构成纪律(跨表件)·
|
||||
# 暂不接线(寻址行类门只放 battle/boss,elite 行静置合法)·生产表 goblin_elite 行(随 T3 落表追加)。
|
||||
# 断言从公开入口进出:EnemyTable.validate()/effective_pool()/validate_skill_pool_links()/
|
||||
# resolve_enemy_rowset()/ConfigDB.load_tables()。夹具一律 rows.assign([...])(定型数组直赋会触发
|
||||
# 脚本错误静默中断——config_db_test 同款坑注)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/data_config/elite_row_test.gd(expect 0)
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const EnemyTableScript := preload("res://data/schemas/enemy_table.gd")
|
||||
const ValidatorScript := preload("res://core/config_db_cross_validator.gd")
|
||||
const SkillTableScript := preload("res://data/schemas/skill_table.gd")
|
||||
const StatusTableScript := preload("res://data/schemas/status_effect_table.gd")
|
||||
const ResolverScript := preload("res://core/config_db_resolver.gd")
|
||||
|
||||
var _diags: Array[String] = [] # T2.4 夹具 resolver 诊断捕获
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
_test_elite_validation_rejects()
|
||||
_test_effective_pool_merge()
|
||||
_test_base_pool_change_propagates()
|
||||
_test_merged_pool_composition()
|
||||
_test_elite_unreachable_by_encounter()
|
||||
_test_production_goblin_elite()
|
||||
quit(Kit.finish("elite_row_test", "PASS — 精英行校验/有效池/继承不复制/合并池纪律/暂不接线/生产表落行 全过"))
|
||||
|
||||
|
||||
# 行构造助手(照 config_db_test mk_row 写全字段;elite 行形态=去掉 pool、加 base_ref/pool_extra)
|
||||
func _mk_row(l: int, kind: String, band: int, eid: String) -> Dictionary:
|
||||
return {"level_index": l, "row_kind": kind, "depth_band": band, "enemy_id": eid, "display_name": eid + "名",
|
||||
"unit_count": 1, "hp": 10, "atk": 2, "pool": [{"skill_id": "attack", "weight": 9}]}
|
||||
|
||||
|
||||
func _mk_elite_row(l: int, band: int, eid: String, base_ref: String, extra: Array) -> Dictionary:
|
||||
return {"level_index": l, "row_kind": "elite", "depth_band": band, "enemy_id": eid, "display_name": eid + "名",
|
||||
"unit_count": 1, "hp": 20, "atk": 2, "base_ref": base_ref, "pool_extra": extra}
|
||||
|
||||
|
||||
# 合法夹具(三行——battle 锚/boss/elite 引用同关 battle 敌种)
|
||||
func _legal_rows() -> Array:
|
||||
var goblin := _mk_row(1, "battle", 0, "goblin")
|
||||
goblin["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
|
||||
return [goblin, _mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}])]
|
||||
|
||||
|
||||
# category 计数(错误列表按类别筛)
|
||||
func _count_cat(errs: Array, cat: String) -> int:
|
||||
var n := 0
|
||||
for e in errs:
|
||||
if String((e as Dictionary).get("category", "")) == cat:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
# ---- R1:elite 行类合法与字段互锁、引用校验(合法零错+十一违例各自「含」预期类别)----
|
||||
func _test_elite_validation_rejects() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:325 elite 行字段互锁与基础引用违例加载期返回错误
|
||||
var t_ok = EnemyTableScript.new()
|
||||
t_ok.rows.assign(_legal_rows())
|
||||
var errs: Array = t_ok.validate()
|
||||
Kit.check("合法夹具 validate 零错(battle 锚+boss+elite 引用在位——实得 %s)" % str(errs), errs.is_empty())
|
||||
# ① elite 删 base_ref → cross_field
|
||||
var rows1 := _legal_rows()
|
||||
rows1[2].erase("base_ref")
|
||||
Kit.check("① elite 删 base_ref 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows1), "cross_field") >= 1)
|
||||
# ② base_ref 与自身同名 → cross_field
|
||||
var rows2 := _legal_rows()
|
||||
rows2[2]["base_ref"] = "goblin_elite"
|
||||
Kit.check("② base_ref 与 enemy_id 同名报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows2), "cross_field") >= 1)
|
||||
# ③ pool_extra 为空数组 → cross_field
|
||||
var rows3 := _legal_rows()
|
||||
rows3[2]["pool_extra"] = []
|
||||
Kit.check("③ pool_extra 空数组报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows3), "cross_field") >= 1)
|
||||
# ④ elite 删 pool_extra 键 → cross_field
|
||||
var rows4 := _legal_rows()
|
||||
rows4[2].erase("pool_extra")
|
||||
Kit.check("④ elite 删 pool_extra 键报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows4), "cross_field") >= 1)
|
||||
# ⑤ elite 加 pool → cross_field
|
||||
var rows5 := _legal_rows()
|
||||
rows5[2]["pool"] = [{"skill_id": "attack", "weight": 9}]
|
||||
Kit.check("⑤ elite 带 pool 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows5), "cross_field") >= 1)
|
||||
# ⑥ battle 行加 base_ref → cross_field
|
||||
var rows6 := _legal_rows()
|
||||
rows6[0]["base_ref"] = "ogre_chief"
|
||||
Kit.check("⑥ battle 行带 base_ref 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows6), "cross_field") >= 1)
|
||||
# ⑦ battle 行加 pool_extra → cross_field
|
||||
var rows7 := _legal_rows()
|
||||
rows7[0]["pool_extra"] = [{"skill_id": "heavy", "weight": 2}]
|
||||
Kit.check("⑦ battle 行带 pool_extra 报 cross_field〔精英批 V1〕", _count_cat(_validate_rows(rows7), "cross_field") >= 1)
|
||||
# ⑧ base_ref="hobgoblin"(同关无此 battle 敌种)→ dangling_base
|
||||
var rows8 := _legal_rows()
|
||||
rows8[2]["base_ref"] = "hobgoblin"
|
||||
Kit.check("⑧ base_ref 悬空报 dangling_base〔精英批 V1〕", _count_cat(_validate_rows(rows8), "dangling_base") >= 1)
|
||||
# ⑨ 再加 elite2 行、base_ref 改指它 → dangling_base 且 message 含「elite」
|
||||
var rows9 := _legal_rows()
|
||||
rows9.append(_mk_elite_row(1, 0, "goblin_elite2", "goblin", [{"skill_id": "heavy", "weight": 2}]))
|
||||
rows9[2]["base_ref"] = "goblin_elite2"
|
||||
var errs9: Array = _validate_rows(rows9)
|
||||
var has_elite_base := false
|
||||
for e in errs9:
|
||||
if String((e as Dictionary).get("category", "")) == "dangling_base" \
|
||||
and String((e as Dictionary).get("message", "")).contains("elite"):
|
||||
has_elite_base = true
|
||||
Kit.check("⑨ base_ref 指向 elite 行报 dangling_base 且 message 含 elite〔精英批 V1〕", has_elite_base)
|
||||
# ⑩ pool_extra 含基础池已有 lunge → dup_key
|
||||
var rows10 := _legal_rows()
|
||||
rows10[2]["pool_extra"] = [{"skill_id": "heavy", "weight": 2}, {"skill_id": "lunge", "weight": 1}]
|
||||
Kit.check("⑩ 附加技与基础池重复报 dup_key〔精英批 V1〕", _count_cat(_validate_rows(rows10), "dup_key") >= 1)
|
||||
# ⑪ pool_extra 条目权重 0 → domain 且 message 含 pool_extra
|
||||
var rows11 := _legal_rows()
|
||||
rows11[2]["pool_extra"] = [{"skill_id": "heavy", "weight": 0}]
|
||||
var errs11: Array = _validate_rows(rows11)
|
||||
var has_extra_domain := false
|
||||
for e in errs11:
|
||||
if String((e as Dictionary).get("category", "")) == "domain" \
|
||||
and String((e as Dictionary).get("message", "")).contains("pool_extra"):
|
||||
has_extra_domain = true
|
||||
Kit.check("⑪ pool_extra 权重越域报 domain 且定位含 pool_extra〔精英批 V1〕", has_extra_domain)
|
||||
|
||||
|
||||
func _validate_rows(rows: Array) -> Array:
|
||||
var t = EnemyTableScript.new()
|
||||
t.rows.assign(rows)
|
||||
return t.validate()
|
||||
|
||||
|
||||
# ---- R2:有效池=基础池+附加技,基础池取深度带最小行(深带行在前防取首行假绿)----
|
||||
func _test_effective_pool_merge() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:149 精英有效池=同关战斗行类基础敌种深度带最小行的池+附加技
|
||||
var rows := [
|
||||
_mk_row(1, "battle", 3, "goblin"), # 深带行在前(取首行的错误实现必红)
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
|
||||
]
|
||||
rows[0]["pool"] = [{"skill_id": "attack", "weight": 5}, {"skill_id": "charge", "weight": 2}]
|
||||
rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
|
||||
var t = EnemyTableScript.new()
|
||||
t.rows.assign(rows)
|
||||
var got := {}
|
||||
for e in t.effective_pool(rows[3]):
|
||||
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
|
||||
Kit.check("精英有效池=浅带基础池+附加技({attack9,lunge1,heavy2}——非深带行池)〔精英批 V2〕",
|
||||
got.get("attack", 0) == 9 and got.get("lunge", 0) == 1 and got.get("heavy", 0) == 2 and got.size() == 3)
|
||||
var got_b0 := {}
|
||||
for e in t.effective_pool(rows[1]):
|
||||
got_b0[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
|
||||
Kit.check("battle 行有效池=自身 pool(读法等价面——{attack9,lunge1})〔精英批 V2〕",
|
||||
got_b0.get("attack", 0) == 9 and got_b0.get("lunge", 0) == 1 and got_b0.size() == 2)
|
||||
|
||||
|
||||
# ---- R3:继承不复制——基础池改表传到精英、改写返回数组不回灌 ----
|
||||
func _test_base_pool_change_propagates() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:149 继承不复制——基础池调参自动传到精英
|
||||
var rows := [
|
||||
_mk_row(1, "battle", 3, "goblin"),
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
|
||||
]
|
||||
rows[0]["pool"] = [{"skill_id": "attack", "weight": 5}, {"skill_id": "charge", "weight": 2}]
|
||||
rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
|
||||
var t = EnemyTableScript.new()
|
||||
t.rows.assign(rows)
|
||||
var first: Array = t.effective_pool(rows[3])
|
||||
for e in first:
|
||||
if String((e as Dictionary).get("skill_id", "")) == "attack":
|
||||
e["weight"] = 99 # 改写返回数组条目——表内行不得被回灌
|
||||
rows[1]["pool"][0]["weight"] = 7 # 基础行(depth_band==0)attack 权重改 7
|
||||
var got := {}
|
||||
for e in t.effective_pool(rows[3]):
|
||||
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
|
||||
Kit.check("基础池改权重 7 传到精英有效池(按需现算零缓存)〔精英批 V3〕", got.get("attack", 0) == 7)
|
||||
Kit.check("改写返回数组不回灌表内行(基础行 attack 仍=7)〔精英批 V3〕",
|
||||
int(rows[1]["pool"][0]["weight"]) == 7)
|
||||
|
||||
|
||||
# ---- R4:合并池过池构成纪律(跨表件校验对象=合并后的池)----
|
||||
func _test_merged_pool_composition() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:150 池构成纪律校验对象是合并后的池
|
||||
var skills := SkillTableScript.new()
|
||||
skills.rows.assign([
|
||||
{"skill_id": "attack", "type": "basic"},
|
||||
{"skill_id": "lunge", "type": "damage"},
|
||||
{"skill_id": "heavy", "type": "damage"},
|
||||
{"skill_id": "harden", "type": "buff"},
|
||||
{"skill_id": "charge", "type": "buff"},
|
||||
])
|
||||
var status := StatusTableScript.new()
|
||||
status.rows.assign([]) # 空状态表——buff 无 buff_ref 可解析时 cast_side 不报(cross_validator :179-180)
|
||||
# 合规精英:基础 attack/lunge+附加 heavy → 恰一普攻/增益≤1/无条件特殊≥1 全合规
|
||||
var enemy_ok = EnemyTableScript.new()
|
||||
enemy_ok.rows.assign([
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
|
||||
])
|
||||
enemy_ok.rows[0]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
|
||||
enemy_ok.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池(纪律对全行生效——本组只数精英违规)
|
||||
var errs_ok: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_ok, status)
|
||||
Kit.check("合规精英合并池 pool_composition=0(实得 %s)" % str(errs_ok), _count_cat(errs_ok, "pool_composition") == 0)
|
||||
# 附加第二普攻:基础 attack+附加 attack → basic=2 → pool_composition=1 且 message 含 goblin_elite
|
||||
var enemy_dup_basic = EnemyTableScript.new()
|
||||
enemy_dup_basic.rows.assign([
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "attack", "weight": 2}]),
|
||||
])
|
||||
enemy_dup_basic.rows[0]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}]
|
||||
enemy_dup_basic.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池
|
||||
var errs_dup: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_dup_basic, status)
|
||||
Kit.check("附加第二普攻报 pool_composition=1 且 message 含 goblin_elite〔精英批 V4〕",
|
||||
_count_cat(errs_dup, "pool_composition") == 1 and _msgs(errs_dup).any(func(m: String) -> bool: return m.contains("goblin_elite")))
|
||||
# 附加第二增益:基础 attack/lunge/harden+附加 charge → buff=2 → pool_composition=1
|
||||
var enemy_dup_buff = EnemyTableScript.new()
|
||||
enemy_dup_buff.rows.assign([
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "charge", "weight": 2}]),
|
||||
])
|
||||
enemy_dup_buff.rows[0]["pool"] = [
|
||||
{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}, {"skill_id": "harden", "weight": 2}]
|
||||
enemy_dup_buff.rows[1]["pool"] = [{"skill_id": "attack", "weight": 9}, {"skill_id": "lunge", "weight": 1}] # boss 行自带合规池
|
||||
var errs_buff: Array = ValidatorScript.new().validate_skill_pool_links(skills, enemy_dup_buff, status)
|
||||
Kit.check("合并后增益超 1 报 pool_composition=1 且 message 含 goblin_elite〔精英批 V4〕",
|
||||
_count_cat(errs_buff, "pool_composition") == 1 and _msgs(errs_buff).any(func(m: String) -> bool: return m.contains("goblin_elite")))
|
||||
|
||||
|
||||
func _msgs(errs: Array) -> Array[String]:
|
||||
var out: Array[String] = []
|
||||
for e in errs:
|
||||
out.append(String((e as Dictionary).get("message", "")))
|
||||
return out
|
||||
|
||||
|
||||
# ---- R5:暂不接线——表里有 elite 行时寻址拿不到它(行类门改动特有判据)----
|
||||
func _test_elite_unreachable_by_encounter() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:151 精英行接线前不会被任何节点读到
|
||||
_diags.clear()
|
||||
var resolver = ResolverScript.new(
|
||||
func(level, category, message) -> void:
|
||||
_diags.append("%s/%s/%s" % [str(level), str(category), str(message)]),
|
||||
func() -> Array: return [],
|
||||
func() -> Array: return [
|
||||
_mk_row(1, "battle", 0, "goblin"),
|
||||
_mk_row(1, "boss", 9, "ogre_chief"),
|
||||
_mk_elite_row(1, 0, "goblin_elite", "goblin", [{"skill_id": "heavy", "weight": 2}]),
|
||||
],
|
||||
func(): return null,
|
||||
)
|
||||
# ① 请求 elite → 空数组且诊断含 fatal 与 request_oob(只改枚举不改行类门时此请求会返回 goblin_elite——判据绑行类门改动)
|
||||
var hit: Array = resolver.resolve_enemy_rowset(1, 0, "elite")
|
||||
var has_fatal_oob := false
|
||||
for d in _diags:
|
||||
if d.contains("fatal") and d.contains("request_oob"):
|
||||
has_fatal_oob = true
|
||||
Kit.check("请求 elite 返回空且诊断含 fatal/request_oob〔精英批 V5〕", hit.is_empty() and has_fatal_oob)
|
||||
# ② 同夹具 battle/boss 查询返回行中 elite 计数=0(非空前置:合计行数>0)
|
||||
var battle_rows: Array = resolver.resolve_enemy_rowset(1, 0, "battle")
|
||||
var boss_rows: Array = resolver.resolve_enemy_rowset(1, 9, "boss")
|
||||
var elite_n := 0
|
||||
for r in battle_rows + boss_rows:
|
||||
if String((r as Dictionary).get("row_kind", "")) == "elite":
|
||||
elite_n += 1
|
||||
Kit.check("battle/boss 查询合计>0 且零 elite 行(非空前置在判据内)〔精英批 V5〕",
|
||||
battle_rows.size() + boss_rows.size() > 0 and elite_n == 0)
|
||||
|
||||
|
||||
# ---- R6:生产表地精精英落表(真实加载零错+行值逐位+有效池字典)----
|
||||
func _test_production_goblin_elite() -> void:
|
||||
# oracle: design/gdd/systems/战斗循环/敌人系统.md:147 精英=敌人表行类为 elite 的独立一行
|
||||
var db = load("res://core/config_db.gd").new()
|
||||
var ok: bool = db.load_tables("res://data/tables", true)
|
||||
Kit.check("生产表 ConfigDB.load_tables 装载(is_ready=%s)〔精英批 V6〕" % str(db.is_ready), ok and db.is_ready)
|
||||
Kit.check("生产表加载 error 级诊断计数=0(实得 %d)〔精英批 V6〕" % db.diagnostics_count("error"),
|
||||
db.diagnostics_count("error") == 0)
|
||||
var elite_rows: Array = []
|
||||
for row in db.combat.enemy.rows:
|
||||
if String(row.get("enemy_id", "")) == "goblin_elite":
|
||||
elite_rows.append(row)
|
||||
Kit.check("goblin_elite 行恰 1 条(实得 %d)〔精英批 V6〕" % elite_rows.size(), elite_rows.size() == 1)
|
||||
if elite_rows.is_empty():
|
||||
return
|
||||
var r: Dictionary = elite_rows[0]
|
||||
var checks := {
|
||||
"level_index": 1, "row_kind": "elite", "depth_band": 0,
|
||||
"display_name": "精英·地精", "hp": 20, "atk": 2, "atk_min": 1, "atk_max": 3,
|
||||
"armor_class": 2, "armor_type": "light", "unit_count": 1, "base_ref": "goblin",
|
||||
}
|
||||
var bad: Array[String] = []
|
||||
for k in checks:
|
||||
if r.get(k) != checks[k]:
|
||||
bad.append("%s=%s≠%s" % [k, str(r.get(k)), str(checks[k])])
|
||||
Kit.check("行值 12 字段逐位相等(失配项 %s)〔精英批 V6〕" % [bad if not bad.is_empty() else "无"], bad.is_empty())
|
||||
Kit.check("无 unit_count_max 键、无 pool 键(精英行形态面)〔精英批 V6〕",
|
||||
not r.has("unit_count_max") and not r.has("pool"))
|
||||
var got := {}
|
||||
for e in db.combat.enemy.effective_pool(r):
|
||||
got[String(e.get("skill_id", ""))] = int(e.get("weight", 0))
|
||||
Kit.check("有效池字典={attack9,lunge1,desperate_guard1,heavy2}(实得 %s)〔精英批 V6〕" % str(got),
|
||||
got.get("attack", 0) == 9 and got.get("lunge", 0) == 1 and got.get("desperate_guard", 0) == 1 and got.get("heavy", 0) == 2 and got.size() == 4)
|
||||
@@ -0,0 +1 @@
|
||||
uid://ccl0nket4ybvu
|
||||
@@ -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 垂直切片冒烟 ===")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# tests/unit/data_config/table_structure_guard_test.gd — 表结构守卫单元测试宿主
|
||||
# 断言面 = data/tables 各表的结构锚与守卫,七套:
|
||||
# adventure 冒险表(档位配额/件值对/同调目录 12 件/装配→战斗贯通)
|
||||
# enemy 敌人表(29 行带键行序/混编行集/三 Boss 行)
|
||||
# enemy 敌人表(30 行带键行序/混编行集/三 Boss 行/精英行)
|
||||
# AC 单轨与掷点参数(status_effect/reward/settlement 三处无双轨/免伤墙护栏/敌 AC 带宽)
|
||||
# variation 地形变异(行数/候选池门控/跨字段互锁负样本)
|
||||
# difficulty 难度表(空表零行为/修饰套乘/越域 clamp)
|
||||
@@ -208,18 +208,18 @@ func _smoke_adventure_table() -> void:
|
||||
v18_ok and locked_n == 12)
|
||||
|
||||
|
||||
# ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:29 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单)----
|
||||
# ---- 敌人表深度带键结构(迭代 34 建·L1 混编批换代:30 行键序=覆盖表序·(1,3) 混编行集·Boss 行集带键清单·精英行)----
|
||||
|
||||
func _smoke_enemy_bands() -> void:
|
||||
# oracle: design/gdd/systems/冒险循环/难度曲线与Boss.md:316 难度与敌人带校验(敌人带组)
|
||||
print("--- 敌人表深度带键结构(29 行键序 · (1,3) 混编 · Boss 带键覆盖表)---")
|
||||
# ① 29 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前)
|
||||
print("--- 敌人表深度带键结构(30 行键序 · (1,3) 混编 · Boss 带键覆盖表 · 精英行)---")
|
||||
# ① 30 行计数 + 键序 = 覆盖表序(文件行序不变——行序即契约;(1,3) 两行相邻、熊地精在前;精英行随精英专属池批)
|
||||
# 迭代 35 生成图深度扩面覆盖批随动:战斗带顶扩到各关最大生成深度(L1→6/L2→9/L3→13)、
|
||||
# Boss 行按终点深度分带(L1 6/L2 8·9/L3 11·12·13),模板时代带键 4/9/9 全部在册不动
|
||||
var expect_keys: Array[String] = [
|
||||
"1|battle|0|goblin", "1|battle|1|goblin", "1|battle|2|bugbear", "1|battle|3|bugbear", "1|battle|3|goblin",
|
||||
"1|battle|6|bugbear",
|
||||
"1|boss|4|ogre_chief", "1|boss|6|ogre_chief",
|
||||
"1|boss|4|ogre_chief", "1|boss|6|ogre_chief", "1|elite|0|goblin_elite",
|
||||
"2|battle|0|bugbear", "2|battle|1|bugbear", "2|battle|4|giant_spider",
|
||||
"2|battle|5|bugbear", "2|battle|5|giant_spider", "2|battle|6|giant_spider", "2|battle|7|giant_spider",
|
||||
"2|battle|9|giant_spider",
|
||||
@@ -232,8 +232,8 @@ func _smoke_enemy_bands() -> void:
|
||||
for row in _db.combat.enemy.rows:
|
||||
got_keys.append("%d|%s|%d|%s" % [int(row.get("level_index", -1)), String(row.get("row_kind", "")),
|
||||
int(row.get("depth_band", -1)), String(row.get("enemy_id", ""))])
|
||||
Kit.check("敌人表 29 行且键序 = 覆盖表序((1,3) 两行相邻、熊地精在前)〔L1混编 v1〕",
|
||||
_db.combat.enemy.rows.size() == 29 and got_keys == expect_keys)
|
||||
Kit.check("敌人表 30 行且键序 = 覆盖表序((1,3) 两行相邻、熊地精在前;精英行接 boss 行后)〔L1混编 v1〕",
|
||||
_db.combat.enemy.rows.size() == 30 and got_keys == expect_keys)
|
||||
# ② (1,3) 混编构成与行序+静态最坏骰和+行值整行复用(R1-R4)
|
||||
var rowset13: Array[Dictionary] = _db.combat.enemy.resolve_rowset(1, 3, "battle")
|
||||
var ids13: Array[String] = []
|
||||
@@ -386,6 +386,8 @@ func _smoke_ac_swap_guard() -> void:
|
||||
and l12_pool == {"attack": 6, "charge": 2, "heavy": 2})
|
||||
var l1_totals := {}
|
||||
for row in _db.combat.enemy.rows:
|
||||
if String(row.get("row_kind", "")) == "elite":
|
||||
continue # 曲线护栏管遭遇曲线——elite 不进遭遇(敌人系统.md:151),不跳过则 band0 失真
|
||||
if int(row.get("level_index", -1)) != 1:
|
||||
continue
|
||||
var cnt_lo: int = int(row.get("unit_count", 1))
|
||||
|
||||
+3
-3
@@ -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")
|
||||
File renamed without changes.
@@ -0,0 +1,44 @@
|
||||
# tests/unit/presentation/animation_frame_spec_test.gd — 动画帧数规格守门(定值面=本测试)
|
||||
# 断言面:①帧数规格表冻结(待机循环 4 帧/攻击准备 6 帧单次/受击 2 帧——样张冻结定值,
|
||||
# 规格表与圣经 §8.4 帧数规格条对照,守门防漂移);②现役四态单帧形制锚定(idle/walk 32×32、attack 92×92、
|
||||
# hit 88×92——现役为单帧状态件,序列帧资产入包时按规格表逐件判帧数并同批换代本套件);
|
||||
# ③死亡=灰化+碎裂退场(非帧件,无帧数断言面)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/animation_frame_spec_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
|
||||
# 帧数规格表(样张冻结值——多帧序列帧资产的生产规格;现役单帧占位件不适用本表)
|
||||
const SPEC := {"idle": 4, "attack": 6, "hit": 2}
|
||||
# 现役单帧形制(宽,高)——序列帧资产替换形制时同批修订本表并激活规格逐件断言
|
||||
const CURRENT_FORM := {
|
||||
"character_hero_idle.png": Vector2i(32, 32),
|
||||
"character_hero_walk.png": Vector2i(32, 32),
|
||||
"character_hero_attack.png": Vector2i(92, 92),
|
||||
"character_hero_hit.png": Vector2i(88, 92),
|
||||
}
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# oracle: design/art/art-bible.md:539 (帧数规格条——样张冻结定值组)
|
||||
_test_spec_table()
|
||||
_test_current_form()
|
||||
quit(Kit.finish("animation_frame_spec_test"))
|
||||
|
||||
|
||||
# ① 规格表冻结面(帧数档定值单点=本表)
|
||||
func _test_spec_table() -> void:
|
||||
# oracle: design/art/art-bible.md:539 (帧数规格表冻结组)
|
||||
Kit.check("待机循环 4 帧在表〔帧规格 V1〕", SPEC.get("idle", 0) == 4)
|
||||
Kit.check("攻击准备 6 帧单次在表〔帧规格 V2〕", SPEC.get("attack", 0) == 6)
|
||||
Kit.check("受击 2 帧在表〔帧规格 V3〕", SPEC.get("hit", 0) == 2)
|
||||
|
||||
|
||||
# ② 现役单帧形制锚定(真实加载链——资产被换形制而未走规格表时此处红)
|
||||
func _test_current_form() -> void:
|
||||
# oracle: design/art/art-bible.md:539 (现役单帧形制组)
|
||||
for fn: String in CURRENT_FORM:
|
||||
var tex: Texture2D = load("res://assets/art/character/" + fn)
|
||||
var want := Vector2(CURRENT_FORM[fn])
|
||||
Kit.check("现役件在库且形制冻结:%s %s〔帧规格 V4〕" % [fn, want],
|
||||
tex != null and tex.get_size() == want)
|
||||
@@ -92,7 +92,7 @@ func _test_boss_name_uses_boss_rowset() -> void:
|
||||
# 关卡完成停点文案族措辞钉子(口径换代批):回血行/满血合法态/渠道句/摘要关数行——
|
||||
# 旧措辞退役后玩家可见面由本组断言钉在新措辞上(撤销证明:回退 battle_narrator 对应行→断言红→复原复绿)
|
||||
func _test_level_clear_wording() -> void:
|
||||
# oracle: design/gdd/ux/level-clear.md:11 (关卡结算屏文案组)
|
||||
# oracle: design/ux/level-clear.md:11 (关卡结算屏文案组)
|
||||
var healed: String = NarratorScript.curio_pick_header({"level_index": 1, "heal_gained": 5, "hp_before": 10, "hp_after": 15})
|
||||
Kit.check("关卡完成回血行带入回血量与生命区间(实际「%s」)〔口径换代 V1〕" % healed,
|
||||
healed.contains("关卡完成回血 +5(生命 10 → 15)"))
|
||||
|
||||
@@ -10,16 +10,22 @@ const _EXPECTED_KEYS: Array[StringName] = [&"gate", &"forge", &"dummy", &"chest"
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:29 (营地场景布局数据件——纵深与地平线组)
|
||||
call_deferred("_test_layout_hotspot_geometry")
|
||||
# oracle: design/ux/camp.md:29 (营地场景布局数据件——纵深与地平线组)
|
||||
call_deferred("_run_all")
|
||||
|
||||
|
||||
func _run_all() -> void:
|
||||
# oracle: design/ux/camp.md:29 (套件统一收尾——双测试函数单进程单 quit)
|
||||
await _test_layout_hotspot_geometry()
|
||||
await _test_hud_band_contrast_over_bg()
|
||||
quit(Kit.finish("camp_scene_layout_test", "PASS — 布局几何+顶栏栏体对比 全过"))
|
||||
|
||||
|
||||
func _test_layout_hotspot_geometry() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:16 (七物件命中区几何红线组)
|
||||
# oracle: design/ux/camp.md:16 (七物件命中区几何红线组)
|
||||
var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres")
|
||||
Kit.check("V14 布局表加载且 schema 类在位〔营地图批 V14〕", table is CampSceneLayoutTable and table != null)
|
||||
if table == null:
|
||||
quit(Kit.finish("camp_scene_layout_test"))
|
||||
return
|
||||
var problems: Array[String] = table.validate()
|
||||
Kit.check("V14 校验函数通过(问题清单空,实得 %s)〔营地图批 V14〕" % [problems], problems.is_empty())
|
||||
@@ -88,4 +94,54 @@ func _test_layout_hotspot_geometry() -> void:
|
||||
var load_ok: bool = db.load_tables("res://data/tables", true)
|
||||
Kit.check("V14 ConfigDB 装载含营地布局表且校验过(is_ready=%s)〔营地图批 V14〕" % db.is_ready,
|
||||
load_ok and db.is_ready and db.presentation.camp_scene_layout != null)
|
||||
quit(Kit.finish("camp_scene_layout_test"))
|
||||
|
||||
|
||||
# 顶栏栏体规格侧对比核验(R6——与 R5 分工:R5 核实装等值,本行核规格值本身达标):
|
||||
# 按规格句在测试内写死 #6E4526 与 α0.80,与背景贴图顶带实测均色做 α 合成后算 WCAG 对比 ≥3.0。
|
||||
# 禁把视图件的 color 直接当预期值喂进算式(oracle 镜像——test-conventions §8.7 判红)。
|
||||
func _test_hud_band_contrast_over_bg() -> void:
|
||||
# oracle: design/ux/camp.md:41 (顶栏栏体定值——对落位带底色对比 ≥3:1 组)
|
||||
var table: CampSceneLayoutTable = load("res://data/tables/camp_scene_layout.tres")
|
||||
var band_h := 48
|
||||
if table != null:
|
||||
band_h = int(table.hud_band_h) # 带高取数据出口(禁字面量)
|
||||
var tex = load("res://assets/art/scene/scene_camp_bg.png")
|
||||
var img: Image = null
|
||||
if tex != null and tex.get_image() != null:
|
||||
img = tex.get_image()
|
||||
var scan_n := 0
|
||||
var r_sum := 0.0
|
||||
var g_sum := 0.0
|
||||
var b_sum := 0.0
|
||||
if img != null:
|
||||
for y in range(0, mini(band_h, img.get_height())):
|
||||
for x in range(img.get_width()): # 双层全像素扫描(非步长抽样——terrain_tile_sets_test 同款)
|
||||
var c := img.get_pixel(x, y)
|
||||
r_sum += c.r
|
||||
g_sum += c.g
|
||||
b_sum += c.b
|
||||
scan_n += 1
|
||||
Kit.check("R6 扫描面非空前置(顶带像素 %d > 0——贴图装载且带高在界)〔营地交互批 V5〕" % scan_n, scan_n > 0)
|
||||
if scan_n == 0:
|
||||
return
|
||||
var bg := Color(r_sum / float(scan_n), g_sum / float(scan_n), b_sum / float(scan_n))
|
||||
# 规格侧算式(camp.md:41 写死——#6E4526 × α0.80 对落位带背景合成)
|
||||
var fg := Color(0x6e / 255.0, 0x45 / 255.0, 0x26 / 255.0, 0.80)
|
||||
var mixed := Color(
|
||||
fg.r * fg.a + bg.r * (1.0 - fg.a),
|
||||
fg.g * fg.a + bg.g * (1.0 - fg.a),
|
||||
fg.b * fg.a + bg.b * (1.0 - fg.a))
|
||||
var lum_fg: float = _wcag_lum(mixed)
|
||||
var lum_bg: float = _wcag_lum(bg)
|
||||
var ratio: float = (maxf(lum_fg, lum_bg) + 0.05) / (minf(lum_fg, lum_bg) + 0.05)
|
||||
print("[camp_scene_layout_test] 顶带均色=%s 合成后=%s WCAG 对比=%.2f:1(规格 ≥3:1)" % [bg, mixed, ratio])
|
||||
Kit.check("R6 栏体合成色对落位带底色对比 ≥3.0(实测 %.2f)〔营地交互批 V5〕" % ratio, ratio >= 3.0)
|
||||
|
||||
|
||||
## WCAG 相对亮度(sRGB 线性化——命名函数形态,避多行 lambda 嵌套三元解析坑)
|
||||
func _wcag_lum(c: Color) -> float:
|
||||
return 0.2126 * _srgb_lin(c.r) + 0.7152 * _srgb_lin(c.g) + 0.0722 * _srgb_lin(c.b)
|
||||
|
||||
|
||||
func _srgb_lin(v: float) -> float:
|
||||
return v / 12.92 if v <= 0.03928 else pow((v + 0.055) / 1.055, 2.4)
|
||||
@@ -0,0 +1,59 @@
|
||||
extends SceneTree
|
||||
# 确认弹窗按钮位次与语义同钉单元套——
|
||||
# 守 ConfirmDialog 公开入口 open()/open_three()(四处消费方实际用的两个公开方法)的按钮行子节点序:
|
||||
# 推进钮恒 child(0)、次推进 child(1)、维持 child(2)(HUD框架.md:90 三条纪律·其三——营地交互修复批 R3)。
|
||||
# 判据面=树的公开子节点序+Button 公开 text,零私有成员直读;次钮在 open() 里只置 visible=false
|
||||
# 不从子节点表移除——双钮形态下子节点仍为 3,按下标断必取 child(2)。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/confirm_dialog_test.gd(expect 0)
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const ConfirmDialogScript := preload("res://presentation/views/confirm_dialog.gd")
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 (确认弹窗按钮位次定值——位次与语义同钉组)
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
# 按类型向下定位第一个 HBoxContainer(该弹窗树内唯一横排——modal_shell 产 CenterContainer+
|
||||
# PanelContainer+VBox 不产横排;HSeparator 与 UiKit.button 产件也不是 HBoxContainer)
|
||||
func _find_btn_row(node: Node) -> HBoxContainer:
|
||||
if node is HBoxContainer:
|
||||
return node
|
||||
for ch in node.get_children():
|
||||
var found := _find_btn_row(ch)
|
||||
if found != null:
|
||||
return found
|
||||
return null
|
||||
|
||||
|
||||
func _test_two_button_row_order() -> void:
|
||||
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 双钮形态位次=推进在左、维持在右(两钮同钉组)
|
||||
var ov: Control = ConfirmDialogScript.new()
|
||||
root.add_child(ov)
|
||||
ov.open("t", "推进", "维持")
|
||||
var row := _find_btn_row(ov)
|
||||
Kit.check("双钮形态按钮行子节点数=3(次钮只隐藏不移除——非空前置)", row != null and row.get_child_count() == 3)
|
||||
if row != null and row.get_child_count() == 3:
|
||||
Kit.check("双钮形态位次 child(0)=推进", String((row.get_child(0) as Button).text) == "推进")
|
||||
Kit.check("双钮形态位次 child(2)=维持(次钮占 child(1) 位——按下标断必取 child(2))", String((row.get_child(2) as Button).text) == "维持")
|
||||
ov.queue_free()
|
||||
|
||||
|
||||
func _test_three_button_row_order() -> void:
|
||||
# oracle: design/gdd/systems/表现与支撑/HUD框架.md:90 三钮形态位次=推进/次推/维持(三钮同钉组)
|
||||
var ov: Control = ConfirmDialogScript.new()
|
||||
root.add_child(ov)
|
||||
ov.open_three("t", "推进", "次推", "维持")
|
||||
var row := _find_btn_row(ov)
|
||||
Kit.check("三钮形态按钮行子节点数=3(非空前置)", row != null and row.get_child_count() == 3)
|
||||
if row != null and row.get_child_count() == 3:
|
||||
Kit.check("三钮形态位次 child(0)=推进", String((row.get_child(0) as Button).text) == "推进")
|
||||
Kit.check("三钮形态位次 child(1)=次推", String((row.get_child(1) as Button).text) == "次推")
|
||||
Kit.check("三钮形态位次 child(2)=维持", String((row.get_child(2) as Button).text) == "维持")
|
||||
ov.queue_free()
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
_test_two_button_row_order()
|
||||
_test_three_button_row_order()
|
||||
quit(Kit.finish("confirm_dialog_test", "PASS — 双钮/三钮形态位次与语义同钉 全过"))
|
||||
@@ -0,0 +1 @@
|
||||
uid://d0afgefg84yin
|
||||
@@ -0,0 +1,101 @@
|
||||
# tests/unit/presentation/palette_token_pairwise_test.gd — 语义/UI token 板两两互查守门
|
||||
# 断言面:ui_kit 全部 Color 常量两两 RGB 三通道差禁止全部 <8/255(圣经 §4.1 色板增量纪律第二道门的
|
||||
# 存量面执法件,数值门=本件 NEAR_EPS);六对已登记欠账走豁免账(债面=圣经 §4.1 欠账清单+待办清单
|
||||
# 「色板近色欠账六对清偿」条;新增欠账禁直入本账,须走待办清单修订)。
|
||||
# 豁免账中两对伙伴色(3e3731/3e4048/3f4147)为迷雾合成底素材侧色——不在 ui_kit 常量面,
|
||||
# 其清偿随素材批处理;本测试扫常量面,素材侧对子由互辨抽测件近色读数承载。
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/palette_token_pairwise_test.gd
|
||||
extends SceneTree
|
||||
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const UI_KIT_PATH := "res://presentation/views/ui_kit.gd"
|
||||
|
||||
const NEAR_EPS := 8
|
||||
|
||||
# 存量近色关系在册账(键=两色 hex 升序拼接,值=性质标签)。账面=常量面全量现状的机器真值;
|
||||
# 闸只拦「账外新增」。标「欠账」四对=art-bible 原六对债的常量面部分(清偿批销账);
|
||||
# 另两对(3e362c3e3731/3e40483f4147)伙伴为迷雾合成底素材侧色,不在常量面,随素材批清偿。
|
||||
# 同一键可覆盖多名对(同值别名与其伙伴构成的同 hex 对)。
|
||||
const EXEMPT_LEDGER := {
|
||||
"140f0d18120c": "深墨族(DIM↔DIM_NIGHT)",
|
||||
"140f0d181410": "深墨族(DIM↔INK_ROUTE)",
|
||||
"18120c181410": "深墨族(DIM_NIGHT↔INK_ROUTE)〔欠账〕",
|
||||
"18120c1e1812": "深墨族(DIM_NIGHT↔FOG_OUTLINE)",
|
||||
"291f172e2218": "暗底族(INK_LABEL↔TIP_BG)",
|
||||
"2820182e2218": "暗底族(CAMP_BADGE_PANEL↔INK_LABEL)",
|
||||
"282018291f17": "暗底族(CAMP_BADGE_PANEL↔TIP_BG)",
|
||||
"33261b342a20": "墨族近结构件(E_FACADE_FOOT_COL↔INK)",
|
||||
"33261b362b20": "墨族近屏底(INK↔BG_OVERLAY)〔欠账〕",
|
||||
"342a20362b20": "结构件暗棕近屏底(E_FACADE_FOOT_COL↔BG_OVERLAY)",
|
||||
"342a203a2e22": "结构件暗棕近屏底(E_FACADE_FOOT_COL↔BG_ROOT)",
|
||||
"362b203a2e22": "屏底双件(BG_OVERLAY↔BG_ROOT)〔欠账〕",
|
||||
"3a3a3a403a34": "暗板族(DARK_UPCOMING↔FOG_MEMORY_PLATE)",
|
||||
"4537234a3b2a": "暖亮底双件(BG_BRIGHT↔BG_MAP_TOP)〔欠账〕",
|
||||
"4a3b2a4a3e30": "同域暗棕(BG_MAP_TOP↔HINT_STRIP_EDGE)",
|
||||
"6e45266e4526": "α 派生别名(CAMP_HEADER_BAND↔CAMP_HUD_BAND,非色板新增)",
|
||||
"786e58786e58": "文档别名(DOT_OFF↔PANEL_BORDER——兼熄灭点阵)",
|
||||
"1a17141e1812": "深墨族(FOG_OUTLINE↔ICON_TINT_ON_GOLD)",
|
||||
"1814101a1714": "深墨族(ICON_TINT_ON_GOLD↔INK_ROUTE)",
|
||||
"1a17141e1a16": "深墨族(ICON_TINT_ON_GOLD↔INK_TITLE)",
|
||||
"1814101e1812": "深墨族(FOG_OUTLINE↔INK_ROUTE)",
|
||||
"1e18121e1a16": "深墨族(FOG_OUTLINE↔INK_TITLE)",
|
||||
"1814101e1a16": "深墨族(INK_ROUTE↔INK_TITLE)",
|
||||
"1a1a1c1e1a16": "深墨族(HP_BAR_TRACK↔INK_TITLE)",
|
||||
"2e221833261b": "墨族(INK↔INK_LABEL)",
|
||||
"c9b88ecdb88c": "暖米族(MAP_VDIV↔WARM_FAINT)",
|
||||
"e0b860e6be5a": "金族亮档双件(GOLD_BRIGHT↔GOLD_LIT)",
|
||||
"f0e2c8f0e2c8": "同值别名(INK_L_WARM↔WARM)",
|
||||
"f0e2c8f0e4c8": "暖米板族(INK_L_WARM/MAP_BAR_PLATE/WARM 同 hex 双名对)",
|
||||
"f0e2c8f0e6ce": "暖米板族(CAMP_ROW_PLATE↔INK_L_WARM/WARM 同 hex 双名对)",
|
||||
"f0e4c8f0e6ce": "暖米板族(CAMP_ROW_PLATE↔MAP_BAR_PLATE)",
|
||||
"f0e6cef5ead3": "暖米板族(CAMP_ROW_PLATE↔DIGIT_PLATE)",
|
||||
"f0e8d6f5ead3": "暖纸板族(DIGIT_PLATE↔TOAST_BG)",
|
||||
"f5ead3f6eeda": "暖纸板族(DIGIT_PLATE↔INK_ON_DEEP)",
|
||||
"f0e8d6f6eeda": "暖纸板族(INK_ON_DEEP↔TOAST_BG)",
|
||||
"f8e2a8f8e3a8": "同族亮金墨(INK_L_GOLD↔RAP_DOT_CORE_WARM)",
|
||||
}
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# oracle: design/art/art-bible.md:220 (色板增量纪律条——存量两两互查组)
|
||||
_test_pairwise()
|
||||
quit(Kit.finish("palette_token_pairwise_test"))
|
||||
|
||||
|
||||
func _token_colors() -> Dictionary:
|
||||
var script: Script = load(UI_KIT_PATH)
|
||||
var cmap: Dictionary = script.get_script_constant_map()
|
||||
var out: Dictionary = {}
|
||||
for name: String in cmap:
|
||||
if cmap[name] is Color:
|
||||
out[name] = cmap[name]
|
||||
return out
|
||||
|
||||
|
||||
func _hex(c: Color) -> String:
|
||||
return "%02x%02x%02x" % [c.r8, c.g8, c.b8]
|
||||
|
||||
|
||||
# 存量两两互查:豁免账外零近色对(扫描面>0 前置防静默绿)
|
||||
func _test_pairwise() -> void:
|
||||
# oracle: design/art/art-bible.md:220 (色板增量纪律条——存量两两互查组)
|
||||
var tokens := _token_colors()
|
||||
Kit.check("token 面非空(扫描面>0)〔token互查 V1〕", tokens.size() >= 40)
|
||||
var names: Array = tokens.keys()
|
||||
names.sort()
|
||||
var bad: Array = []
|
||||
for i in range(names.size()):
|
||||
for j in range(i + 1, names.size()):
|
||||
var a: Color = tokens[names[i]]
|
||||
var b: Color = tokens[names[j]]
|
||||
if absi(a.r8 - b.r8) < NEAR_EPS and absi(a.g8 - b.g8) < NEAR_EPS \
|
||||
and absi(a.b8 - b.b8) < NEAR_EPS:
|
||||
var pair: Array = [_hex(a), _hex(b)]
|
||||
pair.sort()
|
||||
var key: String = pair[0] + pair[1]
|
||||
if not EXEMPT_LEDGER.has(key):
|
||||
bad.append("%s↔%s(%s)" % [names[i], names[j], key])
|
||||
Kit.check("豁免账外零新增近色对〔token互查 V2〕", bad.is_empty())
|
||||
if not bad.is_empty():
|
||||
for item: String in bad:
|
||||
print(" [近色对] ", item)
|
||||
@@ -6,10 +6,11 @@ extends SceneTree
|
||||
# 运行:godot --headless --path game --script res://tests/unit/presentation/reward_overlay_test.gd(expect 0)
|
||||
# 断言走 test_kit 计数(Kit.check)+ 失败即停早退协议(顺序状态机——坏态下后继断言无效)。
|
||||
const Kit := preload("res://tests/unit/test_kit.gd")
|
||||
const ProbeUiUtil := preload("res://probe/probe_ui_util.gd") # 真输入合成单点(tests 面复用共用件——禁复制第二份合成输入件)
|
||||
|
||||
# 断言 + 失败即停(原直写 print FAIL + quit(1) + return 样板单点化——早退语义逐位保持)
|
||||
func _req(desc: String, ok: bool) -> bool:
|
||||
# oracle: design/gdd/ux/node-reward.md:9 (奖励覆盖层直验——需求包装组)
|
||||
# oracle: design/ux/node-reward.md:9 (奖励覆盖层直验——需求包装组)
|
||||
Kit.check(desc, ok)
|
||||
if not ok:
|
||||
quit(1)
|
||||
@@ -17,11 +18,11 @@ func _req(desc: String, ok: bool) -> bool:
|
||||
return true
|
||||
|
||||
func _initialize() -> void:
|
||||
# oracle: design/gdd/ux/node-reward.md:9 (奖励节点装载组)
|
||||
# oracle: design/ux/node-reward.md:9 (奖励节点装载组)
|
||||
call_deferred("_run")
|
||||
|
||||
func _run() -> void:
|
||||
# oracle: design/gdd/ux/node-reward.md:9 (奖励三选一呈现组)
|
||||
# oracle: design/ux/node-reward.md:9 (奖励三选一呈现组)
|
||||
var ov = load("res://presentation/views/curio_pick_overlay.gd").new()
|
||||
root.add_child(ov)
|
||||
var picked := []
|
||||
@@ -60,13 +61,15 @@ func _run() -> void:
|
||||
if not _req("付费档余额可付:免费位 0 但钮点亮(对齐商店——GP 档可刷)",
|
||||
ov._refresh_btn.visible and not ov._refresh_btn.disabled):
|
||||
return
|
||||
ov.press_refresh()
|
||||
if not _req("press_refresh 未外发 refresh_requested", refreshed.size() == 1):
|
||||
await process_frame # 刷新钮现形当帧容器重排在途——排版帧后矩形成形再真点
|
||||
ProbeUiUtil.push_click(root, ov._refresh_btn) # 真点刷新钮(disabled 守卫=真实拒绝路径)
|
||||
if not _req("真点刷新未外发 refresh_requested", refreshed.size() == 1):
|
||||
return
|
||||
# ② 点选→高亮→确认外发(真实按钮路径);确认前改选以最后点选为准
|
||||
ov.press_card(0)
|
||||
ov.press_card(1)
|
||||
ov.press_confirm()
|
||||
# ② 点选→高亮→确认外发(真输入管线:bcard gui_input 点选——与 press_card 同一 _select_card 处理器);确认前改选以最后点选为准
|
||||
ProbeUiUtil.push_click_at(root, (ov._bcards[0] as Control).get_global_rect().get_center())
|
||||
ProbeUiUtil.push_click_at(root, (ov._bcards[1] as Control).get_global_rect().get_center())
|
||||
await process_frame # 确认钮现形当帧容器重排在途——排版帧后矩形成形再真点
|
||||
ProbeUiUtil.push_click(root, ov._confirm_btn)
|
||||
if not _req("点选确认未外发 pick_confirmed(gp)(%s)" % str(picked),
|
||||
ov._confirm_btn.visible and picked.size() == 1 and String(picked[0]) == "gp"):
|
||||
return
|
||||
@@ -76,7 +79,8 @@ func _run() -> void:
|
||||
if not _req("结果态应点亮继续且收三卡与刷新钮",
|
||||
ov._continue_btn.visible and not ov._choice_box.visible and not ov._refresh_btn.visible):
|
||||
return
|
||||
ov.press_continue()
|
||||
await process_frame # 继续钮现形当帧容器重排在途——排版帧后矩形成形再真点
|
||||
ProbeUiUtil.push_click(root, ov._continue_btn)
|
||||
if not _req("继续未外发 continue_pressed", continued.size() == 1):
|
||||
return
|
||||
ov.close_pick()
|
||||
|
||||
@@ -10,7 +10,7 @@ const CELL_UNIT := preload("res://presentation/views/board_cell_unit.gd")
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:128 (封印印记参数宿主——装载组)
|
||||
# oracle: design/ux/battle.md:128 (封印印记参数宿主——装载组)
|
||||
_test_param_values()
|
||||
_test_shader_uniforms()
|
||||
_test_fog_gate()
|
||||
@@ -19,7 +19,7 @@ func _init() -> void:
|
||||
|
||||
# R1–R3/R7 前置:五常量取值逐项相等(art-bible §8.2 定值)
|
||||
func _test_param_values() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:128 (环径与法阵定值组)
|
||||
# oracle: design/ux/battle.md:128 (环径与法阵定值组)
|
||||
Kit.check("SEAL_RING_FRAC = 0.82〔seal批 V1〕", UiKit.SEAL_RING_FRAC == 0.82)
|
||||
Kit.check("SEAL_WALL_FRAC = 0.11〔seal批 V2〕", UiKit.SEAL_WALL_FRAC == 0.11)
|
||||
Kit.check("SEAL_ARM_FRAC = 0.156〔seal批 V2〕", UiKit.SEAL_ARM_FRAC == 0.156)
|
||||
@@ -29,7 +29,7 @@ func _test_param_values() -> void:
|
||||
|
||||
# R1–R3/R7:着色器存在+七个 uniform 名在文(参数注入面契约)
|
||||
func _test_shader_uniforms() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:128 (着色器参数化绘制组)
|
||||
# oracle: design/ux/battle.md:128 (着色器参数化绘制组)
|
||||
var src := FileAccess.get_file_as_string(SEAL_SHADER)
|
||||
Kit.check("着色器文件存在且非空:%s〔seal批 V1〕" % SEAL_SHADER, src.length() > 0)
|
||||
if src.length() == 0:
|
||||
@@ -43,7 +43,7 @@ func _test_shader_uniforms() -> void:
|
||||
# R4 迷雾门控:seal_on × fog_state → 封印子件可见性(内部字段直查=受控例外——子件可见性无公开可观测面,
|
||||
# 卡面 R4 断言即 prescribe 子件 visible 直查;对照表该行注记理由)
|
||||
func _test_fog_gate() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:128 (迷雾联动门控组)
|
||||
# oracle: design/ux/battle.md:128 (迷雾联动门控组)
|
||||
var u: Node2D = CELL_UNIT.new()
|
||||
u.seal_on = true
|
||||
Kit.check("seal_on 即挂封印子件〔seal批 V4〕", u._seal_layer != null)
|
||||
|
||||
@@ -26,12 +26,12 @@ class AllMissingStage extends BattleStageView:
|
||||
|
||||
|
||||
func _initialize() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (舞台背景宿主——装载组)
|
||||
# oracle: design/ux/battle.md:122 (舞台背景宿主——装载组)
|
||||
call_deferred("_run")
|
||||
|
||||
|
||||
func _run() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (B 区背景宿主组)
|
||||
# oracle: design/ux/battle.md:122 (B 区背景宿主组)
|
||||
_test_layout_ground_field()
|
||||
await _test_background_selection()
|
||||
await _test_background_fallback()
|
||||
@@ -42,7 +42,7 @@ func _run() -> void:
|
||||
|
||||
# ---- V1 ground_h 字段与校验(表值/默认表 validate 空/三态拒绝族) ----
|
||||
func _test_layout_ground_field() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (连续地面层组)
|
||||
# oracle: design/ux/battle.md:122 (连续地面层组)
|
||||
var layout = load("res://data/tables/battle_layout.tres")
|
||||
Kit.check("布局表 ground_h = 100 且默认表 validate 为空〔舞台背景 V1〕",
|
||||
int(layout.ground_h) == 100 and layout.validate().is_empty())
|
||||
@@ -75,7 +75,7 @@ func _make_stage() -> Node:
|
||||
|
||||
# ---- V2 背景按 board_type 选图(直建默认 dungeon + 三值路径逐值对应 + 层序在世界之下) ----
|
||||
func _test_background_selection() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (三关变体选图组)
|
||||
# oracle: design/ux/battle.md:122 (三关变体选图组)
|
||||
var layout = load("res://data/tables/battle_layout.tres")
|
||||
var stage: Node = _make_stage()
|
||||
await process_frame
|
||||
@@ -104,7 +104,7 @@ func _test_background_selection() -> void:
|
||||
|
||||
# ---- V3 缺图回退(目标缺→dungeon;全缺→平色兜底不崩不刷产品日志) ----
|
||||
func _test_background_fallback() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:122 (缺位回退组)
|
||||
# oracle: design/ux/battle.md:122 (缺位回退组)
|
||||
var s1: Node = MissingUnderdarkStage.new(null, load("res://data/tables/battle_layout.tres"))
|
||||
root.add_child(s1)
|
||||
await process_frame
|
||||
|
||||
@@ -69,7 +69,7 @@ class FakeDB:
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:154 (站桩呈现域缺陷扫掠组)
|
||||
# oracle: design/ux/battle.md:154 (站桩呈现域缺陷扫掠组)
|
||||
UiKit.motion_scale = 1.0
|
||||
|
||||
# ---- ⑤ 站桩按 id 解析(两口共用同名字敌载体)----
|
||||
|
||||
@@ -31,7 +31,7 @@ func _read_source(rel_path: String) -> String:
|
||||
|
||||
|
||||
func _init() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:154 (站桩呈现定值与素材绑定组)
|
||||
# oracle: design/ux/battle.md:154 (站桩呈现定值与素材绑定组)
|
||||
# ---- V5 血条填充 = 危险红 #A8321F(§4.2 语义物件显式落法) ----
|
||||
Kit.check("血条填充常量 = 危险红 #A8321F〔样张批 V5〕",
|
||||
UiKit.HP_BAR_FILL.to_html(false) == "a8321f")
|
||||
@@ -137,7 +137,7 @@ func _enemy_tex_path(key: String) -> String:
|
||||
|
||||
|
||||
func _check_display_tier() -> void:
|
||||
# oracle: design/gdd/ux/battle.md:154 (显示档与分辨率绑定组)
|
||||
# oracle: design/ux/battle.md:154 (显示档与分辨率绑定组)
|
||||
# V13/V15 九键逐一:真实入口 StandeeBody → set_data(enemy_id) → set_portrait_texture(真纹理) → portrait_probe
|
||||
for key: String in _DISPLAY_SPEC:
|
||||
var spec: Array = _DISPLAY_SPEC[key]
|
||||
|
||||
@@ -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
|
||||
@@ -805,7 +805,7 @@ func _all_labels(root: Node) -> Array:
|
||||
# 只读性连接巡检与零动效断言归 ui_struct_probe 呈现侧,本段只管数据面+旧档载入显示
|
||||
# (v5 样档升 v6——完成定义 6「计数域缺省补零、页正常显示」))----
|
||||
func _career_overlay_projection() -> void:
|
||||
# oracle: design/gdd/ux/camp.md:45 (生涯页只读投影组)
|
||||
# oracle: design/ux/camp.md:45 (生涯页只读投影组)
|
||||
print("--- 生涯页·只读投影一致性(隔离档 smoke_meta_career/career_v5)---")
|
||||
var sv = _meta_new_save(_META_SMOKE_PATHS[9])
|
||||
var mp = load("res://core/meta_progression.gd").new(_db, sv)
|
||||
|
||||
+3
-3
@@ -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()
|
||||
File renamed without changes.
@@ -1,109 +0,0 @@
|
||||
# 复核报告 · 营地交互修复批需求卡片(2026-11-05)
|
||||
|
||||
> 被复核对象:`reports/需求卡片-营地交互修复批-2026-11-05.md`(v1.0 成文态)
|
||||
> 复核者:未参与撰写的只读子代理(独立会话,禁写文件、禁跑闸、禁运行游戏测试),任务=对卡内每一处行号、契约、判据逐条开火
|
||||
> 复核轮次:1(第 16 节逐条登记去向与处置;本报告留发现原文与依据索引)
|
||||
> 复核对象工作副本:`D:/code/g-plan2-camp-interact`(分支 camp-interact-20261105)
|
||||
|
||||
## 复核者总判原文(摘录)
|
||||
|
||||
> 按现状不能进实现段。缺四块闭合:①契约1 收窄措辞(否则实现首帧即自触 L3);②R3/R4 的按钮行句柄定义+§12 受控例外改判(否则判据落不了码);③T3.1 的 `UiAssembler` 引用形态+T3.2 内缩实现与 R5 的 x 向判据(否则 12px 无机制、断言可假绿);④R6 呈色输入来源改绑视图实件(否则撞 §8.7 第 5 条判红)。另有 6 处纯事实订正可自判直改。T 卡估时合计 0.25+0.5+0.75+0.5=2.0 与「≈1.75–2.0」自洽;四处消费方与 HUD框架:90 语义映射核过无冲突。
|
||||
|
||||
## 发现与依据索引(22 条)
|
||||
|
||||
| # | 卡内位置 | 发现 | 复核者给的依据 | 去向 | 写卡段处置 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | §4 契约1 | 「隐藏点只许两处」按字面过宽,仓库另有 5 处以上 | `camp_shop_presenter.gd:43`、`main_overlay_flow.gd:8/41/95`、`main_debug_flow.gd:88` | 退回上游(复核者判) | 改自判:契约1 收窄为「限开局/续玩路径」,12 处显隐点逐条列名。定名不采纳——卡内措辞问题,非规范冲突 |
|
||||
| 2 | §3.1 | `_start_adventure()` 行号段错(写 311) | `main.gd:308`=`return`,`:311`=下一函数声明 | 自判 | 改 292-308 |
|
||||
| 3 | §9 T1.1 | 失败分支的开始层终态变化未申报 | `main.gd:306-307` `fatal_screen` | 补写 | §3.1 补终态申报 |
|
||||
| 4 | §8 R2/§9 T1.4 | 段内 await 1 帧不足以让选路停点成形 | `ui_click_probe.gd:703`;先例 `screen_flow_probe.gd:186-192` 用 `_frames(3)` | 补写 | T1.4 补帧口径+实测最小帧数入实现报告 |
|
||||
| 5 | §8 R3 | `btn_row` 是局部变量,判据无法写 | `confirm_dialog.gd:27`(成员仅 `:13-16`) | 补写(阻塞) | T2.1 升成员 `_btn_row`;R3 按 test-conventions §8.2 受控例外申报 |
|
||||
| 6 | §12 | 自称不直取私有,实际 R3/R4 直取 | `confirm_dialog.gd` 成员表;`main._confirm_consumer` | 自判 | 改为受控例外口径,不再谎称 |
|
||||
| 7 | §9 T2.3 | `:702` 真点已消耗 `gate_new`,R4 无左位可点 | 段结构 `ui_click_probe.gd:684-705`;`main.gd:215` 置空 | 补写 | T2.3 补前置重开步与坐标取法 |
|
||||
| 8 | §10 L1 | `:679` 引用定性错;直发面在 `:697`;归属应系 §8.3 而非契约7 | 复核者回读 | 自判 | **部分不采信**:`:679` 经写卡段回读确为段头注「大门两分支断言段」,断点注入起 `:706`;直发行号实为 `:696`(复核者写 697 亦偏);§8.3 归属采纳。附带自查纠正 R8 的次钮断言行 `:696`→`:694` |
|
||||
| 9 | §9 T3.1/§12 | `UiAssembler` 无 `class_name`,`views/` 零引用先例,卡内调用写法不成立 | `ui_assembler.gd:1-5`(`extends RefCounted`+头注「供组合根接线」)、引用方 `main_ui_build.gd:7`/`scenes/adventure_screen.gd:21`/`scenes/menu_screen.gd:15`、`views/` 内联先例 `battle_end_overlay.gd:261/289/314` | 待裁 | 第 15 节 Q1 两案并列;T3.1 改「待 Q1 后落」 |
|
||||
| 10 | §3.3/§8 R5 | 徽章自高 48 系推导,`panel_flat` `border_w=1` 若计入则 50 | `ui_kit.gd:640`+`panel_flat` `ui_kit.gd:455-464`(复核者自标「这是推测,未跑批」) | 补写 | T3.0 新增前置读数;≠48 走 L3 第 5 条 |
|
||||
| 11 | §9 T3.2/§11.3 | 12px 内缩无可达机制(宽度随位数变);R5 缺 x 向判据=改了仍绿 | `camp_hud_band.gd:16`(现值写死 x)+`:22`(文案「钥石 %d」) | 待裁+补写 | Q2 三案并列;R5 已补 `end.x == 栏 end.x − 12` |
|
||||
| 12 | §8 R6 | 呈色若取自实现表达式=oracle 镜像 | test-conventions §8.7 第 5 条 | 自判 | R6 改规格侧写死值,禁喂视图件 color |
|
||||
| 13 | §11.4 | 「4×8 步长沿该件口径」错,该件全像素扫描 | `terrain_tile_sets_test.gd:105-107` | 自判 | 白名单第 4 条改全扫描并声明旧口径作废 |
|
||||
| 14 | 数学校核 | 全扫与抽样均色皆 RGB(223,235,232)(卡 `#DEEAE7` 差 ≤1/255);叠后 4.29:1、徽章对天空 1.30:1;落位带 0–48 只含背景成立 | `camp_scene_layout.tres:9`(背景 `Rect2(0,0,1280,800)`)、围墙 y=261、大门 y=250;`project.godot:30` 视口宽 1280 | 自判(确认+读数微差) | 差值落 L2 容差,阈值不放宽;卡内读数保留 |
|
||||
| 15 | §1/§5 | 「七行」与对照表实数 R1–R8 不符 | 卡内自相 | 自判 | 改八行 |
|
||||
| 16 | §5 | 引「第 1 节行为表」——卡内无该表(悬空引用) | 卡内自相 | 自判 | 改自足表述 |
|
||||
| 17 | §7 | 结构图验收行缺 R7/R8 | 卡内自相 | 补写 | 两行补齐 |
|
||||
| 18 | §13/§10 L3 第4条 | gdstyle 不扫 tests,新件三查与互斥条对新件无触发路径;套件入口是 `_initialize()` 非 `_init` | `tools/gdstyle_gate.py:45`(SCAN_DIRS 三目录)+头注 `:8`(probe/sim/tests 排除);`camp_scene_layout_test.gd:12` | 自判 | §13 改「对本件不适用」;L3 第 4 条限定 presentation 面;T2.2 入口名与先例行号订正 |
|
||||
| 19 | §13 存量一查 | 核过无误:基线 `:11`=32、`:12`=23;`main.gd` 公开方法 31+1 静态=32 持平 | `tools/gdstyle_baseline.txt` | 自判(确认类) | 无改动,留档已核 |
|
||||
| 20 | §4 契约4 依据 | 称五 token 对齐机检「查无该件」(`palette_check.py` 无 CAMP_HEADER 逻辑,全仓只命中视图用法) | 复核者 grep | 退回上游(复核者判) | **不采信其结论**:写卡段回读 `tools/palette_check.py:22` 有 `ui_tokens.png`=ui_kit 全量 Color8 token 快照基线,机制存在但不逐名比对表内五行;契约4 依据改成该准确口径 |
|
||||
| 21 | §9 T2.2 | 册为硬编码清单,「接进套件册」不够具体 | `game/tests/run_all.gd:166` 起条目形态 `{"path":…, "expect":0}`+行尾注 | 补写 | T2.2 写死条目全文 |
|
||||
| 22 | 横面 | `ANCHOR_RE` 字符类不含中文段→中文名现行文档无法被机检认锚 | `tools/pipeline_gate.py:68` | 退回上游 | 列 §6 末行;本批不动闸件,是否登记归用户发令 |
|
||||
|
||||
## 采信统计与残余
|
||||
|
||||
- 采信并落卡:19 条(自判 11/补写 8)。
|
||||
- 转待裁(不代用户选):2 条(#9、#11 的机制案),落第 15 节 Q1/Q2。
|
||||
- 不采信结论:2 条(#8 的 `:679` 定性、#20 的「机检不存在」),均经写卡段逐行回读推翻,依据记在表内。
|
||||
- 残余(本卡范围外):#22 机检件缺口;以及本批既有的「art-bible §4.5 #7 破坏性确认异位是否追认」——早于本卡在册,见第 5 节不做表。
|
||||
|
||||
**卡片状态**:轮次 1 后为「待裁未清零」;轮次 2 后见下节。
|
||||
|
||||
---
|
||||
|
||||
## 轮次 2(Q1/Q2 裁决落卡之后,另起一个未参与撰写的只读子代理)
|
||||
|
||||
任务书限定只查五处,且每处须给 `文件:行号` 依据。发现五条:
|
||||
|
||||
| # | 卡内位置 | 发现 | 依据 | 去向 | 处置 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | §8 R3/§9 T2.2 | 双钮形态断言「child(1)=维持」必红——次钮只 `visible=false`、不移除 | `confirm_dialog.gd:47` | 补写 | 改 child(2) + 子节点数=3 非空前置(已落卡) |
|
||||
| 2 | §8 R5 | 未写「须先开营地」前置,boot 态营地屏为隐读不到尺寸 | `camp_scene_view.gd:51` | 补写 | 真实入口补 `open_camp()`+await 排版帧(已落卡) |
|
||||
| 3 | §9 T3.0 | PanelContainer 最小尺寸入树排版后才可信,刚 `new()` 读数不可信,时机未写 | `ui_kit.gd:634-656` | 补写 | 改为经公开句柄读在树的现成实例、await 后再读(已落卡) |
|
||||
| 4 | §3/§8 确认类 | 八处行号全对;`to_html(true)` 追加 α 两位、`6e4526cc` 串对;1280×48 尺寸链可达;「按类型找第一个 HBoxContainer」在本弹窗树内唯一 | `main.gd:211-212`/`:223-224`/`:292-308`、`ui_kit.gd:640`、`ui_assembler.gd:100/214/224-230`、`ui_kit.gd:189/190/200`、`standee_present_values_test.gd:36`、`scenes/camp_screen.tscn:7-11`、`project.godot:30-31` | 自判(留档) | 无改动 |
|
||||
| 5 | §15 Q1 | 裁决句字面只覆盖「色值放公共层」这条轴,「矩形谁建」是写卡段的展开 | 对话原话序列:写卡段推荐句为「色值进公共件、矩形留在顶栏件自建」,用户回话「那没有问题,就按放在公共区域的这一层的方案来实现」 | 待裁(复核者判)→ 按自判落并报备 | 卡内第 15 节与第 16 节各写入解释面一句;用户不认该读法即重开,重开范围仅止于矩形建造方 |
|
||||
|
||||
轮次 2 总判:三条补写已落卡,一条确认留档,一条按自判落并向用户报备。**该"自判闭合"事后被判无效**——见轮次 3 与卡片第 15/16 节的裁决史。
|
||||
|
||||
---
|
||||
|
||||
## 轮次 3(11-06 用户驳回"值与形拆两处"、改判整案收编之后;第三个只读子代理)
|
||||
|
||||
| # | 卡内位置 | 发现 | 依据 | 去向 | 处置 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | 契约4/§12 | 两处旧实现确未设 mouse_filter,ColorRect 默认即 STOP,出口默认值取 STOP 才不改三处旧遮罩语义 | `ui_assembler.gd:224-230`、`ui_kit.gd:486-491`;旁证 `ui_kit.gd:540`/`:864`、`camp_scene_view.gd:145` | 自判(确认) | 卡内补入旁证 |
|
||||
| 2 | T3.2 | 三处既有调用点都是"挂宿主+把返回句柄当父用",转调在层序上等值 | `ui_assembler.gd:100-110`/`:214`/`:232-239`、`ui_kit.gd:511-514` | 自判(确认) | 无需改 |
|
||||
| 3 | R9/T3.7 | `_modal_dim()` 经 `modal_shell()` 扇出 11 家消费件,R9 只点两处探针=覆盖不足 | `grep modal_shell(` 全仓 12 命中(含定义) | 补写 | 判据改源扫描式,并如实写明覆盖面(2 家断言+其余渲染闸与目检承接) |
|
||||
| 4 | R5②⑤ | 1280 宽的承重链引错(非 tscn 锚点,是注册层 FULL_RECT);⑤ 全局矩形与局部 position 混写 | `camp_scene_view.gd:13-14`、`ui_registry.gd:26`/`:61` | 补写 | R5 改引注册层、⑤ 改 `get_global_rect()` |
|
||||
| 5 | R9/T3.2 | "两文件 ColorRect 合计=1"算术不成立(`ui_kit` 实有 3 处自建) | `ui_kit.gd:487`/`:537`/`:862` | 补写 | 判据改为"两个被收编函数体内不再自建",范围显式声明,不写全仓唯一 |
|
||||
| 6 | R9 | `:537` 页壳底板与出口形状相同、仅显式 STOP 与实色之差——是否同一件事,判为待裁 | `ui_kit.gd:536-542` | **待裁** | 新增 Q4(两案代价并列,不自决) |
|
||||
| 7 | 多处 | 数字与交叉引用过期:显隐点 12→13(漏 `main_overlay_flow.gd:101`)、`views/` 自建先例 8 文件→7 文件 15 处、§5 与 §1 行数不一致、§12 步骤号未随重排更新、T4.1 应引 T3.9、§14/§15 把 tooltip 底误算进收编用途(`TIP_BG:190` 不经这两个函数) | `grep start_overlay.visible` 实测 13 处写点;`grep -c ColorRect.new()` views/*.gd 实测 7 文件 15 处 | 补写 | 全部按实测改正 |
|
||||
| 8 | 轮次 2 记录 | 复核者上一轮给的 `camp_scene_view.gd:53` 才是正确行号,**写卡段上一轮"纠正复核者"改成的 `:51` 是错的** | 本轮 grep 复核 | 自判(推翻写卡段自己) | 全文改回 `:53`,卡片第 16 节轮次 3 记一条 |
|
||||
|
||||
**写卡段对轮次 3 的不采信项**:无——这一轮唯一被推翻的是写卡段自己上一轮的判断。
|
||||
|
||||
**轮次 3 总判**:不能进实现段;除 Q4 待用户一句裁决外,其余补写已全部落卡。
|
||||
|
||||
## 轮次 4(11-06 撤收编版落卡后的独立复核)
|
||||
|
||||
复核者=第四个未参与撰写的只读子代理(禁写禁跑,须给 `文件:行号`)。审的是撤收编后的终态(`UiKit.camp_hud_band_surface(host)` 单参具名出口+零收编)。它报 17 条:硬伤 2、事实错 2、一致性 4、补写 3、确认 5、外部 1;未新增待裁点。
|
||||
|
||||
| # | 卡片位置 | 发现 | 依据 | 去向 | 落卡 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | R4 | 「按按钮行实际矩形算左位坐标」必打空:行 `alignment=CENTER`,双钮可见宽 216+12+216=444<壳宽 460,左位落 8px 空档 | `confirm_dialog.gd:29`、`ui_kit.gd:578`、`confirm_dialog.gd:20` | 补写 | R4 入口改取按钮行 child(0) 全局矩形中心并写入算式(与 T2.3 统一) |
|
||||
| 2 | T3.4/§7/§11.5 | 「`_smoke_camp_hud_band()` 接进 `_initialize()`」不成立:`_initialize` 只调 `_split_tail()`,摘要与 `quit()` 在尾段,不被 await=静默不执行=假绿 | `system_test.gd:26-28`/`:35-48`/`:40` 行尾注/`:213-217` | 补写 | 三处同批改「接进 `_split_tail()` 的 await 链」,段内自持 instantiate/queue_free 照 `_smoke_run_lifecycle()` |
|
||||
| 3 | §3.2/R3 | 四消费方行号偏一(卡 72/76-78/82,实 73/79-80/83;129 对) | `main_overlay_flow.gd:69-83`/`:125-129` | 自判 | 两处改正 |
|
||||
| 4 | R5② | 营地场景根代码 new 的位置引错:`camp_scene_view.gd:13-14` 是命中区名表,new 在屏壳 | `camp_scene_view.gd:12-15`、`scenes/camp_screen.gd:13-14`、`camp_screen.tscn` 根锚 preset=15 | 自判 | 改引 `scenes/camp_screen.gd:13-14`(**同时推翻轮次 3 第 4 行沿用的 `camp_scene_view.gd:13-14`**) |
|
||||
| 5 | R3 | 括注「`modal_shell()` 产 VBox+HSeparator」不实,HSeparator 是弹窗自加 | `ui_kit.gd:509-525`、`confirm_dialog.gd:26` | 自判 | 改为「产 CenterContainer+PanelContainer+VBox,不产横排」 |
|
||||
| 6 | T3.3/§12 | 「`confirm_dialog.gd:27-29` 是容器右对齐先例」不准:该处 alignment 取 CENTER,是同机制先例 | `confirm_dialog.gd:29` | 自判 | 措辞改正,明写本件取 END 才是右对齐 |
|
||||
| 7 | R6 | 「§8.4」未冠文档名,卡内无 8.4 小节 | `test-conventions.md` 正面效力自检条 | 自判 | 改「test-conventions §8.4」 |
|
||||
| 8 | R4 | 读 `main._confirm_consumer`(私有)与 §12「不读私有」口径不一,但探针先例在案、扫描射程不含探针 | `ui_click_probe.gd:272`/`:286`、`presentation_encapsulation_sweep_test.gd:80-94`(该件在 `game/tests/unit/presentation/`,复核者误记 `unit/contracts/`) | 自判 | 主证改列 `is_adventure`,私有一读注明先例与射程;**路径误记已纠正,结论不受影响** |
|
||||
| 9 | §14.1 | 出口职责句「返回句柄供顶栏件摆放徽章」与实际(返回值不用于摆位)不符 | 卡 T3.2/T3.3 | 自判 | 职责句改「供调用方读取与断言」,摆位归 T3.3 容器 |
|
||||
| 10 | T3.0/L3.5 | 徽章自高≠48 只列描边一因,漏 18px 文字行高这一同因 | `ui_kit.gd:648`(Label 取 `FS_SUB`)/`:438` | 补写 | 两因并列,处置强度不变 |
|
||||
| 11 | R2 | 判据非件1 改动特有面 | `ui_click_probe.gd:704` 既有同面断言 | 补写 | R2 注明特有提交面由 R1 承载、两行同批缺一即红 |
|
||||
| 12 | 待办清单 | 实况仍写「两轮独立复核报告」 | `待办清单.md:30` | 补写 | 改「四轮」 |
|
||||
| 13 | 全文 | 收编版残留扫描:无一处把收编/通用出口/color·pass_input/R9/改 `ui_assembler` 写成现行方案;T3.0–T3.7、R1–R8、白名单、L3、裁决史交叉引用零悬空 | 逐节比对+符号 grep | 确认 | 无需改 |
|
||||
| 14 | 全文 | 40 处行号引用逐行打开无误(不含本表第 3、4 两条例外) | 复核者逐行比对 | 确认 | 无需改 |
|
||||
| 15 | R5②③ | 未入树调 `set_anchors_preset(FULL_RECT)` 铺满成立、1280 宽承重链成立、色面必须入 `_init()`(入树前) | `ui_kit.gd:486-491`、`ui_assembler.gd:227`、`ui_registry.gd:61` | 确认 | 判据不变;按建议在 T3.4 补时机一句 |
|
||||
| 16 | §2 实测句 | `Color(常量, α)` 构造子库内大量现役;const 引同文件 const 无库内先例,卡凭 11-06 临时工程实测(仓库无产物可复验=推测),失配属编译期 fail-fast | grep 零命中 | 确认 | 维持实测句,不设断言 |
|
||||
| 17 | 契约/§5/§13/README | 契约4·5 与 §5 不做行无矛盾无空隙;两行新增能力名与 `tests/README.md:159/162/163` 逐字一致;预算实算正确(29 static func-6 私有=23→24) | 逐处比对 | 确认 | 无需改 |
|
||||
|
||||
**写卡段对轮次 4 的不采信项**:1 条——第 8 行的文件路径误记(实际在 `game/tests/unit/presentation/`),已按正确路径落卡,其结论经回读成立。
|
||||
|
||||
**轮次 4 总判**:两条硬伤与四处事实错、四条一致性问题、三条补写全部落卡,未新增待裁点。撤收编后的终态设计至此经独立复核闭合:零待裁点,开工门=U0(用户批卡)。
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
> 版本 v1 | 2026-10-30 | 目标引擎 Godot 4.7.2-stable(`gl_compatibility` / 纯 2D)| 平台 PC Windows 先行 | 设计分辨率 1280×800 基座 + 1920×1080(全局 1.35× 等比)
|
||||
> 性质:评估过程件,对应美术师交付物「美术资产清单」+「美术规格与预算」两章合并(一次问三事,不拆两份重复)。**未改任何权威文档与 `game/` 文件**;结论若要进 `design/` 走 doc-design 登记 + 用户发令。
|
||||
> 事实源:`design/art/art-assets-checklist.md`(需求)· `art-status-checklist.md`(现状)· `art-bible.md` §8(尺寸定值单点)· `asset-path-map.md`(路径)· `game/assets/` 实扫 · `game/project.godot` 与 117 份 `.import`(引擎现状)· `design/gdd/ux/battle.md` 板形表 · `game/data/tables/enemy.tres`。
|
||||
> 事实源:`design/art/art-assets-checklist.md`(需求)· `art-status-checklist.md`(现状)· `art-bible.md` §8(尺寸定值单点)· `asset-path-map.md`(路径)· `game/assets/` 实扫 · `game/project.godot` 与 117 份 `.import`(引擎现状)· `design/ux/battle.md` 板形表 · `game/data/tables/enemy.tres`。
|
||||
> 显存口径:`art-engine-handoff/scripts/asset_budget.py` 实算;格式按项目真实导入(`compress/mode=0` Lossless → 运行时解码为 RGBA8 = 4 B/px,关 mipmap)。Draw Call 与峰值显存**未真机实测**,只给内控线与估算口径。
|
||||
|
||||
## 0. 结论先行
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
# 需求卡片 · 营地交互修复批(2026-11-05)
|
||||
|
||||
> **来源**:待办清单条目「营地交互修复批——大门无断点开局被开始层盖住/确认框钮序/营地顶栏无栏体」(2026-11-05 登记,来源=会话提及)
|
||||
> **唯一事实源**:`design/gdd/ux/camp.md` v2.8 §顶栏 · §点选反馈 + `design/gdd/systems/表现与支撑/HUD框架.md` §提示与弹层(三条纪律·其三) + `design/gdd/systems/冒险循环/关卡节点地图.md` §核心循环位置 + `design/art/art-bible.md` v3.43 §6.2
|
||||
> 验收档:中(三件做错的后果都是玩家可见但不损持久状态——呈现与交互逻辑;不触存档写读、不触结算公式、不触 RNG 流。判据=本技能第 16 节单维失败代价)
|
||||
> **改动面**:游戏代码(`game/` 表现层+测试面)。文档面已由文档批落定为现行规格,本卡不携任何 `design/` 改动。
|
||||
> **编排状态**:未编迭代(roadmap 现行最高编号=迭代 52,已完成;本卡待 iter-plan 段编排)
|
||||
|
||||
---
|
||||
|
||||
## 1. 目标与完成定义
|
||||
|
||||
三件互不相干的营地屏交互缺陷,一批修完:
|
||||
|
||||
1. **大门无断点开局被开始层盖住**——玩家点大门、按「是」之后,冒险其实已经开局,但主菜单层(开始层)重新显出来并把冒险画面盖住,玩家看到的是主菜单,误以为没开始。修完=从大门真实点击进入后,开始层不可见、关卡画面(节点图选路停点)可见。
|
||||
2. **确认弹窗按钮位次反直觉**——同一个共用弹窗件把「维持现状」那颗放在左、「推进」那颗放在右,四处消费方(删档/大门三出口/大门两出口/篝火)皆然。修完=整体镜像为「推进在左、次推进居中、维持在右」,且位次与语义的绑定有真点验收。
|
||||
3. **营地顶栏看不出是一行 HUD 栏**——顶栏件已装配、已落座、预留位 48px 全宽都在,缺的是那层看得见的底:现在是透明 Control 只挂一枚徽章,而徽章的烛光黄底对背后天空均色只有 1.29:1。修完=按 camp.md v2.8 §顶栏 定值画出栏体(`CAMP_HEADER_BAND` × 不透明度 0.80),徽章顶格右位落进栏内,并有尺寸与对比的机检。
|
||||
|
||||
**完成定义**:四条全落即算这批做完——
|
||||
|
||||
- 对照表(第 8 节)八行断言(R1–R8)在当轮真实跑批中执行且通过,无红项遗留;
|
||||
- 第 8 节标注的两行新增能力已随写卡段落进 `game/tests/README.md`,实现批末核对名实相符;
|
||||
- 回归面(第 5 节列的七条未改动动线)在跑批中仍绿,未因位次改动或栏体新增而失守;
|
||||
- 末卡产出物(实现报告+反哺点清单)写齐,`reports/` 两份落位。
|
||||
|
||||
真人目检项(营地顶栏观感)登记进实现报告实机目检总清单即算义务了结,**不作合回前置**(AGENTS.md 红线:真人检测只登记不阻塞)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 设计事实源
|
||||
|
||||
本节为卡片设计依据的唯一出处声明,与待办清单条目实况的闭合凭据同批一致:
|
||||
|
||||
| 需求点 | 现行规格句 | 出处 |
|
||||
|---|---|---|
|
||||
| 件1 | 点大门永远先出确认,无未完冒险=是否开始新冒险 | `design/gdd/ux/camp.md:16` |
|
||||
| 件1 | 关卡开始 → 呈现节点图选路停点(关卡画面;入口不自动进入) | `design/gdd/systems/冒险循环/关卡节点地图.md:78` |
|
||||
| 件2 | 确认弹窗的按钮位次定值:推进钮在左、维持现状钮在右,三钮形态次推进居中,四处共用同一件同一序 | `design/gdd/systems/表现与支撑/HUD框架.md:90` |
|
||||
| 件3 | 栏体底=`#6E4526`×不透明度 0.80、高 48px 全宽浮层、栏内只钥石徽章一件右位顶格、验收=目检窄带可辨+机检对落位带底色 ≥3:1 | `design/gdd/ux/camp.md:41` |
|
||||
| 件3 | 色板五件对齐表在册色 `CAMP_HEADER_BAND #6E4526`(机检〈ui_kit 五 token 与本表一致〉) | `design/art/art-bible.md:452` |
|
||||
| 件3 | 命中区不侵入顶栏禁区(y ≤ 48) | `design/gdd/ux/camp.md:37` |
|
||||
|
||||
预览人审在案:2026-11-05 会话内实尺 HTML 预览(十张已过审场景层按 `camp_scene_layout.tres` 现值叠放+1:1 顶栏放大条+页内 WCAG 即时算式),用户审语「我看了预览的效果是OK的」,选定项=甲 深棕带 × 不透明度 0.80;快照文件 `chat-session-visualizations/…/visualizations/snapshot-444250310dc22234ac755b11fb31ce6885adb634b271c7083d53cc5d72b98640.html`(会话侧不可变快照,非仓库件)。像素级判据句已随附进 `camp.md` v2.8 §顶栏。
|
||||
|
||||
顶栏这块带子的落点定稿(用户 2026-11-05/11-06 四次裁决的终态,全程留痕见第 15 节 Q1 裁决史):**一个 UI 物件的外观不得拆两处**——色值与透明度、铺满方式、不吃点击、挂上宿主、返回句柄这五件事全在公共件 `ui_kit.gd` 的**一个具名公开出口**里;出口只接「挂到谁身上」一个形参,色与点击语义写死在它内部(它们是这块带子的定义,不是调用方每次要重述的选择)。同时**不收编盘上任何同形旧实现**:`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537` 只是形状撞车、变化原因各不同,把它们绑进同一个出口=为没到来的问题做设计,并把回归面扩到全站弹窗底与两个全屏页。顶栏件只调该出口一次,不再自建 ColorRect。两项引擎实测(4.7.2,工程外临时工程跑通后即删,与本条封装方式无关,仍成立):①`Color(母token, 0.80)` 形态的 const 派生编译通过,`to_html(true)` 读数 `6e4526cc`、三分量与母 token 逐一相等;②**色道存单精度浮点,`.a == 0.80` 判为 false**(实测 `a=0.80000001192093`)——断言一律写 `to_html(true) == "6e4526cc"` 或整体等值比较,禁浮点分量直等,否则实现正确而断言红,最容易的自救就成了放宽阈值(触 test-conventions §8.5 禁止削弱测试)。
|
||||
|
||||
---
|
||||
|
||||
## 3. 现状事实(2026-11-05 实查;开工日自行复核行号)
|
||||
|
||||
### 3.1 件1——开始层可见性无单点
|
||||
|
||||
- `game/presentation/camp_shop_presenter.gd:85`:`on_camp_closed()` 体内无条件 `start_overlay.visible = true`。该函数的本职是「回主菜单」,篝火离场(`game/presentation/main.gd:214`)与 Esc 返回主菜单(`game/presentation/main_esc_flow.gd:46`)都靠这句显出菜单——**语义正确,不是缺陷本体**。
|
||||
- `game/presentation/main.gd:211-212`(`_on_confirm_accepted()` 的 `"gate_new"` 分支)与 `game/presentation/main.gd:223-224`(`_on_confirm_secondary()` 的 `"gate"` 分支):两处都是先调 `camp_pres().on_camp_closed()`(把开始层显出来),紧接着调 `_start_adventure()`。
|
||||
- `game/presentation/main.gd:292-308`:`_start_adventure()` 全函数体(体止于 `:308` 的 `return`,`:311` 已是下一函数声明行)零处触碰 `start_overlay`——于是上面那句「显出来」没有对手句把它收回去。失败分支(`main.gd:306-307` `fatal_screen`)在 T1.1 落笔后开始层终态由「可见」变「隐藏」,该终态差在本卡第 5 节明示为可接受(致命屏自会接管,无菜单可用)。
|
||||
- 既有先例=每条启动路径在自己函数内部自管开始层:`game/presentation/main.gd:194`(`_start_adventure_from_menu()` 内先隐藏再转调)、`game/presentation/main.gd:246`(`_resume_from_menu()` 内隐藏)。所以现状的隐藏点是散在入口层的两处,缺的是开局那一条。
|
||||
- `_start_adventure()` 全库调用点仅三处(`main.gd:195`/`212`/`224`),无一需要开局后保留开始层。
|
||||
- 层序事实:`game/tests/system_test.gd:87-89` 断言「start_overlay 在冒险屏壳之上(盖住战斗内容)」——被盖住的正是冒险画面,故真机观感=「画面回主菜单」。
|
||||
- 覆盖缺口事实:`game/presentation/main.gd:210` 的 `"gate_new"` 与 `main.gd:223` 的次确认分支,现有断言只核 `run_scope.is_adventure` 与营地场景屏隐藏(`game/probe/ui_click_probe.gd:703-705`),**没有一条核开始层可见性**,故缺陷在绿批里静默存活。
|
||||
|
||||
### 3.2 件2——弹窗件位次写死
|
||||
|
||||
- `game/presentation/views/confirm_dialog.gd:31-39`:`_init()` 内三颗钮的 `btn_row.add_child()` 次序=取消 → 次 → 确认。
|
||||
- 信号按按钮对象路由、不按位次:`confirm_dialog.gd:32/35/38` 各自 `.pressed.connect(_emit_cancelled/_emit_secondary/_emit_confirmed)`——换序不改路由,文案也不用来认钮。
|
||||
- 四处消费方(全部经同一件):删档确认 `game/presentation/main_overlay_flow.gd:129`(钮标「删除」/「取消」)、大门有断点三出口 `main_overlay_flow.gd:79-80`(「继续上一段冒险」/「开始新冒险」/「取消」)、大门无断点 `main_overlay_flow.gd:83`(「是」/「否」)、篝火离场 `main_overlay_flow.gd:73`(「离开」/「留下」)。
|
||||
- 现有断言只核「次钮在两出口形态下隐藏」(`ui_click_probe.gd:696`),**无一条核位次**。
|
||||
|
||||
### 3.3 件3——顶栏件有装配无栏体
|
||||
|
||||
- `game/presentation/views/camp_hud_band.gd:10-17`:`_init()` 设 `PRESET_TOP_WIDE` + `custom_minimum_size=(0,48)` + `mouse_filter=MOUSE_FILTER_IGNORE`,体内只挂一枚徽章,**无任何底色件**——即整条顶栏是透明的。
|
||||
- 徽章现状:`camp_hud_band.gd:15-16` 设 `PRESET_CENTER_RIGHT` 后 `position=Vector2(-160, 8)`(注释自标「代拍板 3」)。
|
||||
- 徽章自高=48px:`game/presentation/views/ui_kit.gd:640` 的 `panel_flat(..., Vector4(14, 8, 14, 8))` 给图标 32px 上下各加 8px 内边距=48px,正等于栏高——**顶距 8px 在栏内放不下**,落座只能顶格。
|
||||
- 装配链在位:`game/presentation/main_ui_build.gd:120-121` 造件并调 `set_hud_band()` → `game/presentation/views/camp_scene_view.gd:88-91` 把件塞进 `_hud_slot` → `camp_scene_view.gd:188` 预留位 `custom_minimum_size=(0, layout.hud_band_h)`,取数自 `game/data/tables/camp_scene_layout.tres` 的 `hud_band_h=48`(schema 见 `game/data/schemas/camp_scene_layout_table.gd:7`)。
|
||||
- 盘上「新建 ColorRect+铺满宿主」的写法现有三处,意图各不相同,本批一处都不动:`game/presentation/ui_assembler.gd:224-230` `overlay_bg(host, color)`(亮底面板与模态压暗遮罩共用,外部调用者零、仅该文件自身 `:100`/`:214` 用,**没设 mouse_filter**);`game/presentation/views/ui_kit.gd:486-491` 私有 `_modal_dim()`(同一份东西的第二实现,同样不设 mouse_filter,仅 `:511` 经 `modal_shell()` 一处用、经该壳扇出 11 家模态消费件);`ui_kit.gd:537` 页壳底板(`page_shell()` 内,**显式**写 `MOUSE_FILTER_STOP`、色取 `BG_BRIGHT`/`BG_ROOT` 实色,其后紧跟石纹层与暗角层作兄弟节点)。三者与顶栏带只有形状撞车:前两者的变化原因是「遮罩挡不挡点击」,页壳底板的变化原因是「这块屏用哪档底色」,顶栏带的变化原因是「这条 HUD 带的定值」。另:顶栏件 `camp_hud_band.gd:14` 现役已在调 `UiKit.keystone_badge()`——视图件引公共件在本文件内就有先例,故出口落 `ui_kit`、不经装配器。用户 2026-11-06 终裁=**顶栏带的外观整件封进公共件一个具名出口,同形旧实现不收编**(早先两版——「值进公共件+矩形视图自建」与「四份同形收成带形参的通用出口」——都已作废,逐次理由见第 15 节 Q1 裁决史与第 12 节末段)。
|
||||
- 落位带实际背景:营地背景层 `game/assets/art/scene/scene_camp_bg.png` 的 y0–48 带均色实测 `#DEEAE7`(逐像素取样,画面最上的可点物件是大门,顶缘 y=250,故 48px 带整条落在远景天空上)。徽章烛光黄底 `#F6C667` 对该天空 1.29:1;定值 `#6E4526`×0.80 叠后呈色 `#84664D`,对天空 4.27:1、对徽章底 3.31:1。
|
||||
- 像素取样先例:`game/tests/unit/presentation/terrain_tile_sets_test.gd:107` 已用 `img.get_pixel()` 在单元套内取样贴图。
|
||||
- 无 golden 锚:`game/tests/golden/README.md`「未建锚面备案」明写营地屏无改前 golden 锚,该面等价证据=探针/单元断言+实机目检。
|
||||
|
||||
---
|
||||
|
||||
## 4. 设计契约(红线,违反=L3 停机呈报)
|
||||
|
||||
- 🔴 **契约1|冒险启动路径的隐藏点收一处**:**限「开局」与「续玩」两条路径**——隐藏责任只许落在 `_start_adventure()`(开局)与 `_resume_from_menu()`(续玩)两个函数内部。第 3.1 条的先例形态即本契约依据。**本契约不管其余开层自管点**:全库开始层显隐共 13 处(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`/`:101`),那些是选档层/生涯页/终局回菜单等各自开合面的自管句,本批一律不动、也不受本契约约束。禁再新增**开局或续玩路径**上的第三处隐藏点,禁把隐藏写回 gate 出口(调用方)。
|
||||
- 🔴 **契约2|`on_camp_closed()` 语义不动**:`camp_shop_presenter.gd:85` 那句显示开始层是本批禁改项——篝火离场与 Esc 返回主菜单两条动线靠它。改动=回退主菜单功能。
|
||||
- 🔴 **契约3|位次改动只动 `add_child` 次序**:`confirm_dialog.gd:31-39` 的三句连接(`.pressed.connect(...)`)、三个钮对象的创建与文案参数一律不动;四处消费方的 `open()`/`open_three()` 传参零改动。位次与语义的绑定由第 8 节 R4 行的坐标真点断言钉。
|
||||
- 🔴 **契约4|这块带子的全部外观封在公共件的同一个具名出口里**:`ui_kit.gd` 出一个公开出口(本卡定名 `camp_hud_band_surface(host)`,实现可按同文件命名习惯微调后缀形态,见白名单第 6 条),**色值与 α、铺满、不吃点击、挂上宿主、返回句柄**五件事全在它内部;**形参只有 host 一个**——禁把颜色与「吃不吃点击」开成形参(那等于把这块带子的定义推回调用方每次重述,也正是本契约要禁的拆两处)。色值取派生常量 `Color(CAMP_HEADER_BAND, 0.80)`(三分量继承在册 token,同文件先例 `TOAST_BG:189`/`TIP_BG:190`/`HP_BAR_TRACK:200` 注「收口单点——非色板新增」)。**同时禁收编无关同形件**:`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537` 三处原样不动,出口也不得被它们转调——形状撞车不等于同一件事,绑起来就得到一个同时要回答「页底用哪档色」「遮罩挡不挡点击」「HUD 带穿不穿点击」三个变化原因的函数,并把回归面扩到全站弹窗底(用户 2026-11-06 终裁,见第 15 节 Q1 裁决史④与第 12 节末段)。依据=`ui_kit.gd:2`「全站取色/描边唯一出口,换皮只改本文件」+ code-conventions §1.1 一个类一个职责、单一变化原因判据。色板快照不受新常量影响(`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 从不进快照)。
|
||||
- 🔴 **契约5|不吃点击由出口内部保证**:栏体必须 `mouse_filter = Control.MOUSE_FILTER_IGNORE`,写死在出口内、不靠调用方记得设(顶栏件自身 `camp_hud_band.gd:13` 同款设置是既有锚)。漏设=48px 带内点击被栏体吃掉,与 `camp.md:37` 禁区纪律叠加成「顶排物件点不动」;且 ColorRect 默认即 STOP,漏设不会报错只会静默失守(同文件 `:540` 显式 STOP、`:864` 与 `camp_scene_view.gd:145` 须显式 IGNORE 为旁证)。本条与契约4 合起来把这块带子的外观压到一处。
|
||||
- 🔴 **契约6|本批禁写文档面**:`design/` 整个目录、`待办清单.md`/`体验验证清单.md` 两份根流程清单、`game/tests/README.md` 一律禁改(能力两行已由写卡段落表,实现批只改名实相符);产出物只准落 `reports/`。
|
||||
- 🔴 **契约7|禁变异类测试与撤销类取证**(test-conventions §8.4,全项目红线):本批所有断言从真实入口进入、绑被测改动特有的可观测提交面,禁人为制造红、禁回退 hunk 取证。
|
||||
|
||||
---
|
||||
|
||||
## 5. 范围边界
|
||||
|
||||
**做**:
|
||||
1. `_start_adventure()` 入口收一个隐藏点,摘掉 `main.gd:194` 的冗余句;
|
||||
2. `ConfirmDialog` 三颗钮的 `add_child` 次序整体镜像;
|
||||
3. 公共件出一个具名出口封装这块带子的全部外观(色值与 α、铺满、不吃点击、挂上宿主、返回句柄)+ `CampHudBand` 改调该出口、徽章落座改右对齐容器;
|
||||
4. 上述三件各自的测试(对照表 8 行 R1–R8)+能力清单两行(写卡段已落)+回归面确认。
|
||||
|
||||
**不做(含去向)**:
|
||||
|
||||
| 不做的东西 | 为什么 | 去向 |
|
||||
|---|---|---|
|
||||
| 「破坏性确认与常规确认禁止同位」(art-bible §4.5 清单 #7 后半句/interaction-patterns §7)在弹窗层的闭合 | 该句系 2026-09-14 文档骨架初版提交随件进入,全库查无用户裁决记录,且 battle.md:335 指定的无障碍基线权威(HUD框架 §可访问性基线)不含它——它是不是用户定的规则,本身待裁 | 本卡不动它;现状四处同位属既有状态,不随本批产生或消失。要不要闭合,归用户另发令,另立待办条目 |
|
||||
| 顶栏栏内加第二件(存档位号/关卡语境等) | 用户 2026-11-05 裁「栏内只放钥石徽章一件」,左位不放件 | 无 |
|
||||
| 营地场景层坐标、素材、七物件摆放的重排 | 属场景重排批,不与本卡混装(用户 2026-11-05 口径:代码批与素材批分两张卡) | 归那一批自己的卡 |
|
||||
| 把盘上三处同形铺面写法(`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`)收敛成一个通用铺面器 | 形状同而变化原因不同(遮罩遮挡语义/页面底色档位/HUD 带定值);收编=把三个不相关变化原因绑进一个带形参的函数,回归面扩到全站弹窗底与两个全屏页底板,而这三个地方今天都没有待改的东西——为没到来的问题做设计(用户 2026-11-06 终裁,第 15 节 Q1 裁决史④) | 无。三处原样留底;日后若真出现意图相同的第二个消费者,届时按 code-conventions §1.1 重议 |
|
||||
| 营地屏 golden 建锚 | 无改前基线可留底,新建锚不属缺陷修 | 不登记(顶栏观感走目检) |
|
||||
| `main.gd:137` 的 boot 期 `start_overlay.visible = true` | 启动默认态,与三件无关 | 无 |
|
||||
|
||||
**回归面(本批不改、但跑批必须仍绿)**:七条未改动动线——大门有断点按「继续上一段冒险」/大门按「取消」「否」/篝火「离开」「留下」/Esc 浮窗三项/五功能窗开合/进入存档与生涯页返回的开始层显隐/终局回菜单。其中前三条已被 `ui_click_probe.gd:679-723` 段覆盖(段止于「开场行三路径段」头注之前),本批在该段内追加断言,不新建平行段。
|
||||
|
||||
---
|
||||
|
||||
## 6. 关联文档同步面
|
||||
|
||||
实现批禁写 `design/` 与根规则文件;下表逐条给出「要改哪一节、改成什么、依据是什么」,实现批把命中项列进 `reports/iter<53>-反哺点-<日期>.md`,由用户在会话中裁决后归文档批落文。
|
||||
|
||||
| 文档 | 关联点 | 同步义务 |
|
||||
|---|---|---|
|
||||
| `design/gdd/ux/camp.md` v2.8 §顶栏 | 本批把「栏体定值」变成实装;若实现期样张/目检意见要求改 α、改右位内缩或改栏高,现行句即落后 | 反哺点:只在**实际改值**时列,附实测读数;未改值不列 |
|
||||
| `design/art/art-bible.md` v3.43 §6.2 钥石余额行 | 同上(该行已把事实源指回 camp §顶栏,改值须两处同改) | 反哺点:与上一行同批列,禁单改一处 |
|
||||
| `design/gdd/systems/表现与支撑/HUD框架.md` §提示与弹层·其三 | 「四处共用同一件同一序」实装后须与代码一致;若实现发现某消费方不遵此序(例如自拼按钮行的弹窗件),句与码冲突 | 反哺点:发现即列,附 `文件:行` |
|
||||
| `design/engineering/architecture/architecture.md` 呈现层模块表 | 两处随本批失真:`:175` CampHudBand 行现写「顶栏 48px 全宽窄条:**钥石余额 badge 唯一呈现**」——加可见栏体后badge 不再是唯一呈现物;`:168` D1 HUD 行的视图单元接口若因本批改动而与「信号外发、setter 只读注入」不符亦须同步 | 反哺点:实现批禁写该文档;本批落地后把该行的实际形态(栏体色面+badge 右位)列进反哺点清单,附 `文件:行` 与实装读数 |
|
||||
| `体验验证清单.md` | 顶栏观感属「需真人试玩才能下结论」项,需一行观测项 | 反哺点:实现批禁写该清单,由文档批落;卡内先在实现报告目检总清单登记 |
|
||||
| `game/tests/README.md` 能力表 | 两行新增能力已由写卡段落表(第 8 节能力行列名即清单名) | 实现批禁写;批末只核「清单名=实际落地函数名」,名实不符=停卡退回写卡段 |
|
||||
| `roadmap.md` 迭代总表 | 本卡尚未编入迭代 | 非实现批义务,归 iter-plan 段 |
|
||||
| (非文档面·写卡期发现一)`tools/pipeline_gate.py:68` 判据出处行锚的机检式 `ANCHOR_RE` 字符类=`[A-Za-z0-9_-.\\/]`,不含中文段 | 本库现行设计文档多为中文名(`HUD框架.md`/`关卡节点地图.md`/`待办清单.md`),凡判据句落在这类文档即无法被机检认锚——本卡 R2/R3/R4/R8 因此同时挂一个 ASCII 路径锚(`camp.md`/`level-map.md`),中文出处改以「第 N 行」散文形写明 | **已登记**:待办清单「流水线闸认锚式不含中文名文档」条(工具代码/待执行),本批不动闸件 |
|
||||
| (非文档面·写卡期发现二)`game/tests/unit/contracts/source_guard_test.gd:85-99` 列目录不递归 | 「V1d 层序落位单口」的扫描面漏掉 `views/` 与 `scenes/` 两个子目录(注释仍写「presentation 无子目录」,实为两个);视图层的插位不在该闸视野内。当前无漏网真违规(`views/ui_kit.gd:841` 属卡片内部重排) | **已登记**:待办清单「层序落位单口检查的扫描面不含子目录」条(工具代码/待执行);两目录是否纳入扫描面本条不预裁,启动时再裁 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件结构图
|
||||
|
||||
| 文件 | 职责 | 新增/改动 | 行为改动 | 验收行 |
|
||||
|---|---|---|---|---|
|
||||
| `game/presentation/main.gd` | 组合根:冒险启动与确认分派编排 | 改动(`_start_adventure()` 加一句;`_start_adventure_from_menu()` 删一句) | 行为改动 | R1、R2 |
|
||||
| `game/presentation/views/confirm_dialog.gd` | 通用确认弹窗视图件 | 改动(三句 `add_child` 次序,**不动成员面**) | 行为改动 | R3、R4 |
|
||||
| `game/presentation/views/ui_kit.gd` | 呈现层共享工厂与全站取色单点 | 改动(新增带 α 派生常量 + **新增一个具名公开出口**,该出口承载这块带子的全部外观;既有三处同形铺面写法一律不动) | 行为改动(顶栏呈现源) | R5、R6 |
|
||||
| `game/presentation/views/camp_hud_band.gd` | 营地顶栏 HUD 件 | 改动(调该出口得栏体,不再自建 ColorRect;徽章落座改右对齐容器) | 行为改动 | R5、R6 |
|
||||
| `game/probe/ui_click_probe.gd` | 真点探针:大门两分支段所在宿主 | 改动(同段追加断言) | 非行为改动(测试面) | R1、R2、R4、R7、R8 |
|
||||
| `game/tests/system_test.gd` | 系统测试宿主 | 改动(新增 `_smoke_camp_hud_band()`,接进 `_split_tail()` 的 await 链〔`:40-44`〕) | 非行为改动(测试面) | R5、R8 |
|
||||
| `game/tests/unit/presentation/camp_scene_layout_test.gd` | 营地屏布局与顶栏禁区单元套 | 改动(新增 `_test_hud_band_contrast_over_bg()`) | 非行为改动(测试面) | R6 |
|
||||
| `game/tests/unit/presentation/confirm_dialog_test.gd` | 确认弹窗按钮位次与语义单元套 | **新增** | 非行为改动(测试面) | R3 |
|
||||
| `game/tests/README.md` | 能力清单 | 改动(两行,写卡段落) | 非行为改动 | — |
|
||||
|
||||
结构图与第 12 节「落位与可复用性预检」互指:改动件七个(四件表现层+三件测试面)、新增件仅一枚单元套;这块带子的全部外观收在公共件的同一个具名出口里,出口只服务这块带子(判据见第 15 节 Q1 裁决史④与契约4)。
|
||||
|
||||
---
|
||||
|
||||
## 8. 验证闸(改动—验收对照表)
|
||||
|
||||
| 行 | 需求点(判据出处行锚) | 改动(文件:行/行为) | 验收测试(名称+落点+尾缀) | 层级 | 真实入口 | 判据绑定面 | 能力行 |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| R1 | 件1 大门无断点按「是」后开始层不可见(`design/gdd/ux/camp.md:16`+`design/gdd/systems/冒险循环/关卡节点地图.md:78`) | `main.gd:211-212` 分支经 `main.gd:292` 新入口隐藏 | `ui_click_probe.gd:_ready`(大门无断点段追加「确认后开始层不可见」) | 探针 | 营地场景根大门命中区 → `ProbeUiUtil.push_click_at` 真点 → 弹窗「是」钮 `ProbeUiUtil.push_click(_confirm_btn)` 真点 | `main.ui.screens.start_overlay.visible` 取值(被测改动特有的可见性提交面) | 既有:营地大门两分支确认与断点续玩接线 |
|
||||
| R2 | 件1 开局落在节点图选路停点(`design/gdd/ux/level-map.md:69`;停点句唯一出处=`design/gdd/systems/冒险循环/关卡节点地图.md` 第 78 行「关卡开始 → 呈现节点图选路停点」) | 同 R1 | `ui_click_probe.gd:_ready`(同段追加「关卡画面在位」) | 探针 | 同 R1 真点链(大门命中区真点 →「是」钮真点) | `run_scope.is_adventure` + `main.ui.modals.run.level_screen.visible`(停点呈现面,非终态推断)。**本行是开局终态在位断言,被测改动的特有提交面由 R1 承载**——`is_adventure` 在既有探针已断(`ui_click_probe.gd:704`),把件1 的改动绑在它上不成立,故本行的效力=与 R1 同批缺一即红 | 既有:营地大门两分支确认与断点续玩接线 |
|
||||
| R3 | 件2 两出口与三出口形态的按钮位次=推进→次→维持(`design/gdd/ux/camp.md:16`;位次定值唯一出处=`design/gdd/systems/表现与支撑/HUD框架.md` 第 90 行「三条纪律·其三」) | `confirm_dialog.gd:31-39` 三句 `add_child` 次序镜像;**成员面与连接一律不动** | `confirm_dialog_test.gd:_test_two_button_row_order`;`confirm_dialog_test.gd:_test_three_button_row_order` | 单元 | 视图件对外入口 `ConfirmDialog.open(text, confirm_label, cancel_label)`/`open_three(text, confirm_label, secondary_label, cancel_label)`——即四消费方实际用的那两个公开方法(`main_overlay_flow.gd:73`/`:79`/`:83`/`:129`) | 测试自己传入三档互异文案(如「推进」「次推」「维持」),从弹窗节点向下按类型定位那个横排容器(HBoxContainer),断言其 `get_children()` 的**子节点顺序**:双钮形态 child(0) 文案=「推进」且 **child(2)** 文案=「维持」——次钮在 `open()` 里只是 `visible=false`、不从子节点表移除(`confirm_dialog.gd:47`),双钮形态下子节点仍为 3 个,按下标断必须取 child(2)(写 child(1) 必红);三钮形态 child(0)/child(1)/child(2)=「推进」「次推」「维持」。另配非空前置:按钮行子节点数=3(防扫描面缩水静默绿)。按类型定位可成立已核:该弹窗树内只有这一个 `HBoxContainer`(`UiKit.modal_shell()` 产 CenterContainer+PanelContainer+VBox,不产横排——`ui_kit.gd:509-525`;`confirm_dialog.gd:26` 那条 HSeparator 与 `UiKit.button()` 的产件都不是 HBoxContainer)。判据面=树的公开子节点序(同 `system_test.gd:81-89` 断言层序的先例形态),**不读任何私有成员**,避开 `architecture.md:168` 视图单元接口与 `:323` 禁直引实现内部 | 新增:确认弹窗按钮位次与语义同钉 |
|
||||
| R4 | 件2 左位那颗真点下去就是推进(`design/gdd/ux/camp.md:16`;位次定值出处同 R3) | 同 R3 | `ui_click_probe.gd:_ready`(大门无断点段内,`push_click_at` 打**推进钮自身**全局矩形中心) | 探针 | 弹窗开态下按类型定位按钮行 `HBoxContainer`,取其 child(0) 的 `get_global_rect()` 中心真点——**禁按「行矩形左位」算坐标**:该行 `alignment=CENTER`(`confirm_dialog.gd:29`),双钮可见宽 216+12+216=444<壳宽 460(`ui_kit.gd:578` 钮默认 `min_w=216.0`/`confirm_dialog.gd:20` 壳 `Vector2(460,0)`),左右各留 8px 空档,打行矩形左位会落空 | 主证=`run_scope.is_adventure` 翻转+`_confirm_consumer` 清零;真点前置读 `main._confirm_consumer == "gate_new"`(该私有的直读属**探针既有先例**,`ui_click_probe.gd:272`/`:286` 现役同款,封装扫描件只覆盖 flow 件的 `root._`——见 `presentation_encapsulation_sweep_test.gd:80-94`,故本行不新开辟读面)——位次若错接,坐标命中的将是「否」,主证面即红 | 新增:确认弹窗按钮位次与语义同钉 |
|
||||
| R5 | 件3 顶栏栏体几何与徽章落座(`design/gdd/ux/camp.md:41`) | `camp_hud_band.gd:10-17` 改为调公共件出口得栏体+徽章落座改右对齐容器;`ui_kit.gd` 加带 α 常量与公开出口 | `system_test.gd:_smoke_camp_hud_band` | 集成 | 真实工程 boot 后经装配链 `main_ui_build.gd:120-121` → `camp_scene_view.gd:88-91` 落座;**须先开营地**(营地屏 boot 态为隐,`camp_scene_view.gd:53`),走 `main._camp_pres.open_camp()` 探针既有用法并 await 排版帧,再读 `main.ui.modals.camp.scene.hud_band`(`camp_scene_view.gd:49` 的公开句柄) | ①栏体色 `color.to_html(true) == "6e4526cc"`——即规格「`#6E4526` × α0.80」的字面编码;**禁写 `.a == 0.80`**(实测色道单精度使该比较为 false,见第 2 节实测句);②栏体铺满:出口产出的那块色面 `get_global_rect()` 宽=1280、高=48——**宽度不是 tscn 锚点给的**(营地场景根由屏壳代码 new——`game/presentation/scenes/camp_screen.gd:13` `CampSceneView.new()` + `:14` `add_child`,该根自身无锚无尺寸;承重实为 `ui_registry.gd:26`/`:61` 打的 FULL_RECT 注册,屏壳 `camp_screen.tscn` 根锚 preset=15 满铺),故实现首步先按 T3.0 同款时机量一次顶栏件现尺寸,再以此定断言值;③`mouse_filter == MOUSE_FILTER_IGNORE`;④栏体是顶栏件的**第一个子节点**(子节点序=绘制序,色面必须先入才不盖徽章);⑤徽章 `get_global_rect().position.y == 0` 且 `get_global_rect().end.x == 顶栏 get_global_rect().end.x − 12`(x 向缺判据则位次改错仍绿,故与顶格同批配齐;两项都用全局矩形,不与局部 `position` 混写)。前置量测=T3.0 读 `get_combined_minimum_size().y` 须为 48(描边若计入得 50 → 触第 10 节 L3 第 5 条) | 新增:营地顶栏栏体定值与落座 |
|
||||
| R6 | 件3 栏体对落位带底色对比 ≥3:1(`design/gdd/ux/camp.md:41`+`design/art/art-bible.md:452`) | 同 R5 | `camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg` | 单元 | 单元从 `camp_scene_layout_table.gd` 的 `hud_band_h` 出口取带高,读现行背景贴图 `res://assets/art/scene/scene_camp_bg.png` 该带像素(`load(...)` 取纹理再 `get_image()`+`get_pixel`,先例=`terrain_tile_sets_test.gd:105-107` 的全像素扫描) | 规格侧算式核验(与 R5 分工:R5 核实装等值,本行核规格值本身达标)=测试内按 `camp.md:41` 写死 `#6E4526` 与 α0.80,与该带实测均色 alpha 合成后算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 test-conventions §8.4(正面效力自检)配齐。**禁把视图件的 color 直接当预期值喂进算式**(那是 oracle 镜像,test-conventions §8.7 判红) | 新增:营地顶栏栏体定值与落座 |
|
||||
| R7 | 回归·件1 未动的三条动线(`design/gdd/ux/camp.md:16`/`:22`) | 无改动(`main.gd:208`/`214`、`main_esc_flow.gd:46`) | `ui_click_probe.gd:_ready`(既有大门两分支段+篝火离场段现有断言,本批零改写) | 探针 | 既有真点链 | 既有绑定面原样:`is_adventure` 不翻转、营地场景根 `visible`、`_camp_data()["balance"]` 不变 | 既有:营地大门两分支确认与断点续玩接线 |
|
||||
| R8 | 回归·件2 未触的路由与件数断言(`design/gdd/ux/camp.md:16`;位次与件数定值出处同 R3) | 无改动 | `ui_click_probe.gd:_ready`(次钮隐藏断言 `:694`)+ `system_test.gd:_smoke_boot_chain`(开始层三钮计数 `:195`) | 探针/集成 | 既有真点与 boot 链 | 既有绑定面:`_secondary_btn.visible` 为假(两出口形态)、开始层可见钮数 == 3 | 既有:营地大门两分支确认与断点续玩接线 |
|
||||
|
||||
闸口径:本批改动含 `game/` 下非 `.md` 的行为载体,合回后远端 CI 走代码域全档;开发期按改完即测铁规以 `tools/run_all_gates.sh --code` 为选档入口(域划分唯一出处在脚本内,卡内不列闸件名单)。里程碑步出口跑一次所选档全套;波内修复只做出红件的最小单元最窄确认;新增测试先行孤立验证再并入全套。
|
||||
|
||||
**测试削弱申报**:允许改动 0 条既有断言。`ui_click_probe.gd:694`(次钮隐藏)与 `system_test.gd:195`(三钮计数)必须原样通过;本批位次改动若使二者失守=契约3 被违,停卡呈报,禁改判据求绿。**唯一允许的判据形态约束**:涉及色值与透明度的断言一律走 `to_html(true)` 或整体等值比较,禁浮点分量直等(第 2 节实测依据)。
|
||||
|
||||
**独立验收审计**:入口=`reports/iter<53>-验收审计-<日期>.md`;由未参与实现的只读会话或子代理执行,未过不得合回(test-conventions §8.7+iter 技能第 5 步)。验收档=中 ⇒ 审计深度按 §8.7 中档梯度。
|
||||
|
||||
---
|
||||
|
||||
## 9. 任务卡
|
||||
|
||||
### T1|开局路径的隐藏点收一处(≈0.25 工作日)
|
||||
|
||||
取用:`main.gd:292 func _start_adventure() -> void`(既有签名,零参数零返回)。
|
||||
|
||||
- [ ] T1.1 实装:`game/presentation/main.gd:292` `_start_adventure()` 函数体首行加 `ui.screens.start_overlay.visible = false`(位置在该函数现有第一句 `ui.flow.close(...)` 之前)。
|
||||
- [ ] T1.2 实装:删除 `game/presentation/main.gd:194` 那句 `ui.screens.start_overlay.visible = false`(转调 T1.1 后它已无职责)。
|
||||
- [ ] T1.3 验证:`python tools/run_system_test.py` 全绿——核 `_smoke_boot_chain` 与 `_smoke_run_lifecycle` 的既有开始层断言(`system_test.gd:170-171`/`:253`/`:261`)未失守。
|
||||
- [ ] T1.4 验收:`ui_click_probe.gd` 大门无断点段(`:684-705` 区间内)追加两条断言并实跑——R1(确认后 `main.ui.screens.start_overlay.visible` 为假)、R2(`main.ui.modals.run.level_screen.visible` 为真,与既有 `is_adventure` 断言同批)。**帧口径**:段内既有 await 只 1 帧(`:703-704`),选路停点成形的先例是 3 帧(`screen_flow_probe.gd:185-193`,该段在 `await _frames(3)` 后断言「入口停点未落在关卡地图屏(level_screen 不可见)」——但它走 `_start_adventure_from_menu()` 函数直调,那条路径自带隐藏,所以从未抓到本缺陷)——追加断言前 await 到 3 帧,并把「停点成形所需最小帧数」实测值记进实现报告。落点=既有段内,不新建段。
|
||||
- [ ] T1.5 验证:`python tools/run_all_gates.sh --quick` 对该件做最窄确认(探针闸段),先红后绿记进实现报告。
|
||||
|
||||
产出(T2/T3 无关,供回归引用):`_start_adventure()` 成为开始层隐藏的唯一开局出口。
|
||||
|
||||
### T2|确认弹窗按钮位次镜像(≈0.5 工作日)
|
||||
|
||||
取用:`confirm_dialog.gd:43 func open(text: String, confirm_label: String, cancel_label: String = "取消") -> void`;`confirm_dialog.gd:55 func open_three(text: String, confirm_label: String, secondary_label: String, cancel_label: String) -> void`。三颗钮的成员声明(`:14-16`)与连接属本卡禁动面,不取用。
|
||||
|
||||
- [ ] T2.1 实装:`game/presentation/views/confirm_dialog.gd:31-39` 三颗钮的 `add_child()` 次序整体镜像为 确认 → 次 → 取消。对象创建、`.pressed.connect(...)` 三句连接、成员声明(`:14-16`)、`open()`/`open_three()` 签名与文案参数一律不动(契约3)——**本卡不给测试开任何新成员或新公开出口**,位次判据走公开子节点序(依 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部)。
|
||||
- [ ] T2.2 验收(新增件):新建 `game/tests/unit/presentation/confirm_dialog_test.gd`,套件入口=`func _initialize() -> void`(视图件套形态先例=`camp_scene_layout_test.gd:12-13` 的 `call_deferred` 串法,**不是** `_init`),体内 `call_deferred` 两个私有函数——`_test_two_button_row_order()`:自造实例后调 `open("t", "推进", "维持")`,从该实例向下按类型定位那个 `HBoxContainer`,断言其 `get_children()` 的 child(0).text 为「推进」、**child(2).text** 为「维持」,且子节点数=3(次钮只置 `visible=false` 不移除——`confirm_dialog.gd:47`,双钮形态下维持钮在 child(2) 不是 child(1));`_test_three_button_row_order()`:调 `open_three("t", "推进", "次推", "维持")`,断言 child(0)/child(1)/child(2) 文案依次=「推进」「次推」「维持」。全程只读公开树面与 Button 的公开 `text`,不触任何私有成员。入册=在 `game/tests/run_all.gd:166` 起的硬编码清单里追加一行 `{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}` 并行尾注一句本套件职责(清单条目形态照同目录既有行)。
|
||||
- [ ] T2.3 验收:`ui_click_probe.gd` 大门无断点段内追加 R4。**前置重开**:`:702` 那一次真点已把 `gate_new` 消费掉(`main.gd:215` 置空),故本步须先 `main._camp_pres.open_camp()` → `ProbeUiUtil.push_click_at(gate 命中区中心)` 重出弹窗,再取按钮行左位——坐标=自弹窗节点向下按类型定位到的 `HBoxContainer` 的 child(0) 全局矩形中心,经 `ProbeUiUtil.push_click_at` 真点;断言真点前 `_confirm_consumer == "gate_new"`、真点后 `run_scope.is_adventure` 为真且 `_confirm_consumer` 清零。
|
||||
- [ ] T2.4 验证:孤立先跑 T2.2 新套(`… --script res://tests/run_all.gd` 单套件口径),绿后并入全套;四处消费方动线由 `ui_click_probe.gd` 既有段与 `run_all` 单元套确认未失守(R8)。
|
||||
|
||||
产出:位次成为规格钉死的呈现定值,推进钮恒为该按钮行的 child(0)。
|
||||
|
||||
### T3|营地顶栏可见栏体(整件外观封在公共件一个具名出口,≈0.75 工作日)
|
||||
|
||||
取用:`UiKit.CAMP_HEADER_BAND`(`ui_kit.gd:195`,色值单点);`UiKit.keystone_badge(text: String) -> KeystoneBadge`(`ui_kit.gd:655`);`CampHudBand._init()`(`camp_hud_band.gd:10`,该件 `:14` 现役已在调 `UiKit`)。**盘上三处同形铺面写法不在取用面**(`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`)——本批既不让顶栏件复用它们,也不把它们收编进出口,理由见第 3.3 条、契约4 与第 12 节末段。
|
||||
|
||||
- [ ] T3.0 前置读数:**入树之后**再量——`KeystoneBadge` 是 PanelContainer(`ui_kit.gd:634-656`),最小尺寸由容器排版产出,刚 `new()` 未入树时读数不可信。落点=在系统测试宿主里经 `main.ui.modals.camp.scene.hud_band` 链取**已在树的现成徽章实例**,先开营地并 `await` 排版帧(帧数口径同 T1.4),再读 `get_combined_minimum_size().y`(`ui_kit.gd:640` 的 `panel_flat(...Vector4(14,8,14,8))` 加在 32px 图标上下各 8=48,两个未证实项都能把它推离 48:①`border_w=1` 是否计入最小尺寸;②徽章内 Label 用 18px 档(`ui_kit.gd:648` 取 `FS_SUB`,常量见 `:438`),字体行高若大于 32 则最小高由文字而非图标决定——**这两条都是推导,不是实测量**)。读数=48 → 按 T3.3 顶格落座;读数≠48(如 50)→ 停步走第 10 节 L3 第 5 条呈报,禁自行改栏高或改徽章内边距。读数与取数时机一并写进实现报告。
|
||||
- [ ] T3.1 实装·出口本体(Q1 终裁·11-06 用户裁「整件外观封在公共件一个具名出口,同形旧实现不收编」):`game/presentation/views/ui_kit.gd` 色板段加 `const CAMP_HUD_BAND := Color(CAMP_HEADER_BAND, 0.80)`(带 α 先例=同文件 `:189`/`:190`,收口非新增语义色的先例=`:200`);工厂段加公开出口 **`static func camp_hud_band_surface(host: Control) -> ColorRect`**,体内依次=新建 ColorRect + `rect.color = CAMP_HUD_BAND` + `rect.set_anchors_preset(Control.PRESET_FULL_RECT)` + `rect.mouse_filter = Control.MOUSE_FILTER_IGNORE` + `host.add_child(rect)` + `return rect`。**形参只有 host**:色、α、铺法、点击语义全写死在体内——这就是「一个物件的外观不得拆两处」的确切实现形(对照两个作废形态:值在公共件+铺法在视图=拆两处;出口开 color/pass_input 形参=把物件定义推回调用方,并为无关同形件预留通用性)。出口名钉死在这块带子上,不作通用铺面器。
|
||||
- [ ] T3.2 实装·顶栏件:`camp_hud_band.gd` 的 `_init()` 内在挂徽章之前调 `UiKit.camp_hud_band_surface(self)`(契约5 的 IGNORE 由出口内部保证,调用方没有可以忘记的形参);子节点序即绘制序,栏体先入、徽章后入。本文件内不再出现 `ColorRect.new()`。
|
||||
- [ ] T3.3 实装·徽章落座(**Q2 已裁乙·2026-11-05 用户裁**):`camp_hud_band.gd` 内加一层右对齐容器承载徽章——`HBoxContainer` 铺满栏体(`PRESET_FULL_RECT` 后置 `offset_right = -12.0`)、`alignment = BoxContainer.ALIGNMENT_END`、**`mouse_filter = Control.MOUSE_FILTER_IGNORE`(容器自身默认 STOP,与出口内那处 IGNORE 是两个点、必须同批落,漏一处顶排物件就点不动)**;替换 `camp_hud_band.gd:15-16` 现值(`PRESET_CENTER_RIGHT` + `position=(-160, 8)`),位数增宽自然向左展。容器摆位机制先例=`confirm_dialog.gd:27-29`(同件同机制,那里 alignment 取 CENTER、这里取 END);内缩 12px 见白名单第 3 条。
|
||||
- [ ] T3.4 验收:`game/tests/system_test.gd` 新增 `_smoke_camp_hud_band()`,**接进 `_split_tail()` 的 await 链**(`:40-44` 那一串 `await _smoke_*()` 之后追加 `await _smoke_camp_hud_band()`),**不是接进 `_initialize()`**——`_initialize()` 只调一句 `_split_tail()`(`:26-28`),而摘要打印与 `quit()` 都在该尾段末尾(`:47-48`),协程段不被 await 就会在首个 await 处被 quit 截断=静默不执行=假绿(该教训已写在 `:40` 行尾注里)。段内自持 `load("res://presentation/main.tscn").instantiate()`+`root.add_child`+await 帧、末了 `queue_free()`,形态照 `_smoke_run_lifecycle()`(`:213-217`/`:206-207`)。实现 R5 的五项判据(色值走 `to_html(true) == "6e4526cc"` 或整体等值比较,**禁浮点分量直等**;`get_global_rect()` 的尺寸=顶栏件实测宽 × 48;`mouse_filter == MOUSE_FILTER_IGNORE`;栏体是顶栏件的第一个子节点;徽章全局矩形 `position.y == 0` 且 `end.x == 顶栏 end.x − 12`)。**出口须在 `_init()`(入树前)被调**:`set_anchors_preset(PRESET_FULL_RECT)` 是把四边 offset 归零的入树前动作,与 `_modal_dim()`/`overlay_bg()` 的现役形态一致。
|
||||
- [ ] T3.5 验收:`game/tests/unit/presentation/camp_scene_layout_test.gd` 新增 `_test_hud_band_contrast_over_bg()` 实现 R6:读 `hud_band_h` 与背景贴图该带**全像素**均色(取样法照 `terrain_tile_sets_test.gd:105-107` 的双层全扫,非步长抽样),规格值 `#6E4526`/α0.80 按 `camp.md:41` 在测试内写死后合成算 WCAG 对比,断言 ≥3.0;扫描面非空前置照 §8.4 配齐。
|
||||
- [ ] T3.6 验证:两条新测先行孤立跑绿再并入 `--code` 全套。本批不触任何既有遮罩/亮底/页壳件,故无等值回归义务——回归证据=第 5 节七条未改动动线与全站弹窗现有断言在 `--code` 全档中仍绿。营地屏截图面由渲染闸的七屏段随批产出,作 R5/R6 对照取证。
|
||||
- [ ] T3.7 目检登记:营地顶栏观感(「成一条可辨窄带、徽章落在带内」)登记一行进实现报告实机目检总清单,登记即完成义务。
|
||||
|
||||
产出:`CAMP_HUD_BAND` + `camp_hud_band_surface()` 是这块带子外观的唯一出处(同文件相邻两段);`CampHudBand` 的外观=该出口+右对齐容器+徽章三件,视图内无一处自建 ColorRect;盘上三处同形写法原样未动。
|
||||
|
||||
### T4|产出物落位卡(≈0.5 工作日,末卡)
|
||||
|
||||
- [ ] T4.1 代码域全档全绿后、合并前写 `reports/iter<53>-实现报告-<日期>.md`:各卡状态+实机目检总清单(含 T3.7)+遗留读数义务+实现决定记录(超白名单的微决策逐条登记)。
|
||||
- [ ] T4.2 写 `reports/iter<53>-反哺点-<日期>.md`:第 6 节每条命中项都要有落点(哪一节、改成什么、依据什么);无命中也要写「本批零反哺点」并附核过哪几处。
|
||||
- [ ] T4.3 核对 `game/tests/README.md` 两行新增能力名与实际落地函数名逐字一致(名实不符=停卡退回写卡段)。
|
||||
- [ ] T4.4 呈报合回清单,等用户发令再走收口(冻结→件核→并 master→重塑→删区)。
|
||||
|
||||
**估时合计 ≈2.0 个工作日**(T1 0.25+T2 0.5+T3 0.75+T4 0.5)。T3 的 0.75=出口一枚(十行内)+顶栏件改两处(调出口+落座容器)+两条新测+孤立验证。无收编、无等值回归、无两次全档义务。
|
||||
|
||||
---
|
||||
|
||||
## 10. 异常分级
|
||||
|
||||
- **L1 自救**:探针坐标算错导致 R4 打空——重算按钮行全局矩形中心,禁改为信号直发(段内既有 `ui_click_probe.gd:696` 的 `cancelled.emit()` 是取消路径的信号注入先例,注释自陈「真实信号——cancelled 钮同源」;本批 R4 要钉的正是**位次**,直发不经位次,等于没测——依据=test-conventions §8.3 真实入口收窄条,契约7 同源)。
|
||||
- **L2 记录继续**:R6 均色取样读数与卡内实测 `#DEEAE7` 有 ≤3/255 的取样步长差——按实测值断言、卡内读数差写进实现报告,不改阈值。
|
||||
- **L3 停机呈报(写报告等用户)**:
|
||||
1. boot 后 `main.ui.modals.camp.scene.hud_band` 取不到实例(装配链与第 3.3 条现状不符)=卡片现状事实失效,停卡退写卡段;
|
||||
2. R4 坐标真点无法区分左位钮与相邻钮(按钮行 `alignment=CENTER` 下实际矩形与规格不符)=位次与语义绑不上观测面,触 test-conventions §8.4「判据绑不上=停卡呈报」出口;
|
||||
3. 位次改动使 `system_test.gd:195` 或 `ui_click_probe.gd:694`(次钮隐藏断言)失守=契约3 被违,禁改判据求绿;
|
||||
4. 机检预算互斥限**本批触碰的 presentation 面文件**:`tools/gdstyle_gate.py:45` 的扫描面=`game/core`/`game/data`/`game/presentation`,脚本头注(`:8`)明写排除 probe/sim/tests——故新增单元套**不在** S13 射程,该条只对 `main.gd`/`confirm_dialog.gd`/`camp_hud_band.gd` 生效。若这三个件的 S13 键与第 13 节预算数字冲突且无豁免通道=上游缺陷,停卡退回写卡段,禁自造第三条路(本技能第 13/14 节)。
|
||||
5. T3.0 量得徽章 `get_combined_minimum_size().y` ≠ 48(描边宽 1px 计入、或 18px 文字行高高于 32px 图标使最小高由文字决定,两条都会推得 >48)=「顶格落座」在 48px 栏内放不下,第 2 节与 `camp.md:41` 的推导句失效,停步呈报,禁自行改栏高、改徽章内边距或放宽 R5 判据。
|
||||
|
||||
---
|
||||
|
||||
## 11. 代拍板白名单(超清单必进实现报告「实现决定记录」并呈报)
|
||||
|
||||
1. 断言消息尾缀文案(照 `〔营地图批 V3〕` 形态自拟)。
|
||||
2. 新单元套件的套件注册名与 `run_all.gd` 册内落点序。
|
||||
3. **徽章水平内缩 12px**:`camp.md` v2.8 只钉「右位顶格」未钉内缩值,取 UiKit 钮间距同档 12(先例=`confirm_dialog.gd:28` `separation=12`)。
|
||||
4. R6 取样密度——照 `terrain_tile_sets_test.gd:105-107` 的**双层全像素扫描**(该件不是步长抽样,卡内早先写的「每 4 行×每 8 列」口径作废)。
|
||||
5. `_smoke_camp_hud_band()` 在 `_split_tail()` await 链内的插入位(不改变既有段序;接进 `_initialize()` 不成立——见 T3.4)。
|
||||
6. 出口与常量的命名(卡内定名 `UiKit.camp_hud_band_surface()` 与 `CAMP_HUD_BAND`)——可按同文件命名习惯微调后缀形态;**若实现把它改成带 color/pass_input 形参的通用铺面器、改成「值与形分两处」、或另起第二家公共出口,一律触契约4,属 L3 呈报**。
|
||||
|
||||
不在白名单:α 值、栏底色、栏高、位次序、隐藏点数量与位置——这些是规格钉死项,改动一律 L3 呈报。
|
||||
|
||||
---
|
||||
|
||||
## 12. 落位与可复用性预检(与第 7 节结构图互指)
|
||||
|
||||
| 新增/改动件 | 复用面(盘上已有什么) | 唯一面 | 分层与交互面 |
|
||||
|---|---|---|---|
|
||||
| 顶栏带外观出口(T3.1/T3.2) | **现成件已复用**:徽章走 `UiKit.keystone_badge()`(`ui_kit.gd:655`,`camp_hud_band.gd:14` 是现役调用方),色值走在册 `CAMP_HEADER_BAND`(`:195`)。盘上另有三处「新建 ColorRect+铺满宿主」的同形写法(`ui_assembler.gd:224-230` 外部调用者零、`ui_kit.gd:486-491` 私有且仅 `:511` 一处用、`ui_kit.gd:537` 页壳底板)——**本批三者都不复用也不收编**:它们的变化原因是遮罩遮挡语义与页面底色档位,与本带的 HUD 定值无关。出口落 `ui_kit`、不经装配器,因顶栏件已在引用 `UiKit`,而 `views/` 引装配器零先例。早先两版判据(「两份都不复用、按 views 现行形态自建」与「四份同形收成带形参的通用出口」)都已作废,逐次理由见第 15 节 Q1 裁决史 | 这块带子的外观全仓一处定义=`CAMP_HUD_BAND`+`camp_hud_band_surface()`(同文件相邻两段);禁在别处出现第二组「同一色+同一 α」;出口内 `MOUSE_FILTER_IGNORE` 写死——旁证:同文件 `:540` 页壳底板显式写 STOP、`:864` 与 `camp_scene_view.gd:145` 须显式写 IGNORE,可见 ColorRect 默认即 STOP,漏设不报错只会静默吃掉顶排点击 | 呈现层内一个纯构建出口,零状态、零信号、不持引用;顶栏读数仍经 `camp_data()` 投影单口(`camp_hud_band.gd:26-27`);`ui_assembler.gd` 与 `modal_shell()`/`page_shell()` 一律不动 |
|
||||
| 开始层隐藏(T1.1) | **复用** 既有语义句本身——`main.gd:194`/`:246` 就是同一条责任,本卡把它收进被调方 | 开局路径隐藏点唯一(`_start_adventure()`),续玩路径唯一(`_resume_from_menu()`);其余 10 处开层自管点不在本契约射程(契约1 已收窄) | 组合根内;不触 Core,不新增信号 |
|
||||
| 按钮位次(T2.1) | 无新增件——只镜像既有三句的次序。**本卡不新增成员、不新增公开出口**:位次判据走公开子节点序(R3),语义判据走坐标真点(R4) | 位次定值只写在视图件 `_init()` 一处,四处消费方零副本 | 呈现层;信号对象路由不动;依 `architecture.md:168`「视图单元接口=信号外发+setter 只读注入」与 `:323`「禁直引呈现层实现内部」,不为测试开成员面(该写法曾在本卡出现过,已撤回,登记在第 16 节轮次 2) |
|
||||
| 徽章落座(T3.3,Q2 已裁乙) | 已有做同样事的东西:`HBoxContainer` + `alignment` 摆位机制(`confirm_dialog.gd:27-29`——该件 `alignment` 取的是 `ALIGNMENT_CENTER`,**是同一机制的先例、不是右对齐的先例**;本件同处改取 `ALIGNMENT_END` 即右对齐)——**Q2 已裁乙(2026-11-05 用户裁)**,采容器右对齐形态;另两案(锚点生长、`size_updated` 回调)作淘汰留档见第 15 节 | 内缩值 12px 只在落座一处出现一次,禁在断言里另写第二套推导 | 呈现层内 |
|
||||
| `confirm_dialog_test.gd`(T2.2 新增) | 盘上无 ConfirmDialog 专属单元套(grep 命中面=`system_test.gd`/探针的宿主级引用与 `README.md:159` 能力行);同目录 `camp_scene_layout_test.gd:12-13` 是可参照的套件形态(入口 `_initialize()`+`call_deferred`),不是可复用的承载 | 位次判据只此一处;R4 探针判据与之互补(一个核序、一个核语义) | 测试层;经 `ConfirmDialog.open()/open_three()` 公开入口进出,断言只读公开子节点序与 Button 的公开 `text`——「读私有成员」的写法曾在 R3 出现,已按 `architecture.md:323` 撤回 |
|
||||
| `system_test.gd:_smoke_camp_hud_band`(T3.4)/`camp_scene_layout_test.gd:_test_hud_band_contrast_over_bg`(T3.5) | **扩展**既有套件,不新建宿主;取样法复用 `terrain_tile_sets_test.gd:105-107` 的双层全扫+`get_pixel` 路径 | 几何判据落系统测试、对比判据落布局单元套——两处职责已由套件名界定,禁第三处重复断言 | 测试层 |
|
||||
|
||||
**为什么出口只服务这块带子(2026-11-06 用户终裁;本段取代本卡此前两份互相矛盾的留痕句——「接受第四处重复故不抽工厂」与「四份同形收成带形参的通用出口」)**:用户裁的是两件事的合成。①**一个物件的外观不得拆两处**——色值、α、铺法、点击、挂载这五件事必须在同一个出口内定义,不许「常量在公共件、矩形在视图」;那等于要求每个改它的人同时记得两处,漏一处就得到一个自相矛盾的物件。本会话确有改漏实例:同一组色值要在两份文档里成对改,卡内同步面还写了「禁单改一处」的防呆,却在代码面上造出另一个新的成对结构。②**抽不抽公共器不由形状撞车决定**——不得为了「全仓只剩一处 ColorRect 铺满」这种读数好看,把遮罩、页底、HUD 带三个变化原因绑进一个带形参的通用器;那既不内聚(一个函数回答三个不相关的问题)也不低耦合(改顶栏带要回归全站弹窗底与两个全屏页),而这三处今天都没有待改的东西。**早先那份「三次法则、意图未第三次出现故不抽工厂」的留痕句,其动机经用户点破是「少动文件」而非纪律,作废**;但据此反弹出的「整案收编+形参化点击语义」同样作废。代价面(`ui_kit` 公开方法 23→24 须修订登记基线)见第 13 节例外申报;回归面因不收编而留在本件射程内,估时相应回落。
|
||||
|
||||
---
|
||||
|
||||
## 13. 实现规范预算(与第 7 节结构图互指)
|
||||
|
||||
**新件三查**——本批新增文件仅 `game/tests/unit/presentation/confirm_dialog_test.gd`:
|
||||
|
||||
- **三查对本件不适用**:`tools/gdstyle_gate.py:45` 扫描面=`game/core`/`game/data`/`game/presentation`,脚本头注 `:8` 明写「probe/sim/tests 测量面」排除——测试面文件不进 S13 读数,无键可对账。
|
||||
- 尺寸预期(照套件形态先例自持,不作机检项):套件入口 1 个(`func _initialize() -> void`,**非** `_init`——先例=`camp_scene_layout_test.gd:12-13`)+私有 `_test_*` 2 个+类变量 0–1。
|
||||
- 迁移函数对账:无迁入(该件全新增,`tools/gdstyle_baseline.txt` 无历史键,跑批后若出超默认新键须申报)
|
||||
|
||||
**存量触面一查**——S13 键较登记值只降不升:
|
||||
|
||||
| 文件 | 基线登记(`tools/gdstyle_baseline.txt`) | 本批预期 |
|
||||
|---|---|---|
|
||||
| `game/presentation/main.gd` | 第 11 行 `quality/max-public-methods = 32` | 不动公开方法面(函数体内加一句+另一函数删一句)→ 读数 32 持平 ✓ |
|
||||
| `game/presentation/views/ui_kit.gd` | 第 12 行 `quality/max-public-methods = 23` | 本批**加一枚 `const` + 加一个公开出口方法** → 读数预期 23→**24**,超登记值 → 见下方「指标例外申报」。色板快照面不受常量影响(`tools/gen_palette_baseline.py:42` 只正则 `Color8(数字,数字,数字)`,带 α 的 `Color(...)` 从不进快照,同文件 `TOAST_BG`/`TIP_BG` 即此形态);但**方法数是真升**,不申报就是假绿 |
|
||||
| `game/presentation/views/confirm_dialog.gd` | 无登记键(在默认阈值内) | 仅语句重排,分支与返回数不变 ✓ |
|
||||
| `game/presentation/views/camp_hud_band.gd` | 无登记键 | `_init()` 换成调出口+加一层容器,分支数不变 ✓ |
|
||||
| `game/probe/ui_click_probe.gd` | 无登记键(探针大文件另有基线口径) | 同段追加断言,函数不拆;若单函数分支数超默认,按 L3 第 4 条呈报而非自拆 |
|
||||
| `game/tests/system_test.gd` | 无登记键 | 新增一个私有函数,公开方法面不变 ✓ |
|
||||
|
||||
**指标例外申报(本批一项)**:`game/presentation/views/ui_kit.gd|quality/max-public-methods` 由登记值 23 升至 24,起因=用户 2026-11-06 裁「一个物件的外观不得拆两处」,要求在公共件出一个公开出口承载这块带子的整件外观。处置=实现批跑出该新键后,把 `tools/gdstyle_baseline.txt` 该行改为 24 并在提交信息里申报(基线手动修订须经用户批准——本条即申报与请求)。除此之外本批不申请任何超默认项。
|
||||
|
||||
---
|
||||
|
||||
## 14. 拆分边界纪律
|
||||
|
||||
本批新增的可复用单元只有公共件里那一个出口函数,故本节条款的适用面=该出口,判据权威=code-conventions §1.1 拆分判据,此处不复制定文:
|
||||
|
||||
1. **它有可陈述的职责与单一变化原因**:`UiKit.camp_hud_band_surface()` 的职责语句=「把营地顶栏这块带子按定值铺满宿主并挂上去,返回句柄供调用方读取与断言」;单一变化原因=这块带子的外观要变(以后要求圆角、内缩、换遮挡方式,都只改这一个函数+那枚常量)。它不承载玩法状态、不承载文案、不做判定、不接形参选择;徽章的摆位不由它负责(归顶栏件内那层右对齐容器,T3.3)。
|
||||
2. **集体身份成立**:它服务的是营地顶栏这一块具名带子,不是「所有 ColorRect 铺满动作」的收纳盒。**故意不做**成通用铺面器:若把模态遮罩、页壳底板一并收进来(用户 2026-11-06 否掉的形态),这个单元就从「一块带子的定义」变成「三种不相关意图的并集」——正是拆分判据要拦的形态。
|
||||
3. **「过阈值」只作发现信号**:本批不以任何计数变好看为目标;公开方法数上升是它的代价(见第 13 节例外申报),不是它的动机。
|
||||
4. **拆不出内聚单元即停卡呈报**:若实现期发现该出口必须塞进条件分支才能同时容纳顶栏带与其它用途,说明本卡对「只有这一个消费者」的判断失真——停卡呈报退设计段重议,禁硬并为凑绿。新增单元仅这一个出口函数+一枚常量+一个私有测试套件,无段件/状态件迁出。code-conventions §1.1 与本节互指;实现期若需拆 `camp_hud_band.gd` 或 `main.gd` 才落得下,属卡片未预见的结构动作=L3 呈报,禁为凑绿硬拆。
|
||||
|
||||
---
|
||||
|
||||
## 15. 待裁点(三轮复核产生;Q1 因写卡段自决被驳回而重开过、又因出口的执行形态被再裁一次——全程留痕)
|
||||
|
||||
独立复核(未参与撰写的只读子代理)三轮:轮次 1 打回一次,四条闭合里三条由写卡段按权威文档自决落卡,第四条拆成两案呈用户裁决;轮次 2 抓到「矩形谁建」这条轴属越权自决;轮次 3 抓到收编版四处硬伤并生成 Q4。**当前状态:Q1/Q2/Q3 已裁尽,Q4 因 11-06 用户再裁而消解(不再是一条分叉),本卡零待裁点,开工门=U0(用户批卡)。**
|
||||
|
||||
### Q1|顶栏这块色面从哪来——**已裁(终态=公共件一个具名单参出口,同形旧实现一律不动)**
|
||||
|
||||
事实底座:`ui_assembler.gd:224-230` 的 `overlay_bg()` 做了「新建 ColorRect+铺满锚点+挂上宿主+返回句柄」四步,**没设 mouse_filter**(ColorRect 默认 STOP),外部调用者零(只有该文件自身 `:100` 传 `BG_BRIGHT` 亮底、`:214` 传黑 0.6 遮罩);`ui_kit.gd:486-491` 的私有 `_modal_dim()` 是同一份东西的第二实现(同样不设 mouse_filter,仅 `:511` 一处用);`ui_kit.gd:537` 页壳底板同形但**显式**写 STOP、色为实色、后有石纹/暗角兄弟层;`views/` 目录自建 ColorRect 有 7 个文件 15 处先例;顶栏件 `camp_hud_band.gd:14` 现役已在调 `UiKit`。
|
||||
|
||||
当时并列过的案与代价:甲=顶栏件 preload 装配器复用那一个出口(零重复,但给 `views/` 开一条该目录零先例的引用路径,且该文件头注自陈「接线拓扑唯一归组合根」);乙=顶栏件自建(与 `views/` 现状同构,但重复四行,且色值与铺法分居两处);丙=公共件出一个出口(真「整件外观一处定义」,代价是 `ui_kit` 公开方法 23→24 要修订登记基线)。
|
||||
|
||||
> **裁决史(四次)**
|
||||
> ① **11-05**:用户裁「就按放在公共区域的这一层的方案来实现」。写卡段把它解释成"采纳我当轮给的整条推荐",落成**色值进公共件+矩形留在视图自建**,并在第 12 节写下"不抽公共工厂"的留痕句。
|
||||
> ② **11-06 用户驳回①并重裁**:驳回理由是那条解释本身——"值"和"形"是同一个 UI 物件的两半,拆两处就要求每个改它的人同时记得两处,而"少动文件"不是纪律。**当时落成的重裁结果=丙的整案**:公共件出一个 `band_surface(host, color, pass_input)` 通用出口封装铺面与点击语义,并把 `_modal_dim()`、`overlay_bg()` 连同三处调用点同批收编,据此生成 Q4。
|
||||
> ③ **11-06 写卡段据 Q4 呈「页壳底板收不收」**,用户判该设计整体不成立(原话:「你现在是有非常严重的设计问题」)——出口一旦需要形参承载三种点击语义,它就不再是「一个物件」而是三个意图的并集;会问出 Q4 本身就说明抽象切错了。
|
||||
> ④ **11-06 终裁(现行)**:出口**只服务营地顶栏这块带子**——具名 `camp_hud_band_surface(host)`,**形参只有 host**,色值、α、铺满、穿点击全写死在体内;`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`、`ui_kit.gd:862` 一律**不收编、不动**。已落契约4/5、§2、§3.3、§5、§7、§12、§14、T3.1–T3.3。
|
||||
> ⑤ 流程账:轮次 2 复核当时已把①那条轴标为**待裁**(原话:裁决句字面只覆盖色值轴),写卡段改成"自判+报备"自行闭合——那是越权(规矩:待裁必须问用户、禁代裁),第 16 节轮次 2 相应行按实情登记。
|
||||
|
||||
### Q2|徽章「右位顶格+内缩 12px」用什么机制随位数自适应——**已裁(2026-11-05 用户裁乙·容器右对齐)**
|
||||
|
||||
事实底座:徽章文案是 `"钥石 %d" % v`(`camp_hud_band.gd:22`,格式串单点在 `UiKit` badge 工厂),**宽度随余额位数变**;现值 `position = Vector2(-160, 8)`(`camp_hud_band.gd:16`)把 x 写死——R5 要断言「徽章右缘 == 栏右缘 − 12」,写死偏移在位数变化时必然失位(现值 -160 只对当时那个位数读数成立)。
|
||||
|
||||
| 案 | 做法 | 代价 |
|
||||
|---|---|---|
|
||||
| 甲 | 锚点右钉 + `grow_horizontal = GROW_DIRECTION_BEGIN` + `offset_right = -12`、`offset_top = 0` | 纯声明式、无回调;依赖 Control 生长语义(须开工首步实测读数确认,Godot 4.7 锚点+offset 与 custom_minimum_size 的相互作用我不凭记忆断言) |
|
||||
| 乙 | 栏体内套一个右对齐 `HBoxContainer`(`alignment = END`),徽章作子件由容器摆位 | 与 `UiKit` 既有容器用法同构(弹窗钮行 `confirm_dialog.gd:27-29` 就是 `HBoxContainer` + `alignment`);代价是多一层容器节点,栏体色面与容器的父子关系要重排 |
|
||||
| 丙 | 保持现锚点,接徽章 `size_updated` 信号在回调里重算 x | 改动面最小;代价是多一条运行期回调路径(与「显式触发才变」的顶栏刷新纪律并存,需在卡内申报新观测路径) |
|
||||
|
||||
写卡推荐=**乙**,理由=仓内已有同类摆位先例(容器 `alignment` 摆位,非手算偏移),且 R5 的 x 向判据在容器形态下读数最直白。甲依赖我不肯凭记忆断言的引擎语义(AGENTS.md:不确定的 API 一律核实后再用,核实前不作推荐)、丙新增运行期路径。
|
||||
|
||||
> **裁决(2026-11-05 用户裁·乙 容器右对齐)**:已落 T3.3 与第 12 节,Q2 清零。
|
||||
|
||||
**Q1/Q2 与第三条(闸件缺口是否登记)都已裁完并落卡,本卡零待裁点,开工门=U0。**
|
||||
|
||||
### Q4|页壳底板要不要一并收进同一个出口——**已消解(11-06 用户终裁:不收编,出口只服务顶栏带)**
|
||||
|
||||
留档(非现行分叉):`ui_kit.gd:537` 页壳底板与出口形状相同(新建 ColorRect、赋色、铺满、挂上宿主),差别是它**显式**写 `MOUSE_FILTER_STOP`、色取 `BG_BRIGHT`/`BG_ROOT` 实色,且后面紧跟石纹层与暗角层作兄弟节点——它是「页面底」,不是「遮罩/条带」。`ui_kit.gd:862`(竖卡 18×16 小色块)不同形(手工 position/size、不铺满)。轮次 3 把前者判为待裁并生成 Q4,呈报时并列「不收编(甲)/一并收编(乙)」两案。用户回话否定的是这条呈报的**前提**:出口本来就不该是通用铺面器,所以「第四处收不收」不成立。按终裁④,`ui_assembler.gd:224-230`、`ui_kit.gd:486-491`、`ui_kit.gd:537`、`ui_kit.gd:862` 四处全部原样不动。
|
||||
|
||||
### 其余复核发现的处置(已落卡,非待裁)
|
||||
|
||||
自判与补写两类已按复核者自己落卡的规矩改进行内:契约1 收窄措辞、`_start_adventure()` 函数体行号订正、失败分支终态申报、帧口径、R6 输入来源改读实件、R8 行号与能力行拆正、结构图验收行补 R7/R8、悬空「行为表」引用、L1 引用行号、白名单第 4 条取样口径、`_initialize()` 入口名、新件三查对 tests 不适用、`main.gd:137` 与其余开层点不受契约1 约束的明示、R3 child(2) 硬伤、R5 开营地前置与 await 时机、T3.0 入树读数时机、1280 宽承重链改引 `ui_registry.gd:26`/`:61`、徽章 x 向判据改全局矩形、显隐点 12→13、`views/` 先例 8 文件→7 文件 15 处、步骤号与 R 行口径重编号。(轮次 1 对 R3 给的「升成员供测试直取」处置已在轮次 2 撤回——它抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部,改判为公开子节点序判据。)
|
||||
|
||||
两条闸件面发现**均已登记为工程改动待办**(用户 2026-11-05 口径:登记后本批不管):认锚式字符类不含中文段(`tools/pipeline_gate.py:68`)、层序落位单口的扫描面不含子目录(`game/tests/unit/contracts/source_guard_test.gd:85-99`)。两条都在待办清单在册(改动面=工具代码/状态=待执行),本批不动闸件。
|
||||
|
||||
复核未采信两条:复核者称 `ui_click_probe.gd:679` 是断点写盘句(实为段头注,断点注入在 `:706` 起);称 art-bible:452 宣称的五 token 机检「查无该件」(`tools/palette_check.py:22` 的 `ui_tokens.png` 就是 ui_kit 全量 Color8 token 快照基线,机制存在,只是不逐名比对该表五行——已写进契约4 依据,未采纳其结论)。
|
||||
|
||||
---
|
||||
|
||||
## 16. 复核记录
|
||||
|
||||
复核者身份=未参与本卡撰写的只读子代理(Agent 工具起的独立会话,禁写文件、禁跑闸),形态=只读子代理。轮次 1,发现 22 条,四类去向逐条登记:
|
||||
|
||||
| 轮次 | 复核者身份 | 发现(一句话) | 去向 | 处置结果 |
|
||||
|---|---|---|---|---|
|
||||
| 1 | 只读子代理·未参与撰写 | 契约1「隐藏点只许两处」按字面过宽——全库开始层显隐共 13 处 | 退回上游(复核者判)→ 落卡改自判 | 契约1 收窄为「限开局/续玩两条路径」,13 处逐条列名(`camp_shop_presenter.gd:43`/`:85`、`main.gd:137`/`:194`/`:246`、`main_adventure_flow.gd:145`、`main_debug_flow.gd:88`、`main_overlay_flow.gd:8`/`:17`/`:41`/`:48`/`:95`/`:101`)。复核者的「退回上游」定名不采纳:这是卡内条款措辞过宽,非规范冲突,写卡段可自改 |
|
||||
| 1 | 同上 | `_start_adventure()` 函数体行号错(写 311,实止 308) | 自判 | §3.1 改 `292-308` 并注明 `:311` 是下一函数声明行 |
|
||||
| 1 | 同上 | T1.1 落笔后启动失败分支的开始层终态由可见变隐藏,卡未申报 | 补写 | §3.1 该行补终态申报(致命屏自接管,无菜单可用) |
|
||||
| 1 | 同上 | 大门段 await 只 1 帧,选路停点成形先例是 3 帧 | 补写 | T1.4 补帧口径+最小帧数实测记进实现报告;引 `screen_flow_probe.gd:185-193` |
|
||||
| 1 | 同上 | `btn_row` 是 `_init()` 局部变量,R3「逐位相等」写不出来 | 补写(阻塞) | 当时把 `btn_row` 升为成员并申报受控例外;该处置在轮次 2 撤回(抵触 `architecture.md:168` 视图单元接口与 `:323` 禁直引呈现层实现内部),现行判据=公开子节点序,不新增成员 |
|
||||
| 1 | 同上 | §12 自称「不直取私有」,R3/R4 实际直取私有成员 | 自判 | 同上:先改为受控例外口径,轮次 2 再改为完全不读私有成员(R3 走子节点序、R4 走坐标真点) |
|
||||
| 1 | 同上 | `:702` 真点已消耗 `gate_new`,R4 需重开营地+大门才有左位可点 | 补写 | T2.3 补「前置重开」步与坐标取法 |
|
||||
| 1 | 同上 | L1 引用的 `ui_click_probe.gd:679` 不是旧口径注释;段内直发面在 `:696`;「emit 不算真实入口」属 §8.3 而非契约7 | 自判+**部分不采信** | L1 改引 `:696` 的 `cancelled.emit()` 并注依据=test-conventions §8.3。**但复核者称 `:679` 是断点写盘句,经我回读不成立**——`:679` 确为段头注「大门两分支断言段」,断点注入起 `:706`;另 R8 原引 `:696` 亦错,次钮隐藏断言实为 `:694` |
|
||||
| 1 | 同上 | `ui_assembler.gd` 无 `class_name`,现网一律 preload,`views/` 零先例;卡内 `UiAssembler.overlay_bg(...)` 写法不成立 | 待裁 | 第 15 节 Q1 全案并列(复用装配器/视图内自建/公共件出出口),含三案代价;T3.1 改为「待 Q1 裁决后落」 |
|
||||
| 1 | 同上 | 徽章自高 48 系推导,`panel_flat` 带 `border_w=1`,描边若计入则 50、R5 顶格判据即红 | 补写(复核者自标「这是推测,未跑批」) | T3.0 新增前置读数步(量 `get_combined_minimum_size().y`);≠48 走 L3 第 5 条;§3.3 与 R5 的推导句标为未实测 |
|
||||
| 1 | 同上 | 12px 内缩无可达机制(徽章宽随位数变,现值写死 x 偏移);R5 只断 y 向,x 写错仍绿 | 待裁+补写 | 第 15 节 Q2 三案并列(锚点生长/容器右对齐/size_updated 回调);T3.3 改「待 Q2 裁决后落」;R5 已补 x 向判据(`end.x == 栏 end.x − 12`) |
|
||||
| 1 | 同上 | R6 若把实现的 `Color(UiKit.CAMP_HEADER_BAND, 0.80)` 当输入=oracle 镜像 | 自判 | R6 改为规格侧算式核验(测试内按 `camp.md:41` 写死 `#6E4526`/α0.80),并明写「禁把视图件 color 喂进算式」,依据=test-conventions §8.7 |
|
||||
| 1 | 同上 | §11.4 称 4×8 步长「沿 `terrain_tile_sets_test` 口径」错,该件是全像素扫描 | 自判 | 白名单第 4 条改全像素双层扫描,并声明卡内早先步长口径作废 |
|
||||
| 1 | 同上 | 数学校核:全扫与抽样均色=RGB(223,235,232),与卡内 `#DEEAE7` 差 ≤1/255;叠后对比 4.29:1(卡 4.27)、徽章对天空 1.30:1(卡 1.29) | 自判(读数采纳,容差不改) | 落位带前提经其独立核对(背景层 `Rect2(0,0,1280,800)`、围墙 y=261、大门 y=250 → 0–48 带只含背景);差值落 L2 容差内,不放宽阈值 |
|
||||
| 1 | 同上 | §1 完成定义与 §5 写「七行」,对照表实为 R1–R8 | 自判 | 完成定义改「八行(R1–R8)」(收编版曾扩到 R9,随该版作废回到八行) |
|
||||
| 1 | 同上 | §5 引「第 1 节行为表」,本卡无该表(悬空引用) | 自判 | 改为「七条未改动动线」自足表述 |
|
||||
| 1 | 同上 | §7 结构图 `ui_click_probe`/`system_test` 的验收行列缺 R7/R8 | 补写 | 结构图两行补齐 R1、R2、R4、R7、R8 / R5、R8 |
|
||||
| 1 | 同上 | gdstyle 只扫 core/data/presentation,tests 不在面 → §13 新件三查与 L3 第 4 条对新件无触发路径;套件入口是 `_initialize()` 非 `_init` | 自判 | §13 三查改「对本件不适用」并给依据(`tools/gdstyle_gate.py:45`+头注 `:8`);L3 第 4 条限定为 presentation 面三件;T2.2 入口名改 `_initialize()` 并附先例行号 |
|
||||
| 1 | 同上 | §13 存量一查核过无误(基线 32/23,`main.gd` 公开方法 31+1 静态=32 持平成立) | 自判(确认类) | 无需改动,留档为已核项 |
|
||||
| 1 | 同上 | 契约4 依据「加枚即触五 token 机检表」查无该件 | 自判+**不采信其结论** | 复核者称机检不存在,经我回读:`tools/palette_check.py:22` 的 `ui_tokens.png`=ui_kit 全量 Color8 token 快照基线,机制在——只是不逐名比对该表五行。契约4 依据改写成这个准确口径,仍禁新增 token |
|
||||
| 1 | 同上 | `run_all.gd` 册是硬编码清单,T2.2「接进套件册」不够具体 | 补写 | T2.2 写死条目形态 `{"path": "res://tests/unit/presentation/confirm_dialog_test.gd", "expect": 0}` +行尾注,并指 `run_all.gd:166` 起的清单 |
|
||||
| 1 | 同上 | 总判:按现状不能进实现段,缺四块闭合 | 退回上游(部分) | 四块里①②④已自决落卡;③的 `UiAssembler` 形态与 12px 机制转 Q1/Q2 待裁。另其归入的机检缺口(`pipeline_gate.py:68` `ANCHOR_RE` 不含中文段)已列 §6 末行,去向=用户发令登记 |
|
||||
|
||||
**写卡段对复核结论的两处不采信**(复核者也是读代码,不是跑批):`:679` 段头注的定性、五 token 机检「查无该件」——两处均经我逐行回读推翻,依据写在对应处置格。
|
||||
|
||||
**复核报告落盘**:`reports/复核-营地交互修复批-2026-11-05.md`(轮次 1=22 条,轮次 2=5 条,轮次 3=8 条,同一文件续段登记)。
|
||||
|
||||
## 轮次 2(Q1/Q2 首次裁决落卡后的独立复核)
|
||||
|
||||
复核者身份=第二个未参与撰写的只读子代理(与轮次 1 不同一会话),任务书限定只查五处并须给 `文件:行号` 依据。
|
||||
|
||||
| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 |
|
||||
|---|---|---|---|---|
|
||||
| 2 | 只读子代理·未参与撰写 | **硬伤**:双钮形态下断言「child(1)=维持」必红——次钮在 `open()` 里只置 `visible=false`、不从子节点表移除 | 补写 | R3 与 T2.2 改判 child(2),并配「按钮行子节点数=3」非空前置;依据 `confirm_dialog.gd:47` |
|
||||
| 2 | 同上 | R5 缺前置:营地屏 boot 态为隐,未开营地读不到尺寸 | 补写 | R5 真实入口补「先 `open_camp()` + await 排版帧」;依据 `camp_scene_view.gd:53` |
|
||||
| 2 | 同上 | T3.0 未写取数时机:PanelContainer 的最小尺寸在入树排版后才可信,刚 `new()` 读数不可信 | 补写 | T3.0 改为「入树后经公开句柄读在树的现成徽章实例,await 排版帧再读」;依据 `ui_kit.gd:634-656` |
|
||||
| 2 | 同上 | 确认类(无发现):八处行号全对(`main.gd:211-212`/`:223-224`/`:292-308`/`:194`/`:246`、`ui_kit.gd:640` 带 `border_w=1`、`ui_assembler.gd:100`/`:214`/`:224-230`、`ui_kit.gd:189/190/200`);`to_html(true)` 追加 α 两位、期望串 `6e4526cc` 对;尺寸链 1280×48 可达(CampScreen 满铺+`_hud_slot` 全宽,父链无容器改写);「按类型找第一个 HBoxContainer」不会找错(该树只有一个) | 自判(确认留档) | 无需改卡,核法记档:逐行打开比对+仓内既有用法 `standee_present_values_test.gd:36`(`to_html(false)` 六无 #) |
|
||||
| 2 | 同上 | Q1 的裁决句「按放在公共区域的这一层」字面只覆盖色值落点;「矩形由顶栏件自建、不抽公共工厂」是写卡段对裁决的展开,该轴轮次 1 原属甲/乙案内容 | **待裁(复核者判,事后证明它判对)** | 写卡段当时把去向改成"自判+报备"并自行闭合——**该改判无效**:规矩是待裁必须问用户、禁代裁。11-06 用户驳回拆两半的方案,落点见第 15 节 Q1 裁决史②与④;本行处置因此从"已闭合"改记为"越权闭合→已由用户重裁" |
|
||||
|
||||
**轮次 2 总判**:三处补写已落卡(现行有效);一处确认留档;一处按自判落并向用户报备(该自判后被用户驳回)。
|
||||
|
||||
## 轮次 3(11-06 收编版落卡后的独立复核,第三个未参与撰写的只读子代理)
|
||||
|
||||
复核者身份=第三个只读子代理(与前两轮不同会话,禁写禁跑)。它抓到 6 项:4 项我照改、1 项它判对而我上一轮判断错、1 项转用户裁决。
|
||||
|
||||
| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 |
|
||||
|---|---|---|---|---|
|
||||
| 3 | 第三只读子代理 | 两处旧实现确实没设 mouse_filter、ColorRect 默认即 STOP——出口默认值取 STOP 保旧语义的做法成立 | 自判(确认) | 该事实现行有效,已并入契约5 作「漏设不报错」的旁证(`:540` 显式 STOP、`:864`/`camp_scene_view.gd:145` 显式 IGNORE);「出口默认取 STOP」这一设计随收编版作废 |
|
||||
| 3 | 同上 | 三处既有调用点都是"挂宿主+把返回句柄当父用",收编为转调在层序上等值 | 自判(确认) | 随收编版作废(11-06 终裁不收编),留档 |
|
||||
| 3 | 同上 | `_modal_dim()` 经 `modal_shell()` 扇出 **11 家**模态消费件,R9 只点两处探针不足以覆盖 | 补写(收编版内部) | 随收编版作废;它揭示的事实(该私有件扇出面广)现作为**不收编的理由**并入契约4 与 §5「不做」表 |
|
||||
| 3 | 同上 | **卡内 1280 宽的承重链引错**:宽度不是 tscn 锚点给的,是 `ui_registry.gd:26`/`:61` 的 FULL_RECT 注册撑出;R5 第⑤条还把全局矩形与局部 `position` 混写 | 补写(且推翻我自己) | **现行有效**:R5 改引注册面并把⑤改为 `get_global_rect()`。**同一条我上一轮"纠正"错过**:我曾判 `camp_scene_view.gd` 初始隐在 `:51` 并把复核者的 `:53` 改回去,本轮 grep 复核实为 `:53`,已全文改回 |
|
||||
| 3 | 同上 | R9/T3.2 的收编完成判据算术不成立:`ui_kit.gd` 现有 3 处 ColorRect 自建(`:487`/`:537`/`:862`),"两文件合计=1"必红 | 补写(收编版内部) | 该判据随收编版作废;它抓到的事实(同形写法实为三处,不止两处)保留,现行 §3.3/§12 按三处陈述 |
|
||||
| 3 | 同上 | `:537` 页壳底板与出口同形(铺满+纯色),仅显式 STOP 与色不同——形状同而语义是否同,判为待裁 | **待裁(转用户)** | 当时列进第 15 节 Q4;用户 11-06 回话否定该呈报的前提(出口不该是通用铺面器),Q4 消解,见 Q1 裁决史③④ |
|
||||
| 3 | 同上 | 卡内多处数字与交叉引用过期:显隐点 12→**13**(漏 `main_overlay_flow.gd:101`)、`views/` 自建先例 8 文件→**7 文件 15 处**、§5 写 R1–R8 而 §1 写 R1–R9、§12 三处步骤号、T4.1 目检引用、§14 与 §15 把 tooltip 底算进收编用途(`TIP_BG:190` 是主题色常量,不经这两个函数) | 补写 | 数字类改正(13 处、7 文件 15 处)现行有效;tooltip 底不属于任何用途清单,现行 §3.3 按「亮底/黑 0.6 遮罩/页底/顶栏带」四种意图分述;步骤号与 R 行口径随本轮撤收编重编号一并落定 |
|
||||
|
||||
**写卡段对轮次 3 的不采信项**:无(它唯一被我推翻的是我自己上一轮写错的 `:53`,本轮已按它的正确值改回)。
|
||||
|
||||
**轮次 3 之后的改判**:轮次 3 审的是「收编版」;11-06 用户终裁(Q1 裁决史④)撤掉收编与带形参的通用出口,本卡据此重落成「一个具名单参出口」——该终态须经一轮新的独立复核才算闭合,遂有轮次 4。
|
||||
|
||||
## 轮次 4(11-06 撤收编版落卡后的独立复核,第四个未参与撰写的只读子代理)
|
||||
|
||||
复核者身份=第四个只读子代理(与前三轮不同会话,禁写禁跑,任务书要求逐条给 `文件:行号`)。它报 17 条:2 条硬伤、4 条事实错/一致性问题、2 条补写、5 条确认无发现;**未新增待裁点**(无需用户裁决)。发现与处置:
|
||||
|
||||
| 轮次 | 复核者身份 | 发现(一句话) | 主去向 | 处置结果 |
|
||||
|---|---|---|---|---|
|
||||
| 4 | 第四只读子代理 | **硬伤**:R4 字面「按按钮行实际矩形算左位坐标真点」必打空——该行 `alignment=CENTER`,双钮可见宽 216+12+216=444<壳宽 460,行矩形左位落在 8px 空档上 | 补写 | R4 真实入口改为取按钮行 child(0) 的 `get_global_rect()` 中心,并把算式与依据行号写进格内(`confirm_dialog.gd:29`、`ui_kit.gd:578` `min_w=216.0`、`confirm_dialog.gd:20` 壳宽 460)——**我回读三行确认成立**;T2.3 原本就写的就是 child(0) 中心,两处口径现已统一 |
|
||||
| 4 | 同上 | **硬伤**:T3.4 让 `_smoke_camp_hud_band()` 「接进 `_initialize()`」不成立——`_initialize()` 只调 `_split_tail()`,摘要与 `quit()` 都在尾段,不被 await 的协程段=静默不执行=R5 假绿 | 补写 | T3.4/§7 结构图/§11 第 5 条三处同批改为「接进 `_split_tail()` 的 await 链(`:40-44`)」并写明失败机理与段内自持 instantiate/queue_free 的先例形态——**我回读 `system_test.gd:26-28`/`:35-48`/`:213-217` 确认成立**,`:40` 行尾注本身就记着这条教训 |
|
||||
| 4 | 同上 | 四消费方行号偏一:卡写 `:72`/`:76-78`/`:82`,实为 `:73`/`:79-80`/`:83`(`:129` 对) | 自判 | §3.2 与 R3 两处改正——我逐行打开 `main_overlay_flow.gd:69-83`/`:125-129` 核实 |
|
||||
| 4 | 同上 | R5② 把「营地场景根代码 new」引到 `camp_scene_view.gd:13-14`,该行实为命中区名表 `_HOTSPOT_NAMES`;真正的 new 在 `scenes/camp_screen.gd:13` | 自判 | R5② 改引 `game/presentation/scenes/camp_screen.gd:13-14` 并补 `camp_screen.tscn` 根锚 preset=15 的承重事实——我回读两文件确认 |
|
||||
| 4 | 同上 | R3 括注「`modal_shell()` 产 VBox+HSeparator」不实:HSeparator 是弹窗自造(`confirm_dialog.gd:26`),该壳不产横排 | 自判 | R3 括注改为「产 CenterContainer+PanelContainer+VBox,不产横排;HSeparator 系弹窗自加」——回读 `confirm_dialog.gd:26` 与 `ui_kit.gd:509-525` 确认 |
|
||||
| 4 | 同上 | T3.3/§12 称 `confirm_dialog.gd:27-29` 为「容器右对齐先例」,而 `:29` 实为 `ALIGNMENT_CENTER`——是同机制先例、不是右对齐先例 | 自判 | 两处措辞改正,并明写本件同处取 `ALIGNMENT_END` 才是右对齐(免得实现误认现成右对齐件) |
|
||||
| 4 | 同上 | R6/T3.5 引「§8.4」未冠文档名,卡内 §8 无 8.4 小节,易读作自引 | 自判 | 改为「test-conventions §8.4(正面效力自检)」 |
|
||||
| 4 | 同上 | R4 判据读 `main._confirm_consumer`(私有),与 §12「不读私有成员」的自陈口径不一;但探针面先例在案、封装扫描件射程不含探针 | 自判 | R4 明写主证=`run_scope.is_adventure` 翻转,私有一读注为探针既有先例(`ui_click_probe.gd:272`/`:286` 现役直读),并记扫描射程(`presentation_encapsulation_sweep_test.gd:80-94` 的 `root\._` 规则只套在五个 flow 件上,`main.gd` 那格取 false)——**其路径写法有误**(该件在 `game/tests/unit/presentation/`,不在 `unit/contracts/`),结论经我回读不受影响 |
|
||||
| 4 | 同上 | §14 出口职责句「返回句柄供顶栏件摆放徽章」与 T3.2 实际丢弃返回值、摆位归容器不符 | 自判 | 职责句改为「供调用方读取与断言」,并明写摆位不由该出口负责(归 T3.3 的容器) |
|
||||
| 4 | 同上 | T3.0 只把「徽章自高≠48」归因于描边 1px,漏了第二条成因:徽章内 Label 取 18px 档(`ui_kit.gd:648`→`:438` `FS_SUB=18`),行高若高于 32px 图标则最小高由文字决定 | 补写 | T3.0 与 L3 第 5 条两处成因并列;处置强度不变(实测+≠48 即停机呈报)——回读 `ui_kit.gd:638-656` 确认该 Label 用 FS_SUB |
|
||||
| 4 | 同上 | R2 的判据(`is_adventure`+`level_screen.visible`)不是件1 改动特有的面,改了也可能不红 | 补写 | R2 格内明写「本行是开局终态在位断言,被测改动的特有提交面由 R1 承载;`is_adventure` 既有探针已断(`ui_click_probe.gd:704`)」,两行同批缺一即红 |
|
||||
| 4 | 同上 | 待办清单本条实况仍写「两轮独立复核报告」,现为三轮+本轮 | 补写 | 待办清单实况行随本轮改卡同批更新(写卡段 owns 该条目状态与实况,非实现批动作) |
|
||||
| 4 | 同上 | 收编版残留扫描:无一处把收编/通用出口/color·pass_input 形参/R9/改 `ui_assembler` 写成现行方案;历史叙述均带作废标记;T3.0–T3.7、R1–R8、白名单 1–6、L3 1–5、裁决史①–⑤ 交叉引用零悬空 | 自判(确认留档) | 无需改;核法=全文逐节比对+按符号 grep 残留 |
|
||||
| 4 | 同上 | 行号核对:40 处引用逐行打开无误(`ui_kit.gd` 17 处、`camp_hud_band.gd` 6 处、`confirm_dialog.gd` 6 处、`camp_scene_view.gd` 5 处、`main.gd` 8 处、presenter 2 处、装配链与注册面、两份基线脚本、五份文档锚点) | 自判(确认留档) | 无需改——注意该「无误」不含本表第 3、4 两条(它们正是被推翻的例外) |
|
||||
| 4 | 同上 | 引擎语义:未入树节点调 `set_anchors_preset(FULL_RECT)` 的四边 offset 归零、铺满宿主,与现役 `_modal_dim()`/`overlay_bg()` 同形,R5② 的 1280×48 成立;顶栏件 1280 宽由 `ui_registry.gd:61` 的 FULL_RECT 注册撑出 | 自判(确认) | 无需改判据;按建议在 T3.4 补一句「出口须在 `_init()`(入树前)调用」的时机理由 |
|
||||
| 4 | 同上 | `Color(常量, α)` 的 const 派生在库内大量现役;const 引用同文件 const 的写法 grep 零先例,卡内凭 11-06 临时工程实测(仓库无产物可复验) | 自判(确认,标风险) | 维持卡内实测句,不另设断言;该写法若失配属编译期 fail-fast,不会静默绿 |
|
||||
| 4 | 同上 | 契约面与外部一致性:契约4/契约5/§5 不做行三者无矛盾无空隙;`game/tests/README.md` 两行新增能力名与 §8 能力行列逐字一致;§13 预算实算正确(`ui_kit` 29 个 static func 减 6 个私有=23→24;`main.gd` 32 持平;tests 不在 gdstyle 射程;palette 只匹 `Color8(`) | 自判(确认留档) | 无需改 |
|
||||
|
||||
**写卡段对轮次 4 的不采信项**:1 条——它把 `presentation_encapsulation_sweep_test.gd` 记在 `game/tests/unit/contracts/` 下,实际在 `game/tests/unit/presentation/`;该错的只是路径,其结论(扫描射程不含探针、`root._` 规则只套 flow 件)经我回读 `:80-94` 成立,已按正确路径写进 R4。
|
||||
|
||||
**轮次 4 总判与现状态**:两条硬伤与四处事实错已全部落卡,四条一致性问题与三条补写同批落定,未新增待裁点。**本卡的终态设计(一个具名单参出口、零收编)至此经第四轮独立复核并闭合,零待裁点,开工门=U0(用户批卡)。**
|
||||
|
||||
Loaded 100 of 116 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user